diff options
Diffstat (limited to 'src/devices/bus/ti99x/gromport.c')
-rw-r--r-- | src/devices/bus/ti99x/gromport.c | 2509 |
1 files changed, 2509 insertions, 0 deletions
diff --git a/src/devices/bus/ti99x/gromport.c b/src/devices/bus/ti99x/gromport.c new file mode 100644 index 00000000000..2119a93c348 --- /dev/null +++ b/src/devices/bus/ti99x/gromport.c @@ -0,0 +1,2509 @@ +// license:LGPL-2.1+ +// copyright-holders:Michael Zapf +/*************************************************************************** + GROM port - the cartridge port of the TI-99/4, TI-99/4A, and + TI-99/8 console. + + The name refers to the main intended application scenario, that is, + to host cartridges with GROMs. The second, wider port of the console is + called I/O port and connects to the Peripheral Expansion System + (see peribox.h). + + LEFT + + /RESET 1||2 GND + D7 3||4 CRUCLK + D6 5||6 CRUIN + D5 7||8 A15/CRUOUT + D4 9||10 A13 + D3 11||12 A12 + D2 13||14 A11 + D1 15||16 A10 + D0 17||18 A9 + +5V 19||20 A8 + /GS 21||22 A7 + A14 23||24 A3 + DBIN 25||26 A6 + GRMCLK 27||28 A5 + -5V 29||30 A4 + READY 31||32 /WE + GND 33||34 /ROMG + GND 35||36 GND + + RIGHT + + Address bus line ordering, according to TI convention, is A0 (MSB) ... A15 (LSB). + A0, A1, and A2 are not delivered to the port but decoded in the console: + + /ROMG is asserted for A0/A1/A2 = 011 (addresses 6000 - 7fff) + /GS is asserted for A0...A5 = 100110 (addresses 9800 - 9bff) + + This means that a maximum of 8 KiB of direct memory space can be accessed. + The /GS line is used to enable GROM circuits on the board (serial ROMs with + own address counter, see grom.h). + + When a cartridge is inserted the /RESET line is pulled to ground, which + via a R/C component pulls down the /RESET input of the timer circuit for + a short time, which in turn resets the CPU. In order to dump cartridges, + a common procedure was to tape the /RESET line of the cartridge. However, + inserting a cartridge without resetting often caused so much data bus noise + that the console usually locked up. + + ---------------- + + The TI-99/4A computer was strictly designed for cartridge usage. The basic + console had only little directly accessible RAM and offered no ways to + write machine language programs; cartridges were intended to add various + capabilities, or just for running games. + + Beside the seemingly simple handling, Texas Instruments had own intentions + behind their cartridge strategy. With only 8 KiB of direct access memory, a + major part of the cartridge code had to be stored in GROMs, which had to be + licensed from Texas Instruments. Thus they kept firm control over all + software development. + + Over the years, and with the increasingly difficult market situations, + TI's policies seem to have changed. This may be the reason that the built-in + operating system actually allowed for running ROM-only cartridges until TI + clipped out this part in the OS, banning cartridges without GROMs. Consoles + with this modification were produced in 1983, TI's last year in the home + computer business. + + Although only 8 KiB were available for direct addressing, clever techniques + were invented by third-party manufacturers. The first extension was utilized + by TI themselves in the Extended Basic cartridge which offers two banks + of ROM contents. Switching between the banks is achieved by writing a value + to the ROM space at 6000 or 6002. Later, cartridges with much more memory + space were created, up to the Super Space II cartridge with 128 KiB of + buffered SRAM. + + ---------------- + + From the console case layout the GROM port was intended for a single + cartridge only. Although never officially released, the operating system + of the TI console supported a multi-cartridge extender with software + switching. There were also extenders based on hardware switching (like + the Navarone Widget). + + This emulation offers both variants as slot options: + + -gromport single : default single cartridge connector + -gromport multi : software-switchable 4-slot cartridge extender + -gromport gkracker : GRAM Kracker + + The last option enables another popular device, the GRAM Kracker. This is + a device to be plugged into the cartridge slot with five manual switches + at the front and an own cartridge slot at its top. It contains buffered + SRAM, a built-in ROM, and a GROM simulator which simulates GROM behaviour + when accessing the buffered RAM. Its main use is to provide editable + storage for the read-only cartridge contents. Cartridges can be plugged + into its slot; their contents can be read and written to disk, modified as + needed, and loaded into the buffered RAM. Even the console GROMs can be + copied into the device; despite running in parallel, the GROM simulator + is able to override the console GROMs, thus allowing the user to install + a customized OS. + + (Emulation detail: Take care when changing something in this emulation - + this overrun is emulated by the sequence in which the devices on the datamux + are executed.) + + Michael Zapf, July 2012 + +***************************************************************************/ +#include "gromport.h" + +#define TRACE_RPK 0 +#define TRACE_CHANGE 0 +#define TRACE_ILLWRITE 0 +#define TRACE_CONFIG 0 +#define TRACE_READ 0 +#define TRACE_GROM 0 +#define TRACE_GKRACKER 0 +#define TRACE_CRU 0 + +#define GROM3_TAG "grom3" +#define GROM4_TAG "grom4" +#define GROM5_TAG "grom5" +#define GROM6_TAG "grom6" +#define GROM7_TAG "grom7" + +#define CARTGROM_TAG "grom_contents" +#define CARTROM_TAG "rom_contents" +#define GKRACKER_ROM_TAG "gkracker_rom" +#define GKRACKER_NVRAM_TAG "gkracker_nvram" + +gromport_device::gromport_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : bus8z_device(mconfig, GROMPORT, "Cartridge port", tag, owner, clock, "gromport", __FILE__), + device_slot_interface(mconfig, *this), + m_connector(NULL), + m_reset_on_insert(true), + m_console_ready(*this), + m_console_reset(*this) { } + +/* Only called for addresses 6000-7fff and GROM addresses (see datamux config) */ +READ8Z_MEMBER(gromport_device::readz) +{ + if (m_connector != NULL) + m_connector->readz(space, offset, value); +} + +WRITE8_MEMBER(gromport_device::write) +{ + if (m_connector != NULL) + m_connector->write(space, offset, data); +} + +READ8Z_MEMBER(gromport_device::crureadz) +{ + if (m_connector != NULL) + m_connector->crureadz(space, offset, value); +} + +WRITE8_MEMBER(gromport_device::cruwrite) +{ + if (m_connector != NULL) + m_connector->cruwrite(space, offset, data); +} + +WRITE_LINE_MEMBER(gromport_device::ready_line) +{ + m_console_ready(state); +} + +void gromport_device::device_start() +{ + m_console_ready.resolve(); + m_console_reset.resolve(); +} + +void gromport_device::device_reset() +{ + m_reset_on_insert = (ioport("CARTRESET")->read()==0x01); +} + +void gromport_device::set_grom_base(UINT16 grombase, UINT16 grommask) +{ + m_grombase = grombase; + m_grommask = grommask; +} + +/* + Shall we reset the console when a cartridge has been inserted? + This is triggered by the cartridge by pulling down /RESET via a capacitor. + Accordingly, when we put a tape over the /RESET contact we can avoid the + reset, which is useful when we want to swap the cartridges while a program + is runnning. +*/ +void gromport_device::cartridge_inserted() +{ + if (m_reset_on_insert) + { + m_console_reset(ASSERT_LINE); + m_console_reset(CLEAR_LINE); + } +} + +void gromport_device::device_config_complete() +{ + m_connector = static_cast<ti99_cartridge_connector_device*>(first_subdevice()); + set_grom_base(0x9800, 0xf800); +} + +SLOT_INTERFACE_START( gromport ) + SLOT_INTERFACE("single", GROMPORT_SINGLE) + SLOT_INTERFACE("multi", GROMPORT_MULTI) + SLOT_INTERFACE("gkracker", GROMPORT_GK) +SLOT_INTERFACE_END + + +INPUT_PORTS_START(gromport) + PORT_START( "CARTRESET" ) + PORT_CONFNAME( 0x01, 0x01, "RESET on cartridge insert" ) + PORT_CONFSETTING( 0x00, DEF_STR( Off ) ) + PORT_CONFSETTING( 0x01, DEF_STR( On ) ) +INPUT_PORTS_END + +ioport_constructor gromport_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(gromport); +} + +const device_type GROMPORT = &device_creator<gromport_device>; + +/*************************************************************************** + Different versions of cartridge connections + + single: the standard console connector, one cartridge + multi: a multi-cart expander, up to 4 cartridges with software selection + gkracker: GRAMKracker, a device with NVRAM which allows the user to copy + the contents of the cartridge plugged into its slot into the NVRAM + and to modify it. + +***************************************************************************/ + +const device_type GROMPORT_SINGLE = &device_creator<single_conn_device>; +const device_type GROMPORT_MULTI = &device_creator<multi_conn_device>; +const device_type GROMPORT_GK = &device_creator<gkracker_device>; + +ti99_cartridge_connector_device::ti99_cartridge_connector_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source) + : bus8z_device(mconfig, type, name, tag, owner, clock, shortname, source), + m_gromport(NULL) +{ +} + +WRITE_LINE_MEMBER( ti99_cartridge_connector_device::ready_line ) +{ + m_gromport->ready_line(state); +} + +void ti99_cartridge_connector_device::device_config_complete() +{ + m_gromport = static_cast<gromport_device*>(owner()); +} + +UINT16 ti99_cartridge_connector_device::grom_base() +{ + return m_gromport->get_grom_base(); +} + +UINT16 ti99_cartridge_connector_device::grom_mask() +{ + return m_gromport->get_grom_mask(); +} + +single_conn_device::single_conn_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : ti99_cartridge_connector_device(mconfig, GROMPORT_SINGLE, "Standard cartridge connector", tag, owner, clock, "single", __FILE__), + m_cartridge(NULL) +{ +} + +READ8Z_MEMBER(single_conn_device::readz) +{ + // Pass through + m_cartridge->readz(space, offset, value); +} + +WRITE8_MEMBER(single_conn_device::write) +{ + // Pass through + m_cartridge->write(space, offset, data); +} + +READ8Z_MEMBER(single_conn_device::crureadz) +{ + // Pass through + m_cartridge->crureadz(space, offset, value); +} + +WRITE8_MEMBER(single_conn_device::cruwrite) +{ + // Pass through + m_cartridge->cruwrite(space, offset, data); +} + +void single_conn_device::device_start() +{ + m_cartridge = static_cast<ti99_cartridge_device*>(first_subdevice()); +} + +void single_conn_device::device_reset() +{ + m_cartridge->set_slot(0); +} + +static MACHINE_CONFIG_FRAGMENT( single_slot ) + MCFG_DEVICE_ADD("cartridge", TI99CART, 0) +MACHINE_CONFIG_END + +machine_config_constructor single_conn_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME( single_slot ); +} + +/********************************************************/ + +/* + The multi-cartridge extender + + This is a somewhat mythical device which was never available for the normal + customer, but there are reports of the existence of such a device + in development labs or demonstrations. + + The interesting thing about this is that the OS of the console + fully supports this multi-cartridge extender, providing a selection + option on the screen to switch between different plugged-in + cartridges. + + The switching is possible by decoding address lines that are reserved + for GROM access. GROMs are accessed via four separate addresses + 9800, 9802, 9C00, 9C02. The addressing scheme looks like this: + + 1001 1Wxx xxxx xxM0 W = write(1), read(0), M = address(1), data(0) + + This leaves 8 bits (256 options) which are not decoded inside the + console. As the complete address is routed to the port, some circuit + just needs to decode the xxx lines and turn on the respective slot. + + One catch must be considered: Some cartridges contain ROMs which are + directly accessed and not via ports. This means that the ROMs must + be activated according to the slot that is selected. + + Another issue: Each GROM contains an own address counter and an ID. + According to the ID the GROM only delivers data if the address counter + is within the ID area (0 = 0000-1fff, 1=2000-3fff ... 7=e000-ffff). + Thus it is essential that all GROMs stay in sync with their address + counters. We have to route all address settings to all slots and their + GROMs, even when the slot has not been selected before. The selected + just shows its effect when data is read. In this case, only the + data from the selected slot will be delivered. + + This may be considered as a design flaw within the complete cartridge system + which eventually led to TI not manufacturing that device for the broad + market. +*/ + +#define AUTO -1 + +multi_conn_device::multi_conn_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : ti99_cartridge_connector_device(mconfig, GROMPORT_MULTI, "Multi-cartridge extender", tag, owner, clock, "multi", __FILE__), + m_active_slot(0), + m_fixed_slot(0), + m_next_free_slot(0) +{ +} + +/* + Activates a slot in the multi-cartridge extender. + Setting the slot is done by accessing the GROM ports using a + specific address: + + slot 0: 0x9800 (0x9802, 0x9c00, 0x9c02) : cartridge1 + slot 1: 0x9804 (0x9806, 0x9c04, 0x9c06) : cartridge2 + ... + slot 15: 0x983c (0x983e, 0x9c3c, 0x9c3e) : cartridge16 + + Scheme: + + 1001 1Wxx xxxx xxM0 (M=mode; M=0: data, M=1: address; W=write) + + The following addresses are theoretically available, but the + built-in OS does not use them; i.e. cartridges will not be + included in the selection list, and their features will not be + found by lookup, but they could be accessed directly by user + programs. + slot 16: 0x9840 (0x9842, 0x9c40, 0x9c42) + ... + slot 255: 0x9bfc (0x9bfe, 0x9ffc, 0x9ffe) + + Setting the GROM base should select one cartridge, but the ROMs in the + CPU space must also be switched. As there is no known special mechanism + we assume that by switching the GROM base, the ROM is automatically + switched. + + Caution: This means that cartridges which do not have at least one + GROM cannot be switched with this mechanism. + + We assume that the slot number is already calculated in the caller: + slotnumber>=0 && slotnumber<=255 + + NOTE: The OS will stop searching when it finds slots 1 and 2 empty. + Interestingly, cartridge subroutines are found nevertheless, even when + the cartridge is plugged into a higher slot. +*/ +void multi_conn_device::set_slot(int slotnumber) +{ + if (TRACE_CHANGE) + if (m_active_slot != slotnumber) logerror("%s: Setting cartslot to %d\n", tag(), slotnumber); + + if (m_fixed_slot==AUTO) + m_active_slot = slotnumber; + else + m_active_slot = m_fixed_slot; +} + +int multi_conn_device::get_active_slot(bool changebase, offs_t offset) +{ + int slot; + if (changebase) + { + if ((offset & grom_mask()) == grom_base()) + { + set_slot((offset>>2) & 0x00ff); + } + } + slot = m_active_slot; + return slot; +} + +void multi_conn_device::insert(int index, ti99_cartridge_device* cart) +{ + if (TRACE_CHANGE) logerror("%s: Insert slot %d\n", tag(), index); + m_cartridge[index] = cart; + m_gromport->cartridge_inserted(); +} + +void multi_conn_device::remove(int index) +{ + if (TRACE_CHANGE) logerror("%s: Remove slot %d\n", tag(), index); + m_cartridge[index] = NULL; +} + +READ8Z_MEMBER(multi_conn_device::readz) +{ + int slot = get_active_slot(true, offset); + + // If we have a GROM access, we need to send the read request to all + // attached cartridges so the slot is irrelevant here. Each GROM + // contains an internal address counter, and we must make sure they all stay in sync. + if ((offset & grom_mask()) == grom_base()) + { + for (int i=0; i < NUMBER_OF_CARTRIDGE_SLOTS; i++) + { + if (m_cartridge[i] != NULL) + { + UINT8 newval = *value; + m_cartridge[i]->readz(space, offset, &newval, mem_mask); + if (i==slot) + { + *value = newval; + } + } + } + } + else + { + if (slot < NUMBER_OF_CARTRIDGE_SLOTS && m_cartridge[slot] != NULL) + { + m_cartridge[slot]->readz(space, offset, value, mem_mask); + } + } +} + +WRITE8_MEMBER(multi_conn_device::write) +{ + int slot = get_active_slot(true, offset); + + // Same issue as above (read) + // We don't have GRAM cartridges, anyway, so it's just used for setting the address. + if ((offset & grom_mask()) == grom_base()) + { + for (int i=0; i < NUMBER_OF_CARTRIDGE_SLOTS; i++) + { + if (m_cartridge[i] != NULL) + { + m_cartridge[i]->write(space, offset, data, mem_mask); + } + } + } + else + { + if (slot < NUMBER_OF_CARTRIDGE_SLOTS && m_cartridge[slot] != NULL) + { + // logerror("%s: try it on slot %d\n", tag(), slot); + m_cartridge[slot]->write(space, offset, data, mem_mask); + } + } +} + +READ8Z_MEMBER(multi_conn_device::crureadz) +{ + int slot = get_active_slot(false, offset); + /* Sanity check. Higher slots are always empty. */ + if (slot >= NUMBER_OF_CARTRIDGE_SLOTS) + return; + + if (m_cartridge[slot] != NULL) + { + m_cartridge[slot]->crureadz(space, offset, value); + } +} + +WRITE8_MEMBER(multi_conn_device::cruwrite) +{ + int slot = get_active_slot(true, offset); + + /* Sanity check. Higher slots are always empty. */ + if (slot >= NUMBER_OF_CARTRIDGE_SLOTS) + return; + + if (m_cartridge[slot] != NULL) + { + m_cartridge[slot]->cruwrite(space, offset, data); + } +} + +void multi_conn_device::device_start() +{ + m_next_free_slot = 0; + m_active_slot = 0; + for (int i=0; i < NUMBER_OF_CARTRIDGE_SLOTS; i++) + { + m_cartridge[i] = NULL; + } +} + +void multi_conn_device::device_reset(void) +{ + m_active_slot = 0; + m_fixed_slot = ioport("CARTSLOT")->read() - 1; +} + +static MACHINE_CONFIG_FRAGMENT( multi_slot ) + MCFG_DEVICE_ADD("cartridge1", TI99CART, 0) + MCFG_DEVICE_ADD("cartridge2", TI99CART, 0) + MCFG_DEVICE_ADD("cartridge3", TI99CART, 0) + MCFG_DEVICE_ADD("cartridge4", TI99CART, 0) +MACHINE_CONFIG_END + +machine_config_constructor multi_conn_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME( multi_slot ); +} + +INPUT_PORTS_START(multi_slot) + PORT_START( "CARTSLOT" ) + PORT_DIPNAME( 0x0f, 0x00, "Multi-cartridge slot" ) + PORT_DIPSETTING( 0x00, "Auto" ) + PORT_DIPSETTING( 0x01, "Slot 1" ) + PORT_DIPSETTING( 0x02, "Slot 2" ) + PORT_DIPSETTING( 0x03, "Slot 3" ) + PORT_DIPSETTING( 0x04, "Slot 4" ) +INPUT_PORTS_END + +ioport_constructor multi_conn_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(multi_slot); +} + +/************************************************************************** + + The GRAM Kracker was manufactured by Miller's Graphics and designed to + fit into the cartridge slot. + + It offers one own cartridge slot at the top side and a row of switches + at its front. It contains buffered SRAM circuits; the base version has + 56 KiB, and the extended version has 80 KiB. + + The operation of the GRAM Kracker is a bit complex and most likely + bound to fail when you have no manual. Accordingly, this emulation is + neither simpler nor more difficult to use. + + Concept of operation: + + Loader: The GRAM Kracker contains a small loader utility + which allows you to dump cartridges and to load the contents into the + SRAM of the GK. This loader utility is active when the switch 5 is put + into "Loader on" state. The activated loader hides the TI BASIC + interpreter in the console. + + Cartridges: When a cartridge is plugged into the GK the contents may be + dumped and saved to disk by the loader. They cannot be directly copied + into the GK because the memory locations are hidden by the cartridge. + + Loading the cartridge into the SRAM: With the cartridge unplugged, dumps + can be loaded into the SRAM using the loader. This is one major use case + of the GK, that is, to load dumps from disk, and in particular modified + dumps. (There is no checksum, so contents may be freely changed.) + + Console dump: The GK is also able to dump the console GROMs and also to + load them into the SRAM (only in the extended version). Due to a + peculiarity of the TI console design it is possible to override the + console GROMs with the contents in the cartridge slot. + + A standard procedure for use with the GK: + + Save cartridge: + + - Put switches to [Normal | OpSys | TI BASIC | W/P | Loader On] + - Insert a disk image into disk drive 1 + - Plug in a cartridge + - Reset the console (done automatically here) + - Visit the option screen, press 1 for GRAM KRACKER + - In the GK loader, select 2 for Save Module + - Follow the on-screen instructions. Switches are set via the dip switch menu. + - Enter a target file name + - Saving is complete when the Save operation has been unmarked. + + Load cartridge: + + - Put switches to [Normal | OpSys | TI BASIC | W/P | Loader On] + - Insert a disk image into disk drive 1 + - Make sure no cartridge is plugged in + - Press 1 for GRAM KRACKER + - Press 3 for Init Module space; follow instructions + - Press 1 for Load Module; specify file name on disk + - Loading is complete when the Load operation has been unmarked. + + Memory organisation: + + The console has three GROMs with 6 KiB size and occupying 8 KiB of address + space each. These are called GROMs 0, 1, and 2. GROM 0 contains the common + routines for the computer operation; GROMs 1 and 2 contain TI BASIC. + + Memory locations 6000-7fff are assigned to cartridge ROMs; in some + cartridges, a second ROM bank can be used by writing a value to a special + ROM access. This way, instead of 8 KiB we often have 16 KiB at these + locations. + + Each cartridge can host up to 5 GROMs (called GROM 3, 4, 5, 6, and 7). + As in the console, each one occupies 6 KiB in an 8 KiB window. + + The GRAM Kracker offers + + - a loader in an own GROM 1 (which hides the console GROM 1 when active, + so we have no BASIC anymore). The contents of the loader must be found + by the emulator in a file named ti99_gkracker.zip. + + - a complete set of 8 (simulated) GRAMs with full 8 KiB each (done by a + simple addressing circuit); the basic version only offered GRAMs 3-7 + + - 16 KiB of RAM memory space for the 6000-7fff area (called "bank 1" and "bank 2") + + Notes: + + - it is mandatory to turn off the loader when loading into GRAM 1, but only + after prompted in the on-screen instructions, or the loader will crash + - GRAM0 must be properly loaded if switch 2 is set to GRAM0 and the computer is reset + - Switch 4 must not be in W/P position (write protect) when loading data + into the GK (either other position will do). + + +***************************************************************************/ +enum +{ + GK_OFF = 0, + GK_NORMAL = 1, + GK_GRAM0 = 0, + GK_OPSYS = 1, + GK_GRAM12 = 0, + GK_TIBASIC = 1, + GK_BANK1 = 0, + GK_WP = 1, + GK_BANK2 = 2, + GK_LDON = 0, + GK_LDOFF = 1 +}; + +#define GKSWITCH1_TAG "GKSWITCH1" +#define GKSWITCH2_TAG "GKSWITCH2" +#define GKSWITCH3_TAG "GKSWITCH3" +#define GKSWITCH4_TAG "GKSWITCH4" +#define GKSWITCH5_TAG "GKSWITCH5" + +gkracker_device::gkracker_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : ti99_cartridge_connector_device(mconfig, GROMPORT_GK, "GRAMKracker", tag, owner, clock, "ti99_gkracker", __FILE__), + device_nvram_interface(mconfig, *this), + m_ram_page(0), + m_grom_address(0), + m_ram_ptr(NULL), + m_grom_ptr(NULL), + m_waddr_LSB(false), + m_cartridge(NULL) +{ +} + +READ8Z_MEMBER(gkracker_device::readz) +{ + if ((offset & grom_mask()) == grom_base()) + { + // Reads from the GRAM space of the GRAM Kracker. + + // The GK does not have a readable address counter, but the console + // GROMs will keep our address counter up to date. That is + // exactly what happens in the real machine. + // (The console GROMs are not accessed here but directly via the datamux + // so we can just return without doing anything) + if ((offset & 0x0002)!=0) return; + + int id = ((m_grom_address & 0xe000)>>13)&0x07; + switch (id) + { + case 0: + // GRAM 0. Only return a value if switch 2 is in GRAM0 position. + if (m_gk_switch[2]==GK_GRAM0) + *value = m_ram_ptr[m_grom_address]; + break; + case 1: + // If the loader is turned on, return loader contents. + if (m_gk_switch[5]==GK_LDON) + { + // The only ROM contained in the GK box is the loader + // Adjust the address + *value = m_grom_ptr[m_grom_address & 0x1fff]; + } + else + { + // Loader off + // GRAM 1. Only return a value if switch 3 is in GRAM12 position. + // Otherwise, the console GROM 1 will respond (not here; it is the grom_device + // whose output would then not be overwritten) + if (m_gk_switch[3]==GK_GRAM12) + *value = m_ram_ptr[m_grom_address]; + } + break; + case 2: + // GRAM 2. Only return a value if switch 3 is in GRAM12 position. + if (m_gk_switch[3]==GK_GRAM12) + *value = m_ram_ptr[m_grom_address]; + break; + default: + // Cartridge space (0x6000 - 0xffff) + // When a cartridge is installed, it overrides the GK contents + // but only if it has GROMs + bool guest_has_grom = false; + + if (m_cartridge != NULL) + { + guest_has_grom = m_cartridge->has_grom(); + // Note that we only have ONE real cartridge and the GK; + // we need not access all slots. + if (guest_has_grom) + { + m_cartridge->readz(space, offset, value, mem_mask); // read from guest + } + } + if (!guest_has_grom && (m_gk_switch[1]==GK_NORMAL)) + *value = m_ram_ptr[m_grom_address]; // use the GK memory + } + + // The GK GROM emulation does not wrap at 8K boundaries. + m_grom_address = (m_grom_address + 1) & 0xffff; + + // Reset the write address flipflop. + m_waddr_LSB = false; + } + else + { + if (m_cartridge != NULL) + { + // Read from the guest cartridge. + m_cartridge->readz(space, offset, value, mem_mask); + } + else + { + // Reads from the RAM space of the GRAM Kracker. + if (m_gk_switch[1] == GK_OFF) return; // just don't do anything + switch (m_gk_switch[4]) + { + // RAM is stored behind the GRAM area + case GK_BANK1: + *value = m_ram_ptr[offset+0x10000 - 0x6000]; + break; + case GK_BANK2: + *value = m_ram_ptr[offset+0x12000 - 0x6000]; + break; + + default: + // Switch in middle position (WP, implies auto-select according to the page flag) + if (m_ram_page==0) + *value = m_ram_ptr[offset+0x10000 - 0x6000]; + else + *value = m_ram_ptr[offset+0x12000 - 0x6000]; + break; + } + } + } + if (TRACE_READ) logerror("%s: Read %04x -> %02x\n", tag(), offset, *value); +} + +WRITE8_MEMBER(gkracker_device::write) +{ + // write to the guest cartridge if present + if (m_cartridge != NULL) + { + m_cartridge->write(space, offset, data, mem_mask); + } + + if ((offset & grom_mask()) == grom_base()) + { + // Write to the GRAM space of the GRAM Kracker. + if ((offset & 0x0002)==0x0002) + { + // Set address + if (m_waddr_LSB == true) + { + // Accept low address byte (second write) + m_grom_address = (m_grom_address & 0xff00) | data; + m_waddr_LSB = false; + if (TRACE_GROM) logerror("%s: Set grom address %04x\n", tag(), m_grom_address); + } + else + { + // Accept high address byte (first write) + m_grom_address = (m_grom_address & 0x00ff) | (data << 8); + m_waddr_LSB = true; + } + } + else + { + // Write data byte to GRAM area. + if (TRACE_GROM) logerror("%s: gwrite %04x(%04x) <- %02x\n", tag(), offset, m_grom_address, data); + + // According to manual: + // Writing to GRAM 0: switch 2 set to GRAM 0 + Write protect switch (4) in 1 or 2 position + // Writing to GRAM 1: switch 3 set to GRAM 1-2 + Loader off (5); write prot has no effect + // Writing to GRAM 2: switch 3 set to GRAM 1-2 (write prot has no effect) + // Writing to GRAM 3-7: switch 1 set to GK_NORMAL, no cartridge inserted + // GK_NORMAL switch has no effect on GRAM 0-2 + int id = ((m_grom_address & 0xe000)>>13)&0x07; + switch (id) + { + case 0: + if (m_gk_switch[2]==GK_GRAM0 && m_gk_switch[4]!=GK_WP) + m_ram_ptr[m_grom_address] = data; + break; + case 1: + if (m_gk_switch[3]==GK_GRAM12 && m_gk_switch[5]==GK_LDOFF) + m_ram_ptr[m_grom_address] = data; + break; + case 2: + if (m_gk_switch[3]==GK_GRAM12) + m_ram_ptr[m_grom_address] = data; + break; + default: + if (m_gk_switch[1]==GK_NORMAL && m_cartridge == NULL) + m_ram_ptr[m_grom_address] = data; + break; + } + // The GK GROM emulation does not wrap at 8K boundaries. + m_grom_address = (m_grom_address + 1) & 0xffff; + + // Reset the write address flipflop. + m_waddr_LSB = false; + } + } + else + { + // Write to the RAM space of the GRAM Kracker + // (only if no cartridge is present) + if (TRACE_GROM) logerror("%s: write %04x <- %02x\n", tag(), offset, data); + if (m_cartridge == NULL) + { + if (m_gk_switch[1] == GK_OFF) return; // just don't do anything + switch (m_gk_switch[4]) + { + // RAM is stored behind the GRAM area + case GK_BANK1: + m_ram_ptr[offset+0x10000 - 0x6000] = data; + break; + + case GK_BANK2: + m_ram_ptr[offset+0x12000 - 0x6000] = data; + break; + + default: + // Switch in middle position (WP, implies auto-select according to the page flag) + // This is handled like in Extended Basic (using addresses) + m_ram_page = (offset >> 1) & 1; + break; + } + } + } +} + +READ8Z_MEMBER( gkracker_device::crureadz ) +{ + if (m_cartridge != NULL) m_cartridge->crureadz(space, offset, value); +} + +WRITE8_MEMBER( gkracker_device::cruwrite ) +{ + if (m_cartridge != NULL) m_cartridge->cruwrite(space, offset, data); +} + +INPUT_CHANGED_MEMBER( gkracker_device::gk_changed ) +{ + if (TRACE_GKRACKER) logerror("%s: Input changed %d - %d\n", tag(), (int)((UINT64)param & 0x07), newval); + m_gk_switch[(UINT64)param & 0x07] = newval; +} + +void gkracker_device::insert(int index, ti99_cartridge_device* cart) +{ + if (TRACE_CHANGE) logerror("%s: Insert cartridge\n", tag()); + m_cartridge = cart; + // Switch 1 has a third location for resetting. We do the reset by default + // here. It can be turned off in the configuration. + m_gromport->cartridge_inserted(); +} + +void gkracker_device::remove(int index) +{ + if (TRACE_CHANGE) logerror("%s: Remove cartridge\n", tag()); + m_cartridge = NULL; +} + +void gkracker_device::gk_install_menu(const char* menutext, int len, int ptr, int next, int start) +{ + const int base = 0x0000; + m_ram_ptr[base + ptr] = (UINT8)((next >> 8) & 0xff); + m_ram_ptr[base + ptr+1] = (UINT8)(next & 0xff); + m_ram_ptr[base + ptr+2] = (UINT8)((start >> 8) & 0xff); + m_ram_ptr[base + ptr+3] = (UINT8)(start & 0xff); + + m_ram_ptr[base + ptr+4] = (UINT8)(len & 0xff); + memcpy(m_ram_ptr + base + ptr+5, menutext, len); +} + +/* + Define the default for the GRAM Kracker device. The memory is preset with + some sample entries which shall indicate that the memory has been tested + by the manufacturer. +*/ +void gkracker_device::nvram_default() +{ + if (TRACE_GKRACKER) logerror("%s: Creating default NVRAM\n", tag()); + memset(m_ram_ptr, 0, 81920); + + m_ram_ptr[0x6000] = 0xaa; + m_ram_ptr[0x6001] = 0x01; + m_ram_ptr[0x6002] = 0x01; + + m_ram_ptr[0x6006] = 0x60; + m_ram_ptr[0x6007] = 0x20; + + gk_install_menu("GROM 3 OK", 9, 0x60e0, 0, 0x6100); + gk_install_menu("GROM 4 OK", 9, 0x60c0, 0x60e0, 0x6100); + gk_install_menu("GROM 5 OK", 9, 0x60a0, 0x60c0, 0x6100); + gk_install_menu("GROM 6 OK", 9, 0x6080, 0x60a0, 0x6100); + gk_install_menu("PROM OK", 9, 0x6060, 0x6080, 0x6100); + gk_install_menu("RAMS OK", 9, 0x6040, 0x6060, 0x6100); + gk_install_menu("OPTION GRAMS OK", 15, 0x6020, 0x6040, 0x6100); + + m_ram_ptr[0x6100] = 0x0b; // GPL EXIT +} + +void gkracker_device::nvram_read(emu_file &file) +{ + int readsize = file.read(m_ram_ptr, 81920); + if (TRACE_GKRACKER) logerror("%s: Reading NVRAM\n", tag()); + // If we increased the size, fill the remaining parts with 0 + if (readsize < 81920) + { + memset(m_ram_ptr + readsize, 0, 81920-readsize); + } +} + +void gkracker_device::nvram_write(emu_file &file) +{ + if (TRACE_GKRACKER) logerror("%s: Writing NVRAM\n", tag()); + file.write(m_ram_ptr, 81920); +} + +void gkracker_device::device_start() +{ + m_ram_ptr = memregion(GKRACKER_NVRAM_TAG)->base(); + m_grom_ptr = memregion(GKRACKER_ROM_TAG)->base(); + m_cartridge = NULL; + for (int i=1; i < 6; i++) m_gk_switch[i] = 0; +} + +void gkracker_device::device_reset() +{ + m_gk_switch[1] = ioport(GKSWITCH1_TAG)->read(); + m_gk_switch[2] = ioport(GKSWITCH2_TAG)->read(); + m_gk_switch[3] = ioport(GKSWITCH3_TAG)->read(); + m_gk_switch[4] = ioport(GKSWITCH4_TAG)->read(); + m_gk_switch[5] = ioport(GKSWITCH5_TAG)->read(); + m_grom_address = 0; // for the GROM emulation + m_ram_page = 0; + m_waddr_LSB = false; +} + +static MACHINE_CONFIG_FRAGMENT( gkracker_slot ) + MCFG_DEVICE_ADD("cartridge", TI99CART, 0) +MACHINE_CONFIG_END + +/* + The GRAMKracker ROM +*/ +ROM_START( gkracker_rom ) + ROM_REGION(0x14000, GKRACKER_NVRAM_TAG, ROMREGION_ERASE00) + ROM_REGION(0x2000, GKRACKER_ROM_TAG, 0) + ROM_LOAD("gkracker.bin", 0x0000, 0x2000, CRC(86eaaf9f) SHA1(a3bd5257c63e190800921b52dbe3ffa91ad91113)) +ROM_END + +const rom_entry *gkracker_device::device_rom_region() const +{ + return ROM_NAME( gkracker_rom ); +} + +machine_config_constructor gkracker_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME( gkracker_slot ); +} + +INPUT_PORTS_START(gkracker) + PORT_START( GKSWITCH1_TAG ) + PORT_DIPNAME( 0x01, 0x01, "GK switch 1" ) PORT_CHANGED_MEMBER(DEVICE_SELF, gkracker_device, gk_changed, 1) + PORT_DIPSETTING( 0x00, "GK Off" ) + PORT_DIPSETTING( 0x01, DEF_STR( Normal ) ) + + PORT_START( GKSWITCH2_TAG ) + PORT_DIPNAME( 0x01, 0x01, "GK switch 2" ) PORT_CHANGED_MEMBER(DEVICE_SELF, gkracker_device, gk_changed, 2) + PORT_DIPSETTING( 0x00, "GRAM 0" ) + PORT_DIPSETTING( 0x01, "Op Sys" ) + + PORT_START( GKSWITCH3_TAG ) + PORT_DIPNAME( 0x01, 0x01, "GK switch 3" ) PORT_CHANGED_MEMBER(DEVICE_SELF, gkracker_device, gk_changed, 3) + PORT_DIPSETTING( 0x00, "GRAM 1-2" ) + PORT_DIPSETTING( 0x01, "TI BASIC" ) + + PORT_START( GKSWITCH4_TAG ) + PORT_DIPNAME( 0x03, 0x01, "GK switch 4" ) PORT_CHANGED_MEMBER(DEVICE_SELF, gkracker_device, gk_changed, 4) + PORT_DIPSETTING( 0x00, "Bank 1" ) + PORT_DIPSETTING( 0x01, "W/P" ) + PORT_DIPSETTING( 0x02, "Bank 2" ) + + PORT_START( GKSWITCH5_TAG ) + PORT_DIPNAME( 0x01, 0x00, "GK switch 5" ) PORT_CHANGED_MEMBER(DEVICE_SELF, gkracker_device, gk_changed, 5) + PORT_DIPSETTING( 0x00, "Loader On" ) + PORT_DIPSETTING( 0x01, "Loader Off" ) +INPUT_PORTS_END + +ioport_constructor gkracker_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(gkracker); +} + +/*************************************************************************** + Cartridge implementation + + Every cartridge is an instance of ti99_cartridge_device, implementing the + device_image_interface. This means it is capable of loading cartridge + data into its memory locations. All memory locations are organised as + regions. + + The different cartridge versions are realised by different PCB instances. + All PCBs are subclassed from ti99_cartridge_pcb. + +***************************************************************************/ +enum +{ + PCB_STANDARD=1, + PCB_PAGED, + PCB_MINIMEM, + PCB_SUPER, + PCB_MBX, + PCB_PAGED379I, + PCB_PAGED378, + PCB_PAGED377, + PCB_PAGEDCRU, + PCB_GROMEMU +}; + +static const pcb_type pcbdefs[] = +{ + { PCB_STANDARD, "standard" }, + { PCB_PAGED, "paged" }, + { PCB_MINIMEM, "minimem" }, + { PCB_SUPER, "super" }, + { PCB_MBX, "mbx" }, + { PCB_PAGED379I, "paged379i" }, + { PCB_PAGED378, "paged378" }, + { PCB_PAGED377, "paged377" }, + { PCB_PAGEDCRU, "pagedcru" }, + { PCB_GROMEMU, "gromemu" }, + { 0, NULL} +}; + +// Softlists do not support the cartridges with RAM yet +static const pcb_type sw_pcbdefs[] = +{ + { PCB_STANDARD, "standard" }, + { PCB_PAGED, "paged" }, + { PCB_GROMEMU, "gromemu" }, + { 0, NULL} +}; + +ti99_cartridge_device::ti99_cartridge_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) +: bus8z_device(mconfig, TI99CART, "TI-99 cartridge", tag, owner, clock, "cartridge", __FILE__), + device_image_interface(mconfig, *this), + m_softlist(false), + m_pcbtype(0), + m_slot(0), + m_pcb(NULL), + m_connector(NULL), + m_rpk(NULL) +{ +} + +void ti99_cartridge_device::prepare_cartridge() +{ + int rom2_length; + + UINT8* grom_ptr; + UINT8* rom_ptr; + + memory_region *regg; + memory_region *regr; + + // Initialize some values. + m_pcb->m_rom_page = 0; + m_pcb->m_rom_ptr = NULL; + m_pcb->m_ram_size = 0; + m_pcb->m_ram_ptr = NULL; + m_pcb->m_ram_page = 0; + + for (int i=0; i < 5; i++) m_pcb->m_grom[i] = NULL; + + m_pcb->m_grom_size = m_softlist? get_software_region_length("grom_socket") : m_rpk->get_resource_length("grom_socket"); + if (TRACE_CONFIG) logerror("%s: grom_socket.size=0x%04x\n", tag(), m_pcb->m_grom_size); + + if (m_pcb->m_grom_size > 0) + { + regg = memregion(CARTGROM_TAG); + grom_ptr = m_softlist? get_software_region("grom_socket") : m_rpk->get_contents_of_socket("grom_socket"); + memcpy(regg->base(), grom_ptr, m_pcb->m_grom_size); + m_pcb->m_grom_ptr = regg->base(); // for gromemu + m_pcb->m_grom_address = 0; // for gromemu + + // Find the GROMs and keep their pointers + m_pcb->set_grom_pointer(0, subdevice(GROM3_TAG)); + if (m_pcb->m_grom_size > 0x2000) m_pcb->set_grom_pointer(1, subdevice(GROM4_TAG)); + if (m_pcb->m_grom_size > 0x4000) m_pcb->set_grom_pointer(2, subdevice(GROM5_TAG)); + if (m_pcb->m_grom_size > 0x6000) m_pcb->set_grom_pointer(3, subdevice(GROM6_TAG)); + if (m_pcb->m_grom_size > 0x8000) m_pcb->set_grom_pointer(4, subdevice(GROM7_TAG)); + } + + m_pcb->m_rom_size = m_softlist? get_software_region_length("rom_socket") : m_rpk->get_resource_length("rom_socket"); + if (m_pcb->m_rom_size > 0) + { + if (TRACE_CONFIG) logerror("%s: rom_socket.size=0x%04x\n", tag(), m_pcb->m_rom_size); + regr = memregion(CARTROM_TAG); + rom_ptr = m_softlist? get_software_region("rom_socket") : m_rpk->get_contents_of_socket("rom_socket"); + memcpy(regr->base(), rom_ptr, m_pcb->m_rom_size); + // Set both pointers to the same region for now + m_pcb->m_rom_ptr = regr->base(); + } + + rom2_length = m_softlist? get_software_region_length("rom2_socket") : m_rpk->get_resource_length("rom2_socket"); + if (rom2_length > 0) + { + // sizes do not differ between rom and rom2 + // We use the large cartrom space for the second bank as well + regr = memregion(CARTROM_TAG); + rom_ptr = m_softlist? get_software_region("rom2_socket") : m_rpk->get_contents_of_socket("rom2_socket"); + memcpy(regr->base() + 0x2000, rom_ptr, rom2_length); + } + + // NVRAM cartridges are not supported by softlists (we need to find a way to load the nvram contents first) + if (!m_softlist) + { + m_pcb->m_ram_size = m_rpk->get_resource_length("ram_socket"); + if (m_pcb->m_ram_size > 0) + { + // TODO: Consider to use a region as well. If so, do not forget to memcpy. + m_pcb->m_ram_ptr = m_rpk->get_contents_of_socket("ram_socket"); + } + } +} + +/* + Find the index of the cartridge name. We assume the format + <name><number>, i.e. the number is the longest string from the right + which can be interpreted as a number. Subtract 1. +*/ +int ti99_cartridge_device::get_index_from_tagname() +{ + const char *mytag = tag(); + int maxlen = strlen(mytag); + int i; + + for (i=maxlen-1; i >=0; i--) + if (mytag[i] < 48 || mytag[i] > 57) break; + + if (i==maxlen-1) return 0; + return atoi(mytag+i+1)-1; +} + +bool ti99_cartridge_device::has_grom() +{ + return m_pcb->m_grom_size>0; +} + +UINT16 ti99_cartridge_device::grom_base() +{ + return m_connector->grom_base(); +} + +UINT16 ti99_cartridge_device::grom_mask() +{ + return m_connector->grom_mask(); +} + +bool ti99_cartridge_device::call_load() +{ + // File name is in m_basename + // return true = error + if (TRACE_CHANGE) logerror("%s: Loading %s in slot %s\n", tag(), m_basename.c_str(), tag()); + + if (m_softlist) + { + if (TRACE_CONFIG) logerror("%s: Using softlists\n", tag()); + int i = 0; + const char* pcb = get_feature("pcb"); + do + { + if (strcmp(pcb, sw_pcbdefs[i].name)==0) + { + m_pcbtype = sw_pcbdefs[i].id; + break; + } + i++; + } while (sw_pcbdefs[i].id != 0); + if (TRACE_CONFIG) logerror("%s: Cartridge type is %s (%d)\n", tag(), pcb, m_pcbtype); + } + else + { + rpk_reader *reader = new rpk_reader(pcbdefs); + try + { + m_rpk = reader->open(machine().options(), filename(), machine().system().name); + m_pcbtype = m_rpk->get_type(); + } + catch (rpk_exception& err) + { + logerror("%s: Failed to load cartridge '%s': %s\n", tag(), basename(), err.to_string()); + m_rpk = NULL; + m_err = IMAGE_ERROR_INVALIDIMAGE; + return true; + } + } + + switch (m_pcbtype) + { + case PCB_STANDARD: + if (TRACE_CONFIG) logerror("%s: Standard PCB\n", tag()); + m_pcb = new ti99_standard_cartridge(); + break; + case PCB_PAGED: + if (TRACE_CONFIG) logerror("%s: Paged PCB\n", tag()); + m_pcb = new ti99_paged_cartridge(); + break; + case PCB_MINIMEM: + if (TRACE_CONFIG) logerror("%s: Minimem PCB\n", tag()); + m_pcb = new ti99_minimem_cartridge(); + break; + case PCB_SUPER: + if (TRACE_CONFIG) logerror("%s: Superspace PCB\n", tag()); + m_pcb = new ti99_super_cartridge(); + break; + case PCB_MBX: + if (TRACE_CONFIG) logerror("%s: MBX PCB\n", tag()); + m_pcb = new ti99_mbx_cartridge(); + break; + case PCB_PAGED379I: + if (TRACE_CONFIG) logerror("%s: Paged379i PCB\n", tag()); + m_pcb = new ti99_paged379i_cartridge(); + break; + case PCB_PAGED378: + if (TRACE_CONFIG) logerror("%s: Paged378 PCB\n", tag()); + m_pcb = new ti99_paged378_cartridge(); + break; + case PCB_PAGED377: + if (TRACE_CONFIG) logerror("%s: Paged377 PCB\n", tag()); + m_pcb = new ti99_paged377_cartridge(); + break; + case PCB_PAGEDCRU: + if (TRACE_CONFIG) logerror("%s: PagedCRU PCB\n", tag()); + m_pcb = new ti99_pagedcru_cartridge(); + break; + case PCB_GROMEMU: + if (TRACE_CONFIG) logerror("%s: Grom Emulation PCB\n", tag()); + m_pcb = new ti99_gromemu_cartridge(); + break; + } + + prepare_cartridge(); + m_pcb->set_cartridge(this); + m_pcb->set_tag(tag()); + m_slot = get_index_from_tagname(); + m_connector->insert(m_slot, this); + return false; +} + +void ti99_cartridge_device::call_unload() +{ + if (TRACE_CHANGE) logerror("%s: Unload\n", tag()); + if (m_rpk != NULL) + { + m_rpk->close(); // will write NVRAM contents + delete m_rpk; + } + + delete m_pcb; + m_pcb = NULL; + m_connector->remove(m_slot); +} + +void ti99_cartridge_device::set_slot(int i) +{ + m_slot = i; +} + +bool ti99_cartridge_device::call_softlist_load(software_list_device &swlist, const char *swname, const rom_entry *start_entry) +{ + if (TRACE_CONFIG) logerror("%s: swlist = %s, swname = %s\n", tag(), swlist.list_name(), swname); + load_software_part_region(*this, swlist, swname, start_entry); + m_softlist = true; + m_rpk = NULL; + return true; +} + +READ8Z_MEMBER(ti99_cartridge_device::readz) +{ + if (m_pcb != NULL) m_pcb->readz(space, offset, value); +} + +WRITE8_MEMBER(ti99_cartridge_device::write) +{ + if (m_pcb != NULL) m_pcb->write(space, offset, data); +} + +READ8Z_MEMBER(ti99_cartridge_device::crureadz) +{ + if (m_pcb != NULL) m_pcb->crureadz(space, offset, value); +} + +WRITE8_MEMBER(ti99_cartridge_device::cruwrite) +{ + if (m_pcb != NULL) m_pcb->cruwrite(space, offset, data); +} + +WRITE_LINE_MEMBER( ti99_cartridge_device::ready_line ) +{ + m_connector->ready_line(state); +} + +void ti99_cartridge_device::device_config_complete() +{ + update_names(); + m_softlist = false; + m_connector = static_cast<ti99_cartridge_connector_device*>(owner()); +} + +static GROM_CONFIG(grom3_config) +{ + false, 3, CARTGROM_TAG, 0x0000, 0x1800, GROMFREQ +}; +static GROM_CONFIG(grom4_config) +{ + false, 4, CARTGROM_TAG, 0x2000, 0x1800, GROMFREQ +}; +static GROM_CONFIG(grom5_config) +{ + false, 5, CARTGROM_TAG, 0x4000, 0x1800, GROMFREQ +}; +static GROM_CONFIG(grom6_config) +{ + false, 6, CARTGROM_TAG, 0x6000, 0x1800, GROMFREQ +}; +static GROM_CONFIG(grom7_config) +{ + false, 7, CARTGROM_TAG, 0x8000, 0x1800, GROMFREQ +}; + +/* + 5 GROMs that may be contained in a cartridge +*/ +static MACHINE_CONFIG_FRAGMENT( ti99_cartridge ) + MCFG_GROM_ADD( GROM3_TAG, grom3_config ) + MCFG_GROM_READY_CALLBACK(WRITELINE(ti99_cartridge_device, ready_line)) + MCFG_GROM_ADD( GROM4_TAG, grom4_config ) + MCFG_GROM_READY_CALLBACK(WRITELINE(ti99_cartridge_device, ready_line)) + MCFG_GROM_ADD( GROM5_TAG, grom5_config ) + MCFG_GROM_READY_CALLBACK(WRITELINE(ti99_cartridge_device, ready_line)) + MCFG_GROM_ADD( GROM6_TAG, grom6_config ) + MCFG_GROM_READY_CALLBACK(WRITELINE(ti99_cartridge_device, ready_line)) + MCFG_GROM_ADD( GROM7_TAG, grom7_config ) + MCFG_GROM_READY_CALLBACK(WRITELINE(ti99_cartridge_device, ready_line)) +MACHINE_CONFIG_END + +machine_config_constructor ti99_cartridge_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME( ti99_cartridge ); +} + +/* + Memory area for one cartridge. For most cartridges we only need 8 KiB for + ROM contents, but cartridges of the "paged377" type have up to 2 MiB + organised as selectable banks, so we must be sure there is enough space. +*/ +ROM_START( cartridge_memory ) + ROM_REGION(0xa000, CARTGROM_TAG, ROMREGION_ERASE00) + ROM_REGION(0x200000, CARTROM_TAG, ROMREGION_ERASE00) +ROM_END + +const rom_entry *ti99_cartridge_device::device_rom_region() const +{ + return ROM_NAME( cartridge_memory ); +} + +const device_type TI99CART = &device_creator<ti99_cartridge_device>; + +/*************************************************************************** + Cartridge types + Cartridges differ by the circuits on their PCB which hosts the ROMs. + Some cartridges also have RAM, and some allow for switching between + ROMs. + + Unlike in the previous implementation we do not model it as a full device. +***************************************************************************/ + +ti99_cartridge_pcb::ti99_cartridge_pcb() +{ +} + +UINT16 ti99_cartridge_pcb::grom_base() +{ + return m_cart->grom_base(); +} + +UINT16 ti99_cartridge_pcb::grom_mask() +{ + return m_cart->grom_mask(); +} + +void ti99_cartridge_pcb::set_cartridge(ti99_cartridge_device *cart) +{ + m_cart = cart; +} + +READ8Z_MEMBER(ti99_cartridge_pcb::gromreadz) +{ + for (int i=0; i < 5; i++) + { + if (m_grom[i] != NULL) + { + m_grom[i]->readz(space, offset, value, mem_mask); + } + } +} + +WRITE8_MEMBER(ti99_cartridge_pcb::gromwrite) +{ + for (int i=0; i < 5; i++) + { + if (m_grom[i] != NULL) + { + m_grom[i]->write(space, offset, data, mem_mask); + } + } +} + +READ8Z_MEMBER(ti99_cartridge_pcb::readz) +{ + if ((offset & grom_mask())==grom_base()) + gromreadz(space, offset, value, mem_mask); + else + { + if (m_rom_ptr!=NULL) + { + // For TI-99/8 we should plan for 16K cartridges. However, none was ever produced. + // Well, forget about that. + *value = m_rom_ptr[offset & 0x1fff]; + // logerror("%s: read cartridge rom space %04x = %02x\n", tag(), offset, *value); + } + } +} + +WRITE8_MEMBER(ti99_cartridge_pcb::write) +{ + // logerror("%s: write standard\n", tag()); + if ((offset & grom_mask())==grom_base()) + gromwrite(space, offset, data, mem_mask); + else + { + if (TRACE_ILLWRITE) logerror("%s: Cannot write to ROM space at %04x\n", tag(), offset); + } +} + +READ8Z_MEMBER(ti99_cartridge_pcb::crureadz) +{ +} + +WRITE8_MEMBER(ti99_cartridge_pcb::cruwrite) +{ +} + +inline void ti99_cartridge_pcb::set_grom_pointer(int number, device_t *dev) +{ + m_grom[number] = static_cast<ti99_grom_device*>(dev); +} + +/***************************************************************************** + Cartridge type: Paged (Extended Basic) + This cartridge consists of GROM memory and 2 pages of standard ROM. + The page is set by writing any value to a location in + the address area, where an even word offset sets the page to 0 and an + odd word offset sets the page to 1 (e.g. 6000 = bank 0, and + 6002 = bank 1). +******************************************************************************/ + +READ8Z_MEMBER(ti99_paged_cartridge::readz) +{ + if ((offset & grom_mask())==grom_base()) + gromreadz(space, offset, value, mem_mask); + else + { + *value = m_rom_ptr[(offset & 0x1fff) | (m_rom_page << 13)]; + } +} + +WRITE8_MEMBER(ti99_paged_cartridge::write) +{ + // logerror("%s: write standard\n", tag()); + if ((offset & grom_mask())==grom_base()) + gromwrite(space, offset, data, mem_mask); + + else { + m_rom_page = (offset >> 1) & 1; + } +} + +/***************************************************************************** + Cartridge type: Mini Memory + GROM: 6 KiB (occupies G>6000 to G>7800) + ROM: 4 KiB (romfile is actually 8 K long, second half with zeros, 0x6000-0x6fff) + persistent RAM: 4 KiB (0x7000-0x7fff) +******************************************************************************/ + +/* Read function for the minimem cartridge. */ +READ8Z_MEMBER(ti99_minimem_cartridge::readz) +{ + if ((offset & grom_mask())==grom_base()) + gromreadz(space, offset, value, mem_mask); + + else + { + if ((offset & 0x1000)==0x0000) + { + if (m_rom_ptr!=NULL) // Super-Minimem seems to have no ROM + { + *value = m_rom_ptr[offset & 0x0fff]; + } + } + else + { + *value = m_ram_ptr[offset & 0x0fff]; + } + } +} + +/* Write function for the minimem cartridge. */ +WRITE8_MEMBER(ti99_minimem_cartridge::write) +{ + // logerror("%s: write standard\n", tag()); + if ((offset & grom_mask())==grom_base()) + gromwrite(space, offset, data, mem_mask); + + else + { + if ((offset & 0x1000)==0x0000) + { + if (TRACE_ILLWRITE) logerror("%s: Write access to cartridge ROM at address %04x ignored", tag(), offset); + } + else + { + m_ram_ptr[offset & 0x0fff] = data; + } + } +} + +/***************************************************************************** + Cartridge type: SuperSpace II + + SuperSpace is intended as a user-definable blank cartridge containing + buffered RAM. It has an Editor/Assembler GROM which helps the user to load + the user program into the cartridge. If the user program has a suitable + header, the console recognizes the cartridge as runnable, and + assigns a number in the selection screen. Switching the RAM banks in this + cartridge is achieved by setting CRU bits (the system serial interface). + + GROM: Editor/Assembler GROM + ROM: none + persistent RAM: 32 KiB (0x6000-0x7fff, 4 banks) + Banking: via CRU write +******************************************************************************/ + +/* Read function for the super cartridge. */ +READ8Z_MEMBER(ti99_super_cartridge::readz) +{ + if ((offset & grom_mask())==grom_base()) + gromreadz(space, offset, value, mem_mask); + else + { + if (m_ram_ptr != NULL) + { + *value = m_ram_ptr[(m_ram_page << 13) | (offset & 0x1fff)]; + } + } +} + +/* Write function for the super cartridge. */ +WRITE8_MEMBER(ti99_super_cartridge::write) +{ + if ((offset & grom_mask())==grom_base()) + gromwrite(space, offset, data, mem_mask); + else + { + m_ram_ptr[(m_ram_page << 13) | (offset & 0x1fff)] = data; + } +} + +READ8Z_MEMBER(ti99_super_cartridge::crureadz) +{ + // offset is the bit number. The CRU base address is already divided by 2. + + // ram_page contains the bank number. We have a maximum of + // 4 banks; the Super Space II manual says: + // + // Banks are selected by writing a bit pattern to CRU address >0800: + // + // Bank # Value + // 0 >02 = 0000 0010 + // 1 >08 = 0000 1000 + // 2 >20 = 0010 0000 + // 3 >80 = 1000 0000 + // + // With the bank number (0, 1, 2, or 3) in R0: + // + // BNKSW LI R12,>0800 Set CRU address + // LI R1,2 Load Shift Bit + // SLA R0,1 Align Bank Number + // JEQ BNKS1 Skip shift if Bank 0 + // SLA R1,0 Align Shift Bit + // BNKS1 LDCR R1,0 Switch Banks + // SRL R0,1 Restore Bank Number (optional) + // RT + + // Our implementation in MESS always gets 8 bits in one go. Also, the address + // is twice the bit number. That is, the offset value is always a multiple + // of 0x10. + + if ((offset & 0xfff0) == 0x0800) + { + if (TRACE_CRU) logerror("%s: CRU accessed at %04x\n", tag(), offset); + UINT8 val = 0x02 << (m_ram_page << 1); + *value = (val >> ((offset - 0x0800)>>1)) & 0xff; + } +} + +WRITE8_MEMBER(ti99_super_cartridge::cruwrite) +{ + if ((offset & 0xfff0) == 0x0800) + { + if (TRACE_CRU) logerror("%s: CRU accessed at %04x\n", tag(), offset); + if (data != 0) + m_ram_page = (offset-0x0802)>>2; + } +} + +/***************************************************************************** + Cartridge type: MBX + GROM: up to 40 KiB + ROM: up to 16 KiB (in up to 2 banks of 8KiB each) + RAM: 1022 B (0x6c00-0x6ffd, overrides ROM in that area) + ROM mapper: 6ffe + + TODO: Some MBX cartridges assume the presence of the MBX system + (special user interface box with speech input/output) + and will not run without it. This MBX hardware is not emulated yet. +******************************************************************************/ + +/* Read function for the mbx cartridge. */ +READ8Z_MEMBER(ti99_mbx_cartridge::readz) +{ + if ((offset & grom_mask())==grom_base()) + gromreadz(space, offset, value, mem_mask); + else + { + if ((offset & 0x1c00)==0x0c00) + { + // This is the RAM area which overrides any ROM. There is no + // known banking behavior for the RAM, so we must assume that + // there is only one bank. + if (m_ram_ptr != NULL) + *value = m_ram_ptr[offset & 0x03ff]; + } + else + { + if (m_rom_ptr!=NULL) + *value = m_rom_ptr[(offset & 0x1fff) | (m_rom_page<<13)]; + } + } +} + +/* Write function for the mbx cartridge. */ +WRITE8_MEMBER(ti99_mbx_cartridge::write) +{ + if ((offset & grom_mask())==grom_base()) + gromwrite(space, offset, data, mem_mask); + else + { + if (offset == 0x6ffe) + { + m_rom_page = data & 1; + return; + } + + if ((offset & 0x1c00)==0x0c00) + { + if (m_ram_ptr == NULL) return; + m_ram_ptr[offset & 0x03ff] = data; + } + } +} + +/***************************************************************************** + Cartridge type: paged379i + This cartridge consists of one 16 KiB, 32 KiB, 64 KiB, or 128 KiB EEPROM + which is organised in 2, 4, 8, or 16 pages of 8 KiB each. The complete + memory contents must be stored in one dump file. + The pages are selected by writing a value to some memory locations. Due to + using the inverted outputs of the LS379 latch, setting the inputs of the + latch to all 0 selects the highest bank, while setting to all 1 selects the + lowest. There are some cartridges (16 KiB) which are using this scheme, and + there are new hardware developments mainly relying on this scheme. + + Writing to selects page (16K/32K/64K/128K) + >6000 1 / 3 / 7 / 15 + >6002 0 / 2 / 6 / 14 + >6004 1 / 1 / 5 / 13 + >6006 0 / 0 / 4 / 12 + >6008 1 / 3 / 3 / 11 + >600A 0 / 2 / 2 / 10 + >600C 1 / 1 / 1 / 9 + >600E 0 / 0 / 0 / 8 + >6010 1 / 3 / 7 / 7 + >6012 0 / 2 / 6 / 6 + >6014 1 / 1 / 5 / 5 + >6016 0 / 0 / 4 / 4 + >6018 1 / 3 / 3 / 3 + >601A 0 / 2 / 2 / 2 + >601C 1 / 1 / 1 / 1 + >601E 0 / 0 / 0 / 0 + + The paged379i cartrige does not have any GROMs. +******************************************************************************/ + +/* + Determines which bank to set, depending on the size of the ROM. This is + some magic code that actually represents different PCB versions. +*/ +int ti99_paged379i_cartridge::get_paged379i_bank(int rompage) +{ + int mask = 0; + if (m_rom_size > 16384) + { + if (m_rom_size > 32768) + { + if (m_rom_size > 65536) + mask = 15; + else + mask = 7; + } + else + mask = 3; + } + else + mask = 1; + + return rompage & mask; +} + + +/* Read function for the paged379i cartridge. */ +READ8Z_MEMBER(ti99_paged379i_cartridge::readz) +{ + if ((offset & 0xe000)==0x6000) + *value = m_rom_ptr[(m_rom_page<<13) | (offset & 0x1fff)]; +} + +/* Write function for the paged379i cartridge. Only used to set the bank. */ +WRITE8_MEMBER(ti99_paged379i_cartridge::write) +{ + // Bits: 0110 0000 000b bbbx + // x = don't care, bbbb = bank + if ((offset & 0xffe0)==0x6000) + { + // Set bank + m_rom_page = get_paged379i_bank(15 - ((offset>>1) & 15)); + } +} + +/***************************************************************************** + Cartridge type: paged378 + This type is intended for high-capacity cartridges of up to 512 KiB + plus GROM space of 120KiB (not supported yet) + + Due to its huge GROM space it is also called the "UberGROM" + + The cartridge may also be used without GROM. + + The selection scheme is much simpler than in the 379i type. + +******************************************************************************/ + +/* Read function for the paged378 cartridge. */ +READ8Z_MEMBER(ti99_paged378_cartridge::readz) +{ + if ((offset & 0xe000)==0x6000) + *value = m_rom_ptr[(m_rom_page<<13) | (offset & 0x1fff)]; +} + +/* Write function for the paged378 cartridge. Only used to set the bank. */ +WRITE8_MEMBER(ti99_paged378_cartridge::write) +{ + // Bits: 0110 0000 0bbb bbbx + // x = don't care, bbbb = bank + if ((offset & 0xff80)==0x6000) + { + // Set bank + m_rom_page = ((offset >> 1)&0x003f); + } +} + +/***************************************************************************** + Cartridge type: paged377 + This type is intended for high-capacity cartridges of up to 2 MiB + + The paged379i cartrige does not have any GROMs. +******************************************************************************/ + +/* Read function for the paged377 cartridge. */ +READ8Z_MEMBER(ti99_paged377_cartridge::readz) +{ + if ((offset & 0xe000)==0x6000) + *value = m_rom_ptr[(m_rom_page<<13) | (offset & 0x1fff)]; +} + +/* Write function for the paged377 cartridge. Only used to set the bank. */ +WRITE8_MEMBER(ti99_paged377_cartridge::write) +{ + // Bits: 0110 000b bbbb bbbx + // x = don't care, bbbb = bank + if ((offset & 0xfe00)==0x6000) + { + // Set bank + m_rom_page = ((offset >> 1)&0x00ff); + } +} + +/***************************************************************************** + Cartridge type: pagedcru + This cartridge consists of one 16 KiB, 32 KiB, or 64 KiB EEPROM which is + organised in 2, 4, or 8 pages of 8 KiB each. We assume there is only one + dump file of the respective size. + The pages are selected by writing a value to the CRU. This scheme is + similar to the one used for the SuperSpace cartridge, with the exception + that we are using ROM only, and we can have up to 8 pages. + + Bank Value written to CRU>0800 + 0 >0002 = 0000 0000 0000 0010 + 1 >0008 = 0000 0000 0000 1000 + 2 >0020 = 0000 0000 0010 0000 + 3 >0080 = 0000 0000 1000 0000 + 4 >0200 = 0000 0010 0000 0000 + 5 >0800 = 0000 1000 0000 0000 + 6 >2000 = 0010 0000 0000 0000 + 7 >8000 = 1000 0000 0000 0000 + + No GROMs used in this type. +******************************************************************************/ + +/* Read function for the pagedcru cartridge. */ +READ8Z_MEMBER(ti99_pagedcru_cartridge::readz) +{ + if ((offset & 0xe000)==0x6000) + *value = m_rom_ptr[(m_rom_page<<13) | (offset & 0x1fff)]; +} + +/* Write function for the pagedcru cartridge. No effect. */ +WRITE8_MEMBER(ti99_pagedcru_cartridge::write) +{ + return; +} + +READ8Z_MEMBER(ti99_pagedcru_cartridge::crureadz) +{ + int page = m_rom_page; + if ((offset & 0xf800)==0x0800) + { + int bit = (offset & 0x001e)>>1; + if (bit != 0) + { + page = page-(bit/2); // 4 page flags per 8 bits + } + *value = 1 << (page*2+1); + } +} + +WRITE8_MEMBER(ti99_pagedcru_cartridge::cruwrite) +{ + if ((offset & 0xf800)==0x0800) + { + int bit = (offset & 0x001e)>>1; + if (data != 0 && bit > 0) + { + m_rom_page = (bit-1)/2; + } + } +} + +/***************************************************************************** + Cartridge type: GROM emulation/paged + + This cartridge offers GROM address space without real GROM circuits. The GROMs + are emulated by a normal EPROM with a circuits that mimics GROM behavior. + Each simulated GROM offers 8K (real GROMs only offer 6K). + + Some assumptions: + - No readable address counter. This means the parallel console GROMs + will deliver the address when reading. + - No wait states. Reading is generally faster than with real GROMs. + - No wrapping at 8K boundaries. + - Two pages of ROM at address 6000 + + If any of these fails, the cartridge will crash, so we'll see. + + Typical cartridges: RXB, Super Extended Basic + + For the sake of simplicity, we register GROMs like the other PCB types, but + we implement special access methods for the GROM space. + + Still not working: + rxb1002 (Set page to 1 (6372 <- 00), lockup) + rxb237 (immediate reset) + rxbv555 (repeating reset on Master Title Screen) + superxb (lockup, fix: add RAM at 7c00) + + Super-MiniMemory is also included here. We assume a RAM area at addresses + 7000-7fff for this cartridge. + +******************************************************************************/ + +READ8Z_MEMBER(ti99_gromemu_cartridge::readz) +{ + if ((offset & grom_mask())==grom_base()) + gromemureadz(space, offset, value, mem_mask); + else + { + if (m_ram_ptr != NULL) + { + // Variant of the cartridge which emulates MiniMemory. We don't introduce + // another type for this single cartridge. + if ((offset & 0x1fff)==0x1000) { + *value = m_ram_ptr[offset & 0x0fff]; + return; + } + } + + if (m_rom_ptr == NULL) return; + *value = m_rom_ptr[(offset & 0x1fff) | (m_rom_page << 13)]; + } +} + +WRITE8_MEMBER(ti99_gromemu_cartridge::write) +{ + // logerror("%s: write standard\n", tag()); + if ((offset & grom_mask())==grom_base()) + gromemuwrite(space, offset, data, mem_mask); + + else { + if (m_ram_ptr != NULL) + { + // Lines for Super-Minimem; see above + if ((offset & 0x1fff)==0x1000) { + m_ram_ptr[offset & 0x0fff] = data; + } + return; // no paging + } + + m_rom_page = (offset >> 1) & 1; + } +} + +READ8Z_MEMBER(ti99_gromemu_cartridge::gromemureadz) +{ + // Similar to the GKracker implemented above, we do not have a readable + // GROM address counter but use the one from the console GROMs. + if ((offset & 0x0002)!=0) return; + int id = ((m_grom_address & 0xe000)>>13)&0x07; + if (id > 2) { + // Cartridge space (0x6000 - 0xffff) + *value = m_grom_ptr[m_grom_address-0x6000]; // use the GROM memory + } + + // The GROM emulation does not wrap at 8K boundaries. + m_grom_address = (m_grom_address + 1) & 0xffff; + + // Reset the write address flipflop. + m_waddr_LSB = false; +} + +WRITE8_MEMBER(ti99_gromemu_cartridge::gromemuwrite) +{ + // Set GROM address + if ((offset & 0x0002)==0x0002) { + if (m_waddr_LSB == true) + { + // Accept low address byte (second write) + m_grom_address = (m_grom_address & 0xff00) | data; + m_waddr_LSB = false; + if (TRACE_GROM) logerror("%s: Set grom address %04x\n", tag(), m_grom_address); + } + else + { + // Accept high address byte (first write) + m_grom_address = (m_grom_address & 0x00ff) | (data << 8); + m_waddr_LSB = true; + } + } + else { + if (TRACE_ILLWRITE) logerror("%s: Ignoring write to GROM area at address %04x\n", tag(), m_grom_address); + } +} + +/**************************************************************************** + + RPK loader + + RPK format support + + A RPK file ("rompack") contains a collection of dump files and a layout + file that defines the kind of circuit board (PCB) used in the cartridge + and the mapping of dumps to sockets on the board. + +Example: + <?xml version="1.0" encoding="utf-8"?> + <romset> + <resources> + <rom id="gromimage" file="ed-assmg.bin" /> + </resources> + <configuration> + <pcb type="standard"> + <socket id="grom_socket" uses="gromimage"/> + </pcb> + </configuration> + </romset> + +DTD: + <!ELEMENT romset (resources, configuration)> + <!ELEMENT resources (rom|ram)+> + <!ELEMENT rom EMPTY> + <!ELEMENT ram EMPTY> + <!ELEMENT configuration (pcb)> + <!ELEMENT pcb (socket)+> + <!ELEMENT socket EMPTY> + <!ATTLIST romset version CDATA #IMPLIED> + <!ATTLIST rom id ID #REQUIRED + <!ATTLIST rom file CDATA #REQUIRED> + <!ATTLIST rom crc CDATA #IMPLIED> + <!ATTLIST rom sha1 CDATA #IMPLIED> + <!ATTLIST ram id ID #REQUIRED> + <!ATTLIST ram type (volatile|persistent) #IMPLIED> + <!ATTLIST ram store (internal|external) #IMPLIED> + <!ATTLIST ram file CDATA #IMPLIED> + <!ATTLIST ram length CDATA #REQUIRED> + <!ATTLIST pcb type CDATA #REQUIRED> + <!ATTLIST socket id ID #REQUIRED> + <!ATTLIST socket uses IDREF #REQUIRED> + +****************************************************************************/ + +#include "unzip.h" +#include "xmlfile.h" + +/**************************************** + RPK class +****************************************/ +/* + Constructor. +*/ +rpk::rpk(emu_options& options, const char* sysname) + :m_options(options) + //,m_system_name(sysname) +{ + m_sockets.reset(); +} + +rpk::~rpk() +{ + if (TRACE_RPK) logerror("gromport/RPK: Destroy RPK\n"); +} + +/* + Deliver the contents of the socket by name of the socket. +*/ +UINT8* rpk::get_contents_of_socket(const char *socket_name) +{ + rpk_socket *socket = m_sockets.find(socket_name); + if (socket==NULL) return NULL; + return socket->get_contents(); +} + +/* + Deliver the length of the contents of the socket by name of the socket. +*/ +int rpk::get_resource_length(const char *socket_name) +{ + rpk_socket *socket = m_sockets.find(socket_name); + if (socket==NULL) return 0; + return socket->get_content_length(); +} + +void rpk::add_socket(const char* id, rpk_socket *newsock) +{ + m_sockets.append(id, *newsock); +} + +/*------------------------------------------------- + rpk_close - closes a rpk + Saves the contents of the NVRAMs and frees all memory. +-------------------------------------------------*/ + +void rpk::close() +{ + // Save the NVRAM contents + rpk_socket *socket = m_sockets.first(); + while (socket != NULL) + { + if (socket->persistent_ram()) + { + image_battery_save_by_name(m_options, socket->get_pathname(), socket->get_contents(), socket->get_content_length()); + } + socket->cleanup(); + socket = socket->m_next; + } +} + +/************************************************************** + RPK socket (location in the PCB where a chip is plugged in; + not a network socket) +***************************************************************/ + +rpk_socket::rpk_socket(const char* id, int length, UINT8* contents, const char *pathname) +: m_id(id), m_length(length), m_next(NULL), m_contents(contents), m_pathname(pathname) +{ +} + +rpk_socket::rpk_socket(const char* id, int length, UINT8* contents) +: m_id(id), m_length(length), m_next(NULL), m_contents(contents), m_pathname(NULL) +{ +} + +/* + Locate a file in the ZIP container +*/ +const zip_file_header* rpk_reader::find_file(zip_file *zip, const char *filename, UINT32 crc) +{ + const zip_file_header *header; + for (header = zip_file_first_file(zip); header != NULL; header = zip_file_next_file(zip)) + { + // We don't check for CRC == 0. + if (crc != 0) + { + // if the CRC and name both match, we're good + // if the CRC matches and the name doesn't, we're still good + if (header->crc == crc) + return header; + } + else + { + if (core_stricmp(header->filename, filename)==0) + { + return header; + } + } + } + return NULL; +} + +/* + Load a rom resource and put it in a pcb socket instance. +*/ +rpk_socket* rpk_reader::load_rom_resource(zip_file* zip, xml_data_node* rom_resource_node, const char* socketname) +{ + const char* file; + const char* crcstr; + const char* sha1; + zip_error ziperr; + UINT32 crc; + int length; + UINT8* contents; + const zip_file_header *header; + + // find the file attribute (required) + file = xml_get_attribute_string(rom_resource_node, "file", NULL); + if (file == NULL) throw rpk_exception(RPK_INVALID_LAYOUT, "<rom> must have a 'file' attribute"); + + if (TRACE_RPK) logerror("gromport/RPK: Loading ROM contents for socket '%s' from file %s\n", socketname, file); + + // check for crc + crcstr = xml_get_attribute_string(rom_resource_node, "crc", NULL); + if (crcstr==NULL) + { + // no CRC, just find the file in the RPK + header = find_file(zip, file, 0); + } + else + { + crc = strtoul(crcstr, NULL, 16); + header = find_file(zip, file, crc); + } + if (header == NULL) throw rpk_exception(RPK_INVALID_FILE_REF, "File not found or CRC check failed"); + + length = header->uncompressed_length; + + // Allocate storage + contents = global_alloc_array_clear(UINT8, length); + if (contents==NULL) throw rpk_exception(RPK_OUT_OF_MEMORY); + + // and unzip file from the zip file + ziperr = zip_file_decompress(zip, contents, length); + if (ziperr != ZIPERR_NONE) + { + if (ziperr == ZIPERR_UNSUPPORTED) throw rpk_exception(RPK_ZIP_UNSUPPORTED); + else throw rpk_exception(RPK_ZIP_ERROR); + } + + // check for sha1 + sha1 = xml_get_attribute_string(rom_resource_node, "sha1", NULL); + if (sha1 != NULL) + { + hash_collection actual_hashes; + actual_hashes.compute((const UINT8 *)contents, length, hash_collection::HASH_TYPES_CRC_SHA1); + + hash_collection expected_hashes; + expected_hashes.add_from_string(hash_collection::HASH_SHA1, sha1, strlen(sha1)); + + if (actual_hashes != expected_hashes) throw rpk_exception(RPK_INVALID_FILE_REF, "SHA1 check failed"); + } + + // Create a socket instance + return new rpk_socket(socketname, length, contents); +} + +/* + Load a ram resource and put it in a pcb socket instance. +*/ +rpk_socket* rpk_reader::load_ram_resource(emu_options &options, xml_data_node* ram_resource_node, const char* socketname, const char* system_name) +{ + const char* length_string; + const char* ram_type; + const char* ram_filename; + const char* ram_pname; + unsigned int length; + UINT8* contents; + + // find the length attribute + length_string = xml_get_attribute_string(ram_resource_node, "length", NULL); + if (length_string == NULL) throw rpk_exception(RPK_MISSING_RAM_LENGTH); + + // parse it + char suffix = '\0'; + sscanf(length_string, "%u%c", &length, &suffix); + switch(tolower(suffix)) + { + case 'k': // kilobytes + length *= 1024; + break; + + case 'm': + /* megabytes */ + length *= 1024*1024; + break; + + case '\0': + break; + + default: // failed + throw rpk_exception(RPK_INVALID_RAM_SPEC); + } + + // Allocate memory for this resource + contents = global_alloc_array_clear(UINT8, length); + if (contents==NULL) throw rpk_exception(RPK_OUT_OF_MEMORY); + + if (TRACE_RPK) logerror("gromport/RPK: Allocating RAM buffer (%d bytes) for socket '%s'\n", length, socketname); + + ram_pname = NULL; + + // That's it for pure RAM. Now check whether the RAM is "persistent", i.e. NVRAM. + // In that case we must load it from the NVRAM directory. + // The file name is given in the RPK file; the subdirectory is the system name. + ram_type = xml_get_attribute_string(ram_resource_node, "type", NULL); + if (ram_type != NULL) + { + if (strcmp(ram_type, "persistent")==0) + { + // Get the file name (required if persistent) + ram_filename = xml_get_attribute_string(ram_resource_node, "file", NULL); + if (ram_filename==NULL) + { + global_free_array(contents); + throw rpk_exception(RPK_INVALID_RAM_SPEC, "<ram type='persistent'> must have a 'file' attribute"); + } + std::string ram_pathname = std::string(system_name).append(PATH_SEPARATOR).append(ram_filename); + ram_pname = core_strdup(ram_pathname.c_str()); + // load, and fill rest with 00 + if (TRACE_RPK) logerror("gromport/RPK: Loading NVRAM contents from '%s'\n", ram_pname); + image_battery_load_by_name(options, ram_pname, contents, length, 0x00); + } + } + + // Create a socket instance + return new rpk_socket(socketname, length, contents, ram_pname); +} + +/*------------------------------------------------- + rpk_open - open a RPK file + options - parameters from the settings; we need it only for the NVRAM directory + system_name - name of the driver (also just for NVRAM handling) +-------------------------------------------------*/ + +rpk* rpk_reader::open(emu_options &options, const char *filename, const char *system_name) +{ + zip_error ziperr; + + const zip_file_header *header; + const char *pcb_type; + const char *id; + const char *uses_name; + const char *resource_name; + + zip_file* zipfile; + + std::vector<char> layout_text; + xml_data_node *layout_xml = NULL; + xml_data_node *romset_node; + xml_data_node *configuration_node; + xml_data_node *resources_node; + xml_data_node *resource_node; + xml_data_node *socket_node; + xml_data_node *pcb_node; + + rpk_socket *newsock; + + int i; + + rpk *newrpk = new rpk(options, system_name); + + try + { + /* open the ZIP file */ + ziperr = zip_file_open(filename, &zipfile); + if (ziperr != ZIPERR_NONE) throw rpk_exception(RPK_NOT_ZIP_FORMAT); + + /* find the layout.xml file */ + header = find_file(zipfile, "layout.xml", 0); + if (header == NULL) throw rpk_exception(RPK_MISSING_LAYOUT); + + /* reserve space for the layout file contents (+1 for the termination) */ + layout_text.resize(header->uncompressed_length + 1); + + /* uncompress the layout text */ + ziperr = zip_file_decompress(zipfile, &layout_text[0], header->uncompressed_length); + if (ziperr != ZIPERR_NONE) + { + if (ziperr == ZIPERR_UNSUPPORTED) throw rpk_exception(RPK_ZIP_UNSUPPORTED); + else throw rpk_exception(RPK_ZIP_ERROR); + } + + layout_text[header->uncompressed_length] = '\0'; // Null-terminate + + /* parse the layout text */ + layout_xml = xml_string_read(&layout_text[0], NULL); + if (layout_xml == NULL) throw rpk_exception(RPK_XML_ERROR); + + // Now we work within the XML tree + + // romset is the root node + romset_node = xml_get_sibling(layout_xml->child, "romset"); + if (romset_node==NULL) throw rpk_exception(RPK_INVALID_LAYOUT, "document element must be <romset>"); + + // resources is a child of romset + resources_node = xml_get_sibling(romset_node->child, "resources"); + if (resources_node==NULL) throw rpk_exception(RPK_INVALID_LAYOUT, "<romset> must have a <resources> child"); + + // configuration is a child of romset; we're actually interested in ... + configuration_node = xml_get_sibling(romset_node->child, "configuration"); + if (configuration_node==NULL) throw rpk_exception(RPK_INVALID_LAYOUT, "<romset> must have a <configuration> child"); + + // ... pcb, which is a child of configuration + pcb_node = xml_get_sibling(configuration_node->child, "pcb"); + if (pcb_node==NULL) throw rpk_exception(RPK_INVALID_LAYOUT, "<configuration> must have a <pcb> child"); + + // We'll try to find the PCB type on the provided type list. + pcb_type = xml_get_attribute_string(pcb_node, "type", NULL); + if (pcb_type==NULL) throw rpk_exception(RPK_INVALID_LAYOUT, "<pcb> must have a 'type' attribute"); + if (TRACE_RPK) logerror("gromport/RPK: Cartridge says it has PCB type '%s'\n", pcb_type); + + i=0; + do + { + if (strcmp(pcb_type, m_types[i].name)==0) + { + newrpk->m_type = m_types[i].id; + break; + } + i++; + } while (m_types[i].id != 0); + + if (m_types[i].id==0) throw rpk_exception(RPK_UNKNOWN_PCB_TYPE); + + // Find the sockets and load their respective resource + for (socket_node = pcb_node->child; socket_node != NULL; socket_node = socket_node->next) + { + if (strcmp(socket_node->name, "socket")!=0) throw rpk_exception(RPK_INVALID_LAYOUT, "<pcb> element has only <socket> children"); + id = xml_get_attribute_string(socket_node, "id", NULL); + if (id == NULL) throw rpk_exception(RPK_INVALID_LAYOUT, "<socket> must have an 'id' attribute"); + uses_name = xml_get_attribute_string(socket_node, "uses", NULL); + if (uses_name == NULL) throw rpk_exception(RPK_INVALID_LAYOUT, "<socket> must have a 'uses' attribute"); + + bool found = false; + // Locate the resource node + for (resource_node = resources_node->child; resource_node != NULL; resource_node = resource_node->next) + { + resource_name = xml_get_attribute_string(resource_node, "id", NULL); + if (resource_name == NULL) throw rpk_exception(RPK_INVALID_LAYOUT, "resource node must have an 'id' attribute"); + + if (strcmp(resource_name, uses_name)==0) + { + // found it + if (strcmp(resource_node->name, "rom")==0) + { + newsock = load_rom_resource(zipfile, resource_node, id); + newrpk->add_socket(id, newsock); + } + else + { + if (strcmp(resource_node->name, "ram")==0) + { + newsock = load_ram_resource(options, resource_node, id, system_name); + newrpk->add_socket(id, newsock); + } + else throw rpk_exception(RPK_INVALID_LAYOUT, "resource node must be <rom> or <ram>"); + } + found = true; + } + } + if (!found) throw rpk_exception(RPK_INVALID_RESOURCE_REF, uses_name); + } + } + catch (rpk_exception &exp) + { + newrpk->close(); + if (layout_xml != NULL) xml_file_free(layout_xml); + if (zipfile != NULL) zip_file_close(zipfile); + + // rethrow the exception + throw exp; + } + + if (layout_xml != NULL) xml_file_free(layout_xml); + if (zipfile != NULL) zip_file_close(zipfile); + + return newrpk; +} |