#!/usr/bin/env python3 # nsi2hfe.py # # Convert PM file to HFEv3. v0.1 # Only SSSD is supported. # # Written & released by Keir Fraser and Eric Anderson # # # This is free and unencumbered software released into the public domain. # See the file COPYING for more details, or visit . import sys,struct,argparse # Encode fm with twice as many bits to avoid runs of 1s that could be # interpreted as opcodes def fm(bs): os = bytearray() o = 0 for b in bs: for _ in range(4): for _ in range(2): o >>= 4 d = bool(b & 0x80) o |= 1 << 4 o |= d << 6 b <<= 1 os.append(o) return os def check(bs): c = 0 for s in struct.unpack('128H', bs): c = (c + s) & 0xFFFF return struct.pack('H', c ^ 0xFFFF) def main(argv): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("--rate", type=int, default=250, help="data rate, kbit/s") parser.add_argument("--rpm", type=int, default=300, help="rotational rate, rpm") parser.add_argument("--cyls", type=int, default=35, help="number of cylinders") parser.add_argument("--sides", type=int, default=1, help="number of sides") parser.add_argument("--hard-sectors", type=int, default=10, help="number of hard sectors") parser.add_argument("infile", help="input filename") parser.add_argument("outfile", help="output filename") args = parser.parse_args(argv[1:]) in_f = open(args.infile, "rb") preamble = bytes(10) + b'\xe6\xe6' filler = b'\x11' v3 = args.hard_sectors != 0 bits = (args.rate * 1000 * 60) // args.rpm bits *= 2 # clock bits bits *= 2 # 2 sides byte_cnt = (bits + 7) // 8 # convert to bytes, rounded up if not v3: byte_cnt = (byte_cnt + 15) & ~15 # round up to 16-byte boundary raw_bytes = byte_cnt if args.hard_sectors: byte_cnt += (args.hard_sectors + 1) * 2 # 2 sides blocks = (byte_cnt + 511) // 512 # convert to 512-byte blocks, rounded up print("Geometry: %u cylinders, %u sides" % (args.cyls, args.sides)) print("%ukbit/s @ %uRPM -> %u Encoded Bits" % (args.rate, args.rpm, bits/2)) print("Data per HFE Track: %u bytes, %u blocks" % (byte_cnt, blocks)) # Header out_f = open(args.outfile, "wb") sig = b'HXCHFEV3' if v3 else b"HXCPICFE" out_f.write(struct.pack("<8s4B2H2BH", sig, # signature 0, # revision args.cyls, # nr_tracks args.sides, # nr_sides 0xff, # track_encoding args.rate, # bitrate 0, # rpm 0xfe, # interface_mode 1, # rsvd 1)) # track_list_offset out_f.write(bytearray(b'\xff'*(512-20))) # TLUT tlut_blocks = (args.cyls*4 + 511) // 512 base = 1 + tlut_blocks for i in range(args.cyls): out_f.write(struct.pack("<2H", base, byte_cnt)) base += blocks out_f.write(bytearray(b'\xff'*(tlut_blocks*512-args.cyls*4))) # Data if not args.hard_sectors: out_f.write(bytearray(b'\x88'*(blocks*512*args.cyls))) else: for cyl in range(args.cyls): side = [] for ss in range(2): trk_side = bytearray(b'\x88')*(raw_bytes//2) sector_len = float(raw_bytes//2) / args.hard_sectors if args.sides == 2 or ss == 0: if ss == 0: in_base = cyl * 256 * args.hard_sectors else: in_base = (2*args.cyls - cyl - 1) * 256 * args.hard_sectors last_sector_pos = len(trk_side) for sector in range(args.hard_sectors-1, -1, -1): hdr = struct.pack("BB", sector | 0x80, cyl) in_f.seek(in_base + sector * 256) raw_b = in_f.read(256) or bytes(256) raw_b = preamble + hdr + raw_b + check(raw_b) sector_pos = int(sector_len*sector) raw_mfm = fm(raw_b) raw_mfm += filler * (last_sector_pos-sector_pos-len(raw_mfm)) trk_side[sector_pos:sector_pos+len(raw_mfm)] = raw_mfm last_sector_pos = sector_pos trk_side.insert(-int(sector_len)//2, 0x8F) for sector in range(args.hard_sectors-1, -1, -1): trk_side.insert(int(sector_len*sector), 0x8F) assert len(trk_side) == byte_cnt//2 trk_side += b'\x0F'*(256 - len(trk_side)%256) assert len(trk_side) == blocks*256 side.append(trk_side) if args.sides == 1: side.append(trk_side) trk = bytearray() for pos in range(0, len(side[0]), 256): trk.extend(side[0][pos:pos+256]) trk.extend(side[1][pos:pos+256]) out_f.write(trk) return 0 if __name__ == "__main__": sys.exit(main(sys.argv))