mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2024-11-22 05:59:15 +01:00
[SC64][DOCS][SW] Adjusted documentation / merged dd64.py into sc64.py
This commit is contained in:
parent
98fa69e4d7
commit
fc42292af0
7
build.sh
7
build.sh
@ -5,11 +5,10 @@ set -e
|
|||||||
PACKAGE_FILE_NAME="SC64"
|
PACKAGE_FILE_NAME="SC64"
|
||||||
|
|
||||||
TOP_FILES=(
|
TOP_FILES=(
|
||||||
"./sw/pc/dd64.py"
|
|
||||||
"./sw/pc/primer.py"
|
"./sw/pc/primer.py"
|
||||||
"./sw/pc/requirements.txt"
|
"./sw/pc/requirements.txt"
|
||||||
"./sw/pc/sc64.py"
|
"./sw/pc/sc64.py"
|
||||||
"./sw/update/sc64_update_package.bin"
|
"./sw/update/sc64_firmware.bin"
|
||||||
)
|
)
|
||||||
|
|
||||||
FILES=(
|
FILES=(
|
||||||
@ -87,7 +86,7 @@ build_update () {
|
|||||||
|
|
||||||
pushd sw/update > /dev/null
|
pushd sw/update > /dev/null
|
||||||
if [ "$FORCE_CLEAN" = true ]; then
|
if [ "$FORCE_CLEAN" = true ]; then
|
||||||
rm -f ./sc64_update_package.bin
|
rm -f ./sc64_firmware.bin
|
||||||
fi
|
fi
|
||||||
GIT_INFO=""
|
GIT_INFO=""
|
||||||
if [ ! -z "${GIT_BRANCH}" ]; then GIT_INFO+="branch: [$GIT_BRANCH] "; fi
|
if [ ! -z "${GIT_BRANCH}" ]; then GIT_INFO+="branch: [$GIT_BRANCH] "; fi
|
||||||
@ -101,7 +100,7 @@ build_update () {
|
|||||||
--fpga ../../fw/project/lcmxo2/impl1/sc64_impl1.jed \
|
--fpga ../../fw/project/lcmxo2/impl1/sc64_impl1.jed \
|
||||||
--boot ../bootloader/build/bootloader.bin \
|
--boot ../bootloader/build/bootloader.bin \
|
||||||
--primer ../controller/build/primer/primer.bin \
|
--primer ../controller/build/primer/primer.bin \
|
||||||
sc64_update_package.bin
|
sc64_firmware.bin
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
|
|
||||||
BUILT_UPDATE=true
|
BUILT_UPDATE=true
|
||||||
|
@ -24,9 +24,9 @@
|
|||||||
|
|
||||||
Keeping SC64 firmware up to date is highly recommended. `sc64.py` script is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version.
|
Keeping SC64 firmware up to date is highly recommended. `sc64.py` script is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version.
|
||||||
|
|
||||||
To download and backup current version of SC64 firmware run `python3 sc64.py --backup-firmware sc64_backup_package.bin`
|
To download and backup current version of SC64 firmware run `python3 sc64.py --backup-firmware sc64_firmware_backup.bin`
|
||||||
|
|
||||||
To update SC64 firmware run `python3 sc64.py --update-firmware sc64_update_package.bin`
|
To update SC64 firmware run `python3 sc64.py --update-firmware sc64_firmware.bin`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -57,6 +57,8 @@ There are no special requirements for soldering components to board. All chips a
|
|||||||
|
|
||||||
### **Initial programming**
|
### **Initial programming**
|
||||||
|
|
||||||
|
**Please read the following instructions carefully before proceeding with programming.**
|
||||||
|
|
||||||
For initial programming you are going to need a PC and a USB to UART (serial) adapter (3.3V signaling is required). These steps assume you are using modern Windows OS (version 10 or higher).
|
For initial programming you are going to need a PC and a USB to UART (serial) adapter (3.3V signaling is required). These steps assume you are using modern Windows OS (version 10 or higher).
|
||||||
|
|
||||||
As for software here's list of required applications:
|
As for software here's list of required applications:
|
||||||
@ -83,11 +85,11 @@ Second, program FPGA, microcontroller and Flash memory:
|
|||||||
4. Check in device manager which port number `COMx` is assigned to serial adapter
|
4. Check in device manager which port number `COMx` is assigned to serial adapter
|
||||||
5. Connect SC64 board to the PC with USB-C cable (***IMPORTANT:*** connect it to the same computer as serial adapter)
|
5. Connect SC64 board to the PC with USB-C cable (***IMPORTANT:*** connect it to the same computer as serial adapter)
|
||||||
6. Locate `primer.py` script in root folder
|
6. Locate `primer.py` script in root folder
|
||||||
7. Make sure these files are located in the same folder as `primer.py` script: `requirements.txt`, `sc64.py`, `dd64.py`, `sc64_update_package.bin`
|
7. Make sure these files are located in the same folder as `primer.py` script: `requirements.txt`, `sc64.py`, `sc64_firmware.bin`
|
||||||
8. Run `pip3 install -r requirements.txt` to install required python packages
|
8. Run `pip3 install -r requirements.txt` to install required python packages
|
||||||
9. Run `python3 primer.py COMx sc64_update_package.bin` (replace `COMx` with port located in step **4**)
|
9. Run `python3 primer.py COMx sc64_firmware.bin` (replace `COMx` with port located in step **4**)
|
||||||
10. Follow the instructions on the screen
|
10. Follow the instructions on the screen
|
||||||
11. Wait until programming process has finished
|
11. Wait until programming process has finished (**DO NOT STOP PROGRAMMING PROCESS OR DISCONNECT SC64 BOARD FROM PC**, doing so might irrecoverably break programming through UART header and you would need to program microcontroller, FPGA and bootloader with separate dedicated programming interfaces through *Tag-Connect* connector on the PCB)
|
||||||
|
|
||||||
Congratulations! Your SC64 flashcart should be ready for use!
|
Congratulations! Your SC64 flashcart should be ready for use!
|
||||||
|
|
||||||
@ -97,4 +99,4 @@ Congratulations! Your SC64 flashcart should be ready for use!
|
|||||||
|
|
||||||
*`primer.py` threw error on `Bootloader -> SC64 FLASH` step*
|
*`primer.py` threw error on `Bootloader -> SC64 FLASH` step*
|
||||||
|
|
||||||
This issue can be attributed to incorrectly programmed FT232H EEPROM in the first programming step. Check again in `FT_PROG` program if device was configured properly. Once FPGA and microcontroller has been programmed successfully `primer.py` script needs to be run in special mode. Please use command `python3 primer.py COMx sc64_update_package.bin --only-bootloader` to try programming bootloader again.
|
This issue can be attributed to incorrectly programmed FT232H EEPROM in the first programming step. Check again in `FT_PROG` program if device was configured properly. Once FPGA and microcontroller has been programmed successfully `primer.py` script needs to be run in special mode. Please use command `python3 primer.py COMx sc64_firmware.bin --only-bootloader` to try programming bootloader again.
|
||||||
|
236
sw/pc/dd64.py
236
sw/pc/dd64.py
@ -1,236 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from io import BufferedReader
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BadBlockError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DD64Image:
|
|
||||||
__DISK_HEADS = 2
|
|
||||||
__DISK_TRACKS = 1175
|
|
||||||
__DISK_BLOCKS_PER_TRACK = 2
|
|
||||||
__DISK_SECTORS_PER_BLOCK = 85
|
|
||||||
__DISK_BAD_TRACKS_PER_ZONE = 12
|
|
||||||
__DISK_SYSTEM_SECTOR_SIZE = 232
|
|
||||||
__DISK_ZONES = [
|
|
||||||
(0, 232, 158, 0),
|
|
||||||
(0, 216, 158, 158),
|
|
||||||
(0, 208, 149, 316),
|
|
||||||
(0, 192, 149, 465),
|
|
||||||
(0, 176, 149, 614),
|
|
||||||
(0, 160, 149, 763),
|
|
||||||
(0, 144, 149, 912),
|
|
||||||
(0, 128, 114, 1061),
|
|
||||||
(1, 216, 158, 157),
|
|
||||||
(1, 208, 158, 315),
|
|
||||||
(1, 192, 149, 464),
|
|
||||||
(1, 176, 149, 613),
|
|
||||||
(1, 160, 149, 762),
|
|
||||||
(1, 144, 149, 911),
|
|
||||||
(1, 128, 149, 1060),
|
|
||||||
(1, 112, 114, 1174),
|
|
||||||
]
|
|
||||||
__DISK_VZONE_TO_PZONE = [
|
|
||||||
[0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10],
|
|
||||||
[0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11],
|
|
||||||
[0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12],
|
|
||||||
[0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13],
|
|
||||||
[0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14],
|
|
||||||
[0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15],
|
|
||||||
[0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8],
|
|
||||||
]
|
|
||||||
__DISK_DRIVE_TYPES = [(
|
|
||||||
'development',
|
|
||||||
192,
|
|
||||||
[11, 10, 3, 2],
|
|
||||||
[0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23],
|
|
||||||
), (
|
|
||||||
'retail',
|
|
||||||
232,
|
|
||||||
[9, 8, 1, 0],
|
|
||||||
[2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23],
|
|
||||||
)]
|
|
||||||
|
|
||||||
__file: Optional[BufferedReader]
|
|
||||||
__drive_type: Optional[str]
|
|
||||||
__block_info_table: list[tuple[int, int]]
|
|
||||||
loaded: bool = False
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.__file = None
|
|
||||||
self.__drive_type = None
|
|
||||||
block_info_table_length = self.__DISK_HEADS * self.__DISK_TRACKS * self.__DISK_BLOCKS_PER_TRACK
|
|
||||||
self.__block_info_table = [None] * block_info_table_length
|
|
||||||
|
|
||||||
def __del__(self) -> None:
|
|
||||||
self.unload()
|
|
||||||
|
|
||||||
def __check_system_block(self, lba: int, sector_size: int, check_disk_type: bool) -> tuple[bool, bytes]:
|
|
||||||
self.__file.seek(lba * self.__DISK_SYSTEM_SECTOR_SIZE * self.__DISK_SECTORS_PER_BLOCK)
|
|
||||||
system_block_data = self.__file.read(sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
|
||||||
system_data = system_block_data[:sector_size]
|
|
||||||
for sector in range(1, self.__DISK_SECTORS_PER_BLOCK):
|
|
||||||
sector_data = system_block_data[(sector * sector_size):][:sector_size]
|
|
||||||
if (system_data != sector_data):
|
|
||||||
return (False, None)
|
|
||||||
if (check_disk_type):
|
|
||||||
if (system_data[4] != 0x10):
|
|
||||||
return (False, None)
|
|
||||||
if ((system_data[5] & 0xF0) != 0x10):
|
|
||||||
return (False, None)
|
|
||||||
return (True, system_data)
|
|
||||||
|
|
||||||
def __parse_disk(self) -> None:
|
|
||||||
disk_system_data = None
|
|
||||||
disk_id_data = None
|
|
||||||
disk_bad_lbas = []
|
|
||||||
|
|
||||||
drive_index = 0
|
|
||||||
while (disk_system_data == None) and (drive_index < len(self.__DISK_DRIVE_TYPES)):
|
|
||||||
(drive_type, system_sector_size, system_data_lbas, bad_lbas) = self.__DISK_DRIVE_TYPES[drive_index]
|
|
||||||
disk_bad_lbas.clear()
|
|
||||||
disk_bad_lbas.extend(bad_lbas)
|
|
||||||
for system_lba in system_data_lbas:
|
|
||||||
(valid, system_data) = self.__check_system_block(system_lba, system_sector_size, check_disk_type=True)
|
|
||||||
if (valid):
|
|
||||||
self.__drive_type = drive_type
|
|
||||||
disk_system_data = system_data
|
|
||||||
else:
|
|
||||||
disk_bad_lbas.append(system_lba)
|
|
||||||
drive_index += 1
|
|
||||||
|
|
||||||
for id_lba in [15, 14]:
|
|
||||||
(valid, id_data) = self.__check_system_block(id_lba, self.__DISK_SYSTEM_SECTOR_SIZE, check_disk_type=False)
|
|
||||||
if (valid):
|
|
||||||
disk_id_data = id_data
|
|
||||||
else:
|
|
||||||
disk_bad_lbas.append(id_lba)
|
|
||||||
|
|
||||||
if not (disk_system_data and disk_id_data):
|
|
||||||
raise ValueError('Provided 64DD disk file is not valid')
|
|
||||||
|
|
||||||
disk_zone_bad_tracks = []
|
|
||||||
|
|
||||||
for zone in range(len(self.__DISK_ZONES)):
|
|
||||||
zone_bad_tracks = []
|
|
||||||
start = 0 if zone == 0 else system_data[0x07 + zone]
|
|
||||||
stop = system_data[0x07 + zone + 1]
|
|
||||||
for offset in range(start, stop):
|
|
||||||
zone_bad_tracks.append(system_data[0x20 + offset])
|
|
||||||
for ignored_track in range(self.__DISK_BAD_TRACKS_PER_ZONE - len(zone_bad_tracks)):
|
|
||||||
zone_bad_tracks.append(self.__DISK_ZONES[zone][2] - ignored_track - 1)
|
|
||||||
disk_zone_bad_tracks.append(zone_bad_tracks)
|
|
||||||
|
|
||||||
disk_type = disk_system_data[5] & 0x0F
|
|
||||||
|
|
||||||
current_lba = 0
|
|
||||||
starting_block = 0
|
|
||||||
disk_file_offset = 0
|
|
||||||
|
|
||||||
for zone in self.__DISK_VZONE_TO_PZONE[disk_type]:
|
|
||||||
(head, sector_size, tracks, track) = self.__DISK_ZONES[zone]
|
|
||||||
|
|
||||||
for zone_track in range(tracks):
|
|
||||||
current_zone_track = (
|
|
||||||
(tracks - 1) - zone_track) if head else zone_track
|
|
||||||
|
|
||||||
if (current_zone_track in disk_zone_bad_tracks[zone]):
|
|
||||||
track += (-1) if head else 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
for block in range(self.__DISK_BLOCKS_PER_TRACK):
|
|
||||||
index = (track << 2) | (head << 1) | (starting_block ^ block)
|
|
||||||
if (current_lba not in disk_bad_lbas):
|
|
||||||
self.__block_info_table[index] = (disk_file_offset, sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
|
||||||
else:
|
|
||||||
self.__block_info_table[index] = None
|
|
||||||
disk_file_offset += sector_size * self.__DISK_SECTORS_PER_BLOCK
|
|
||||||
current_lba += 1
|
|
||||||
|
|
||||||
track += (-1) if head else 1
|
|
||||||
starting_block ^= 1
|
|
||||||
|
|
||||||
def __check_track_head_block(self, track: int, head: int, block: int) -> None:
|
|
||||||
if (track < 0 or track >= self.__DISK_TRACKS):
|
|
||||||
raise ValueError('Track outside of possible range')
|
|
||||||
if (head < 0 or head >= self.__DISK_HEADS):
|
|
||||||
raise ValueError('Head outside of possible range')
|
|
||||||
if (block < 0 or block >= self.__DISK_BLOCKS_PER_TRACK):
|
|
||||||
raise ValueError('Block outside of possible range')
|
|
||||||
|
|
||||||
def __get_table_index(self, track: int, head: int, block: int) -> int:
|
|
||||||
return (track << 2) | (head << 1) | (block)
|
|
||||||
|
|
||||||
def __get_block_info(self, track: int, head: int, block: int) -> Optional[tuple[int, int]]:
|
|
||||||
if (self.__file.closed):
|
|
||||||
return None
|
|
||||||
self.__check_track_head_block(track, head, block)
|
|
||||||
index = self.__get_table_index(track, head, block)
|
|
||||||
return self.__block_info_table[index]
|
|
||||||
|
|
||||||
def load(self, path: str) -> None:
|
|
||||||
self.unload()
|
|
||||||
self.__file = open(path, 'rb+')
|
|
||||||
self.__parse_disk()
|
|
||||||
self.loaded = True
|
|
||||||
|
|
||||||
def unload(self) -> None:
|
|
||||||
self.loaded = False
|
|
||||||
if (self.__file != None and not self.__file.closed):
|
|
||||||
self.__file.close()
|
|
||||||
self.__drive_type = None
|
|
||||||
|
|
||||||
def get_block_info_table(self) -> list[tuple[int, int]]:
|
|
||||||
return self.__block_info_table
|
|
||||||
|
|
||||||
def get_drive_type(self) -> str:
|
|
||||||
return self.__drive_type
|
|
||||||
|
|
||||||
def read_block(self, track: int, head: int, block: int) -> bytes:
|
|
||||||
info = self.__get_block_info(track, head, block)
|
|
||||||
if (info == None):
|
|
||||||
raise BadBlockError
|
|
||||||
(offset, block_size) = info
|
|
||||||
self.__file.seek(offset)
|
|
||||||
return self.__file.read(block_size)
|
|
||||||
|
|
||||||
def write_block(self, track: int, head: int, block: int, data: bytes) -> None:
|
|
||||||
info = self.__get_block_info(track, head, block)
|
|
||||||
if (info == None):
|
|
||||||
raise BadBlockError
|
|
||||||
(offset, block_size) = info
|
|
||||||
if (len(data) != block_size):
|
|
||||||
raise ValueError(f'Provided data block size is different than expected ({len(data)} != {block_size})')
|
|
||||||
self.__file.seek(offset)
|
|
||||||
self.__file.write(data)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
id_lba_locations = [
|
|
||||||
(7, 0, 1),
|
|
||||||
(7, 0, 0)
|
|
||||||
]
|
|
||||||
if (len(sys.argv) >= 2):
|
|
||||||
dd = DD64Image()
|
|
||||||
dd.load(sys.argv[1])
|
|
||||||
print(dd.get_drive_type())
|
|
||||||
for (track, head, block) in id_lba_locations:
|
|
||||||
try:
|
|
||||||
print(dd.read_block(track, head, block)[:4])
|
|
||||||
except BadBlockError:
|
|
||||||
print(f'Bad ID block [track: {track}, head: {head}, block: {block}]')
|
|
||||||
if (len(sys.argv) >= 3):
|
|
||||||
with open(sys.argv[2], 'wb') as f:
|
|
||||||
block_info_table = dd.get_block_info_table()
|
|
||||||
for block in block_info_table:
|
|
||||||
offset = 0xFFFFFFFF if block == None else block[0]
|
|
||||||
f.write(offset.to_bytes(4, byteorder='big'))
|
|
||||||
dd.unload()
|
|
||||||
else:
|
|
||||||
print(f'[{sys.argv[0]}]: Expected disk image path as first argument')
|
|
206
sw/pc/sc64.py
206
sw/pc/sc64.py
@ -9,8 +9,8 @@ import sys
|
|||||||
import time
|
import time
|
||||||
from binascii import crc32
|
from binascii import crc32
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dd64 import BadBlockError, DD64Image
|
|
||||||
from enum import Enum, IntEnum
|
from enum import Enum, IntEnum
|
||||||
|
from io import BufferedReader
|
||||||
from serial.tools import list_ports
|
from serial.tools import list_ports
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
@ -18,6 +18,210 @@ from PIL import Image
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BadBlockError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DD64Image:
|
||||||
|
__DISK_HEADS = 2
|
||||||
|
__DISK_TRACKS = 1175
|
||||||
|
__DISK_BLOCKS_PER_TRACK = 2
|
||||||
|
__DISK_SECTORS_PER_BLOCK = 85
|
||||||
|
__DISK_BAD_TRACKS_PER_ZONE = 12
|
||||||
|
__DISK_SYSTEM_SECTOR_SIZE = 232
|
||||||
|
__DISK_ZONES = [
|
||||||
|
(0, 232, 158, 0),
|
||||||
|
(0, 216, 158, 158),
|
||||||
|
(0, 208, 149, 316),
|
||||||
|
(0, 192, 149, 465),
|
||||||
|
(0, 176, 149, 614),
|
||||||
|
(0, 160, 149, 763),
|
||||||
|
(0, 144, 149, 912),
|
||||||
|
(0, 128, 114, 1061),
|
||||||
|
(1, 216, 158, 157),
|
||||||
|
(1, 208, 158, 315),
|
||||||
|
(1, 192, 149, 464),
|
||||||
|
(1, 176, 149, 613),
|
||||||
|
(1, 160, 149, 762),
|
||||||
|
(1, 144, 149, 911),
|
||||||
|
(1, 128, 149, 1060),
|
||||||
|
(1, 112, 114, 1174),
|
||||||
|
]
|
||||||
|
__DISK_VZONE_TO_PZONE = [
|
||||||
|
[0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10],
|
||||||
|
[0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11],
|
||||||
|
[0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12],
|
||||||
|
[0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13],
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14],
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15],
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8],
|
||||||
|
]
|
||||||
|
__DISK_DRIVE_TYPES = [(
|
||||||
|
'development',
|
||||||
|
192,
|
||||||
|
[11, 10, 3, 2],
|
||||||
|
[0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
|
), (
|
||||||
|
'retail',
|
||||||
|
232,
|
||||||
|
[9, 8, 1, 0],
|
||||||
|
[2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
|
)]
|
||||||
|
|
||||||
|
__file: Optional[BufferedReader]
|
||||||
|
__drive_type: Optional[str]
|
||||||
|
__block_info_table: list[tuple[int, int]]
|
||||||
|
loaded: bool = False
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.__file = None
|
||||||
|
self.__drive_type = None
|
||||||
|
block_info_table_length = self.__DISK_HEADS * self.__DISK_TRACKS * self.__DISK_BLOCKS_PER_TRACK
|
||||||
|
self.__block_info_table = [None] * block_info_table_length
|
||||||
|
|
||||||
|
def __del__(self) -> None:
|
||||||
|
self.unload()
|
||||||
|
|
||||||
|
def __check_system_block(self, lba: int, sector_size: int, check_disk_type: bool) -> tuple[bool, bytes]:
|
||||||
|
self.__file.seek(lba * self.__DISK_SYSTEM_SECTOR_SIZE * self.__DISK_SECTORS_PER_BLOCK)
|
||||||
|
system_block_data = self.__file.read(sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
||||||
|
system_data = system_block_data[:sector_size]
|
||||||
|
for sector in range(1, self.__DISK_SECTORS_PER_BLOCK):
|
||||||
|
sector_data = system_block_data[(sector * sector_size):][:sector_size]
|
||||||
|
if (system_data != sector_data):
|
||||||
|
return (False, None)
|
||||||
|
if (check_disk_type):
|
||||||
|
if (system_data[4] != 0x10):
|
||||||
|
return (False, None)
|
||||||
|
if ((system_data[5] & 0xF0) != 0x10):
|
||||||
|
return (False, None)
|
||||||
|
return (True, system_data)
|
||||||
|
|
||||||
|
def __parse_disk(self) -> None:
|
||||||
|
disk_system_data = None
|
||||||
|
disk_id_data = None
|
||||||
|
disk_bad_lbas = []
|
||||||
|
|
||||||
|
drive_index = 0
|
||||||
|
while (disk_system_data == None) and (drive_index < len(self.__DISK_DRIVE_TYPES)):
|
||||||
|
(drive_type, system_sector_size, system_data_lbas, bad_lbas) = self.__DISK_DRIVE_TYPES[drive_index]
|
||||||
|
disk_bad_lbas.clear()
|
||||||
|
disk_bad_lbas.extend(bad_lbas)
|
||||||
|
for system_lba in system_data_lbas:
|
||||||
|
(valid, system_data) = self.__check_system_block(system_lba, system_sector_size, check_disk_type=True)
|
||||||
|
if (valid):
|
||||||
|
self.__drive_type = drive_type
|
||||||
|
disk_system_data = system_data
|
||||||
|
else:
|
||||||
|
disk_bad_lbas.append(system_lba)
|
||||||
|
drive_index += 1
|
||||||
|
|
||||||
|
for id_lba in [15, 14]:
|
||||||
|
(valid, id_data) = self.__check_system_block(id_lba, self.__DISK_SYSTEM_SECTOR_SIZE, check_disk_type=False)
|
||||||
|
if (valid):
|
||||||
|
disk_id_data = id_data
|
||||||
|
else:
|
||||||
|
disk_bad_lbas.append(id_lba)
|
||||||
|
|
||||||
|
if not (disk_system_data and disk_id_data):
|
||||||
|
raise ValueError('Provided 64DD disk file is not valid')
|
||||||
|
|
||||||
|
disk_zone_bad_tracks = []
|
||||||
|
|
||||||
|
for zone in range(len(self.__DISK_ZONES)):
|
||||||
|
zone_bad_tracks = []
|
||||||
|
start = 0 if zone == 0 else system_data[0x07 + zone]
|
||||||
|
stop = system_data[0x07 + zone + 1]
|
||||||
|
for offset in range(start, stop):
|
||||||
|
zone_bad_tracks.append(system_data[0x20 + offset])
|
||||||
|
for ignored_track in range(self.__DISK_BAD_TRACKS_PER_ZONE - len(zone_bad_tracks)):
|
||||||
|
zone_bad_tracks.append(self.__DISK_ZONES[zone][2] - ignored_track - 1)
|
||||||
|
disk_zone_bad_tracks.append(zone_bad_tracks)
|
||||||
|
|
||||||
|
disk_type = disk_system_data[5] & 0x0F
|
||||||
|
|
||||||
|
current_lba = 0
|
||||||
|
starting_block = 0
|
||||||
|
disk_file_offset = 0
|
||||||
|
|
||||||
|
for zone in self.__DISK_VZONE_TO_PZONE[disk_type]:
|
||||||
|
(head, sector_size, tracks, track) = self.__DISK_ZONES[zone]
|
||||||
|
|
||||||
|
for zone_track in range(tracks):
|
||||||
|
current_zone_track = (
|
||||||
|
(tracks - 1) - zone_track) if head else zone_track
|
||||||
|
|
||||||
|
if (current_zone_track in disk_zone_bad_tracks[zone]):
|
||||||
|
track += (-1) if head else 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
for block in range(self.__DISK_BLOCKS_PER_TRACK):
|
||||||
|
index = (track << 2) | (head << 1) | (starting_block ^ block)
|
||||||
|
if (current_lba not in disk_bad_lbas):
|
||||||
|
self.__block_info_table[index] = (disk_file_offset, sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
||||||
|
else:
|
||||||
|
self.__block_info_table[index] = None
|
||||||
|
disk_file_offset += sector_size * self.__DISK_SECTORS_PER_BLOCK
|
||||||
|
current_lba += 1
|
||||||
|
|
||||||
|
track += (-1) if head else 1
|
||||||
|
starting_block ^= 1
|
||||||
|
|
||||||
|
def __check_track_head_block(self, track: int, head: int, block: int) -> None:
|
||||||
|
if (track < 0 or track >= self.__DISK_TRACKS):
|
||||||
|
raise ValueError('Track outside of possible range')
|
||||||
|
if (head < 0 or head >= self.__DISK_HEADS):
|
||||||
|
raise ValueError('Head outside of possible range')
|
||||||
|
if (block < 0 or block >= self.__DISK_BLOCKS_PER_TRACK):
|
||||||
|
raise ValueError('Block outside of possible range')
|
||||||
|
|
||||||
|
def __get_table_index(self, track: int, head: int, block: int) -> int:
|
||||||
|
return (track << 2) | (head << 1) | (block)
|
||||||
|
|
||||||
|
def __get_block_info(self, track: int, head: int, block: int) -> Optional[tuple[int, int]]:
|
||||||
|
if (self.__file.closed):
|
||||||
|
return None
|
||||||
|
self.__check_track_head_block(track, head, block)
|
||||||
|
index = self.__get_table_index(track, head, block)
|
||||||
|
return self.__block_info_table[index]
|
||||||
|
|
||||||
|
def load(self, path: str) -> None:
|
||||||
|
self.unload()
|
||||||
|
self.__file = open(path, 'rb+')
|
||||||
|
self.__parse_disk()
|
||||||
|
self.loaded = True
|
||||||
|
|
||||||
|
def unload(self) -> None:
|
||||||
|
self.loaded = False
|
||||||
|
if (self.__file != None and not self.__file.closed):
|
||||||
|
self.__file.close()
|
||||||
|
self.__drive_type = None
|
||||||
|
|
||||||
|
def get_block_info_table(self) -> list[tuple[int, int]]:
|
||||||
|
return self.__block_info_table
|
||||||
|
|
||||||
|
def get_drive_type(self) -> str:
|
||||||
|
return self.__drive_type
|
||||||
|
|
||||||
|
def read_block(self, track: int, head: int, block: int) -> bytes:
|
||||||
|
info = self.__get_block_info(track, head, block)
|
||||||
|
if (info == None):
|
||||||
|
raise BadBlockError
|
||||||
|
(offset, block_size) = info
|
||||||
|
self.__file.seek(offset)
|
||||||
|
return self.__file.read(block_size)
|
||||||
|
|
||||||
|
def write_block(self, track: int, head: int, block: int, data: bytes) -> None:
|
||||||
|
info = self.__get_block_info(track, head, block)
|
||||||
|
if (info == None):
|
||||||
|
raise BadBlockError
|
||||||
|
(offset, block_size) = info
|
||||||
|
if (len(data) != block_size):
|
||||||
|
raise ValueError(f'Provided data block size is different than expected ({len(data)} != {block_size})')
|
||||||
|
self.__file.seek(offset)
|
||||||
|
self.__file.write(data)
|
||||||
|
|
||||||
|
|
||||||
class ConnectionException(Exception):
|
class ConnectionException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user