summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--scripts/src/formats.lua12
-rw-r--r--scripts/target/mame/mess.lua1
-rw-r--r--src/devices/bus/hp9845_io/98034.cpp15
-rw-r--r--src/devices/bus/ieee488/hp9895.cpp11
-rw-r--r--src/lib/formats/hpi_dsk.cpp430
-rw-r--r--src/lib/formats/hpi_dsk.h55
-rw-r--r--src/tools/floptool.cpp4
7 files changed, 520 insertions, 8 deletions
diff --git a/scripts/src/formats.lua b/scripts/src/formats.lua
index ba20df392a0..6129e45b2b6 100644
--- a/scripts/src/formats.lua
+++ b/scripts/src/formats.lua
@@ -895,6 +895,18 @@ end
--------------------------------------------------
--
+--@src/lib/formats/hpi_dsk.h,FORMATS["HPI_DSK"] = true
+--------------------------------------------------
+
+if (FORMATS["HPI_DSK"]~=null or _OPTIONS["with-tools"]) then
+ files {
+ MAME_DIR.. "src/lib/formats/hpi_dsk.cpp",
+ MAME_DIR.. "src/lib/formats/hpi_dsk.h",
+ }
+end
+
+--------------------------------------------------
+--
--@src/lib/formats/hp_ipc_dsk.h,FORMATS["HP_IPC_DSK"] = true
--------------------------------------------------
diff --git a/scripts/target/mame/mess.lua b/scripts/target/mame/mess.lua
index cbaa512d363..32f315c8b00 100644
--- a/scripts/target/mame/mess.lua
+++ b/scripts/target/mame/mess.lua
@@ -800,6 +800,7 @@ FORMATS["GTP_CAS"] = true
FORMATS["HECTOR_MINIDISC"] = true
FORMATS["HECT_DSK"] = true
FORMATS["HECT_TAP"] = true
+FORMATS["HPI_DSK"] = true
FORMATS["HP_IPC_DSK"] = true
FORMATS["IQ151_DSK"] = true
FORMATS["ITT3030_DSK"] = true
diff --git a/src/devices/bus/hp9845_io/98034.cpp b/src/devices/bus/hp9845_io/98034.cpp
index c9a2f263259..37a679fa3c3 100644
--- a/src/devices/bus/hp9845_io/98034.cpp
+++ b/src/devices/bus/hp9845_io/98034.cpp
@@ -133,9 +133,12 @@ READ16_MEMBER(hp98034_io_card_device::reg_r)
m_force_flg = true;
update_flg();
- // PPU yields to let NP see FLG=0 immediately
- // (horrible race conditions lurking...)
- space.device().execute().yield();
+ // PPU pauses for an instant to let NP see FLG=0 immediately
+ // There's a bug in Mass Memory opt. ROM because the PPU
+ // doesn't wait for FLG from 98034 in a few (common) cases.
+ // A magic combination of relative speeds between PPU and
+ // NP always hides this bug in the real hw.
+ space.device().execute().spin_until_time(attotime::from_usec(5));
LOG("read R%u=%04x\n" , offset + 4 , res);
return res;
@@ -156,9 +159,9 @@ WRITE16_MEMBER(hp98034_io_card_device::reg_w)
m_force_flg = true;
update_flg();
- // PPU yields to let NP see FLG=0 immediately
- // (horrible race conditions lurking...)
- space.device().execute().yield();
+ // PPU pauses for an instant to let NP see FLG=0 immediately
+ // (see above)
+ space.device().execute().spin_until_time(attotime::from_usec(5));
LOG("write R%u=%04x\n" , offset + 4 , data);
}
diff --git a/src/devices/bus/ieee488/hp9895.cpp b/src/devices/bus/ieee488/hp9895.cpp
index 6e5e62d6e53..73ab69a5e00 100644
--- a/src/devices/bus/ieee488/hp9895.cpp
+++ b/src/devices/bus/ieee488/hp9895.cpp
@@ -56,6 +56,7 @@
#include "emu.h"
#include "hp9895.h"
+#include "formats/hpi_dsk.h"
// Debugging
#define VERBOSE 1
@@ -866,6 +867,12 @@ static SLOT_INTERFACE_START(hp9895_floppies)
SLOT_INTERFACE("8dsdd" , FLOPPY_8_DSDD)
SLOT_INTERFACE_END
+static const floppy_format_type hp9895_floppy_formats[] = {
+ FLOPPY_MFI_FORMAT,
+ FLOPPY_HPI_FORMAT,
+ nullptr
+};
+
static MACHINE_CONFIG_FRAGMENT(hp9895)
MCFG_CPU_ADD("cpu" , Z80 , 4000000)
MCFG_CPU_PROGRAM_MAP(z80_program_map)
@@ -884,9 +891,9 @@ static MACHINE_CONFIG_FRAGMENT(hp9895)
MCFG_PHI_DIO_READWRITE_CB(READ8(hp9895_device , phi_dio_r) , WRITE8(hp9895_device , phi_dio_w))
MCFG_PHI_INT_WRITE_CB(WRITELINE(hp9895_device , phi_int_w))
- MCFG_FLOPPY_DRIVE_ADD("floppy0" , hp9895_floppies , "8dsdd" , floppy_image_device::default_floppy_formats)
+ MCFG_FLOPPY_DRIVE_ADD("floppy0" , hp9895_floppies , "8dsdd" , hp9895_floppy_formats)
MCFG_SLOT_FIXED(true)
- MCFG_FLOPPY_DRIVE_ADD("floppy1" , hp9895_floppies , "8dsdd" , floppy_image_device::default_floppy_formats)
+ MCFG_FLOPPY_DRIVE_ADD("floppy1" , hp9895_floppies , "8dsdd" , hp9895_floppy_formats)
MCFG_SLOT_FIXED(true)
MACHINE_CONFIG_END
diff --git a/src/lib/formats/hpi_dsk.cpp b/src/lib/formats/hpi_dsk.cpp
new file mode 100644
index 00000000000..2f48f873f59
--- /dev/null
+++ b/src/lib/formats/hpi_dsk.cpp
@@ -0,0 +1,430 @@
+// license:BSD-3-Clause
+// copyright-holders: Ansgar Kückes, F. Ulivi
+/*********************************************************************
+
+ hpi_dsk.cpp
+
+ HP9895A "HPI" disk images
+
+ CHS = 77/2/30
+ Sector size 256 bytes
+ Cell size 2 µs
+ Gap1 = 16 x 0x00
+ Gap2 = 34 x 0x00
+ Gap3 = ~490 x 0x00 (depends on actual rotation speed)
+ Sync = 4 x 0x00 + 4 x 0xff
+ ID AM = 0x0e clock + 0x70 data
+ Data AM = 0x0e clock + 0x50 data
+ DefectTrack AM = 0x0e clock + 0xf0 data
+ CRC-16 excludes address markers
+ MMFM/M2FM encoding (LS bit first)
+
+ The order of sectors in a track depends on the interleave factor
+ which is the distance (in number of sectors) between two consecutively
+ numbered sectors. Interleave factor is specified at formatting time.
+ The default factor is 7. The order of sectors for this factor is:
+ 0, 13, 26, 9, 22, 5, 18, 1, 14, 27, 10, 23, 6, 19, 2,
+ 15, 28, 11, 24, 7, 20, 3, 16, 29, 12, 25, 8, 21, 4, 17
+
+ <Track> := [Index hole]|Sector0|Gap2|Sector1|Gap2|...|Sector29|Gap3|
+
+ <Sector> := ID field|Gap1|Data field
+
+ <ID field> := Sync|ID AM|Track no.|Sector no.|CRC-16|0x00
+
+ <Data field> := Sync|Data AM|Data|CRC-16|0x00
+
+ This format is just a raw image of every sector on a HP-formatted
+ 8" floppy disk. Files with this format have no header/trailer and
+ are exactly 1182720 bytes in size (30 sectors, 2 heads, 77 tracks,
+ 256 bytes per sector). There's also a "reduced" version holding
+ just 75 cylinders.
+ When loading, the disk image is translated to MMFM encoding so
+ that it can be loaded into HP9895 emulator.
+
+*********************************************************************/
+
+#include "emu.h"
+#include "hpi_dsk.h"
+
+// Debugging
+#define VERBOSE 0
+#define LOG(...) do { if (VERBOSE) printf(__VA_ARGS__); } while (false)
+
+constexpr unsigned IL_OFFSET = 0x12; // Position of interleave factor in HPI image (2 bytes, big-endian)
+constexpr unsigned DEFAULT_IL = 7; // Default interleaving factor
+constexpr unsigned CELL_SIZE = 1200; // Bit cell size (1 µs)
+constexpr uint8_t ID_AM = 0x70; // ID address mark
+constexpr uint8_t DATA_AM = 0x50; // Data address mark
+constexpr uint8_t AM_CLOCK = 0x0e; // Clock pattern of AM
+constexpr uint32_t ID_CD_PATTERN= 0x55552a54; // C/D pattern of 0xff + ID_AM
+constexpr uint32_t DATA_CD_PATTERN = 0x55552a44; // C/D pattern of 0xff + DATA_AM
+constexpr unsigned GAP1_SIZE = 17; // Size of gap 1 (+1)
+constexpr unsigned GAP2_SIZE = 35; // Size of gap 2 (+1)
+constexpr unsigned ID_DATA_OFFSET = 30 * 16; // Nominal distance (in cells) between ID & DATA AM
+// Size of image file (holding 77 cylinders)
+constexpr unsigned HPI_IMAGE_SIZE = HPI_TRACKS * HPI_HEADS * HPI_SECTORS * HPI_SECTOR_SIZE;
+constexpr unsigned HPI_RED_TRACKS = 75; // Reduced number of tracks
+// Size of reduced image file (holding 75 cylinders)
+constexpr unsigned HPI_RED_IMAGE_SIZE = HPI_RED_TRACKS * HPI_HEADS * HPI_SECTORS * HPI_SECTOR_SIZE;
+
+hpi_format::hpi_format()
+{
+}
+
+int hpi_format::identify(io_generic *io, uint32_t form_factor)
+{
+ uint64_t size = io_generic_size(io);
+
+ // we try to stay back and give only 50 points, since another image
+ // format may also have images of the same size (there is no header and no
+ // magic number for HPI format...
+ if (((form_factor == floppy_image::FF_8) || (form_factor == floppy_image::FF_UNKNOWN)) &&
+ ((size == HPI_RED_IMAGE_SIZE) || (size == HPI_IMAGE_SIZE))) {
+ return 50;
+ } else {
+ return 0;
+ }
+}
+
+bool hpi_format::load(io_generic *io, uint32_t form_factor, floppy_image *image)
+{
+ image->set_variant(floppy_image::DSDD); // We actually need to derive the variant from the image size depending on the form factor
+
+ uint64_t size = io_generic_size(io);
+ unsigned cylinders;
+ if (size == HPI_RED_IMAGE_SIZE) {
+ cylinders = HPI_RED_TRACKS;
+ } else if (size == HPI_IMAGE_SIZE) {
+ cylinders = HPI_TRACKS;
+ } else {
+ return false;
+ }
+
+ // Suck in the whole image
+ std::vector<uint8_t> image_data(size);
+ io_generic_read(io, (void *)image_data.data(), 0, size);
+
+ // Get interleave factor from image
+ unsigned il = (unsigned)image_data[ IL_OFFSET ] * 256 + image_data[ IL_OFFSET + 1 ];
+ LOG("I/L from image: %u\n" , il);
+ if (il < 1 || il >= HPI_SECTORS) {
+ il = DEFAULT_IL;
+ }
+ LOG("Actual I/L: %u\n" , il);
+
+ sector_list_t sector_list;
+ interleaved_sectors(il, sector_list);
+
+ unsigned list_offset = 0;
+ for (unsigned cyl = 0; cyl < cylinders; cyl++) {
+ for (unsigned head = 0; head < HPI_HEADS; head++) {
+ std::vector<uint32_t> track_data;
+ for (unsigned sector = 0; sector < HPI_SECTORS; sector++) {
+ unsigned real_sector = sector_list[ (sector + list_offset) % HPI_SECTORS ];
+ unsigned offset_in_image = chs_to_lba(cyl, head, real_sector) * HPI_SECTOR_SIZE;
+ write_sector(track_data , cyl , real_sector + (head << 7) , &image_data[ offset_in_image ]);
+ }
+ fill_with_gap3(track_data);
+ generate_track_from_levels(cyl , head , track_data , 0 , image);
+ list_offset = (list_offset + m_track_skew[ il - 1 ][ head ]) % HPI_SECTORS;
+ }
+ }
+ return true;
+}
+
+bool hpi_format::save(io_generic *io, floppy_image *image)
+{
+ for (unsigned cyl = 0; cyl < HPI_TRACKS; cyl++) {
+ for (unsigned head = 0; head < HPI_HEADS; head++) {
+ uint8_t bitstream[ 21000 ];
+ int bitstream_size;
+ generate_bitstream_from_track(cyl , head , CELL_SIZE , bitstream , bitstream_size , image , 0);
+ int pos = 0;
+ unsigned track_no , head_no , sector_no;
+ uint8_t sector_data[ HPI_SECTOR_SIZE ];
+ while (get_next_sector(bitstream , bitstream_size , pos , track_no , head_no , sector_no , sector_data)) {
+ if (track_no == cyl && head_no == head && sector_no < HPI_SECTORS) {
+ unsigned offset_in_image = chs_to_lba(cyl, head, sector_no) * HPI_SECTOR_SIZE;
+ io_generic_write(io, sector_data, offset_in_image, HPI_SECTOR_SIZE);
+ }
+ }
+ }
+ }
+ return true;
+}
+
+const char *hpi_format::name() const
+{
+ return "hpi";
+}
+
+const char *hpi_format::description() const
+{
+ return "HP9895A floppy disk image";
+}
+
+const char *hpi_format::extensions() const
+{
+ return "hpi";
+}
+
+bool hpi_format::supports_save() const
+{
+ return true;
+}
+
+void hpi_format::interleaved_sectors(unsigned il_factor , sector_list_t& sector_list)
+{
+ sector_list.fill(0xff);
+
+ unsigned idx = HPI_SECTORS - il_factor;
+ for (unsigned sect = 0; sect < HPI_SECTORS; sect++) {
+ idx = (idx + il_factor) % HPI_SECTORS;
+ while (sector_list[ idx ] != 0xff) {
+ idx = (idx + 1) % HPI_SECTORS;
+ }
+ LOG("[%u]=%u\n" , idx , sect);
+ sector_list[ idx ] = sect;
+ }
+}
+
+void hpi_format::write_mmfm_bit(std::vector<uint32_t> &buffer , bool data_bit , bool clock_bit)
+{
+ bool had_transition = buffer.size() < 2 ? false : bit_r(buffer, buffer.size() - 1) || bit_r(buffer , buffer.size() - 2);
+ clock_bit = !data_bit && (clock_bit || !had_transition);
+ bit_w(buffer , clock_bit , CELL_SIZE);
+ bit_w(buffer , data_bit , CELL_SIZE);
+}
+
+void hpi_format::write_mmfm_byte(std::vector<uint32_t> &buffer , uint8_t data , uint8_t clock)
+{
+ for (unsigned i = 0; i < 8; i++) {
+ write_mmfm_bit(buffer , BIT(data , i) , BIT(clock , i));
+ }
+}
+
+void hpi_format::write_sync(std::vector<uint32_t> &buffer)
+{
+ // Sync
+ // 4x 00
+ for (unsigned i = 0; i < 4; i++) {
+ write_mmfm_byte(buffer , 0);
+ }
+ // 4x ff
+ for (unsigned i = 0; i < 4; i++) {
+ write_mmfm_byte(buffer , 0xff);
+ }
+}
+
+void hpi_format::write_crc(std::vector<uint32_t> &buffer , uint16_t crc)
+{
+ // Note that CRC is stored with MSB (x^15) first
+ for (unsigned i = 0; i < 16; i++) {
+ write_mmfm_bit(buffer , BIT(crc , 15 - i) , 0);
+ }
+}
+
+void hpi_format::write_sector(std::vector<uint32_t> &buffer , uint8_t track_no , uint8_t sect_head_no , const uint8_t *sect_data)
+{
+ // **** On-disk format of a sector ****
+ //
+ // | Offset | Size | Value | Content |
+ // |--------+------+-------+----------------------|
+ // | 0 | 4 | 00 | Sync |
+ // | 4 | 4 | ff | Sync |
+ // | 8 | 1 | 70 | ID AM (clock = 0e) |
+ // | 9 | 1 | xx | Track no. |
+ // | 10 | 1 | xx | Sector and head no. |
+ // | 11 | 2 | xx | ID CRC |
+ // | 13 | 1 | 00 | ID tail |
+ // | 14 | 16 | 00 | Gap 1 |
+ // | 30 | 4 | 00 | Sync |
+ // | 34 | 4 | ff | Sync |
+ // | 38 | 1 | 50 | Data AM (clock = 0e) |
+ // | 39 | 256 | xx | Sector data |
+ // | 295 | 2 | xx | Data CRC |
+ // | 297 | 1 | 00 | Data tail |
+ // | 298 | 34 | 00 | Gap 2 |
+ // | 332 | | | |
+
+ // Sync
+ write_sync(buffer);
+ // ID AM
+ write_mmfm_byte(buffer , ID_AM , AM_CLOCK);
+ auto crc_start = buffer.size();
+ // Track #
+ write_mmfm_byte(buffer , track_no);
+ // Sector/head #
+ write_mmfm_byte(buffer , sect_head_no);
+ uint16_t crc = calc_crc_ccitt(buffer , crc_start , buffer.size());
+ // ID CRC
+ write_crc(buffer , crc);
+ // Gap 1
+ for (unsigned i = 0; i < GAP1_SIZE; i++) {
+ write_mmfm_byte(buffer , 0);
+ }
+ // Sync
+ write_sync(buffer);
+ // Data AM
+ write_mmfm_byte(buffer , DATA_AM , AM_CLOCK);
+ crc_start = buffer.size();
+ for (unsigned i = 0; i < HPI_SECTOR_SIZE; i += 2) {
+ // Data: bytes are swapped in pairs
+ write_mmfm_byte(buffer , sect_data[ i + 1 ]);
+ write_mmfm_byte(buffer , sect_data[ i ]);
+ }
+ crc = calc_crc_ccitt(buffer , crc_start , buffer.size());
+ // Data CRC
+ write_crc(buffer , crc);
+ // Gap 2
+ for (unsigned i = 0; i < GAP2_SIZE; i++) {
+ write_mmfm_byte(buffer , 0);
+ }
+}
+
+void hpi_format::fill_with_gap3(std::vector<uint32_t> &buffer)
+{
+ // Cell count in a track (1 µs cells in a 1/6 s track)
+ unsigned cell_count = (500000 * 120) / 360;
+ unsigned cells_in_buffer = buffer.size();
+ // Size of gap 3
+ unsigned gap_3 = (cell_count - cells_in_buffer) / 16;
+ // Gap 3
+ for (unsigned i = 0; i < gap_3; i++) {
+ write_mmfm_byte(buffer , 0);
+ }
+ // Last cell to round everything up to 2E+8
+ if (buffer.size() * CELL_SIZE < 200000000) {
+ bit_w(buffer , false , 200000000 - buffer.size() * CELL_SIZE);
+ }
+}
+
+unsigned hpi_format::chs_to_lba(unsigned cylinder , unsigned head , unsigned sector)
+{
+ return sector + (head + cylinder * HPI_HEADS) * HPI_SECTORS;
+}
+
+std::vector<uint8_t> hpi_format::get_next_id_n_block(const uint8_t *bitstream , int bitstream_size , int& pos , int& start_pos)
+{
+ std::vector<uint8_t> res;
+ uint32_t sr = 0;
+ // Look for either sync + ID AM or sync + DATA AM
+ while (pos < bitstream_size && sr != ID_CD_PATTERN && sr != DATA_CD_PATTERN) {
+ bool bit = BIT(bitstream[ pos >> 3 ] , 7 - (pos & 7));
+ pos++;
+ sr = (sr << 1) | bit;
+ }
+ if (pos == bitstream_size) {
+ // End of track reached
+ return res;
+ }
+ start_pos = pos;
+ // ID blocks: Track no. + sector/head no. + CRC
+ // Data blocks: Sector data + CRC
+ unsigned to_dump;
+ if (sr == ID_CD_PATTERN) {
+ to_dump = 4;
+ res.push_back(ID_AM);
+ } else {
+ to_dump = HPI_SECTOR_SIZE + 2;
+ res.push_back(DATA_AM);
+ }
+ // Align to data cells
+ pos++;
+ for (unsigned i = 0; i < to_dump && pos < bitstream_size; i++) {
+ uint8_t byte = 0;
+ unsigned j;
+ for (j = 0; j < 8 && pos < bitstream_size; j++) {
+ bool bit = BIT(bitstream[ pos >> 3 ] , 7 - (pos & 7));
+ pos += 2;
+ byte >>= 1;
+ if (bit) {
+ byte |= 0x80;
+ }
+ }
+ if (j == 8) {
+ res.push_back(byte);
+ }
+ }
+ return res;
+}
+
+bool hpi_format::get_next_sector(const uint8_t *bitstream , int bitstream_size , int& pos , unsigned& track , unsigned& head , unsigned& sector , uint8_t *sector_data)
+{
+ std::vector<uint8_t> block;
+ while (true) {
+ // Scan for ID block first
+ int id_pos;
+ while (true) {
+ if (block.size() == 0) {
+ block = get_next_id_n_block(bitstream , bitstream_size , pos , id_pos);
+ if (block.size() == 0) {
+ return false;
+ }
+ }
+ if (block[ 0 ] == ID_AM && block.size() >= 3) {
+ track = block[ 1 ];
+ head = block[ 2 ] >> 7;
+ sector = block[ 2 ] & 0x7f;
+ break;
+ } else {
+ block.clear();
+ }
+ }
+ // Then for DATA block
+ int data_pos;
+ block = get_next_id_n_block(bitstream , bitstream_size , pos , data_pos);
+ if (block.size() == 0) {
+ return false;
+ }
+ if (block[ 0 ] == DATA_AM && block.size() >= (HPI_SECTOR_SIZE + 1) && abs((data_pos - id_pos) - ID_DATA_OFFSET) <= 16) {
+ break;
+ }
+ }
+
+ for (unsigned i = 0; i < HPI_SECTOR_SIZE; i += 2) {
+ sector_data[ i ] = block[ i + 2 ];
+ sector_data[ i + 1 ] = block[ i + 1 ];
+ }
+
+ return true;
+}
+
+// This table comes straight from hp9895 firmware (it's @ 0x0f90)
+// For each interleave factor in [1..29] it stores the number of positions
+// to move forward in the interleaved sector list when beginning a new track.
+// There are different offsets for tracks on head 0 and tracks on head 1.
+const uint8_t hpi_format::m_track_skew[ HPI_SECTORS - 1 ][ HPI_HEADS ] = {
+ { 0x1c , 0x18 }, // Interleave = 1
+ { 0x1c , 0x18 }, // Interleave = 2
+ { 0x1c , 0x18 }, // Interleave = 3
+ { 0x1d , 0x1a }, // Interleave = 4
+ { 0x1a , 0x18 }, // Interleave = 5
+ { 0x19 , 0x18 }, // Interleave = 6
+ { 0x00 , 0x00 }, // Interleave = 7
+ { 0x1d , 0x1d }, // Interleave = 8
+ { 0x1c , 0x1c }, // Interleave = 9
+ { 0x15 , 0x15 }, // Interleave = 10
+ { 0x00 , 0x00 }, // Interleave = 11
+ { 0x19 , 0x19 }, // Interleave = 12
+ { 0x00 , 0x00 }, // Interleave = 13
+ { 0x1d , 0x1d }, // Interleave = 14
+ { 0x10 , 0x10 }, // Interleave = 15
+ { 0x1d , 0x1d }, // Interleave = 16
+ { 0x00 , 0x00 }, // Interleave = 17
+ { 0x19 , 0x19 }, // Interleave = 18
+ { 0x00 , 0x00 }, // Interleave = 19
+ { 0x15 , 0x15 }, // Interleave = 20
+ { 0x1c , 0x1c }, // Interleave = 21
+ { 0x1d , 0x1d }, // Interleave = 22
+ { 0x00 , 0x00 }, // Interleave = 23
+ { 0x19 , 0x19 }, // Interleave = 24
+ { 0x1a , 0x1a }, // Interleave = 25
+ { 0x1d , 0x1d }, // Interleave = 26
+ { 0x1c , 0x1c }, // Interleave = 27
+ { 0x1d , 0x1d }, // Interleave = 28
+ { 0x00 , 0x00 } // Interleave = 29
+};
+
+const floppy_format_type FLOPPY_HPI_FORMAT = &floppy_image_format_creator<hpi_format>;
diff --git a/src/lib/formats/hpi_dsk.h b/src/lib/formats/hpi_dsk.h
new file mode 100644
index 00000000000..ded492cb74d
--- /dev/null
+++ b/src/lib/formats/hpi_dsk.h
@@ -0,0 +1,55 @@
+// license:BSD-3-Clause
+// copyright-holders: Ansgar Kückes, F. Ulivi
+/*********************************************************************
+
+ hpi_dsk.h
+
+ "HPI" disk format
+
+*********************************************************************/
+
+#pragma once
+
+#ifndef _HPI_DSK_H_
+#define _HPI_DSK_H_
+
+#include "flopimg.h"
+
+// Geometry constants
+constexpr unsigned HPI_TRACKS = 77;
+constexpr unsigned HPI_HEADS = 2;
+constexpr unsigned HPI_SECTORS = 30;
+constexpr unsigned HPI_SECTOR_SIZE = 256;
+
+class hpi_format : public floppy_image_format_t
+{
+public:
+ hpi_format();
+
+ virtual int identify(io_generic *io, uint32_t form_factor) override;
+ virtual bool load(io_generic *io, uint32_t form_factor, floppy_image *image) override;
+ virtual bool save(io_generic *io, floppy_image *image) override;
+ virtual const char *name() const override;
+ virtual const char *description() const override;
+ virtual const char *extensions() const override;
+ virtual bool supports_save() const override;
+
+private:
+ typedef std::array<uint8_t , HPI_SECTORS> sector_list_t;
+ static void interleaved_sectors(unsigned il_factor , sector_list_t& sector_list);
+ void write_mmfm_bit(std::vector<uint32_t> &buffer , bool data_bit , bool clock_bit);
+ void write_mmfm_byte(std::vector<uint32_t> &buffer , uint8_t data , uint8_t clock = 0);
+ void write_sync(std::vector<uint32_t> &buffer);
+ void write_crc(std::vector<uint32_t> &buffer , uint16_t crc);
+ void write_sector(std::vector<uint32_t> &buffer , uint8_t track_no , uint8_t sect_head_no , const uint8_t *sect_data);
+ void fill_with_gap3(std::vector<uint32_t> &buffer);
+ static unsigned chs_to_lba(unsigned cylinder , unsigned head , unsigned sector);
+ std::vector<uint8_t> get_next_id_n_block(const uint8_t *bitstream , int bitstream_size , int& pos , int& start_pos);
+ bool get_next_sector(const uint8_t *bitstream , int bitstream_size , int& pos , unsigned& track , unsigned& head , unsigned& sector , uint8_t *sector_data);
+
+ static const uint8_t m_track_skew[ HPI_SECTORS - 1 ][ HPI_HEADS ];
+};
+
+extern const floppy_format_type FLOPPY_HPI_FORMAT;
+
+#endif /* _HPI_DSK_H_ */
diff --git a/src/tools/floptool.cpp b/src/tools/floptool.cpp
index 190e2004ea6..a1a5f5a44d7 100644
--- a/src/tools/floptool.cpp
+++ b/src/tools/floptool.cpp
@@ -46,6 +46,8 @@
#include "formats/applix_dsk.h"
+#include "formats/hpi_dsk.h"
+
static floppy_format_type floppy_formats[] = {
FLOPPY_MFI_FORMAT,
FLOPPY_DFI_FORMAT,
@@ -75,6 +77,8 @@ static floppy_format_type floppy_formats[] = {
FLOPPY_ORIC_DSK_FORMAT,
FLOPPY_APPLIX_FORMAT,
+
+ FLOPPY_HPI_FORMAT
};
void CLIB_DECL ATTR_PRINTF(1,2) logerror(const char *format, ...)