// BuildBoot.bcpl -- Ed McCreight

//	A program to create a type B boot file
//	from run files and segment files.
// Copyright Xerox Corporation 1979
// Last modified December 22, 1978  5:44 PM by Boggs

get "Streams.d"
get "BcplFiles.d"
get "AltoFileSys.d"

external
[
// incoming procedures
SetupReadParam; 
ReadParam; 
EvalParam
PutTemplate

ReadBlock; WriteBlock; GetCurrentFa; RealDiskDA
OpenFile; PositionPage; PositionPtr; SetFilePos
Resets; Closes; Endofs; Gets; Puts
CreateDisplayStream; ShowDisplayStream; SetLmarg
Wss; Ws; Usc; ReadCalendar

// incoming statics
fpComCm; keys; sysDisk
]

static
[
currentLC = 0; maxLC = 0
defaultStartAddr = 1000b
layoutloc; layoutVec; useLayout = false
txtStr; lstStr; outStr
dspStr; dspCnt = 0
]


manifest
[
ts = 0		// it is a real os word stream
tv = 2		// it is a fake word stream
tz = 1		// it is a block of zeros
bufSize = 8192	// a big buffer
dspLines = 30
]

//----------------------------------------------------------------------------
let BuildBoot() be
//----------------------------------------------------------------------------
[
Ws("Buildboot of December 22, 1978.....*n")

let comCm = OpenFile("Com.Cm", ksTypeReadOnly, 1, 0, fpComCm)

let switchVec, stringVec = vec 256, vec 256
SetupReadParam(stringVec, switchVec, comCm, 0)

until ReadParam(0, -1) eq -1 do
   if switchVec!0 ne 0 & (switchVec!1 eq $O % switchVec!1 eq $o) break
outStr = EvalParam(stringVec, "OW", "Output file: ", 0)

Resets(comCm); 
ReadParam()	// skip over program name
until ReadParam(0, -1) eq -1 % lstStr ne 0 do
   if switchVec!0 ne 0 & (switchVec!1 eq $L % switchVec!1 eq $l) then
       lstStr = EvalParam(stringVec, "OC", "Listing file: ", 0)

let da = vec 20000
dspStr = CreateDisplayStream(dspLines, da, 20000)
ShowDisplayStream(dspStr)
SetLmarg(dspStr, 40b)

let v = vec lST; txtStr = v
txtStr>>ST.puts = PutOnBoth


let hasLoader = false
let hasStartAddr, startAddr = false, nil
let installDate, dateAddress = false, nil
let blv = vec size BLV/16; layoutVec = blv

Resets(comCm); 
ReadParam()	// skip over the program name
while ReadParam(0, -1) ne true do
   [
   let switch = switchVec!0 eq 0? $~, (switchVec!0 gr 1? $#, switchVec!1)
   PutTemplate(txtStr, "*N$US/$US*N", stringVec, switchVec)

   switchon switch into

      [
      case $D: case $d:
         [
         installDate = true
         dateAddress = EvalParam(stringVec, $B, "Date address: ", 0)
         endcase 
         ]
      case $~:
      case $R: case $r:
      case $E: case $e:
         [
         let inStr = EvalParam(stringVec, "IW", "Executable (.Run) file: ", 0)
         DoRunFile(inStr, outStr)
         Closes(inStr)
         endcase
         ]
      case $N: case $n:
         [
         currentLC = EvalParam(stringVec, $B, "Location ctr: ", 0)
         endcase
         ]
      case $S: case $s:
         [
         let inStr = EvalParam(stringVec, "IW", "Segment (.XC) file: ", 0)
         let olc = currentLC
         currentLC = currentLC + TransferBlock(currentLC, 177777b,
          ts, inStr, outStr)
         PutTemplate(txtStr, "*T$UO - $UO*N", olc, currentLC-1)
         Closes(inStr)
         endcase

         ]

      case $B: case $b:
         [
         let inStr = EvalParam(stringVec, "IW", "Boot loader file:", 0)
         CopyBootLoader(inStr, outStr)
         Closes(inStr)
         hasLoader = true
         endcase
         ]

      case $G: case $g:
         [
         hasStartAddr = true
         startAddr = EvalParam(stringVec, $B, "Start address: ", 0)
         endcase
         ]

      case $L: case $l: endcase

      case $O: case $o: endcase

      case $V: case $v:
         [
         useLayout = true
         layoutloc = EvalParam(stringVec, $B, "Layout vector address: ", 0)
         endcase
         ]


      case $#:
         [
         Wss(txtStr, "*TMultiple switches ignored*N")
         endcase

         ]


      default: Wss(txtStr, "*TUnknown switch ignored*N")
      ]
   ]
Closes(comCm)
Puts(txtStr, $*N)

unless hasLoader do
   [
   let inStr = OpenFile("DiskBoot.Run", ksTypeReadOnly)
   test inStr eq 0
      ifso Wss(txtStr, "*TNo boot loader*N")
      ifnot
         [
         Wss(txtStr, "Boot loader defaulted to DiskBoot.run*N")
         CopyBootLoader(inStr, outStr)
         Closes(inStr)
         ]
   ]

if installDate then
   [
   let dv = vec 1; ReadCalendar(dv)
   SetFilePos(outStr, 0, dateAddress lshift 1)
   WriteBlock(outStr, dv, 2)
   ]

unless hasStartAddr do
   [
   PutTemplate(txtStr, 
"Start address defaulted to $UO*N", defaultStartAddr)
   startAddr = defaultStartAddr
   ]

let fws = vec 3		// set up fake word stream
fws!0 = 1		// length
fws!1 = 0		// current position
fws!2 = lv startAddr	// block
TransferBlock(0, 1, tv, fws, outStr)


Resets(outStr)
let fa = vec lFA; GetCurrentFa(outStr, fa)
let addr = nil; RealDiskDA(sysDisk, fa>>FA.da, lv addr)

PutTemplate(txtStr, "*NBoot disk address is $UO, accessed by: ", addr)

let maskBit, bitNo = 100000b, 0
while bitNo ls 16 do
   [
   if (addr & maskBit) ne 0 then
      Wss(txtStr, selecton bitNo into
         [
         case 0: "5 "
         case 1: "4 "
         case 2: "6 "
         case 3: "E "
         case 4: "7 "
         case 5: "D "
         case 6: "U "
         case 7: "V "
         case 8: "zero "
         case 9: "K "
         case 10: "minus "
         case 11: "P "
         case 12: "/ "
         case 13: "\ "
         case 14: "linefeed "
         case 15: "backspace "
         ])
   maskBit = maskBit rshift 1
   bitNo = bitNo +1
   ]
Puts(txtStr, $*N)

SetLC(outStr, maxLC+1)
Closes(outStr)  //truncates file

test lstStr eq 0
   ifso
      [
      Wss(txtStr, "*NType any character to finish")
      Gets(keys)
      ]
   ifnot Closes(lstStr)
Closes(dspStr)
]

//----------------------------------------------------------------------------
and SetLC(outStr, address) = valof
//----------------------------------------------------------------------------
// Position 'outStr' so that the next word written
//  will appear at 'address' in the boot image.
[
unless outStr resultis true
let page = (address rshift 8) & 377b
if page eq 1 resultis false	// page 1 is illegal
if page eq 0 then page = 1	// page 0 goes to pg 1 of boot file
PositionPage(outStr, page+1)
PositionPtr(outStr, (address & 377b) lshift 1)
resultis true
]


//----------------------------------------------------------------------------
and PutOnBoth(s, c) be
//----------------------------------------------------------------------------
[
Puts(dspStr, c)
if lstStr then Puts(lstStr, c)

if c eq $*n then
   [
   dspCnt = dspCnt +1
   if dspCnt gr dspLines & lstStr eq 0 then
      [
      Wss(dspStr, "*nReady for more?*n")
      Gets(keys)
      dspCnt = 0
      ]
   ]
]


//----------------------------------------------------------------------------
and CopyBootLoader(inStr, outStr) be
//----------------------------------------------------------------------------

[
Resets(outStr)

let staticStart = nil
let staticLength = nil
let codeStart = nil
let codeLength = nil
let dummy = nil

unless ReadLayout(lv staticStart, lv staticLength, lv codeStart,
 lv codeLength, lv dummy, inStr) do
   [
   Wss(txtStr, "*TBoot loader file ended in layout vector*N")
   return
   ]

unless CopyBlock(size SV.page0/16, ts, inStr, 0) eq size SV.page0/16 do
   [
   Wss(txtStr, "*TBoot loader file ended in page 0*N")
   return
   ]

unless CopyBlock(staticLength, ts, inStr, 0) eq staticLength do
   [
   Wss(txtStr, "*TBoot loader file ended in statics*N")
   return
   ]

if Usc(codeLength, 256) gr 0 then
   [
   Wss(txtStr, "*TBoot loader code truncated*N")
   codeLength = 256
   ]

if CopyBlock(codeLength, ts, inStr, outStr) ne codeLength then
   Wss(txtStr, "*TBoot loader file ended in code*N")

WriteLayout(outStr)
]


//----------------------------------------------------------------------------
and DoRunFile(inStr, outStr) be
//----------------------------------------------------------------------------
[
let staticStart = nil
let staticLength = nil
let codeStart = nil
let codeLength = nil

unless ReadLayout(lv staticStart, lv staticLength, lv codeStart,
 lv codeLength, lv defaultStartAddr, inStr) do

   [
   Wss(txtStr, "*TInput file ended in layout vector*N")
   return
   ]

PutTemplate(txtStr, "*TStatics: $UO - $UO*N", staticStart,
 staticStart+staticLength-1)
PutTemplate(txtStr, "*TCode: $UO - $UO*N", codeStart, codeStart+codeLength-1)
PutTemplate(txtStr, "*TStart address: $UO*N", defaultStartAddr)

unless TransferBlock(0, size SV.page0/16, ts, inStr, outStr) do
   [
   Wss(txtStr, "*TInput file ended in page 0 statics*N")
   return
   ]

unless TransferBlock(staticStart, staticLength, ts, inStr, outStr) do
   [
   Wss(txtStr, "*TInput file ended in statics*N")
   return
   ]

unless TransferBlock(codeStart, codeLength, ts, inStr, outStr) do

   [
   Wss(txtStr, "*TInput file ended in code*N")
   return
   ]

WriteLayout(outStr)
]

//----------------------------------------------------------------------------
and ReadLayout(pss, psl, pcs, pcl, psa, inStr) = valof
//----------------------------------------------------------------------------

[
Resets(inStr)

let h = vec size SV.H/16
if ReadBlock(inStr, h, size SV.H/16) ne size SV.H/16 resultis false
@psa = h>>SV.H.startingAddress

if Endofs(inStr) resultis false
if ReadBlock(inStr, layoutVec, size BLV/16) ne size BLV/16 resultis false

@pss = layoutVec>>BLV.startOfStatics
@psl = layoutVec>>BLV.endOfStatics+1-@pss
@pcs = layoutVec>>BLV.startOfCode
@pcl = layoutVec>>BLV.endCode-@pcs
	
resultis true
]

//----------------------------------------------------------------------------
and WriteLayout(outStr) be if useLayout then
//----------------------------------------------------------------------------
   [
   let fws = vec 3	// make word stream of layout
   fws!0 = size BLV/16
   fws!1 = 0
   fws!2 = layoutVec
   TransferBlock(layoutloc, size BLV/16, tv, fws, outStr)

   PutTemplate(txtStr, "*TLayout vec: $UO - $UO*N",
    layoutloc, layoutloc+size BLV/16-1)
   ]


//----------------------------------------------------------------------------
and TransferBlock(lowAddr, length, iType, inStr, outStr) = valof
//----------------------------------------------------------------------------

[
let wordsDone = 0
let highAddr = lowAddr + length -1

if Usc(lowAddr, maxLC+1) gr 0 then
   TransferBlock(maxLC+1, lowAddr-maxLC-1, tz, false, outStr)

test Usc(1000b, lowAddr) gr 0 & Usc(highAddr, 377b) gr 0

   ifso
      [
      // this 'ifso' code should be disabled in the
      // event that page 1 is included in the boot file
      let nl = nil	// nominal block length
      let al = nil	// actual block length

      if Usc(400b, lowAddr) gr 0 then

         [
         SetLC(outStr, lowAddr)
         nl = 400b - lowAddr
         al = CopyBlock(nl, iType, inStr, outStr)
         wordsDone = wordsDone + al
         if al ne nl then break
         ]

      nl = (Usc(highAddr, 777b) gr 0? 777b, highAddr) -
       (Usc(lowAddr, 400b) gr 0? lowAddr, 400b)+1
      al = CopyBlock(nl, iType, inStr, false)
      wordsDone = wordsDone + al

      if al ne 0 & iType ne tz then
         Wss(txtStr, "*TWords overlapping page 1 ignored*N")

      if al ne nl break

      if Usc(highAddr, 777b) gr 0 then

         [
         SetLC(outStr, 1000b)
         nl = highAddr - 777b
         al = CopyBlock(nl, iType, inStr, outStr)
         wordsDone = wordsDone+al
         ]

      break
      ] repeat

   ifnot
      [
      SetLC(outStr, lowAddr)
      wordsDone = wordsDone + CopyBlock(length, iType, inStr, outStr)

      ]

highAddr = lowAddr + wordsDone -1
if Usc(highAddr, maxLC) gr 0 then maxLC = highAddr

resultis length eq 177777b? wordsDone, (wordsDone eq length)
]


//----------------------------------------------------------------------------
and CopyBlock(nWords, iType, inStr, oStream) = valof
//----------------------------------------------------------------------------
[
let wordsToGo = nWords
let endOfFile = false
let buffer = vec bufSize
let bufIsClear = false

while Usc(wordsToGo, 0) gr 0 & not endOfFile do

   [
   let wordsToRead = Usc(wordsToGo, bufSize) gr 0? bufSize, wordsToGo

   let wordsRead = selecton iType into

      [
      case ts: Endofs(inStr)? 0, ReadBlock(inStr, buffer, wordsToRead)
      case tv: valof
         [
         buffer = inStr!1 + inStr!2
         let nw = Usc(inStr!0, nWords+inStr!1) gr 0? nWords, inStr!0-inStr!1
         inStr!1 = inStr!1 + nw
         resultis nw
         ]


      case tz: valof
         [
         unless bufIsClear do
            for i = 0 to wordsToRead do buffer!i = 0
         bufIsClear = true
         resultis wordsToRead

         ]
      ]

   if wordsRead gr 0 & oStream ne 0 then
      WriteBlock(oStream, buffer, wordsRead)

   if wordsRead ne wordsToRead then endOfFile = true
   wordsToGo = wordsToGo - wordsRead
   ]

resultis nWords - wordsToGo
]