├── .gitignore ├── 35track.dsk ├── 35track.po ├── 40track.dsk ├── 40track.po ├── README.md ├── po2dsk.py └── dsk2po.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/dsk2po.cpython-311.pyc 2 | -------------------------------------------------------------------------------- /35track.dsk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulhagstrom/dsk2po/HEAD/35track.dsk -------------------------------------------------------------------------------- /35track.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulhagstrom/dsk2po/HEAD/35track.po -------------------------------------------------------------------------------- /40track.dsk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulhagstrom/dsk2po/HEAD/40track.dsk -------------------------------------------------------------------------------- /40track.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulhagstrom/dsk2po/HEAD/40track.po -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dsk2po and po2dsk 2 | Python scripts to convert Apple II/III .DSK (DO) images to ProDOS-ordered images 3 | and vice versa. 4 | 5 | This is nothing very exciting, it just maps sectors in .dsk (DOS-order, .do) files 6 | into those of a .po (ProDOS-order) file. Most Apple II emulators can handle both, 7 | but certain utilities (with which I wanted to use existing .dsk images) assume 8 | ProDOS-ordered files. 9 | 10 | Usage is just (assuming you've done `chmod 755 dsk2po.py`, else precede with python command): 11 | 12 | ./dsk2po.py image.dsk 13 | 14 | This will create image.dsk.po alongside it. Pretty much no checking is done. 15 | It just goes through all the tracks and converts them, then ends. 16 | 17 | Both scripts will handle an arbitrary number of tracks, but will print a 18 | warning if the number of tracks is not 35. 19 | 20 | This can be used as an action for find, like so: 21 | 22 | find imagefolders/\*/\*.dsk -exec ./dsk2po.py {} \; 23 | 24 | ...which was mostly the point. 25 | 26 | You can also go the opposite direction: 27 | 28 | ./po2dsk.py image.po 29 | -------------------------------------------------------------------------------- /po2dsk.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # scramble po back into dsk 3 | # Chris Torrence, Oct 2020 4 | import sys, getopt, re, os 5 | from dsk2po import dsk2po 6 | def main(argv=None): 7 | print("po2dsk - convert po files to dsk files") 8 | 9 | try: 10 | opts, args = getopt.getopt(sys.argv[1:], '') 11 | except getopt.GetoptError as err: 12 | print(str(err)) 13 | usage() 14 | return 1 15 | try: 16 | filenameIn = args[0] 17 | except: 18 | print('You need to provide the name of a PO file to begin.') 19 | return 1 20 | 21 | # Handle arbitrary number of tracks (normally should be 35) 22 | fileSize = os.path.getsize(filenameIn) 23 | ntracks = fileSize // 4096 24 | if ntracks != 35: 25 | print("Warning: PO file has non-standard {} tracks".format(ntracks)) 26 | 27 | tracks = [] 28 | 29 | # Note that the same algorithm can be used to convert in either direction 30 | with open(filenameIn, mode="rb") as fileIn: 31 | for track in range(ntracks): 32 | trackbuffer = fileIn.read(4096) 33 | tracks.append(dsk2po(trackbuffer)) 34 | dskfilename = re.sub('\.po$', '', filenameIn, flags=re.IGNORECASE) + ".dsk" 35 | print('Writing dsk image to {}'.format(dskfilename)) 36 | with open(dskfilename, mode="wb") as file: 37 | for track in tracks: 38 | file.write(track) 39 | return 1 40 | 41 | if __name__ == "__main__": 42 | sys.exit(main()) 43 | -------------------------------------------------------------------------------- /dsk2po.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # unscramble dsk into po 3 | # Paul Hagstrom, Dec 2015 4 | import sys, getopt, re, os 5 | def main(argv=None): 6 | print("dsk2po - convert dsk files to po files") 7 | 8 | try: 9 | opts, args = getopt.getopt(sys.argv[1:], '') 10 | except getopt.GetoptError as err: 11 | print(str(err)) 12 | usage() 13 | return 1 14 | try: 15 | dskfilename = args[0] 16 | except: 17 | print('You need to provide the name of a DSK file to begin.') 18 | return 1 19 | 20 | # Handle arbitrary number of tracks (normally should be 35) 21 | fileSize = os.path.getsize(dskfilename) 22 | ntracks = fileSize // 4096 23 | if ntracks != 35: 24 | print("Warning: DSK file has non-standard {} tracks".format(ntracks)) 25 | 26 | potracks = [] 27 | with open(dskfilename, mode="rb") as dskfile: 28 | for track in range(ntracks): 29 | trackbuffer = dskfile.read(4096) 30 | potracks.append(dsk2po(trackbuffer)) 31 | pofilename = re.sub('\.dsk$', '', dskfilename, flags=re.IGNORECASE) + ".po" 32 | print('Writing po image to {}'.format(pofilename)) 33 | with open(pofilename, mode="wb") as pofile: 34 | for potrack in potracks: 35 | pofile.write(potrack) 36 | return 0 37 | 38 | # From Beneath Apple ProDOS, table 3.1 39 | # block 000 physical 0, 2 DOS 0, E page 0, 1 40 | # block 001 physical 4, 6 DOS D, C page 2, 3 41 | # block 002 physical 8, A DOS B, A page 4, 5 42 | # block 003 physical C, E DOS 9, 8 page 6, 7 43 | # block 004 physical 1, 3 DOS 7, 6 page 8, 9 44 | # block 005 physical 5, 7 DOS 5, 4 page a, b 45 | # block 006 physical 9, B DOS 3, 2 page c, d 46 | # block 007 physical D, F DOS 1, F page e, f 47 | 48 | # dsk images are in DOS order, so I need to convert from 49 | # DOS sectors into blocks. That is combine D and C into 2 and 3 50 | 51 | block_map = [0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 15] 52 | 53 | def dsk2po(trackbuffer): 54 | potrack = bytearray() 55 | for chunk in range(16): 56 | chunk_start = 256*block_map[chunk] 57 | chunk_end = chunk_start + 256 58 | potrack.extend(trackbuffer[chunk_start:chunk_end]) 59 | return potrack 60 | 61 | if __name__ == "__main__": 62 | sys.exit(main()) 63 | --------------------------------------------------------------------------------