The template for SHUTTLE2

I created my second bootloader SHUTTLE2 which consists of two components: a template and a program that fills the gaps in the template with data. In this post, I describe SHUTTEMP: the template for SHUTTLE2. SHUTTEMP is quite complicated, so much so that I prefer to organize the presentation with paragraphs.

The sequence of the paragraphs follows the order by which you find them when reading the file shuttemp.npp (you find it in the DOWNLOAD AREA).

The binary of SHUTTEMP

Ok, let me put all the code first, and then let me talk you about it.

Fig. A - Binary of SHUTTEMP
Fig. A - Binary of SHUTTEMP

In the very same way, as it was for the original "Space Shuttle", SHUTTLE2 starts at 0x7c00 and relocates itself at 0x7a00. In real terms, the execution starts at 0x7c00 and then jumps at 0x7c7a, but in Fig. A, I drew the code as it looked after relocation, so you have to follow with your eyes from 0x7a7a until 0x7a9b knowing that the execution is indeed occurring from 0x7c7a to 0x7c9b. At this point, the code jumped to 0x0000:7a9c (opcode sequence 0x EA 9C 7A 00 00) and from there on what you see is what really gets executed.
SHUTTEMP is the code template for SHUTTLE2. I coded the template starting at address 0x7a00, so exactly the way you see in the picture. I had to do like this because all jumps and calls had to have the addresses as when the code runs for real. At the same time, you have to imagine that MAKESHUT will manipulate SHUTTEMP (the template) to produce SHUTTLE2 which then is loaded and start execution at 0x7c00, it relocates itself at 0x7a00, jumps at 0x7a9c and continues from there.

Before moving down and looking to the code closer, let me move away from it an talk about some differences between the original "Space Shuttle" and SHUTTLE2. In the time between, I realized a very important point about INT 13H: it manipulates the LBA packet in input to return information when and if things go wrong1. The consequence is that if the very first attempt of LBA read with INT 13H goes wrong, then the other two attempts will generate a bug. In fact, the LBA packet after the first failed read attempt is no longer in the original state as required for correct operation. At this point I had two options: either I wrote a code that safeguards the original LBA packet restoring it at each subsequent attempt, or I gave up trying more than once and I set an error immediately.

512 Bytes is very tight space for all this code and it was already quite a challenge to squeeze the code that much down for the original Space Shuttle, that I thought I couldn't make it to write the additional required code that restores the LBA packet at the beginning of every new INT 13H attempt and stay within the given space. I observed also that the original Space Shuttle was working always fine since then so it must be that all reads went right at the very first attempt otherwise I should have experienced bad failure some now and then. Based on this consideration I decided to give away the tree times attempt approach with INT 13H and move to a simple go/no-go based on a single read attempt. This saved me some Bytes, which I decided to invest in more descriptive error messages. The original Space Shuttle wasn’t giving back on screen the INT 13H returned error code. I didn't make at that time to find the space for the nibble_to_ASCII procedure and all the rest, but since I was going to save Bytes removing the INT 13H loops, I determined myself to find the place for it in the code this time.

Another problematic point in the original Space Shuttle, that I had to solve for SHUTTLE2 to make it generic, is the way the original Space Shuttle handles the two copies of FAT. Looking back at Fig. H of the post "Design and build the space shuttle", you can see that I hardcoded the LBA addresses of the two copies of FAT at 0xFBDC and 0xFBDE. Moreover, I relied on the fact that the two high Bytes that forms the total LBA address were the same for the two copies of the FAT and the ROOT. The two high Bytes were directly hardcoded in the LBA packed for ROOT and FAT at address 0xFBFA and 0xFBFB, meanwhile only the changing two low Bytes were stored separately. In this way, I could squeeze code, but it was just good luck. The total LBA address is four Bytes long2 so it can be perfectly possible that the starting address of FAT1 and the distance between the two FATs is such that the two address differs among them not just in the lower two Bytes, but some carryover in the third Byte needs to be considered as well. In such a case this strategy used with the original Space Shuttle that hard codes the Bytes from third to eighth in the LBA packet and saves the first and second Byte separately, it wouldn’t just work anymore. Furthermore, I hardcoded the use of two copies of FAT, but there could be any number of FAT copies: it can be just one FAT or even tree of four.

Yet another problematic point was the check of the size of SOFTWARE.BIG before start loading it into RAM. The original Space Shuttle checks the information about the size stored in the directory entry. This is just informative but no guarantee that the file is exactly that big. In theory, it can be that this information is wrong (it is almost impossible, but let suppose this error happens) and tells that the file is smaller than the available space in RAM, then the original Space Shuttle starts loading it. While loading the file, the chain of clusters goes above and beyond the RAM limit but the original Space Shuttle keeps loading until it finds the cluster marked with 0xffff. Additionally, if the original Space Shuttle finds any cluster marked with 0x0000 or 0x0001 it keeps doing the job. It doesn't recognize them as the reserved cluster numbers that cannot be used for a read operation. In the same way, if the original Space Shuttle reads any cluster greater than the last maximum cluster, it just tries to load it into RAM. There is a lot of things that I wasn't aware when I wrote the first original Space Shuttle and that just happen to run ok because Windows XP is managing the drive when I write and delete different files and folder from it so that the file system is always consistent by itself, but there is no guarantee that it will always be. In fact, I want to try to write on disk and, despite all efforts I can put in making it good, I will probably make just small mistakes. Small mistakes are the really bad ones because one doesn't see them immediately, but they may leave a slightly corrupted FAT in the drive which is not immediately detected. In this condition, a boot loader like the original Space Shuttle will fail very badly because it relies on the assumption that the file system is good and consistent to do its job. I didn't make this assumption anymore in SHUTTLE2. Instead, I assumed that the drive could be inconsistent and I performed some consistency checks during the load of the file SOFTWARE.BIG in RAM.

The final problem that I addressed is the fact that the original Space Shuttle doesn't check SOFTWARE.BIG at all. As long as a file exists on ROOT directory whose name is "SOFTWARE.BIG" and whose size is lower or equal 622592 Bytes, the original Space Shuttle loads and passes control to it. The consequence is that one can take any file (for instance a bitmap) and as long as the size is ok, just rename it in SOFTWARE.BIG and place in ROOT folder. The original Space Shuttle will load and pass control with unpredictable results when the CPU starts to executes it. This time I introduced a small check. SHUTTLE2 loads SOFTWARE.BIG in RAM but before passing execution control to it, it looks for a signature. If the signature is wrong, SHUTTLE2 retains execution control and terminates with an error message.

With all that been said, it is about time to move closer to the code of SHUTTEMP and this will be a long journey: a very long one. In the beginning, when I decided to rewrite the original Space Shuttle as a concluding exercise for DEBUG.EXE, I thought that it wouldn't take me so long. I was very wrong in my prediction. So take all your quiet, time, paper and pencil that you may need, seat dawn on your desk and let us walk together again.

Undefined BIOS Parameter Block (BPB)

SHUTTEMP: Undefined BIOS Parameter Block
-f 7a00 7cff "X" -a 7a00 137B:7A00 ;234567890123456789012345678901234567890123456789012345678901234567890123456789 137B:7A00 ;-------10--------20--------30--------40--------50--------60--------70-------79 137B:7A00 ;############################################################################## 137B:7A00 ; SHUTTEMP: SHUTtle TEMPlate 137B:7A00 ; 137B:7A00 ; Copyright (C) 2020 - Michele Musci 137B:7A00 ; Distributed under the GNU Affero General Public License version 3. 137B:7A00 ; See https://www.gnu.org/licenses/agpl-3.0.txt 137B:7A00 ; 137B:7A00 ; This is the template for the SHUTTLE2 boot Software. 137B:7A00 ; It creates the boot software, but leaves the BIOS Parameter Block and the 137B:7A00 ; LBA Packets undefined. 137B:7A00 ; As a consequence, this templete must be completed with the specific 137B:7A00 ; information suited by the target Hard Disk Drive that SHUTTLE2 will boot. 137B:7A00 ; 137B:7A00 ; IMPORTANT!!! 137B:7A00 ; This new version of SHUTTLE2 does NOT attempt 3 times a read on HDD, but only 137B:7A00 ; once! 137B:7A00 ; The reason is that during the development of RW_LBA 137B:7A00 ;(See 6.3 - READ / WRITE LBA (A WRAPPER FOR INT13)) I discovered that INT13 137B:7A00 ; manipulates the original LBA packet so that all repeated attempts following 137B:7A00 ; the very first one should reset the LBA to initial condition before repeating 137B:7A00 ; the attempt. Unfortunatelly there is no enoutgh space for this extra code, 137B:7A00 ; so I removed the repetition of attempts. 137B:7A00 ; 137B:7A00 ; Build it with command: 137B:7A00 ; debug < shuttemp.npp > shuttemp_dbg.npp 137B:7A00 ;############################################################################## 137B:7A00 ; 137B:7A00 ;############################################################################## 137B:7A00 ; 137B:7A00 ; DATA 137B:7A00 ; Undefined BIOS PARAMETER BLOCK 137B:7A00 ; 137B:7A00 ;############################################################################## 137B:7A00 ;------------------------------------------------------------------------------ 137B:7A00 ; IMPORTANT!!! 137B:7A00 ; The first 3 bytes of code are considered as integral part of the BPB! 137B:7A00 ;------------------------------------------------------------------------------ 137B:7A00 ; EB 78: Jump over the BIOS Parameter Block 137B:7A00 ; JUMP relocate_and_general_reset: ---> 137B:7A00 ; 90: nop this is one byte for allignment with BPB 137B:7A00 ; 137B:7A00 DB eb 78 90 137B:7A03 ; 137B:7A03 ;------------------------------------------------------------------------------ 137B:7A03 ; BS_OEMName -> [BPB + 0x03] (8 Bytes) 137B:7A03 ; This is the manufacturer description. 137B:7A03 ; “MSWIN4.1” There are many misconceptions about this 137B:7A03 ; field. It is only a name string. Microsoft operating 137B:7A03 ; systems don’t pay any attention to this field. 137B:7A03 ; Some FAT drivers do. This is the reason that 137B:7A03 ; the indicated string, “MSWIN4.1”, is the suggested 137B:7A03 ; setting, because it is the setting least likely 137B:7A03 ; to cause compatibility problems. If you want to put 137B:7A03 ; something else in here, that is your option, but 137B:7A03 ; the result may be that some FAT drivers might not 137B:7A03 ; recognize the volume. Typically this is some indication 137B:7A03 ; of what system formatted the volume. 137B:7A03 ; 137B:7A03 ; Given the description above from Microsoft, I'll keep open the option for 137B:7A03 ; SHUTTLE to use this field if I'll need it. 137B:7A03 DB 'MSWIN4.1' 137B:7A0B ; 137B:7A0B ;------------------------------------------------------------------------------ 137B:7A0B ; BPB_BytsPerSec -> [BPB + 0x0b] (2 Bytes) 137B:7A0B ; Number of Bytes per block (0x200 = 512 bytes). 137B:7A0B DB 3f 3f 137B:7A0D ; 137B:7A0D ;------------------------------------------------------------------------------ 137B:7A0D ; BPB_SecPerClus -> [BPB + 0x0d] (1 Bytes) 137B:7A0D ; Number of blocks per cluster of file. 137B:7A0D DB 3f 137B:7A0E ; 137B:7A0E ;------------------------------------------------------------------------------ 137B:7A0E ; BPB_RsvdSecCnt -> [BPB + 0x0e] (2 Bytes) 137B:7A0E ; Number of reserved blocks: this is the number of blocks 137B:7A0E ; on the disk that are not actualy part of the file 137B:7A0E ; system. In most cases this is 1 being the allowance 137B:7A0E ; for the boot block 137B:7A0E DB 3f 3f 137B:7A10 ; 137B:7A10 ;------------------------------------------------------------------------------ 137B:7A10 ; BPB_NumFATs -> [BPB + 0x10] (1 Bytes) 137B:7A10 ; Number of FAT including copies. 137B:7A10 DB 3f 137B:7A11 ; 137B:7A11 ;------------------------------------------------------------------------------ 137B:7A11 ; BPB_RootEntCnt -> [BPB + 0x11] (2 Bytes) 137B:7A11 ; Number of ROOT directories entries including 137B:7A11 ; unused one (0x200=512). 137B:7A11 DB 3f 3f 137B:7A13 ; 137B:7A13 ;------------------------------------------------------------------------------ 137B:7A13 ; BPB_TotSec16 -> [BPB + 0x13] (2 Bytes) 137B:7A13 ; Total number of blocks in the entire disk. If the size 137B:7A13 ; is larger than 65535 this value is set to 0 and 137B:7A13 ; the true size is stored at offset 0x20 137B:7A13 DB 3f 3f 137B:7A15 ; 137B:7A15 ;------------------------------------------------------------------------------ 137B:7A15 ; BPB_Media -> [BPB + 0x15] (1 Bytes) 137B:7A15 ; Media descriptor (rarely used) 0xf8 means an hard disk 137B:7A15 ; of any capacity. 137B:7A15 DB 3f 137B:7A16 ; 137B:7A16 ;------------------------------------------------------------------------------ 137B:7A16 ; BPB_FATSz16 -> [BPB + 0x16] (2 Bytes) 137B:7A16 ; Number of blocks occupied by one copy of FAT. 137B:7A16 ; It is also the displacement among blocks in the two 137B:7A16 ; copies of FAT. 137B:7A16 DB 3f 3f 137B:7A18 ; 137B:7A18 ;------------------------------------------------------------------------------ 137B:7A18 ; BPB_SecPerTrk -> [BPB + 0x18] (2 Bytes) 137B:7A18 ; Number of blocks per track in CHS addressing mode. 137B:7A18 ; 0x3f = 63 blocks default when disk size is larger than 137B:7A18 ; highest CHS address. 137B:7A18 DB 3f 3f 137B:7A1A ; 137B:7A1A ;------------------------------------------------------------------------------ 137B:7A1A ; BPB_NumHeads -> [BPB + 0x1a] (2 Bytes) 137B:7A1A ; Number of heads (disk surfaces) in CHS addressing mode. 137B:7A1A DB 3f 3f 137B:7A1C ; 137B:7A1C ;------------------------------------------------------------------------------ 137B:7A1C ; BPB_HiddSec -> [BPB + 0x1c] (4 Bytes) 137B:7A1C ; Number of hidden blocks that are on disk all before the 137B:7A1C ; current partition. This is the start LBA address of 137B:7A1C ; the current partition. 137B:7A1C DB 3f 3f 3f 3f 137B:7A20 ; 137B:7A20 ;------------------------------------------------------------------------------ 137B:7A20 ; BPB_TotSec32 -> [BPB + 0x20] (4 Bytes) 137B:7A20 ; Total number of blocks in the entire partition. 137B:7A20 DB 3f 3f 3f 3f 137B:7A24 ; 137B:7A24 ;------------------------------------------------------------------------------ 137B:7A24 ; BS_DrvNum -> [BPB + 0x24] (1 Bytes) 137B:7A24 ; Physical drive number; 0x80 = means primary partition. 137B:7A24 ; This field supports MS-DOS bootstrap and is set to the 137B:7A24 ; INT 0x13 drive number of the media (0x00 for floppy 137B:7A24 ; disks, 0x80 for hard disks). 137B:7A24 ; NOTE: This field is actually operating system specific. 137B:7A24 DB 3f 137B:7A25 ; 137B:7A25 ;------------------------------------------------------------------------------ 137B:7A25 ; BS_Reserved1 -> [BPB + 0x25] (1 Bytes) 137B:7A25 ; Reserved by Microsoft (used by Windows NT). 137B:7A25 ; Code that formats FAT volumes should always set 137B:7A25 ; this byte to 0x00. 137B:7A25 DB 3f 137B:7A26 ; 137B:7A26 ;------------------------------------------------------------------------------ 137B:7A26 ; BS_BootSig -> [BPB + 0x26] (1 Bytes) 137B:7A26 ; Extended boot signature (always 0x29). 137B:7A26 DB 3f 137B:7A27 ; 137B:7A27 ;------------------------------------------------------------------------------ 137B:7A27 ; BS_VolID -> [BPB + 0x27] (4 Bytes) 137B:7A27 ; Volume serial number (used for volume identification). 137B:7A27 DB 3f 3f 3f 3f 137B:7A2B ; 137B:7A2B ;------------------------------------------------------------------------------ 137B:7A2B ; BS_VolLab -> [BPB + 0x2b] (11 Bytes) 137B:7A2B ; This field matches the 11-byte volume label recorded 137B:7A2B ; in the root directory. 137B:7A2B ; NOTE: FAT file system drivers should make sure that 137B:7A2B ; they update this field when the volume label file 137B:7A2B ; in the root directory has its name changed or created. 137B:7A2B ; The setting for this field when there is no volume label 137B:7A2B ; is the string “NO NAME ”. 137B:7A2B ; 137B:7A2B ; Given the description above from Microsoft, I'll keep my finger away from 137B:7A2B ; this field since this may be updated by the OS. 137B:7A2B DB 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 3f 137B:7A36 ; 137B:7A36 ;------------------------------------------------------------------------------ 137B:7A36 ; BS_FilSysType -> [BPB + 0x36] (8 Bytes) 137B:7A36 ; Text ASCII identifier of file system (space padded). 137B:7A36 ; Example: "FAT16 ". 137B:7A36 ; NOTE: Many people think that the string in this field 137B:7A36 ; has something to do with the determination of what type 137B:7A36 ; of FAT —FAT12, FAT16, or FAT32— that the volume has. 137B:7A36 ; This is not true. You will note from its name that this 137B:7A36 ; field is not actually part of the BPB. This string is 137B:7A36 ; informational only and is not used by Microsoft file 137B:7A36 ; system drivers to determine FAT type, because it is 137B:7A36 ; frequently not set correctly or is not present. 137B:7A36 ; This string should be set based on the FAT type though, 137B:7A36 ; because some non-Microsoft FAT file system drivers 137B:7A36 ; do look at it. 137B:7A36 ; 137B:7A36 ; Given the description above from Microsoft, I'll keep open the option for 137B:7A36 ; SHUTTLE2 to use this field if I'll need it. 137B:7A36 ; '12345678' 137B:7A36 DB 'FAT16 ' 137B:7A3E ; 137B:7A3E ;------------------------------------------------------------------------------ 137B:7A3E ; 137B:7A3E ; [...]

The very beginning part of the SHUTTEMP is a jump directly to the entry point and an empty BIOS Parameter Block. I used the Microsoft's specification as a reference in the preparation of the BIOS parameter block. I pre-filled the RAM space in DEBUG.EXE with "X" ( f 7a00 7cff "X") because I used "?" (0x3F) to mark all Bytes of data that needed a fix with MAKESHUT later on. As a result, I could see (more or less) how the things were in memory (see the memory dump following here).

SHUTTEMP: the memory dump
[...] -d 7a00 7cff 137B:7A00 EB 78 90 4D 53 57 49 4E-34 2E 31 3F 3F 3F 3F 3F .x.MSWIN4.1????? 137B:7A10 3F 3F 3F 3F 3F 3F 3F 3F-3F 3F 3F 3F 3F 3F 3F 3F ???????????????? 137B:7A20 3F 3F 3F 3F 3F 3F 3F 3F-3F 3F 3F 3F 3F 3F 3F 3F ???????????????? 137B:7A30 3F 3F 3F 3F 3F 3F 46 41-54 31 36 20 20 20 B4 0E ??????FAT16 .. 137B:7A40 BB 07 00 CD 10 AC 3C 00-75 F4 C3 04 30 3C 39 76 ......<.u...0<9v 137B:7A50 02 04 07 C3 B1 04 D3 E8-D2 E8 E8 EE FF 88 44 01 ..............D. 137B:7A60 88 E0 E8 E6 FF 88 44 00-E8 DA FF BE DB 7B E8 D4 ......D......{.. 137B:7A70 FF 31 C0 CD 16 EA 00 00-FF FF FA 31 DB 8E DB 8E .1.........1.... 137B:7A80 C3 8E D3 BC 00 78 FC BE-00 7C BF 00 7A B9 FF 00 .....x...|..z... 137B:7A90 F3 A5 89 1E FE 7B FB EA-9C 7A 00 00 F8 BE F0 7B .....{...z.....{ 137B:7AA0 B2 80 B4 42 CD 13 BE B6-7B 72 A9 8B 16 11 7A BB ...B....{r....z. 137B:7AB0 00 05 B9 0C 00 89 DF BE-95 7B F3 A6 E3 0B 83 C3 .........{...... 137B:7AC0 20 4A 75 EE BE 95 7B EB-9F 8B 45 0E 8B 2E EA 7B Ju...{...E....{ 137B:7AD0 8B 3E E8 7B 50 2D 02 00-B9 BB AA F7 E1 01 F8 11 .>.{P-.......... 137B:7AE0 EA 89 16 EA 7B A3 E8 7B-F8 BE E0 7B B2 80 B4 42 ....{..{...{...B 137B:7AF0 CD 13 73 06 BE C2 7B E9-5A FF B8 BB AA 01 06 E6 ..s...{.Z....... 137B:7B00 7B 58 BB 02 00 B9 00 70-F7 E3 F7 F1 50 52 3A 06 {X.....p....PR:. 137B:7B10 DD 7B 74 3A 8A 26 F2 7B-F6 E4 89 C3 B9 BB AA E3 .{t:.&.{........ 137B:7B20 27 49 A1 16 7A F7 E1 01-D8 83 D2 00 05 BB AA 81 'I..z........... 137B:7B30 D2 BB AA A3 F8 7B 89 16-FA 7B F8 BE F0 7B B2 80 .....{...{...{.. 137B:7B40 B4 42 CD 13 73 08 EB D7-BE CE 7B E9 06 FF 5B 58 .B..s.....{...[X 137B:7B50 A2 DD 7B 8B 87 00 05 3D-FF FF 74 19 3D 02 00 72 ..{....=..t.=..r 137B:7B60 21 3D BB AA 77 1C FF 0E-DE 7B 74 03 E9 65 FF BE !=..w....{t..e.. 137B:7B70 AB 7B E9 F3 FE B9 09 00-BF 00 7C BE 50 7B F3 A7 .{........|.P{.. 137B:7B80 E3 09 BE 91 7B 8C 5C 0F-E9 DD FE E9 82 00 3F 3F ....{.\.......?? 137B:7B90 3F 42 61 64 20 53 4F 46-54 57 41 52 45 42 49 47 ?Bad SOFTWAREBIG 137B:7BA0 20 6E 6F 74 20 66 6F 75-6E 64 00 46 69 6C 65 20 not found.File 137B:7BB0 74 6F 6F 20 62 69 67 00-68 3A 20 42 61 64 20 52 too big.h: Bad R 137B:7BC0 4F 4F 54 00 68 3A 20 42-61 64 20 44 41 54 41 00 OOT.h: Bad DATA. 137B:7BD0 68 3A 20 42 61 64 20 46-41 54 00 2E 00 FF 3F 3F h: Bad FAT....?? 137B:7BE0 10 00 3F 3F 00 00 C0 07-3F 3F 3F 3F 00 00 00 00 ..??....????.... 137B:7BF0 10 00 3F 3F 00 05 00 00-3F 3F 3F 3F 00 00 55 AA ..??....????..U. 137B:7C00 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7C10 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7C20 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7C30 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7C40 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7C50 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7C60 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7C70 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7C80 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7C90 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7CA0 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7CB0 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7CC0 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7CD0 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7CE0 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 137B:7CF0 58 58 58 58 58 58 58 58-58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX - [...]

Local procedures: Show String and Nibble to ASCII

SHUTTEMP local procedures: Show String and Nibble to ASCII
[...] 137B:7A3E ; 137B:7A3E ;############################################################################## 137B:7A3E ; 137B:7A3E ; LOCAL PROCEDURES 137B:7A3E ; 137B:7A3E ;############################################################################## 137B:7A3E ;------------------------------------------------------------------------------ 137B:7A3E ; 137B:7A3E ; Local procedure SHOW_STR 137B:7A3E ; 137B:7A3E ; IMPORTANT!!!! 137B:7A3E ; In order to save bytes we don't preserve any used registers (such as AX and 137B:7A3E ; BX). This is ok because we call this function just before termination 137B:7A3E ; of code so it doesn't really matter... 137B:7A3E ; 137B:7A3E ; IMPORTANT!!!! 137B:7A3E ; In order to save bytes and avoid one extra jump, the entry point of the 137B:7A3E ; procedure is just in the middle of the loop, rather that at the beginning of 137B:7A3E ; the loop. In this way we make the behaviour of the first loop slightly 137B:7A3E ; different from the standard loop. 137B:7A3E ;------------------------------------------------------------------------------ 137B:7A3E ;--------------------------------------- 137B:7A3E ; standard setup for displaying 137B:7A3E ; characters on the screen 137B:7A3E ;--------------------------------------- 137B:7A3E ; 137B:7A3E ; one_char_on_screen: <--- 137B:7A3E ; 137B:7A3E mov ah, 0e ; int10/ah = 0e : video teletype out 137B:7A40 ; al = character to write 137B:7A40 mov bx, 7 ; bh = page number 137B:7A43 ; bl = foreground color (graphics mode only) 137B:7A43 ; int10 returns nothing 137B:7A43 int 10 ; 137B:7A45 ; 137B:7A45 ; 137B:7A45 ; SHOW_STR: <--- ENTRY POINT is HERE!!! 137B:7A45 ; 137B:7A45 lodsb ; AL = DS:[SI]; SI = SI + 1 137B:7A46 cmp al, 00 ; flags = AL - 0x 00 137B:7A48 ; Unsigned comparison: 137B:7A48 jne 7a3e ; JumpNotEqual one_char_on_screen: ---> 137B:7A4A ; 137B:7A4A ;------------------------------------------------------------------------------ 137B:7A4A ; END of local procedure SHOW_STR 137B:7A4A ;------------------------------------------------------------------------------ 137B:7A4A ret 137B:7A4B ; 137B:7A4B ; 137B:7A4B ; 137B:7A4B ; 137B:7A4B ;------------------------------------------------------------------------------ 137B:7A4B ; 137B:7A4B ; Local procedure NIBBLE_ASCII 137B:7A4B ; convert AL into ASCII 137B:7A4B ; 137B:7A4B ;------------------------------------------------------------------------------ 137B:7A4B ; 137B:7A4B ; NIBBLE_ASCII: <--- 137B:7A4B ; 137B:7A4B ADD AL, 30 ; AL get increased by the ascii value for '0' 137B:7A4D ; 0x30 = '0' ... 0x39 = '9' 137B:7A4D ; 137B:7A4D ; 137B:7A4D ;--------------------------------------- 137B:7A4D ; IF (AL > 0x39) 137B:7A4D ;--------------------------------------- 137B:7A4D CMP AL, 39 ; flag = AL - 0x39 137B:7A4F ; 0x39 = '9' in ascii, so if AL is now anywhere 137B:7A4F ; above 0x39 then we are in hex range from 0xA 137B:7A4F ; to 0xF. If so we have to add 0x07 137B:7A4F ; to the calculation for conversion. 137B:7A4F ; Unsigned comparison: 137B:7A4F JBE 7a53 ; JumpBelowOrEqual endif_AL: ---> 137B:7A51 ; 137B:7A51 ; 137B:7A51 ;--------------------------------------- 137B:7A51 ; THEN...IF (AL > 0x39) 137B:7A51 ;--------------------------------------- 137B:7A51 ADD AL, 7 ; 137B:7A53 ; 137B:7A53 ; 137B:7A53 ;--------------------------------------- 137B:7A53 ; END...IF (AL > 0x39) 137B:7A53 ;--------------------------------------- 137B:7A53 ; 137B:7A53 ;endif_AL: <--- 137B:7A53 ; 137B:7A53 ;------------------------------------------------------------------------------ 137B:7A53 ; END of local procedure NIBBLE_ASCII 137B:7A53 ;------------------------------------------------------------------------------ 137B:7A53 ret 137B:7A54 ; 137B:7A54 ; 137B:7A54 ; [...]

Immediately after the undefined BIOS parameter block, I placed two service procedures: Show String and nibble_to_ASCII. I reduced these two as much as I could and they became a kind of special version here. These procedures didn't care preserving registers because they got active, only if something went wrong, so SHUTTLE2 was just going to display the error message and terminate. Additionally, I did something with this version of Show String that cannot be done with a high-level language: I entered the loop for the first time just in the middle, because I took advantage of a slightly different behaviour in the first loop, from any other generic following loop. So I saved one jump instruction with this trick (save one instruction means saving Bytes). You may want to compare this implementation with show_string_II to see the differences among them.

Terminates with an error

SHUTTEMP: set error code and exit
[...] 137B:7A54 ;############################################################################## 137B:7A54 ; 137B:7A54 ; CODE 137B:7A54 ; 137B:7A54 ;############################################################################## 137B:7A54 ; 137B:7A54 ;############################################################################## 137B:7A54 ; SET ERROR CODE AND EXIT 137B:7A54 ;############################################################################## 137B:7A54 ; 137B:7A54 ; set_code_exit: <--- 137B:7A54 ; 137B:7A54 ;------------------------------------------------------------------------------ 137B:7A54 ; Convert the INT13 error code from AH in ASCII and then write into memory. 137B:7A54 ; 137B:7A54 ; IMPORTANT!!! 137B:7A54 ; We partially overwrite the string that preceed the one of interest 137B:7A54 ; pointed by SI. 137B:7A54 ; Example: 137B:7A54 ; DB 'File too larg' 137B:7A54 ; DB 'e' 00 'h: Bad ROOT' 00 137B:7A54 ; becomes 137B:7A54 ; DB 'File too larg' 137B:7A54 ; DB 'A7h: Bad ROOT' 00 137B:7A54 ; In this way we save bytes of string in memory. 137B:7A54 ; 137B:7A54 ; We expect the following situation in registers: 137B:7A54 ; AH = INT13 error code 137B:7A54 ; SI = pointer to error message. 137B:7A54 ;------------------------------------------------------------------------------ 137B:7A54 ;------------------------------------------------------------------------------ 137B:7A54 ; Separate the nibbles of AH in AH and AL. 137B:7A54 ;------------------------------------------------------------------------------ 137B:7A54 MOV CL, 04 ; Set the number of bits to shift 137B:7A56 SHR AX, CL ; Convert AX = a7xx to AX = 0a7x 137B:7A58 SHR AL, CL ; Convert AX = 0a7x to AX = 0a07 137B:7A5A ; 137B:7A5A ; 137B:7A5A ;------------------------------------------------------------------------------ 137B:7A5A ; Convert and write low nibble 137B:7A5A ;------------------------------------------------------------------------------ 137B:7A5A CALL 7a4b ; CALL NIBBLE_ASCII 137B:7A5D MOV [SI + 1], AL ; 137B:7A60 ; 137B:7A60 ; 137B:7A60 ;------------------------------------------------------------------------------ 137B:7A60 ; Convert and write high nibble 137B:7A60 ;------------------------------------------------------------------------------ 137B:7A60 MOV AL, AH ; AL = AH 137B:7A62 CALL 7a4b ; CALL NIBBLE_ASCII 137B:7A65 MOV [SI + 0], AL ; 137B:7A68 ; 137B:7A68 ; 137B:7A68 ;############################################################################## 137B:7A68 ; ERROR EXIT 137B:7A68 ;############################################################################## 137B:7A68 ; 137B:7A68 ; error_exit: <--- 137B:7A68 ; 137B:7A68 CALL 7A45 ; CALL SHOW_STR 137B:7A6B MOV SI, 7BDB ; SI -> "." 137B:7A6E CALL 7A45 ; CALL SHOW_STR 137B:7A71 XOR AX, AX ; int 16/ah = 00 : keyboard get keystroke 137B:7A73 INT 16 ; returns: ah = BIOS scan code 137B:7A75 ; al = ASCII character 137B:7A75 ; 137B:7A75 JMP FFFF:0000 ; Total system reset since, sometimes, 137B:7A7A ; INT19 hangs... 137B:7A7A ; [...]

After the local procedures, I placed the exit sequence that got active in case of error. Notice that this is an exit sequence, so we get here with a "JUMP" and not with a "CALL". Additionally, I created two different entry points instead of one: the topmost is marked by the label "set_code_exit:" at address 0x7A5A and the lower one by the label "error_exit:" at address 0x7A68. The idea was that in case of errors caused by a bad execution of INT 13H, I could enter in the topmost address ("set_code_exit:") in order to convert the error code in AH into an ASCII readable form. Immediately after that, the execution fell down to "error_exit:" which displayed the message and terminated the program. In case the error was a different one, with no need for manipulation of the output string, I could have jumped and entered immediately at "error_exit:".

I want to tell you something about the line at address 0x7A75. In the past, with the original Space Shuttle, I used to terminate with an INT 19H and it was always working. During initial testing with SHUTTLE2 it wasn't. I had a hard time to figure it out why until I looked at the "Ralf Brown Interrupt List", and read that INT 19H can hang the PC without proper rebooting. For this reason, I decided to change it from 0xCD 19 (invoke INT 19H) to 0xEA 00 00 FF FF (far jump to the absolute entry point in BIOS after CPU power on) even if it took 3 Bytes more of code (and I was economizing on Bytes as much as I possibly could so it was a hard decision to take).

Talking about economizing on Bytes, I want to anticipate a little bit the way I prepared the strings for messages. Usually, when I don't have size constrain I would write something like: "INT 13H error while reading DATA from disk with error code: 0x??" 0x0D 0x0A 0x00. Then I would substitute the two Bytes of "??" with the error code in AH and use SHOW_STR to display the message. But this time I had to squeeze as much juice as I possibly could from the 512 Bytes at my disposal, so I sacrificed eloquence and made the messages almost cryptic: “??h: Bad DATA” 0x00. This time the INT 13H error code is placed just in front of the message. The "h" is the other possibility to highlight that the error number is in hexadecimal but "h" at the end is one Byte shorter than "0x" in front of error code. Finally two Bytes of uninitialized data "??" was a space luxury that I couldn't afford so that these Bytes actually were still conceptually there even if they overlapped in address space with other valuable Bytes so that they disappeared (so to say) until they really needed to be there.

Fig. B - Overlapping messages in RAM
Fig. B - Overlapping messages in RAM

At address 0x7BC0 you see the termination of the previous string and the continuation in the string "??h: Bad DATA". The previous message will terminate (if used) at 0x7BC3 because of the 0x00 which tells to SHOW_STR that it has to stop. Since SHUTTLE2 displays only one message and then terminates, there is mutual exclusion, so I imagined that the message which was the following started at 0x7BC2 where I overwrote the virtual "??" ( real "T" and 0x00) with the actual error code in AH. For this reason, you see towards the end of the code that I defined the string starting strangely. What I was really looking for was the position of the virtual "??" which are kept hidden by the overlap.

SHUTTEMP: messages in RAM
[...] 137B:7B91 ; MESSAGES: 137B:7B91 DB 'Bad ' 137B:7B95 DB 'SOFTWAREBIG' 137B:7BA0 DB ' not found' 00 137B:7BAB DB 'File too bi' 137B:7BB6 DB 'g' 00 'h: Bad ROO' 137B:7BC2 DB 'T' 00 'h: Bad DAT' 137B:7BCE DB 'A' 00 'h: Bad FAT' 00 137B:7BDB DB '.' 00 137B:7BDD ; [...]

I know, that this may sound crazy, but I couldn't use more than 512 Bytes in total, so in condition with scarce resources, fantasy helps.

The MAIN entry point

SHUTTEMP: MAIN entry point
[...] 137B:7A7A ;############################################################################## 137B:7A7A ; 137B:7A7A ; MAIN ENTRY POINT 137B:7A7A ; 137B:7A7A ;############################################################################## 137B:7A7A ;------------------------------------------------------------------------------ 137B:7A7A ; relocate_and_general_reset: 137B:7A7A ; relocates the code from 0x7c00 to 0x7a00 in RAM and resets all segments 137B:7A7A ; registers to the desired initial value. 137B:7A7A ;------------------------------------------------------------------------------ 137B:7A7A ; 137B:7A7A ; relocate_and_general_reset: <--- 137B:7A7A ; 137B:7A7A CLI ; Clear interrupts flag. Disable interrupts during 137B:7A7B XOR BX, BX ; reset of segments. 137B:7A7D MOV DS, BX ; 137B:7A7F MOV ES, BX ; 137B:7A81 MOV SS, BX ; 137B:7A83 MOV SP, 7800 ; 137B:7A86 ; 137B:7A86 ; 137B:7A86 CLD ; Clear dir. flag -> make auto increment SI, DI. 137B:7A87 MOV SI, 7c00 ; DS:[SI] source of copy. 137B:7A8A MOV DI, 7a00 ; ES:[DI] destination of copy. 137B:7A8D MOV CX, 00ff ; Total number of WORDS to be copied. 137B:7A90 REP ; Repeat the following sequence of operations: 137B:7A91 MOVSW ; 1. CX == 0 ? --> if yes terminate. 137B:7A92 ; 2. CX = CX - 1 137B:7A92 ; 3. ES:[DI] = DS:[SI] word by word. 137B:7A92 ; 4. SI = SI + 2 and DI = DI + 2 (word operation). 137B:7A92 ; 137B:7A92 MOV [7bfe], BX ; Overwrite the BOOT signature to 0x0000 in the 137B:7A96 ; relocated copy of SHUTTLE in RAM. 137B:7A96 STI ; Set interrupts flag. Re-enable interrupts. 137B:7A97 ; 137B:7A97 ; 137B:7A97 ; END of general reset. 137B:7A97 JMP 0000:7a9c ; Final reset of CS:IP after copy of SHUTTLE at 137B:7A9C ; target position in RAM. 137B:7A9C ; JUMP load_ROOT: ---> 137B:7A9C ; [...]

Finally, I show you the MAIN entry point: the destination of the very first jump from address 0x7a00 (well when it really runs in RAM it is still at 0x7c00 because relocation is about to happen but not yet done). Here the code is quite similar to the previous version of the original Space Shuttle. It relocates all the 512 Bytes, then it sets 0x0000 to overwrite the boot signature and finally jumps to the reallocated code.

Load the ROOT in RAM

SHUTTEMP: load the ROOT in RAM and search for SOFTWARE.BIG
[...] 137B:7A9C ;------------------------------------------------------------------------------ 137B:7A9C ; Transfer ROOT in RAM 137B:7A9C ; The LBA Packet is pre initialized by MAKESHUT for ROOT. 137B:7A9C ;------------------------------------------------------------------------------ 137B:7A9C ; 137B:7A9C ; load_ROOT: <--- 137B:7A9C ; 137B:7A9C CLC ; Clear carry flag before invocation of INT13 137B:7A9D ; to be sure that CF was zero before INT13. 137B:7A9D MOV SI, 7BF0 ; SI -> LBA packet for ROOT and FAT. 137B:7AA0 ; The packet is actually pre-initialized for ROOT. 137B:7AA0 MOV DL, 80 ; DL = 0x80 (first HDD). 137B:7AA2 MOV AH, 42 ; AH = 0x42 (READ with LBA). 137B:7AA4 INT 13 ; 137B:7AA6 MOV SI, 7BB6 ; "Bad ROOT" 137B:7AA9 JC 7A54 ; JumpCarry set_code_exit: ---> 137B:7AAB ; 137B:7AAB ; 137B:7AAB MOV DX, [7a00 + 11] ; DX = BPB_RootEntCnt 137B:7AAF ; -> [BPB + 0x11] (2 Bytes) 137B:7AAF ; Number of ROOT directories entries including 137B:7AAF ; unused one (example: 0x200=512). 137B:7AAF ; This value is validated by MAKESHUT and then 137B:7AAF ; injected at [BPB + 0x11] 137B:7AAF ; 137B:7AAF MOV BX, 500 ; BX = 0x0500 start of BUFFER in RAM. 137B:7AB2 ; 137B:7AB2 ; 137B:7AB2 ;------------------------------------------------------------------------------ 137B:7AB2 ; Search ROOT directory for file "SOFTWARE.BIG" 137B:7AB2 ;------------------------------------------------------------------------------ 137B:7AB2 ; 137B:7AB2 ; next_element: <--- 137B:7AB2 ; 137B:7AB2 MOV CX, 0c ; CX = 0x0c (12 decimal) 137B:7AB5 ; = length of file name(11) + 1. 137B:7AB5 MOV DI, BX ; Current ROOT entry in RAM 137B:7AB7 MOV SI, 7B95 ; SI = "SOFTWAREBIG". 137B:7ABA ; File name to search. 137B:7ABA ; 137B:7ABA REPE ; Repeat the following sequence of operations: 137B:7ABB CMPSB ; 1. CX == 0 ? --> if yes terminate. 137B:7ABC ; 2. CX = CX - 1 137B:7ABC ; 3. flags = ES:[DI] - DS:[SI] byte by byte. 137B:7ABC ; 4. SI = SI + 1 and DI = DI + 1 (byte operation). 137B:7ABC ; 5. REPE -> ZeroFlag = 0 ? --> if yes terminate. 137B:7ABC ; 137B:7ABC JCXZ 7AC9 ; JumpCXZero Read_DATA: ---> 137B:7ABE ; 137B:7ABE ADD BX, 20 ; Increase BX to point next directory entry. 137B:7AC1 DEC DX ; Decrease total count of DIR_ENTRIES 137B:7AC2 JNZ 7AB2 ; JumpNotZero next_element: ---> 137B:7AC4 ; 137B:7AC4 ; 137B:7AC4 ;------------------------------------------------------------------------------ 137B:7AC4 ; Display "SOFTWAREBIG not found": 137B:7AC4 ;------------------------------------------------------------------------------ 137B:7AC4 MOV SI, 7B95 ; "SOFTWAREBIG not found" 137B:7AC7 JMP 7A68 ; JUMP error_exit: ---> 137B:7AC9 ; 137B:7AC9 ; [...]

Loading the ROOT in RAM was a problem that I needed to solve if I wanted to make SHUTTLE2 generic. The way I solved it was in the next piece of code MAKESHUT which took care that the total number of directory entries in ROOT was smaller or equal to the number that would fit in the read buffer in RAM from 0x0500 to 0x74ff.
In 99% of the cases, there are 512 directory entries for a total of 512 x 32 byes for each entry = 16 KB which fits all in the 28 KB available (have a look again at Fig. B of the post "Design and build the space shuttle"). According to the Microsoft's specification, there could even be more entries that just 512 in the ROOT directory so I checked with MAKESHUT that there were no more than 896 directory entries in ROOT so that ROOT fitted always inside the buffer in RAM. If a FAT16 partition had more than 896 entries MAKESHUT stops and doesn't create SHUTTLE2 starting from SHUTTEMP. Once I knew for sure that the partition contained a number of ROOT entries that SHUTTLE2 could manage I did the rest of the job. Note at address 0x7AAB that I didn't hardcoded 512 directory entries in ROOT (as it was with the previous original Space Shuttle), but I got the real value from the BIOS parameter block. The code searched for "SOFTWAREBIG" and if not found it falled down to the error detection and terminated. You see at address 0x7AC7 the jump to error_exit: and at address 0x7AA9 the jump to set_code_exit:, in fact, I distinguished among the two different cases here.

Load the SOFTWARE.BIG in RAM

This is going to be completely different from the previous version of the original Space Shuttle. Let us look again at Fig. A: you see in purple a code that starts at 0x7AC9 and terminates at 0x7B6E. In between, there is another block of code that I marked in grey (from 0x7B14 to 0x7B4D) which serves the same purpose of loading SOFTWARE.BIG in RAM but it takes care of one special aspect (so I marked it in a different colour). Altogether you have to look at the purple-grey block of code because this is the one that loads SOFTWARE.BIG in RAM. The CPU enters in the first purple block (at address 0x7AC9 with label Read_DATA:) when the search of ROOT directory could find "SOFTWARE.BIG". This time I didn't perform any check of size but started immediately with loading the SOFTWARE.BIG in RAM cluster after cluster. The final part of the purple code (address 0x7B4E with label Get_next_cluster:) controls the remaining space available in RAM before loading the next cluster and looping back to Read_DATA:. In this way SHUTTLE2 doesn't rely on the information stored in the directory entry (which may be wrong) but it keeps trace of all load steps and it stops either if it finds the last cluster in the chain (0xFFFF), or if there is no more RAM available to keep loading the file from disk. At the same time, the last part of purple code (the one at entry point Get_next_cluster:) checks that the chain of clusters is healthy, meaning that no prohibited cluster numbers are read (such as 0x0000, 0x0001, or grater then LAST_CLS_DSK). The machinery goes like this: the top purple block (starting at Read_DATA:) reads the data from disk to RAM pointed by the current cluster. At this point if the FAT page contained in the RAM buffer from 0x0500 to 0x74ff contains already the cluster where to read the next cluster from (IF REQUIRED_FAT_PAGE == IS_FAT_PAGE_IN_RAM) it jumps over the grey block directly to the following purple block at Get_next_cluster: otherwise the code just continues execution falling in the grey code which updates the FAT image in RAM in such a way that the final part of the purple code can read indeed which is the next cluster in chain. The one Byte variable IS_FAT_PAGE_IN_RAM at address 0x7BDD stores the information about which page of FAT is currently loaded into RAM. SHUTTEMP is pre-initialized with this variable set to 0xFF in such a way that by the very first time the required page is different from the current page in RAM and this forces an upload of a FAT page in RAM right at the beginning.

There is one special thing to mention here. Here and there you see the word 0xBBAA marked inside black rectangular boxes (for instance the one at address 0x7AD9). All these words are undetermined in the template SHUTTEMP and get calculated and injected inside the code when MAKESHUT creates SHUTTLE2 based on the real BIOS Parameter Block of the partition. At the end SHUTTLE2 has a lot of hardcoded constants as it was with the first version of the original Space Shuttle. I didn't manage to find room for the necessary Bytes of code that calculates these constants starting from the BIOS Parameter Block.
So the workflow now is the following one.

  • SHUTTEMP is coded only once and forever.
  • MAKESHUT is coded only once and forever.
  • The BIOS Parameter Block is determined once the partition is created and formatted and then it remains in this way until the partition is formatted again.
  • MAKESHUT takes the SHUTTEMP and the BIOS Parameter Block and builds the real SHUTTLE2 which is not generic but customized for this very partition.

In the end, I am still happy because two pieces (SHUTTEMP and MAKESHUT) out of the tree (the last one being the BIOS Parameter Block) are constant and I can produce in few seconds as many SHUTTLE2 as I need every time the partition gets newly formatted. There is more to say about the special words 0xBBAA inside the black rectangular boxes, and I am going to explain it soon, but now it is time to give the code a closer look.

SHUTTEMP: Read_DATA: (the first purple block)
[...] 137B:7AC9 ;------------------------------------------------------------------------------ 137B:7AC9 ; Read the complete file in RAM. 137B:7AC9 ; 137B:7AC9 ; IMPORTANT!!!! 137B:7AC9 ; We do not check the size of file before loading it into RAM, but we use a 137B:7AC9 ; decresing counter that terminates the transfer of file into RAM even if 137B:7AC9 ; last cluster (0xffff) is not reached. A message informs that the transfer 137B:7AC9 ; was terminated by the counter and not by last cluster. 137B:7AC9 ;------------------------------------------------------------------------------ 137B:7AC9 ; 137B:7AC9 ; Read_DATA: <--- 137B:7AC9 ; 137B:7AC9 MOV AX, [DI + 0e] ; AX = DIR_CLS_LO, start cluster of file. 137B:7ACC ; DIR_CLS_HI::DIR_CLS_LO on FAT32... 137B:7ACC ; ...just DIR_CLS_LO on FAT16. 137B:7ACC ; 137B:7ACC MOV BP, [7BE8 + 2] ; BP::DI = = ABS_LBA_DATA_START (04 Bytes) 137B:7AD0 MOV DI, [7BE8] ; IMPORTANT!!! 137B:7AD4 ; Remember that this field contains ABS_LBA_CLS_2 137B:7AD4 ; (absolute LBA start of Cluster 2) in the 137B:7AD4 ; initial condition so that: 137B:7AD4 ; BP::DI = the beginning of data on disk from now 137B:7AD4 ; on. 137B:7AD4 ; 137B:7AD4 ; Read_DATA_CLS_NOW: <--- 137B:7AD4 ; 137B:7AD4 PUSH AX ; Save a copy of CLS_NOW (AX) on stack. 137B:7AD5 SUB AX, 02 ; AX = AX - 2 because cluster count starts at 137B:7AD8 ; cluster 2. 137B:7AD8 ; Cluster must be in AX because this is an 137B:7AD8 ; implicit (fix) operand of MUL. 137B:7AD8 ;----------------------------- 137B:7AD8 ; IMPORTANT!!! 137B:7AD8 ; The byte definition that follows is equivalent to: 137B:7AD8 ; MOV CX, aabb ; CX = BPB_SecPerClus. 137B:7AD8 ; 137B:7AD8 ; where MAKESHUT substitutes the 0xaabb with the real number calculated with 137B:7AD8 ; the actual setting of the partition. We choose 0xaabb in order to make 137B:7AD8 ; VISIBLE the byte ordering sequence in code (little endian). 137B:7AD8 ; 137B:7AD8 ; IMPORTANT!!! 137B:7AD8 ; code injection with MAKESHUT (MOV CX, aabb) is 3 bytes shorter than the 137B:7AD8 ; original implementation with: 137B:7AD8 ; XOR CX, CX ; 137B:7AD8 ; MOV CL, [7a00 + 0d] ; CX = BPB_SecPerClus 137B:7AD8 ; -> [BPB + 0x0d] (1 Bytes) 137B:7AD8 ; 137B:7AD8 ;----------------------------- 137B:7AD8 DB b9 137B:7AD9 DB bb aa 137B:7ADB ; 137B:7ADB ; 137B:7ADB MUL CX ; DX::AX = AX*CX -> REL_LBA 137B:7ADD ; relative LBA starting from cluster 2. 137B:7ADD ; 137B:7ADD ADD AX, DI ; ABS_LBA = REL_LBA + ABS_LBA_CLS_2 137B:7ADF ADC DX, BP ; DX::AX = DX::AX + BP::DI 137B:7AE1 ; ABS_LBA_CLS_2 (04 Bytes) 137B:7AE1 ; Absolute LBA start of Cluster 2. This is 137B:7AE1 ; the beginning of data on disk. 137B:7AE1 ; Now DX::AX is the absolute LBA address of 137B:7AE1 ; desired cluster on disk. 137B:7AE1 ; 137B:7AE1 MOV [7BE8 + 2], DX ; ABS_LBA_DATA_START = DX::AX 137B:7AE5 MOV [7BE8], AX ; Store the calculated value in the LBA address 137B:7AE8 ; packet for DATA. 137B:7AE8 ; 137B:7AE8 ;------------------------------------------------------------------------------ 137B:7AE8 ; read one cluster in RAM: 137B:7AE8 ;------------------------------------------------------------------------------ 137B:7AE8 CLC ; Clear carry flag before invocation of INT13 137B:7AE9 ; to be sure that CF was zero before INT13. 137B:7AE9 MOV SI, 7BE0 ; SI -> LBA packet for DATA. 137B:7AEC ; The packet is partially pre-initialized 137B:7AEC ; for DATA. 137B:7AEC MOV DL, 80 ; DL = 0x80 (first HDD). 137B:7AEE MOV AH, 42 ; AH = 0x42 (READ with LBA). 137B:7AF0 INT 13 ; 137B:7AF2 JNC 7AFA ; JumpNoCarry Point_next_free_RAM: ---> 137B:7AF4 ; 137B:7AF4 ; 137B:7AF4 ;------------------------------------------------------------------------------ 137B:7AF4 ; Display "Bad data": 137B:7AF4 ;------------------------------------------------------------------------------ 137B:7AF4 MOV SI, 7BC2 ; "Bad DATA" 137B:7AF7 JMP 7A54 ; JUMP set_code_exit: ---> 137B:7AFA ; 137B:7AFA ; 137B:7AFA ;------------------------------------------------------------------------------ 137B:7AFA ; Point next free RAM location 137B:7AFA ; 137B:7AFA ; IMPORTANT!!! 137B:7AFA ; FREE_RAM_INCREMENT is hard coded and determined at initialization 137B:7AFA ; of partition by MAKESHUT. 137B:7AFA ; 137B:7AFA ; IMPORTANT!!! 137B:7AFA ; The increment is added at the segment part and not on the offset part! 137B:7AFA ; Example: 137B:7AFA ; Add 0x80 to the segment part in the LBA packet for data. 0x80 in the segment 137B:7AFA ; is equivalent to add 0x800 in the offset. The address is specified as 137B:7AFA ; 0x07c0:0000 instead of 0x0000:7c00 but it is the same 0x07c00 real address. 137B:7AFA ; The idea here is to change only the segment part of the address since 137B:7AFA ; we allign the file at paragraph boundary. 137B:7AFA ; 137B:7AFA ;------------------------------------------------------------------------------ 137B:7AFA ; 137B:7AFA ; Point_next_free_RAM: <--- 137B:7AFA ; 137B:7AFA ;----------------------------- 137B:7AFA ; IMPORTANT!!! 137B:7AFA ; The byte definition that follows is equivalent to: 137B:7AFA ; MOV AX, aabb ; AX = FREE_RAM_INCREMENT (= size of one cluster). 137B:7AFA ; 137B:7AFA ; where MAKESHUT substitutes the 0xaabb with the real number calculated with 137B:7AFA ; the actual setting of the partition. We choose 0xaabb in order to make 137B:7AFA ; VISIBLE the byte ordering sequence in code (little endian). 137B:7AFA ;----------------------------- 137B:7AFA DB b8 137B:7AFB DB bb aa 137B:7AFD ; 137B:7AFD ; 137B:7AFD ADD [7be6], AX ; ABS_RAM_SEG = ABS_RAM_SEG + FREE_RAM_INCREMENT. 137B:7B01 ; 137B:7B01 ; 137B:7B01 ;------------------------------------------------------------------------------ 137B:7B01 ; Compare current portion of FAT in RAM against the required one. 137B:7B01 ; The size of FAT is bigger than the buffer in RAM so we load only portion 137B:7B01 ; of it at each time. 137B:7B01 ;------------------------------------------------------------------------------ 137B:7B01 POP AX ; Restore the copy of the current cluster in AX. 137B:7B02 MOV BX, 02 ; BX = 0x02, every cluster is 2 bytes long in FAT. 137B:7B05 MOV CX, 7000 ; CX = size of RAM BUFFER. Every FAT portion can 137B:7B08 ; be just as big as the RAM BUFFER. 137B:7B08 MUL BX ; DX::AX = AX*BX -> This is the address of cluster 137B:7B0A ; relative to start of FAT. 137B:7B0A DIV CX ; AX = INT(DX::AX / CX) -> REQUIRED_FAT_PAGE. 137B:7B0C ; DX = RES(DX::AX / CX) -> Relative offset inside 137B:7B0C ; RAM BUFFER. 137B:7B0C ; 137B:7B0C PUSH AX ; Save REQUIRED_FAT_PAGE on stack. 137B:7B0D PUSH DX ; Save FAT offset on stack. 137B:7B0E CMP AL, [7BDD] ; Flag = REQUIRED_FAT_PAGE - IS_FAT_PAGE_IN_RAM. 137B:7B12 ; unsigned comparison: 137B:7B12 JE 7B4E ; JumpEqual Get_next_cluster: ---> 137B:7B14 ; 137B:7B14 ; [...]

The first purple block of code begins with a little bit of initialization after which it enters the loop at Read_DATA_CLS_NOW: (address 0x7AD4) to load SOFTWARE.BIG into RAM. At address 0x7AD9 we find the first injection point in code. Actually, I need to put the value of BLK_X_CLS (block per cluster) into register CX. This value is already available in the BIOS Parameter Block (BPB) so, just to be as generic as I possibly could, I did read it directly from the BPB (at least I did so in the beginning). The problem is that BLK_X_CLS is one Byte long, so I had to read it into CL, but I needed to be sure that CH was 0x00 so I set CX = 0x0000 with an XOR operation. Unfortunately, I was some Bytes longer than 512 when I finished everything, so I started to scratch my heat to cut Bytes. I had to use code injections at other places in SHUTTEMP for other reasons, so I had to accept an additional code injection here just for the purpose of making the whole code some Bytes shorter (there is another code injection point with the same background in SHUTTEMP). At address 0x7AFA you can see the moving ahead of the RAM pointer used to load the next cluster of SOFTWARE.BIG. For this I used the very same logic as in the original Space Shuttle with one difference: the original Space Shuttle has hardcoded the increment for a fixed size of the cluster (4 blocks per cluster and each block 512 Bytes long), meanwhile MAKESHUT calculate which is the real size of the cluster for this very partition so that SHUTTLE2 gets tailored to each and every partition. MAKESHUT performs plausibility check while calculating the size of the cluster before injecting this into SHUTTEMP which enhance the quality of the final SHUTTLE2. In case of an implausible size of the cluster, MAKESHUT terminates with an error message.

Update IS_FAT_PAGE_IN_RAM

The code starting from 0x7B14 and marked with grey colour (Fig. A) takes care of updating the portion of FAT (the page) that is currently loaded in RAM.

Fig. C - Buffered read of multiple FAT copies
Fig. C - Buffered read of multiple FAT copies

Before looking at the code closer, I want to comment on the algorithm used and I have prepared Fig. C for this purpose. Suppose that there were three copies of FAT. Suppose that each copy was seven blocks wide. Suppose that the total size of the buffer in RAM was three blocks wide (FR_BLK_TRNSF3). BPB_FATSz16 is the size of one copy of FAT which indeed is the offset from the beginning of the same page between two subsequent copies of FAT. Note that since the FAT is 7 blocks wide and the RAM buffer is just 3 blocks wide, one needs 3 pages in total (from Page 0 to Page 2) to read one copy of FAT in this example. Note also that when one reads the last page of FAT (page 2 in the example), it loads also into RAM a portion of data that is on disk after the end of the current copy of FAT (this is not a problem because even if this data was loaded into RAM, I was not going to read and use it at all).

Let me define a decreasing counter that is equal to the number of FAT copies minus one (3 – 1 = 2). I used this counter to read the copies of FAT in the loop that restarted again every time I had a bad INT 13 read (in which case, I supposed that the FAT copy was bad) and it went on until I had copies of FAT available. Suppose also that I had to read Page 1 into RAM buffer, so I calculated the start address in the following way:

LOOP 1:
REL_LBA_FAT1_START = (REQUIRED_FAT_PAGE * FR_BLK_TRNSF) = (1*3) = 3
(BPB_FATSz16 * COUNTER) + REL_LBA_FAT1_START = (7*2) + 3 = 17

LOOP 2:
REL_LBA_FAT1_START = (REQUIRED_FAT_PAGE * FR_BLK_TRNSF) = (1*3) = 3
(BPB_FATSz16 * COUNTER) + REL_LBA_FAT1_START = (7*1) + 3 = 10

LOOP 3:
REL_LBA_FAT1_START = (REQUIRED_FAT_PAGE * FR_BLK_TRNSF) = (1*3) = 3
(BPB_FATSz16 * COUNTER) + REL_LBA_FAT1_START = (7*0) + 3 = 3

At this simple calculation, I just had to add the absolute LBA start address fo FAT one (ABS_LBA_FAT1_START, this is also the address of REL_BLK 0 in Fig. C). I used a decreasing counter in such a way that I could rise a zero flag without using a CMP instruction. If I was using an increasing counter going from 0 to 2 I would have to compare the counter with the last copy of FAT (2 in our case) with a CMP instruction that costs Bytes of code. A decreasing counter is the same amount of Bytes of an increasing counter (both INC and DEC are one Byte long) without the cost of a CMP instruction to set the flag. So you see that actually, I was reading data from the backup copy of the FAT at first, and only if this was bad I used the main. Now it is time to have a closer look at the code.

SHUTTEMP: Update IS_FAT_PAGE_IN_RAM
[...] 137B:7B14 ;------------------------------------------------------------------------------ 137B:7B14 ; 137B:7B14 ; Update IS_FAT_PAGE_IN_RAM 137B:7B14 ; 137B:7B14 ;------------------------------------------------------------------------------ 137B:7B14 ; List of operations: 137B:7B14 ; 1. X_1 = ABS_LBA_FAT1_START + REL_LBA_FAT1_START 137B:7B14 ; 2. X_n = X_1 + (BPB_FATSz16 * COUNTER) 137B:7B14 ; 3. COUNTER starts at BPB_NumFATs and decreases until ZERO. 137B:7B14 ; 137B:7B14 ; where: 137B:7B14 ; BX = REL_LBA_FAT1_START = (REQUIRED_FAT_PAGE * FR_BLK_TRNSF) 137B:7B14 ; 137B:7B14 ; rearranging the operations we have: 137B:7B14 ; X_n = X_1 + (BPB_FATSz16 * COUNTER) 137B:7B14 ; X_n = ABS_LBA_FAT1_START + REL_LBA_FAT1_START + (BPB_FATSz16 * COUNTER) 137B:7B14 ; X_n = ABS_LBA_FAT1_START + BX + (BPB_FATSz16 * COUNTER) 137B:7B14 ; X_n = ( (BPB_FATSz16 * COUNTER) + BX ) + ABS_LBA_FAT1_START 137B:7B14 ;------------------------------------------------------------------------------ 137B:7B14 MOV AH, [7BF2] ; AH = FR_BLK_TRNSF 137B:7B18 ; this is 2 things at the same time: 137B:7B18 ; 1. number of blocks by each LBA transfer, 137B:7B18 ; 2. the size of buffer reserved on RAM espressed 137B:7B18 ; in blocks rather than bytes. 137B:7B18 ; This value is hard coded and determined 137B:7B18 ; by MAKESHUT at initialization of partition. 137B:7B18 ; 137B:7B18 MUL AH ; AX = AL * AH. 137B:7B1A ; AX = REQUIRED_FAT_PAGE * FR_BLK_TRNSF. 137B:7B1A ; Remember that AL contains the integer part of 137B:7B1A ; DX::AX / CX -> REQUIRED_FAT_PAGE. 137B:7B1A ; By the way REQUIRED_FAT_PAGE (AX) is always 137B:7B1A ; below 0xFF given the CX = 0x7000 and 137B:7B1A ; MAX_CLS = 0xFFF6. Infact 0xFFF7 is a bad cluster 137B:7B1A ; and any number >= 0xFFF7 is reserved. 137B:7B1A MOV BX, AX ; Now BX = REL_LBA_FAT1_START. 137B:7B1C ; 137B:7B1C ; 137B:7B1C ;----------------------------- 137B:7B1C ; IMPORTANT!!! 137B:7B1C ; The byte definition that follows is equivalent to: 137B:7B1C ; MOV CX, aabb ; CX = BPB_NumFATs. 137B:7B1C ; 137B:7B1C ; where MAKESHUT substitutes the 0xaabb with the real number calculated with 137B:7B1C ; the actual setting of the partition. We choose 0xaabb in order to make 137B:7B1C ; VISIBLE the byte ordering sequence in code (little endian). 137B:7B1C ; 137B:7B1C ; IMPORTANT!!! 137B:7B1C ; code injection with MAKESHUT (MOV CX, aabb) is 3 bytes shorter than the 137B:7B1C ; original implementation with: 137B:7B1C ; XOR CX, CX ; 137B:7B1C ; MOV CL, [7a00 + 10] ; CX = BPB_NumFATs 137B:7B1C ; ; -> [BPB + 0x10] (1 Bytes) 137B:7B1C ; ; Number of FAT including copies. 137B:7B1C ; ; 137B:7B1C ;----------------------------- 137B:7B1C DB b9 137B:7B1D DB bb aa 137B:7B1F ; 137B:7B1F ; 137B:7B1F ; 137B:7B1F ; FAT_loop_START: <--- 137B:7B1F ; 137B:7B1F JCXZ 7b48 ; JumpCXZero FAT_loop_END: ---> 137B:7B21 DEC CX ; Number of FAT copy - 1 = FAT copy address. 137B:7B22 ; 137B:7B22 MOV AX, [7a00 + 16] ; AX = BPB_FATSz16 137B:7B25 ; -> [BPB + 0x15] (2 Bytes) 137B:7B25 ; Number of blocks occupied by one copy of FAT. 137B:7B25 ; It is also the displacement among blocks in the 137B:7B25 ; copies of FAT. 137B:7B25 ; 137B:7B25 MUL CX ; DX::AX = AX * CX 137B:7B27 ; DX::AX = BPB_FATSz16 * COUNTER 137B:7B27 ; 137B:7B27 ADD AX, BX ; DX::AX = ( (BPB_FATSz16 * COUNTER) + BX ) 137B:7B29 ADC DX, 00 ; NOW DX::AX = REL_LBA_FAT_START. 137B:7B2C ; Of any FAT, not just FAT1! 137B:7B2C ; 137B:7B2C ;----------------------------- 137B:7B2C ; IMPORTANT!!! 137B:7B2C ; The byte definition that follows is equivalent to: 137B:7B2C ; ADD AX, aabb ; X_n = DX::AX + ABS_LBA_FAT1_START 137B:7B2C ; ADC DX, aabb ; 137B:7B2C ; 137B:7B2C ; where MAKESHUT substitutes the 0xaabb with the real number calculated with 137B:7B2C ; the actual setting of the partition. We choose 0xaabb in order to make 137B:7B2C ; VISIBLE the byte ordering sequence in code (little endian). 137B:7B2C ;----------------------------- 137B:7B2C DB 05 137B:7B2D DB bb aa 137B:7B2F DB 81 d2 137B:7B31 DB bb aa 137B:7B33 ; 137B:7B33 ; 137B:7B33 ;------------------------------------------------------------------------------ 137B:7B33 ; Update LBA Packet for ROOT and FAT 137B:7B33 ;------------------------------------------------------------------------------ 137B:7B33 MOV [7BF8], AX ; ABS_LBA_F_ROOT_STR = DX::AX 137B:7B36 MOV [7BF8 + 2], DX ; 137B:7B3A ; 137B:7B3A ; 137B:7B3A ;------------------------------------------------------------------------------ 137B:7B3A ; Transfer FAT portion in RAM: 137B:7B3A ;------------------------------------------------------------------------------ 137B:7B3A CLC ; Clear carry flag before invocation of INT13 137B:7B3B ; to be sure that CF was zero before INT13. 137B:7B3B MOV SI, 7BF0 ; SI -> LBA packet for ROOT and FAT. 137B:7B3E ; The packet has just been updated for FAT. 137B:7B3E MOV DL, 80 ; DL = 0x80 (first HDD). 137B:7B40 MOV AH, 42 ; AH = 0x42 (READ with LBA). 137B:7B42 INT 13 ; 137B:7B44 JNC 7B4E ; JumpNoCarry Get_next_cluster: ---> 137B:7B46 ; 137B:7B46 ;------------------------------------------------------------------------------ 137B:7B46 ; n-th copy of FAT is bad, now try with (n-th - 1) copy. 137B:7B46 ;------------------------------------------------------------------------------ 137B:7B46 JMP 7B1F ; JUMP FAT_loop_START: ---> 137B:7B48 ; 137B:7B48 ; FAT_loop_END: <--- 137B:7B48 ; 137B:7B48 ; 137B:7B48 ; 137B:7B48 ;------------------------------------------------------------------------------ 137B:7B48 ; Display "Bad FAT": 137B:7B48 ;------------------------------------------------------------------------------ 137B:7B48 MOV SI, 7BCE ; "Bad FAT" 137B:7B4B JMP 7A54 ; JUMP set_code_exit: ---> 137B:7B4E ; 137B:7B4E ; 137B:7B4E ; 137B:7B4E ; [...]

From 0x7B14 until 0x7B1D, I did some preparation before entering the loop. I calculated the value of REL_LBA_FAT1_START = (REQUIRED_FAT_PAGE * FR_BLK_TRNSF) and I stored it in register BX (this value doesn't change by any loop). Later on, I put in CX the total number of FAT copies, which is a piece of information available in the BIOS Parameter Block, but again a code injection was three Bytes shorter than zeros CX and read CL from BIOS Parameter Block.
After that, the loop started at 0x7B1F (marked with FAT_loop_START:). I placed the exit condition at loop begin, and, as soon as I entered the loop, I decreased the counter because the counter was also used to build the address (remember the example of Fig. C: three copies of FAT are addressed with 0 * BPB_FATSz16, 1 * BPB_FATSz16 and 2 * BPB_FATSz16 from FAT 1 respectively). Once the relative address was calculated, I added the absolute start of FAT1 which I did again with a code injection at address 0x7B2C. If the read was successful, the code jumped to Get_next_cluster: and continued, otherwise it tried again with a different copy of FAT until it fell down the the "Bad FAT" error message.

It is true that SHUTTLE2 has code injections that make it impossible to just copy-paste this code and make it work on another partion4, but MAKESHUT adapts it for each partition. I also solved the issue about the way the original Space Shuttle was building the addresses of the two copies of FAT and I can even handle a generic number of copies of FAT so I was happy with this result.

Get_next_cluster

SHUTTEMP: Get_next_cluster
[...] 137B:7B4E ;------------------------------------------------------------------------------ 137B:7B4E ; Get next cluster. 137B:7B4E ;------------------------------------------------------------------------------ 137B:7B4E ; 137B:7B4E ; Get_next_cluster: <--- 137B:7B4E ; 137B:7B4E POP BX ; BX = FAT offset from stack. 137B:7B4F ; Recall relative FAT offset inside RAM BUFFER 137B:7B4F ; from stack. 137B:7B4F ; 137B:7B4F POP AX ; AX = REQUIRED_FAT_PAGE from stack. 137B:7B50 ; Recall current portion of FAT from stack. 137B:7B50 ; 137B:7B50 MOV [7BDD], AL ; Set IS_FAT_PAGE_IN_RAM = REQUIRED_FAT_PAGE 137B:7B53 ; for future checks. 137B:7B53 ; 137B:7B53 MOV AX, [BX + 0500] ; AX = [relative FAT offset + RAM BUFFER START]. 137B:7B57 ; This get the next cluster in AX. 137B:7B57 CMP AX, ffff ; Flag = AX - 0xffff 137B:7B5A ; unsigned comparison: 137B:7B5A JE 7B75 ; JumpEqual check_signature: ---> 137B:7B5C ; 137B:7B5C ; Cluster_0 and Cluster_1 are reserved number so 137B:7B5C ; that is CLS_NOW (AX) is below Cluster_2 then 137B:7B5C ; this is an error. 137B:7B5C CMP AX, 02 ; Flag = AX - 0x0002 137B:7B5F ; unsigned comparison: 137B:7B5F JB 7B82 ; JumpBelow bad_file: ---> 137B:7B61 ; 137B:7B61 ;----------------------------- 137B:7B61 ; IMPORTANT!!! 137B:7B61 ; LAST_CLS_DSK is the latest addressable cluster on disk. This value depends 137B:7B61 ; on the size of the partition and can be at maximum 0xfff6. 137B:7B61 ; 0xfff7 indicates a bad or defective cluster. Moreover all values from 0xfff8 137B:7B61 ; until 0xfffe are reserved and cannot be used. If CLS_NOW (AX) is above 137B:7B61 ; LAST_CLS_DSK then this is an error. 137B:7B61 ; 137B:7B61 ; IMPORTANT!!! 137B:7B61 ; The byte definition that follows is equivalent to: 137B:7B61 ; CMP AX, LAST_CLS_DSK ; Flag = AX - LAST_CLS_DSK 137B:7B61 ; 137B:7B61 ; where MAKESHUT substitutes the 0xaabb with the real number calculated with 137B:7B61 ; the actual setting of the partition. We choose 0xaabb in order to make 137B:7B61 ; VISIBLE the byte ordering sequence in code (little endian). 137B:7B61 ;----------------------------- 137B:7B61 DB 3d 137B:7B62 DB bb aa 137B:7B64 ; 137B:7B64 ; 137B:7B64 ; unsigned comparison: 137B:7B64 JA 7B82 ; JumpAbove bad_file: ---> 137B:7B66 ; 137B:7B66 DEC WORD PTR [7BDE] ; MAX_FILE_STOP_CNTR = MAX_FILE_STOP_CNTR - 1 137B:7B6A JZ 7B6F ; JumpZero file_too_big: ---> 137B:7B6C JMP 7AD4 ; JUMP Read_DATA_CLS_NOW: ---> 137B:7B6F ; 137B:7B6F ; 137B:7B6F ;------------------------------------------------------------------------------ 137B:7B6F ; Display "File too big": 137B:7B6F ;------------------------------------------------------------------------------ 137B:7B6F ; 137B:7B6F ; file_too_big: <--- 137B:7B6F ; 137B:7B6F MOV SI, 7BAB ; "File too big" 137B:7B72 JMP 7A68 ; JUMP error_exit: ---> 137B:7B75 ; 137B:7B75 ; [...]

Once the data of the current cluster was in RAM (job done by the first purple block of code), and once the FAT page in RAM has been updated (if necessary by the grey code), I could use the FAT in RAM and read which was the next cluster in the chain and start the loop again, and again until the total SOFTWARE.BIG was loaded into RAM. This is in extremely short words, what the latest purple block of code (from 0x7B4E to 0x7B6E) does (see Fig. A); but additionally, it performs checks on the next cluster in the chain and on the total loads already used for SOFTWARE.BIG. I checked the next cluster in the chain first of all against the 0xFFFF which is the conventional termination of file. As soon as this condition was met, the code moved on with the check of the signature, since SOFTWARE.BIG was fully loaded into RAM. If the next cluster was not 0xFFFF, then I checked that the cluster was inside the legal range which goes from cluster 2 until LAST_CLS_DSK (the latest addressable cluster on disk). If the cluster number was ok, I checked the size of SOFTWARE.BIG in an indirect way. Actually, I calculated with MAKESHUT the total number of cluster that can be entirely loaded inside the total FREE_RAM that goes from 0x07C00 to 0x9FFFF for a total of 0x98400 = 0x9FFFF - 0x0fC00 + 1 = (623616 decimal) = 609 KBytes. The size of the cluster depends on the setting inside of the BIOS Parameter Block (BYTES_X_CLS = BPB_SecPerClus * BPB_BytsPerSec). The maximum number of clusters that fit in FREE_RAM is used as a decreasing counter (MAX_FILE_STOP_CNTR = INT(FREE_RAM / BYTES_X_CLS) ) that runs towards zero after each loading. In this way, if SOFTWARE.BIG had filled the FREE_RAM (MAX_FILE_STOP_CNTR = 0) before reaching the last cluster 0xFFFF, then this was the moment where I knew without error that the file was too big. This is the safe approach because it doesn't rely on the information about the size of the file stored inside the directory entry, but it keeps count of FREE_RAM as it fills cluster after cluster.

Final check before passing control

SHUTTEMP: last check for signature
[...] 137B:7B75 ;------------------------------------------------------------------------------ 137B:7B75 ; last check for signature 137B:7B75 ;------------------------------------------------------------------------------ 137B:7B75 ; 137B:7B75 ; check_signature: <--- 137B:7B75 ; 137B:7B75 MOV CX, 09 ; CX = 0x09 (9 decimal) 137B:7B78 ; = length of signature in words + 1 137B:7B78 MOV DI, 7C00 ; DI -> Start of "SOFTWARE.BIG" 137B:7B7B MOV SI, 7B50 ; SI -> just in the middle of code for SHUTTLE, 137B:7B7E ; used as signature. 137B:7B7E REPE ; Repeat the following sequence of operations: 137B:7B7F CMPSW ; 1. CX == 0 ? --> if yes terminate. 137B:7B80 ; 2. CX = CX - 1 137B:7B80 ; 3. flags = ES:[DI] - DS:[SI] word by word. 137B:7B80 ; 4. SI = SI + 2 and DI = DI + 2 (word operation). 137B:7B80 ; 5. REPE -> ZeroFlag = 0 ? --> if yes terminate. 137B:7B80 ; 137B:7B80 ; 137B:7B80 JCXZ 7B8B ; JumpCXZero transfer_contol: ---> 137B:7B82 ; 137B:7B82 ; 137B:7B82 ;------------------------------------------------------------------------------ 137B:7B82 ; Display "Bad SOFTWAREBIG": 137B:7B82 ;------------------------------------------------------------------------------ 137B:7B82 ; 137B:7B82 ; bad_file: <--- 137B:7B82 ; 137B:7B82 MOV SI, 7B91 ; "Bad SOFTWAREBIG" 137B:7B85 MOV [SI + 0f], DS ; Set the termination of string. 137B:7B88 ; Remember that DS = ES = CS = 0x0000. 137B:7B88 JMP 7A68 ; JUMP error_exit: ---> 137B:7B8B ; 137B:7B8B ; 137B:7B8B ; 137B:7B8B ; 137B:7B8B ; 137B:7B8B ;############################################################################## 137B:7B8B ; 137B:7B8B ; Successful end with control transfer. 137B:7B8B ; 137B:7B8B ;############################################################################## 137B:7B8B ; 137B:7B8B ; transfer_contol: <--- 137B:7B8B ; 137B:7B8B JMP 7c10 ; Transfer control to SOFTWARE.BIG!!! 137B:7B8E ; 137B:7B8E ; [...]

When I did the first version of the original Space Shuttle, I introduced a final message "Loaded!" together with a wait for press any key. I did so because it helped to distinguish if it was the original Space Shuttle that crashed or the SOFTWARE.BIG that run after it. For SHUTTLE2 I used a different strategy: SHUTTLE2 remained silent as long as no error occured. It checked a signature before passing control to SOFTWARE.BIG and then it went off the stage. So if something didn't work, it must have been SOFTWARE.BIG. It remained a small problem with the signature. I supposed to take a bitmap no larger than 609 KB and rename it in SOFTWARE.BIG, so if I were using a one Byte signature there were 1/256 chance that the file was recognized as a good one. The more the Bytes used for the signature, the lower the probability that this happened. Since I had to economize on Bytes as much as I could, I had the idea to use a string comparison for the signature check. This string comparison can be coded in a quite compact way and let me check a signature of any length. I decided to use a full paragraph for the signature, because in this way SOFTWARE.BIG entry point was aligned at 0x7C10 when it was loaded in memory. So it was just an esthetic alignment that made my life a little bit easier when I looked code in DEBUG.EXE, but at the same time 16 Bytes was a very long signature and the chances to find such a signature in any file by coincidence were almost nothing5. The last point to set was which sequence of 16 Bytes had I to use as signature. Like any human being I wanted to use something personal, something invented by me that would let a small sign in SHUTTLE2 forever, but the rigid rule of 512 Bytes imposed me to give up romanticism. The true story goes like this: since I couldn’t manage to perform a personalized signature check then I could use any string of any length for the check. Since the length was no longer a problem from the point of view of the code because I was using the string comparison, I decided to use 16 Bytes for the signature even if it was much longer than I originally imagined for my personalized signature. At that point, I wanted to have a visual alignment with the paragraph boundary for the entry point of the code. Since the content of the signature was not important, I decided to take a paragraph just in the middle of the code of SHUTTEMP ad use it as the signature. The only point I took care of was to exclude any paragraph where a code injection from MAKESHUT occurred, otherwise this would make SOFTWARE.BIG tight to the SHUTTLE2 that exists in one partition and I couldn’t just copy-paste SOFTWARE.BIG between two computers. So I choose a paragraph starting at 0x7B50 (which is a piece of code just in between of Get_next_cluster:) that is always the same in any SHUTTLE2 (because it doesn't get modified by code injection).

Strings and LBA packages

SHUTTEMP: PRE INITIALIZED DATA
[...] 137B:7B8E ;############################################################################## 137B:7B8E ; 137B:7B8E ; PRE INITIALIZED DATA 137B:7B8E ; 137B:7B8E ;############################################################################## 137B:7B8E ;------------------------------------------------------------------------------ 137B:7B8E ; ALLIGNMENT -> These are bytes necessary just for allignment of DATA 137B:7B8E ; with the BOOT SIGNATURE 0x55AA to be at last 2 bytes. 137B:7B8E DB '???' 137B:7B91 ; 137B:7B91 ;------------------------------------------------------------------------------ 137B:7B91 ; MESSAGES: 137B:7B91 DB 'Bad ' 137B:7B95 DB 'SOFTWAREBIG' 137B:7BA0 DB ' not found' 00 137B:7BAB DB 'File too bi' 137B:7BB6 DB 'g' 00 'h: Bad ROO' 137B:7BC2 DB 'T' 00 'h: Bad DAT' 137B:7BCE DB 'A' 00 'h: Bad FAT' 00 137B:7BDB DB '.' 00 137B:7BDD ; 137B:7BDD ;------------------------------------------------------------------------------ 137B:7BDD ; IS_FAT_PAGE_IN_RAM -> (01 Byte) 137B:7BDD ; This is the portion of FAT currently loaded in RAM. 137B:7BDD ; We set the initial value to 0xff in such a way that 137B:7BDD ; at the very first attempt it triggers an updated 137B:7BDD ; of the FAT in RAM which is the load of FAT 137B:7BDD ; for the very first time indeed. 137B:7BDD DB ff 137B:7BDE ; 137B:7BDE ;------------------------------------------------------------------------------ 137B:7BDE ; MAX_FILE_STOP_CNTR -> (02 Bytes) 137B:7BDE ; This is the countdown counter used to stop loading 137B:7BDE ; SOFTWARE.BIG into RAM. Safeguard RAM integrity. 137B:7BDE ; MAKESHUT calculates this value based on the actual 137B:7BDE ; setting for BPB_SecPerClus and BPB_BytsPerSec. 137B:7BDE DB 3f 3f 137B:7BE0 ; [...]

The strings are arranged strangely to save as many Bytes as I could. Besides the virtual overlapping of strings (as I explained above in Fig. B), I also used the very same "SOFTWAREBIG" in three different contexts.

  • First of all "SOFTWAREBIG" is the string I was searching in the ROOT directory entry to find the file (please note in this case that "." is missing since this is implicitly assumed by FAT).
  • A second time, I built an error message "SOFTWAREBIG not found" which saved Bytes from the originally planned message "File not found" because I reused the Bytes of "SOFTWAREBIG" instead of spending four Bytes for "File" (please note that the message "SOFTWAREBIG not found" is slightly wrong from the end-user perspective because the "." Is missing, but this was due by the first way the very same string was used).
  • Finally, I reused "SOFTWAREBIG" a third time in the error message "Bad SOFTWAREBIG".

The three usages were chained in such a way that I pointed "S" at 0x7B95 for the first two way of usage and I pointed "B" at 0x7B91 for the last usage, but in this last case I had to stop the string placing a 0x00 at the end of "SOFTWAREBIG" (otherwise it would display the full string "Bad SOFTWAREBIG not found"). Since all segment registers were already initialized to 0x0000 I just injected the termination Byte 0x00 with the line of code at 0x7B85. Actually, it set a full word that overwrites the " n" Bytes, but the effect was the desired one because SHOW_STR terminates as soon as the first 0x00 is found.

IS_FAT_PAGE_IN_RAM can be pre-initialized with 0xFF in such a way that the very first comparison with REQUIRED_FAT_PAGE forced the initial upload of FAT in RAM buffer. MAX_FILE_STOP_CNTR is initialized with “??” because the real value had to be calculated by MAKESHUT.

Fig. D - pre-initialized LBA packets
Fig. D - pre-initialized LBA packets

In Fig. D, you see the last two LBA packets. Both packets started with the 0x0010 (remember Little-Endian) because this was the size of the packets so it could be pre-initialized without MAKESHUT doing anything. The number of blocks to transfer was unknown by SHUTTEMP because in case of LBA for DATA (at address 0x7BE2) this had to be equal to the number of blocks by each cluster (BPB_SecPerClus) and in case of LBA for ROOT and FAT (at address 0x7BF2) this had to be equal to the number of blocks that fitted inside the RAM buffer. The number of blocks that fitted the RAM buffer depends on the size of the block (BPB_BytsPerSec) which was unknown by SHUTTEMP.
The destination SEG:OFF address in RAM where the INT 13H will write was known and could be pre-initialized for both packets. The remaining Bytes marked with 0x3F ("?") couldn't be determined by SHUTTEMP and hat to be filled by MAKESHUT.

The last portion of the code in SHUTTEMP is the following.

SHUTTEMP: LBA packets
[...] 137B:7BE0 ; ################################################################### 137B:7BE0 ; # LBA_DATA: 137B:7BE0 ; # This is the partially pre configured LBA package used for data. 137B:7BE0 ; ################################################################### 137B:7BE0 ; LBA_PKG_SIZE -> (02 Bytes) 137B:7BE0 ; Size of packet either 0x10 or 0x18 (16 or 24) 137B:7BE0 ; bytes long. We use always 0x10. 137B:7BE0 DB 10 00 137B:7BE2 ; 137B:7BE2 ;------------------------------------------------------------------------------ 137B:7BE2 ; DATA_BLK_TRNSF -> (02 Bytes) 137B:7BE2 ; Number of blocks to transfer. 137B:7BE2 ; MAKESHUT calculates this value based on the actual 137B:7BE2 ; setting for BPB_SecPerClus. 137B:7BE2 DB 3f 3f 137B:7BE4 ; 137B:7BE4 ;------------------------------------------------------------------------------ 137B:7BE4 ; ABS_RAM_OFF -> (02 Bytes) 137B:7BE4 ; This is the offset of the RAM that together 137B:7BE4 ; with the following word builds the full address. 137B:7BE4 DB 00 00 137B:7BE6 ; 137B:7BE6 ;------------------------------------------------------------------------------ 137B:7BE6 ; ABS_RAM_SEG -> (02 Bytes) 137B:7BE6 ; This is the segment part of it so that 137B:7BE6 ; ABS_RAM_SEG:ABS_RAM_OFF is the full address. 137B:7BE6 DB c0 07 137B:7BE8 ; 137B:7BE8 ;------------------------------------------------------------------------------ 137B:7BE8 ; ABS_LBA_DATA_START -> (04 Bytes) 137B:7BE8 ; This is initialized with the absolute LBA start of 137B:7BE8 ; Cluster 2 (beginning of data on disk). At runtime 137B:7BE8 ; the initialization value is stored in BP::DI and this 137B:7BE8 ; field is updated with the portion of data to read from 137B:7BE8 ; disk. 137B:7BE8 DB 3f 3f 3f 3f 137B:7BEC ; 137B:7BEC ;------------------------------------------------------------------------------ 137B:7BEC ; 0x0000_0000 -> (04 Bytes) 137B:7BEC ; Always ZERO! This is the high 32Bit of the 64bit LBA 137B:7BEC ; which is never used. Look at Bios Parameter Block and 137B:7BEC ; you will see that the addres is in 32bit implicitelly 137B:7BEC ; assuming 00 for the rest. 137B:7BEC DB 00 00 00 00 137B:7BF0 ; 137B:7BF0 ;------------------------------------------------------------------------------ 137B:7BF0 ; ########################################################################### 137B:7BF0 ; # LBA_ROOT_FAT: 137B:7BF0 ; # This is the partially pre configured LBA package used for ROOT and FAT. 137B:7BF0 ; ########################################################################### 137B:7BF0 ; LBA_PKG_SIZE -> (02 Bytes) 137B:7BF0 ; Size of packet either 0x10 or 0x18 (16 or 24) 137B:7BF0 ; bytes long. We use always 0x10. 137B:7BF0 DB 10 00 137B:7BF2 ; 137B:7BF2 ;------------------------------------------------------------------------------ 137B:7BF2 ; FR_BLK_TRNSF -> (02 Bytes) 137B:7BF2 ; Number of blocks to transfer. 137B:7BF2 ; MAKESHUT calculates this value based on the actual 137B:7BF2 ; setting for BPB_SecPerClus. 137B:7BF2 DB 3f 3f 137B:7BF4 ; 137B:7BF4 ;------------------------------------------------------------------------------ 137B:7BF4 ; ABS_RAM_OFF -> (02 Bytes) 137B:7BF4 ; This is the offset of the RAM that together 137B:7BF4 ; with the following word builds the full address. 137B:7BF4 DB 00 05 137B:7BF6 ; 137B:7BF6 ;------------------------------------------------------------------------------ 137B:7BF6 ; ABS_RAM_SEG -> (02 Bytes) 137B:7BF6 ; This is the segment part of it so that 137B:7BF6 ; ABS_RAM_SEG:ABS_RAM_OFF is the full address. 137B:7BF6 DB 00 00 137B:7BF8 ; 137B:7BF8 ;------------------------------------------------------------------------------ 137B:7BF8 ; ABS_LBA_F_ROOT_STR -> (04 Bytes) 137B:7BF8 ; This is the absolute LBA start used for ROOT and FAT. 137B:7BF8 ; It is initialized with absolute LBA start of ROOT. 137B:7BF8 DB 3f 3f 3f 3f 137B:7BFC ; 137B:7BFC ;------------------------------------------------------------------------------ 137B:7BFC ; 0x0000_55aa -> (02 Bytes) 137B:7BFC ; Always ZERO! This is the high 32Bit of the 64bit LBA 137B:7BFC ; which is never used. Look at Bios Parameter Block and 137B:7BFC ; you will see that the addres is in 32bit implicitelly 137B:7BFC ; assuming 00 for the rest. 137B:7BFC ; IMPORTANT!!!! This must end with the BOOT signature!!! 137B:7BFC DB 00 00 55 aa 137B:7C00 ; 137B:7C00 ;------------------------------------------------------------------------------ 137B:7C00 [...]


  1. If you want to know more about this topic, I suggest you to read also the posts "Read write LBA (a wrapper for INT 13H)" and "How to rename the volume label of partition". [click back]
  2. The true total LBA address is 8 Bytes long (as you can tell by looking at Fig. A of the post "Read the first block of HDD") but in case of FAT16, it can only address the lower 4 Bytes (as you can tell by looking at the Bios Parameter Block at offset 0x1C and 0x20). So FAT16 works with 4 Bytes LBA addresses implicitly using 0x00 for the remaining 4 high Bytes. [click back]
  3. This is the number of block to transfer (BLK_TRNSF) from disk to RAM buffer in the LBA packet for both FAT and ROOT (FR_). [click back]
  4. But wait a moment, one can never just copy-paste 512 Bytes of 1st block and to another partition, because the BIOS Parameter Block goes with it. So I think that probably there is no other solution different from this one with SHUTTEMP (the template) and MAKESHUT (the builder). [click back]
  5. The chance is 1/ 2^128 = ~2,94 e-39. There is more space between electron and nucleus of a hydrogen atom (120 e-12 meters) than the chances to get the signature right by coincidence. [click back]

<PREV.  -  ALL  -  NEXT>

Comments