D64 / 1541 Reader

Started by Baggey, September 23, 2023, 15:30:51

Previous topic - Next topic

Baggey

The next quest for C64 Blitzmax is to load a Game in. I thought of going for .Tap but realized its slow and not actually a .wav such as the .P file on the ZX81.

So armed with the riveting read of "The Anatomy of the 1541 Disk Drive"
https://ia803008.us.archive.org/31/items/The_Anatomy_of_the_1541_Disk_Drive/The_Anatomy_of_the_1541_Disk_Drive.pdf

and using https://vice-emu.sourceforge.io/vice_17.html to check values ive obtained for the D64.Reader so far.

and the File Jetpac.d64 which is the first game i owned!
https://wowroms.com/en/roms/commodore-64/download-jet-pac/131572.html
This file needs to go in the same place as you saved D64.Reader but in a folder called Games ie,"Games/Jetpac.D64"

So Emulating the 1541 has 35 sectors with varying sizes as it goes out to the edge. Sort of makes sense as it a circumference of data so as you get to the inner tracks there smaller. Each sector has 256 Bytes counting the zero. Laid out into 32 partitions of 8 bytes.

Ive put this together to open the file and read it in using TStream, Tbank and pointers for faster access.

we can see the sector track list and a HexDump of the .D64 file its self.

Interestingly the Music data for the SID seems to be at $158A0.

I know from reading that Track 18 has a BAM or Directory file list. Of whats on the disc and where etc.

So making sence of this is the next step.

Here is Runable code in BlitzmaxNG
' D64 Reader    Written By Baggey
'
' Not finished yet!

SuperStrict

Global Disk:TBank=CreateBank(1)
Global DiskPtr:Byte Ptr = Disk.buf()

Print ; D64.Create()

'Rem ' For Verifying visualy the Data needed for each Track
       ' Note 1514 has 35 Tracks
       For Local i:Int= 1 To 42

           D64.PrintT(i)' "Track "+RSet(i,2)+" "+(D64.Tracks[i])

       Next
' End Rem

' Now lets Load a Disk in
    D64.LoadD64("Games/jetpac.d64",1) ' 1 allows Hex Dump of file 0 No HexDump

Type D64

     Global Tracks:D64[43]

     Global File_TypeName:String[] = ["DEL","SEQ","PRG","USR","REL"]
    
     Field Sector:Int, Sectors:Int, SectorsIN:Int, D64_Offset:Int, File_Type:String
    
     Function LoadD64:Short(FileName:String, DumpPrintOut:Int)
    
       ' Loading large files into TBank directly didnt seem to work?
       ' I Read somewhere TBank dosent go Larger than Integer values
       ' Read Stream into an Array using a Byte pointer For speed
       ' Dumps the contents from a stream

       Local StreamIN:TStream = ReadStream(FileName)
       If Not StreamIN RuntimeError "Failed to open a ReadStream to file "+FileName

       Local Text:String= "", t:Int=0, Counter:Int
       Local Text2:String=""
       Local Streamlength:Int=StreamSize(StreamIN)
       Print ; Print "Stream Size = "+RSet(Hex(Streamlength),6) ; Print

       ' Resize Bank
       Disk.unlock()             ' UnLock bank so we can resize it
       Disk.Resize(StreamLength) ' Create a New Sized Bank every time for different Streamed File
       ' Reallocate Pointer otherwise it will point to Gobbledegook
       DiskPtr=Disk.buf()        ' TapePtr to *Tape ' Allows faster access
       Disk.Lock()               ' Re-Lock Buffer again faster Access

       While Not Eof(StreamIN)
  
         If DumpPrintOut Then
                For counter=0 To 15
                      Local value:Byte=ReadByte(StreamIN)
                      DiskPtr[t]=value
                            text=text+RSet(Hex(DiskPtr[t]),2)+" "
                            If ((DiskPtr[t] < 32) Or (DiskPtr[t] > 127)) Then text2=text2+"." Else text2=text2+Chr(DiskPtr[t])
                            t:+1
                            If t=(StreamLength) Then Exit 'Print "t is "+RSet(Hex(t),6)+" on exit!" ; Exit
                   Next
           
                  ' If the last Line of Data dosent contain a complete line of bytes
                  ' Truncate the Line and so it is
                  If t=StreamLength Then
                          Print RSet(Hex(t-counter-1),8)+"  "+text[..49]+text2
                     Else
                          Print RSet(Hex(t-16),8)+"  "+text[..49]+text2
                  End If
                 text="" ; text2=""

         Else
       
             For counter=0 Until StreamLength
               
                 Local value:Byte=ReadByte(StreamIN)
                 DiskPtr[counter]=value
                                                 
                   Next
           
        End If
      
      Wend

      CloseStream StreamIN ' Always Close the stream!

      ' Now that the Stream is in the Array, we can manipulate it at will!

      Print ; Print "D64 Disk File "+FileName+" Loaded!"

      'End Rem

    End Function

 
    Function Create()

          For Local i:Int = 1 To 42
             
               ' Create Disk in D64 Type there will be 42 but for 1541 i think only 35 are needed?
               ' Ill know more latter
               Local Disk:D64= New D64

               Select True

                  Case (i>=1 And i<=17)
                         ' We have 21 Sectors in these tracks
                         Local Sectors_Track:Int=21
                         'Print "im in "+i
                         Disk.Sector=Sectors_Track
                         Disk.Sectors=Sectors_Track*17
                         Disk.SectorsIN=((i-1)*Sectors_Track)
                         Disk.D64_Offset=(((i-1)*Sectors_Track)*256)
                         Disk.File_Type="UnKnown"
                         Tracks[i] = Disk
                
                  Case (i>=18 And i<=24)
                         ' We have 19 Sectors in these tracks
                         Local Sectors_Track:Int=19
                         'Print "im in "+i
                         Disk.Sector=Sectors_Track
                         Disk.Sectors=Sectors_Track*7
                         Disk.SectorsIN=((i)*Sectors_Track)+15
                         Disk.D64_Offset=((((i)*Sectors_Track)+15)*256)
                         Disk.File_Type="UnKnown"
                         Tracks[i] = Disk

                  Case (i>=25 And i<=30)
                         ' We have 18 Sectors in these tracks
                         Local Sectors_Track:Int=18
                         'Print "im in "+i
                         Disk.Sector=Sectors_Track
                         Disk.Sectors=Sectors_Track*6
                         Disk.SectorsIN=((i)*Sectors_Track)+40
                         Disk.D64_Offset=((((i)*Sectors_Track)+40)*256)
                         Disk.File_Type="UnKnown"
                         Tracks[i] = Disk
                  Case (i>=31 And i<=35)
                         ' We have 17 Sectors in these tracks
                         Local Sectors_Track:Int=17
                         'Print "im in "+i
                         Disk.Sector=Sectors_Track
                         Disk.Sectors=Sectors_Track*5
                         Disk.SectorsIN=((i)*Sectors_Track)+71
                         Disk.D64_Offset=((((i)*Sectors_Track)+71)*256)
                         Disk.File_Type="UnKnown"
                         Tracks[i] = Disk
                  Case (i>=36 And i<=42)
                         ' We have 17 Sectors in these tracks
                         Local Sectors_Track:Int=17
                         'Print "im in "+i
                         Disk.Sector=Sectors_Track
                         Disk.Sectors=Sectors_Track*5
                         Disk.SectorsIN=((i)*Sectors_Track)+71
                         Disk.D64_Offset=((((i)*Sectors_Track)+71)*256)
                         Disk.File_Type="UnKnown"
                         Tracks[i] = Disk
                  Default
                         'Print "Default "+i
                         Disk.Sector=0
                         Tracks[i] = Disk

                End Select
              

          Next

    End Function

    Function PrintT(Number:Int)
        Tracks[Number].PrintOne(Number)
    End Function

    Method PrintOne(Number:Int)
             Print "Track "+Replace(RSet(Number,2)," ","0")+"  "+sector+" Sector  SectorsIN "+Replace(RSet(sectorsIN,3)," ","0")+"  D64 Offset $"+Replace(RSet(Hex(D64_Offset),5)," ","0")
    End Method
    
End Type


Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 4Ghz Quad core 24GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Baggey

Does anyone else ever get that sinking feeling when you start something new! and you then realize how complex its going tobe! ???

So, Anyone else interested in how the 1541 Drive works :-X

I find myself being slowly dragged into watching some videos. Too try and get a better understanding!
Otherwise im not going to get any games loaded into the C64Blitzmax Emulator!?

Here is a video for further understanding in English https://youtu.be/_1jXExwse08

and 'Sprichst du Deutsch?' https://www.pagetable.com/?p=1505

This guy really seems to know his stuff and i really realize i dont  :))

Baggey
Running a PC that just Aint fast enough!? i7 4Ghz Quad core 24GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Baggey

Ive managed to get the Directory list working from the BAM file off, of the disk now. It wasn't as bad as i thought it was going tobe :D

Ive printed the file Directory list out. How you would get it on screen, on the C64. There is a bit more info needed but its getting there.

To my surprise the files contained are actually PRG's :o 

Im not sure what i was expecting but PRG's aren't that bad to load. The Zx Spectrum formats are a lot more complicated. So im happy for that!

Now, we have the file's for Jetpac. We can now get the relevant track and sector info for the relevant Prg we need from the Disk. 
If all goes well then into the C64Blitmax Emulator.

Here is the latest runnable BlitzmaxNG code

' D64 BAM Reader    Written By Baggey
'
' Not Finished! 26/09/2023
'
' This will read a File and tell you whats on a Digital Disk .d64 format
'

SuperStrict

D64.Create(1) ' 1 Gives PrintOut


' Now lets Load a Disk in ' 1 allows Hex Dump of file 0 No HexDump
D64.LoadD64("Games/jetpac.d64",1)

D64.ReadAndPrintBAM()

End


Type D64

     Global Disk:TBank=CreateBank(1)
     Global DiskPtr:Byte Ptr = Disk.buf()

     Global Tracks:D64[43]

     Global File_TypeName:String[] = ["DEL","SEQ","PRG","USR","REL"]
    
     Field Sector:Int, Sectors:Int, SectorsIN:Int, D64_Offset:Int
     Global CurrentTrack:Int, CurrentOffset:Int, NextSector:Int, File_Type:String
     Global CurrentSector:Int, FileName:String, ThisFilesTrack:Int=18, ThisFilesSector:Int=1
     Global LengthInBlocks:Int, BlocksUsed:Int, BlocksFree:Int, LastSector:Int
     Global DiskettesName:String, DiskettesBlock:Int
     Global DiskettsID1:String, DiskettsID2:String
     Global EnteriesRead:Int=0

     Function ReadBAM(TrackNum:Int, SecNum:Int)
      
       ' On Start of ReadBam we have 0,0 so Read sector 18/0 And point To next file
   
       ' Track (TrackNum/SecNum) contains this information
       CurrentTrack=TrackNum
      
       If EnteriesRead=0 Then
 
                CurrentOffset=Tracks[TrackNum].D64_Offset+(SecNum*256)
                EnteriesRead:+1
                If DiskPtr[CurrentOffset+1]=255 Then LastSector=255 ; CurrentSector=SecNum
               
           Else
                CurrentOffset=Tracks[TrackNum].D64_Offset+(SecNum*256)+(EnteriesRead*32)
                ThisFilesTrack=18
                EnteriesRead:+1
                CurrentTrack=DiskPtr[CurrentOffset]
                NextSector=DiskPtr[CurrentOffset+1]
      
       End If
      
       LengthInBlocks=DiskPtr[CurrentOffset+30]+(DiskPtr[CurrentOffset+31]*256)
       BlocksUsed:+LengthInBlocks
       BlocksFree=664-BlocksUsed
       File_Type=File_TypeName[DiskPtr[CurrentOffset+2]&15]

       ' Temp File Name from Countpos 5 to 21 ie 16 Bytes long starting at 5
       Local TempName:String=""

       For Local CountPos:Int=5 To 21

           If DiskPtr[CurrentOffset+CountPos]=$A0 ; Exit ' No more characters in file Name SO EXIT FOR LOOP
           TempName=TempName+Chr(DiskPtr[CurrentOffset+CountPos])

       Next

       ' WE now have Filename from TempName
       FileName=TempName

     End Function


     Function ReadDiskName()

       ' Name starts at 144 in
       ' Temp File Name from pos 5 to 21 ie 16 Bytes long starting at 5
       CurrentOffset=Tracks[18].D64_Offset
       ' Note, Offset 2 is Write protected if value is 41
       ' I get the impression if the 1541 sees this value it wont Write to disk
       DiskettesBlock=DiskPtr[CurrentOffset+3] ' This is a guess but seems to work?
       Local TempName:String=""

       For Local CountPos:Int=0 To 15

           If DiskPtr[CurrentOffset+CountPos+144]=$A0 ; Exit ' No more characters in file Name SO EXIT FOR LOOP
                TempName=TempName+Chr(DiskPtr[CurrentOffset+CountPos+144])

       Next

       ' WE now have file name in TempName
       DiskettesName=TempName

       'Look for ID Tags
       If DiskPtr[$165A2]<>$A0 Then
             
               DiskettsID1:String=Chr(DiskPtr[$165A2])+Chr(DiskPtr[$165A2+1])
       End If

       If DiskPtr[$165A5]<>$A0 Then
 
               DiskettsID2:String=Chr(DiskPtr[$165A5])+Chr(DiskPtr[$165A5+1])
       End If

     End Function
    
     Function LoadD64:Short(FileName:String, DumpPrintOut:Int)
    
       ' Loading large files into TBank directly didnt seem to work?
       ' I Read somewhere TBank dosent go Larger than Integer values
       ' Read Stream into an Array using a Byte pointer For speed
       ' Dumps the contents from a stream

       Local StreamIN:TStream = ReadStream(FileName)
       If Not StreamIN RuntimeError "Failed to open a ReadStream to file "+FileName

       Local Text:String= "", t:Int=0, Counter:Int
       Local Text2:String=""
       Local Streamlength:Int=StreamSize(StreamIN)
       Print "Stream Size = "+RSet(Hex(Streamlength),6) ; Print

       ' Resize Bank
       Disk.unlock()             ' UnLock bank so we can resize it
       Disk.Resize(StreamLength) ' Create a New Sized Bank every time for different Streamed File

       ' Reallocate Pointer otherwise it will point to Gobbledegook
       DiskPtr=Disk.buf()        ' TapePtr to *Tape ' Allows faster access
       Disk.Lock()               ' Re-Lock Buffer again faster Access

       While Not Eof(StreamIN)
  
         If DumpPrintOut Then
             Local NumOfBytesToPrint:Int=32 ' Number of Bytes accross screen
             Local TextLength:Int=(3*NumOfBytesToPrint)

            For counter=0 Until NumOfBytesToPrint
                    
                      Local value:Byte=ReadByte(StreamIN)
                      DiskPtr[t]=value
                text=text+RSet(Hex(DiskPtr[t]),2)+" "
                If ((DiskPtr[t] < 32) Or (DiskPtr[t] > 127)) Then text2=text2+"." Else text2=text2+Chr(DiskPtr[t])
                t:+1
                If t=(StreamLength) Then Exit
       Next

      ' If the last Line of Data dosent contain a complete line of bytes
      ' Truncate the Line and so it is
      If t=StreamLength Then
              Print RSet(Hex(t-counter-1),8)+"  "+text[..TextLength]+text2
         Else
              Print RSet(Hex(t-NumOfBytesToPrint),8)+"  "+text[..TextLength]+text2
      End If
         text="" ; text2=""

         Else
       
             For counter=0 Until StreamLength
               
                 Local value:Byte=ReadByte(StreamIN)
                 DiskPtr[counter]=value
                 
       Next

        End If
      
      Wend

      CloseStream StreamIN ' Always Close the stream!

      ' Now that the Stream is in the Array, we can manipulate it at will!

      Print "D64 Disk File "+FileName+" Loaded!"


    End Function

 
    Function Create(DoPrint:Int)

          For Local i:Int = 1 To 42
             
               ' Create Disk in D64 Type there will be 42 but for 1541 i think only 35 are needed?
               ' Ill know more latter
               Local Disk:D64= New D64

               Select True

                  Case (i>=1 And i<=17)
                         ' We have 21 Sectors in these tracks
                         Local Sectors_Track:Int=21
                         'Print "im in "+i
                         Disk.Sector=Sectors_Track
                         Disk.Sectors=Sectors_Track*17
                         Disk.SectorsIN=((i-1)*Sectors_Track)
                         Disk.D64_Offset=(((i-1)*Sectors_Track)*256)
                         Disk.File_Type="UnKnown"
                         Tracks[i] = Disk
                
                  Case (i>=18 And i<=24)
                         ' We have 19 Sectors in these tracks
                         Local Sectors_Track:Int=19
                         'Print "im in "+i
                         Disk.Sector=Sectors_Track
                         Disk.Sectors=Sectors_Track*7
                         Disk.SectorsIN=((i)*Sectors_Track)+15
                         Disk.D64_Offset=((((i)*Sectors_Track)+15)*256)
                         Disk.File_Type="UnKnown"
                         Tracks[i] = Disk

                  Case (i>=25 And i<=30)
                         ' We have 18 Sectors in these tracks
                         Local Sectors_Track:Int=18
                         'Print "im in "+i
                         Disk.Sector=Sectors_Track
                         Disk.Sectors=Sectors_Track*6
                         Disk.SectorsIN=((i)*Sectors_Track)+40
                         Disk.D64_Offset=((((i)*Sectors_Track)+40)*256)
                         Disk.File_Type="UnKnown"
                         Tracks[i] = Disk
                  Case (i>=31 And i<=35)
                         ' We have 17 Sectors in these tracks
                         Local Sectors_Track:Int=17
                         'Print "im in "+i
                         Disk.Sector=Sectors_Track
                         Disk.Sectors=Sectors_Track*5
                         Disk.SectorsIN=((i)*Sectors_Track)+71
                         Disk.D64_Offset=((((i)*Sectors_Track)+71)*256)
                         Disk.File_Type="UnKnown"
                         Tracks[i] = Disk
                  Case (i>=36 And i<=42)
                         ' We have 17 Sectors in these tracks
                         Local Sectors_Track:Int=17
                         'Print "im in "+i
                         Disk.Sector=Sectors_Track
                         Disk.Sectors=Sectors_Track*5
                         Disk.SectorsIN=((i)*Sectors_Track)+71
                         Disk.D64_Offset=((((i)*Sectors_Track)+71)*256)
                         Disk.File_Type="UnKnown"
                         Tracks[i] = Disk
                  Default
                         'Print "Default "+i
                         Disk.Sector=0
                         Tracks[i] = Disk

                End Select
              
          Next

       If DoPrint Then
           'Rem ' For Verifying visualy the Data needed for each Track
           ' Note 1514 has 35 Tracks
           For Local i:Int= 1 To 42

               D64.PrintT(i)' "Track "+RSet(i,2)+" "+(D64.Tracks[i])

           Next
          'End Rem
       End If

    End Function

    Function ReadAndPrintBAM()

    ' Get the disks Name info
    D64.ReadDiskName()
    Print ; Print RSet(D64.DiskettesBlock,3)+"   "+LSet((Chr(34)+D64.DiskettesName+Chr(34)),16)+" "+D64.DiskettsID1+" "+D64.DiskettsID2

    ' Lets get Start info of track 18 / Sector
    ' You would Start at Sector 18/0 But it always points to Sector 18/1 for a valid Disk!
    For Local dir:Int=0 Until 144
  
       D64.ReadBAM(D64.ThisFilesTrack,D64.ThisFilesSector) ' Track/Sector
  
       ' Print out like youd see on C64 Screen. Not sure if this is of any use in this format yet
       ' as there is a lot more info needed with the file
       If D64.CurrentSector=$FF

                Print RSet(D64.LengthInBlocks,3)+"   "+LSet((Chr(34)+D64.FileName+Chr(34)),16)+" "+D64.File_Type
                Print RSet(D64.BlocksFree,3)+" BLOCKS FREE."
                Exit
   
             Else

                 If D64.LengthInBlocks<>0 Then

                 Print RSet(D64.LengthInBlocks,3)+"   "+LSet((Chr(34)+D64.FileName+Chr(34)),16)+" "+D64.File_Type
         
                 End If

        End If

      Next
 
      Print RSet(D64.BlocksFree,3)+" BLOCKS FREE."
      Print

   End Function

   Function PrintT(Number:Int)
        Tracks[Number].PrintOne(Number)
   End Function

   Method PrintOne(Number:Int)
             Print "Track "+Replace(RSet(Number,2)," ","0")+"  "+sector+" Sector  SectorsIN "+Replace(RSet(sectorsIN,3)," ","0")+"  D64 Offset $"+Replace(RSet(Hex(D64_Offset),5)," ","0")
   End Method
    
End Type

Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 4Ghz Quad core 24GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Dan

This one is interesting:



It has, as well, a few links to downloadable d64 images, if you would like to test them.
65536 GOTO Back2Basic

Baggey

#4
@Dan

Thankyou for the share dan! It's a good job i had my kleenex tissues at hand ;)

Ive updated my code so it's a bit more readable. Okay i cant Re-Edit the last Post i did so ill wait until ive added some more.

Baggey
Running a PC that just Aint fast enough!? i7 4Ghz Quad core 24GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!