diff options
| author | 2025-12-26 14:40:01 +0100 | |
|---|---|---|
| committer | 2025-12-26 14:40:01 +0100 | |
| commit | 14bc9a659667136262e91da60fdf47a35a913138 (patch) | |
| tree | 492b46772cc260282cc82e0ba8032cd7fa607cd1 | |
| parent | 0cd594231537ec9c0299f9f11c8ee18330b930d7 (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.cpp | 541 | ||||
| -rw-r--r-- | src/devices/bus/megadrive/cart/segach.h | 50 | ||||
| -rw-r--r-- | src/devices/bus/megadrive/cart/segach_img.h | 168 |
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 |
