diff --git a/src/mame/konami/k573acio.cpp b/src/mame/konami/k573acio.cpp new file mode 100644 index 0000000000000..7e1a4e8bee7d9 --- /dev/null +++ b/src/mame/konami/k573acio.cpp @@ -0,0 +1,374 @@ +// license:BSD-3-Clause +// copyright-holders:windyfairy +/* + * Konami 573 Magnetic Card R/W Unit + * + */ +#include "emu.h" +#include "k573acio.h" + +#define LOG_OUTPUT_FUNC osd_printf_info +#include "logmacro.h" + +DEFINE_DEVICE_TYPE(KONAMI_573_ACIO_HOST, k573acio_host_device, "k573acio", "Konami 573 ACIO Host") + +k573acio_host_device::k573acio_host_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, KONAMI_573_ACIO_HOST, tag, owner, clock), + device_serial_interface(mconfig, *this), + device_rs232_port_interface(mconfig, *this), + m_timer_response(nullptr), + m_nodecnt(0) +{ + m_node_device = nullptr; +} + +void k573acio_host_device::device_start() +{ + int startbits = 1; + int databits = 8; + parity_t parity = PARITY_NONE; + stop_bits_t stopbits = STOP_BITS_1; + + set_data_frame(startbits, databits, parity, stopbits); + set_rate(BAUDRATE); + + output_rxd(1); + output_dcd(0); + output_dsr(0); + output_ri(0); + output_cts(0); + + m_message.clear(); + m_response.clear(); + + m_timer_response = timer_alloc(FUNC(k573acio_host_device::send_response), this); + +} + +void k573acio_host_device::device_reset() +{ + m_timer_response->adjust(attotime::never); + + m_message.clear(); + m_response.clear(); + + m_init_state = 0; +} + +void k573acio_host_device::add_device(k573acio_node_device *dev) +{ + if (dev == nullptr) + return; + + m_nodecnt++; + + dev->set_node_id(m_nodecnt); + + if(m_node_device) + m_node_device->chain(dev); + else + m_node_device = dev; +} + +void k573acio_host_device::tra_callback() +{ + output_rxd(transmit_register_get_data_bit()); +} + +void k573acio_host_device::tra_complete() +{ + m_timer_response->adjust(attotime::from_hz(BAUDRATE)); +} + +TIMER_CALLBACK_MEMBER(k573acio_host_device::send_response) +{ + if (!m_response.empty() && is_transmit_register_empty()) + { + auto c = m_response.front(); + m_response.pop_front(); + transmit_register_setup(c); + } +} + +void k573acio_host_device::rcv_complete() +{ + receive_register_extract(); + + m_message.push_back(get_received_char()); + + while (!m_message.empty() && m_message.front() != HEADER_BYTE) + m_message.pop_front(); + + // A message must have at least a header byte, command, node ID, and sub command + if (m_message.size() < 4) + return; + + if (m_message[0] == 0xaa && m_message[1] == 0xaa && m_message[2] == 0xaa && m_message[3] == 0x55) + { + // Sync command + for (int i = 0; i < 4; i++) + m_message.pop_front(); + + m_response.push_back(0xaa); + m_response.push_back(0xaa); + m_response.push_back(0xaa); + m_response.push_back(0x55); + + m_init_state = 1; + + } + else if (m_init_state == 1 && m_message[0] == 0xaa && m_message[1] == 0xaa && m_message[2] == 0x00 && m_message[3] == 0x00) + { + // Sync command 2 + for (int i = 0; i < 4; i++) + m_message.pop_front(); + + m_response.push_back(0xaa); + m_response.push_back(0xaa); + m_response.push_back(0x00); + m_response.push_back(0x00); + + m_init_state = 2; + } + else if (m_init_state == 2 && m_message.size() >= 6) + { + auto cmd = m_message[1]; + auto node_id = m_message[2]; + auto subcmd = m_message[3]; + const int packet_len = m_message[4] ? (1 << (m_message[4] - 1)) + ((m_message[4] & 0xf0) ? 1 : 0) : 0; + + LOG("packet len %02x -> %02x\n", m_message[4], packet_len); + + if (m_message.size() >= packet_len + 6) + { + auto crc = calculate_crc8(m_message.begin() + 1, m_message.begin() + packet_len + 5); + LOG("CRC: %02x vs %02x\n", m_message[packet_len + 5], crc); + + if (crc != m_message[packet_len + 5]) + { + LOG("CRC mismatch!\n"); + + for (int i = 0; i < packet_len + 6; i++) + LOG("%02x ", m_message[i]); + LOG("\n"); + + for (int i = 0; i < packet_len + 6; i++) + m_message.pop_front(); + return; + } + } + else + { + return; + } + + auto resplen = m_response.size(); + LOG("Command: "); + for (int i = 0; i < m_message.size(); i++) + LOG("%02x ", m_message[i]); + LOG("\n"); + + if (cmd == SERIAL_REQ) // host device + { + if (subcmd != CMD_NODE_COUNT && subcmd != CMD_VERSION && subcmd != CMD_EXEC) + { + LOG("Unknown command! %02x %02x\n", cmd, subcmd); + return; + } + + m_response.push_back(HEADER_BYTE); + m_response.push_back(cmd == SERIAL_REQ ? SERIAL_RESP : NODE_RESP); + m_response.push_back(node_id); + m_response.push_back(subcmd); + + const auto payloadLengthIdx = m_response.size(); + m_response.push_back(0); + + if (subcmd != CMD_NODE_COUNT) + { + for (int i = 0; i < 6; i++) + m_response.push_back(m_message[i]); + } + + auto responseIdx = m_response.size() - payloadLengthIdx; + + if (subcmd == CMD_NODE_COUNT) + { + // ref: GF11 80090794 + + m_response.push_back(m_nodecnt); + } + else if (subcmd == CMD_VERSION) + { + // TODO: Have node device generate this directly instead of doing it here + + // ref: GF11 80090988 + auto node = get_node_by_id(node_id); + + if (node != nullptr) + { + auto node_info = node->get_node_info(); + + m_response.push_back(BIT(node_info->type, 0, 8)); + m_response.push_back(BIT(node_info->type, 8, 8)); + m_response.push_back(BIT(node_info->type, 16, 8)); + m_response.push_back(BIT(node_info->type, 24, 8)); + m_response.push_back(node_info->flag); + m_response.push_back(node_info->major); + m_response.push_back(node_info->minor); + m_response.push_back(node_info->revision); + + for (int i = 0; i < std::size(node_info->product_name); i++) + m_response.push_back(node_info->product_name[i]); + } + else + { + // TODO: how to handle empty node properly? + for (int i = 0; i < 16; i++) + m_response.push_back(0); + } + } + else if (subcmd == CMD_EXEC) + { + m_response.push_back(0x00); // Status + } + + for (int i = 0; i < packet_len + 6; i++) + m_message.pop_front(); + + auto size = m_response.size() - (payloadLengthIdx + 1); + int bit = 0; + while ((1 << bit) < (size & ~0x0f)) + bit++; + + m_response[payloadLengthIdx] = (bit + 1) | ((size - (1 << bit)) ? 0x10 : 0); + + m_response.push_back(calculate_crc8(m_response.begin() + responseIdx, m_response.end())); + } + else if (cmd == NODE_REQ) + { + std::deque response, responsepost; + + m_node_device->message(node_id, m_message, response, responsepost); + + if (!response.empty()) + { + for (int i = 0; i < 6 + packet_len; i++) + m_response.push_back(m_message[i]); + + m_response.push_back(HEADER_BYTE); + const auto responseIdx = m_response.size(); + m_response.push_back(cmd == SERIAL_REQ ? SERIAL_RESP : NODE_RESP); + m_response.push_back(node_id); + m_response.push_back(subcmd); + + auto size = response.size(); + int bit = 0; + while ((1 << bit) < (size & ~0x0f)) + bit++; + + m_response.push_back((bit + 1) | ((size - (1 << bit)) ? 0x10 : 0)); + + while (!response.empty()) + { + m_response.push_back(response.front()); + response.pop_front(); + } + + m_response.push_back(calculate_crc8(m_response.begin() + responseIdx, m_response.end())); + + while (!responsepost.empty()) + { + m_response.push_back(responsepost.front()); + responsepost.pop_front(); + } + + for (int i = 0; i < packet_len + 6; i++) + m_message.pop_front(); + + // if (m_response[payloadLengthIdx] > 0x7f) + } + } + + if (m_response.size() > resplen) + { + LOG("Response: "); + for (int i = 0; i < m_response.size(); i++) + LOG("%02x ", m_response[i]); + LOG("\n\n"); + } + } + + m_timer_response->adjust(attotime::from_hz(BAUDRATE)); +} + +k573acio_node_device *k573acio_host_device::get_node_by_id(uint32_t node_id) +{ + k573acio_node_device *node = m_node_device; + + while (node != nullptr) + { + if (node->get_node_id() == node_id) + return node; + + node = node->next_node(); + } + + return nullptr; +} + +uint8_t k573acio_host_device::calculate_crc8(std::deque::iterator start, std::deque::iterator end) +{ + return std::accumulate(start, end, 0) & 0xff; +} + + +//// + + +k573acio_node_device::k573acio_node_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, type, tag, owner, clock) +{ + memset(&m_node_info, 0, sizeof(m_node_info)); + m_next_device = nullptr; +} + +void k573acio_node_device::device_start() +{ +} + +void k573acio_node_device::device_reset() +{ +} + +void k573acio_node_device::chain(k573acio_node_device *dev) +{ + if(m_next_device) + m_next_device->chain(dev); + else + m_next_device = dev; +} + +void k573acio_node_device::message(uint32_t dest, std::deque &message, std::deque &response, std::deque &responsepost) +{ + if (dest == m_node_id) + { + handle_message(message, response, responsepost); + return; + } + + if (m_next_device != nullptr) + m_next_device->message(dest, message, response, responsepost); +} + +void k573acio_node_device::handle_message(std::deque &message, std::deque &response, std::deque &responsepost) +{ + [[maybe_unused]] const auto cmd = message[1]; + [[maybe_unused]] const auto node_id = message[2]; + const auto subcmd = message[3]; + + if (subcmd == NODE_CMD_INIT) + { + response.push_back(0); // Status + } +} diff --git a/src/mame/konami/k573acio.h b/src/mame/konami/k573acio.h new file mode 100644 index 0000000000000..846e2e7e6291b --- /dev/null +++ b/src/mame/konami/k573acio.h @@ -0,0 +1,125 @@ +// license:BSD-3-Clause +// copyright-holders:windyfairy +/* + * Konami 573 Magnetic Card R/W Unit + * + */ +#ifndef MAME_MACHINE_K573ACIO_H +#define MAME_MACHINE_K573ACIO_H + +#pragma once + +#include "diserial.h" +#include "bus/rs232/rs232.h" + +#include +#include + +class k573acio_node_device; + +class k573acio_host_device : public device_t, + public device_serial_interface, + public device_rs232_port_interface +{ +public: + k573acio_host_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + void add_device(k573acio_node_device *dev); + + virtual void input_txd(int state) override { device_serial_interface::rx_w(state); } + +protected: + virtual void device_start() override; + virtual void device_reset() override; + + virtual void tra_callback() override; + virtual void tra_complete() override; + virtual void rcv_complete() override; + + TIMER_CALLBACK_MEMBER(send_response); + TIMER_CALLBACK_MEMBER(send_io_packet); + +private: + static constexpr int TIMER_RESPONSE = 1; + static constexpr int TIMER_IO = 2; + static constexpr int BAUDRATE = 38400; + + const uint8_t HEADER_BYTE = 0xaa; + + enum : uint8_t { + SERIAL_REQ = 0xaa, + SERIAL_RESP = 0xaa, + NODE_REQ = 0x00, + NODE_RESP = 0x01, + }; + + enum : uint8_t { + CMD_INIT = 0x00, + CMD_NODE_COUNT = 0x01, + CMD_VERSION = 0x02, + CMD_EXEC = 0x03, + }; + + k573acio_node_device *get_node_by_id(uint32_t node_id); + + uint8_t calculate_crc8(std::deque::iterator start, std::deque::iterator end); + + emu_timer* m_timer_response; + + std::deque m_message; + std::deque m_response; + + k573acio_node_device *m_node_device; + + uint32_t m_init_state; + uint32_t m_nodecnt; +}; + +DECLARE_DEVICE_TYPE(KONAMI_573_ACIO_HOST, k573acio_host_device) + + +/// + +struct k573acio_node_info +{ + uint32_t type; + uint8_t flag; + uint8_t major; + uint8_t minor; + uint8_t revision; + char product_name[8]; +}; + +class k573acio_node_device : public device_t +{ +public: + void chain(k573acio_node_device *dev); + + uint32_t get_node_id() { return m_node_id; } + k573acio_node_device *next_node() { return m_next_device; } + + const k573acio_node_info* get_node_info() { return &m_node_info; } + + void set_node_id(uint32_t node_id) { m_node_id = node_id; } + + void message(uint32_t dest, std::deque &message, std::deque &response, std::deque &responsepost); + +protected: + k573acio_node_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); + + virtual void device_start() override; + virtual void device_reset() override; + + virtual void handle_message(std::deque &message, std::deque &response, std::deque &responsepost); + + uint32_t m_node_id; + + k573acio_node_device *m_next_device; + k573acio_node_info m_node_info; + + enum : uint8_t { + NODE_CMD_INIT = 0x00, + }; +}; + +#endif // MAME_MACHINE_K573ACIO_H diff --git a/src/mame/konami/k573cardunit.cpp b/src/mame/konami/k573cardunit.cpp new file mode 100644 index 0000000000000..25977633dc5e2 --- /dev/null +++ b/src/mame/konami/k573cardunit.cpp @@ -0,0 +1,231 @@ +// license:BSD-3-Clause +// copyright-holders:windyfairy +/* + * Konami 573 Magnetic Card R/W Unit + * + */ +#include "emu.h" +#include "k573cardunit.h" + +#include "formats/imageutl.h" + +enum : uint8_t { + NODE_CMD_INIT = 0x00, + NODE_CMD_INIT2 = 0x01, // Called after NODE_CMD_INIT in some cases + + NODE_CMD_CARD_INIT = 0x10, + NODE_CMD_CARD_INIT2 = 0x11, // Called after NODE_CMD_CARD_INIT in some cases + NODE_CMD_CARD_GET_STATUS = 0x12, + NODE_CMD_CARD_CTRL = 0x14, + NODE_CMD_CARD_CTRL2 = 0x15, // Called after NODE_CMD_CARD_CTRL in some cases + NODE_CMD_CARD_WRITE = 0x16, + NODE_CMD_CARD_READ = 0x18, + NODE_CMD_CARD_FORMAT = 0x1e, + NODE_CMD_CARD_FORMAT2 = 0x1f, // Called after NODE_CMD_CARD_FORMAT in some cases + + NODE_CMD_KEYBOARD_INIT = 0x20, + // ? = 0x22 + NODE_CMD_KEYBOARD_GET_STATUS = 0x24, + NODE_CMD_KEYBOARD_READ_DATA = 0x26, + NODE_CMD_KEYBOARD_GET_SIZE = 0x27, +}; + +enum : uint8_t { + CARD_SLOT_STATE_CLOSE = 0, + CARD_SLOT_STATE_OPEN = 1, + CARD_SLOT_STATE_EJECT = 2, + CARD_SLOT_STATE_FORMAT = 3, + CARD_SLOT_STATE_READ = 4, + CARD_SLOT_STATE_WRITE = 5, +}; + +DEFINE_DEVICE_TYPE(KONAMI_573_MAGNETIC_CARD_READER, k573cardunit_device, "k573cardunit", "Konami 573 Magnetic Card R/W Unit") + +k573cardunit_device::k573cardunit_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : k573acio_node_device(mconfig, KONAMI_573_MAGNETIC_CARD_READER, tag, owner, clock) + , device_memcard_image_interface(mconfig, *this) + , m_keypad(*this, "KEYPAD") +{ + std::fill(std::begin(m_card_data), std::end(m_card_data), 0); + m_card_inserted = false; + + m_node_info.type = 3; + m_node_info.major = 1; + m_node_info.minor = 6; + memcpy(m_node_info.product_name, "ICCA", 4); +} + +void k573cardunit_device::device_start() +{ + k573acio_node_device::device_start(); +} + +void k573cardunit_device::device_reset() +{ + k573acio_node_device::device_reset(); + + m_last_input = 0; +} + +void k573cardunit_device::device_config_complete() +{ + add_format("mag", "Magnetic Card Image", "bin", ""); +} + +INPUT_CHANGED_MEMBER(k573cardunit_device::card_media_update) +{ + m_card_inserted = m_keypad->read() & 0x1000; +} + +void k573cardunit_device::handle_message(std::deque &message, std::deque &response, std::deque &responsepost) +{ + const auto cmd = message[3]; + const int packet_len = message[4] ? (1 << (message[4] - 1)) + ((message[4] & 0xf0) ? 1 : 0) : 0; + + if (cmd == NODE_CMD_INIT + || cmd == NODE_CMD_CARD_INIT + || cmd == NODE_CMD_CARD_INIT2 + || cmd == NODE_CMD_KEYBOARD_INIT + || cmd == NODE_CMD_KEYBOARD_GET_STATUS + || cmd == NODE_CMD_CARD_FORMAT + || cmd == NODE_CMD_CARD_FORMAT2) + { + response.push_back(0); + } + else if (cmd == NODE_CMD_CARD_GET_STATUS) + { + int state = 0; + + if (m_card_inserted) + { + state |= 2; + state |= 64; // Front sensor + state |= 128; // Back sensor + } + + response.push_back(state); + } + else if (cmd == NODE_CMD_CARD_CTRL || cmd == NODE_CMD_CARD_CTRL2) + { + const auto new_card_slot_state = message[5]; + printf("card slot state: %d\n", new_card_slot_state); + response.push_back(0); + } + else if (cmd == NODE_CMD_CARD_WRITE) + { + std::copy(std::begin(message) + 5, std::begin(message) + std::min(std::size(m_card_data), packet_len), std::begin(m_card_data)); + response.push_back(0); + } + else if (cmd == NODE_CMD_CARD_READ) + { + if (!is_open() || !m_card_inserted) + { + response.push_back(0xff); + } + else + { + response.push_back(0); + + for (int i = 0; i < std::size(m_card_data); i++) + response.push_back(m_card_data[i]); + } + } + else if (cmd == NODE_CMD_KEYBOARD_READ_DATA) + { + const int padlen = message[5] ? (1 << (message[5] - 1)) + ((message[5] & 0xf0) ? 1 : 0) : 0; // is this right? + const auto input = m_keypad->read(); + uint32_t found = 0; + + constexpr uint8_t keypad_vals[] = { + 0x69, // 1 + 0x72, // 2 + 0x7a, // 3 + 0x6b, // 4 + 0x73, // 5 + 0x74, // 6 + 0x6c, // 7 + 0x75, // 8 + 0x7d, // 9 + 0x70, // 0 + 0x70, // 000? + 0x66, // ? + }; + + const auto resplen = response.size(); + + for (int i = 0; i < std::size(keypad_vals) && i < padlen; i++) + { + if (!BIT(found, i) && BIT(input, i)) + { + if (!BIT(m_last_input, i)) + response.push_back(keypad_vals[i]); + found |= 1 << i; + } + } + + if (response.size() == resplen) + response.push_back(0); + + m_last_input = found; + } + else if (cmd == NODE_CMD_KEYBOARD_GET_SIZE) + { + // TODO + response.push_back(0); + } + else + k573acio_node_device::handle_message(message, response, responsepost); +} + +std::pair k573cardunit_device::call_load() +{ + if (is_open()) + { + fread(m_card_data, std::size(m_card_data)); + } + + return std::make_pair(std::error_condition(), std::string()); +} + +std::pair k573cardunit_device::call_create(int format_type, util::option_resolution *format_options) +{ + // TODO: Fill in random card ID here + uint8_t header[] = {0x08, 0x1f, 0x7d, 0xf0, 0x56}; + + std::fill(std::begin(m_card_data), std::end(m_card_data), 0); + std::copy(std::begin(header), std::end(header), std::begin(m_card_data)); + + const auto ret = fwrite(m_card_data, std::size(m_card_data)); + if(ret != std::size(m_card_data)) + return std::make_pair(image_error::UNSPECIFIED, std::string()); + + return std::make_pair(std::error_condition(), std::string()); +} + +void k573cardunit_device::call_unload() +{ + fseek(0, SEEK_SET); + fwrite(m_card_data, std::size(m_card_data)); +} + +INPUT_PORTS_START( k573cardunit_controls ) + PORT_START("KEYPAD") + PORT_BIT( 0x00000001, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_NAME("Keypad 1") + PORT_BIT( 0x00000002, IP_ACTIVE_HIGH, IPT_BUTTON2) PORT_NAME("Keypad 2") + PORT_BIT( 0x00000004, IP_ACTIVE_HIGH, IPT_BUTTON3) PORT_NAME("Keypad 3") + PORT_BIT( 0x00000008, IP_ACTIVE_HIGH, IPT_BUTTON4) PORT_NAME("Keypad 4") + PORT_BIT( 0x00000010, IP_ACTIVE_HIGH, IPT_BUTTON5) PORT_NAME("Keypad 5") + PORT_BIT( 0x00000020, IP_ACTIVE_HIGH, IPT_BUTTON6) PORT_NAME("Keypad 6") + PORT_BIT( 0x00000040, IP_ACTIVE_HIGH, IPT_BUTTON7) PORT_NAME("Keypad 7") + PORT_BIT( 0x00000080, IP_ACTIVE_HIGH, IPT_BUTTON8) PORT_NAME("Keypad 8") + PORT_BIT( 0x00000100, IP_ACTIVE_HIGH, IPT_BUTTON9) PORT_NAME("Keypad 9") + PORT_BIT( 0x00000200, IP_ACTIVE_HIGH, IPT_BUTTON10) PORT_NAME("Keypad 0") + PORT_BIT( 0x00000400, IP_ACTIVE_HIGH, IPT_BUTTON11) PORT_NAME("Keypad 000") + PORT_BIT( 0x00000800, IP_ACTIVE_HIGH, IPT_BUTTON11) PORT_NAME("Keypad Unk") + PORT_BIT( 0x00001000, IP_ACTIVE_HIGH, IPT_BUTTON12) PORT_TOGGLE PORT_NAME("Insert/Eject Card") PORT_CHANGED_MEMBER(DEVICE_SELF, k573cardunit_device, card_media_update, 0) +INPUT_PORTS_END + +ioport_constructor k573cardunit_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(k573cardunit_controls); +} diff --git a/src/mame/konami/k573cardunit.h b/src/mame/konami/k573cardunit.h new file mode 100644 index 0000000000000..b0e601564419f --- /dev/null +++ b/src/mame/konami/k573cardunit.h @@ -0,0 +1,52 @@ +// license:BSD-3-Clause +// copyright-holders:windyfairy +/* + * Konami 573 Magnetic Card R/W Unit + * + */ +#ifndef MAME_MACHINE_K573CARDUNIT_H +#define MAME_MACHINE_K573CARDUNIT_H + +#pragma once + +#include "k573acio.h" + +#include "imagedev/memcard.h" + +class k573cardunit_device : public k573acio_node_device, public device_memcard_image_interface +{ +public: + k573cardunit_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + virtual bool is_reset_on_load() const noexcept override { return false; } + virtual const char *file_extensions() const noexcept override { return "bin"; } + virtual const char *image_type_name() const noexcept override { return "magcard"; } + virtual const char *image_brief_type_name() const noexcept override { return "mag"; } + + virtual std::pair call_load() override; + virtual std::pair call_create(int format_type, util::option_resolution *format_options) override; + virtual void call_unload() override; + + virtual ioport_constructor device_input_ports() const override; + + DECLARE_INPUT_CHANGED_MEMBER(card_media_update); + +protected: + virtual void device_start() override; + virtual void device_reset() override; + virtual void device_config_complete() override; + + virtual void handle_message(std::deque &message, std::deque &response, std::deque &responsepost) override; + +private: + required_ioport m_keypad; + + uint8_t m_card_data[0x80]; + + uint32_t m_last_input; + bool m_card_inserted; +}; + +DECLARE_DEVICE_TYPE(KONAMI_573_MAGNETIC_CARD_READER, k573cardunit_device) + +#endif // MAME_MACHINE_K573CARDUNIT_H diff --git a/src/mame/konami/k573npu_card.cpp b/src/mame/konami/k573npu_card.cpp new file mode 100644 index 0000000000000..56f290020a522 --- /dev/null +++ b/src/mame/konami/k573npu_card.cpp @@ -0,0 +1,2963 @@ +// license:BSD-3-Clause +// copyright-holders:windyfairy +/* +TODO: Clean up sockets and pointers as needed? +TODO: Implement a proper way to lock the output buffer in a thread safe manner +TODO: There's probably a lot of endianness issues, but the code works on x64 and ARM so not a big deal +TODO: Custom DNS would be nice + + +How to prepare CHD HDD image: +chdman createhd --sectorsize 512 --size 10056130560 -o filename.chd + + +How to configure internet settings: +Make a new file named k573npu.ini and put it in the same folder as mame.exe with the following format: + +# 16 character hex string (0-9a-fA-F) +# This value also acts as the PCBID sent to server for machine identification. +# You can scramble your PCBID to match the NPU ID format as shown below (aa, bb, cc, ... groups are 2 hex characters) +# PCBID format: 0140aaggffeeddccbbhh +# NPU ID format: aabbccddeeffgghh +npu_id 1234567890abcdef + +# MAC address of the network PCB unit +mac_address 12:34:56:78:9a:bc + +# IP address of the network PCB unit +ip_address 10.1.1.24 + +# Point to whatever server has the service URL information configured +dns_server1 10.1.1.1 +dns_server2 10.1.1.1 + +# The service URL will become service. +domain_name "" + +# NTP time server, must be able to respond to ICMP pings +# Provided default: pool.ntp.org +ntp_server 162.159.200.123 + +# If you don't know then just copy what is in your network adapter settings (or look at ipconfig/ifconfig output) +subnet_mask 255.255.255.0 +default_gateway 10.1.1.1 +dhcp_server 10.1.1.1 + +*/ +#include "emu.h" + +#include "k573npu_card.h" + +#include "corestr.h" +#include "osdcomm.h" +#include "osdcore.h" +#include "utilfwd.h" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0600 +#endif + +#include +#include +#include + +#ifndef sa_family_t + typedef ADDRESS_FAMILY sa_family_t; +#endif + +#ifndef in_port_t + typedef USHORT in_port_t; +#endif + +#ifndef in_addr_t + typedef ULONG in_addr_t; +#endif +#else +#include +#include +#include +#include +#endif + +#define LOG_REG (1U << 1) +#define LOG_MEM_VERBOSE (1U << 2) +#define LOG_CMD (1U << 3) +#define LOG_PACKET (1U << 4) +#define LOG_NET (1U << 5) +#define LOG_FS (1U << 6) + +// #define VERBOSE (LOG_REG | LOG_CMD | LOG_NET | LOG_FS | LOG_PACKET /*| LOG_MEM_VERBOSE*/) +#define LOG_OUTPUT_FUNC osd_printf_info + +#include "logmacro.h" + +#define LOGREG(...) LOGMASKED(LOG_REG, __VA_ARGS__) +#define LOGMEMVERBOSE(...) LOGMASKED(LOG_MEM_VERBOSE, __VA_ARGS__) +#define LOGCMD(...) LOGMASKED(LOG_CMD, __VA_ARGS__) +#define LOGNET(...) LOGMASKED(LOG_NET, __VA_ARGS__) +#define LOGFS(...) LOGMASKED(LOG_FS, __VA_ARGS__) +#define LOGPACKET(...) LOGMASKED(LOG_PACKET, __VA_ARGS__) + + +enum +{ + // These are just to make it easier to see where things failed. Not used on real hardware + NOT_VALID_FD = -2, + SEEK_OUT_OF_RANGE = -3, +}; + + +konami_573_network_pcb_unit_hdd::konami_573_network_pcb_unit_hdd(harddisk_image_device *hdd) + : m_hdd(hdd) +{ + m_partition = {}; + m_next_fd = 0; + m_filedescs.clear(); +} + +void konami_573_network_pcb_unit_hdd::reset() +{ + if (m_hdd && m_hdd->exists()) + m_hdd->set_block_size(SECTOR_SIZE); + + m_partition = {}; + m_next_fd = 0; + m_mount_size_sectors = 0; + m_filedescs.clear(); + + m_hdd_offset = 0; +} + +void konami_573_network_pcb_unit_hdd::write_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) +{ + if (!m_hdd || !m_hdd->exists()) + return; + + uint8_t sector[SECTOR_SIZE]; + + actual = 0; + + // Handle partial sector first + if ((offset % SECTOR_SIZE) != 0) + { + const auto size = std::min( + SECTOR_SIZE - (offset % SECTOR_SIZE), + length - actual + ); + + std::fill(std::begin(sector), std::end(sector), 0); + + m_hdd->read(offset / SECTOR_SIZE, sector); + memcpy(sector + (offset % SECTOR_SIZE), &((uint8_t*)buffer)[actual], size); + + m_hdd->write(offset / SECTOR_SIZE, sector); + + actual += size; + offset += size; + } + + while (actual < length) + { + const auto size = std::min( + SECTOR_SIZE, + length - actual + ); + + std::fill(std::begin(sector), std::end(sector), 0); + + memcpy(sector, &((uint8_t*)buffer)[actual], size); + + m_hdd->write(offset / SECTOR_SIZE, sector); + + actual += size; + offset += size; + } +} + +void konami_573_network_pcb_unit_hdd::read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) +{ + if (!m_hdd || !m_hdd->exists()) + return; + + uint8_t sector[SECTOR_SIZE]; + + actual = 0; + + while (actual < length) + { + const auto size = std::min(SECTOR_SIZE - (offset % SECTOR_SIZE), length - actual); + m_hdd->read(offset / SECTOR_SIZE, sector); + memcpy(&((uint8_t*)buffer)[actual], §or[offset % SECTOR_SIZE], size); + actual += size; + offset += size; + } +} + +void konami_573_network_pcb_unit_hdd::read_some(void *buffer, std::size_t length, std::size_t &actual) +{ + if (!m_hdd || !m_hdd->exists()) + return; + + uint8_t sector[SECTOR_SIZE]; + + actual = 0; + + while (actual < length) + { + const auto size = std::min(SECTOR_SIZE - (m_hdd_offset % SECTOR_SIZE), length - actual); + m_hdd->read(m_hdd_offset / SECTOR_SIZE, sector); + memcpy(&((uint8_t*)buffer)[actual], §or[m_hdd_offset % SECTOR_SIZE], size); + actual += size; + m_hdd_offset += size; + } +} + +void konami_573_network_pcb_unit_hdd::seek(std::int64_t offset, int whence) +{ + if (whence == SEEK_SET) + m_hdd_offset = offset; + else if (whence == SEEK_CUR) + m_hdd_offset += offset; +} + +bool konami_573_network_pcb_unit_hdd::is_valid_fd(int fd) +{ + return m_filedescs.find(fd) != m_filedescs.end() && m_filedescs[fd].is_open; +} + +uint32_t konami_573_network_pcb_unit_hdd::get_next_fd() +{ + uint32_t fd = m_next_fd; + + while (m_filedescs.find(fd) != m_filedescs.end()) + fd++; + + m_next_fd = fd + 1; + + return fd; +} + +int konami_573_network_pcb_unit_hdd::parse_filesystem() +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + // TODO: This only currently handles one partition chunk, but there can be multiple. + // Never seen it use multiple partitions so not sure how it's handled yet. + uint64_t next_partition = START_SECTOR * SECTOR_SIZE; + size_t bytes_read = 0; + + if (m_partition.blocks != nullptr) + free(m_partition.blocks); + + m_partition.is_loaded = false; + + m_partition.offset = next_partition; + + read_some_at(next_partition, &m_partition.header, sizeof(partition_header_t), bytes_read); + m_partition.header.partitionTotalSectorCount = big_endianize_int32(m_partition.header.partitionTotalSectorCount); + m_partition.header.nodeBlockTableSectorCount = big_endianize_int32(m_partition.header.nodeBlockTableSectorCount); + m_partition.header.dataSectorOffset = big_endianize_int32(m_partition.header.dataSectorOffset); + m_partition.header.unk1 = big_endianize_int32(m_partition.header.unk1); + m_partition.blockSize = (1 << m_partition.header.blockSizeMult) * SECTOR_SIZE; + + if (bytes_read != sizeof(partition_header_t)) + return -1; + + if (memcmp(m_partition.header.magic, "PythonFS", 8) != 0) + return -2; + if (memcmp(m_partition.header.magic2, "\xaf\x86\x8b\x97\x90\x91\xb9\xac", 8) != 0) + return -3; + + const auto nodeBlockTableCount = m_partition.header.nodeBlockTableSectorCount * SECTOR_SIZE / 4; + m_partition.blocks = new uint32_t[nodeBlockTableCount]; + + seek(next_partition + (1 * SECTOR_SIZE), 0); // seek to node blocks table + + for (int i = 0; i < nodeBlockTableCount; i++) + { + uint32_t val = 0; + read_some(&val, sizeof(uint32_t), bytes_read); + + if (bytes_read != sizeof(uint32_t)) + return -4; + + m_partition.blocks[i] = big_endianize_int32(val); + } + + m_partition.is_loaded = true; + + if (chdir("/") < 0) + return -5; + + return 0; +} + +int konami_573_network_pcb_unit_hdd::mount(const char *path) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + int idx = 0; + + while (idx < strlen(path) && path[idx] != ',') + idx++; + while (idx < strlen(path) && !std::isdigit(path[idx])) + idx++; + + const int size = atoi(path + idx); + + while (idx < strlen(path) && !std::isalpha(path[idx])) + idx++; + + const char m = path[idx]; + + uint32_t bytes = 0; + if (m == 'k') + bytes = size << 1; + else if (m == 'M') + bytes = size << 11; + else if (m == 'G') + bytes = size << 21; + + m_mount_size_sectors = bytes; + + return parse_filesystem(); +} + +int konami_573_network_pcb_unit_hdd::umount(const char *path) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + // TODO: Clean up mounted partition state + return 0; +} + +int konami_573_network_pcb_unit_hdd::open(const char *path, uint32_t flags, uint32_t mode) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (path == nullptr) + return -2; + + if (!m_partition.is_loaded) + return -3; + + // chdir to path if needed + char *pathptr = (char*)path; + size_t pathoffs = 0; + + while (strlen(pathptr) > 0 && (pathptr = strstr((char*)pathptr, "/")) != nullptr) + { + pathptr++; + pathoffs = pathptr - path; + } + + std::string curpath = m_partition.directory_path; + if (pathoffs > 0) + { + if (path[0] == '/') + curpath = std::string(path, pathoffs); + else + curpath += std::string(path, pathoffs); + } + + std::vector directory; + if (!curpath.empty()) + { + if (chdir(curpath.c_str(), directory) < 0) + return -4; + } + else + { + directory = m_partition.directory; + } + + // TODO: handle flags and mode + for (auto f : directory) + { + if (strcmp(f.name, path + pathoffs) == 0 && !(flags & 0x200)) // TODO: What's 0x200 and 0x100? + { + const uint32_t fd = get_next_fd(); + + m_filedescs[fd] = file_desc_t{ + .fd = fd, + .is_open = true, + .is_dir = false, + .flags = flags, + .mode = mode, + .curblock = f.offset & 0xffffff, + .block_offset = 0, + .block_suboffset = 0, + .entry = f, + .path = curpath, + .filename = std::string(path + pathoffs), + }; + + return fd; + } + } + + if (flags & FILE_FLAG_WRITE) + { + const uint32_t fd = get_next_fd(); + + std::vector blocks; + if (get_available_block_offsets(SECTOR_SIZE, SEARCH_MODE_ANY, SEARCH_START_NONE, blocks) < 0) + return -1; + + directory_t f = { + .size = 0, + .unk = 0x1dfe200, // ? + .offset = blocks[0] & 0xffffff, + }; + strcpy(f.name, path + pathoffs); + + m_filedescs[fd] = file_desc_t{ + .fd = fd, + .is_open = true, + .is_dir = false, + .flags = flags, + .mode = mode, + .curblock = f.offset & 0xffffff, + .block_offset = 0, + .block_suboffset = 0, + .entry = f, + .path = curpath, + .filename = std::string(path + pathoffs), + }; + + return fd; + } + + return -5; +} + +int konami_573_network_pcb_unit_hdd::close(int fd, bool allow_write) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (!m_partition.is_loaded) + return -1; + + if (!is_valid_fd(fd)) + return 0; + + if (allow_write && m_filedescs[fd].flags & FILE_FLAG_WRITE) + { + if (m_filedescs[fd].path == "") + m_filedescs[fd].path = m_partition.directory_path; + + add_new_entry_to_directory(m_filedescs[fd].path.c_str(), m_filedescs[fd].entry); + } + + // TODO: Clean up state here in case anything is referencing it + m_filedescs[fd].is_open = false; + + m_filedescs.erase(fd); + + return 0; +} + +int konami_573_network_pcb_unit_hdd::lseek(int fd, uint32_t offset, int whence) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (!m_partition.is_loaded) + return -5; + + if (!is_valid_fd(fd)) + return NOT_VALID_FD; + + auto &filedesc = m_filedescs[fd]; + + if (whence == SEEK_SET) + { + if (offset > filedesc.entry.size) + return SEEK_OUT_OF_RANGE; + + filedesc.block_offset = offset; + } + else if (whence == SEEK_CUR) + { + if (offset + filedesc.block_offset > filedesc.entry.size) + return SEEK_OUT_OF_RANGE; + + filedesc.block_offset += offset; + } + else if (whence == SEEK_END) + { + // TODO: This probably has an off by 1 issue, untested + if (offset > filedesc.entry.size) + return SEEK_OUT_OF_RANGE; + + filedesc.block_offset = filedesc.entry.size - offset; + } + + filedesc.block_suboffset = filedesc.block_offset % m_partition.blockSize; + + // TODO: + uint32_t skiplen = 0; + filedesc.curblock = filedesc.entry.offset & 0xffffff; + while (skiplen + m_partition.blockSize <= filedesc.block_offset) + { + skiplen += m_partition.blockSize; + + if (m_partition.blocks[filedesc.curblock] == 0xffffff || m_partition.blocks[filedesc.curblock] == 0) + { + if (skiplen >= filedesc.block_offset) + break; + + return -4; + } + + filedesc.curblock = m_partition.blocks[filedesc.curblock]; + } + + return filedesc.block_offset; +} + +int konami_573_network_pcb_unit_hdd::chdir(const char *path) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (path[0] == '/') + { + m_partition.directory_path = path; + } + else + { + if (m_partition.directory_path.back() != '/') + m_partition.directory_path += "/"; + m_partition.directory_path += path; + } + + return chdir(m_partition.directory_path.c_str(), m_partition.directory); +} + +int konami_573_network_pcb_unit_hdd::chdir(const char *path, std::vector &output) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (!m_partition.is_loaded) + return -2; + + std::vector parts; + + if (path == nullptr) + return 0; + + if (strcmp(path, "/") == 0) + { + parts.push_back("/"); + } + else + { + int idx = 0; + + // handle / at beginning of path + if (path[idx] == '/') + { + parts.push_back("/"); + while(idx < strlen(path) && path[idx] == '/') + idx++; + } + + int lastidx = idx; + while (idx < strlen(path)) + { + if (path[idx] == '/') + { + if (idx == lastidx) + break; + + parts.push_back(std::string(path + lastidx, idx - lastidx)); + + while(idx < strlen(path) && path[idx] == '/') + idx++; + + lastidx = idx; + } + else + { + idx++; + } + } + + if (idx - lastidx > 0 && lastidx < strlen(path)) + parts.push_back(std::string(path + lastidx, idx - lastidx)); + } + + auto directory_block_sector = 1; + for (auto s : parts) + { + if (s == "/") + { + directory_block_sector = 1; + } + else + { + bool found = false; + + // Find path in current directory list + for (auto f : output) + { + if (strcmp(f.name, s.c_str()) == 0) + { + found = true; + if ((f.offset & ATTRIB_IS_FOLDER) != ATTRIB_IS_FOLDER) + return -3; // can't chdir into a file + else + directory_block_sector = f.offset & 0xffffff; + break; + } + } + + if (!found) + return -4; + } + + output.clear(); + + while (true) + { + for (int i = 0; i < m_partition.blockSize / sizeof(directory_t); i++) + { + directory_t dir = {}; + size_t bytes_read = 0; + + read_some_at( + get_raw_offset_from_block_offset(directory_block_sector) + (i * sizeof(directory_t)), + &dir, + sizeof(directory_t), + bytes_read + ); + + if (bytes_read != sizeof(directory_t) || strlen(dir.name) == 0) + break; + + dir.offset = big_endianize_int32(dir.offset); + dir.size = big_endianize_int32(dir.size); + dir.unk = big_endianize_int32(dir.unk); + + // printf("Entry #%d:\n", i); + // printf("\tName: %s\n", dir.name); + // printf("\tOffset: %08x\n", dir.offset); + // printf("\tSize: %08x\n", dir.size); + // printf("\tAttrib: %02x\n", BIT(dir.offset, 24, 8)); + // printf("\tUnk: %08x\n", dir.unk); + + output.push_back(dir); + } + + if (m_partition.blocks[directory_block_sector] == 0xffffff || m_partition.blocks[directory_block_sector] == 0) + break; + + directory_block_sector = m_partition.blocks[directory_block_sector]; + } + + if (output.size() < 2) + return -5; // must have at least . and .. as entries + } + + return 0; +} + +int konami_573_network_pcb_unit_hdd::read(int fd, uint32_t len, uint8_t *outbuf, uint32_t &read_count) +{ + read_count = 0; + + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (!m_partition.is_loaded) + return -1; + + if (!is_valid_fd(fd)) + return NOT_VALID_FD; + + auto &filedesc = m_filedescs[fd]; + + if ((filedesc.flags & 0x01) == 0 || filedesc.entry.size - filedesc.block_offset <= 0) + return 0; + + while (read_count < len) + { + if (lseek(fd, filedesc.block_offset, 0) < 0) + break; + + size_t bytes_read = 0; + const auto readlen = std::min( + std::min(len - read_count, m_partition.blockSize - filedesc.block_suboffset), + filedesc.entry.size - filedesc.block_offset + ); + + if (readlen <= 0) + break; + + const auto readoffs = get_raw_offset_from_block_offset(filedesc.curblock) + filedesc.block_suboffset; + read_some_at( + readoffs, + outbuf + read_count, + readlen, + bytes_read + ); + + read_count += bytes_read; + filedesc.block_offset += bytes_read; + } + + return read_count; +} + +int konami_573_network_pcb_unit_hdd::write(int fd, uint32_t len, uint8_t *data) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (!m_partition.is_loaded) + return -1; + + if (!is_valid_fd(fd)) + return NOT_VALID_FD; + + auto &filedesc = m_filedescs[fd]; + + if ((filedesc.flags & FILE_FLAG_WRITE) == 0) + return -1; + + size_t written_len = 0; + uint8_t *block = (uint8_t*)calloc(m_partition.blockSize, sizeof(uint8_t)); + + // Write remainder of current sector + if ((filedesc.block_offset % m_partition.blockSize) > 0) + { + auto write_len = std::min( + m_partition.blockSize - (filedesc.block_offset % m_partition.blockSize), + len + ); + + const auto offs = get_raw_offset_from_block_offset(filedesc.curblock) + filedesc.block_suboffset; + + size_t written = 0; + write_some_at(offs, data, write_len, written); + + len -= written; + written_len += written; + filedesc.block_suboffset += written; + filedesc.block_offset += written; + + if (filedesc.block_suboffset >= m_partition.blockSize) + { + filedesc.block_suboffset %= m_partition.blockSize; + filedesc.curblock = m_partition.blocks[filedesc.curblock]; + } + } + + // Write out the rest of the data as full blocks, ignoring anything that might already be in the sectors already + if (len > 0) + { + // Find as many new blocks required to hold the requested data size + std::vector blockList; + // TODO: Why does this break when specifying the first block? + if (get_available_block_offsets(len, SEARCH_MODE_ANY, SEARCH_START_NONE, blockList) < 0) + return -1; + + if (filedesc.curblock == BLOCK_END || filedesc.curblock == 0) + { + filedesc.curblock = filedesc.entry.offset & 0xffffff; + while (m_partition.blocks[filedesc.curblock] != BLOCK_END && m_partition.blocks[filedesc.curblock] != 0) + filedesc.curblock = m_partition.blocks[filedesc.curblock]; + } + // Update blocks table + if (blockList.size() > 0) + { + uint32_t offs = filedesc.curblock; + for (int i = 0; i < blockList.size(); i++) + { + m_partition.blocks[offs] = blockList[i]; + offs = blockList[i]; + } + + m_partition.blocks[offs] = BLOCK_END; + filedesc.curblock = blockList[0]; + } + + while (len > 0) + { + const auto write_len = std::min( + m_partition.blockSize, + len + ); + + std::fill_n(block, m_partition.blockSize, 0); + memcpy(block, data + written_len, write_len); + + const auto offs = get_raw_offset_from_block_offset(filedesc.curblock); + + size_t written = 0; + write_some_at(offs, block, write_len, written); + + len -= written; + written_len += written; + filedesc.block_suboffset += written; + filedesc.block_offset += written; + + if (filedesc.block_suboffset >= m_partition.blockSize) + { + filedesc.block_suboffset %= m_partition.blockSize; + filedesc.curblock = m_partition.blocks[filedesc.curblock]; + } + } + } + + filedesc.entry.size += written_len; + + if (block != nullptr) + free(block); + + return written_len; +} + +int konami_573_network_pcb_unit_hdd::dopen(const char *path) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (!m_partition.is_loaded) + return -1; + + const uint32_t fd = get_next_fd(); + + m_filedescs[fd] = file_desc_t{ + .fd = fd, + .is_open = true, + .is_dir = true, + .block_offset = 0, + .directory = m_partition.directory, + }; + + if (chdir(path, m_filedescs[fd].directory) < 0) + { + m_filedescs.erase(fd); + return -1; + } + + return fd; +} + +int konami_573_network_pcb_unit_hdd::dclose(int fd) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + return close(fd, false); +} + +int konami_573_network_pcb_unit_hdd::dread(int fd, uint8_t *outbuf) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (!m_partition.is_loaded) + return -1; + + if (!is_valid_fd(fd)) + return NOT_VALID_FD; + + auto &filedesc = m_filedescs[fd]; + + if (filedesc.block_offset >= filedesc.directory.size()) + return -1; + + dirent_t *out = (dirent_t*)outbuf; + memset(out, 0, sizeof(dirent_t)); + + if ((filedesc.directory[filedesc.block_offset].offset & ATTRIB_IS_FOLDER) == ATTRIB_IS_FOLDER) + { + directory_t dir = {}; + size_t bytes_read; + + auto directory_block_sector = filedesc.directory[filedesc.block_offset].offset & 0xffffff; + + while (true) + { + for (int i = 0; i < m_partition.blockSize / sizeof(directory_t); i++) + { + read_some_at( + get_raw_offset_from_block_offset(directory_block_sector) + (i * sizeof(directory_t)), + &dir, + sizeof(directory_t), + bytes_read + ); + + if (bytes_read != sizeof(directory_t) || strlen(dir.name) == 0) + break; + + if (strcmp(dir.name, ".") == 0) + { + filedesc.directory[filedesc.block_offset].size = big_endianize_int32(dir.size); + break; + } + } + + directory_block_sector = m_partition.blocks[directory_block_sector]; + if (directory_block_sector == BLOCK_END || directory_block_sector == 0) + break; + } + } + + strcpy(out->name, filedesc.directory[filedesc.block_offset].name); + out->stat.size = filedesc.directory[filedesc.block_offset].size; + + filedesc.block_offset++; + + return 0; +} + +int konami_573_network_pcb_unit_hdd::getstat(const char *path, uint8_t *outbuf) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (!m_partition.is_loaded) + return -1; + + stat_t *out = (stat_t*)outbuf; + memset(out, 0, sizeof(stat_t)); + + int fd = open(path, 0, 0); + + if (!is_valid_fd(fd)) + return NOT_VALID_FD; + + auto &filedesc = m_filedescs[fd]; + out->size = filedesc.entry.size; + out->attr = BIT(filedesc.entry.offset, 24, 8); + + return 0; +} + +int konami_573_network_pcb_unit_hdd::devctl(uint32_t reqtype, uint32_t param2, uint32_t resplen, uint8_t *data) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (!m_partition.is_loaded) + return -1; + + if (reqtype == 2) + { + // Disk/partition information? + uint32_t *output = (uint32_t*)data; + + output[0] = m_partition.header.partitionTotalSectorCount; + output[1] = m_partition.header.partitionCount1; + output[2] = m_partition.header.partitionCount2; + output[3] = m_partition.header.nodeBlockTableSectorCount >> 5; + } + else + { + LOGFS("devctl unknwon type: %02x %04x %04x\n", reqtype, param2, resplen); + return -1; + } + + return 0; +} + +int konami_573_network_pcb_unit_hdd::format(uint32_t startLba, uint32_t partitionCount1, uint32_t partitionCount2, uint32_t param4, uint32_t param5) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + uint8_t blockSizeMult = 0; + for (int i = 0; (param5 >> i) > 0x200; i++) + blockSizeMult++; + blockSizeMult &= 0x1f; + + const uint32_t sectorsPerPartition = (m_mount_size_sectors - startLba) / (partitionCount1 + partitionCount2); + const uint32_t nodeBlockTableSectorCount = ((sectorsPerPartition >> blockSizeMult) * 4 + 0x1ff) >> 9; + const uint32_t dataSectorOffset = nodeBlockTableSectorCount * param4 + 1; + const uint32_t unk1 = ((sectorsPerPartition - dataSectorOffset) >> blockSizeMult) + - ((((((nodeBlockTableSectorCount + 7) >> 3) + 0x202) >> 9) + (1 << blockSizeMult) - 1) >> blockSizeMult); + + uint8_t headerSector[SECTOR_SIZE]; + std::fill(std::begin(headerSector), std::end(headerSector), 0); + + partition_header_t *header = (partition_header_t*)headerSector; + std::fill(std::begin(header->padding), std::end(header->padding), 0); + memcpy(header->magic, "PythonFS", 8); + memcpy(header->magic2, "\xaf\x86\x8b\x97\x90\x91\xb9\xac", 8); + header->partitionCount1 = uint8_t(partitionCount1); + header->partitionCount2 = uint8_t(partitionCount2); + header->blockSizeMult = blockSizeMult; + header->nodeBlockTableCount = uint8_t(param4); + header->partitionTotalSectorCount = big_endianize_int32(sectorsPerPartition); + header->nodeBlockTableSectorCount = big_endianize_int32(nodeBlockTableSectorCount); + header->dataSectorOffset = big_endianize_int32(dataSectorOffset); + header->unk1 = big_endianize_int32(unk1); + + const auto blocksCount = nodeBlockTableSectorCount * SECTOR_SIZE / 4; + uint32_t *blocks = (uint32_t*)calloc(blocksCount, sizeof(uint32_t)); + std::fill_n(blocks, blocksCount, 0); + blocks[0] = 0; + blocks[1] = big_endianize_int32(0xffffff); // for the first directory + + uint8_t directorySector[SECTOR_SIZE]; + std::fill(std::begin(directorySector), std::end(directorySector), 0); + + directory_t *directory = (directory_t*)directorySector; + strcpy(directory->name, "."); + directory->size = big_endianize_int32(sizeof(directory_t) * 2); + directory->unk = big_endianize_int32(0); + directory->offset = big_endianize_int32(1 | ATTRIB_IS_FOLDER); + + directory++; + strcpy(directory->name, ".."); + directory->size = 0; + directory->unk = big_endianize_int32(0); + directory->offset = big_endianize_int32(1 | ATTRIB_IS_FOLDER); + + uint32_t curLba = startLba; + for (int i = 0; i < partitionCount1 + partitionCount2; i++) + { + m_hdd->write(curLba, headerSector); + + for (int j = 0; j < nodeBlockTableSectorCount; j++) + m_hdd->write(curLba + 1 + j, &blocks[j * (SECTOR_SIZE / 4)]); + + m_hdd->write(curLba + dataSectorOffset, directorySector); + + curLba += sectorsPerPartition; + } + + parse_filesystem(); + + return 0; +} + +int konami_573_network_pcb_unit_hdd::get_available_block_offsets(size_t size, int mode, uint32_t start, std::vector &blockList) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + const auto blocks = (size / m_partition.blockSize) + ((size % m_partition.blockSize) > 0 ? 1 : 0); + const auto maxCount = m_partition.header.nodeBlockTableSectorCount * SECTOR_SIZE / 4; + + blockList.clear(); + + const bool is_valid_start_addr = start != SEARCH_START_NONE && start != 0 && start != BLOCK_END; + + if (is_valid_start_addr) + blockList.push_back(start); + + int idx = is_valid_start_addr ? start : 1; + while (idx < maxCount && blockList.size() < blocks) + { + int free = 0; + + if (is_valid_start_addr && idx == start && m_partition.blocks[idx] == BLOCK_END) + { + free++; + idx++; + } + + if (mode == SEARCH_MODE_CONTIGUOUS || mode == SEARCH_MODE_ANY) + { + for (int j = idx; j < maxCount; j++) + { + if (m_partition.blocks[j] != 0) + break; + + free++; + } + + if (free >= blocks) + { + blockList.clear(); + + for (int block = 0; block < blocks; block++) + blockList.push_back(idx + block); + + break; + } + } + + if (mode == SEARCH_MODE_ANY) + { + if (m_partition.blocks[idx] == 0) + blockList.push_back(idx); + } + + idx++; + } + + return blockList.size() > 0 ? 0 : -1; +} + +uint32_t konami_573_network_pcb_unit_hdd::get_raw_offset_from_block_offset(uint32_t offset) +{ + return m_partition.offset + (m_partition.header.dataSectorOffset * SECTOR_SIZE) + ((offset - 1) * m_partition.blockSize); +} + +uint32_t konami_573_network_pcb_unit_hdd::get_lba_from_block_offset(uint32_t offset) +{ + return get_raw_offset_from_block_offset(offset) / SECTOR_SIZE; +} + +uint32_t konami_573_network_pcb_unit_hdd::get_lba_from_file_offset(uint32_t block_offset, uint32_t offset) +{ + while (m_partition.blocks[block_offset] != BLOCK_END) + block_offset = m_partition.blocks[block_offset]; + + return (get_raw_offset_from_block_offset(m_partition.blocks[block_offset]) + offset) / SECTOR_SIZE; +} + +int konami_573_network_pcb_unit_hdd::write_directories(std::vector dir, std::vector &blockList) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + if (blockList.size() == 0) + return dir.size() > 0 ? -1 : 0; + + // Deallocate any blocks that may not need allocating anymore + uint32_t offs = blockList[0]; + while (offs != BLOCK_END && offs != 0) + { + auto cur = offs; + offs = m_partition.blocks[offs]; + m_partition.blocks[cur] = 0; + } + + // Update blocks table + offs = blockList[0]; + for (int i = 1; i < blockList.size(); i++) + { + m_partition.blocks[offs] = blockList[i]; + offs = blockList[i]; + } + m_partition.blocks[offs] = BLOCK_END; + + int parsed_idx = 0; + for (auto offset : blockList) + { + for (int j = 0; j < m_partition.blockSize / SECTOR_SIZE; j++) + { + uint8_t sector[SECTOR_SIZE]; + directory_t *output = (directory_t*)sector; + + std::fill(std::begin(sector), std::end(sector), 0); + + for (int i = 0; i < SECTOR_SIZE / sizeof(directory_t) && parsed_idx < dir.size(); i++) + { + strcpy(output->name, dir[parsed_idx].name); + output->size = big_endianize_int32(dir[parsed_idx].size); + output->unk = big_endianize_int32(dir[parsed_idx].unk); + output->offset = big_endianize_int32(dir[parsed_idx].offset); + + output++; + parsed_idx++; + } + + m_hdd->write(get_lba_from_block_offset(offset) + j, sector); + } + } + + return 0; +} + +int konami_573_network_pcb_unit_hdd::add_new_entry_to_directory(const char *path, directory_t &entry) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + std::vector curdir; + if (chdir(path, curdir) < 0) + return -1; + + directory_t thisdir; + bool found = false; + for (auto &c : curdir) + { + if (strcmp(c.name, ".") == 0) + { + thisdir = c; + found = true; + break; + } + } + + if (!found || strcmp(thisdir.name, ".") != 0) + return -1; + + found = false; + for (int i = 0; i < curdir.size(); i++) + { + if (strcmp(curdir[i].name, entry.name) == 0) + { + curdir[i] = entry; + found = true; + break; + } + } + + if (!found) + curdir.push_back(entry); + + for (auto &c : curdir) + { + if (strcmp(c.name, ".") == 0) + { + c.size = curdir.size() * sizeof(directory_t); + break; + } + } + + // Can fit into this block? + const auto cursize = curdir.size() * sizeof(directory_t); + if (cursize + sizeof(directory_t) >= m_partition.blockSize) + { + // TODO: Write out the new blocks + fatalerror("TODO: Write out the new blocks\n"); + } + else + { + std::vector curdirBlocks; + + uint32_t curoffs = thisdir.offset & 0xffffff; + uint32_t lastoffs = 0xffffffff; + while (true) + { + if (curoffs == BLOCK_END || curoffs == lastoffs) + break; + + curdirBlocks.push_back(curoffs); + lastoffs = curoffs; + curoffs = m_partition.blocks[curoffs]; + } + + write_directories(curdir, curdirBlocks); + } + + write_block_table(); + + return 0; +} + +int konami_573_network_pcb_unit_hdd::remove_entry_from_directory(const char *path, const char *name) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + std::vector curdir; + if (chdir(path, curdir) < 0) + return -1; + + directory_t thisdir; + bool found = false; + for (auto &c : curdir) + { + if (strcmp(c.name, ".") == 0) + { + thisdir = c; + found = true; + break; + } + } + + if (!found || strcmp(thisdir.name, ".") != 0) + return -1; + + found = false; + for (int i = 0; i < curdir.size(); i++) + { + if (strcmp(curdir[i].name, name) == 0) + { + curdir.erase(curdir.begin() + i); + found = true; + break; + } + } + + if (!found) + return -1; + + for (auto &c : curdir) + { + if (strcmp(c.name, ".") == 0) + { + c.size = curdir.size() * sizeof(directory_t); + break; + } +} + std::vector curdirBlocks; + uint32_t curoffs = thisdir.offset & 0xffffff; + uint32_t lastoffs = 0xffffffff; + while (true) + { + if (curoffs == BLOCK_END || curoffs == lastoffs) + break; + + curdirBlocks.push_back(curoffs); + lastoffs = curoffs; + curoffs = m_partition.blocks[curoffs]; + } + + write_directories(curdir, curdirBlocks); + + return 0; +} + +int konami_573_network_pcb_unit_hdd::mkdir_internal(const char *path, const char *folder) +{ + if (!m_hdd || !m_hdd->exists()) + return -10; + + std::vector curdir; + if (chdir(path, curdir) < 0) + return -11; + + // Don't make a folder again if it already exists + for (auto &c : curdir) + { + if (strcmp(c.name, folder) == 0) + return 0; + } + + directory_t thisdir; + bool found = false; + for (auto &c : curdir) + { + if (strcmp(c.name, ".") == 0) + { + thisdir = c; + found = true; + break; + } + } + + if (!found || strcmp(thisdir.name, ".") != 0) + return -12; + + // Find offset to place new folder entry at + std::vector availableBlocksList; + if (get_available_block_offsets(sizeof(directory_t) * 2, SEARCH_MODE_ANY, SEARCH_START_NONE, availableBlocksList) < 0) + return -13; + + std::vector newdir; + newdir.push_back(directory_t{ + .name = ".", + .size = sizeof(directory_t) * 2, + .unk = 0, + .offset = uint32_t(availableBlocksList[0] & 0xffffff) | ATTRIB_IS_FOLDER, + }); + newdir.push_back(directory_t{ + .name = "..", + .size = 0, + .unk = 0, + .offset = (thisdir.offset & 0xffffff) | ATTRIB_IS_FOLDER, + }); + + write_directories(newdir, availableBlocksList); + + auto newdirentry = directory_t{ + .size = 0, + .unk = 0x1dfe200, + .offset = uint32_t(availableBlocksList[0] & 0xffffff) | ATTRIB_IS_FOLDER, + }; + strcpy(newdirentry.name, folder); + add_new_entry_to_directory(path, newdirentry); + + return 0; +} + +void konami_573_network_pcb_unit_hdd::write_block_table() +{ + if (!m_hdd || !m_hdd->exists()) + return; + + uint8_t sector[SECTOR_SIZE]; + uint32_t block_idx = 0; + + for (int i = 0; i < m_partition.header.nodeBlockTableSectorCount; i++) + { + std::fill(std::begin(sector), std::end(sector), 0); + + uint32_t *output = (uint32_t*)sector; + + for (int j = 0; j < SECTOR_SIZE / 4; j++) + output[j] = big_endianize_int32(m_partition.blocks[block_idx++]); + + m_hdd->write(m_partition.offset / SECTOR_SIZE + (1 + i), sector); + } +} + +int konami_573_network_pcb_unit_hdd::mkdir(const char *path, int mode) +{ + if (!m_hdd || !m_hdd->exists()) + return -21; + + // check if path already exists + if (chdir(path) >= 0) + return 0; + + int pathoffs = 0; + + if (path[0] == '/') + { + chdir("/"); + while(pathoffs < strlen(path) && path[pathoffs] == '/') + pathoffs++; + } + + // make all folders in path as needed + int lastpathoffs = pathoffs; + while (pathoffs < strlen(path)) + { + while(pathoffs < strlen(path) && path[pathoffs] != '/') + pathoffs++; + + if (pathoffs == lastpathoffs) + break; + + auto folder = std::string(path, pathoffs); + std::vector ignore; + if (chdir(folder.c_str(), ignore) < 0) + { + int r; + + // make new directory + if ((r = mkdir_internal(std::string(path, lastpathoffs).c_str(), std::string(path + lastpathoffs, pathoffs - lastpathoffs).c_str())) < 0) + return r; + } + + // If we still can't chdir into the directory then something is broken + ignore.clear(); + if (chdir(folder.c_str(), ignore) < 0) + return -22; + + while(pathoffs < strlen(path) && path[pathoffs] == '/') + pathoffs++; + + lastpathoffs = pathoffs; + } + + return 0; +} + +int konami_573_network_pcb_unit_hdd::rmdir(const char *path) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + std::vector entries; + if (chdir(path, entries) < 0) + return -1; + + bool has_files = false; + for (auto c : entries) + { + if (strcmp(c.name, "..") != 0 && strcmp(c.name, ".") != 0) + { + has_files = true; + break; + } + } + + if (has_files) + return -1; + + // Get upper path + char *pathptr = (char*)path; + size_t pathoffs = 0; + + while (strlen(pathptr) > 0 && (pathptr = strstr((char*)pathptr, "/")) != nullptr) + { + pathptr++; + pathoffs = pathptr - path; + } + + std::string curpath = m_partition.directory_path; + if (pathoffs > 0) + { + if (path[0] == '/') + curpath = std::string(path, pathoffs); + else + curpath += std::string(path, pathoffs); + } + + return remove_entry_from_directory(curpath.c_str(), path + pathoffs); +} + +int konami_573_network_pcb_unit_hdd::remove(const char *path) +{ + if (!m_hdd || !m_hdd->exists()) + return -1; + + // chdir to path if needed + char *pathptr = (char*)path; + size_t pathoffs = 0; + + while (strlen(pathptr) > 0 && (pathptr = strstr((char*)pathptr, "/")) != nullptr) + { + pathptr++; + pathoffs = pathptr - path; + } + + std::string curpath = m_partition.directory_path; + if (pathoffs > 0) + { + if (path[0] == '/') + curpath = std::string(path, pathoffs); + else + curpath += std::string(path, pathoffs); + } + + // Can't remove these? + if (strcmp(path + pathoffs, "..") == 0 || strcmp(path + pathoffs, ".") == 0) + return 0; + + std::vector entries; + if (chdir(curpath.c_str(), entries) < 0) + return -1; + + for (auto c : entries) + { + if (strcmp(c.name, path + pathoffs) == 0) + { + // Deallocate blocks used by file + auto offs = c.offset & 0xffffff; + while (offs != BLOCK_END && offs != 0) + { + auto cur = offs; + offs = m_partition.blocks[cur]; + m_partition.blocks[cur] = 0; + } + + write_block_table(); + + // TODO: Clean up sectors to make HDD image compress better? + break; + } + } + + return remove_entry_from_directory(curpath.c_str(), path + pathoffs); +} + +//////////////////////////////// + + +enum { + // Used by 0x3a opcode + REQVAL_ADDR_SUBNET_MASK = 0x01, + REQVAL_ADDR_DEFAULT_GATEWAY = 0x03, + REQVAL_ADDR_DNS_SERVERS = 0x06, + REQVAL_ADDR_DOMAIN_NAME = 0x0f, + REQVAL_ADDR_NTP_SERVER = 0x2a, + REQVAL_ADDR_DHCP_SERVER = 0x36, + REQVAL_ADDR_IP_ADDR = 0x101, +}; + +enum { + // System opcodes: + // 0x00 + // 0x01 + OPCODE_WRITE_MEMORY = 0x80, + OPCODE_READ_MEMORY = 0x81, + OPCODE_EXECUTE_MEMORY = 0x82, + // 0x83 + + // Network opcodes: + // OPCODE_SOCKET_ACCEPT = 0x20, + // OPCODE_SOCKET_BIND = 0x21, + OPCODE_SOCKET_CLOSE = 0x22, + OPCODE_SOCKET_CONNECT = 0x23, + // OPCODE_SOCKET_GETPEERNAME = 0x24, + // OPCODE_SOCKET_GETSOCKNAME = 0x25, + // OPCODE_SOCKET_IOCTLSOCKET = 0x26, + // OPCODE_SOCKET_LISTEN = 0x27, + OPCODE_SOCKET_RECV = 0x28, + OPCODE_SOCKET_RECVFROM = 0x29, + OPCODE_SOCKET_SEND = 0x2a, + OPCODE_SOCKET_SENDTO = 0x2b, + OPCODE_SOCKET_SETSOCKOPT = 0x2c, + // OPCODE_SOCKET_SHUTDOWN = 0x2d, + OPCODE_SOCKET_SOCKET = 0x2e, + // OPCODE_NET_GET_IP_ADDR = 0x2f, + // OPCODE_NET_SET_IP_ADDR = 0x30, + // OPCODE_NET_GET_SUBNET = 0x31, + // OPCODE_NET_SET_SUBNET = 0x32, + // OPCODE_NET_GET_DEFAULT_GATEWAY = 0x33, + // OPCODE_NET_SET_DEFAULT_GATEWAY = 0x34, + // OPCODE_NET_ = 0x35, // Somewhere around here is probably DNS server(s) and NTP server set/get + // OPCODE_NET_ = 0x36, + // OPCODE_NET_ = 0x37, + // OPCODE_NET_ = 0x38, + // OPCODE_NET_ = 0x39, + OPCODE_NET_GET_SETTINGS = 0x3a, // naming? + + // Filesystem opcodes: + // Seems to be loosely based on the PS2's PFS, maybe Sony was involved in some way, or it was backported to Sys573 NPU while working on Python games? + OPCODE_FILESYSTEM_INITIALIZE = 0x40, + OPCODE_FILESYSTEM_OPEN = 0x41, + OPCODE_FILESYSTEM_CLOSE = 0x42, + OPCODE_FILESYSTEM_READ = 0x43, + OPCODE_FILESYSTEM_WRITE = 0x44, + OPCODE_FILESYSTEM_LSEEK = 0x45, + OPCODE_FILESYSTEM_IOCTL = 0x46, + OPCODE_FILESYSTEM_DOPEN = 0x47, + OPCODE_FILESYSTEM_DCLOSE = 0x48, + OPCODE_FILESYSTEM_DREAD = 0x49, + OPCODE_FILESYSTEM_REMOVE = 0x4a, + OPCODE_FILESYSTEM_MKDIR = 0x4b, + OPCODE_FILESYSTEM_RMDIR = 0x4c, + OPCODE_FILESYSTEM_GETSTAT = 0x4d, + OPCODE_FILESYSTEM_CHSTAT = 0x4e, + OPCODE_FILESYSTEM_RENAME = 0x4f, + OPCODE_FILESYSTEM_CHDIR = 0x50, + OPCODE_FILESYSTEM_MOUNT = 0x51, + OPCODE_FILESYSTEM_UMOUNT = 0x52, + OPCODE_FILESYSTEM_DEVCTL = 0x53, + OPCODE_FILESYSTEM_FORMAT = 0x54, +}; + +enum { + REG_REQUESTED_STATE = 0x00, + REG_CURRENT_STATE = 0x02, + REG_FIFO_READ = 0x20, + REG_FIFO_WRITE = 0x20, + REG_FIFO_READ_AVAIL_SIZE = 0x26, + REG_FIFO_WRITE_AVAIL_SIZE = 0x2c, + REG_FIFO_READ_OFFSET = 0x34, + REG_FIFO_WRITE_OFFSET = 0x36, + REG_FPGA_CHECK_RUNNING = 0x3a, +}; + + +DEFINE_DEVICE_TYPE(KONAMI_573_NETWORK_PCB_UNIT_PCCARD, konami_573_network_pcb_unit_pccard_device, "k573npu_card", "Konami Network PCB Unit PC Card") + +konami_573_network_pcb_unit_pccard_device::konami_573_network_pcb_unit_pccard_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : + device_t(mconfig, KONAMI_573_NETWORK_PCB_UNIT_PCCARD, tag, owner, clock), + device_pccard_interface(mconfig, *this), + m_npu_hdd(*this, "npu_hdd") +{ +} + +void konami_573_network_pcb_unit_pccard_device::device_start() +{ +#ifdef _WIN32 + WSADATA wsa_data; + WSAStartup(MAKEWORD(1,1), &wsa_data); +#endif + + m_thread = std::thread([this] { + asio::io_service::work work(m_io_service); + m_io_service.run(); + }); + + std::fill(std::begin(config.npu_id), std::end(config.npu_id), 0); + std::fill(std::begin(config.mac_address), std::end(config.mac_address), 0); + std::fill(std::begin(config.ip_address), std::end(config.ip_address), 0); + std::fill(std::begin(config.subnet_mask), std::end(config.subnet_mask), 0); + std::fill(std::begin(config.default_gateway), std::end(config.default_gateway), 0); + std::fill(std::begin(config.dns_server1), std::end(config.dns_server1), 0); + std::fill(std::begin(config.dns_server2), std::end(config.dns_server2), 0); + std::fill(std::begin(config.dhcp_server), std::end(config.dhcp_server), 0); + std::fill(std::begin(config.ntp_server), std::end(config.ntp_server), 0); + std::fill(std::begin(config.domain_name), std::end(config.domain_name), 0); + parse_ini_file("k573npu.ini"); +} + +void konami_573_network_pcb_unit_pccard_device::device_stop() +{ +#ifdef _WIN32 + WSACleanup(); +#endif + + m_io_service.stop(); + m_thread.join(); +} + +void konami_573_network_pcb_unit_pccard_device::interface_post_start() +{ + m_hdd = std::make_unique(m_npu_hdd); +} + +void konami_573_network_pcb_unit_pccard_device::device_reset() +{ + m_hdd->reset(); + + m_cd1_cb(0); + m_cd2_cb(0); + + m_state_cur = 0; + m_val_unk = 0; + m_init_cur_step = 0; + m_buffer_write_length = 0; + m_buffer_read_length = 0; + m_state_requested = 0; + m_packet_id = 0; + + m_output_buf.clear(); + m_input_buf.clear(); +} + +void konami_573_network_pcb_unit_pccard_device::device_add_mconfig(machine_config &config) +{ + HARDDISK(config, m_npu_hdd); +} + +uint16_t konami_573_network_pcb_unit_pccard_device::read_memory(offs_t offset, uint16_t mem_mask) +{ + auto r = 0; + + /* + 0x24 = 0x2c = number of bytes written to 0x30??? + (0x06 & 0xfff0) = 0x0e = reg 0x24 & 0xfff0 + + 0x3c - some offset?? + 0x00 - reg | (reg 0x3c & 0xfff0) + 0x02 - reg | (reg 0x3c & 0xfff0) + 0x0e - reg | (reg 0x3c & 0xfff0) + 0x10 - (reg & 0xfff0) | (reg 0x3c & 0xfff0) + 0x1e - reg | (reg 0x3c & 0xfff0) + 0x2c - reg | (reg 0x3c) + 0x2e - reg | (reg 0x3c & 0xfff0) | ((reg 0x3c / 4) * 4) + 0x3e - reg | (reg 0x3c & 0xfff0) | ((reg 0x3c / 4) * 4) + + 0x30 - after writing value to 0x30, this returns 1 instead of 0? + + 0x22 is the same as 0x32? + 0x34 = 0x36 = 0x16 = 0x18 + 0x22 = 0x26 = 0x02 = 0x08 <- decrements when reg 0x20 is read when bit 15 of reg 0x30 is set + 0x04 = 0x08? + */ + + switch (offset * 2) + { + case REG_REQUESTED_STATE: + r = m_state_requested; + break; + + case REG_CURRENT_STATE: + r = m_state_cur; + break; + + case 0x06: + r = 5; + break; + + case REG_FIFO_READ: + if (!m_output_buf.empty()) + r = m_output_buf.pop(); + + if (m_state_requested != 8 && m_state_requested != 0x0f) + r ^= 0xffff; + + if (m_buffer_read_length) + m_buffer_read_length--; + break; + + case 0x24: + case REG_FIFO_WRITE_AVAIL_SIZE: + r = 0x100 - (m_input_buf.length_locked() % 0x100); // FIFO available size?? + break; + + case 0x22: + case REG_FIFO_READ_AVAIL_SIZE: + r = m_buffer_read_length; + break; + + case 0x30: + r = m_buffer_write_length > 0; + break; + + case 0x32: + r = 0;//m_fifo_buf.length() > 0; + break; + + case REG_FIFO_READ_OFFSET: + r = m_output_buf.tail_locked() & 0xffff; + break; + + case REG_FIFO_WRITE_OFFSET: + r = m_input_buf.tail_locked() & 0xffff; + break; + + case REG_FPGA_CHECK_RUNNING: + r = 0x5963; // FPGA initialized check + break; + + case 0x3c: + r = m_val_unk; + break; + } + + if (offset * 2 != REG_FIFO_READ || (offset * 2 == REG_FIFO_READ && m_output_buf.length_locked() < 0x10)) + LOGMEMVERBOSE("%s read_memory %08x %04x %04x\n", machine().describe_context().c_str(), offset * 2, r, mem_mask); + + return r; +} + +void konami_573_network_pcb_unit_pccard_device::write_memory(offs_t offset, uint16_t data, uint16_t mem_mask) +{ + if (offset * 2 != REG_FIFO_WRITE || (offset * 2 == REG_FIFO_WRITE && m_input_buf.length_locked() < 0x10)) + LOGMEMVERBOSE("%s write_memory %08x %04x %04x\n", machine().describe_context().c_str(), offset * 2, data, mem_mask); + + switch (offset * 2) + { + case REG_REQUESTED_STATE: + { + const auto prev_cmd = m_state_requested; + m_state_requested = data; + + m_state_cur = data; + + if (m_init_cur_step) + { + m_state_cur ^= 0x0f; + m_init_cur_step--; + } + + if (data == 2) + { + while (m_input_buf.length_locked() > 0) + m_input_buf.pop(); + + while (m_output_buf.length_locked() > 0) + m_output_buf.pop(); + + for (int i = 0; i < 0xa00; i++) + write_fifo_u16(m_output_buf, i); + } + else if (prev_cmd == 8 && data == 0x0f) + { + m_output_buf.clear(); + // m_input_buf.clear(); + + // 1 must be sent or else the Sys573 thinks the NPU isn't running + write_fifo_u16(m_output_buf, 1); + write_fifo_u16(m_output_buf, 0); + write_fifo_u16(m_output_buf, 0); + } + break; + } + + case 0x04: + // ??? + m_init_cur_step = data ? 5 : 0; + break; + + case REG_FIFO_WRITE: + if (m_buffer_write_length) + { + write_fifo_u16(m_input_buf, data); + m_buffer_write_length--; + + if (!m_buffer_write_length) + { + if ((m_state_requested == 8 || m_state_requested == 0x0f)/*x && m_fifo_buf.queue_length() == 0*/) + process_opcode(); + + // m_input_buf.clear(); + } + } + break; + + case 0x30: + // FIFO requested write count? + if (BIT(data, 15)) + { + m_buffer_read_length = data & 0x7fff; + } + else + { + m_buffer_write_length += data; + } + + break; + + case 0x34: + // m_input_buf.head(data); + break; + + case 0x36: + // m_output_buf.head(data); + break; + + case 0x3c: + m_val_unk = data; + break; + } +} + +uint16_t konami_573_network_pcb_unit_pccard_device::read_reg(offs_t offset, uint16_t mem_mask) +{ + auto r = 0; + LOGREG("%s read_reg %08x %04x %04x\n", machine().describe_context().c_str(), offset * 2, r, mem_mask); + return r; +} + +void konami_573_network_pcb_unit_pccard_device::write_reg(offs_t offset, uint16_t data, uint16_t mem_mask) +{ + LOGREG("%s write_reg %08x %04x %04x\n", machine().describe_context().c_str(), offset * 2, data, mem_mask); +} + +void konami_573_network_pcb_unit_pccard_device::write_fifo_bytes(ringbuffer &buf, const uint8_t *input, size_t len) +{ + for (int i = 0; i < len; i+=2) + { + uint16_t val = input[i]; + + if (i + 1 < len) + val |= input[i+1] << 8; + + buf.push(val); + } +} + +void konami_573_network_pcb_unit_pccard_device::write_fifo_u16(ringbuffer &buf, uint16_t val) +{ + buf.push(val); +} + +void konami_573_network_pcb_unit_pccard_device::write_fifo_u32(ringbuffer &buf, uint32_t val) +{ + write_fifo_u16(buf, (uint16_t)BIT(val, 0, 16)); + write_fifo_u16(buf, (uint16_t)BIT(val, 16, 16)); +} + +uint32_t konami_573_network_pcb_unit_pccard_device::read_fifo_u32(ringbuffer &buf) +{ + return buf.pop() | (buf.pop() << 16); +} + +uint16_t konami_573_network_pcb_unit_pccard_device::read_fifo_u16(ringbuffer &buf) +{ + return buf.pop(); +} + +size_t konami_573_network_pcb_unit_pccard_device::read_fifo_bytes(ringbuffer &buf, uint8_t *output, size_t len) +{ + size_t read_len = 0; + + memset(output, 0, len); + + while (read_len < len) + { + auto v = read_fifo_u16(buf); + output[read_len++] = BIT(v, 0, 8); + + if ((len & 1) == 0) + output[read_len++] = BIT(v, 8, 8); + } + + return read_len; +} + +void konami_573_network_pcb_unit_pccard_device::process_opcode() +{ + ringbuffer packet(m_input_buf, (((m_input_buf.at(2) + 1) & 0xfffe) >> 1) + 3); + const auto expected_head = (m_input_buf.head() + 6 + m_input_buf.at(2)) % m_input_buf.size(); + + LOGPACKET("\nCommand: "); + for (auto i = 0; i < packet.length(); i+=2) + LOGPACKET("%04x ", packet.at(i >> 1)); + LOGPACKET("\n"); + + // Remove copied range + while (m_input_buf.head() != expected_head) + m_input_buf.pop(); + + asio::dispatch(m_io_service, std::bind(&konami_573_network_pcb_unit_pccard_device::process_opcode_internal, this, packet, std::ref(m_output_buf))); +} + +void konami_573_network_pcb_unit_pccard_device::process_opcode_internal(ringbuffer &input_buf, ringbuffer &output_buf) +{ + while (output_buf.is_locked()); + + output_buf.lock(); + + const int arg0 = read_fifo_u16(input_buf); + const int opcode = arg0 & 0xff; + const int packet_id = read_fifo_u16(input_buf); + const int remaining = read_fifo_u16(input_buf); + + LOGPACKET("\nFound command %04x %04x %04x | ", arg0, packet_id, remaining); + for (auto i = 0; i < remaining; i+=2) + LOGPACKET("%04x ", input_buf.at(i >> 1)); + LOGPACKET("\n"); + + m_packet_id = packet_id; + + if (opcode == OPCODE_WRITE_MEMORY) + { + // upload code + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); // block addr + write_fifo_u16(output_buf, 0); + } + else if (opcode == OPCODE_READ_MEMORY) + { + const uint32_t addr = read_fifo_u32(input_buf); + const uint16_t reqlen = read_fifo_u16(input_buf); + + // mem copy? + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, reqlen); + + const auto curlen = output_buf.length(); + + if (addr == 0x80e00000 || addr == 0x80800000) + { + // Receive NPU ID + write_fifo_bytes(output_buf, config.npu_id, sizeof(config.npu_id)); + } + else if (addr == 0x80e00008 || addr == 0x80800008) + { + // MAC address + write_fifo_bytes(output_buf, config.mac_address, sizeof(config.mac_address)); + } + else if (addr == 0x80e00010 || addr == 0x80800010) + { + // ? + write_fifo_u16(output_buf, 1); + } + + while (output_buf.length() - curlen < reqlen) + write_fifo_u16(output_buf, 0); + } + else if (opcode == OPCODE_EXECUTE_MEMORY) + { + // execute code at offset + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 0); + + m_state_cur = 0x0f; + } + else if (opcode == 0x00) + { + } + else if (opcode == 0x01) + { + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 0); + } + else if (opcode == 0x37) + { + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, 1); + } + else if (opcode == 0x38) + { + write_fifo_u16(output_buf, 0x39); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, 1); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 0); + } + else if (opcode == OPCODE_SOCKET_CLOSE) + { + // close socket + const uint32_t fd = read_fifo_u32(input_buf); + + int ret = 0; + +#ifdef _WIN32 + ret = closesocket(fd); +#else + ret = close(fd); +#endif + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, ret); + + LOGNET("socket close(%d) = %d\n", fd, ret); + } + else if (opcode == OPCODE_SOCKET_CONNECT) + { + // connect + const uint32_t fd = read_fifo_u32(input_buf); + read_fifo_u32(input_buf); + const uint32_t sin_family = read_fifo_u16(input_buf); // should be sockaddr_in struct + const uint32_t sin_port = read_fifo_u16(input_buf); + const uint32_t sin_addr = read_fifo_u32(input_buf); + + struct sockaddr_in dest; + dest.sin_family = sa_family_t(sin_family), + dest.sin_port = in_port_t(sin_port), + dest.sin_addr.s_addr = in_addr_t(sin_addr); + + const int ret = connect(fd, (struct sockaddr*)&dest, sizeof(dest)); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 8); + + write_fifo_u32(output_buf, ret); + write_fifo_u32(output_buf, 0); // this value actually gets copied into NVRAM and then is read back out for the 0x37 command. What is it? + + LOGNET("socket connect(%d, { family=%d, port=%d, addr=%d }, %d) = %d\n", fd, sin_family, sin_port, sin_addr, sizeof(dest), ret); + } + else if (opcode == OPCODE_SOCKET_RECV) + { + // recv + const uint32_t fd = read_fifo_u32(input_buf); + const uint32_t buffer_len = read_fifo_u32(input_buf); + [[maybe_unused]] const uint32_t unk = read_fifo_u32(input_buf); + + uint8_t *response = (uint8_t*)calloc(buffer_len, sizeof(uint8_t)); + const ssize_t bytes = recv(fd, (char*)response, buffer_len, 0); + uint32_t total_message_size = 4; + + if (bytes > 0) + total_message_size += bytes; + + total_message_size = (total_message_size + 1) & 0xfffe; + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, total_message_size); + + write_fifo_u32(output_buf, bytes); + write_fifo_bytes(output_buf, response, bytes); + + if (response != nullptr) + free(response); + + LOGNET("socket recv(%d, ..., %d) = %d\n", fd, buffer_len, bytes); + for (int i = 0; i < bytes; i++) + LOGNET("%02x ", response[i]); + LOGNET("\n"); + } + else if (opcode == OPCODE_SOCKET_RECVFROM) + { + // recvfrom + const uint32_t fd = read_fifo_u32(input_buf); + const uint32_t buffer_len = read_fifo_u32(input_buf); + [[maybe_unused]] const uint32_t unk = read_fifo_u32(input_buf); + + uint8_t *response = (uint8_t*)calloc(buffer_len, sizeof(uint8_t)); + + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + + ssize_t bytes = recvfrom(fd, (char*)response, buffer_len, 0, (struct sockaddr*)&from, &fromlen); + ssize_t total_bytes = bytes; + int total_message_size = 8; + + if (bytes >= 0) + { + total_bytes = bytes + 20; + total_message_size += total_bytes; + } + + total_message_size = (total_message_size + 1) & 0xfffe; + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, total_message_size); + + write_fifo_u32(output_buf, bytes); // 8 + write_fifo_u32(output_buf, sizeof(from)); // c + + if (bytes >= 0) + { + write_fifo_u32(output_buf, sizeof(from)); // 10 + write_fifo_u16(output_buf, from.sin_family); // 14 + write_fifo_u16(output_buf, from.sin_port); // 16 + write_fifo_u32(output_buf, from.sin_addr.s_addr); // 18 + write_fifo_bytes(output_buf, (uint8_t*)from.sin_zero, sizeof(from.sin_zero)); // 1c + write_fifo_bytes(output_buf, response, bytes); // 24 + } + + if (response != nullptr) + free(response); + + LOGNET("socket recvfrom(%d, ..., %d, 0, ..., ...) = %d\n", fd, buffer_len, bytes); + for (int i = 0; i < bytes; i++) + LOGNET("%02x ", response[i]); + LOGNET("\n"); + } + else if (opcode == OPCODE_SOCKET_SEND) + { + // send + const uint32_t fd = read_fifo_u32(input_buf); + const uint32_t payload_len = read_fifo_u32(input_buf); + [[maybe_unused]] const uint32_t unk = read_fifo_u32(input_buf); + + uint8_t *payload = (uint8_t*)calloc(((payload_len + 1) & 0xfffe), sizeof(uint8_t)); + read_fifo_bytes(input_buf, payload, (payload_len + 1) & 0xfffe); + + const ssize_t ret = send(fd, (char*)payload, payload_len, 0); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, ret); + + LOGNET("socket send(%d, ..., %d) = %d\n", fd, payload_len, ret); + for (int i = 0; i < payload_len; i++) + LOGNET("%02x ", payload[i]); + LOGNET("\n"); + + if (payload != nullptr) + free(payload); + } + else if (opcode == OPCODE_SOCKET_SENDTO) + { + // sendto + const uint32_t fd = read_fifo_u32(input_buf); + const uint32_t payload_len = read_fifo_u32(input_buf); + [[maybe_unused]] const uint32_t unk = read_fifo_u32(input_buf); + [[maybe_unused]] const uint32_t hdr_len = read_fifo_u32(input_buf); // or maybe payload offset? + const uint32_t sin_family = read_fifo_u16(input_buf); // should be sockaddr_in struct + const uint32_t sin_port = read_fifo_u16(input_buf); + const uint32_t sin_addr = read_fifo_u32(input_buf); + read_fifo_u16(input_buf); // should always be zero + read_fifo_u16(input_buf); + read_fifo_u16(input_buf); + read_fifo_u16(input_buf); + + uint8_t *payload = (uint8_t*)calloc(((payload_len + 1) & 0xfffe), sizeof(uint8_t)); + read_fifo_bytes(input_buf, payload, (payload_len + 1) & 0xfffe); + + struct sockaddr_in dest; + dest.sin_family = sa_family_t(sin_family), + dest.sin_port = in_port_t(sin_port), + dest.sin_addr.s_addr = in_addr_t(sin_addr); + + const ssize_t ret = sendto(fd, (char*)payload, payload_len, 0, (struct sockaddr*)&dest, sizeof(dest)); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 8); + + write_fifo_u32(output_buf, ret); + write_fifo_u32(output_buf, 0); + + LOGNET("socket sendto(%d, ..., %d, 0, { family=%d, port=%d, addr=%d }, %d) = %d | %d\n", fd, payload_len, sin_family, sin_port, sin_addr, sizeof(dest), ret, hdr_len); + for (int i = 0; i < payload_len; i++) + LOGNET("%02x ", payload[i]); + LOGNET("\n"); + + if (payload != nullptr) + free(payload); + } + else if (opcode == OPCODE_SOCKET_SETSOCKOPT) + { + // Found command 0a2c 000d 0018 | 0006 0000 ffff 0000 0200 0000 0008 0000 0002 0000 0000 0000 + // int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen); + const uint32_t fd = read_fifo_u32(input_buf); + const uint32_t level = read_fifo_u32(input_buf); + const uint32_t optname = read_fifo_u32(input_buf); + const uint32_t optlen = read_fifo_u32(input_buf); + + uint8_t *optval = (uint8_t*)calloc(optlen, sizeof(uint8_t)); + read_fifo_bytes(input_buf, optval, optlen); + + const uint32_t val = (optval[0] | (optval[1] << 8) | (optval[2] << 16) | (optval[3] << 24)) & 1; + const int ret = setsockopt(fd, level, optname, (const char*)&val, sizeof(val)); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, ret); + + if (optval != nullptr) + free(optval); + + LOGNET("socket setsockopt(%d, %d, %d, ...) = %d | %d\n", fd, level, optname, ret, optlen); + } + else if (opcode == OPCODE_SOCKET_SOCKET) + { + const uint32_t domain = read_fifo_u32(input_buf); + uint32_t type = read_fifo_u32(input_buf); + uint32_t protocol = read_fifo_u32(input_buf); + + if (type == 1 && protocol == 1) // for TCP + { + type = SOCK_STREAM; + protocol = IPPROTO_TCP; + } + else if (type == 2 && protocol == 3) // for UDP (used by DNS) + { + type = SOCK_DGRAM; + protocol = IPPROTO_UDP; + } + else if (type == 3 && protocol == 2) // for NTP + { +#ifdef _WIN32 + type = SOCK_RAW; +#else + type = SOCK_DGRAM; +#endif + protocol = IPPROTO_ICMP; + } + else + { + fatalerror("Unknown socket configuration! %d %d %d\n", domain, type, protocol); + } + + const int socketfd = socket(domain, type, protocol); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, socketfd); + + LOGNET("socket socket(%d, %d, %d) = %d\n", domain, type, protocol, socketfd); + } + else if (opcode == OPCODE_NET_GET_SETTINGS) + { + const uint32_t addr = read_fifo_u32(input_buf); + const uint32_t reqlen = read_fifo_u32(input_buf); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, reqlen + 4); + write_fifo_u32(output_buf, reqlen); + + LOGCMD("net getsettings(0x%x, %d)\n", addr, reqlen); + + if (reqlen > 0) + { + write_fifo_u16(output_buf, 0); + + if (addr == REQVAL_ADDR_IP_ADDR) + { + write_fifo_bytes(output_buf, config.ip_address, sizeof(config.ip_address)); + } + else if (addr == REQVAL_ADDR_SUBNET_MASK) + { + write_fifo_bytes(output_buf, config.subnet_mask, sizeof(config.subnet_mask)); + } + else if (addr == REQVAL_ADDR_DEFAULT_GATEWAY) + { + write_fifo_bytes(output_buf, config.default_gateway, sizeof(config.default_gateway)); + } + else if (addr == REQVAL_ADDR_DNS_SERVERS) + { + write_fifo_bytes(output_buf, config.dns_server1, sizeof(config.dns_server1)); + write_fifo_bytes(output_buf, config.dns_server2, sizeof(config.dns_server2)); + } + else if (addr == REQVAL_ADDR_DOMAIN_NAME) + { + // Domain name, up to 32 bytes total + write_fifo_bytes(output_buf, config.domain_name, sizeof(config.domain_name)); + } + else if (addr == REQVAL_ADDR_DHCP_SERVER) + { + write_fifo_bytes(output_buf, config.dhcp_server, sizeof(config.dhcp_server)); + } + else if (addr == REQVAL_ADDR_NTP_SERVER) + { + write_fifo_bytes(output_buf, config.ntp_server, sizeof(config.ntp_server)); + } + else + { + for (int i = 0; i < reqlen - 2; i+=2) + write_fifo_u16(output_buf, 0); + fatalerror("Found unhandled value request: %02x\n", addr); + } + } + } + else if (opcode == OPCODE_FILESYSTEM_INITIALIZE) + { + const uint32_t offset = read_fifo_u32(input_buf); + const uint32_t arg = read_fifo_u32(input_buf); + auto r = 0; + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_initialize(0x%08x, 0x%x) = %d\n", offset, arg, r); + } + else if (opcode == OPCODE_FILESYSTEM_FORMAT) + { + auto r = 0; + + char param1[0x40]; + read_fifo_bytes(input_buf, param1, sizeof(param1)); + + char param2[0x40]; + read_fifo_bytes(input_buf, param2, sizeof(param2)); + + const uint32_t param3 = read_fifo_u32(input_buf); + const uint32_t param4 = read_fifo_u32(input_buf); + const uint32_t param5 = read_fifo_u32(input_buf); + const uint32_t param6 = read_fifo_u32(input_buf); + const uint32_t param7 = read_fifo_u32(input_buf); + const uint32_t param8 = read_fifo_u32(input_buf); + + if (strncmp(param1, "atam:", 5) == 0 && m_hdd) + r = m_hdd->format(param4, param5, param6, param7, param8); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_format(\"%s\", \"%s\", 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x) = %d\n", param1, param2, param3, param4, param5, param6, param7, param8, r); + } + else if (opcode == OPCODE_FILESYSTEM_MOUNT) + { + auto r = 0; + + char fstype[0x40]; + read_fifo_bytes(input_buf, fstype, sizeof(fstype)); + + const uint32_t param2 = read_fifo_u32(input_buf); + + char loadinfo[0x40]; + read_fifo_bytes(input_buf, loadinfo, sizeof(loadinfo)); + + const uint32_t param4 = read_fifo_u32(input_buf); + + if (strncmp(loadinfo, "atam:", 5) == 0 && m_hdd) + r = m_hdd->mount(loadinfo + 5); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_mount(\"%s\", 0x%x, \"%s\", 0x%x) = %d\n", fstype, param2, loadinfo, param4, r); + } + else if (opcode == OPCODE_FILESYSTEM_UMOUNT) + { + auto r = 0; + + char path[0x40]; + read_fifo_bytes(input_buf, path, sizeof(path)); + + if (strncmp(path, "atam:", 5) == 0 && m_hdd) + r = m_hdd->umount(path + 5); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_umount(\"%s\") = %d\n", path, r); + } + else if (opcode == OPCODE_FILESYSTEM_DEVCTL) + { + auto r = 0; + + char path[0x40]; + read_fifo_bytes(input_buf, path, sizeof(path)); + + const uint32_t reqtype = read_fifo_u32(input_buf); + const uint32_t param3 = read_fifo_u32(input_buf); + const uint32_t resplen = read_fifo_u32(input_buf); + + uint8_t *data = (uint8_t*)calloc(resplen, sizeof(uint8_t)); + if (m_hdd && strncmp(path, "atam:", 5) == 0) + r = m_hdd->devctl(reqtype, param3, resplen, data); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, resplen + 4); + write_fifo_u32(output_buf, r); + write_fifo_bytes(output_buf, data, resplen); + + if (data != nullptr) + free(data); + + LOGFS("pythonfs_devctl(\"%s\", 0x%x, 0x%x, 0x%x) = %d\n", path, reqtype, param3, resplen, r); + } + else if (opcode == OPCODE_FILESYSTEM_CHDIR) + { + uint32_t r = 0; + + char path[0x100]; + read_fifo_bytes(input_buf, path, sizeof(path)); + + if (m_hdd && strncmp(path, "atam:", 5) == 0) + r = m_hdd->chdir(path + 5); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_chdir(\"%s\") = %d\n", path, r); + } + else if (opcode == OPCODE_FILESYSTEM_OPEN) + { + uint32_t r = 0; + + char filename[0x100]; + read_fifo_bytes(input_buf, filename, sizeof(filename)); + + const uint32_t flags = read_fifo_u32(input_buf); + const uint32_t mode = read_fifo_u32(input_buf); + + if (m_hdd && strncmp(filename, "atam:", 5) == 0) + r = m_hdd->open(filename + 5, flags, mode); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_open(\"%s\", 0x%x, 0x%x) = %d\n", filename, flags, mode, r); + } + else if (opcode == OPCODE_FILESYSTEM_LSEEK) + { + const uint32_t fd = read_fifo_u32(input_buf); + const uint32_t offset = read_fifo_u32(input_buf); + const uint32_t whence = read_fifo_u32(input_buf); + + auto r = offset; + + if (m_hdd) + r = m_hdd->lseek(fd, offset, whence); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_lseek(%d, 0x%x, %d) = %d\n", fd, offset, whence, r); + } + else if (opcode == OPCODE_FILESYSTEM_READ) + { + const uint32_t fd = read_fifo_u32(input_buf); + const uint32_t orig_size = read_fifo_u32(input_buf); + uint32_t r = 0; + + uint32_t size = 0; + uint8_t *data = (uint8_t*)calloc(orig_size + (2 - (orig_size % 2)), sizeof(uint8_t)); + + if (m_hdd && data != nullptr) + { + if ((r = m_hdd->read(fd, orig_size, data, size)) < 0) + size = 0; + } + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4 + size); + + write_fifo_u32(output_buf, r); + + if (r > 0) + write_fifo_bytes(output_buf, data, size); + + if (data != nullptr) + free(data); + + LOGFS("pythonfs_read(%d, 0x%x) = %d\n", fd, orig_size, r); + } + else if (opcode == OPCODE_FILESYSTEM_WRITE) + { + const uint32_t fd = read_fifo_u32(input_buf); + const uint32_t len = read_fifo_u32(input_buf); + uint32_t r = 0; + + uint8_t *data = (uint8_t*)calloc(len, sizeof(uint8_t)); + + read_fifo_bytes(input_buf, data, len); + + if (m_hdd) + { + if (m_hdd->write(fd, len, data) >= 0) + r = len; + } + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + if (data != nullptr) + free(data); + + LOGFS("pythonfs_write(%d, 0x%x, ...) = %d\n", fd, len, r); + } + else if (opcode == OPCODE_FILESYSTEM_CLOSE) + { + const uint32_t fd = read_fifo_u32(input_buf); + auto r = 0; + + if (m_hdd) + r = m_hdd->close(fd); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_close(%d) = %d\n", fd, r); + } + else if (opcode == OPCODE_FILESYSTEM_GETSTAT) + { + char filename[0x100]; + read_fifo_bytes(input_buf, filename, sizeof(filename)); + + auto r = 0; + + uint8_t data[0x40]; + std::fill(std::begin(data), std::end(data), 0); + + if (m_hdd && strncmp(filename, "atam:", 5) == 0) + r = m_hdd->getstat(filename + 5, data); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + + if (r < 0) + { + write_fifo_u16(output_buf, 4); + write_fifo_u32(output_buf, r); + } + else + { + write_fifo_u16(output_buf, sizeof(data) + 4); + write_fifo_u32(output_buf, r); + write_fifo_bytes(output_buf, data, sizeof(data)); + } + + LOGFS("pythonfs_getstat(\"%s\") = %d\n", filename, r); + } + else if (opcode == OPCODE_FILESYSTEM_DOPEN) + { + uint32_t r = 0; + + char filename[0x100]; + read_fifo_bytes(input_buf, filename, sizeof(filename)); + + if (m_hdd && strncmp(filename, "atam:", 5) == 0) + r = m_hdd->dopen(filename + 5); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_dopen(\"%s\") = %d\n", filename, r); + } + else if (opcode == OPCODE_FILESYSTEM_DCLOSE) + { + const uint32_t fd = read_fifo_u32(input_buf); + auto r = 0; + + if (m_hdd) + r = m_hdd->dclose(fd); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_dclose(%d) = %d\n", fd, r); + } + else if (opcode == OPCODE_FILESYSTEM_DREAD) + { + const uint32_t fd = read_fifo_u32(input_buf); + uint32_t r = 0; + + uint8_t data[0x144]; + std::fill(std::begin(data), std::end(data), 0); + + if (m_hdd) + { + if (m_hdd->dread(fd, &data[0]) < 0) + r = 0; + else + r = sizeof(data); + } + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4 + r); + + write_fifo_u32(output_buf, r); + write_fifo_bytes(output_buf, data, r); + + LOGFS("pythonfs_dread(%d) = %d\n", fd, r); + } + else if (opcode == OPCODE_FILESYSTEM_MKDIR) + { + uint32_t r = 0; + + char filename[0x100]; + read_fifo_bytes(input_buf, filename, sizeof(filename)); + + const uint32_t mode = read_fifo_u32(input_buf); + + if (m_hdd && strncmp(filename, "atam:", 5) == 0) + r = m_hdd->mkdir(filename + 5, mode); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_mkdir(\"%s\", 0x%x) = %d\n", filename, mode, r); + } + else if (opcode == OPCODE_FILESYSTEM_RMDIR) + { + uint32_t r = 0; + + char filename[0x100]; + read_fifo_bytes(input_buf, filename, sizeof(filename)); + + if (m_hdd && strncmp(filename, "atam:", 5) == 0) + r = m_hdd->rmdir(filename + 5); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_rmdir(\"%s\") = %d\n", filename, r); + } + else if (opcode == OPCODE_FILESYSTEM_REMOVE) + { + uint32_t r = 0; + + char filename[0x100]; + read_fifo_bytes(input_buf, filename, sizeof(filename)); + + if (m_hdd && strncmp(filename, "atam:", 5) == 0) + r = m_hdd->remove(filename + 5); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 4); + + write_fifo_u32(output_buf, r); + + LOGFS("pythonfs_remove(\"%s\") = %d\n", filename, r); + } + else + { + if (opcode >= OPCODE_FILESYSTEM_INITIALIZE) + fatalerror("Implement this opcode: %02x\n", opcode); + + write_fifo_u16(output_buf, 0); + write_fifo_u16(output_buf, packet_id); + write_fifo_u16(output_buf, 0); + } + + output_buf.unlock(); + + // Add temp buffer data to main output buffer + LOGPACKET("\nResponse (%04x vs %04x, %04x %04x): ", output_buf.length(), output_buf.at(2) + 6, output_buf.head(), output_buf.tail()); + for (auto i = 0; i < output_buf.at(2) + 6; i+=2) + LOGPACKET("%04x ", output_buf.at(i >> 1)); + LOGPACKET("\n"); +} + +void konami_573_network_pcb_unit_pccard_device::parse_ini_file(const char *filename) +{ + util::core_file::ptr inifile = nullptr;; + auto filerr = util::core_file::open(filename, OPEN_FLAG_READ, inifile); + + if (filerr) + { + osd_printf_error("Error opening input file (%s): %s\n", filename, filerr.message()); + return; + } + + // loop over lines in the file + char buffer[4096]; + while (inifile->gets(buffer, std::size(buffer)) != nullptr) + { + // find the extent of the name + char *optionname; + for (optionname = buffer; *optionname != 0; optionname++) + if (!isspace((uint8_t)*optionname)) + break; + + // skip comments + if (*optionname == 0 || *optionname == '#') + continue; + + // scan forward to find the first space + char *temp; + for (temp = optionname; *temp != 0; temp++) + if (isspace((uint8_t)*temp)) + break; + + // if we hit the end early, print a warning and continue + if (*temp == 0) + { + osd_printf_warning("Warning: invalid line in INI: %s", buffer); + continue; + } + + // NULL-terminate + *temp++ = 0; + + // scan forward to the first non-space + for (; *temp != 0; temp++) + if (!isspace((uint8_t)*temp)) + break; + + char *optiondata = temp; + + // scan the data, stopping when we hit a comment + bool inquotes = false; + for (temp = optiondata; *temp != 0; temp++) + { + if (*temp == '"') + inquotes = !inquotes; + if (*temp == '#' && !inquotes) + break; + if (*temp == '\n' || *temp == '\r') + break; + } + *temp = 0; + + LOG("config: %s %s\n", optionname, optiondata); + + if (core_stricmp(optionname, "npu_id") == 0) + parse_hexstr(optiondata, config.npu_id, sizeof(config.npu_id)); + else if (core_stricmp(optionname, "mac_address") == 0) + parse_mac_address(optiondata, config.mac_address, sizeof(config.mac_address)); + else if (core_stricmp(optionname, "ip_address") == 0) + parse_ip_address(optiondata, config.ip_address, sizeof(config.ip_address)); + else if (core_stricmp(optionname, "subnet_mask") == 0) + parse_ip_address(optiondata, config.subnet_mask, sizeof(config.subnet_mask)); + else if (core_stricmp(optionname, "default_gateway") == 0) + parse_ip_address(optiondata, config.default_gateway, sizeof(config.default_gateway)); + else if (core_stricmp(optionname, "dns_server1") == 0) + parse_ip_address(optiondata, config.dns_server1, sizeof(config.dns_server1)); + else if (core_stricmp(optionname, "dns_server2") == 0) + parse_ip_address(optiondata, config.dns_server2, sizeof(config.dns_server2)); + else if (core_stricmp(optionname, "dhcp_server") == 0) + parse_ip_address(optiondata, config.dhcp_server, sizeof(config.dhcp_server)); + else if (core_stricmp(optionname, "ntp_server") == 0) + parse_ip_address(optiondata, config.ntp_server, sizeof(config.ntp_server)); + else if (core_stricmp(optionname, "domain_name") == 0) + { + const int startIdx = optiondata[0] == '"' ? 1 : 0; + int len = 0; + + for (int i = startIdx; i < strlen(optiondata); i++) + { + if (optiondata[i] == '"') + { + len = i - startIdx; + break; + } + } + + if (len > 0) + strncpy(config.domain_name, optiondata + startIdx, len); + } + } +} + +uint8_t konami_573_network_pcb_unit_pccard_device::str2hex(const char *input, int len) +{ + const char val[3] = { + len >= 2 ? input[len - 2] : '0', + len >= 1 ? input[len - 1] : '0', + 0 + }; + + return strtoul(val, NULL, 16); +} + +void konami_573_network_pcb_unit_pccard_device::parse_hexstr(const char *input, uint8_t* output, size_t outputlen) +{ + // Supports variable size strings + for (int i = 0; i < strlen(input) && i / 2 < outputlen; i+=2) + { + if (i + 1 < strlen(input)) + output[outputlen - 1 - (i / 2)] = str2hex(&input[strlen(input) - i - 1 - 1], 2); + else + output[outputlen - 1 - (i / 2)] = str2hex(&input[strlen(input) - i - 1], 1); + } +} + +void konami_573_network_pcb_unit_pccard_device::parse_mac_address(const char *input, uint8_t* output, size_t outputlen) +{ + // 6 groups of 2 hex bytes separated by : + int curbyte = 0; + int idx = 0; + + while (idx < strlen(input)) + { + int idx2 = idx; + while (idx2 < strlen(input) && input[idx2] != ':') + idx2++; + + output[curbyte++] = str2hex(input + idx, idx2 - idx); + idx = idx2 + 1; + } +} + +void konami_573_network_pcb_unit_pccard_device::parse_ip_address(const char *input, uint8_t* output, size_t outputlen) +{ + // 4 digits between 0-255 separated by . + int curbyte = 0; + int idx = 0; + + while (idx < strlen(input)) + { + int idx2 = idx; + while (idx2 < strlen(input) && input[idx2] != '.') + idx2++; + + output[curbyte++] = atoi(input + idx); + idx = idx2 + 1; + } +} + +uint32_t konami_573_network_pcb_unit_pccard_device::parse_ip_address(const char *input) +{ + uint8_t addr[4]; + parse_ip_address(input, addr, sizeof(addr)); + return (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3]; +} diff --git a/src/mame/konami/k573npu_card.h b/src/mame/konami/k573npu_card.h new file mode 100644 index 0000000000000..5211d9402cad9 --- /dev/null +++ b/src/mame/konami/k573npu_card.h @@ -0,0 +1,421 @@ +// license:BSD-3-Clause +// copyright-holders:windyfairy +#ifndef MAME_KONAMI_K573NPU_CARD_H +#define MAME_KONAMI_K573NPU_CARD_H + +#pragma once + +#include "emu.h" + +#include + +#include "imagedev/harddriv.h" +#include "bus/pccard/pccard.h" + +#include "asio.h" + +class konami_573_network_pcb_unit_hdd +{ +public: + konami_573_network_pcb_unit_hdd(harddisk_image_device *hdd); + + void reset(); + + int open(const char *path, uint32_t flags, uint32_t mode); + int close(int fd, bool allow_write); + int close(int fd) { return close(fd, true); } + int read(int fd, uint32_t len, uint8_t *outbuf, uint32_t &read_count); + int write(int fd, uint32_t len, uint8_t *data); + int lseek(int fd, uint32_t offset, int whence); + // int ioctl(); + int dopen(const char *path); + int dclose(int fd); + int dread(int fd, uint8_t *outbuf); + int remove(const char *path); + int mkdir(const char *path, int mode); + int rmdir(const char *path); + int getstat(const char *path, uint8_t *outbuf); + // int chstat(); + // int rename(const char *old_filename, const char *new_filename); + int chdir(const char *path); + int mount(const char *path); + int umount(const char *path); + int devctl(uint32_t reqtype, uint32_t param2, uint32_t resplen, uint8_t *data); + int format(uint32_t startLba, uint32_t partitionCount1, uint32_t partitionCount2, uint32_t param4, uint32_t param5); + +private: + enum { + SECTOR_SIZE = 0x200, + START_SECTOR = 8, + + BLOCK_END = 0x00ffffff, + }; + + enum { + ATTRIB_IS_FOLDER = 1 << 24, + }; + + enum { + FILE_FLAG_WRITE = 0x02, + }; + + enum { + SEARCH_MODE_CONTIGUOUS = 1 << 0, + SEARCH_MODE_ANY = 1 << 1, + + SEARCH_START_NONE = 0, // 0 is never a valid sector so ignore it + }; + +#ifdef _MSC_VER +#pragma pack(push,1) +#define NPU_STRUCT_PACKED +#else +#define NPU_STRUCT_PACKED __attribute__((packed)) +#endif + + typedef struct NPU_STRUCT_PACKED { + uint8_t padding[4]; + uint8_t magic[8]; + uint8_t magic2[8]; // magic ^ 0xff + uint8_t partitionCount1; + uint8_t partitionCount2; + uint8_t blockSizeMult; + uint8_t nodeBlockTableCount; + uint32_t partitionTotalSectorCount; + uint32_t nodeBlockTableSectorCount; + uint32_t dataSectorOffset; // nodeBlockTableEntryCount * nodeBlockTableCount + 1 + uint32_t unk1; // ??? + } partition_header_t; + + typedef struct NPU_STRUCT_PACKED { + char name[20]; + uint32_t size; + uint32_t unk; + uint32_t offset; + } directory_t; + + typedef struct NPU_STRUCT_PACKED { + uint32_t mode; + uint32_t attr; + uint32_t size; + uint8_t ctime[8]; + uint8_t atime[8]; + uint8_t mtime[8]; + uint32_t hsize; + uint32_t priv0; + uint32_t priv1; + uint32_t priv2; + uint32_t priv3; + uint32_t priv4; + uint32_t priv5; + } stat_t; + + typedef struct NPU_STRUCT_PACKED { + stat_t stat; + char name[256]; + uint32_t unk; + } dirent_t; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + + typedef struct { + uint32_t fd; + bool is_open; + bool is_dir; + uint32_t flags; + uint32_t mode; + + uint32_t curblock; + uint32_t block_offset; + uint32_t block_suboffset; + + directory_t entry; + std::vector directory; + + std::string path; + std::string filename; + } file_desc_t; + + typedef struct { + partition_header_t header; + uint32_t *blocks; + + std::string directory_path; + std::vector directory; + + size_t offset; + uint32_t directory_block_sector; // 0 is not valid + uint32_t blockSize; + + bool is_loaded; + } partition_t; + + void write_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual); + void read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual); + void read_some(void *buffer, std::size_t length, std::size_t &actual); + void seek(std::int64_t offset, int whence); + + int parse_filesystem(); + int chdir(const char *path, std::vector &output); + + int mkdir_internal(const char *path, const char *folder); + int get_available_block_offsets(size_t size, int mode, uint32_t start, std::vector &blockList); + int write_directories(std::vector dir, std::vector &blockList); + uint32_t get_raw_offset_from_block_offset(uint32_t offset); + uint32_t get_lba_from_block_offset(uint32_t offset); + void write_block_table(); + uint32_t get_lba_from_file_offset(uint32_t block_offset, uint32_t offset); + + int add_new_entry_to_directory(const char *path, directory_t &entry); + int remove_entry_from_directory(const char *path, const char *name); + + bool is_valid_fd(int fd); + uint32_t get_next_fd(); + + harddisk_image_device *m_hdd; + uint64_t m_hdd_offset; + + partition_t m_partition; + uint32_t m_next_fd; + uint32_t m_mount_size_sectors; + + std::unordered_map m_filedescs; +}; + +template +class ringbuffer +{ +public: + ringbuffer() + { + clear(); + } + + ringbuffer(ringbuffer &in, std::size_t len) + { + clear(); + + // copy existing buffer data + for (auto i = 0; i < len; i++) + push(in.at(i)); + } + + void clear() + { + std::fill(std::begin(m_buf), std::end(m_buf), 0); + m_head = m_tail = 0; + m_is_locked = false; + } + + bool is_locked() + { + return m_is_locked; + } + + bool lock() + { + if (m_is_locked) + return false; + + m_is_locked = true; + m_head_locked = m_head; + m_tail_locked = m_tail; + + return true; + } + + bool unlock() + { + m_is_locked = false; + return true; + } + + void push(const T val) + { + m_buf[m_tail] = val; + m_tail = (m_tail + 1) % count(); + // printf("inner m_buf_tail: %04zx\n", m_buf_tail); + } + + T pop() + { + T val = m_buf[m_head]; + m_head = (m_head + 1) % count(); + return val; + } + + T peek() + { + return m_buf[m_head]; + } + + T at(const std::size_t offset) + { + return m_buf[(m_head + offset) % count()]; + } + + std::size_t head() + { + return m_head * sizeof(T); + } + + std::size_t tail() + { + return m_tail * sizeof(T); + } + + std::size_t head_locked() + { + if (!m_is_locked) + return head(); + + return m_head_locked * sizeof(T); + } + + std::size_t tail_locked() + { + if (!m_is_locked) + return tail(); + + return m_tail_locked * sizeof(T); + } + + void head(const std::size_t val) + { + m_head = val % count(); + + if (m_head > m_tail) + m_tail = m_head; + } + + void tail(const std::size_t val) + { + m_tail = val % count(); + } + + std::size_t length() + { + return length_from(m_head, m_tail); + } + + std::size_t length_locked() + { + if (!m_is_locked) + return length(); + + return length_from(m_head_locked, m_tail_locked); + } + + std::size_t length_from(const std::size_t start, const std::size_t end) + { + std::size_t r; + if (end < start) + r = (end + count()) - start; + else + r = end - start; + return r * sizeof(T); + } + + bool empty() + { + return m_tail == m_head; + } + + constexpr std::size_t size() + { + return Size; + } + + constexpr std::size_t count() + { + return Size / sizeof(T); + } + +private: + std::array m_buf; + std::size_t m_head, m_tail; + std::size_t m_head_locked, m_tail_locked; + bool m_is_locked; +}; + +class konami_573_network_pcb_unit_pccard_device : public device_t, + public device_pccard_interface +{ +public: + konami_573_network_pcb_unit_pccard_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + virtual uint16_t read_memory(offs_t offset, uint16_t mem_mask = ~0) override; + virtual void write_memory(offs_t offset, uint16_t data, uint16_t mem_mask = ~0) override; + + virtual uint16_t read_reg(offs_t offset, uint16_t mem_mask = ~0) override; + virtual void write_reg(offs_t offset, uint16_t data, uint16_t mem_mask = ~0) override; + +protected: + virtual void device_start() override; + virtual void device_stop() override; + virtual void interface_post_start() override; + virtual void device_reset() override; + virtual void device_add_mconfig(machine_config &config) override; + +private: + enum { + FIFO_SIZE = 0x8000, + }; + + struct { + uint8_t npu_id[8]; + uint8_t mac_address[6]; + uint8_t ip_address[4]; + uint8_t subnet_mask[4]; + uint8_t default_gateway[4]; + uint8_t dns_server1[4]; + uint8_t dns_server2[4]; + uint8_t dhcp_server[4]; + uint8_t ntp_server[4]; + char domain_name[32]; + } config; + + void process_opcode(); + void process_opcode_internal(ringbuffer &input_buf, ringbuffer &output_buf); + void parse_ini_file(const char *filename); + + uint8_t str2hex(const char *input, int len); + void parse_hexstr(const char *input, uint8_t* output, size_t outputlen); + void parse_mac_address(const char *input, uint8_t* output, size_t outputlen); + void parse_ip_address(const char *input, uint8_t* output, size_t outputlen); + uint32_t parse_ip_address(const char *input); + + size_t read_fifo_bytes(ringbuffer &buf, uint8_t *output, size_t len); + size_t read_fifo_bytes(ringbuffer &buf, char *output, size_t len) { return read_fifo_bytes(buf, (uint8_t*)output, len); } + uint16_t read_fifo_u16(ringbuffer &buf); + uint32_t read_fifo_u32(ringbuffer &buf); + + void write_fifo_bytes(ringbuffer &buf, const uint8_t *input, size_t len); + void write_fifo_bytes(ringbuffer &buf, const char *input, size_t len) { return write_fifo_bytes(buf, (const uint8_t*)input, len); } + void write_fifo_u16(ringbuffer &buf, uint16_t val); + void write_fifo_u32(ringbuffer &buf, uint32_t val); + + required_device m_npu_hdd; + + uint16_t m_val_unk; + uint32_t m_init_cur_step; + + int32_t m_buffer_write_length, m_buffer_read_length; + + uint16_t m_state_requested, m_state_cur; + uint16_t m_packet_id; + + ringbuffer m_output_buf; + ringbuffer m_input_buf; + + std::unique_ptr m_hdd; + + asio::io_service m_io_service; + std::thread m_thread; +}; + +DECLARE_DEVICE_TYPE(KONAMI_573_NETWORK_PCB_UNIT_PCCARD, konami_573_network_pcb_unit_pccard_device) + +#endif // MAME_KONAMI_K573NPU_CARD_H diff --git a/src/mame/konami/ksys573.cpp b/src/mame/konami/ksys573.cpp index 7b804d1982d7c..44dda673aac6c 100644 --- a/src/mame/konami/ksys573.cpp +++ b/src/mame/konami/ksys573.cpp @@ -345,6 +345,8 @@ Notes: (all ICs shown) #include "k573mcr.h" #include "k573msu.h" +#include "k573acio.h" +#include "k573cardunit.h" #include "k573martial.h" #include "k573rental.h" @@ -374,6 +376,8 @@ Notes: (all ICs shown) #include "cdrom.h" +#include "k573npu_card.h" + #include #include "pnchmn.lh" @@ -554,6 +558,7 @@ class ksys573_state : public driver_device void pccard1_32mb(machine_config &config); void pccard2_32mb(machine_config &config); void pccard2_64mb(machine_config &config); + void pccard2_npu(machine_config &config); void cassx(machine_config &config); void cassxi(machine_config &config); void cassy(machine_config &config); @@ -2640,6 +2645,12 @@ void ksys573_state::pccard2_64mb(machine_config &config) m_pccard2->set_default_option("konami_dual"); } +void ksys573_state::pccard2_npu(machine_config &config) +{ + m_pccard2->option_add("npucard", KONAMI_573_NETWORK_PCB_UNIT_PCCARD); + m_pccard2->set_default_option("npucard"); +} + // Security eeprom variants // // Suffixes are used to select them @@ -2698,10 +2709,16 @@ void ksys573_state::zi_cassette_install(device_t* device) void ksys573_state::toggle_serial(int state) { + // printf("toggle serial %d\n", state); + // This switches between the MSU and card reader devices on the network port of the security cart auto duart_chan = subdevice("k573msu:duart_com_0:chan1"); if (duart_chan != nullptr) duart_chan->set_clock_scale(!state); + + auto acio_host = subdevice("rs232_network:k573acio_host"); + if (acio_host != nullptr) + acio_host->set_clock_scale(state); } void ksys573_state::cassxzi(machine_config &config) @@ -2811,7 +2828,8 @@ void ddr_state::ddr5m(machine_config &config) k573d(config); m_k573dio->output_callback().set(FUNC(ddr_state::ddr_output_callback)); - pccard2_32mb(config); + pccard1_32mb(config); + pccard2_npu(config); casszi(config); KONAMI_573_MEMORY_CARD_READER(config, "k573mcr", 0, m_sys573_jvs_host); @@ -2962,6 +2980,23 @@ void ksys573_state::msu_remote(machine_config &config) sio1->txd_handler().set(rs232_network, FUNC(rs232_port_device::write_txd)); sio1->dtr_handler().set(rs232_network, FUNC(rs232_port_device::write_dtr)); rs232_network.rxd_handler().set(*sio1, FUNC(psxsio1_device::write_rxd)); + rs232_network.option_add("k573acio_host", KONAMI_573_ACIO_HOST); + rs232_network.set_default_option("k573acio_host"); + + KONAMI_573_MAGNETIC_CARD_READER(config, "icca1", 0); + KONAMI_573_MAGNETIC_CARD_READER(config, "icca2", 0); + + rs232_network.set_option_machine_config("k573acio_host", [this](device_t *device) { + auto &host = *downcast(device); + + auto icca1dev = downcast(subdevice("icca1")); + if (icca1dev != nullptr) + host.add_device(icca1dev); + + auto icca2dev = downcast(subdevice("icca2")); + if (icca2dev != nullptr) + host.add_device(icca2dev); + }); } void ksys573_state::drmn(machine_config &config) @@ -3066,6 +3101,8 @@ void ksys573_state::gtrfrk7m(machine_config &config) casszi(config); pccard1_32mb(config); msu_remote(config); + + pccard2_npu(config); } void ksys573_state::gtfrk10m(machine_config &config) @@ -3076,6 +3113,7 @@ void ksys573_state::gtfrk10m(machine_config &config) msu_remote(config); // KONAMI_573_NETWORK_PCB_UNIT(config, "k573npu", 0); + pccard2_npu(config); } void ksys573_state::gtfrk11m(machine_config &config) @@ -3084,6 +3122,9 @@ void ksys573_state::gtfrk11m(machine_config &config) casszi(config); pccard1_32mb(config); msu_remote(config); + + // KONAMI_573_NETWORK_PCB_UNIT(config, "k573npu", 0); + pccard2_npu(config); } // Miscellaneous @@ -6569,6 +6610,19 @@ ROM_START( kicknkick ) ROM_LOAD( "a36eaa.27h", 0x000000, 0x200000, CRC(1179ab7b) SHA1(19a316cacb6eb87b905884091820e6b53aef64b7) ) ROM_END +ROM_START( k573diotester ) + SYS573_BIOS_A + + ROM_REGION( 0x000008c, "cassette:game:eeprom", 0 ) + ROM_LOAD( "gcb19ja.u1", 0x000000, 0x00008c, BAD_DUMP CRC(680a3288) SHA1(b413c6c43c4a18c5c713049a9c2fbde2d98e36bc) ) + + ROM_REGION( 0x000008, "cassette:game:id", 0 ) + ROM_LOAD( "gcb19ja.u6", 0x000000, 0x000008, BAD_DUMP CRC(ce84419e) SHA1(839e8ee080ecfc79021a06417d930e8b32dfc6a1) ) + + DISK_REGION( "runtime" ) + DISK_IMAGE_READONLY( "k573diotester", 0, BAD_DUMP ) +ROM_END + } // anonymous namespace @@ -6793,3 +6847,5 @@ GAME( 2018, ddrexpro, sys573, ddr5m, ddr, ddr_state, empty_ini GAME( 2019, ddrexproc, sys573, ddr5m, ddr, ddr_state, empty_init, ROT0, "hack", "Dance Dance Revolution Extreme Clarity (hack)", MACHINE_IMPERFECT_SOUND ) GAME( 2019, ddrexplus, sys573, ddrexplus, ddr, ddr_state, empty_init, ROT0, "hack", "Dance Dance Revolution Extreme Plus (hack)", MACHINE_IMPERFECT_SOUND ) GAME( 200?, ddrmegamix,sys573, ddr5m, ddr, ddr_state, empty_init, ROT0, "hack", "Dance Dance Revolution Megamix (hack)", MACHINE_IMPERFECT_SOUND ) + +GAME( 2024, k573diotester, ddrmax, ddr5m, ddr, ddr_state, empty_init, ROT0, "hack", "System 573 Digital I/O Tester", 0 ) diff --git a/src/mame/mame.lst b/src/mame/mame.lst index 2fc76991bef26..b023a0cc02b04 100644 --- a/src/mame/mame.lst +++ b/src/mame/mame.lst @@ -24153,6 +24153,7 @@ toysmarch // (c) 2005 Konami. Toy's March (E00:J:A:A:20050 toysmarch2 // (c) 2005 Konami. Toy's March 2 (F00:J:A:A:2005110400) @source:konami/ksys573.cpp +k573diotester animechmp // 2000 - Anime Champ (GCA07 VER. JAA) bassang2 // 1998 - Bass Angler 2 (GE865 VER. JAA) bassangl // 1998 - Bass Angler (GE765 VER. JAA)