cps2.cpp: Added TOURNAMENT board communication simulation. (#9699) [Vas Crabb, Darksoft, Gregory Lewandowski]

This commit is contained in:
Vas Crabb 2022-05-06 14:03:57 +10:00 committed by GitHub
parent aadf8cb09f
commit fe8e132efa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1941 additions and 1013 deletions

View File

@ -721,6 +721,7 @@ def write_project(options, projectfile, mappings, sources):
' MAME_DIR .. "src/lib/netlist",\n' \
' MAME_DIR .. "3rdparty",\n' \
' GEN_DIR .. "mame/layout",\n' \
' ext_includedir("asio"),\n' \
' ext_includedir("flac"),\n' \
' ext_includedir("glm"),\n' \
' ext_includedir("jpeg"),\n' \

View File

@ -1007,6 +1007,7 @@ function createMAMEProjects(_target, _subtarget, _name)
GEN_DIR .. "mame/layout",
}
includedirs {
ext_includedir("asio"),
ext_includedir("flac"),
ext_includedir("glm"),
ext_includedir("jpeg"),
@ -1543,6 +1544,8 @@ files {
MAME_DIR .. "src/mame/drivers/cps1bl_pic.cpp",
MAME_DIR .. "src/mame/drivers/kenseim.cpp",
MAME_DIR .. "src/mame/drivers/cps2.cpp",
MAME_DIR .. "src/mame/machine/cps2comm.cpp",
MAME_DIR .. "src/mame/machine/cps2comm.h",
MAME_DIR .. "src/mame/machine/cps2crypt.cpp",
MAME_DIR .. "src/mame/machine/cps2crypt.h",
MAME_DIR .. "src/mame/drivers/cps3.cpp",

File diff suppressed because it is too large Load Diff

View File

@ -11,18 +11,20 @@
#pragma once
#include "sound/msm5205.h"
#include "sound/qsound.h"
#include "sound/okim6295.h"
#include "machine/gen_latch.h"
#include "cpu/m68000/m68000.h"
#include "machine/74157.h"
#include "machine/gen_latch.h"
#include "machine/timekpr.h"
#include "machine/timer.h"
#include "cpu/m68000/m68000.h"
#include "sound/msm5205.h"
#include "sound/okim6295.h"
#include "sound/qsound.h"
#include "emupal.h"
#include "screen.h"
#include "tilemap.h"
// Video raw params
// measured clocks:
// CPS2(Guru): V = 59.6376Hz, H = 15,4445kHz *H is probably measured too low!
@ -314,109 +316,6 @@ protected:
output_finder<3> m_led_cboard;
};
class cps2_state : public cps_state
{
public:
cps2_state(const machine_config &mconfig, device_type type, const char *tag)
: cps_state(mconfig, type, tag, 2)
, m_decrypted_opcodes(*this, "decrypted_opcodes")
, m_region_key(*this, "key")
, m_qsound(*this, "qsound")
, m_objram1(*this, "objram1")
, m_objram2(*this, "objram2")
, m_output(*this, "output")
, m_io_in0(*this, "IN0")
, m_io_in1(*this, "IN1")
, m_cps2_dial_type(0)
, m_ecofghtr_dial_direction0(0)
, m_ecofghtr_dial_direction1(0)
, m_ecofghtr_dial_last0(0)
, m_ecofghtr_dial_last1(0)
{ }
void cps2(machine_config &config);
void gigaman2(machine_config &config);
void dead_cps2(machine_config &config);
void init_cps2_video();
void init_cps2();
void init_cps2nc();
void init_cps2crypt();
void init_gigaman2();
void init_ssf2tb();
void init_pzloop2();
void init_singbrd();
void init_ecofghtr();
private:
void init_digital_volume();
uint16_t gigaman2_dummyqsound_r(offs_t offset);
void gigaman2_dummyqsound_w(offs_t offset, uint16_t data);
void gigaman2_gfx_reorder();
void cps2_eeprom_port_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
uint16_t cps2_qsound_volume_r();
uint16_t kludge_r();
uint16_t joy_or_paddle_r();
uint16_t joy_or_paddle_ecofghtr_r();
TIMER_DEVICE_CALLBACK_MEMBER(cps2_interrupt);
TIMER_CALLBACK_MEMBER(cps2_update_digital_volume);
void cps2_objram_bank_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
uint16_t cps2_objram1_r(offs_t offset);
uint16_t cps2_objram2_r(offs_t offset);
void cps2_objram1_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
void cps2_objram2_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
void unshuffle(uint64_t *buf, int len);
void cps2_gfx_decode();
virtual void find_last_sprite() override;
void cps2_render_sprites(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int *primasks);
void cps2_set_sprite_priorities();
void cps2_objram_latch();
uint16_t *cps2_objbase();
virtual void render_layers(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) override;
uint32_t screen_update_cps2(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
DECLARE_MACHINE_START(cps2);
virtual void video_start() override;
void cps2_map(address_map &map);
void dead_cps2_map(address_map &map);
void decrypted_opcodes_map(address_map &map);
optional_shared_ptr<uint16_t> m_decrypted_opcodes;
optional_memory_region m_region_key;
optional_device<qsound_device> m_qsound;
required_shared_ptr<uint16_t> m_objram1;
required_shared_ptr<uint16_t> m_objram2;
required_shared_ptr<uint16_t> m_output;
optional_ioport m_io_in0;
optional_ioport m_io_in1;
std::unique_ptr<uint16_t[]> m_cps2_buffered_obj;
std::unique_ptr<uint16_t[]> m_gigaman2_dummyqsound_ram;
/* video-related */
int m_cps2_last_sprite_offset = 0; /* Offset of the last sprite */
int m_pri_ctrl = 0; /* Sprite layer priorities */
int m_objram_bank = 0;
int m_cps2_obj_size = 0;
/* misc */
int m_readpaddle = 0; // pzloop2
int m_cps2networkpresent = 0;
int m_cps2digitalvolumelevel = 0;
int m_cps2disabledigitalvolume = 0;
emu_timer *m_digital_volume_timer = nullptr;
int m_cps2_dial_type = 0;
int m_ecofghtr_dial_direction0 = 0;
int m_ecofghtr_dial_direction1 = 0;
int m_ecofghtr_dial_last0 = 0;
int m_ecofghtr_dial_last1 = 0;
};
/*----------- defined in drivers/cps1.cpp -----------*/

View File

@ -0,0 +1,846 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
/***********************************************************************
High-level simulation of communication using the TOURNAMENT board.
(Thanks to Darksoft for tracing out the circuit board, and Gregory
Lewandowski for writing a proof of concept implementation.)
This board has two sections, TOURNAMENT and REGISTER, but has only
ever been seen with the TOURNAMENT section populated. There are two
mini-DIN connectors: a black one labelled "IN" and grey one labelled
"OUT". Each connector provides an RS-232 input and output, all of
which are connected to a MAX232 providing level translation.
Systems are daisy-chained, out to in, with a loopback connector on
the IN port at the end of the chain.
A NEC µPD71051C USART handles serialisation/framing. The game
always selects asynchronous mode, x16 transmit/receive clock, 8 data
bits, no parity, and one transmitted stop bit. The transmit/receive
clock is generated by dividing down the 8 MHz clock with a pair of
cascaded counters counters.
A PAL16L8BCN is used for routing signals. It's purely combinatorial
with the three control bits latched separately. It controls the
transmit/receive clock, the output signals on the IN and OUT ports,
and the RxD input to the USART. D4 controls the transmit/receive
clock (halves the data rate when set). D1 ad D0 control how the
serial data is routed. The equations are effectively:
RxCLK/TxCLK <= D4 ? Qd : Qc
RxD <=
Tin1 <= !D0 ? 1 : D1 ? TxD : (TxD & Rout2)
Tin2 <= (!D1 & D0) ? Rout1 : (D1 & !D0) ? TxD : 1
In tabular form:
D1 D0 RxD Tin1 Tin2
0 0 1 1 1
0 1 Rout1 TxD & Rout2 Rout1
1 0 Rout2 1 TxD
1 1 Rout1 TxD 1
Mode 00 disables communication. Mode 01 connects the USART to the
IN port, also forwarding signals between the IN and OUT ports. Mode
10 connects the USART to the OUT port and disconnects the IN port.
Mode 11 connects the USART to the IN port and disconnects the OUT
port.
Mode 11 is used by the game while discovering its position in the
daisy chain. Mode 10 is used during normal operation on the system
at the head of the chain (with the loopback connector on its IN
port). Mode 01 is used during normal operation on the remaining
systems in the daisy chain.
There's no flow control or collision detection in the upstream
direction. It relies on the fact that the games only transmit
briefly so collisions are statistically unlikely.
To use this, enable communication in the Machine Configuration menu
and use the communication settings to daisy-chain for systems. The
system at the head of the daisy chain must have the comm_remotehost
option set to an empty string. For example the four instances could
be run as follows on a single machine - change the comm_remotehost
options as necessary to connect multiple machines:
mame ssf2tb -comm_localhost 0.0.0.0 -comm_localport 1234 -comm_remotehost ""
mame ssf2tb -comm_localhost 0.0.0.0 -comm_localport 1235 -comm_remotehost 127.0.0.1 -comm_remoteport 1234
mame ssf2tb -comm_localhost 0.0.0.0 -comm_localport 1236 -comm_remotehost 127.0.0.1 -comm_remoteport 1235
mame ssf2tb -comm_localhost "" -comm_remotehost 127.0.0.1 -comm_remoteport 1236
TODO:
* When the USART's RxRDY output is asserted, CPU-IPL2 will be
asserted (via CPSB-OBJUP). The game doesn't seem to require this
to work.
***********************************************************************/
#include "emu.h"
#include "cps2comm.h"
#include "emuopts.h"
#include "asio.h"
#include <atomic>
#include <algorithm>
#include <array>
#include <chrono>
#include <iostream>
#include <iterator>
#include <locale>
#include <optional>
#include <sstream>
#include <thread>
#include <utility>
//#define VERBOSE 1
#include "logmacro.h"
namespace {
INPUT_PORTS_START(cps2comm)
PORT_START("CFG")
PORT_CONFNAME(0x01, 0x00, "Communication")
PORT_CONFSETTING( 0x00, "Disable")
PORT_CONFSETTING( 0x01, "Enable")
INPUT_PORTS_END
} // anonymous namespace
class cps2_comm_device::context
{
public:
context(
cps2_comm_device &device,
std::optional<asio::ip::tcp::endpoint> const &local,
std::optional<asio::ip::tcp::endpoint> const &remote) :
m_device(device),
m_acceptor(m_ioctx),
m_in_sock(m_ioctx),
m_out_sock(m_ioctx),
m_connect_timeout(m_ioctx),
m_local(local),
m_remote(remote),
m_route(0U),
m_stopping(false),
m_in_connected(false),
m_usart_wp(0U),
m_usart_rp(0U)
{
}
std::error_code start()
{
std::error_code err;
if (m_remote)
{
m_in_sock.open(m_remote->protocol(), err);
if (err)
return err;
}
if (m_local)
{
m_acceptor.open(m_local->protocol(), err);
if (!err)
m_acceptor.bind(*m_local, err);
if (!err)
m_acceptor.listen(1, err);
if (err)
return err;
}
m_thread = std::thread(
[this] ()
{
if (m_local)
start_accept();
if (m_remote)
start_connect();
m_ioctx.run();
LOG("Network thread completed\n");
});
return err;
}
void stop()
{
m_ioctx.post(
[this] ()
{
m_stopping = true;
std::error_code err;
if (m_acceptor.is_open())
m_acceptor.close(err);
if (m_in_sock.is_open())
m_in_sock.close(err);
if (m_out_sock.is_open())
m_out_sock.close(err);
m_connect_timeout.cancel();
});
m_thread.join();
}
void set_routing(u8 val)
{
m_ioctx.post(
[this, val] ()
{
if (BIT(val, 0, 2) == 0U)
{
unsigned rp = m_usart_rp.load(std::memory_order_relaxed);
m_usart_wp.store(rp, std::memory_order_release);
}
m_route = val;
});
}
bool usart_get(u8 &data)
{
unsigned const rp = m_usart_rp.load(std::memory_order_relaxed);
unsigned const wp = m_usart_wp.load(std::memory_order_acquire);
if (rp == wp)
return false;
data = m_usart_buf[rp];
m_usart_rp.store((rp + 1) % m_usart_buf.size(), std::memory_order_release);
return true;
}
void send_in(u8 data)
{
m_ioctx.post(
[this, data] ()
{
if (m_in_connected)
{
bool const sending = !m_in_snd_buf.empty();
m_in_snd_buf.append(&data, 1U);
if (!sending)
start_send_in();
}
});
}
void send_out(u8 data)
{
m_ioctx.post(
[this, data] ()
{
if (m_out_sock.is_open())
{
bool const sending = !m_out_snd_buf.empty();
m_out_snd_buf.append(&data, 1U);
if (!sending)
start_send_out();
}
});
}
private:
class buffer
{
public:
std::size_t append(u8 const *src, std::size_t len)
{
std::size_t used = 0U;
if (!m_full)
{
if (m_wp >= m_rp)
{
used = std::min<std::size_t>(m_buf.size() - m_wp, len);
std::copy_n(&src[0], used, &m_buf[m_wp]);
m_wp = (m_wp + used) % m_buf.size();
}
std::size_t const block = std::min<std::size_t>(len - used, m_rp - m_wp);
if (block)
{
std::copy_n(&src[used], block, &m_buf[m_wp]);
used += block;
m_wp += block;
}
m_full = m_wp == m_rp;
}
return used;
}
std::size_t consume(std::size_t len)
{
std::size_t const delta = std::min<std::size_t>(
m_full ? m_buf.size() : ((m_wp + m_buf.size() - m_rp) % m_buf.size()),
len);
m_rp = (m_rp + delta) % m_buf.size();
if (delta)
m_full = false;
return delta;
}
auto content() const
{
return asio::buffer(
&m_buf[m_rp],
((m_full || (m_wp < m_rp)) ? m_buf.size() : m_wp) - m_rp);
}
void clear() { m_wp = m_rp = 0U; }
bool empty() const { return !m_full && (m_wp == m_rp); }
private:
unsigned m_wp = 0U, m_rp = 0U;
bool m_full = false;
std::array<u8, 128> m_buf;
};
template <typename Format, typename... Params>
void logerror(Format &&fmt, Params &&... args) const
{
util::stream_format(
std::cerr,
"[%s] %s",
m_device.tag(),
util::string_format(std::forward<Format>(fmt), std::forward<Params>(args)...));
}
bool forwarding() const
{
return BIT(m_route, 0, 2) == 1U;
}
void usart_queue(u8 const *src, std::size_t len)
{
unsigned wp = m_usart_wp.load(std::memory_order_relaxed);
unsigned const rp = m_usart_rp.load(std::memory_order_acquire);
if (wp >= rp)
{
std::size_t const block = std::min<std::size_t>(m_usart_buf.size() - wp - (rp ? 0 : 1), len);
std::copy_n(src, block, &m_usart_buf[wp]);
src += block;
len -= block;
wp = (wp + block) % m_usart_buf.size();
}
if (wp < rp)
{
std::size_t const block = std::min<std::size_t>(rp - wp - 1, len);
std::copy_n(src, block, &m_usart_buf[wp]);
len -= block;
wp += block;
}
m_usart_wp.store(wp, std::memory_order_release);
if (len)
LOG("USART buffer full, dropping %u bytes\n", len);
}
void start_accept()
{
LOG("Accepting OUT port connection on %s\n", *m_local);
m_acceptor.async_accept(
[this] (std::error_code const &err, asio::ip::tcp::socket sock)
{
if (err)
{
LOG("Error accepting OUT port connection: %s\n", err.message());
if (!m_stopping)
start_accept();
}
else
{
LOG("Accepted OUT port connection from %s\n", sock.remote_endpoint());
m_out_sock = std::move(sock);
start_receive_out();
}
});
}
void start_connect()
{
LOG("Initiating IN port connection to %s\n", *m_remote);
m_connect_timeout.expires_after(std::chrono::seconds(10));
m_connect_timeout.async_wait(
[this] (std::error_code const &err)
{
if (!err && !m_stopping && !m_in_connected && m_in_sock.is_open())
{
LOG("IN port connection timed out\n");
schedule_reconnect();
}
});
m_in_sock.async_connect(
*m_remote,
[this] (std::error_code const &err)
{
m_connect_timeout.cancel();
if (err)
{
LOG("IN port connection error: %s\n", err.message());
if (!m_stopping)
schedule_reconnect();
}
else
{
LOG("IN port connection established\n");
m_in_connected = true;
start_receive_in();
}
});
}
void schedule_reconnect()
{
LOG("Scheduling IN port reconnection\n");
std::error_code err;
if (m_in_sock.is_open())
m_in_sock.close(err);
m_connect_timeout.cancel();
m_connect_timeout.expires_after(std::chrono::seconds(10));
m_connect_timeout.async_wait(
[this] (std::error_code const &err)
{
if (!err && !m_stopping)
{
std::error_code e;
m_in_sock.open(m_remote->protocol(), e);
if (e)
{
LOG("Error opening IN port socket: %s\n", e.message());
schedule_reconnect();
}
else
{
start_connect();
}
}
});
}
void start_receive_in()
{
m_in_sock.async_read_some(
asio::buffer(m_in_rcv_buf),
[this] (std::error_code const &err, std::size_t length)
{
if (err || !length)
{
if (err)
LOG("Error receiving from IN port connection: %s\n", err.message());
else
LOG("IN port connection lost\n");
m_in_connected = false;
m_in_snd_buf.clear();
if (!m_stopping)
schedule_reconnect();
}
else
{
if (BIT(m_route, 0))
usart_queue(&m_in_rcv_buf[0], length);
if (forwarding() && m_out_sock.is_open())
{
bool const sending = !m_out_snd_buf.empty();
m_out_snd_buf.append(&m_in_rcv_buf[0], length);
if (!sending)
start_send_out();
}
start_receive_in();
}
});
}
void start_receive_out()
{
m_out_sock.async_read_some(
asio::buffer(m_out_rcv_buf),
[this] (std::error_code const &err, std::size_t length)
{
if (err || !length)
{
if (err)
LOG("Error receiving from OUT port connection: %s\n", err.message());
else
LOG("OUT port connection lost\n");
m_out_snd_buf.clear();
if (!m_stopping)
{
std::error_code e;
m_out_sock.close(e);
start_accept();
}
}
else
{
if (BIT(m_route, 0, 2) == 2U)
usart_queue(&m_out_rcv_buf[0], length);
if (forwarding() && m_in_connected)
{
bool const sending = !m_in_snd_buf.empty();
m_in_snd_buf.append(&m_out_rcv_buf[0], length);
if (!sending)
start_send_in();
}
start_receive_out();
}
});
}
void start_send_in()
{
m_in_sock.async_write_some(
m_in_snd_buf.content(),
[this] (std::error_code const &err, std::size_t length)
{
m_in_snd_buf.consume(length);
if (err)
{
LOG("Error sending to IN port connection: %s\n", err.message().c_str());
m_in_connected = false;
m_in_snd_buf.clear();
m_in_sock.close();
}
else if (!m_in_snd_buf.empty())
{
start_send_in();
}
});
}
void start_send_out()
{
m_out_sock.async_write_some(
m_out_snd_buf.content(),
[this] (std::error_code const &err, std::size_t length)
{
m_out_snd_buf.consume(length);
if (err)
{
LOG("Error sending to OUT port connection: %s\n", err.message().c_str());
m_out_snd_buf.clear();
m_out_sock.close();
}
else if (!m_out_snd_buf.empty())
{
start_send_out();
}
});
}
cps2_comm_device &m_device;
std::thread m_thread;
asio::io_context m_ioctx;
asio::ip::tcp::acceptor m_acceptor;
asio::ip::tcp::socket m_in_sock;
asio::ip::tcp::socket m_out_sock;
asio::steady_timer m_connect_timeout;
std::optional<asio::ip::tcp::endpoint> const m_local;
std::optional<asio::ip::tcp::endpoint> const m_remote;
u8 m_route;
bool m_stopping;
bool m_in_connected;
std::atomic_uint m_usart_wp;
std::atomic_uint m_usart_rp;
buffer m_in_snd_buf;
buffer m_out_snd_buf;
std::array<u8, 128> m_in_rcv_buf;
std::array<u8, 128> m_out_rcv_buf;
std::array<u8, 128> m_usart_buf;
};
DEFINE_DEVICE_TYPE(CAPCOM_CPS2_COMM, cps2_comm_device, "cps2comm", "Capcom CPS-2 communication board")
cps2_comm_device::cps2_comm_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
device_t(mconfig, CAPCOM_CPS2_COMM, tag, owner, clock),
m_config(*this, "CFG")
{
}
u16 cps2_comm_device::usart_data_r(offs_t offset, u16 mem_mask)
{
if (!BIT(m_usart_status, 1) && m_context && !machine().side_effects_disabled() && m_context->usart_get(m_usart_rx_data))
m_usart_status |= 0x02U;
LOG("%s: Read USART data %04X & 0x%04X\n", machine().describe_context(), m_usart_rx_data, mem_mask);
if (ACCESSING_BITS_0_7 && !machine().side_effects_disabled())
m_usart_status &= ~0x02U;
return m_usart_rx_data;
}
u16 cps2_comm_device::usart_status_r(offs_t offset, u16 mem_mask)
{
// 0: TxRDY transmit ready
// 1: RxRDY receive ready
// 2: TxEMP transmit buffers empty (TxD idle)
// 3: PE receive parity error
// 4: OVE receive buffer overrun error
// 5: FE framing error
// 6: SYNC/BRK synchronisation/break detect
// 7: DSR inverted input
if (!BIT(m_usart_status, 1) && m_context && !machine().side_effects_disabled() && m_context->usart_get(m_usart_rx_data))
m_usart_status |= 0x02U;
LOG("%s: Read USART status %04X & 0x%04X\n", machine().describe_context(), m_usart_status, mem_mask);
return m_usart_status;
}
void cps2_comm_device::usart_data_w(offs_t offset, u16 data, u16 mem_mask)
{
LOG("%s: Write USART data 0x%04X & 0x%04X\n", machine().describe_context(), data, mem_mask);
if (ACCESSING_BITS_0_7 && m_context)
{
switch (BIT(m_route, 0, 2))
{
case 0:
break;
case 1:
case 3:
if (m_is_head)
{
m_usart_rx_data = data & 0x00ffU;
if (BIT(m_usart_status, 1))
m_usart_status |= 0x10U; // set OVE
else
m_usart_status |= 0x02U; // set RxRDY
}
else
{
m_context->send_in(data & 0x00ffU);
}
break;
case 2:
m_context->send_out(data & 0x00ffU);
break;
}
}
}
void cps2_comm_device::usart_control_w(offs_t offset, u16 data, u16 mem_mask)
{
if (ACCESSING_BITS_0_7)
{
switch (m_usart_control_phase)
{
case CTRL_MODE:
m_usart_mode = data & 0x00ffU;
if (!BIT(m_usart_mode, 0, 2))
{
LOG("%s: USART mode = 0x%02X: Synchronous, %d-bit, %s Parity, %s Sync Detect, %d Sync Characters\n",
machine().describe_context(),
m_usart_mode,
std::array{ 5, 6, 7, 8 }[BIT(m_usart_mode, 2, 2)],
std::array{ "No", "Odd", "No", "Even" }[BIT(m_usart_mode, 4, 2)],
BIT(m_usart_mode, 6) ? "External" : "Internal",
BIT(m_usart_mode, 7) ? 1 : 2);
m_usart_control_phase = CTRL_SYNC1;
}
else
{
LOG("%s: USART mode = 0x%02X: Asynchronous, x %d Clock, %d-bit, %s Parity, %s Transmit Stop Bits\n",
machine().describe_context(),
m_usart_mode,
std::array{ -1, 1, 16, 64 }[BIT(m_usart_mode, 0, 2)],
std::array{ 5, 6, 7, 8 }[BIT(m_usart_mode, 2, 2)],
std::array{ "No", "Odd", "No", "Even" }[BIT(m_usart_mode, 4, 2)],
std::array{ "Illegal", "1", "1-1/2", "2" }[BIT(m_usart_mode, 6, 2)]);
m_usart_control_phase = CTRL_COMMAND;
m_usart_status |= 0x01U;
}
break;
case CTRL_SYNC1:
m_usart_sync_char[0] = data & 0x00ffU;
LOG("%s: USART sync character 1 = 0x%02X\n", machine().describe_context(), m_usart_sync_char[0]);
if (BIT(m_usart_mode, 7))
{
m_usart_control_phase = CTRL_COMMAND;
m_usart_status |= 0x01U;
}
else
{
m_usart_control_phase = CTRL_SYNC2;
}
break;
case CTRL_SYNC2:
m_usart_sync_char[1] = data & 0x00ffU;
LOG("%s: USART sync character 1 = 0x%02X\n", machine().describe_context(), m_usart_sync_char[1]);
m_usart_control_phase = CTRL_COMMAND;
m_usart_status |= 0x01U;
break;
case CTRL_COMMAND:
LOG("%s: USART command 0x%02X%s /DTR=%u%s%s%s /RTS=%u%s%s\n",
machine().describe_context(),
data & 0x00ff,
BIT(data, 0) ? " TxEN" : "",
BIT(~data, 1),
BIT(data, 2) ? " RxEN" : "",
BIT(data, 3) ? " SBRK" : "",
BIT(data, 4) ? " ECL" : "",
BIT(~data, 5),
BIT(data, 6) ? " SRES" : "",
BIT(data, 7) ? " EH" : "");
if (BIT(data, 4))
m_usart_status &= ~0x38U;
if (BIT(data, 6))
{
m_usart_control_phase = CTRL_MODE;
m_usart_status = 0x00U;
}
break;
}
}
}
void cps2_comm_device::route_w(offs_t offset, u16 data, u16 mem_mask)
{
if (ACCESSING_BITS_0_7)
{
m_route = data & 0x00ffU;
if (m_context)
m_context->set_routing(m_route);
LOG("%s: Write routing control 0x%04X: %s data rate, %s -> IN, %s -> OUT, %s -> RxD\n",
machine().describe_context(),
m_route,
BIT(m_route, 4) ? "low" : "high",
std::array{ "1", "TxD & OUT", "1", "TxD" }[BIT(m_route, 0, 2)],
std::array{ "1", "IN", "TxD", "1" }[BIT(m_route, 0, 2)],
std::array{ "1", "IN", "OUT", "IN" }[BIT(m_route, 0, 2)]);
}
}
ioport_constructor cps2_comm_device::device_input_ports() const
{
return INPUT_PORTS_NAME(cps2comm);
}
void cps2_comm_device::device_start()
{
m_usart_mode = 0x00U;
std::fill(std::begin(m_usart_sync_char), std::end(m_usart_sync_char), 0U);
m_usart_rx_data = 0U;
m_is_head = false;
save_item(NAME(m_usart_control_phase));
save_item(NAME(m_usart_mode));
save_item(NAME(m_usart_sync_char));
save_item(NAME(m_usart_status));
save_item(NAME(m_usart_rx_data));
save_item(NAME(m_route));
}
void cps2_comm_device::device_reset()
{
if (!m_context)
start_comm();
m_usart_control_phase = CTRL_MODE;
m_usart_status = 0x00U;
m_route = 0x00U;
if (m_context)
m_context->set_routing(m_route);
}
void cps2_comm_device::device_stop()
{
if (m_context)
{
m_context->stop();
m_context.reset();
}
}
void cps2_comm_device::start_comm()
{
if (!BIT(m_config->read(), 0))
return;
auto const &opts = mconfig().options();
std::error_code err;
std::istringstream parsestr;
parsestr.imbue(std::locale::classic());
std::optional<asio::ip::tcp::endpoint> local;
if (*opts.comm_localhost())
{
asio::ip::address const bindaddr = asio::ip::make_address(opts.comm_localhost(), err);
if (err)
{
osd_printf_error("[%s] invalid local IP address %s, disabling communication.\n", tag(), opts.comm_localhost());
return;
}
parsestr.str(opts.comm_localport());
parsestr.seekg(0, std::ios_base::beg);
asio::ip::port_type bindport;
parsestr >> bindport;
if (!parsestr || !bindport)
{
osd_printf_error("[%s] invalid local TCP port %s, disabling communication.\n", tag(), opts.comm_localport());
return;
}
local.emplace(bindaddr, bindport);
}
std::optional<asio::ip::tcp::endpoint> remote;
if (*opts.comm_remotehost())
{
asio::ip::address const connaddr = asio::ip::make_address(opts.comm_remotehost(), err);
if (err)
{
osd_printf_error("[%s] invalid remote address %s, disabling communication.\n", tag(), opts.comm_remotehost());
return;
}
parsestr.str(opts.comm_remoteport());
parsestr.seekg(0, std::ios_base::beg);
asio::ip::port_type connport;
parsestr >> connport;
if (!parsestr || !connport)
{
osd_printf_error("[%s] invalid remote TCP port %s, disabling communication.\n", tag(), opts.comm_remoteport());
return;
}
remote.emplace(connaddr, connport);
}
else
{
m_is_head = true;
}
if (!local && !remote)
{
osd_printf_error("[%s] no TCP addresses configured, disabling communication.\n", tag());
return;
}
auto ctx = std::make_unique<context>(*this, local, remote);
err = ctx->start();
if (err)
{
osd_printf_error("[%s] error opening/binding sockets, disabling communication (%s).\n", tag(), err.message());
return;
}
m_context = std::move(ctx);
}

View File

@ -0,0 +1,58 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
#ifndef MAME_MACHINE_CPS2COMM_H
#define MAME_MACHINE_CPS2COMM_H
#pragma once
#include <memory>
class cps2_comm_device : public device_t
{
public:
cps2_comm_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
u16 usart_data_r(offs_t offset, u16 mem_mask);
u16 usart_status_r(offs_t offset, u16 mem_mask);
void usart_data_w(offs_t offset, u16 data, u16 mem_mask);
void usart_control_w(offs_t offset, u16 data, u16 mem_mask);
void route_w(offs_t offset, u16 data, u16 mem_mask);
bool comm_enabled() const { return bool(m_context); }
protected:
enum
{
CTRL_MODE,
CTRL_SYNC1,
CTRL_SYNC2,
CTRL_COMMAND
};
class context;
virtual ioport_constructor device_input_ports() const override ATTR_COLD;
virtual void device_start() override ATTR_COLD;
virtual void device_stop() override ATTR_COLD;
virtual void device_reset() override ATTR_COLD;
void start_comm() ATTR_COLD;
std::unique_ptr<context> m_context;
required_ioport m_config;
bool m_is_head;
u8 m_usart_control_phase;
u8 m_usart_mode;
u8 m_usart_sync_char[2];
u8 m_usart_status;
u8 m_usart_rx_data;
u8 m_route;
};
DECLARE_DEVICE_TYPE(CAPCOM_CPS2_COMM, cps2_comm_device)
#endif // MAME_MACHINE_CPS2COMM_H

View File

@ -113,18 +113,22 @@ the decryption keys.
*******************************************************************************/
#include "emu.h"
#include "cpu/m68000/m68000.h"
#include "ui/uimain.h"
#include "includes/cps1.h"
#include "cpu/m68000/m68000.h"
#include "ui/uimain.h"
namespace {
/******************************************************************************/
static const int fn1_groupA[8] = { 10, 4, 6, 7, 2, 13, 15, 14 };
static const int fn1_groupB[8] = { 0, 1, 3, 5, 8, 9, 11, 12 };
const int fn1_groupA[8] = { 10, 4, 6, 7, 2, 13, 15, 14 };
const int fn1_groupB[8] = { 0, 1, 3, 5, 8, 9, 11, 12 };
static const int fn2_groupA[8] = { 6, 0, 2, 13, 1, 4, 14, 7 };
static const int fn2_groupB[8] = { 3, 5, 9, 10, 8, 15, 12, 11 };
const int fn2_groupA[8] = { 6, 0, 2, 13, 1, 4, 14, 7 };
const int fn2_groupB[8] = { 3, 5, 9, 10, 8, 15, 12, 11 };
/******************************************************************************/
@ -134,6 +138,19 @@ static const int fn2_groupB[8] = { 3, 5, 9, 10, 8, 15, 12, 11 };
struct sbox
{
int extract_inputs(uint32_t val) const
{
int res = 0;
for (int i = 0; i < 6; ++i)
{
if (inputs[i] >= 0)
res |= BIT(val, inputs[i]) << i;
}
return res;
}
const uint8_t table[64];
const int inputs[6]; // positions of the inputs bits, -1 means no input except from key
const int outputs[2]; // positions of the output bits
@ -142,14 +159,40 @@ struct sbox
// the above struct better defines how the hardware works, however
// to speed up the decryption at run time we convert it to the
// following one
struct optimised_sbox
class optimised_sbox
{
public:
void optimise(sbox const &in)
{
// precalculate the input lookup
for (int i = 0; i < 256; ++i)
input_lookup[i] = in.extract_inputs(i);
// precalculate the output masks
for (int i = 0; i < 64; ++i)
{
int const o = in.table[i];
output[i] = 0;
if (o & 1)
output[i] |= 1 << in.outputs[0];
if (o & 2)
output[i] |= 1 << in.outputs[1];
}
}
uint8_t fn(uint8_t in, uint32_t key) const
{
return output[input_lookup[in] ^ (key & 0x3f)];
}
private:
uint8_t input_lookup[256];
uint8_t output[64];
};
static const struct sbox fn1_r1_boxes[4] =
const sbox fn1_r1_boxes[4] =
{
{ // subkey bits 0- 5
{
@ -185,7 +228,7 @@ static const struct sbox fn1_r1_boxes[4] =
},
};
static const struct sbox fn1_r2_boxes[4] =
const sbox fn1_r2_boxes[4] =
{
{ // subkey bits 24-29
{
@ -221,7 +264,7 @@ static const struct sbox fn1_r2_boxes[4] =
},
};
static const struct sbox fn1_r3_boxes[4] =
const sbox fn1_r3_boxes[4] =
{
{ // subkey bits 48-53
{
@ -257,7 +300,7 @@ static const struct sbox fn1_r3_boxes[4] =
},
};
static const struct sbox fn1_r4_boxes[4] =
const sbox fn1_r4_boxes[4] =
{
{ // subkey bits 72-77
{
@ -295,7 +338,7 @@ static const struct sbox fn1_r4_boxes[4] =
/******************************************************************************/
static const struct sbox fn2_r1_boxes[4] =
const sbox fn2_r1_boxes[4] =
{
{ // subkey bits 0- 5
{
@ -331,7 +374,7 @@ static const struct sbox fn2_r1_boxes[4] =
},
};
static const struct sbox fn2_r2_boxes[4] =
const sbox fn2_r2_boxes[4] =
{
{ // subkey bits 24-29
{
@ -367,7 +410,7 @@ static const struct sbox fn2_r2_boxes[4] =
},
};
static const struct sbox fn2_r3_boxes[4] =
const sbox fn2_r3_boxes[4] =
{
{ // subkey bits 48-53
{
@ -403,7 +446,7 @@ static const struct sbox fn2_r3_boxes[4] =
},
};
static const struct sbox fn2_r4_boxes[4] =
const sbox fn2_r4_boxes[4] =
{
{ // subkey bits 72-77
{
@ -442,25 +485,20 @@ static const struct sbox fn2_r4_boxes[4] =
/******************************************************************************/
static uint8_t fn(uint8_t in, const struct optimised_sbox *sboxes, uint32_t key)
uint8_t fn(uint8_t in, const optimised_sbox *sboxes, uint32_t key)
{
const struct optimised_sbox *sbox1 = &sboxes[0];
const struct optimised_sbox *sbox2 = &sboxes[1];
const struct optimised_sbox *sbox3 = &sboxes[2];
const struct optimised_sbox *sbox4 = &sboxes[3];
return
sbox1->output[sbox1->input_lookup[in] ^ ((key >> 0) & 0x3f)] |
sbox2->output[sbox2->input_lookup[in] ^ ((key >> 6) & 0x3f)] |
sbox3->output[sbox3->input_lookup[in] ^ ((key >> 12) & 0x3f)] |
sbox4->output[sbox4->input_lookup[in] ^ ((key >> 18) & 0x3f)];
sboxes[0].fn(in, key >> 0) |
sboxes[1].fn(in, key >> 6) |
sboxes[2].fn(in, key >> 12) |
sboxes[3].fn(in, key >> 18);
}
// srckey is the 64-bit master key (2x32 bits)
// dstkey will contain the 96-bit key for the 1st FN (4x24 bits)
static void expand_1st_key(uint32_t *dstkey, const uint32_t *srckey)
void expand_1st_key(uint32_t *dstkey, const uint32_t *srckey)
{
static const int bits[96] =
{
@ -474,28 +512,27 @@ static void expand_1st_key(uint32_t *dstkey, const uint32_t *srckey)
20, 8, 55, 54, 59, 60,
27, 33, 35, 18, 8, 15,
63, 1, 50, 44, 16, 46,
5, 4, 45, 51, 38, 25,
5, 4, 45, 51, 38, 25,
13, 11, 62, 29, 48, 2,
59, 61, 62, 56, 51, 57,
54, 9, 24, 63, 22, 7,
26, 42, 45, 40, 23, 14,
2, 31, 52, 28, 44, 17,
2, 31, 52, 28, 44, 17,
};
int i;
dstkey[0] = 0;
dstkey[1] = 0;
dstkey[2] = 0;
dstkey[3] = 0;
for (i = 0; i < 96; ++i)
for (int i = 0; i < 96; ++i)
dstkey[i / 24] |= BIT(srckey[bits[i] / 32], bits[i] % 32) << (i % 24);
}
// srckey is the 64-bit master key (2x32 bits) XORed with the subkey
// dstkey will contain the 96-bit key for the 2nd FN (4x24 bits)
static void expand_2nd_key(uint32_t *dstkey, const uint32_t *srckey)
void expand_2nd_key(uint32_t *dstkey, const uint32_t *srckey)
{
static const int bits[96] =
{
@ -532,14 +569,14 @@ static void expand_2nd_key(uint32_t *dstkey, const uint32_t *srckey)
// seed is the 16-bit seed generated by the first FN
// subkey will contain the 64-bit key to be XORed with the master key
// for the 2nd FN (2x32 bits)
static void expand_subkey(uint32_t* subkey, uint16_t seed)
void expand_subkey(uint32_t* subkey, uint16_t seed)
{
// Note that each row of the table is a permutation of the seed bits.
static const int bits[64] =
{
5, 10, 14, 9, 4, 0, 15, 6, 1, 8, 3, 2, 12, 7, 13, 11,
5, 12, 7, 2, 13, 11, 9, 14, 4, 1, 6, 10, 8, 0, 15, 3,
4, 10, 2, 0, 6, 9, 12, 1, 11, 7, 15, 8, 13, 5, 14, 3,
5, 10, 14, 9, 4, 0, 15, 6, 1, 8, 3, 2, 12, 7, 13, 11,
5, 12, 7, 2, 13, 11, 9, 14, 4, 1, 6, 10, 8, 0, 15, 3,
4, 10, 2, 0, 6, 9, 12, 1, 11, 7, 15, 8, 13, 5, 14, 3,
14, 11, 12, 7, 4, 5, 2, 10, 1, 15, 0, 9, 8, 6, 13, 3,
};
int i;
@ -553,8 +590,8 @@ static void expand_subkey(uint32_t* subkey, uint16_t seed)
static uint16_t feistel(uint16_t val, const int *bitsA, const int *bitsB,
const struct optimised_sbox* boxes1, const struct optimised_sbox* boxes2, const struct optimised_sbox* boxes3, const struct optimised_sbox* boxes4,
uint16_t feistel(uint16_t val, const int *bitsA, const int *bitsB,
const optimised_sbox* boxes1, const optimised_sbox* boxes2, const optimised_sbox* boxes3, const optimised_sbox* boxes4,
uint32_t key1, uint32_t key2, uint32_t key3, uint32_t key4)
{
uint8_t l = bitswap<8>(val, bitsB[7],bitsB[6],bitsB[5],bitsB[4],bitsB[3],bitsB[2],bitsB[1],bitsB[0]);
@ -586,63 +623,24 @@ static uint16_t feistel(uint16_t val, const int *bitsA, const int *bitsB,
static int extract_inputs(uint32_t val, const int *inputs)
void optimise_sboxes(optimised_sbox* out, const sbox* in)
{
int i;
int res = 0;
for (i = 0; i < 6; ++i)
{
if (inputs[i] != -1)
res |= BIT(val, inputs[i]) << i;
}
return res;
for (int box = 0; box < 4; ++box)
out[box].optimise(in[box]);
}
} // anonymous namespace
static void optimise_sboxes(struct optimised_sbox* out, const struct sbox* in)
void cps2_decrypt(running_machine &machine, uint16_t *rom, uint16_t *dec, int length, const uint32_t *master_key, uint32_t lower_limit, uint32_t upper_limit)
{
int box;
for (box = 0; box < 4; ++box)
{
int i;
// precalculate the input lookup
for (i = 0; i < 256; ++i)
{
out[box].input_lookup[i] = extract_inputs(i, in[box].inputs);
}
// precalculate the output masks
for (i = 0; i < 64; ++i)
{
int o = in[box].table[i];
out[box].output[i] = 0;
if (o & 1)
out[box].output[i] |= 1 << in[box].outputs[0];
if (o & 2)
out[box].output[i] |= 1 << in[box].outputs[1];
}
}
}
static void cps2_decrypt(running_machine &machine, uint16_t *rom, uint16_t *dec, int length, const uint32_t *master_key, uint32_t lower_limit, uint32_t upper_limit)
{
int i;
uint32_t key1[4];
struct optimised_sbox sboxes1[4*4];
struct optimised_sbox sboxes2[4*4];
optimised_sbox sboxes1[4*4];
optimise_sboxes(&sboxes1[0*4], fn1_r1_boxes);
optimise_sboxes(&sboxes1[1*4], fn1_r2_boxes);
optimise_sboxes(&sboxes1[2*4], fn1_r3_boxes);
optimise_sboxes(&sboxes1[3*4], fn1_r4_boxes);
optimised_sbox sboxes2[4*4];
optimise_sboxes(&sboxes2[0*4], fn2_r1_boxes);
optimise_sboxes(&sboxes2[1*4], fn2_r2_boxes);
optimise_sboxes(&sboxes2[2*4], fn2_r3_boxes);
@ -650,6 +648,7 @@ static void cps2_decrypt(running_machine &machine, uint16_t *rom, uint16_t *dec,
// expand master key to 1st FN 96-bit key
uint32_t key1[4];
expand_1st_key(key1, master_key);
// add extra bits for s-boxes with less than 6 inputs
@ -661,28 +660,24 @@ static void cps2_decrypt(running_machine &machine, uint16_t *rom, uint16_t *dec,
key1[2] ^= BIT(key1[2], 1) << 5;
key1[2] ^= BIT(key1[2], 8) << 11;
for (i = 0; i < 0x10000; ++i)
for (int i = 0; i < 0x10000; ++i)
{
int a;
uint16_t seed;
uint32_t subkey[2];
uint32_t key2[4];
if ((i & 0xff) == 0)
{
char loadingMessage[256]; // for displaying with UI
sprintf(loadingMessage, "Decrypting %d%%", i*100/0x10000);
machine.ui().set_startup_text(loadingMessage,false);
machine.ui().set_startup_text(loadingMessage, false);
}
// pass the address through FN1
seed = feistel(i, fn1_groupA, fn1_groupB,
uint16_t const seed = feistel(i, fn1_groupA, fn1_groupB,
&sboxes1[0*4], &sboxes1[1*4], &sboxes1[2*4], &sboxes1[3*4],
key1[0], key1[1], key1[2], key1[3]);
// expand the result to 64-bit
uint32_t subkey[2];
expand_subkey(subkey, seed);
// XOR with the master key
@ -690,6 +685,7 @@ static void cps2_decrypt(running_machine &machine, uint16_t *rom, uint16_t *dec,
subkey[1] ^= master_key[1];
// expand key to 2nd FN 96-bit key
uint32_t key2[4];
expand_2nd_key(key2, subkey);
// add extra bits for s-boxes with less than 6 inputs
@ -704,7 +700,7 @@ static void cps2_decrypt(running_machine &machine, uint16_t *rom, uint16_t *dec,
// decrypt the opcodes
for (a = i; a < length/2; a += 0x10000)
for (int a = i; a < length/2; a += 0x10000)
{
if (a >= lower_limit && a <= upper_limit)
{
@ -719,66 +715,3 @@ static void cps2_decrypt(running_machine &machine, uint16_t *rom, uint16_t *dec,
}
}
}
struct game_keys
{
const char *name; /* game driver name */
const uint32_t keys[2];
uint32_t upper_limit;
};
void cps2_state::init_cps2crypt()
{
if (m_region_key)
{
unsigned short decoded[10] = { 0 };
for (int b = 0; b < 10 * 16; b++)
{
int bit = (317 - b) % 160;
if ((m_region_key->base()[bit / 8] >> ((bit ^ 7) % 8)) & 1)
{
decoded[b / 16] |= (0x8000 >> (b % 16));
}
}
uint32_t key[2] = { ((uint32_t)decoded[0] << 16) | decoded[1], ((uint32_t)decoded[2] << 16) | decoded[3] };
// decoded[4] == watchdog instruction third word
// decoded[5] == watchdog instruction second word
// decoded[6] == watchdog instruction first word
// decoded[7] == 0x4000 (bits 8 to 23 of CPS2 object output address)
// decoded[8] == 0x0900
uint32_t lower, upper;
if (decoded[9] == 0xffff)
{
// On a dead board, the only encrypted range is actually FF0000-FFFFFF.
// It doesn't start from 0, and it's the upper half of a 128kB bank.
upper = 0xffffff;
lower = 0xff0000;
}
else
{
upper = (((~decoded[9] & 0x3ff) << 14) | 0x3fff) + 1;
lower = 0;
}
logerror("cps2 decrypt 0x%08x,0x%08x,0x%08x,0x%08x\n", key[0], key[1], lower, upper);
// we have a proper key so use it to decrypt
cps2_decrypt(machine(), (uint16_t *)memregion("maincpu")->base(), m_decrypted_opcodes, memregion("maincpu")->bytes(), key, lower / 2, upper / 2);
}
}

View File

@ -1,7 +1,15 @@
// license:BSD-3-Clause
// copyright-holders:Paul Leaman, Andreas Naive, Nicola Salmoria,Charles MacDonald
/******************************************************************************
CPS-2 Encryption
*/
******************************************************************************/
#ifndef MAME_MACHINE_CPS2CRYPT_H
#define MAME_MACHINE_CPS2CRYPT_H
#pragma once
void cps2_decrypt(running_machine &machine, uint16_t *rom, uint16_t *dec, int length, const uint32_t *master_key, uint32_t lower_limit, uint32_t upper_limit);
#endif // MAME_MACHINE_CPS2CRYPT_H

View File

@ -2019,12 +2019,6 @@ static const struct CPS1config cps1_config_table[]=
CPS1 VIDEO RENDERER
*/
#define CPS2_OBJ_BASE 0x00 /* Unknown (not base address of objects). Could be bass address of bank used when object swap bit set? */
#define CPS2_OBJ_UK1 0x02 /* Unknown (nearly always 0x807d, or 0x808e when screen flipped) */
#define CPS2_OBJ_PRI 0x04 /* Layers priorities */
#define CPS2_OBJ_UK2 0x06 /* Unknown (usually 0x0000, 0x1101 in ssf2, 0x0001 in 19XX) */
#define CPS2_OBJ_XOFFS 0x08 /* X offset (usually 0x0040) */
#define CPS2_OBJ_YOFFS 0x0a /* Y offset (always 0x0010) */
MACHINE_RESET_MEMBER(cps_state,cps)
@ -2233,41 +2227,6 @@ void cps_state::cps1_cps_b_w(offs_t offset, uint16_t data, uint16_t mem_mask)
}
void cps2_state::unshuffle( uint64_t *buf, int len )
{
int i;
uint64_t t;
if (len == 2)
return;
assert(len % 4 == 0); /* must not happen */
len /= 2;
unshuffle(buf, len);
unshuffle(buf + len, len);
for (i = 0; i < len / 2; i++)
{
t = buf[len / 2 + i];
buf[len / 2 + i] = buf[len + i];
buf[len + i] = t;
}
}
void cps2_state::cps2_gfx_decode()
{
const int banksize = 0x200000;
int size = memregion("gfx")->bytes();
int i;
for (i = 0; i < size; i += banksize)
unshuffle((uint64_t *)(memregion("gfx")->base() + i), banksize / 8);
}
void cps_state::init_cps1()
{
m_scanline1 = 0;
@ -2277,21 +2236,6 @@ void cps_state::init_cps1()
}
void cps2_state::init_cps2_video()
{
cps2_gfx_decode();
m_scanline1 = 262;
m_scanline2 = 262;
m_scancalls = 0;
m_last_sprite_offset = 0;
m_cps2_last_sprite_offset = 0;
m_pri_ctrl = 0;
m_objram_bank = 0;
}
void cps_state::cps1_get_video_base()
{
int layercontrol=0, videocontrol=0, scroll1xoff=0, scroll2xoff=0, scroll3xoff=0;
@ -2672,22 +2616,6 @@ void cps_state::video_start()
machine().save().register_postload(save_prepost_delegate(FUNC(cps_state::cps1_get_video_base), this));
}
void cps2_state::video_start()
{
cps_state::video_start();
m_cps2_obj_size = 0x2000;
m_cps2_buffered_obj = make_unique_clear<uint16_t[]>(m_cps2_obj_size / 2);
memset(m_objram1, 0, m_cps2_obj_size);
memset(m_objram2, 0, m_cps2_obj_size);
save_item(NAME(m_cps2_last_sprite_offset));
save_pointer(NAME(m_cps2_buffered_obj), m_cps2_obj_size / 2);
save_item(NAME(m_pri_ctrl));
save_item(NAME(m_objram_bank));
}
/***************************************************************************
Build palette from palette RAM
@ -2964,231 +2892,6 @@ void cps_state::cps1_render_sprites( screen_device &screen, bitmap_ind16 &bitmap
void cps2_state::cps2_objram_bank_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
if (ACCESSING_BITS_0_7)
m_objram_bank = data & 1;
}
uint16_t cps2_state::cps2_objram1_r(offs_t offset)
{
if (m_objram_bank & 1)
return m_objram2[offset];
else
return m_objram1[offset];
}
uint16_t cps2_state::cps2_objram2_r(offs_t offset)
{
if (m_objram_bank & 1)
return m_objram1[offset];
else
return m_objram2[offset];
}
void cps2_state::cps2_objram1_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
if (m_objram_bank & 1)
COMBINE_DATA(&m_objram2[offset]);
else
COMBINE_DATA(&m_objram1[offset]);
}
void cps2_state::cps2_objram2_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
if (m_objram_bank & 1)
COMBINE_DATA(&m_objram1[offset]);
else
COMBINE_DATA(&m_objram2[offset]);
}
uint16_t *cps2_state::cps2_objbase()
{
int baseptr;
baseptr = 0x7000;
if (m_objram_bank & 1)
baseptr ^= 0x0080;
//popmessage("%04x %d", cps2_port(machine, CPS2_OBJ_BASE), m_objram_bank & 1);
if (baseptr == 0x7000)
return m_objram1;
else //if (baseptr == 0x7080)
return m_objram2;
}
void cps2_state::find_last_sprite() /* Find the offset of last sprite */
{
cps_state::find_last_sprite();
int offset = 0;
uint16_t *base = m_cps2_buffered_obj.get();
/* Locate the end of table marker */
while (offset < m_cps2_obj_size / 2)
{
if (base[offset + 1] >= 0x8000 || base[offset + 3] >= 0xff00)
{
/* Marker found. This is the last sprite. */
m_cps2_last_sprite_offset = offset - 4;
return;
}
offset += 4;
}
/* Sprites must use full sprite RAM */
m_cps2_last_sprite_offset = m_cps2_obj_size / 2 - 4;
}
void cps2_state::cps2_render_sprites( screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int *primasks )
{
#define DRAWSPRITE(CODE,COLOR,FLIPX,FLIPY,SX,SY) \
{ \
if (flip_screen()) \
m_gfxdecode->gfx(2)->prio_transpen(bitmap,\
cliprect, \
CODE, \
COLOR, \
!(FLIPX),!(FLIPY), \
512-16-(SX),256-16-(SY), screen.priority(),primasks[priority],15); \
else \
m_gfxdecode->gfx(2)->prio_transpen(bitmap,\
cliprect, \
CODE, \
COLOR, \
FLIPX,FLIPY, \
SX,SY, screen.priority(),primasks[priority],15); \
}
int i;
uint16_t *base = m_cps2_buffered_obj.get();
int xoffs = 64 - m_output[CPS2_OBJ_XOFFS /2];
int yoffs = 16 - m_output[CPS2_OBJ_YOFFS /2];
#ifdef MAME_DEBUG
if (machine().input().code_pressed(KEYCODE_Z) && machine().input().code_pressed(KEYCODE_R))
{
return;
}
#endif
for (i = m_cps2_last_sprite_offset; i >= 0; i -= 4)
{
int x = base[i + 0];
int y = base[i + 1];
int priority = (x >> 13) & 0x07;
int code = base[i + 2] + ((y & 0x6000) << 3);
int colour = base[i + 3];
int col = colour & 0x1f;
if (colour & 0x80)
{
x += m_output[CPS2_OBJ_XOFFS /2]; /* fix the offset of some games */
y += m_output[CPS2_OBJ_YOFFS /2]; /* like Marvel vs. Capcom ending credits */
}
if (colour & 0xff00)
{
/* handle blocked sprites */
int nx = (colour & 0x0f00) >> 8;
int ny = (colour & 0xf000) >> 12;
int nxs, nys, sx, sy;
nx++;
ny++;
if (colour & 0x40)
{
/* Y flip */
if (colour & 0x20)
{
for (nys = 0; nys < ny; nys++)
{
for (nxs = 0; nxs < nx; nxs++)
{
sx = (x + nxs * 16 + xoffs) & 0x3ff;
sy = (y + nys * 16 + yoffs) & 0x3ff;
DRAWSPRITE(
code + (nx - 1) - nxs + 0x10 * (ny - 1 - nys),
(col & 0x1f),
1,1,
sx,sy);
}
}
}
else
{
for (nys = 0; nys < ny; nys++)
{
for (nxs = 0; nxs < nx; nxs++)
{
sx = (x + nxs * 16 + xoffs) & 0x3ff;
sy = (y + nys * 16 + yoffs) & 0x3ff;
DRAWSPRITE(
code + nxs + 0x10 * (ny - 1 - nys),
(col & 0x1f),
0,1,
sx,sy);
}
}
}
}
else
{
if (colour & 0x20)
{
for (nys = 0; nys < ny; nys++)
{
for (nxs = 0; nxs < nx; nxs++)
{
sx = (x + nxs * 16 + xoffs) & 0x3ff;
sy = (y + nys * 16 + yoffs) & 0x3ff;
DRAWSPRITE(
code + (nx - 1) - nxs + 0x10 * nys,
(col & 0x1f),
1,0,
sx,sy);
}
}
}
else
{
for (nys = 0; nys < ny; nys++)
{
for (nxs = 0; nxs < nx; nxs++)
{
sx = (x + nxs * 16 + xoffs) & 0x3ff;
sy = (y + nys * 16 + yoffs) & 0x3ff;
DRAWSPRITE(
// code + nxs + 0x10 * nys,
(code & ~0xf) + ((code + nxs) & 0xf) + 0x10 * nys, // pgear fix
(col & 0x1f),
0,0,
sx,sy);
}
}
}
}
}
else
{
/* Simple case... 1 sprite */
DRAWSPRITE(
code,
(col & 0x1f),
colour&0x20,colour&0x40,
(x+xoffs) & 0x3ff,(y+yoffs) & 0x3ff);
}
}
}
void cps_state::cps1_render_stars( screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect )
{
uint8_t const *const stars_rom = m_region_stars->base();
@ -3323,68 +3026,6 @@ void cps_state::render_layers(screen_device &screen, bitmap_ind16 &bitmap, const
cps1_render_layer(screen, bitmap, cliprect, l3, 0);
}
void cps2_state::render_layers(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
/* Draw layers (0 = sprites, 1-3 = tilemaps) */
int layercontrol = m_cps_b_regs[m_game_config->layer_control / 2];
int l0 = (layercontrol >> 0x06) & 0x03;
int l1 = (layercontrol >> 0x08) & 0x03;
int l2 = (layercontrol >> 0x0a) & 0x03;
int l3 = (layercontrol >> 0x0c) & 0x03;
screen.priority().fill(0, cliprect);
int primasks[8], i;
int l0pri = (m_pri_ctrl >> 4 * l0) & 0x0f;
int l1pri = (m_pri_ctrl >> 4 * l1) & 0x0f;
int l2pri = (m_pri_ctrl >> 4 * l2) & 0x0f;
int l3pri = (m_pri_ctrl >> 4 * l3) & 0x0f;
#if 0
if ( (m_output[CPS2_OBJ_BASE /2] != 0x7080 && m_output[CPS2_OBJ_BASE /2] != 0x7000) ||
m_output[CPS2_OBJ_UK1 /2] != 0x807d ||
(m_output[CPS2_OBJ_UK2 /2] != 0x0000 && m_output[CPS2_OBJ_UK2 /2] != 0x1101 && m_output[CPS2_OBJ_UK2 /2] != 0x0001))
popmessage("base %04x uk1 %04x uk2 %04x",
m_output[CPS2_OBJ_BASE /2],
m_output[CPS2_OBJ_UK1 /2],
m_output[CPS2_OBJ_UK2 /2]);
if (0 && machine().input().code_pressed(KEYCODE_Z))
popmessage("order: %d (%d) %d (%d) %d (%d) %d (%d)",l0,l0pri,l1,l1pri,l2,l2pri,l3,l3pri);
#endif
/* take out the CPS1 sprites layer */
if (l0 == 0) { l0 = l1; l1 = 0; l0pri = l1pri; }
if (l1 == 0) { l1 = l2; l2 = 0; l1pri = l2pri; }
if (l2 == 0) { l2 = l3; l3 = 0; l2pri = l3pri; }
{
int mask0 = 0xaa;
int mask1 = 0xcc;
if (l0pri > l1pri) mask0 &= ~0x88;
if (l0pri > l2pri) mask0 &= ~0xa0;
if (l1pri > l2pri) mask1 &= ~0xc0;
primasks[0] = 0xff;
for (i = 1; i < 8; i++)
{
if (i <= l0pri && i <= l1pri && i <= l2pri)
{
primasks[i] = 0xfe;
continue;
}
primasks[i] = 0;
if (i <= l0pri) primasks[i] |= mask0;
if (i <= l1pri) primasks[i] |= mask1;
if (i <= l2pri) primasks[i] |= 0xf0;
}
}
cps1_render_layer(screen, bitmap, cliprect, l0, 1);
cps1_render_layer(screen, bitmap, cliprect, l1, 2);
cps1_render_layer(screen, bitmap, cliprect, l2, 4);
cps2_render_sprites(screen, bitmap, cliprect, primasks);
}
uint32_t cps_state::screen_update_cps1(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
int videocontrol = m_cps_a_regs[CPS1_VIDEOCONTROL];
@ -3465,21 +3106,3 @@ WRITE_LINE_MEMBER(cps_state::screen_vblank_cps1)
}
}
}
uint32_t cps2_state::screen_update_cps2(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
cps2_set_sprite_priorities();
return screen_update_cps1(screen, bitmap, cliprect);
}
void cps2_state::cps2_set_sprite_priorities()
{
m_pri_ctrl = m_output[CPS2_OBJ_PRI /2];
}
void cps2_state::cps2_objram_latch()
{
cps2_set_sprite_priorities();
memcpy(m_cps2_buffered_obj.get(), cps2_objbase(), m_cps2_obj_size);
}