неділю, 29 грудня 2013 р.

Аналіз SYS.COM з PC-DOS 3.00 -- частина третя, нарешті SYS

Все, після виколупування і розборок із printf-подібною штукою, можемо перейти до самого SYS-вже-не-зовсім-COM.


Хоча рекомпілювати вихідний "конвертований" COM-файл особливого сенсу немає, захований в ньому "SYS.EXE" дизасемблювати так, що рекомпілювати його цілком можна. Єдина проблема --- не знаю, як змусити FASM генерувати "великий" MZ-заголовок: вихідний SYS.EXE виділяє під заголовок аж 200h байт, з яких зайнято заледве 30h, решта заповнені нулями. Так як писати заголовок вручну, скориставшись директивою "binary as", було лінь, та й не так цікаво, просто перед сегментом printf вставив 1B8h нулів. Результуючий файл має трохи відмінностей, від адрес релокацій і до синонімічних опкодів, але (якщо я десь не помилився в конкретних інструкціях) еквівалентний вихідному. Нагадаю, що аналіз -- внизу, після коду.

; ===================================================================================
;  This file is generated by The Interactive Disassembler (IDA)     
;  Copyright (c) 2010 by Hex-Rays SA, <support@hex-rays.com>     
;     Licensed to: Freeware version       
; ===================================================================================
;
; Input MD5   : 27E0BA5D178A3FD5DB12EE491042D7B1

; Modified to compile by fasm and commented by Indrekis, indrekis2.blogspot.com
;
; File Name   : PC-DOS_3.00\"Decoded" SYS.COM
; Format      : EXE

  include  'partialBPBrecord.inc'
  ;.8086
  format MZ

  entry   CodeSeg:CodeSegStart
  ;stack   80h 
  stack   StackSeg:StackSegEnd
  ; heap   0xFFFF
;segment temp  
  db 1B8h dup(0)
; ===================================================================================

; Segment type: Regular
segment  PrintfSeg 
OutFileHandler dw 1   
LeftJustify db 0   
IsLong  db 0   
printHexAsLowerLetters db 0  
HexLettersTblDispl db 0   
DoPrintString db 0   
PaddingSize dw 0   
NumberBase dw 0   
PaddingChar db  20h   
a0123456789abcd db '0123456789ABCDEFabcdef' 
temp_IP  dw 0
temp_CS  dw 0   
PrintfOutBuffer db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; 20
EndOfPrintfOutBuffer db    0
  db  15h
  db    0

; === SUBROUTINE ====================================================

; Address of the pointer to the string --- in stack.
; IF there are other data, pointers to them are after the pointer to string.
; Attributes: bp-based frame

Arg_0  EQU 16h

Printf_sub: ; far proc
  push bp
  push dx
  push cx
  push bx
  push ax
  push di
  push si
  push es
  push ds
  mov bp, sp  ; Setup stack frame.
  push cs
  pop es
  ;assume es:PrintfSeg

  mov di, PrintfOutBuffer ; ofs
  mov bp, [bp+Arg_0] ; BP = pushed to stack, before the call, value
     ; (traditionally -- from the DX),
     ; which contains _address_of_pointer to string
     ; Important! Next 2-byte words, after the pointer,
     ;  contain pointers to other printf arguments!
  mov si, [ds:bp+0] ; Now SI --- string address
  xor bx, bx  ; BX --- next variadic argument
     ; displacement in stack (rel. to BP+2)
  call PrintfInitForNext

NextSymbol:    ; 
     ; 
  lodsb
  cmp al, 25h ; '%'

loc_9C3C:
  jz short DirectiveHandling
  or al, al
  jz short ZeroByteMet ; Zero -- C-string terminator
  call StoreChar_and_FlushBufferIfFull ; DI --- destination in buffer
     ; AL --- symbol
  jmp short NextSymbol
; ===================================================================================
; Flush buffer at exit

ZeroByteMet:    ; 
  call DoPrintIfNotEmpty ; DI points to last symbol in buffer.
     ; If it's address == bufer start address, does nothing.
  pop ds
  pop es
  ; assume es:nothing

loc_9C4C:
  pop si
  pop di
  pop ax
  pop bx
  pop cx
  pop dx
  pop bp
; Take return address from the stack,
; clear agument --- pointer to string address,
; then put return address back to stack.
  pop word [cs:temp_IP]
  pop [cs:temp_CS]
  pop ax
  push [cs:temp_CS]
  push word [cs:temp_IP]
  retf
; ===================================================================================

PrintPercentChar:   ; 
  call StoreChar_and_FlushBufferIfFull ; DI --- destination in buffer
     ; AL --- symbol
  jmp short NextSymbol
; ===================================================================================
; http://www.cplusplus.com/reference/cstdio/printf/

DirectiveHandling:   ; 
     ; 
  lodsb   ; Load char after '%' -- directive code
  cmp al, '%'
  jz short PrintPercentChar
  cmp al, '-'         ; Left justify
  jz short SetLeftJustify
  cmp al, '+'         ; ?
  jz short ContDirectivesHandling
  cmp al, 'L'         ; Not Long double as in modern C code
     ; -- just long int
  jz short SetLong
  cmp al, 'l'         ; long int
  jz short SetLong
  cmp al, 30h ; '0'
  jb short NotDigitDirective
  cmp al, 39h ; '9'
  ja short NotDigitDirective
  cmp al, 30h ; '0'
  jnz short SetDefPaddingChar0 ; 
  cmp [cs:PaddingSize], 0
  jnz short SetDefPaddingChar0
  mov byte [cs:PaddingChar], '0'

SetDefPaddingChar0:    
  push ax
; We will read paddign size digit by digist, starting from hi.
; So for each next -- mul by 10 and add it
  mov ax, 10
  mul [cs:PaddingSize]
  mov [cs:PaddingSize], ax
  pop ax
  xor ah, ah
  sub al, 30h ; '0'
  add [cs:PaddingSize], ax
  jmp short ContDirectivesHandling
; ===================================================================================

SetLeftJustify:    ; 
  inc [cs:LeftJustify]
  jmp short ContDirectivesHandling
; ===================================================================================

SetLong:    ; 
     ; 
  inc [cs:IsLong]

ContDirectivesHandling:   ; 
     ; 
  jmp short DirectiveHandling
; ===================================================================================

NotDigitDirective:   ; 
     ; 
  cmp al, 58h ; 'X'   ; Unsigned hexadecimal
     ; Use upper-case digits (ABCDEF)
  jz short DoPrintHex2
  cmp al, 61h ; 'a'
  jb short NotSmallChar
  cmp al, 7Ah ; 'z'
  jg short NotSmallChar
  and al, 0DFh ; ToUpper
  
; Converted directives to Upper
NotSmallChar:    ; 
     ; 
  cmp al, 58h ; 'X'   ; Unsigned hexadecimal
     ; Was small before "ToUpper".
     ; So if we here -- use low-case digits, (abcdef)
  jz short DoPrintHex
  cmp al, 44h ; 'D'   ; Signed decimal integer
  jz short PrintDecimal
  cmp al, 43h ; 'C'   ; Character
  jz short PrintChar ; Save ptr to format string
  cmp al, 53h ; 'S'   ; String of characters
  jz short PrintString
  call PrintfInitForNext
  jmp NextSymbol
; ===================================================================================

DoPrintHex:    ; 
  mov [cs:HexLettersTblDispl], 6

DoPrintHex2:    ; 
  mov [cs:NumberBase], 16
  jmp short DoPrintInt
; ===================================================================================
  nop

PrintDecimal:    ; 
  mov [cs:NumberBase], 10
  jmp short DoPrintInt
; ===================================================================================
  nop

PrintString:    ; 
  inc [cs:DoPrintString]

PrintChar:    ; 
  push si  ; Save ptr to format string
  mov si, bx
  add bx, 2  ; Traveling to each next arg
     ; Pointer to them are after the pointer to format str.
  mov si, [ds:bp+si+2] ; Addres of char/string to print
  cmp [cs:DoPrintString], 0
  jnz short PrintingString
  lodsb
  cmp al, 0
  jz short Exit_PrintCharOrStr
  call StoreChar_and_FlushBufferIfFull ; DI --- destination in buffer
     ; AL --- symbol
  jmp short Exit_PrintCharOrStr
; ===================================================================================

PrintingString:    ; 
  mov cx, [cs:PaddingSize]
  or cx, cx
  jz short do_print_str
  cmp byte [cs:LeftJustify], 0
  jnz short do_print_str
  push si
  call kindof_strlen_and_pad ; SI -- string adress
     ; CX --- padding size
  pop si

do_print_str:    ; 
     ; 
  push si  ; Save ptr to printed string

do_next_char_in_str:   ; 
  lodsb
  cmp al, 0
  jz short zero_char_met_in_str
  call StoreChar_and_FlushBufferIfFull ; DI --- destination in buffer
     ; AL --- symbol
  jmp short do_next_char_in_str
; ===================================================================================

zero_char_met_in_str:   ; 
  pop si  ; Restore ptr to printed string
  cmp [cs:LeftJustify], 0
  jz short Exit_PrintCharOrStr
  mov cx, [cs:PaddingSize]
  or cx, cx
  jz short Exit_PrintCharOrStr
  call kindof_strlen_and_pad ; SI -- string adress
     ; CX --- padding size

Exit_PrintCharOrStr:   ; 
     ; 
  call PrintfInitForNext
  pop si  ; Restore ptr to format string
  jmp NextSymbol
; Printf_sub endp


; === SUBROUTINE ====================================================

; SI -- string adress
; CX --- padding size

kindof_strlen_and_pad: ; proc near  
  xor dx, dx

do_next_char_check:   ; 
  lodsb
  or al, al
  jz short str_finished
  inc dx
  jmp short do_next_char_check
; ===================================================================================

str_finished:    ; 
  sub cx, dx  ; padding symbols
  jbe short no_padding ; If <0 -- no padding
  call PerformPadding ; CX --- number of symbols for padding,
     ; symbol is saved in cs:PaddingChar

no_padding:    ; 
  retn
; kindof_strlen_and_pad endp

; ===================================================================================

DoPrintInt:    ; 
     ; 
  push si
  mov si, bx
  add bx, 2  ; Traveling to each next arg
     ; The number is after the pointer to format str.
  mov ax, [ds:bp+si+2] ; Number to print -- from memory to AX
  cmp byte [cs:IsLong], 0
  jz short notLongInt ; If not long --- upper two bytes, in DX, are zero
  mov si, bx
  add bx, 2  ; If printing long --- it is placed in next two bytes
  mov dx, [ds:bp+si+2]
  jmp short LongIntPrepared
; ===================================================================================

notLongInt:    ; 
  xor dx, dx  ; If not long --- upper two bytes, in DX, are zero
; Here DX:AX contains high and low part of number

LongIntPrepared:   ; 
  push bx
  mov si, [cs:NumberBase]
  mov cx, [cs:PaddingSize]
  call ExtractDigits ; Here DX:AX contains high and low part of number
     ; SI -- base of system
     ; CX --- padding size
     ; Recursively calls itself
     ; On return --- CX=padding left
  call PerformPadding ; CX --- number of symbols for padding,
     ; symbol is saved in cs:PaddingChar
  call PrintfInitForNext
  pop bx
  pop si
  jmp NextSymbol

; === SUBROUTINE ====================================================

; Here DX:AX contains high and low part of number
; SI -- base of system
; CX --- padding size
; Recursively calls itself
; On return --- CX=padding left

ExtractDigits: ; proc near  
  dec cx  ; -1 for padding - we have real digit
  push ax  ; Save AX -- lower part
  mov ax, dx  ; AX = upper part
  xor dx, dx
  div si  ; Div DX:AX pair (DX=0, AX -- upper part of number) by SI -- base
     ; Result: AX=Quo, DX=Rem
     ; SI is base, so DX --- obtained digit
  mov bx, ax  ; quotient->BX
  pop ax  ; Restore AX -- lower part
  div si  ; DX(reminder of upper/base):AX(lower) by SI(base)
  xchg bx, dx  ; BX -- rem, DX --- upper/base
     ; So, BX --- least significant digit in base=SI system
  push ax  ; Save AX=(lower)/SI(base)
  or ax, dx  ; upper/base OR lower/base?..
  pop ax  ; Restore AX
  jz short AllDigitsDone
  push bx
  call ExtractDigits ; Continue with (DX:AX)/SI --- remains
     ; (not remainder!!!) to generate next digit
  pop bx
  jmp short DoLeftJustify ; AX -- obtainded digit
; ===================================================================================

AllDigitsDone:    ; 
  cmp [cs:LeftJustify], 0
  jnz short DoLeftJustify ; AX -- obtainded digit
  call PerformPadding ; CX --- number of symbols for padding,
     ; symbol is saved in cs:PaddingChar

DoLeftJustify:    ; 
  mov ax, bx  ; AX -- obtainded digit
  cmp al, 10  ; Check if use non-0-9-digits
  jb short generateDigit
  cmp [cs:printHexAsLowerLetters], 0
  jnz short generateDigit
  add al, [cs:HexLettersTblDispl]

generateDigit:    ; 
  mov bx, a0123456789abcd ; "0123456789ABCDEFabcdef"
  push ds
  push cs
  pop ds
  ; assume ds:PrintfSeg
  xlatb   ; Set AL to memory byte DS:[(E)BX + unsigned AL]
     ; So, after, AL --- char code for digit, that was in AL
  pop ds
  ; assume ds:nothing
  push cx
  call StoreChar_and_FlushBufferIfFull ; DI --- destination in buffer
     ; AL --- symbol
  pop cx
  retn
; ExtractDigits endp


; === SUBROUTINE ====================================================

; CX --- number of symbols for padding,
; symbol is saved in cs:PaddingChar

PerformPadding: ; proc near  
  or cx, cx
  jle short locret_9E01
  mov al, byte [cs:PaddingChar]

NextSymbolP:    ; 
  push cx
  call StoreChar_and_FlushBufferIfFull ; DI --- destination in buffer
     ; AL --- symbol
  pop cx
  loop NextSymbolP

locret_9E01:    ; 
  retn
; PerformPadding endp


; === SUBROUTINE ====================================================

; DI --- destination in buffer
; AL --- symbol

StoreChar_and_FlushBufferIfFull: ; proc near 
  stosb
  cmp di, word EndOfPrintfOutBuffer ; word -- to choose correct instruction form; ofs
       ; it is not optimal, though
  jz short BufferIsFull

locret_9E09:    ; 
  retn
; ===================================================================================

BufferIsFull:    ; 
  mov cx, EndOfPrintfOutBuffer-PrintfOutBuffer ; Buffer size
; StoreChar_and_FlushBufferIfFull endp


; === SUBROUTINE ====================================================

; Prints fixed-positioned in memory buffer.
; Number of bytes in CX.

DoPrintSymbols: ; proc near  
  push bx
  mov bx, [cs:OutFileHandler]
  push ds
  push cs
  pop ds
  ; assume ds:PrintfSeg
  mov dx, PrintfOutBuffer ; ofs
  mov ah, 40h
  int 21h  ; DOS - 2+ - WRITE TO FILE WITH HANDLE
     ; BX = file handle, CX = number of bytes to write, DS:DX -> buffer
  pop ds
  ; assume ds:nothing
  pop bx
  mov di, PrintfOutBuffer ; ofs
  retn
; DoPrintSymbols endp


; === SUBROUTINE ====================================================

; DI points to last symbol in buffer.
; If it's address == bufer start address, does nothing.

DoPrintIfNotEmpty: ; proc near  
  cmp di, word PrintfOutBuffer ; ofs
  jz short locret_9E09
  sub di, word PrintfOutBuffer ; ofs
  mov cx, di
  call DoPrintSymbols ; Prints fixed-positioned in memory buffer.
     ; Number of bytes in CX.
  retn
; DoPrintIfNotEmpty endp


; === SUBROUTINE ====================================================


PrintfInitForNext: ; proc near  
  xor ax, ax
  mov [cs:LeftJustify], al
  mov [cs:IsLong], al
  mov [cs:HexLettersTblDispl], al
  mov [cs:PaddingSize], ax
  mov byte [cs:PaddingChar], 20h ; ' '
  mov [cs:DoPrintString], al
  retn
; PrintfInitForNext endp

; PrintfSeg ends

segment  CodeSeg
CodeSegStart:
  jmp short EntryPoint2
; ===================================================================================
word_9E52 dw 6C8h   ; NewBoot addr
aSys1_86 db 'SYS 1.86'
; ===================================================================================

EntryPoint2:    ; 
  push ax  ; Save AX
; Then check for DOS version. Exit if older than 3.00
  mov ah, 30h
  int 21h  ; DOS - GET DOS VERSION
     ; Return: AL = major version number (00h for DOS 1.x)
  xchg ah, al
  cmp ax, 300h
  jb short WrongDOSVersion
  pop ax  ; Restore AX
;
; Copy initial FCB from PSP to dedicated area
; Not done by 2.10
  push cs
  pop es
  ; assume es:CodeSeg
  mov si, 5Ch ; '\'   ; First FCB start in PSP
  mov di, FCBs_Copy ; ofs
  mov cx, 20h ; ' '   ; Two initial FCB's size
  rep movsb  ; Copy them
  push cs
  pop ds
; Set new DTA
; Not done by 2.10
  ; assume ds:CodeSeg
  mov dx, NewDefDTA ; ofs
  mov ah, 1Ah
  int 21h  ; DOS - SET DISK TRANSFER AREA ADDRESS
     ; DS:DX -> disk transfer buffer
  jmp short Continue1
; ===================================================================================
  nop
; Still uses AH=9/INT 21h, because system is unknown

WrongDOSVersion:   ; 
  push cs
  pop ds
  mov dx, aIncorrectDosVe ; "Incorrect DOS version\r\n$" ; ofs
  mov ah, 9
  int 21h  ; DOS - PRINT STRING
     ; DS:DX -> string terminated by "$"
  push es  ; Jump to PSP:0 --- efficiently exit program.
     ; Though, why this perverted way?
  xor ax, ax
  push ax
  retf
; ===================================================================================

printInvalidParameter:   ; 
  mov dx, InvalidParameter_Adr ; ofs
  jmp call_printf_n_exit
; ===================================================================================

printInvalidDriveSpec:   ; 
     ; 
  mov dx, InvalidDriveSpec_Adr ; ofs
  jmp call_printf_n_exit
; ===================================================================================
; New in 3.00
; Before ask to insert system disk, checks if it is
; removable

CheckIfSrcRemovable:   ; 
     ; 
  mov ah, 19h
  int 21h  ; DOS - GET DEFAULT DISK NUMBER
     ; Return:
     ; AL = drive (00h = A:, 01h = B:, etc)
  mov bl, al
  inc bl  ; From DOS to FCB driver numbering
  mov ax, 4408h
  int 21h  ; IOCTL - CHECK IF BLOCK DEVICE REMOVABLE
     ; BL = drive number (00h = default, 01h = A:, etc)
     ;
     ; Return:
     ; CF clear if successful, CF set on error
     ; AX = media type (0000h removable, 0001h fixed)
  jb short AskToInsertSysDisk
  cmp ax, 0
  jz short AskToInsertSysDisk
; If IOCTL is successfull and disk is not removable -- exit
  mov dx, NoSystemOnDefDrv_Adr ; ofs
  push dx
  call PrintfSeg:Printf_sub 
  mov ax, 4C01h
  int 21h  ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
     ; AL = exit code
; ===================================================================================

AskToInsertSysDisk:   ; 
     ; 
  mov al, [currentDrive]
  add al, 40h ; '@'
  mov byte [DriverLetter1_Str], al ; "A"
  mov dx, InsertSystemDisk_Adr ; ofs
  push dx
  call PrintfSeg:Printf_sub
  call WaitForAnyKey ; Clears buffer before and after.
  xor al, al  ; Restore AL -- 00 means correct first FCB (we have
     ; already checked).

Continue1:    ; 
  cmp [FCBs_Copy+1], 20h ; ' ' ; Here we have copy of first FCB from PSP.
     ; FCBs_Copy+1 -- first byte of filename parameter
     ; So, here will be any symbol, entered after [a-z][:][\]
     ;
     ; IMPORTANT!
     ; sys b:\a123.bcd
     ; sys b:/cd.efg
     ; will just transfer system! -- '\', '/' are ignored in that FCB
     ; sys b:cd.efg
;

; Checks are in the same order as for 2.10
; though error messages are -- "Invalid parameter", not "Invalid driver spec."
  jnz short printInvalidParameter
  cmp al, 0FFh
  jz short printInvalidDriveSpec
  cmp [FCBs_Copy], 0 ; ""
  jz short printInvalidDriveSpec
  mov ah, 19h
  int 21h  ; DOS - GET DEFAULT DISK NUMBER
  inc al
  mov [currentDrive], al
  cmp [FCBs_Copy], al ; ""
  jz short printInvalidDriveSpec
; Check if disk is remote. (New check)
; Ralf Brown list states, that this call was present, starting from 3.1
; ( http://www.ctyme.com/intr/rb-2891.htm )
; We are in 3.00, but SYS.COM clearly use it :-)
; Though, http://www.os2museum.com/wp/?page_id=639 states,
; that redirector interface was already present in 3.00, so
; this call was already present too, though, may be, undocumented.

  push ax
  mov bl, [FCBs_Copy] ; ""
  mov ax, 4409h
  int 21h  ; IOCTL - CHECK IF BLOCK DEVICE REMOTE
     ; http://www.ctyme.com/intr/rb-2891.htm
     ;
     ; Return: CF clear if successful
     ; DX = device attribute word
     ;
     ; bit 15: Drive is SUBSTituted
     ; bit 12: Drive is remote
     ; bit  9: Direct I/O not allowed.
     ;
     ; CF set on error, AX = error code (01h,0Fh,15h)
     ;
     ; For remote drives, the other bits appear to be undefined for MS-DOS versions prior to 5.0 (they are all cleared in DOS 5+)
  jb short ioctl_err_or_driver_is_not_remote
  test dx, 1000h ; 12-th bit set in mask
  jz short ioctl_err_or_driver_is_not_remote
  mov dx, CantSysToNetDrv_Adr ; ofs
  jmp call_printf_n_exit
; ===================================================================================
;  We jump here, if IOCTL call returned error
; or driver is not remote.
; In opposite case we print "Incorrect DOS version"...

; Check FAT on destination disk -- as in 2.10

ioctl_err_or_driver_is_not_remote: ; 
     ; 
  pop ax
  push ax
  mov al, [FCBs_Copy] ; Target driver number
  dec al  ;  Adjust from FCB disks numbering to DOS numbering
  mov bx, buffer1 ; ofs
; Read first sector --- boot
  mov dx, 1
  mov cx, dx  ; Read one first (starting numeration from 1!) sector
  int 25h  ; DOS - ABSOLUTE DISK READ (except DOS 4.0/COMPAQ DOS 3.31 >32M partitn)
     ; AL = drive number (0=A, 1=B, etc), DS:BX = Disk Transfer Address (buffer)
     ; CX = number of sectors to read, DX = first relative sector to read
     ; Return: CF set on error
  pop ax  ; Pop flags, left by int 25
  pop ax  ; Restore AX
  jb short CheckSysFiles ; If error --- proceed to checking?!
  cmp byte [buffer1], 0F8h ; 'ш' ; Media ID for HDD "Designated to be used for any
     ; partitioned fixed or removable media, where the
     ; geometry  is defined in the BPB."
     ; DOS 2.10 do not know smaller ID's.
     ; Looks like 3.00 -- too.
  jnb short AdditionalChecksForKnownDisk ; Here v2.10 jumps directly
     ; to CheckSysFiles
  jmp printNoRoomForSystem
; ===================================================================================

CheckSysFiles:    ; 
  jmp CheckSysFiles_ ; Disk number to letter --- source
; ===================================================================================
; If we are sure, that disk is of known nature
; let's check, if it has correct structure
; for system files --- for example, their
; directory entries are first and second, correspondingly

AdditionalChecksForKnownDisk:  ; 
  push ax  ; Save AX
; Set driver letter to system filenames strings
  mov al, byte [FCBs_Copy] ; ""
  add al, 40h ; '@'
  mov byte [aAIbmbio_com], al ; "A:\\IBMBIO.COM"
  mov byte [aAIbmdos_com], al ; "A:\\IBMDOS.COM"
;
  mov ah, 4Eh ; 'N'
  mov dx, aAIbmbio_com ; "A:\\IBMBIO.COM" ; ofs
  mov cx, 6
  int 21h  ; DOS - 2+ - FIND FIRST ASCIZ (FINDFIRST)
     ; CX = search attributes
     ; DS:DX -> ASCIZ filespec
     ; (drive, path, and wildcards allowed)
  jb short local_skip_inc1
  inc byte [IBMBIO_fount_flag]

local_skip_inc1:   ; 
  mov ah, 4Eh ; 'N'
  mov dx, aAIbmdos_com ; "A:\\IBMDOS.COM" ; ofs
  int 21h  ; DOS - 2+ - FIND FIRST ASCIZ (FINDFIRST)
     ; CX = search attributes
     ; DS:DX -> ASCIZ filespec
     ; (drive, path, and wildcards allowed)
  jb short local_skip_inc2
  inc [IBMDOS_fount_flag]

local_skip_inc2:   ; 
  mov dl, byte [FCBs_Copy] ; ""
  mov ah, 32h ; '2'
  push ds
  int 21h  ; DOS - 2+ internal - GET DRIVE PARAMETER BLOCK
     ; DL = drive number, 0 = default, 1 = A, etc.
     ; DS:BX -> Drive Parameter Block (DPB)
     ;
     ; http://www.ctyme.com/intr/rb-2724.htm
; Format of DOS Drive Parameter Block:

; Offset  Size   Description   (Table 01395)
; 00h  BYTE  drive number (00h = A:, 01h = B:, etc)
; 01h  BYTE  unit number within device driver
; 02h  WORD  bytes per sector
; 04h  BYTE  highest sector number within a cluster
; 05h  BYTE  shift count to convert clusters into sectors
; 06h  WORD  number of reserved sectors at beginning of drive
; 08h  BYTE  number of FATs
; 09h  WORD  number of root directory entries
; 0Bh  WORD  number of first sector containing user data
; 0Dh  WORD  highest cluster number (number of data clusters + 1)
; 16-bit FAT if greater than 0FF6h, else 12-bit FAT
; 0Fh  BYTE  number of sectors per FAT
; 10h  WORD  sector number of first directory sector
; 12h  DWORD  address of device driver header (see #01646)
; 16h  BYTE  media ID byte (see #01356)
; 17h  BYTE  00h if disk accessed, FFh if not
; 18h  DWORD  pointer to next DPB
; ---DOS 2.x---
; 1Ch  WORD  cluster containing start of current directory, 0000h=root,
; FFFFh = unknown
; 1Eh 64 BYTEs  ASCIZ pathname of current directory for drive
; ---DOS 3.x---
; 1Ch  WORD  cluster at which to start search for free space when writing
; 1Eh  WORD  number of free clusters on drive, FFFFh = unknown
  mov dx, [bx+10h] ; sector number of first directory sector in DPB
  pop ds
  ; assume ds:nothing
  mov al, byte [ds:FCBs_Copy] ; ""
  dec al  ; From FCB to DOS disk number
  mov bx, buffer1 ; ofs
  mov cx, 1  ; Read first root directory sector
  int 25h  ; DOS - ABSOLUTE DISK READ (except DOS 4.0/COMPAQ DOS 3.31 >32M partitn)
     ; AL = drive number (0=A, 1=B, etc), DS:BX = Disk Transfer Address (buffer)
     ; CX = number of sectors to read, DX = first relative sector to read
     ; Return: CF set on error
  pop ax
  jnb short CheckDir_directly
; If direct read fails --- just continue,
; supposing, that disk is just unknown (and managed by some driver, for example)
  jmp short CheckSysFiles_aft
; ===================================================================================
  nop
; Check if first directory entries are IBMBIO and IBMDOS
; in readed first root directory sector

CheckDir_directly:   ; 
  mov si, buffer1 ; ofs
  cmp byte [si], 0 ; 0 --- directory record unused
  jz short CheckIfSysFilesBothFoundNAtCorrectPlace
  cld
  cmp byte [si], 0E5h ; 'е' ; Directory entry -- erased
  jz short local_check_IBMDOS ; NExt directory entry
  mov di, aIbmbioCom ; "IBMBIO  COM" ; ofs
  mov cx, 11
  repe cmpsb
  jnz short printNoRoomForSystem_2
  dec byte [ds:IBMBIO_position_not_correct]

local_check_IBMDOS:   ; 
  mov si, (buffer1+20h) ; NExt directory entry ; ofs
  cmp byte [si], 0
  jz short CheckIfSysFilesBothFoundNAtCorrectPlace
  cmp byte [si], 0E5h ; 'е'
  jz short CheckIfSysFilesBothFoundNAtCorrectPlace
  mov di, aIbmdosCom ; "IBMDOS  COM" ; ofs
  mov cx, 11
  repe cmpsb

printNoRoomForSystem_2:   ; 
  jnz short printNoRoomForSystem_
  dec byte [ds:IBMDOS_position_not_correct]


; If found, hi and lo bytes of word ptr IBMDOS_found_flag will be 1,
; if found at correct position in root dir, both bytes of IBMDOS_position_correct
; will be equal to 0, otherwise will be 0-1 = 0FFh,
; So if not found at all or in correct position in dir -- OK

CheckIfSysFilesBothFoundNAtCorrectPlace: ; 
     ; 
  mov ax, word [ds:IBMDOS_fount_flag]
  and ax, word [ds:IBMDOS_position_not_correct]
  jnz short printNoRoomForSystem_

CheckSysFiles_aft:   ; 
  pop ax

CheckSysFiles_:    ; 
  add al, 40h ; '@'   ; Disk number to letter --- source
  mov byte [ds:aAIbmbio_com], al ; "A:\\IBMBIO.COM"
  mov byte [ds:aAIbmdos_com], al ; "A:\\IBMDOS.COM"
  cld
  mov dx, aAIbmbio_com ; "A:\\IBMBIO.COM" ; ofs
  mov di, IBMBIO_hndlr ; ofs
  call GetFileSizeDateTime ; DX -- filename string
     ; DI -- address of file handler variable
     ; CF -- on error,
     ; If OK, Size, Time and Date are saved after the Handler
     ; Strange that size is saved twice -- two times lo word
     ; and two times hi word.
     ;
     ; Routine is the same as in 2.10
  jnb short checkIBMDOS

AskToInsertSysDisk_:   ; 
     ; 
  jmp CheckIfSrcRemovable
; ===================================================================================

checkIBMDOS:    ; 
  mov dx, aAIbmdos_com ; "A:\\IBMDOS.COM" ; ofs
  mov di, IBMDOS_hndlr ; ofs
  call GetFileSizeDateTime ; DX -- filename string
     ; DI -- address of file handler variable
     ; CF -- on error,
     ; If OK, Size, Time and Date are saved after the Handler
     ; Strange that size is saved twice -- two times lo word
     ; and two times hi word.
     ;
     ; Routine is the same as in 2.10
  jb short AskToInsertSysDisk_
  mov cx, sp
  sub cx, 0B32h ; Approximation of code+stack size?
  mov [ds:PutativeMaxFreeMem], cx
  call ReadFilesToMem ; Tries to read IBMBIO and IBMDOS to memory.
     ; Read as much as fits. Sets CF in case of errors,
     ; clears otherwise.
     ;
     ; Same as in 2.10
  jb short AskToInsertSysDisk_
; Now checking files at target disk root

; Set target driver letter to system filenames strings and search mask
  mov al, byte [ds:FCBs_Copy] ; ""
  add al, 40h ; '@'
  mov byte [ds:aAIbmbio_com], al ; "A:\\IBMBIO.COM"
  mov byte [ds:aAIbmdos_com], al ; "A:\\IBMDOS.COM"
  mov byte [ds:anyFileMask], al ; "A:\\*.*"
  mov ah, 4Eh ; 'N'
  mov dx, anyFileMask ; "A:\\*.*" ; ofs
  mov cx, 10110b ; Hidden + system + dir, any cobination of, inclusive
  int 21h  ; DOS - 2+ - FIND FIRST ASCIZ (FINDFIRST)
     ; CX = search attributes (bits 0 and 5 ignored)
     ; DS:DX -> ASCIZ filespec
     ; (drive, path, and wildcards allowed)
; 7  pending deleted files (Novell DOS, OpenDOS)
; 6  unused
; 5  archive
; 4  directory
; 3  volume label.
; 2  system
; 1   hidden
; 0  read-only
; http://www.ctyme.com/intr/rb-2803.htm
  jnb short someFilesFound

;
; No files, check for volume label
  mov ah, 4Eh ; 'N'
  mov dx, anyFileMask ; "A:\\*.*" ; ofs
; For search attributes other than 08h, all files
; with at MOST the specified combination of hidden,
; system, and directory attributes will be returned.
; Under DOS 2.x, searching for attribute 08h (volume
; label) will also return normal files, while under
; DOS 3.0+ only the volume label (if any) will be returned..
  mov cx, 1000b ; Vol Label
  int 21h  ; DOS - 2+ - FIND FIRST ASCIZ (FINDFIRST)
     ; CX = search attributes
     ; DS:DX -> ASCIZ filespec
     ; (drive, path, and wildcards allowed)
  jb short noneFilesFound
  jmp printNoRoomForSystem
; ===================================================================================
;
; If some files found --- try to find IBMBIO.COM
; IF not found --- exit.

someFilesFound:    ; 
  mov dx, aAIbmbio_com ; "A:\\IBMBIO.COM" ; ofs
  mov cx, 111b
  mov ah, 4Eh
  int 21h  ; DOS - 2+ - FIND FIRST ASCIZ (FINDFIRST)
     ; CX = search attributes
     ; DS:DX -> ASCIZ filespec
     ; (drive, path, and wildcards allowed)
  jnb short someFilesFound_checkIBMDOS

printNoRoomForSystem_:   ; 
     ; 
  jmp printNoRoomForSystem
; ===================================================================================
;
; Otherwise --- check for IBMDOS.COM

someFilesFound_checkIBMDOS:  ; 
  mov dx, aAIbmdos_com ; "A:\\IBMDOS.COM" ; ofs
  mov ah, 4Eh
  int 21h  ; DOS - 2+ - FIND FIRST ASCIZ (FINDFIRST)
     ; CX = search attributes
     ; DS:DX -> ASCIZ filespec
     ; (drive, path, and wildcards allowed)
  jb short printNoRoomForSystem_
;
; Here we, if none files were found, or are some files found,
; and both IBMxxx.COM are found too

noneFilesFound:    ; 
  mov dx, aAIbmbio_com ; "A:\\IBMBIO.COM" ; ofs
  mov cx, 0  ;  Clear attributes of IBMBIO.COM
  mov ax, 4301h
  int 21h  ; DOS - 2+ - SET FILE ATTRIBUTES
     ; DS:DX -> ASCIZ file name
     ; CX = file attribute bits
  mov dx, aAIbmdos_com ; "A:\\IBMDOS.COM" ; ofs
  mov cx, 0  ;  Clear attributes of IBMDOS.COM
  mov ax, 4301h
  int 21h  ; DOS - 2+ - SET FILE ATTRIBUTES
     ; DS:DX -> ASCIZ file name
     ; CX = file attribute bits
; (Re)create IBMxxx.COM with correct attributes --- sys+hid+r/o
  mov dx, aAIbmbio_com ; "A:\\IBMBIO.COM" ; ofs
  mov cx, 111b
  mov ah, 3Ch
  int 21h  ; DOS - 2+ - CREATE A FILE WITH HANDLE (CREAT)
     ; CX = attributes for file
     ; DS:DX -> ASCIZ filename (may include drive and path)
  jb short printNoRoomForSystem_3
  mov [ds:TargetIBMBIO_hndlr], ax
  mov dx, aAIbmdos_com ; "A:\\IBMDOS.COM" ; ofs
  mov ah, 3Ch
  int 21h  ; DOS - 2+ - CREATE A FILE WITH HANDLE (CREAT)
     ; CX = attributes for file
     ; DS:DX -> ASCIZ filename (may include drive and path)
  jb short printNoRoomForSystem_3
  mov [ds:TargetIBMDOS_hndlr], ax
  push ds
  mov ah, 32h ; '2'
  mov dl, byte [ds:FCBs_Copy] ; ""
  int 21h  ; DOS - 2+ internal - GET DRIVE PARAMETER BLOCK
     ; DL = drive number, 0 = default, 1 = A, etc.
; For DPB: cluster containing start of current directory, 0000h=root
; Why to change it?! Some internal undocumented hack?
; --- way to set root dir as current?
  mov word [bx+1Ch], 0
  pop ds

ReadNWriteNextPart:   ; 
  call WriteSysFilesFromMem ; Same as from 2.10
  jb short printNoRoomForSystem_3
  mov ax, [ds:IBMDOS_size_hi_1]
  or ax, [ds:IBMDOS_size_lo_1]
  or ax, [ds:IBMBIO_size_hi_1]
  or ax, [ds:IBMBIO_size_lo_1]
  jz short RestoreFilesDateNTime
  call ReadFilesToMem ; Tries to read IBMBIO and IBMDOS to memory.
     ; Read as much as fits. Sets CF in case of errors,
     ; clears otherwise.
     ;
     ; Same as in 2.10
  jnb short ReadNWriteNextPart
  jmp CheckIfSrcRemovable ; Also asks to insert system disk
; ===================================================================================

printNoRoomForSystem_3:   ; 
     ; 
  jmp printNoRoomForSystem
; ===================================================================================
; Looks like dead, unreachable, code
  mov dx, IncompatibleSysSize_Adr ; ofs
  jmp short call_printf_n_exit
; ===================================================================================
  nop

RestoreFilesDateNTime:   ; 
  mov cx, [ds:IBMBIO_time]
  mov dx, [ds:IBMBIO_date]
  mov bx, [ds:TargetIBMBIO_hndlr]
  mov ax, 5701h
  int 21h  ; DOS - 2+ - SET FILE'S DATE/TIME
     ; BX = file handle, CX = time to be set
     ; DX = date to be set
  mov ah, 3Eh
  int 21h  ; DOS - 2+ - CLOSE A FILE WITH HANDLE
     ; BX = file handle
  mov cx, [ds:IBMDOS_time]
  mov dx, [ds:IBMDOS_date]
  mov bx, [ds:TargetIBMDOS_hndlr]
  mov ax, 5701h
  int 21h  ; DOS - 2+ - SET FILE'S DATE/TIME
     ; BX = file handle, CX = time to be set
     ; DX = date to be set
  mov ah, 3Eh
  int 21h  ; DOS - 2+ - CLOSE A FILE WITH HANDLE
     ; BX = file handle
  call WriteBoot ; Writes boot with correct BPB
     ; For HDD takes BPB from some DOS internal tables.
     ; For FDD --- uses it's own collection of BPB's for different MediaID's
     ; Rewritten for DOS 3.0!
  mov dx, SystemTransferred_Adr ; ofs
  push dx
  call PrintfSeg:Printf_sub
  xor al, al
  jmp short exit_program
; ===================================================================================

call_printf_n_exit:   ; 
     ; 
  push dx  ; DX contains address of message
  call PrintfSeg:Printf_sub
  mov dx, CR_LF_Adr ; ofs
  push dx
  call PrintfSeg:Printf_sub
  mov al, 0FFh ; Error code, -1

exit_program:    ; 
  mov ah, 4Ch
  int 21h  ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
     ; AL = exit code
; ===================================================================================
; START OF FUNCTION CHUNK FOR ReadFilesToMem

local_error:    ; 
     ; 
  pop cx  ; Restore saved CX

Set_CF_NExit_A0E2:   ; 
  stc

Retn_A0E3:    ; 
  retn
; END OF FUNCTION CHUNK FOR ReadFilesToMem

; === SUBROUTINE ====================================================

; Tries to read IBMBIO and IBMDOS to memory.
; Read as much as fits. Sets CF in case of errors,
; clears otherwise.
;
; Same as in 2.10

ReadFilesToMem: ; proc near  
  mov cx, [ds:PutativeMaxFreeMem]
  mov bx, [ds:IBMBIO_hndlr]
  mov dx, buffer1 ; ofs
  push cx
  cmp [ds:IBMBIO_size_hi_1], 0 ; If size above 64k -- read max size, which can be
     ; disposed in free mem.
  ja short PRoceedToRead
  cmp [ds:IBMBIO_size_lo_1], cx ; If size is large than can feet in memory --
     ; read max size, which can be disposed in free mem.
  ja short PRoceedToRead
  mov cx, [ds:IBMBIO_size_lo_1] ; Else read exactly file size bytes

PRoceedToRead:    ; 
     ; 
  mov ah, 3Fh
  int 21h  ; DOS - 2+ - READ FROM FILE WITH HANDLE
     ; BX = file handle, CX = number of bytes to read
     ; DS:DX -> buffer
     ;
     ; Returns AX = number of bytes actually read
  jb short local_error
  cmp ax, cx  ; Readed less than requested size?
  jnz short local_error
  add dx, ax  ; Calculate first free byte address
  mov [ds:IBMDOS_buffer_addr], dx
  sub [ds:IBMBIO_size_lo_1], ax ; Calculate, how much left to read
  sbb [ds:IBMBIO_size_hi_1], 0
  pop cx
  sub cx, ax  ; Calculate free memory left
  mov bx, [ds:IBMDOS_hndlr]
  cmp word [ds:IBMDOS_size_hi_1], 0 ; If size above 64k -- read max size, which can be
     ; disposed in free mem.
  ja short ProceedToRead2
  cmp word [ds:IBMDOS_size_lo_1], cx ; If size is large than can feet in memory --
     ; read max size, which can be disposed in free mem.
  ja short ProceedToRead2
  mov cx, word [ds:IBMDOS_size_lo_1] ; Else read exactly file size bytes

ProceedToRead2:    ; 
     ; 
  mov ah, 3Fh
  int 21h  ; DOS - 2+ - READ FROM FILE WITH HANDLE
     ; BX = file handle, CX = number of bytes to read
     ; DS:DX -> buffer
     ;
     ; Returns AX = number of bytes actually read
  jb short Retn_A0E3 ; Error -- exit
  cmp ax, cx
  jnz short Set_CF_NExit_A0E2 ;  Readed less than requested -- exit
  add dx, ax  ; Calculate first free byte address
  mov [ds:After_IBMDOS_buffer], dx
  sub [ds:IBMDOS_size_lo_1], ax ;  Calculate, how much left to read
  sbb word [ds:IBMDOS_size_hi_1], 0
  clc   ; Clear CF -- no error.

Retn_A14C:    ; 
  retn
; ReadFilesToMem endp 


; === SUBROUTINE ====================================================

; DX -- filename string
; DI -- address of file handler variable
; CF -- on error,
; If OK, Size, Time and Date are saved after the Handler
; Strange that size is saved twice -- two times lo word
; and two times hi word.
;
; Routine is the same as in 2.10

GetFileSizeDateTime:  ;proc near  
  mov ax, 3D00h
  int 21h  ; DOS - 2+ - OPEN DISK FILE WITH HANDLE
     ; DS:DX -> ASCIZ filename
     ; AL = access mode
     ; 0 - read
  jb short Retn_A14C
  stosw   ; Save file handler
; Determine and save file size
  mov bx, ax
  mov ax, 4202h
  xor cx, cx
  xor dx, dx
  int 21h  ; DOS - 2+ - MOVE FILE READ/WRITE POINTER (LSEEK)
     ; AL = method: offset from end of file
     ; Returns: DX:AX = new file position in bytes from
     ; start of file
  stosw   ;  Save lower size word. Twice...
  stosw
  mov ax, dx
  stosw   ; Save higher size word. Twice...
  stosw
  xor dx, dx
  mov ax, 4200h
  int 21h  ; DOS - 2+ - MOVE FILE READ/WRITE POINTER (LSEEK)
     ; AL = method: offset from beginning of file
  mov ax, 5700h
  int 21h  ; DOS - 2+ - GET FILE'S DATE/TIME
     ; BX = file handle
     ; http://www.ctyme.com/intr/rb-2992.htm
     ; CX = file's time (see #01665)
     ; DX = file's date
  mov ax, cx
  stosw   ; Store time
  mov ax, dx
  stosw   ; Store date

Retn_A178:    ; 
     ; WriteSysFilesFromMem+23 j ...
  retn
; GetFileSizeDateTime endp

; ===================================================================================

printNoRoomForSystem:   ; 
     ; 
  mov dx, NoRoomForSystem_Adr ; ofs
  jmp call_printf_n_exit

; === SUBROUTINE ====================================================

; Same as from 2.10

WriteSysFilesFromMem: ;  proc near  
  mov dx, buffer1 ; ofs
  mov cx, [ds:IBMDOS_buffer_addr]
  sub cx, dx
  jz short ProceedToIBMDOS
  mov bx, [ds:TargetIBMBIO_hndlr]
  mov ah, 40h
  int 21h  ; DOS - 2+ - WRITE TO FILE WITH HANDLE
     ; BX = file handle, CX = number of bytes to write, DS:DX -> buffer
  jb short Retn_A178
  cmp ax, cx  ; Written less than should?
  jnz short WriteError

ProceedToIBMDOS:   ; 
  mov dx, [ds:IBMDOS_buffer_addr]
  mov cx, [ds:After_IBMDOS_buffer]
  sub cx, dx
  jz short Retn_A178
  mov bx, [ds:TargetIBMDOS_hndlr]
  mov ah, 40h
  int 21h  ; DOS - 2+ - WRITE TO FILE WITH HANDLE
     ; BX = file handle, CX = number of bytes to write, DS:DX -> buffer
  jb short Retn_A178
  cmp ax, cx  ; Written less than should?
  jz short Retn_A178

WriteError:    ; 
  stc
  retn
; WriteSysFilesFromMem endp


; === SUBROUTINE ====================================================

; Writes boot with correct BPB
; For HDD takes BPB from some DOS internal tables.
; For FDD --- uses it's own collection of BPB's for different MediaID's
; Rewritten for DOS 3.0!

WriteBoot: ; proc near  
  mov ah, 32h ; '2'
  mov dl, byte [ds:FCBs_Copy] ; ""
  int 21h  ; DOS - 2+ internal - GET DRIVE PARAMETER BLOCK
     ; DL = drive number, 0 = default, 1 = A, etc.
     ; DS:BX -> Drive Parameter Block (DPB)
     ;
     ; http://www.ctyme.com/intr/rb-2724.htm
; Format of DOS Drive Parameter Block:

; Offset  Size   Description   (Table 01395)
; 00h  BYTE  drive number (00h = A:, 01h = B:, etc)
; 01h  BYTE  unit number within device driver
; 02h  WORD  bytes per sector
; 04h  BYTE  highest sector number within a cluster
; 05h  BYTE  shift count to convert clusters into sectors
; 06h  WORD  number of reserved sectors at beginning of drive
; 08h  BYTE  number of FATs
; 09h  WORD  number of root directory entries
; 0Bh  WORD  number of first sector containing user data
; 0Dh  WORD  highest cluster number (number of data clusters + 1)
; 16-bit FAT if greater than 0FF6h, else 12-bit FAT
; 0Fh  BYTE  number of sectors per FAT
; 10h  WORD  sector number of first directory sector
; 12h  DWORD  address of device driver header (see #01646)
; 16h  BYTE  media ID byte (see #01356)
; 17h  BYTE  00h if disk accessed, FFh if not
; 18h  DWORD  pointer to next DPB
; ---DOS 2.x---
; 1Ch  WORD  cluster containing start of current directory, 0000h=root,
; FFFFh = unknown
; 1Eh 64 BYTEs  ASCIZ pathname of current directory for drive
; ---DOS 3.x---
; 1Ch  WORD  cluster at which to start search for free space when writing
; 1Eh  WORD  number of free clusters on drive, FFFFh = unknown

  mov al, [bx+16h] ; media ID byte
     ;
     ; http://www.ctyme.com/intr/rb-2590.htm#Table1356
     ; FFh  floppy, double-sided, 8 sectors per track, 40 tracks (320K)
     ; FEh  floppy, single-sided, 8 sectors per track, 40 tracks  (160K)
     ; FDh  floppy, double-sided, 9 sectors per track, 40 tracks  (360K)
     ; FCh  floppy, single-sided, 9 sectors per track, 40 tracks  (180K)
     ; + up to DOS 3.0 (only 5.25 floppies, though ID's were used later for similar 3.5):
     ; FBh  floppy, double-sided, 8 sectors per track, 80 tracks (640K)
     ; FAh  floppy, single-sided, 8 sectors per track, 80 tracks (320K)
     ; F9h  floppy, double-sided, 15/9/18 sectors per track, 80 tracks (1200K/720K/1440K - 5.25/3.5/3.5)
     ; See also: http://en.wikipedia.org/wiki/File_Allocation_Table#FATID
  push cs

loc_A1C0:    ; Restore DS
  pop ds
  ; assume ds:CodeSeg
  sub al, 0F8h ; 'ш'
  cbw   ; AX = MediaID-0F8h, DOS 3.00 knows floppy codes up to F9h
     ; (according to http://en.wikipedia.org/wiki/File_Allocation_Table)
  mov bx, ax
  shl bx, 1  ; mul MediaID - 0F8h by 2, offset in table
  mov si, [bx+DiskBPBsPtrsTable]
  or si, si
  jnz short notHDD ; Zero is defined in table only for 0F8h code
; Looks like here we are only for HDD
  mov byte [MediaIDInBPB], 0F8h ; 'ш' ; Media ID
  xor bx, bx  ; BX = 0
  mov [lds_offset], bx ; 0
  push ds  ; Save DS
  lds bx, dword [lds_offset]
  ; assume ds:nothing
  mov bx, [bx] ; DS:BX = 70:0 --- IBMBIO begins here...
     ; Some pointer there for some data?
  mov al, [bx]
  inc bx
  pop ds  ; Restore DS
  mov [ds:lds_offset], bx ; BX -- value of pointer at 70:0 points + 1...
  mov byte [ds:PossiblyPhysicalDiskNumber], al ; AL -- value, obtained from
     ; where pointer at 70:0 points + 1...
  int 11h  ; EQUIPMENT DETERMINATION
     ; Return: AX = equipment flag bits
     ; http://www.ctyme.com/intr/rb-0575.htm
  rol al, 1
  rol al, 1  ; Bring bits 6-7 to 0-1,
     ; Bits 7-6 - number of floppies installed less 1 (if bit 0 set)
  and al, 11b  ; Mask num_flop-1
  jnz short more_than_1_floppy
  inc al
; Some magic with disks number...

more_than_1_floppy:   ; 
  inc al
; bits fdds  al_val
; 00 1     2
; 00 2     2
; 00 3     3
; 00 4     4
; Accounting "virtual" B for one floppy system?
  mov dl, byte [ds:FCBs_Copy] ; ""
  sub dl, al
  dec dl  ; Disk code from FCB (-1 for DOS numbering)
     ; minus floppies number
; May be this is related to some internal DOS data structures organization
; If target disk number is last floppy --- adjusts by 13, DOS2 BPB size...
  jz short loc_A20E
  add [ds:lds_offset], 13h ; Size of DOS 2.0 BPB
     ; Kind of second HDD detection
     ; and taking into account?
  inc byte [ds:PossiblyPhysicalDiskNumber]
;
; Copy disk BPB from some internall structure

loc_A20E:    ; 
  lds si, dword [ds:lds_offset]
  push cs
  pop es
  mov di, BPB_Start ; Bytes per logical sector in powers of two, 512 ; ofs
  mov cx, 13h  ; ; Size of DOS 2.0 BPB
  cld
  rep movsb  ; Move byte DS:[SI] to ES:[DI]
     ; Looks like from some segment in IBMIO to our BPB image
  push cs
  pop ds  ; Restore DS
  ; assume ds:CodeSeg
  jmp short ProceedToWrite
; ===================================================================================

notHDD:     ; 
  cmp si, 0FFFFh
  jz short skip_writeBoot
; Move part of BPB, from byte 2 (not 0!) to 13 -- 12 bytes (DOS2 part of it)
; Index of correct BPB --- in SI, from table DiskBPBsPtrsTable
  push ds
  pop es
  mov di, SectorsPerCluster ; ofs
  mov cx, 12h
  cld
  rep movsb  ; Move 12h=18 bytes from DS:[SI] to ES:[DI]
; We have prepaired correct BPB for disk ID
; (using some internal buffer for HDD BPB and our own table for floppies)

ProceedToWrite:    ; 
  mov al, byte [FCBs_Copy] ; Target disk
  dec al  ; From FCB to DOS disk number
  mov bx, NewBootBuffer ; Near jump and nop ; ofs
  xor dx, dx
  mov cx, dx
  inc cx
  int 26h  ; DOS - ABSOLUTE DISK WRITE (except DOS 4.0/COMPAQ DOS 3.31 >32M partn)
     ; AL = drive number (0=A, 1=B, etc), DS:BX = Disk Transfer Address (buffer)
     ; CX = number of sectors to write, DX = first relative sector to write
     ; Return: CF set on error
  pop ax

skip_writeBoot:    ; 
  retn
; WriteBoot endp 


; === SUBROUTINE ====================================================

; Clears buffer before and after.

WaitForAnyKey: ; proc near  ; 
  mov ax, 0C08h
  int 21h  ; DOS - CLEAR KEYBOARD BUFFER
     ; AL must be 01h, 06h, 07h, 08h, or 0Ah.
  mov ax, 0C00h
  int 21h  ; DOS - CLEAR KEYBOARD BUFFER
     ; AL must be 01h, 06h, 07h, 08h, or 0Ah.
     ;
     ; (AL=00h, or any
     ; other not from the list above
     ;  -- "the buffer is flushed but no input is attempted")
  mov dx, CR_LF_Adr ; ofs
  push dx
  call PrintfSeg:Printf_sub
  retn
; WaitForAnyKey endp 

; ===================================================================================
CantSysToNetDrv_Str db 'Cannot SYS to a Network drive',0
CantSysToNetDrv_Adr dw CantSysToNetDrv_Str  ; 
aIncorrectDosVe db 'Incorrect DOS version',0Dh,0Ah,'$' ; 
InvalidDriveSpec_Str db 'Invalid drive specification',0
InvalidDriveSpec_Adr dw InvalidDriveSpec_Str 
InvalidParameter_Str db 'Invalid parameter',0
InvalidParameter_Adr dw InvalidParameter_Str
NoRoomForSystem_Str db 'No room for system on destination disk',0
NoRoomForSystem_Adr dw NoRoomForSystem_Str
IncompatibleSysSize_Str db 'Incompatible system size',0
IncompatibleSysSize_Adr dw IncompatibleSysSize_Str
SystemTransferred_Str db 'System transferred',0Dh,0Ah,0
SystemTransferred_Adr dw SystemTransferred_Str
NoSystemOnDefDrv_Str db 'No system on default drive',0Dh,0Ah,0
NoSystemOnDefDrv_Adr dw NoSystemOnDefDrv_Str
InsertSystemDisk_Str db 'Insert system disk in drive %c',0Dh,0Ah
  db 'and strike any key when ready',0
InsertSystemDisk_Adr dw InsertSystemDisk_Str
DriverLetter1_Adr dw DriverLetter1_Str
DriverLetter1_Str db 'A',0              ; 
CR_LF_Str db 0Dh,0Ah,0
CR_LF_Adr dw CR_LF_Str
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0
; CodeSeg  ends

; Displ: 7Bh, StackSeg = PSP seg + 10 + 7Bh, size = 80h
; ===================================================================================

segment StackSeg  
StackSegBegin:
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 16
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 32
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 48
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 64
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 80
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 96
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 112
StackSegEnd:
; StackSeg ends

; ===================================================================================

currentDrive db 0   ; 
     ; 
aAIbmbio_com db 'A:\IBMBIO.COM',0    ; 
     ; 
aAIbmdos_com db 'A:\IBMDOS.COM',0    ; 
     ; 
IBMDOS_fount_flag db 0   ; 
     ; 
IBMBIO_fount_flag db 0   ; 
IBMDOS_position_not_correct db 1 ; 
     ; 
IBMBIO_position_not_correct db 1 ; 
;
; IBMBIO.COM data
IBMBIO_hndlr dw 0   ; 
     ; 
IBMBIO_size_lo_1 dw 0   ; 
     ; 
IBMBIO_size_lo_2 dw 0
IBMBIO_size_hi_1 dw 0   ; 
     ; 
IBMBIO_size_hi_2 dw 0
IBMBIO_time dw 0   ; 
IBMBIO_date dw 0   ; 
TargetIBMBIO_hndlr dw 0   ; 
     ; 
;
; IBMDOS.COM data
IBMDOS_hndlr dw 0   ; 
     ; 
IBMDOS_size_lo_1 dw 0   ; 
     ; 
IBMDOS_size_lo_2 dw 0
IBMDOS_size_hi_1 dw 0   ; 
     ; 
IBMDOS_size_hi_2 dw 0
IBMDOS_time dw 0   ; 
IBMDOS_date dw 0   ; 
TargetIBMDOS_hndlr dw 0   ; 
     ; 
anyFileMask db 'A:\*.*',0           ; 
FCBs_Copy db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
     ; 
aIbmdosCom db 'IBMDOS  COM'        ; 
aIbmbioCom db 'IBMBIO  COM'        ; 
NewDefDTA db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 0
     ; 
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 16
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 32
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 48
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 64
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 80
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 96
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 112
PutativeMaxFreeMem dw 0   ; 
IBMDOS_buffer_addr dw 0   ; 
After_IBMDOS_buffer dw 0  ; 
lds_offset dw 0   ; 
lds_segment dw 70h
;==================================================
;=New boot=========================================
;==================================================
NewBootBuffer db 0EBh, 29h, 90h ; 0 ; 
     ; Near jump and nop
aIbm3_0  db 'IBM  3.0'
; BPB starts at 0Bh offset (in boot sector)
; In DOS2+ it size 0x0D = 13bytes, DOS 3.0 -- 19 bytes (3.2+ --- 25, 6 bytes more)
; Details: http://en.wikipedia.org/wiki/File_Allocation_Table#BPB
BPB_Start dw 200h   ; 
     ; Bytes per logical sector in powers of two, 512
SectorsPerCluster db 8  ; 
     ; Logical sectors per cluster.
  dw 1   ; Count of reserved logical sectors ---
     ; The number of logical sectors before the first FAT in
     ; the file system image. At least 1.
  db    2   ; Number of File Allocation Tables
  dw 200h   ; Maximum number of FAT12 or FAT16 root directory entries.
  dw 5103h  ; Total logical sectors?!!
MediaIDInBPB db 0F8h ; ш  ; 
     ; Media ID
  dw 8   ; Logical sectors per FAT
; Part of DOS 3.0+ BPB
  dw 11h   ; Physical sectors per track for disks with INT 13h CHS geometry,
     ; e.g., 15 for a "1.20 MB" (1200 KiB) floppy.
  dw 4   ; Number of heads for disks with INT 13h CHS
     ; geometry, e.g., 2 for a double sided floppy.
  dw 1   ; Count of hidden sectors preceding the partition that contains this FAT volume.
     ; This field should always be zero on media that are not partitioned.
     ; This DOS 3.0 entry is incompatible with a similar entry at offset 0x01C in BPBs since DOS 3.31.
; End of DOS 3.0 BPB
;
; Are there one more DOS 3.0-specific BPB byte?
; At least, all BPB's images below have one zero byte at the end...
;
; Value 80h for HDD (which can be increased in code) hints
; that this byte is smth. similar to 0x19 byte in DOS 3.31:
; "Physical drive number (0x00 for (first) removable media, 0x80 for (first) fixed disk as per INT 13h)."
PossiblyPhysicalDiskNumber db  80h ; Ђ ; 
     ;
; Rest of the boot sector
  db 0, 0, 0, 0, 0, 0Fh, 0, 0, 0, 0, 1, 0, 0FAh, 33h, 0C0h, 8Eh; 0
  db 0D0h, 0BCh, 0, 7Ch, 16h, 7, 0BBh, 78h, 0, 36h, 0C5h, 37h, 1Eh, 56h, 16h, 53h; 16
  db 0BFh, 20h, 7Ch, 0B9h, 0Bh, 0, 0FCh, 0ACh, 26h, 80h, 3Dh, 0, 74h, 3, 26h, 8Ah; 32
  db 5, 0AAh, 8Ah, 0C4h, 0E2h, 0F1h, 6, 1Fh, 89h, 47h, 2, 0C7h, 7, 20h, 7Ch, 0FBh; 48
  db 0CDh, 13h, 72h, 67h, 0A0h, 10h, 7Ch, 98h, 0F7h, 26h, 16h, 7Ch, 3, 6, 1Ch, 7Ch; 64
  db 3, 6, 0Eh, 7Ch, 0A3h, 34h, 7Ch, 0A3h, 2Ch, 7Ch, 0B8h, 20h, 0, 0F7h, 26h, 11h; 80
  db 7Ch, 8Bh, 1Eh, 0Bh, 7Ch, 3, 0C3h, 48h, 0F7h, 0F3h, 1, 6, 2Ch, 7Ch, 0BBh, 0; 96
  db 5, 0A1h, 34h, 7Ch, 0E8h, 96h, 0, 0B8h, 1, 2, 0E8h, 0AAh, 0, 72h, 19h, 8Bh; 112
  db 0FBh, 0B9h, 0Bh, 0, 0BEh, 0BEh, 7Dh, 0F3h, 0A6h, 75h, 0Dh, 8Dh, 7Fh, 20h, 0BEh, 0C9h; 128
  db 7Dh, 0B9h, 0Bh, 0, 0F3h, 0A6h, 74h, 18h, 0BEh, 5Fh, 7Dh, 0E8h, 61h, 0, 32h, 0E4h; 144
  db 0CDh, 16h, 5Eh, 1Fh, 8Fh, 4, 8Fh, 44h, 2, 0CDh, 19h, 0BEh, 0A8h, 7Dh, 0EBh, 0EBh; 160
  db 0A1h, 1Ch, 5, 33h, 0D2h, 0F7h, 36h, 0Bh, 7Ch, 0FEh, 0C0h, 0A2h, 31h, 7Ch, 0A1h, 2Ch; 176
  db 7Ch, 0A3h, 32h, 7Ch, 0BBh, 0, 7, 0A1h, 2Ch, 7Ch, 0E8h, 40h, 0, 0A1h, 18h, 7Ch; 192
  db 2Ah, 6, 30h, 7Ch, 40h, 50h, 0E8h, 4Eh, 0, 58h, 72h, 0CFh, 28h, 6, 31h, 7Ch; 208
  db 76h, 0Ch, 1, 6, 2Ch, 7Ch, 0F7h, 26h, 0Bh, 7Ch, 3, 0D8h, 0EBh, 0D9h, 8Ah, 2Eh; 224
  db 15h, 7Ch, 8Ah, 16h, 1Eh, 7Ch, 8Bh, 1Eh, 32h, 7Ch, 0EAh, 0, 0, 70h, 0, 0ACh; 240
  db 0Ah, 0C0h, 74h, 22h, 0B4h, 0Eh, 0BBh, 7, 0, 0CDh, 10h, 0EBh, 0F2h, 33h, 0D2h, 0F7h; 256
  db 36h, 18h, 7Ch, 0FEh, 0C2h, 88h, 16h, 30h, 7Ch, 33h, 0D2h, 0F7h, 36h, 1Ah, 7Ch, 88h; 272
  db 16h, 1Fh, 7Ch, 0A3h, 2Eh, 7Ch, 0C3h, 0B4h, 2, 8Bh, 16h, 2Eh, 7Ch, 0B1h, 6, 0D2h; 288
  db 0E6h, 0Ah, 36h, 30h, 7Ch, 8Bh, 0CAh, 86h, 0E9h, 8Bh, 16h, 1Eh, 7Ch, 0CDh, 13h, 0C3h; 304
  db 0Dh, 0Ah, 4Eh, 6Fh, 6Eh, 2Dh, 53h, 79h, 73h, 74h, 65h, 6Dh, 20h, 64h, 69h, 73h; 320
  db 6Bh, 20h, 6Fh, 72h, 20h, 64h, 69h, 73h, 6Bh, 20h, 65h, 72h, 72h, 6Fh, 72h, 0Dh; 336
  db 0Ah, 52h, 65h, 70h, 6Ch, 61h, 63h, 65h, 20h, 61h, 6Eh, 64h, 20h, 73h, 74h, 72h; 352
  db 69h, 6Bh, 65h, 20h, 61h, 6Eh, 79h, 20h, 6Bh, 65h, 79h, 20h, 77h, 68h, 65h, 6Eh; 368
  db 20h, 72h, 65h, 61h, 64h, 79h, 0Dh, 0Ah, 0, 0Dh, 0Ah, 44h, 69h, 73h, 6Bh, 20h; 384
  db 42h, 6Fh, 6Fh, 74h, 20h, 66h, 61h, 69h, 6Ch, 75h, 72h, 65h, 0Dh, 0Ah, 0, 49h; 400
  db 42h, 4Dh, 42h, 49h, 4Fh, 20h, 20h, 43h, 4Fh, 4Dh, 49h, 42h, 4Dh, 44h, 4Fh, 53h; 416
  db 20h, 20h, 43h, 4Fh, 4Dh, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 432
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 448
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0; 464
  db 55h, 0AAh  ; 0
; End of boot image
; =============================================================================================
DiskBPBsPtrsTable dw 0   ; 
     ; Corresponds to 0F8h media ID --- Fixed DISK
     ; (Designated to be used for any partitioned fixed or removable media, where the geometry is defined in the BPB.)
  dw ID_F9_BPB_bytes2_13 ; 0F9h ; ofs
  dw 0FFFFh  ; 0FAh - Skip write boot
  dw 0FFFFh  ; 0FBh - Skip write boot
  dw ID_FC_BPB_bytes2_13 ; 0FCh ; ofs
  dw ID_FD_BPB_bytes2_13 ; 0FDh ; ofs
  dw ID_FE_BPB_bytes2_13 ; 0FEh ; ofs
  dw ID_FF_BPB_bytes2_13 ; 0FFh ; ofs
  
ID_FE_BPB_bytes2_13 partialBPB_record 1, 1, 2, 40h, 140h, 0FEh, 1, 8, 1, 0
ID_FF_BPB_bytes2_13 partialBPB_record 2, 1, 2, 70h, 280h, 0FFh, 1, 8, 2, 0
ID_FC_BPB_bytes2_13 partialBPB_record 1, 1, 2, 40h, 168h, 0FCh, 2, 9, 1, 0
ID_FD_BPB_bytes2_13 partialBPB_record 2, 1, 2, 70h, 2D0h, 0FDh, 2, 9, 2, 0
ID_F9_BPB_bytes2_13 partialBPB_record 1, 1, 2, 0E0h, 960h, 0F9h, 7, 0Fh, 2, 0
  
; Format:
; 00 partialBPB_record struc ; (sizeof=0x12)
; 00 sectors_per_claster db ? ; Logical sectors per cluster.
; 01 reserved_sectors dw ? ; Count of reserved logical sectors.
; 03 FATs      db ? ; Number of File Allocation Tables.
; 04 root_entries    dw ? ; Maximum number of FAT12 or FAT16 root directory entries
; 06 logical_sectors dw ? ; Total logical sectors
; 08 MediaID      db ?
; 09 sectors_per_FAT dw ? ; Logical sectors per FAT
; 0B sectors_per_track dw ? ; DOS 3+ Physical sectors per track for disks with INT 13h CHS geometry
; 0D heads      dw ? ; Number of heads for disks with INT 13h CHS geometry
; 0F hidden_sectors  dw ? ; Count of hidden sectors preceding the partition that contains this FAT volume.
; 0F    ; 0 for floppies --- without MBR
; 11 unknown_fld     db ?
; 12 partialBPB_record ends




; Looks like all below in buffer is just a garbage.
buffer1:
  db  8 dup(0)


Скачати код, разом із лістингом, згенерованим IDA, скомпільованим файлом та, для порівняння, оригінальним SYS.COM і "SYS.EXE", можна тут.

Алгоритм роботи


Під "кришечкою" EXE --- все та ж стара знайома, SYS.COM. В основному зосереджуся на змінах, тому, для повноти вражень, див. також: "Аналіз SYS.COM з PC-DOS 2.10". В точці входу EXE-файла, яка раныше була початком COM-файлу --- перехід до реальної точки входу, після нього --- адреса бут-сектора в коді і стрічка "SYS 1.86" --- внутрішня версія.

Спочатку код, як завжди, перевіряє версію DOS. Якщо менша за 3.00 --- виходить із повідомленням "Incorrect DOS version". Далі, на відміну від 2.10, копіює FCB із PSP у виділену область пам'яті і переставляє DTA у свій сегмент даних. Нова DTA має розмір 128 байт. 

Перевірки переданого диску  здійснюються як і раніше, єдине, якщо після диску є якісь літери -- умовно, ім'я файлу, повідомляє "Invalid parameter" та виходить.

Зауважте (так було і раніше, але явно я цього, здається, не писав), виклики такого типу:

sys b:\a123.bcd
sys b:/cd.efg

перетравляться нормально --- все, що після '\' в FCB просто не потрапить. Повідомлення про інвалідність параметру дасть хіба щось таке:

sys b:cd.efg

Якщо диск некоректний, або цільовим використано поточний диск --- скаже "Invalid drive specification" і вийде, як і раніше.

Для друку повідомлень, крім отого, про погану версію DOS, використовується printf. Коди помилки тепер 0FFh, а не 1, як було в 2.10.

Далі використовується виклик AX=4409h/INT 21h, "IOCTL - CHECK IF BLOCK DEVICE REMOTE", якого в DOS 3.00 ще не мало б існувати. Однак, як і пишуть на сайті музею OS/2 ("DOS 3.0, 3.1, and 3.2"), мережевий редиректор вже присутній в 3.00, хоча ще й не використовувався. Код із SYS, що його використовує -- хороше підтвердження цього. Якщо виклик успішний, результат виклику тестується на біт 12, "Drive is remote". Якщо так --- повідомляє "Cannot SYS to a Network drive". Якщо не віддалений, або виклик IOCTL повернув помилку --- продовжуємо. 

Наступний крок --- той же, що і в 2.10, перевірка Media ID у FAT. Якщо він не нижчий за 0F8h, на  відміну від 2.10, переходить до додаткової перевірки формату диску. Інакше друкує (як на мене --- не зовсім релевантне) "No room for system on destination disk" і виходить. Що цікаво, якщо читання Media ID повернуло помилку --- переходимо зразу ж до тих перевірок, що робив і 2.10. Якщо диск невідомий, просто прямолінійно копіюватимемо, не колупаючись у внутрішній структурі?

Літеру цільового диску встановлюємо у стрічки "A:\\IBMBIO.COM" і "A:\\IBMDOS.COM" замість 'A'. Якщо існує IBMBIO.COM, встановлюємо прапорець (умовно --- IBMBIO_fount_flag), якщо існує IBMDOS.COM --- встановлюємо IBMDOS_fount_flag. 

Читаємо "Drive Parameter Block", з його допомогою знаходимо перший сектор каталогу диску, читаємо його у свій буфер. Якщо читання провалилося --- продовжуємо, вважаючи, що диск невідомого формату (і керується якимось драйвером? чи це просто помилка?), інакше, продовжуємо перевірки.

Якщо перший запис порожній, перевіряємо, чи файл IBMBIO.COM було знайдено, (використовується хитрий трюк, з логічним and: IBMDOS_fount_flag and IBMDOS_position_not_correct, але, здається, про аналогічні прапорці для IBMBIO забули, або я не всю логіку зрозумів), і, якщо так --- повідомляємо помилку "No room for system on destination disk" та виходимо. Природно --- він, у цій версії DOS, має бути першим у каталозі. Однак, наступна перевірка, чи не є перший файл стертим (ознака --- перший байт = 0E5h), дещо дивна: якщо так --- переходимо до перевірки наступного запису на відповідність IBMDOS.COM. А якщо IBMBIO.COM є, а перший запис -- стертий? Чи воно тоді автоматично стертий запис використає? Якщо перший запис каталогу не є незайманим і не є стертим, перевіряється, чи відповідає він IBMBIO.COM. Якщо ні -- "No room for system on destination disk". Інакше --- аналогічна перевірка робиться і для IBMDOS.COM.

Після тих перевірок повертаємося на дорогу, вже проторену 2.10. Звертаємося до системних файлів на вихідному диску. Викликаємо GetFileSizeDateTime, яка не змінилася з 2.10 --- зберігає дати, часи та розміри, для IBMBIO.COM. Якщо помилка --- на відміну від 2.10, що зразу просить вставити системний диск, перевіряє, чи диск змінний (AX=4408h/INT 21h --- "IOCTL - CHECK IF BLOCK DEVICE REMOVABLE"), і якщо так, або виклик завершився не успішно --- просить вставити системний диск  та провалюється до перевірки диску із FCB (описаної вище), інакше --- цільвоий диск фіксований, замінити неможливо, тому "No system on default drive" і виходить з кодом помилки 1.

Далі, аналогічну перевірку та читання часу-дати-розміру робить і для IBMDOS.COM. Якщо проблеми, аналогічно, просимо замінити диск і починаємо все з початку.

Якщо все в порядку --- оцінюємо, скільки у нас вільної пам'яті, та, шматками, читаємо файли у пам'ять, за допомогою функції ReadFilesToMem, яка не змінилася з часів 2.10. Якщо проблеми --- просимо вставити системний диск.

Далі, на відміну від 2.10, не чіпаємо поточну директорію цільового диску. Просто, за маскою із абсолютним шляхом, шукаємо всі файли, зі всіма атрибутами в корені цільового диску ("<target-letter>:\*.*"). Якщо знайдено файли --- йдемо до їх перевірки. Інакше, перевіряємо, чи є мітка тому. Якщо є (і тоді вона --- єдиний файл), кажемо, що "No room for system on destination disk" і виходимо. Якщо ж ні --- переходимо до запису.

Якщо якісь файли було знайдено, перевіряємо, чи є IBMBIO.COM і IBMDOS.COM. Якщо немає --- теж кажемо, що немає місця. (Дивно --- перевірки раніше мали б виявити, що їх немає. Правда, сюди ми провалюємося, також, якщо відмовилися від частини перевірок --- наприклад, MediaID невідомий, або каталог не вдалося прочитати).

Все, якщо ми продовжуємо працювати --- всі перевірки завершено. Можна записувати. Очищаємо атрибути IBMBIO.COM і IBMDOS.COM на цільовому диску. Ігноруємо помилки, (зокрема, файлів може і не бути взагалі), щоб їх можна було модифікувати. Створюємо ці файли, (або "обрізаємо", якщо вже були) із правильними атрибутами --- system+hidden+readonly. Якщо помилка під час створення --- все те ж "No room....". 

Потім йде якась дивна маніпуляція з внутрішніми даними. Не зміг розшифрувати... Отримуємо адресу DPB, і в поле, що містить початок поточної директорії (0h --- коренева), записує нуль. То такий спосіб перейти в кореневу директорію?..

Після цього викликаємо WriteSysFilesFromMem, яка теж не змінилася з часв 2.10. Вона записує те, що прочитала раніше ReadFilesToMem. Якщо файли прочитані-записані ще не всі (пам'яті вмістити їх могло і не вистачити), читає та записує наступну порцію, поки файли не закінчаться. (Детальніше див. опис 2.10).

Коли запис завершено, відновлюємо дату та час файлів, як і в 2.10. Потім записуємо бут-сектор, і завершуємо, вивівши "System transferred", з кодом завершення 0. 

Залишилося описати процедуру запису бут-сектора, WriteBoot. Вона радикально змінилася в 3.00. Хоча, починає із того ж -- добуває Media ID цільового диску. Відмінності починаються потім. Замість вгадувати деталі BPB, вони беруться із таблиці в кінці програми. MediaID слжуить індексом у "диспетчері", табличці DiskBPBsPtrsTable. Якщо в ній 0 --- опрацьовуємо як HDD (детальніше трішки пізніше). Якщо 0xFFFF --- пропускаємо. Інакше копіюємо 12h байт "заготивки" BPB із адреси, вказаної у відповідному елементі  DiskBPBsPtrsTable у образ boot-сектора і він записується на дискету. (Образ бута за міткою NewBootBuffer, початок частини BPB, яка копіюється, починається з мітки  SectorsPerCluster).

Таблиця містить опис для наступних MediaID: 0F9h (варіант 5.25", 1200Кб), 0FCh (180Кб), 0FDh (360Кб), 0FEh (160Кб), 0FFh (320Кб). Пропускатимуться Media ID, рівні 0FAh i 0FBh, які відповідають доволі загадковим форматам (див. вікі).

Якщо ж працюємо з жорстким диском, то, в образі бут-сектора, записуємо 0F8h як MediaID. Потім для чогось ліземо в системні дані. Ймовірно (судячи по коду далі), системні дані... Перевіряємо, скільки є дисководів. Далі певна, не до кінця зрозуміл мені, магія. Ймовірно, визначається номер жорсткого диску по порядку... Ймовірно, знову ж таки, так визначаємо відносне зміщення поля у внутрішніх структурах даних. Тоді, з цього гіпотетичного внутрішнього поля, копіюється той же фрагмент BPB, про який говорилося вище. Можна припустити, що поточний BPB для жорсткого  диска десь там лежить... Після цього теж записуємо boot-сектор.

Все.

"Документація"

Трішки змінилися повідомлення про помилку. Зокрема, якщо після імені диску будуть якісь літери, скаже "Invalid parameter", якщо ж вказано некоректний диск, не вказано взагалі, або цільвоим використано поточний диск, скаже "Invalid drive specification" -- як і раніше.

Хоча офіційно DOS 3.00 ще не підтримує мережі, але утиліта вже перевіряє, чи диск не є віддаленим, і відмовляється писати систему на такі диски.

Нарешті (!!!) справді перевіряє, чи цільовий диск підходить --- чи вільні перші два записи каталогу, чи, якщо вони зайняті, знаходиться в них те, що потрібно --- записи для IBMBIO.COM та IBMDOS.COM. Правда, додатково перевіряє, чи не відповідають перші записи стертим файлам, і якщо так -- вважає, що диск підходить. Дивує також , що якщо читання першого сектору корневого каталогу диску (для цієї перевірки), провалилося, повернуло помилку, переходить до перевіри наявності файлів звичайним FindFirst... Не бачу поки і перевірки, чи справді системні файли попадуть на початок області даних. (Чи вона є ія її пропустив?). Ну, але, все рівно --- прогрес.

Ще одна важлива відмінність --- BPB для boot-сектора цільового диску не придумується, як, фактично, робили попередні версії, а генерується на базі Media ID та спеціальної таблички в кінці програми. Деталі див. вище, в описі алгоритму. Зокрема, знає вона пару дивних форматів, для яких бут-сектор просто не зачіпає. Бут-сектор для жорстких дисків будується, здається, на базі образу BPB десь в системних даних DOS --- не береться вгадувати самостійно, умнічка.


COMMAND.COM все ще не копіюється.


Про код

Ну, про псевдо-printf --- мовчу. Може були у них якісь мотиви. Так виглядає, що вона й в інших утилітах використовується, і в тому ж CHKDSK цілком може бути справді зручною. Як би там не було, код став "дорослішим" -- виглядає солідніше, хоч і громіздкіший. Правда, почав лазити в загадкові системні дані, щоправда... --- нецікаво. :-) З іншого боку, зовсім не використовуєтьс FCB, код дещо стабілізувався --- великі шматки не змінилися з попередньої версії.

На жаль, сказати більше чогось по суті мені складно --- минуло більше кварталу з того часу, як я писав дивився до коду попередніх версій, тому безпосереднього відчуття, що змінилося, що ні --- немає. А ще раз колупатися аж в таких деталях неохота.

На разі:

 Дякую за увагу!

Немає коментарів:

Дописати коментар