summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author superctr <themvrules@gmail.com>2025-12-26 14:40:01 +0100
committer GitHub <noreply@github.com>2025-12-26 14:40:01 +0100
commit14bc9a659667136262e91da60fdf47a35a913138 (patch)
tree492b46772cc260282cc82e0ba8032cd7fa607cd1
parent0cd594231537ec9c0299f9f11c8ee18330b930d7 (diff)
segach.cpp: emulate Sega Channel (US) adapter using simulated transmission (#14712)
* segach.cpp: gets past tuning errors [superctr] segach.cpp: gets past tuner error 000A segach.cpp: make tcu reg map 8-bit segach.cpp: add authorization bits segach.cpp: now gets past tuning * segach.cpp: implement packet broadcast * segach.cpp: now loads into the menu * segach.cpp: add TCU parental controls registers * segach.cpp: first shot at station ID and channel number TCU registers * segach.cpp: Add basic bank switching * segach.cpp: use quickload to open packet dumps * segach.cpp: support the IMG file format from the transmission disc * segach.cpp: cleanup CRC functions * segach.cpp: add menu button
-rw-r--r--src/devices/bus/megadrive/cart/segach.cpp541
-rw-r--r--src/devices/bus/megadrive/cart/segach.h50
-rw-r--r--src/devices/bus/megadrive/cart/segach_img.h168
3 files changed, 705 insertions, 54 deletions
diff --git a/src/devices/bus/megadrive/cart/segach.cpp b/src/devices/bus/megadrive/cart/segach.cpp
index 22be97439cd..0bc4b685ad5 100644
--- a/src/devices/bus/megadrive/cart/segach.cpp
+++ b/src/devices/bus/megadrive/cart/segach.cpp
@@ -1,27 +1,26 @@
// license: BSD-3-Clause
-// copyright-holders: Angelo Salese
+// copyright-holders: Angelo Salese, superctr, Nathan Misner
/**************************************************************************************************
-Sega Channel Game no Kanzume "digest" RAM cart, developed by CRI
-
-https://segaretro.org/Game_no_Kanzume_Otokuyou
-
-TODO:
-- some unknowns, needs PCB picture;
-- Scientific Atlanta Sega Channel cart also derives from SSF but has it's own mapper scheme
- cfr. SCTOOLS/MENUTEST/HARDWARE.I in Sega Channel Jan 1996 dev CD dump.
- The SCI-ATL ASIC also contains an unspecified TCU device (or that's behind the RF shield?),
- in word status/data pair, where status direction is at bit 15 (1) read (0) write
+Sega Channel
**************************************************************************************************/
+#include <fstream>
+
#include "emu.h"
#include "segach.h"
+#include "segach_img.h"
#include "bus/generic/slot.h"
/*
- * Game no Kanzume digest cart
+ * Sega Channel Game no Kanzume "digest" RAM cart, developed by CRI
+ *
+ * https://segaretro.org/Game_no_Kanzume_Otokuyou
+ *
+ * TODO:
+ * - some unknowns, needs PCB picture;
*/
DEFINE_DEVICE_TYPE(MEGADRIVE_SEGACH_JP, megadrive_segach_jp_device, "megadrive_segach_jp", "Megadrive Sega Channel Game no Kanzume RAM cart")
@@ -88,10 +87,23 @@ void megadrive_segach_jp_device::time_io_map(address_map &map)
*
* https://segaretro.org/Sega_Channel#Photo_gallery
*
+ * Use -quik to load a broadcast IMG file, for example DOM0816.IMG
+ * If no IMG is loaded, simulates no signal (you will get error 0005)
+ *
* Various combinations hold at boot calls different menus:
* - START+A: parental control
* - START+C: language select
+ * - START+B allows access to a diagnostics menu if a special
+ * device is connected to controller port 2 that shorts
+ * bit 0 and 6. For segachnl, you can bypass this
+ * by noping out everything from $a61a to $a670 (or doing
+ * the same thing with breakpoints in the debugger).
+ *
+ * The BIOS also listens to controller port 2 in serial mode
+ * for a diagnostics mode.
*
+ * TODO: - Automatic reset timer (used for "Test Drive" demos)
+ * - NVRAM
*/
DEFINE_DEVICE_TYPE(MEGADRIVE_SEGACH_US, megadrive_segach_us_device, "megadrive_segach_us", "Megadrive Sega Channel US cart")
@@ -100,11 +112,35 @@ megadrive_segach_us_device::megadrive_segach_us_device(const machine_config &mco
: device_t(mconfig, MEGADRIVE_SEGACH_US, tag, owner, clock)
, device_megadrive_cart_interface( mconfig, *this )
, device_memory_interface( mconfig, *this )
+ , m_packet_timer(*this, "packet_timer")
, m_rom(*this, "rom")
- , m_space_tcu_config("tcu_io", ENDIANNESS_BIG, 16, 9, 0, address_map_constructor(FUNC(megadrive_segach_us_device::tcu_map), this))
+ , m_space_tcu_config("tcu_io", ENDIANNESS_BIG, 8, 9, 0, address_map_constructor(FUNC(megadrive_segach_us_device::tcu_map), this))
+ , m_sram_view(*this, "sram_view")
+ , m_game_view(*this, "game_view")
+ , m_game_sram_view(*this, "game_sram_view")
{
}
+void megadrive_segach_us_device::device_add_mconfig(machine_config &config)
+{
+ // each broadcast frame has 10 packets and 288 bytes per packet
+ // assuming 12Mbps download rate
+ TIMER(config, "packet_timer", 0).configure_periodic(FUNC(megadrive_segach_us_device::send_packets),
+ attotime::from_hz((12*128072) / 2880));
+
+ QUICKLOAD(config, "packet_stream", "img").set_load_callback(FUNC(megadrive_segach_us_device::quickload_cb));
+}
+
+static INPUT_PORTS_START( megadrive_segach_us )
+ PORT_START("BUTTON")
+ PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_NAME("Menu") PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(megadrive_segach_us_device::menu_pressed), 0)
+INPUT_PORTS_END
+
+ioport_constructor megadrive_segach_us_device::device_input_ports() const
+{
+ return INPUT_PORTS_NAME( megadrive_segach_us );
+}
+
void megadrive_segach_us_device::device_start()
{
memory_region *const romregion(cart_rom_region());
@@ -114,7 +150,17 @@ void megadrive_segach_us_device::device_start()
{
m_rom->configure_entry(0, &base[0]);
});
- m_ram.resize(0x40'0000 / 2);
+ m_dram.resize(0x40'0000 / 2);
+ m_sram.resize(0x2000);
+ m_nvm.fill(0);
+
+ m_broadcast_count = 0;
+
+ m_game_id = 0;
+ m_curr_packet = 0;
+ m_packet_match = 0x7fff;
+ m_gen_control = 0;
+ m_gen_status = 0;
}
void megadrive_segach_us_device::device_reset()
@@ -130,14 +176,23 @@ device_memory_interface::space_config_vector megadrive_segach_us_device::memory_
void megadrive_segach_us_device::cart_map(address_map &map)
{
- map(0x00'0000, 0x03'ffff).bankr(m_rom);
- // TODO: SRAM overlay at 0x20'0000 (length 0x8000)
+ map(0x00'0000, 0x0f'ffff).bankr(m_rom);
- // TODO: banked?
+ // TODO: SSF banking
map(0x10'0000, 0x4f'ffff).lrw16(
- NAME([this] (offs_t offset, u16 mem_mask) { return m_ram[offset]; }),
- NAME([this] (offs_t offset, u16 data, u16 mem_mask) { COMBINE_DATA(&m_ram[offset]); })
+ NAME([this] (offs_t offset, u16 mem_mask) { return m_dram[offset]; }),
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) { COMBINE_DATA(&m_dram[offset]); })
);
+ map(0x20'0000, 0x3f'ffff).view(m_sram_view);
+ m_sram_view[0](0x20'0000, 0x3f'ffff).rw(FUNC(megadrive_segach_us_device::sram_r), FUNC(megadrive_segach_us_device::sram_w));
+
+ map(0x00'0000, 0x3f'ffff).view(m_game_view);
+ m_game_view[0](0x00'0000, 0x3f'ffff).lr16(
+ // TODO: would need to confirm on real adapter if DRAM is locked in game mode.
+ NAME([this] (offs_t offset, u16 mem_mask) { return m_dram[offset]; })
+ );
+ m_game_view[0](0x20'0000, 0x3f'ffff).view(m_game_sram_view);
+ m_game_sram_view[0](0x20'0000, 0x3f'ffff).rw(FUNC(megadrive_segach_us_device::sram_r), FUNC(megadrive_segach_us_device::sram_w));
}
void megadrive_segach_us_device::time_io_map(address_map &map)
@@ -145,81 +200,463 @@ void megadrive_segach_us_device::time_io_map(address_map &map)
// TCU STATUS
map(0x04, 0x05).lrw16(
NAME([] (offs_t offset, u16 mem_mask) {
- // status for something
- return 0xf;
+ // bit 0: PLL locked
+ // bit 1: will cause error 0001 if set
+ // bit 15: TCU is busy
+ return 1;
}),
NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
- // assume always 16-bit access
// 1: set for read, 0: set for write
m_tcu_dir = BIT(data, 15);
m_tcu_index = data & 0x1ff;
})
);
// TCU DATA
- map(0x06, 0x07).lrw16(
- NAME([this] (offs_t offset, u16 mem_mask) -> u16 {
+ map(0x07, 0x07).lrw8(
+ NAME([this] (offs_t offset) -> u16 {
if (!m_tcu_dir && !machine().side_effects_disabled())
{
logerror("Attempting to read with TCU DIR unset\n");
return 0xffff;
}
- return space(0).read_word(m_tcu_index, mem_mask);
+ u16 data = space(0).read_byte(m_tcu_index);
+ if (!machine().side_effects_disabled())
+ m_tcu_index = (m_tcu_index + 1) & 0x1ff;
+ return data;
}),
- NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ NAME([this] (offs_t offset, u16 data) {
if (m_tcu_dir)
{
logerror("Attempting to write with TCU DIR set\n");
return;
}
- space(0).write_word(m_tcu_index, data, mem_mask);
+ space(0).write_byte(m_tcu_index, data);
+ m_tcu_index = (m_tcu_index + 1) & 0x1ff;
})
);
// map(0x10, 0x11) fixit start address
// map(0x12, 0x13) fixit work bound
// map(0x20, 0x21) game timeout
-// map(0x22, 0x23) game id
-// map(0x24, 0x25) pkt match address
-// map(0x26, 0x27) current spacket
-// map(0x30, 0x31) gen status
-// map(0x32, 0x33) gen control
+ map(0x22, 0x23).lrw16( // game id
+ NAME([this] (offs_t offset, u16 mem_mask) -> u16 {
+ return m_game_id;
+ }),
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ logerror("game_id: %04x\n", data);
+ m_game_id = data;
+ })
+ );
+ map(0x24, 0x25).lrw16( // pkt match address
+ NAME([this] (offs_t offset, u16 mem_mask) -> u16{
+ return m_packet_match;
+ }),
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ logerror("packet_match: %04x\n", data);
+ m_packet_match = data;
+ })
+ );
+ map(0x26, 0x27).lrw16( // current superpacket
+ NAME([this] (offs_t offset, u16 mem_mask) -> u16 {
+ return m_curr_packet;
+ }),
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ logerror("curr_packet: %04x\n", data);
+ m_curr_packet = data;
+ })
+ );
+ map(0x30, 0x31).lrw16( // general status
+ NAME([this] (offs_t offset, u16 mem_mask) -> u16 {
+ // bit 0: download complete
+ // bit 1-2: unknown status bits
+ // bit 12-15: ASIC version? causes BIOS to return error 1.
+ return m_gen_status;
+ }),
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ logerror("gen_status: %04x\n", data);
+ m_gen_status &= ~data;
+ })
+ );
+ map(0x32, 0x33).lrw16(
+ NAME([this] (offs_t offset, u16 mem_mask) -> u16 {
+ // bit 0: reset console? (could also be bit 3)
+ // bit 1: download enable
+ // bit 2: packet match counter reset
+ // bit 3: switch BIOS/game view (could also be bit 0)
+ // bit 4: reset CRC
+ // bit 5: enable fixit buffer
+ // bit 8: memory configuration, set if 3MB detected?
+ return m_gen_control;
+ }),
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ logerror("gen_control: %04x\n", data);
+ m_gen_control = data & 0x12e;
+ if (data & 0x01)
+ logerror("console reset\n");
+ if (data & 0x02)
+ logerror("start download\n");
+ if (data & 0x04)
+ m_packet_match = 0x7fff; // side effect?
+ if (data & 0x10)
+ m_crc = 0;
+ if ((data & 0x09) == 0x09)
+ {
+ // boot game
+ m_game_view.select(0);
+ // HACK: should assert /VRES on connector causing 68k soft reset.
+ machine().root_device().reset();
+ }
+ })
+ );
// map(0x34, 0x35) error counter
-// map(0x40, 0x41) CRC input
-// map(0x42, 0x43) CRC low out
-// map(0x44, 0x45) CRC high out
+ map(0x40, 0x41).lw16( // CRC input
+ NAME([this] (offs_t offset, u16 data, u16 mem_mask) {
+ crc_write(data);
+ })
+ );
+ map(0x42, 0x43).lr16( // CRC low out
+ NAME([this] (offs_t offset, u16 mem_mask) -> u16 {
+ return crc_read() & 0xffff;
+ })
+ );
+ map(0x44, 0x45).lr16( // CRC high out
+ NAME([this] (offs_t offset, u16 mem_mask) -> u16 {
+ return crc_read() >> 16;
+ })
+ );
+ map(0xf0, 0xf1).w(FUNC(megadrive_segach_us_device::sram_enable_w));
// map(0xf0, 0xff) SSF style bankswitch?
}
void megadrive_segach_us_device::tcu_map(address_map &map)
{
-// map(0x000, 0x000) AUTH map
-// map(0x010, 0x010) free map
-// map(0x020, 0x020) p2play
-// map(0x023, 0x023) p2pservid
-// map(0x0a0, 0x0a0) transdata
-// map(0x0b1, 0x0b2) gametime
-// map(0x0b3, 0x0b3) resetcond
-// map(0x0b4, 0x0b4) dayofweek
+ map(0x000, 0x00f).lr8( // authorized service map
+ NAME([] () -> u8 {
+ // this is a bitmap containing which service IDs are
+ // authorized to play on the adapter.
+ return 0xff;
+ }));
+ map(0x010, 0x01f).lr8( // free service map
+ NAME([] () -> u8 {
+ // this is a bitmap containing which service IDs are
+ // free to play on any adapter. only checked if the
+ // authorization bit was 0.
+ return 0xff;
+ }));
+// map(0x020, 0x020) pay-to-play
+// map(0x023, 0x023) pay-to-play service ID
+// map(0x0a0, 0x0a0) transaction data
+// map(0x0b1, 0x0b2) game time remaining
+// map(0x0b3, 0x0b3) reset condition
+// map(0x0b4, 0x0b4) day of week
// map(0x0b5, 0x0b5) week
-// map(0x0b6, 0x0b6) todtimout
-// map(0x0b7, 0x0b7) authbyte
+// map(0x0b6, 0x0b6) time of day timeout
+ map(0x0b7, 0x0b7).lr8( // authorization byte
+ NAME([] () -> u8 {
+ // bit 7 is set if the cable company has authorized the adapter
+ return 0x80; // 0x00..0x04 if unauthorized
+ }));
// map(0x0b8, 0x0b8) checksum
-// map(0x0b9, 0x0b9) PLL config
+ map(0x0b9, 0x0b9).lr8( // PLL type
+ NAME([] () -> u8 {
+ return 0x81; // mitsubishi/philips
+ // return 0x87; // rohm
+ }));
// map(0x0ba, 0x0bd) PLL data
-// map(0x0c0, 0x0c1) password
-// map(0x0c2, 0x0c2) chanbitmap
-// map(0x0cb, 0x0cb) parental control level
+ map(0x0c0, 0x0c1).lrw8( // parental control password
+ NAME([this] (offs_t offset) -> u8 {
+ if (!machine().side_effects_disabled())
+ logerror("TCU: read parental control password %d: %02x\n", offset, m_nvm[0xc0 + offset]);
+ return m_nvm[0xc0 + offset];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_nvm[0xc0 + offset] = data;
+ logerror("TCU: write parental control password %d: %02x\n", offset, data);
+ m_nvm[0xcb] = data;
+ })
+ );
+ map(0x0c2, 0x0ca).lr8( // channel bitmap
+ NAME([this] (offs_t offset) -> u8 {
+ // this is a bitmap with the corresponding bit set when the TCU
+ // detects a channel
+ if (!machine().side_effects_disabled())
+ logerror("TCU: read channel bitmap read offset %02x\n", offset);
+ if (m_broadcast.size())
+ return 0xFF; // Signal everywhere :D
+ else
+ return 0x00; // No signal :(
+ }));
+ map(0x0cb, 0x0cb).lrw8( // parental control level
+ NAME([this] (offs_t offset) -> u8 {
+ if (!machine().side_effects_disabled())
+ logerror("TCU: read parental control level: %02x\n", m_nvm[0xcb]);
+ return m_nvm[0xcb];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ logerror("TCU: write parental control level: %02x\n", data);
+ m_nvm[0xcb] = data;
+ })
+ );
// map(0x0cc, 0x0cc) initialized
// map(0x0cd, 0x0ce) random seed, (d?)word
+ map(0x0cd, 0x0ce).lrw8(
+ NAME([this] (offs_t offset) -> u8 {
+ return m_nvm[0xcd + offset];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_nvm[0xcd + offset] = data;
+ })
+ );
// map(0x0cf, 0x0cf) next DRAM test block
-// map(0x0e0, 0x0e0) station
+// map(0x0e0, 0x0e0) station ID
+ map(0x0e0, 0x0e0).lw8(
+ NAME([this] (offs_t offset, u8 data) {
+ logerror("TCU: write station ID: %02x\n", data);
+ // set the "logical channel"
+ m_nvm[0xe5] = data;
+ })
+ );
// map(0x0e1, 0x0e1) slot
// map(0x0e2, 0x0e2) service ID
-// map(0x0e3, 0x0e3) filter code
+// map(0x0e3, 0x0e4) filter code
// map(0x0e5, 0x0e5) channel number
+ map(0x0e5, 0x0e5).lr8(
+ // After setting the station ID, the BIOS wants the "logical channel" to match
+ // a certain value and it will keep tuning to find it, eventually timing out
+ // if it isn't correct.
+ // Rather than trying to calculate what the "logical channel" is,
+ // I'm just bruteforcing a solution here...
+ NAME([this] (offs_t offset) {
+ uint8_t data = m_nvm[0xe5];
+ if (!machine().side_effects_disabled())
+ {
+ logerror("TCU: read channel number: %02x\n", m_nvm[0xe5]);
+ m_nvm[0xe5] = (data + 1) % 16;
+ }
+ return data;
+ })
+ );
+
// map(0x0f3, 0x0f6) unit addr
+
+// map(0x1e0, 0x1ff) mini NVRAM?
+// the following are hard coded for now:
// map(0x1e0, 0x1e0) language
// map(0x1e1, 0x1e1) help communications port
// map(0x1e2, 0x1e2) menu lptr
// map(0x1e6, 0x1e6) tuner type (0) TD1A (1) TD1B
-// map(0x1e1, 0x1ff) mini NVRAM?
+ map(0x1e0, 0x1ff).lrw8(
+ NAME([this] (offs_t offset) -> u8 {
+ return m_nvm[0x1e0 + offset];
+ }),
+ NAME([this] (offs_t offset, u8 data) {
+ m_nvm[0x1e0 + offset] = data;
+ })
+ );
+}
+
+QUICKLOAD_LOAD_MEMBER(megadrive_segach_us_device::quickload_cb)
+{
+ auto helper = megadrive_segach_us_img();
+
+ // Load packets stream
+ size_t num_frames = image.length() / helper.FRAME_LEN;
+ m_broadcast.resize(num_frames);
+
+ struct game_file {
+ int time;
+ int time_buf;
+ int time_bit;
+ };
+ std::map<u16, game_file> files;
+
+ // first pass: get gametime bits
+ for (size_t frame = 0; frame < num_frames; frame++)
+ {
+ u8 data[helper.FRAME_LEN];
+ image.fread(&data[0], helper.FRAME_LEN);
+
+ std::array<std::array<u8, helper.PACKET_LEN>, 10> pipes;
+ helper.deweave(pipes, data);
+ for (auto& pipe : pipes)
+ {
+ helper.deinterleave(pipe);
+
+ u16 file_id = 0;
+ helper.or_bits(&pipe[0], 39, (u8*) &file_id, 14);
+ file_id = bitswap<14>(file_id, 0,1,2,3,4,5,6,7,8,9,10,11,12,13);
+
+ u8 game_time_sync = 0;
+ helper.or_bits(&pipe[0], 30, (uint8_t *)&game_time_sync, 1);
+ u8 game_time_bit = 0;
+ helper.or_bits(&pipe[0], 31, (uint8_t *)&game_time_bit, 1);
+
+ if (!files.count(file_id))
+ {
+ game_file new_file;
+ new_file.time = 0;
+ new_file.time_buf = 0;
+ new_file.time_bit = 0;
+ files[file_id] = new_file;
+ }
+ auto& file = files[file_id];
+
+ if (game_time_sync) {
+ file.time = file.time_buf * 20;
+ file.time_buf = 0;
+ file.time_bit = 15;
+ }
+ if (file.time_bit >= 0) {
+ file.time_buf |= game_time_bit << file.time_bit;
+ file.time_bit--;
+ }
+ }
+ }
+
+ // second pass: copy everything else
+ image.fseek(0, SEEK_SET);
+ for (size_t frame = 0; frame < num_frames; frame++)
+ {
+ u8 data[helper.FRAME_LEN];
+ image.fread(&data[0], helper.FRAME_LEN);
+
+ std::array<std::array<u8, helper.PACKET_LEN>, 10> pipes;
+ helper.deweave(pipes, data);
+
+ size_t pipe_num = 0;
+ for (auto& pipe : pipes)
+ {
+ helper.deinterleave(pipe);
+ u8 data[helper.PACKET_DATA_LEN];
+
+ u16 file_id = 0;
+ helper.or_bits(&pipe[0], 39, (u8*) &file_id, 14);
+ file_id = bitswap<14>(file_id, 0,1,2,3,4,5,6,7,8,9,10,11,12,13);
+
+ u8 service_id = 0;
+ helper.or_bits(&pipe[0], 32, (u8*) &service_id, 7);
+ service_id = bitswap<7>(service_id, 0,1,2,3,4,5,6);
+
+ u16 address = 0;
+ helper.or_bits(&pipe[0], 53, (u8*) &address, 15);
+ address = bitswap<15>(address, 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14);
+
+ helper.get_data(&pipe[0], &data[0]);
+
+ auto& packet = m_broadcast[frame][pipe_num];
+ packet.file_id = file_id;
+ packet.service_id = service_id;
+ packet.game_time = files[file_id].time;
+ packet.address = address;
+
+ for (size_t word_pos = 0; word_pos < helper.PACKET_DATA_LEN / 2; word_pos ++)
+ {
+ size_t byte_pos = word_pos * 2;
+ packet.data[word_pos] = (data[byte_pos] << 8) | data[byte_pos + 1];
+ }
+ pipe_num++;
+ }
+ }
+
+ logerror("broadcast frames loaded: %d\n", num_frames);
+ return std::make_pair(std::error_condition(), std::string());
+}
+
+TIMER_DEVICE_CALLBACK_MEMBER(megadrive_segach_us_device::send_packets)
+{
+ if (m_broadcast.size())
+ {
+ //logerror("current packet = %d\n", m_broadcast_packet);
+ for (int pipe = 0; pipe < 10; pipe++)
+ {
+ auto& packet = m_broadcast[m_broadcast_count][pipe];
+ if ((m_gen_control & 0x0002) && packet.file_id == m_game_id)
+ {
+ // the adapter can probably not read all pipes at a time
+ // the menu is broadcast on two pipes with offset addresses,
+ // so skip the copy to prevent confusing the receiver.
+ if (packet.file_id == 0 && pipe != 0)
+ continue;
+ m_curr_packet = packet.address;
+ if (m_curr_packet == m_packet_match)
+ {
+ logerror("download complete\n");
+ m_gen_status |= 1;
+ m_gen_control &= ~(0x02);
+ }
+ else
+ {
+ size_t dram_address = m_curr_packet * (246 / 2);
+ for (int data_i = 0; data_i < (246 / 2); data_i++)
+ m_dram[(dram_address++) & 0x1fffff] = packet.data[data_i];
+ if (m_curr_packet < m_packet_match)
+ m_packet_match = m_curr_packet;
+ }
+ }
+ }
+ m_broadcast_count = (m_broadcast_count + 1) % m_broadcast.size();
+ }
+}
+
+void megadrive_segach_us_device::sram_enable_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ // the BIOS wants to switch in SRAM by writing bit 0...
+ logerror("sram enable: %04x", data);
+ if (data & 3)
+ {
+ m_sram_view.select(0);
+ m_game_sram_view.select(0);
+ }
+ else
+ {
+ m_sram_view.disable();
+ m_game_sram_view.disable();
+ }
+}
+
+u16 megadrive_segach_us_device::sram_r(offs_t offset)
+{
+ const u32 sram_offset = offset & 0x1fff;
+ return 0xff00 | m_sram[sram_offset];
+}
+
+void megadrive_segach_us_device::sram_w(offs_t offset, u16 data, u16 mem_mask)
+{
+ if (ACCESSING_BITS_0_7)
+ {
+ const u32 sram_offset = offset & 0x1fff;
+ m_sram[sram_offset] = data & 0xff;
+ }
+}
+
+
+void megadrive_segach_us_device::crc_write(u16 data)
+{
+ u32 crc = m_crc;
+ for (int i = 0; i < 16; i++) {
+ u32 input_bit = (data & (0x8000 >> i)) ? 1 : 0;
+ u32 crc_msb = (crc & 0x80000000) ? 1 : 0;
+ if (input_bit == crc_msb) {
+ crc = crc << 1;
+ } else {
+ crc = (crc << 1) ^ 0x04C11DB7;
+ }
+ }
+ m_crc = crc;
+}
+
+u32 megadrive_segach_us_device::crc_read() const
+{
+ return bitswap<32>(m_crc, 0,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);
+}
+
+INPUT_CHANGED_MEMBER( megadrive_segach_us_device::menu_pressed )
+{
+ if (newval)
+ {
+ // boot BIOS.
+ m_game_view.disable();
+ // HACK: should assert /VRES on connector causing 68k soft reset.
+ machine().root_device().reset();
+ }
}
diff --git a/src/devices/bus/megadrive/cart/segach.h b/src/devices/bus/megadrive/cart/segach.h
index 06a3cba3a87..3ac254fd878 100644
--- a/src/devices/bus/megadrive/cart/segach.h
+++ b/src/devices/bus/megadrive/cart/segach.h
@@ -1,5 +1,5 @@
// license: BSD-3-Clause
-// copyright-holders: Angelo Salese
+// copyright-holders: Angelo Salese, superctr
#ifndef MAME_BUS_MEGADRIVE_CART_SEGACH_H
#define MAME_BUS_MEGADRIVE_CART_SEGACH_H
@@ -7,6 +7,8 @@
#pragma once
#include "slot.h"
+#include "machine/timer.h"
+#include "imagedev/snapquik.h"
class megadrive_segach_jp_device : public device_t,
public device_megadrive_cart_interface
@@ -26,6 +28,7 @@ protected:
std::vector<u16> m_ram;
};
+
class megadrive_segach_us_device : public device_t,
public device_megadrive_cart_interface,
public device_memory_interface
@@ -33,10 +36,17 @@ class megadrive_segach_us_device : public device_t,
public:
megadrive_segach_us_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+ DECLARE_QUICKLOAD_LOAD_MEMBER(quickload_cb);
+
virtual void cart_map(address_map &map) override ATTR_COLD;
virtual void time_io_map(address_map &map) override ATTR_COLD;
+ DECLARE_INPUT_CHANGED_MEMBER(menu_pressed);
+
protected:
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+ virtual ioport_constructor device_input_ports() const override ATTR_COLD;
+
virtual void device_start() override ATTR_COLD;
virtual void device_reset() override ATTR_COLD;
@@ -44,12 +54,48 @@ protected:
void tcu_map(address_map &map);
+ void load_packets();
+ TIMER_DEVICE_CALLBACK_MEMBER(send_packets);
+
+ void sram_enable_w(offs_t offset, u16 data, u16 mem_mask);
+ u16 sram_r(offs_t offset);
+ void sram_w(offs_t offset, u16 data, u16 mem_mask);
+
+ void crc_write(u16 data);
+ u32 crc_read() const;
+
private:
+ struct packet
+ {
+ u16 file_id;
+ u16 service_id;
+ u16 game_time;
+ u16 address;
+ std::array<u16, 246 / 2> data;
+ };
+
+ required_device<timer_device> m_packet_timer;
+
memory_bank_creator m_rom;
- std::vector<u16> m_ram;
+ std::vector<u16> m_dram;
+ std::vector<u8> m_sram;
+ std::vector<std::array<packet, 10>> m_broadcast;
+ std::array<u8, 0x200> m_nvm;
address_space_config m_space_tcu_config;
+ memory_view m_sram_view;
+ memory_view m_game_view;
+ memory_view m_game_sram_view;
+
+ size_t m_broadcast_count;
+
+ u16 m_game_id;
+ u16 m_curr_packet, m_packet_match;
+
+ u16 m_gen_control, m_gen_status;
+ u32 m_crc;
+
u16 m_tcu_index, m_tcu_dir;
};
diff --git a/src/devices/bus/megadrive/cart/segach_img.h b/src/devices/bus/megadrive/cart/segach_img.h
new file mode 100644
index 00000000000..073e296edec
--- /dev/null
+++ b/src/devices/bus/megadrive/cart/segach_img.h
@@ -0,0 +1,168 @@
+// license: BSD-3-Clause
+// copyright-holders: Nathan Misner, superctr
+
+#ifndef MAME_BUS_MEGADRIVE_CART_SEGACH_IMG_H
+#define MAME_BUS_MEGADRIVE_CART_SEGACH_IMG_H
+
+#pragma once
+
+class megadrive_segach_us_img
+{
+ public:
+ static constexpr unsigned NUM_PIPES = 10;
+ static constexpr unsigned PACKET_LEN = 288;
+ static constexpr unsigned FRAME_LEN = 288 * 10;
+ static constexpr unsigned PACKET_DATA_LEN = 246;
+
+ static void or_bits(u8* source, unsigned sbitoff, u8* dest, int numbit)
+ {
+ sbitoff--;
+ unsigned sbytes = sbitoff >> 3;
+ sbitoff &= 7;
+
+ unsigned dbitoff = 0;
+ unsigned dbytes = 0;
+
+ while (numbit)
+ {
+ if (sbitoff == 8)
+ {
+ sbytes++;
+ sbitoff = 0;
+ }
+ if (dbitoff == 8)
+ {
+ dbytes++;
+ dbitoff = 0;
+ }
+ if (source[sbytes] & (1 << sbitoff))
+ {
+ dest[dbytes] |= (1 << dbitoff);
+ }
+ sbitoff++;
+ dbitoff++;
+ numbit--;
+ }
+ }
+ static void deweave(std::array<std::array<u8, PACKET_LEN>, 10>& pipes, u8 *data)
+ {
+ unsigned cursor = 0;
+ for (unsigned i = 0; i < PACKET_LEN; i += 2)
+ {
+ for (unsigned j = 0; j < NUM_PIPES; j++)
+ {
+ pipes[j][i] = data[cursor++];
+ pipes[j][i + 1] = data[cursor++];
+ }
+ }
+ }
+ static void deinterleave(std::array<u8, PACKET_LEN>& data)
+ {
+ u8 interFrame[PACKET_LEN] = { 0 };
+ unsigned packetAoff = 0;
+ unsigned packetBoff = 225;
+ unsigned packetXoff = packetAoff;
+ unsigned packet = 0;
+
+ or_bits(&data[0], 1, interFrame, 27);
+ while (packet < 9)
+ {
+ if (packet == 0)
+ {
+ packetAoff += 27;
+ packetBoff += 27;
+ packetXoff += 27;
+ }
+ if (packet == 4)
+ {
+ packetBoff += 27;
+ }
+ if (packet == 6)
+ {
+ packetAoff += 27;
+ packetXoff += 27;
+ }
+
+ unsigned bitCount = 0;
+ unsigned pc = 0;
+ unsigned packetAbit = 0;
+ unsigned packetBbit = 0;
+ unsigned *bitPtr = &packetAbit;
+ while (bitCount < 450)
+ {
+ if (pc == 2)
+ {
+ pc = 0;
+ if (bitPtr == &packetAbit)
+ bitPtr = &packetBbit;
+ else
+ bitPtr = &packetAbit;
+ }
+
+ if (bitCount == 225)
+ {
+ bitPtr = &packetBbit;
+ pc = 0;
+ }
+
+ unsigned packetBits;
+ if (bitPtr == &packetAbit)
+ packetBits = *bitPtr + packetAoff;
+ else
+ packetBits = *bitPtr + packetBoff;
+
+ if ((packet == 4) && (bitCount == 225))
+ packetXoff += 27;
+
+ // copy bit #sourceBits to bit #packetBits
+ unsigned sourceBits = bitCount + packetXoff;
+ if (data[sourceBits >> 3] & (1 << (sourceBits & 7)))
+ interFrame[packetBits >> 3] |= (1 << (packetBits & 7));
+ (*bitPtr)++;
+ bitCount++;
+ pc++;
+ }
+
+ packetAoff += 450;
+ packetBoff += 450;
+ packetXoff = packetAoff;
+ packet += 2;
+ }
+ memcpy(&data[0], interFrame, PACKET_LEN);
+ }
+ static void get_data(u8 *in, u8 *out)
+ {
+ u8 rData[PACKET_DATA_LEN] = { 0 };
+
+ int bitoff = 140;
+ for (int i = 0; i < PACKET_DATA_LEN;)
+ {
+ if (bitoff == 1153)
+ {
+ bitoff += 27;
+ }
+
+ if (i == 0)
+ {
+ or_bits(in, bitoff, rData, 96);
+ bitoff += 96;
+ i += 12;
+ }
+ else
+ {
+ or_bits(in, bitoff, rData + i, 208);
+ bitoff += 208;
+ i += 26;
+ }
+ // skip bch & parity
+ bitoff += 17;
+ }
+
+ for (int i = 0; i < PACKET_DATA_LEN; i++)
+ {
+ out[i] = bitswap<8>(rData[i], 0,1,2,3,4,5,6,7);
+ }
+ }
+};
+
+#endif