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
|
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)
-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).
[...]
-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
[...]
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
[...]
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
|
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.
[...]
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
[...]
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
[...]
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.
[...]
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
|
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.
[...]
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
[...]
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
[...]
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
[...]
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
|
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.
[...]
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
[...]
Comments
Post a Comment