Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

k573mcr: Implementation of JVS memory card reader device for System 573 #7659

Merged
merged 11 commits into from Jan 12, 2021

Conversation

987123879113
Copy link
Contributor

This is a software implementation of the JVS memory card reader device used by System 573. This allows for saving and loading scores in DDR games, loading custom edit chart data in DDR and Guitar Freaks games, and PS1 guitar controller inputs in Guitar Freaks games.

I've included packet captures for every implemented commands and the corresponding responses in the code for documentation purposes. If it's too much or not required then I can remove them.

The following games are affected:

ddr2mc2
ddr2ml
ddr2mla
ddr3mka
ddr3ma
ddr3mk
ddr3mj
ddr3mp
ddr4m
ddr4mj
ddr4ms
ddr4msj
ddr4mp
ddr4mps
ddr5m
ddrmax
ddrmax2
ddrextrm
gtrfrk3m
gtfrk3ma
gtrfrk4m

All of the above have been tested to make sure that the memory card and controller functionality is working. I've also tried to test as much of the other System 573 games as possible (~80 or so) to make sure nothing broke as a result of these changes.

Overview of changes:

  • jvsdev: Added a custom device-specific message handler that can be overridden by devices implementing jvsdev
  • k573mcr: Added documentation about the PCB layout
  • k573mcr: Implemented k573mcr as a jvsdev using the custom message handler
  • ksys573: Implemented registers used for JVS communication
  • ksys573: Removed hacks for ddr2ml/ddr2mla that patched out the security plate errors since this PR fixes the problem

Comment on lines 107 to 120
uint8_t k573mcr_device::controller_port_send_byte(uint32_t port_no, uint8_t data)
{
auto port = m_ports[port_no];
uint8_t output = 0;

for (int i = 0; i < 8; i++) {
port->clock_w(0);
port->tx_w(!!(data & (1 << i)));
port->clock_w(1);
output |= port->rx_r() << i;
}

return output;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better way to implement communication with the PS1 controller ports? I didn't think it was appropriate to reimplement something separate entirely because the hardware uses real PSX controller ports. My other idea was to expose psxcard_device::transfer and psx_standard_controller_device::get_pad but I don't feel comfortable making that change on my own.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it actually is serial, and transfers take time. Pretending it happens instantaneously like this is bad enough; pretending it has a parallel interface would be even worse.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote the PSX controller code to use the PSX controller bus instead of communicating to the ports directly in preparation for trying to implement the PSX code in a more proper way that took transfer time into consideration. The memory card part worked fine because the game polls waiting for a status flag and will just keep polling until the read/write operation has finished, but I ran into a big problem when I tried to implement the pad reads. The pad read function expects to be able to return the pad status as the response to the pad read command. It does not rely on polling the status flag like memory card functions. I think a more skilled person would be able to implement this part in a way that respects transfer times, but that level of implementation is currently outside of my skill level.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't let yourself get intimidated by the codebase. Given the level of skill you've shown in turning around nearly all of the feedback points, I think you're more than skilled enough to handle it.

As part of its core, MAME has a class called emu_timer. It's not meant to encapsulate any particular timer in a chip, it's just intended for use when you want to fire a callback a given amount of time in the future. It can either be implemented using a device's built-in device_timer callback (preferable if a device only has one thing that needs to be timed), or using a specifically-named TIMER_CALLBACK_MEMBER function, which is preferable in the case of a device that has multiple potential timing-related tasks that need to be coordinated.

If you need an example on how to make use of it, I would refer to the SA1110 or SA1111 devices, both of which make heavy use of delayed operations.

If you have any questions, we're here to help.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MooglyGuy Thanks for the comment. I will check out the devices you mentioned. I actually was using TIMER_CALLBACK_MEMBER in my attempt. I pushed the WIP code to a separate branch if you're willing to look at it and give me feedback on what I could do to fix it up. Everything except the pad reporting functionality is working, and reading the pad state itself actually works even. I just can't figure out how to get the data out of the buffer fast enough to return in the response for handle_message.

987123879113@5053cb0

This is kind of a slow realization that I made while rereading my code for this response, but I just realized that I don't need to poll the pad when it's requested. I can just poll for pad status updates in the timer update and store the results for when it's requested and that should probably solve the issue for the most part.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your realization sounds about right. In reality, the actual pad status is most likely only latched when the status changes, and the timer should only be kicked off when a new request comes in. If there's a request currently in-flight, then presumably the status register would indicate that, without the need for kicking off a new read cycle.

Comment on lines 477 to 481
INPUT_PORTS_START( k573mcr_meta_controls )
PORT_START("META")
PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_SERVICE1) PORT_TOGGLE PORT_NAME("Insert/Eject Memory Card 1")
PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_SERVICE2) PORT_TOGGLE PORT_NAME("Insert/Eject Memory Card 2")
INPUT_PORTS_END
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented meta controls for the memory card reader to simulate the action of inserting and ejecting the memory card slot. Is this ok?

The intent was to make interactions like ending a credit in DDR smoother when it tells you to eject your card. Instead of waiting for the counter to tick down, it's possible to press the toggle button to eject your card and then press the button again to insert it, like you would on a real machine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the usual way of removing/inserting media devices is using the file manager menu. It becomes impractical when the media devices are slot devices that can instantiate different objects depending on what you insert into them because those require a hard reset to change. However, as far as I can tell, the PlayStation memory isn’t one of these - the user can pull up the menu and take the memory card out. It doesn’t require a special input.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct, the Playstation memory cards do not require a hard reset to change. My reasoning for this was that I think it's less practical to open the menu every time to eject and reopen the memory card file. Especially so if you are playing on something like a MAME cabinet and don't have as easy access to menus.

For a normal Playstation game you can reasonably expect that the player will generally keep the memory card in the slot. For the supported System 573 games, it's expected that the player will be removing and inserting memory cards after every credit and the games are coded as such. For example, unskippable timers until the player removes their card as a way to remind the player to take their card with them instead of leaving it in the machine.

It's not required but but I believe it's a keybind that smoothly replicates actions that the game expects you to perform. If this isn't convincing enough then I will remove it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the UI could be better. With the recent changes exposing media images to Lua, I think this can be handled in a more generic way using a plugin to allow binding keys to remove/reinsert last media. That leaves the question of whether to leave the convenience hack here in the mean time or not.

Comment on lines 85 to 90
int jvs_device::device_handle_message(const uint8_t *send_buffer, uint32_t send_size, uint8_t *&recv_buffer)
{
// Override this function to provide device-specific JVS message handling.
// If -1 is returned then the base message handler will be used.
return -1;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an antipattern. You should provide default message handling in a protected virtual method, and allow derived classes to override it, and call the base method if they don’t handle the message specially.

@@ -375,6 +376,90 @@ G: gun mania only, drives air soft gun (this game uses real BB bullet)

#define ATAPI_CYCLES_PER_SECTOR ( 5000 ) // plenty of time to allow DMA setup etc. BIOS requires this be at least 2000, individual games may vary.

// #define JVS_DEBUG
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the stuff from logmacro.h for configurable logging. Especially don’t use printf or its friends, they’re not type-safe, and semantics vary depending on the C standard library.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking into this now and I just noticed the rest of the driver uses verboselog. Would it be preferable to just use verboselog to keep with the rest of the driver, or would it be better to update the driver's logging code to use logmacro.h? I'd rather not mix the two so I can update the entire driver's logging code if logmacro.h is the preferable method going forward.

ref:

void ATTR_PRINTF( 3,4 ) ksys573_state::verboselog( int n_level, const char *s_fmt, ... )
{
if( VERBOSE_LEVEL >= n_level )
{
va_list v;
char buf[ 32768 ];
va_start( v, s_fmt );
vsprintf( buf, s_fmt, v );
va_end( v );
logerror( "%s: %s", machine().describe_context(), buf );
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to work out some categories for the logging that’s already there and convert it, go ahead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the JVS_DEBUG macro is completely redundant now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I deleted that but it looks like I left the #define in. Thanks.

Comment on lines 385 to 387
DECLARE_DEVICE_TYPE(JVS_MASTER, jvs_master)

class jvs_master : public jvs_host
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The names for this device (class name, device type name, short name and description) are too generic for something driver-specific like this.

Comment on lines 433 to 436
uint8_t checksum = 0;
for (int i = 0; i < length; i++) {
checksum += data[i];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be done with std::accumulate.

{
if (offset == 0) {
// 0x1f400008-0x1f400009 are for inputs
return ioport("IN2")->read();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don’t add runtime tag map lookups. They’re slow and you don’t benefit from validation.

Comment on lines 1061 to 1062
memset(m_jvs_input_buffer, 0, 512);
memset(m_jvs_output_buffer, 0, 512);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use std::fill in preference to memset.

@@ -2250,6 +2423,8 @@ void ksys573_state::konami573(machine_config &config)

adc0834_device &adc(ADC0834(config, "adc0834"));
adc.set_input_callback(FUNC(ksys573_state::analogue_inputs_callback));

JVS_MASTER(config, "jvs_master", 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use object finders in place of literal tags to make things less error-prone.

{
jvs_device::device_reset();

memset(m_ram.get(), 0, RAM_SIZE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use std::fill_n in preference to memset.


for (int i = 0; i < 8; i++) {
port->clock_w(0);
port->tx_w(!!(data & (1 << i)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the BIT helper for doing this.

uint16_t m_status;
bool m_is_memcard_initialized;

required_device<psx_controller_port_device> m_ports[2];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

required_device_array should be used rather than an array of required_device.

@@ -375,6 +376,90 @@ G: gun mania only, drives air soft gun (this game uses real BB bullet)

#define ATAPI_CYCLES_PER_SECTOR ( 5000 ) // plenty of time to allow DMA setup etc. BIOS requires this be at least 2000, individual games may vary.

// #define JVS_DEBUG
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the JVS_DEBUG macro is completely redundant now.

Comment on lines +92 to +93
PSX_CONTROLLER_PORT(config, "port1", psx_controllers, nullptr);
PSX_CONTROLLER_PORT(config, "port2", psx_controllers, nullptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows the user to select the connected devices. Since this is a sealed-in-a-cabinet system, would it be better to have fixed peripherals set in machine configuration? You can add .set_fixed(true) to each of them to not expose them as user-selectable options.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think technically allowing the user to select the connected devices is more accurate. Whether or not the game will read it is another story, you can connect any memory card and controller device to the machines. The same machine plate with the memory card and controller ports are used on all supported machines.

Picture:
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn’t realise the controller ports were actually on the front panel like that, I thought it was more like the Neo Geo MVS variants with console-style controller ports inside the machine. My bad.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants