summaryrefslogtreecommitdiffstats
path: root/src/frontend/mame/ui/pluginopt.cpp
blob: b9167150b6f249f5b7ab5fd1e5faf6b68dae144e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// license:BSD-3-Clause
// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods
/*********************************************************************

    ui/pluginopt.cpp

    Internal menu for the plugin interface.

*********************************************************************/

#include "emu.h"

#include "ui/pluginopt.h"

#include "mame.h"
#include "luaengine.h"


namespace ui {
void menu_plugin::handle()
{
	const event *menu_event = process(0);

	if (menu_event != nullptr && menu_event->itemref != nullptr)
	{
		if (menu_event->iptkey == IPT_UI_SELECT)
			menu::stack_push<menu_plugin_opt>(ui(), container(), (char *)menu_event->itemref);
	}
}

menu_plugin::menu_plugin(mame_ui_manager &mui, render_container &container) :
		menu(mui, container),
		m_plugins(mame_machine_manager::instance()->lua()->get_menu())
{
}

void menu_plugin::populate()
{
	for (auto &curplugin : m_plugins)
		item_append(curplugin, "", 0, (void *)curplugin.c_str());
	item_append(menu_item_type::SEPARATOR);
}

menu_plugin::~menu_plugin()
{
}

menu_plugin_opt::menu_plugin_opt(mame_ui_manager &mui, render_container &container, char *menu) :
		ui::menu(mui, container),
		m_menu(menu)
{
}

void menu_plugin_opt::handle()
{
	const event *menu_event = process(0);

	if (menu_event != nullptr && (uintptr_t)menu_event->itemref)
	{
		std::string key;
		switch(menu_event->iptkey)
		{
			case IPT_UI_UP:
				key = "up";
				break;
			case IPT_UI_DOWN:
				key = "down";
				break;
			case IPT_UI_LEFT:
				key = "left";
				break;
			case IPT_UI_RIGHT:
				key = "right";
				break;
			case IPT_UI_SELECT:
				key = "select";
				break;
			case IPT_UI_DISPLAY_COMMENT:
				key = "comment";
				break;
			case IPT_UI_CLEAR:
				key = "clear";
				break;
			default:
				return;
		}
		if(mame_machine_manager::instance()->lua()->menu_callback(m_menu, (uintptr_t)menu_event->itemref, key))
			reset(reset_options::REMEMBER_REF);
	}
}

void menu_plugin_opt::populate()
{
	std::vector<lua_engine::menu_item> menu_list;
	mame_machine_manager::instance()->lua()->menu_populate(m_menu, menu_list);
	uintptr_t i = 1;
	for(auto &item : menu_list)
	{
		uint32_t flags = 0;
		if(item.flags == "off")
			flags = FLAG_DISABLE;
		else if(item.flags == "l")
			flags = FLAG_LEFT_ARROW;
		else if(item.flags == "r")
			flags = FLAG_RIGHT_ARROW;
		else if(item.flags == "lr")
			flags = FLAG_RIGHT_ARROW | FLAG_LEFT_ARROW;

		if(item.text == "---")
		{
			item_append(menu_item_type::SEPARATOR);
			i++;
		}
		else
			item_append(item.text, item.subtext, flags, (void *)i++);
	}
	item_append(menu_item_type::SEPARATOR);
}

menu_plugin_opt::~menu_plugin_opt()
{
}

} // namespace ui
GC slowly increases the amplification level until a signal goes over the threshold, then modulates its gain so that said signal is at a fixed position over the threshold. Afterwards the increase happens again. This makes the amplifier calibrate itself to the signals read from the floppy as long as flux transitions happen often enough. Too long and the amplification level will reach a point where the random noise the head picks from the environment is amplified over the threshold, creating a pulse where none should be. Too long in our case happens to be around 16-20us with no transitions. That means a long enough zone with a fixed magnetic orientation or no orientation at all (demagnetized or damaged) is going to be read as a series of random pulses after a brief delay. This is used by protections and is known as "weak bits", which read differently each time they're accessed. A second level of filtering happens after the peak detector. When two transitions are a little close (but still over the media threshold) a bouncing effect happens between them giving two very close pulses in the middle in addition to the two normal pulses. The floppy drive detects when pulses are too close and filter them out, leaving the normal ones. As a result, if one writes a train of high-frequency pulses to the floppy they will be read back as a train of too close pulses (weak because they're over the media tolerance, but picked up by the AGC anyway, only somewhat unreliably) they will be all filtered out, giving a large amount of time without any pulse in the output signal. This is used by some protections since it's not writable with a normally clocked controller. Writing is symmetrical, with a series of pulses sent which make the write head invert the magnetic field orientation each time a pulse is received. So, in conclusion, the floppy drive provides inputs to control disk rotation and head position (and choice when double-sided), and the data goes both way as a train of pulses representing magnetic orientation inversions. The absolute value of the orientation itself is never known. 2.3. Floppy controller ~~~~~~~~~~~~~~~~~~~~~~ The task of the floppy controller is to turn the signals to/from the floppy drive into something the main CPU can digest. The level of support actually done by the controller is extremely variable from one device to the other, from pretty much nothing (Apple II, C64) through minimal (Amiga) to complete (Western Digital chips, uPD765 family). Usual functions include drive selection, motor control, track seeking and of course reading and writing data. Of these only the last two need to be described, the rest is obvious. The data is structured at two levels: how individual bits (or nibbles, or bytes) are encoded on the surface, and how these are grouped in individually-addressable sectors. Two standards exist for these, called FM and MFM, and in addition a number of systems use their home-grown variants. Moreover, some systems such as the Amiga use a standard bit-level encoding (MFM) but a homegrown sector-level organisation. 2.3.1. Bit-level encodings '''''''''''''''''''''''''' 2.3.1.1. Cell organization `````````````````````````` All floppy controllers, even the wonkiest like the Apple II one, start by dividing the track in equally-sized cells. They're angular sections in the middle of which a magnetic orientation inversion may be present. From a hardware point of view the cells are seen as durations, which combined with the floppy rotation give the section. For instance the standard MFM cell size for a 3" double-density floppy is 2us, which combined with the also standard 300 rpm rotational speed gives an angular size of 1/100000th of a turn. Another way of saying it is that there are 100K cells in a 3" DD track. In every cell there may or may not be a magnetic orientation transition, e.g. a pulse coming from (reading) or going to (writing) the floppy drive. A cell with a pulse is traditionally noted '1', and one without '0'. Two constraints apply to the cell contents though. First, pulses must not be too close together or they'll blur each-other and/or be filtered out. The limit is slightly better than 1/50000th of a turn for single and double density floppies, half that for HD floppys, and half that again for ED floppies with perpendicular recording. Second, they must not be too away from each other or either the AGC is going to get wonky and introduce phantom pulses or the controller is going to lose sync and get a wrong timing on the cells on reading. Conservative rule of thumb is not to have more than three consecutive '0' cells. Of course protections play with that to make formats not reproducible by the system controller, either breaking the three-zeroes rule or playing with the cells durations/sizes. Bit endocing is then the art of transforming raw data into a cell 0/1 configuration that respects the two constraints. 2.3.1.2. FM encoding ```````````````````` The very first encoding method developed for floppies is called Frequency Modulation, or FM. The cell size is set at slightly over the physical limit, e.g. 4us. That means it is possible to reliably have consecutive '1' cells. Each bit is encoded on two cells: - the first cell, called the clock bit, is '1' - the second cell, called data bit, is the bit Since every other cell at least is '1' there is no risk of going over three zeroes. The name Frequency Modulation simply derives from the fact that a 0 is encoded with one period of a 125Khz pulse train while a 1 is two periods of a 250Khz pulse train. 2.3.1.3. MFM encoding ````````````````````` The FM encoding has been superseded by the Modified Frequency Modulation encoding, which can cram exactly twice as much data on the same surface, hence its other name of "double density". The cell size is set at slightly over half the physical limit, e.g. 2us usually. The constraint means that two '1' cells must be separated by at least one '0' cell. Each bit is once again encoded on two cells: - the first cell, called the clock bit, is '1' if both the previous and current data bits are 0, '0' otherwise - the second cell, called data bit, is the bit The minimum space rule is respected since a '1' clock bit is by definition surrounded by two '0' data bits, and a '1' data bit is surrounded by two '0' clock bits. The longest '0'-cell string possible is when encoding 101 which gives x10001, respecting the maximum of three zeroes. 2.3.1.4. GCR encodings `````````````````````` Group Coded Recording, or GCR, encodings are a class of encodings where strings of bits at least nibble-size are encoded into a given cell stream given by a table. It has been used in particular by the Apple II, the Mac and the C64, and each system has its own table, or tables. 2.3.1.5. Other encodings ```````````````````````` Other encodings exist, like M2FM, but they're very rare and system-specific. 2.3.1.6. Reading back encoded data `````````````````````````````````` Writing encoded data is easy, you only need a clock at the appropriate frequency and send or not a pulse on the clock edges. Reading back the data is where the fun is. Cells are a logical construct and not a physical measurable entity. Rotational speeds very around the defined one (+/- 2% is not rare) and local perturbations (air turbulence, surface distance...) make the instant speed very variable in general. So to extract the cell values stream the controller must dynamically synchronize with the pulse train that the floppy head picks up. The principle is simple: a cell-sized duration window is build within which the presence of at least one pulse indicates the cell is a '1', and the absence of any a '0'. After reaching the end of the window the starting time is moved appropriately to try to keep the observed pulse at the exact middle of the window. This allows to correct the phase on every '1' cell, making the synchronization work if the rotational speed is not too off. Subsequent generations of controllers used a Phase-Locked Loop (PLL) which vary both phase and window duration to adapt better to wrong rotational speeds, with usually a tolerance of +/- 15%. Once the cell data stream is extracted decoding depends on the encoding. In the FM and MFM case the only question is to recognize data bits from clock bits, while in GCR the start position of the first group should be found. That second level of synchronization is handled at a higher level using patterns not found in a normal stream. 2.3.2. Sector-level organization '''''''''''''''''''''''''''''''' Floppies have been designed for read/write random access to reasonably sized blocks of data. Track selection allows for a first level of random access and sizing, but the ~6K of a double density track would be too big a block to handle. 256/512 bytes are considered a more appropriate value. To that end data on a track is organized as a series of (sector header, sector data) pairs where the sector header indicates important information like the sector number and size, and the sector data contains the data. Sectors have to be broken in two parts because while reading is easy, read the header then read the data if you want it, writing requires reading the header to find the correct place then once that is done switching on the writing head for the data. Starting writing is not instantaneous and will not be perfectly phase-aligned with the read header, so space for synchronization is required between header and data. In addition somewhere in the sector header and in the sector data are pretty much always added some kind of checksum allowing to know whether the data was damaged or not. FM and MFM have (not always used) standard sector layout methods. 2.3.2.1. FM sector layout ````````````````````````` The standard "PC" track/sector layout for FM is as such: - A number of FM-encoded 0xff (usually 40) - 6 FM-encoded 0x00 (giving a steady 125KHz pulse train) - The 16-cell stream 1111011101111010 (f77a, clock 0xd7, data 0xfc) - A number of FM-encoded 0xff (usually 26, very variable) Then for each sector: - 6 FM-encoded 0x00 (giving a steady 125KHz pulse train) - The 16-cell stream 1111010101111110 (f57e, clock 0xc7, data 0xfe) - Sector header, e.g. FM encoded track, head, sector, size code and two bytes of crc - 11 FM-encoded 0xff - 6 FM-encoded 0x00 (giving a steady 125KHz pulse train) - The 16-cell stream 1111010101101111 (f56f, clock 0xc7, data 0xfb) - FM-encoded sector data followed by two bytes of crc - A number of FM-encoded 0xff (usually 48, very variable) The track is finished with a stream of '1' cells. The 125KHz pulse trains are used to lock the PLL to the signal correctly. The specific 16-cells streams allow to distinguish between clock and data bits by providing a pattern that is not supposed to happen in normal FM-encoded data. In the sector header track numbers start at 0, heads are 0/1 depending on the size, sector numbers usually start at 1 and size code is 0 for 128 bytes, 1 for 256, 2 for 512, etc. The CRC is a cyclic redundancy check of the data bits starting with the mark just after the pulse train using polynom 0x11021. The Western Digital-based controllers usually get rid of everything but some 0xff before the first sector and allow a better use of space as a result. 2.3.2.2. MFM sector layout `````````````````````````` The standard "PC" track/sector layout for MFM is as such: - A number of MFM-encoded 0x4e (usually 80) - 12 FM-encoded 0x00 (giving a steady 250KHz pulse train) - 3 times the 16-cell stream 0101001000100100 (5224, clock 0x14, data 0xc2) - The MFM-encoded value 0xfc - A number of MFM-encoded 0x4e (usually 50, very variable) Then for each sector: - 12 FM-encoded 0x00 (giving a steady 250KHz pulse train) - 3 times the 16-cell stream 0100010010001001 (4489, clock 0x0a, data 0xa1) - Sector header, e.g. MFM-encoded 0xfe, track, head, sector, size code and two bytes of crc - 22 MFM-encoded 0x4e - 12 MFM-encoded 0x00 (giving a steady 250KHz pulse train) - 3 times the 16-cell stream 0100010010001001 (4489, clock 0x0a, data 0xa1) - MFM-encoded 0xfb, sector data followed by two bytes of crc - A number of MFM-encoded 0x4e (usually 84, very variable) The track is finished with a stream of MFM-encoded 0x4e. The 250KHz pulse trains are used to lock the PLL to the signal correctly. The cell pattern 4489 does not appear in normal MFM-encoded data and is used for clock/data separation. As for FM, the Western Digital-based controllers usually get rid of everything but some 0x4e before the first sector and allow a better use of space as a result. 2.3.2.3. Formatting and write splices ````````````````````````````````````` To be usable, a floppy must have the sector headers and default sector data written on every track before using it. The controller starts writing at a given place, often the index pulse but on some systems whenever the command is sent, and writes until a complete turn is done. That's called formatting the floppy. At the point where the writing stops there is a synchronization loss since there is no chance the cell stream clock warps around perfectly. This brutal phase change is called a write splice, specifically the track write splice. It is the point where writing should start if one wants to raw copy the track to a new floppy. Similarly two write splices are created when a sector is written at the start and end of the data block part. They're not supposed to happen on a mastered disk though, even if there are some rare exceptions. 3. The new implementation ------------------------- 3.1. Floppy disk representation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The floppy disk contents are represented by the class floppy_image. It contains information of the media type and a representation of the magnetic state of the surface. The media type is divided in two parts. The first half indicates the physical form factor, i.e. all medias with that form factor can be physically inserted in a reader that handles it. The second half indicates the variants which are usually detectable by the reader, such as density and number of sides. Track data consists of a series of 32-bits lsb-first values representing magnetic cells. Bits 0-27 indicate the absolute position of the start of the cell (not the size), and bits 28-31 the type. Type can be: - 0, MG_A -> Magnetic orientation A - 1, MG_B -> Magnetic orientation B - 2, MG_N -> Non-magnetized zone (neutral) - 3, MG_D -> Damaged zone, reads as neutral but cannot be changed by writing The position is in angular units of 1/200,000,000th of a turn. It corresponds to one nanosecond when the drive rotates at 300 rpm. The last cell implicit end position is of course 200,000,000. Unformatted tracks are encoded as zero-size. The "track splice" information indicates where to start writing if you try to rewrite a physical disk with the data. Some preservation formats encode that information, it is guessed for others. The write track function of fdcs should set it. The representation is the angular position relative to the index. 3.2. Converting to and from the internal representation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3.2.1. Class and interface '''''''''''''''''''''''''' We need to be able to convert on-disk formats of the floppy data to and from the internal representation. This is done through classes derived from floppy_image_format_t. The interface to be implemented includes: - **name()** gives the short name of the on-disk format - **description()** gives a short description of the format - **extensions()** gives a comma-separated list of file name extensions found for that format - **supports_save()** returns true is converting to that external format is supported - **identify(file, form factor)** gives a 0-100 score for the file to be of that format: - **0** = not that format - **100** = certainly that format - **50** = format identified from file size only - **load(file, form factor, floppy_image)** loads an image and converts it into the internal representation - **save(file, floppy_image)** (if implemented) converts from the internal representation and saves an image All of these methods are supposed to be stateless. 3.2.2. Conversion helper methods '''''''''''''''''''''''''''''''' A number of methods are provided to simplify writing the converter classes. 3.2.2.1. Load-oriented conversion methods ````````````````````````````````````````` | **generate_track_from_bitstream(track number,** | **head number,** | **UINT8 \*cell stream,** | **int cell count,** | **floppy image)** | Takes a stream of cell types (0/1), MSB-first, converts it to the internal format and stores it at the given track and head in the given image. | **generate_track_from_levels(track number,** | **head number,** | **UINT32 \*cell levels,** | **int cell count,** | **splice position,** | **floppy image)** Takes a variant of the internal format where each value represents a cell, the position part of the values is the size of the cell and the level part is MG_0, MG_1 for normal cell types, MG_N, MG_D for unformatted/damaged cells, and MG_W for Dungeon-Master style weak bits. Converts it into the internal format. The sizes are normalized so that they total to a full turn. | **normalize_times(UINT32 \*levels,** | **int level_count)** Takes an internal-format buffer where the position part represents angle until the next change and turns it into a normal positional stream, first ensuring that the total size is normalized to a full turn. 3.2.2.2. Save-oriented conversion methods ````````````````````````````````````````` | **generate_bitstream_from_track(track number,** | **head number,** | **base cell size**, | **UINT8 \*cell stream,** | **int &cell_stream_size,** | **floppy image)** Extract a cell 0/1 stream from the internal format using a PLL setup with an initial cell size set to 'base cell size' and a +/- 25% tolerance. | **struct desc_xs { int track, head, size; const UINT8 \*data }** | **extract_sectors_from_bitstream_mfm_pc(...)** | **extract_sectors_from_bitstream_fm_pc(const UINT8 \*cell stream,** | **int cell_stream_size,** | **desc_xs \*sectors,** | **UINT8 \*sectdata,** | **int sectdata_size)** Extract standard mfm or fm sectors from a regenerated cell stream. Sectors must point to an array of 256 desc_xs. An existing sector is recognizable by having ->data non-null. Sector data is written in sectdata up to sectdata_size bytes. | **get_geometry_mfm_pc(...)** | **get_geometry_fm_pc(floppy image,** | **base cell size,** | **int &track_count,** | **int &head_count,** | **int &sector_count)** Extract the geometry (heads, tracks, sectors) from a pc-ish floppy image by checking track 20. | **get_track_data_mfm_pc(...)** | **get_track_data_fm_pc(track number,** | **head number,** | **floppy image,** | **base cell size,** | **sector size,** | **sector count,** | **UINT8 \*sector data)** | Extract what you'd get by reading in order 'sector size'-sized sectors from number 1 to sector count and put the result in sector data. 3.3. Floppy drive ~~~~~~~~~~~~~~~~~ The class floppy_image_interface simulates the floppy drive. That includes a number of control signals, reading, and writing. Control signal changes must be synchronized, e.g. fired off a timer to ensure the current time is the same for all devices. 3.3.1. Control signals '''''''''''''''''''''' Due to the way they're usually connected to CPUs (e.g. directly on an I/O port), the control signals work with physical instead of logical values. Which means than in general 0 means active, 1 means inactive. Some signals also have a callback associated called when they change. **mon_w(state) / mon_r()** Motor on signal, rotates on 0. **idx_r() / setup_index_pulse_cb(cb)** Index signal, goes 0 at start of track for about 2ms. Callback is synchronized. Only happens when a disk is in and the motor is running. **ready_r() / setup_ready_cb(cb)** Ready signal, goes to 1 when the disk is removed or the motor stopped. Goes to 0 after two index pulses. **wpt_r() / setup_wpt_cb(cb)** Write protect signal (1 = readonly). Callback is unsynchronized. **dskchg_r()** Disk change signal, goes to 1 when a disk is change, goes to 0 on track change. **dir_w(dir)** Selects track stepping direction (1 = out = decrease track number). **stp_w(state)** Step signal, moves by one track on 1->0 transition. **trk00_r()** Track 0 sensor, returns 0 when on track 0. **ss_w(ss) / ss_r()** Side select 3.3.2. Read/write interface ''''''''''''''''''''''''''' The read/write interface is designed to work asynchronously, e.g. somewhat independently of the current time. .. [1] Cylinder is a hard-drive term somewhat improperly used for floppies. It comes from the fact that hard-drives are similar to floppies but include a series of stacked disks with a read/write head on each. The heads are physically linked and all point to the same circle on every disk at a given time, making the accessed area look like a cylinder. Hence the name.