Zelda HLE: Add support for the light protocol.

Used by a few titles (Luigi's Mansion, Animal Crossing) as well as the GameCube
IPL/BIOS.

Note that the IPL does not work yet because it mixes to unknown buffers.
This commit is contained in:
Pierre Bourdon 2014-12-22 05:37:34 +01:00
parent c033395e28
commit 13ea54628d
3 changed files with 125 additions and 16 deletions

View File

@ -55,6 +55,7 @@ UCodeInterface* UCodeFactory(u32 crc, DSPHLE* dsphle, bool wii)
case 0x86840740: // Zelda WW - US
case 0x6ca33a6d: // Zelda TP GC - US
case 0xd643001f: // Super Mario Galaxy - US
case 0x6ba3b3ea: // GC IPL - US
return new ZeldaUCode(dsphle, crc);
case 0x2ea36ce6: // Some Wii demos

View File

@ -19,6 +19,10 @@ enum ZeldaUCodeFlag
// Multiply by two the computed Dolby positional volumes. Some UCodes do
// not do that (Zelda TWW for example), others do (Zelda TP, SMG).
MAKE_DOLBY_LOUDER = 0x00000002,
// Light version of the UCode: no Dolby mixing, different synchronization
// protocol, etc.
LIGHT_PROTOCOL = 0x00000004,
};
static const std::map<u32, u32> UCODE_FLAGS = {
@ -28,11 +32,12 @@ static const std::map<u32, u32> UCODE_FLAGS = {
{ 0x6CA33A6D, MAKE_DOLBY_LOUDER },
// Super Mario Galaxy.
{ 0xD643001F, NO_ARAM | MAKE_DOLBY_LOUDER },
// GameCube IPL/BIOS.
{ 0x6BA3B3EA, LIGHT_PROTOCOL },
// TODO: Other games that use this UCode (exhaustive list):
// * Animal Crossing (type ????, CRC ????)
// * Donkey Kong Jungle Beat (type ????, CRC ????)
// * IPL (type ????, CRC ????)
// * Luigi's Mansion (type ????, CRC ????)
// * Mario Kart: Double Dash!! (type ????, CRC ????)
// * Pikmin (type ????, CRC ????)
@ -46,15 +51,22 @@ static const std::map<u32, u32> UCODE_FLAGS = {
ZeldaUCode::ZeldaUCode(DSPHLE *dsphle, u32 crc)
: UCodeInterface(dsphle, crc)
{
m_mail_handler.PushMail(DSP_INIT, true);
m_mail_handler.PushMail(0xF3551111); // handshake
auto it = UCODE_FLAGS.find(crc);
if (it == UCODE_FLAGS.end())
PanicAlert("No flags definition found for Zelda CRC %08x", crc);
m_flags = it->second;
m_renderer.SetFlags(m_flags);
if (m_flags & LIGHT_PROTOCOL)
{
m_mail_handler.PushMail(0x88881111);
}
else
{
m_mail_handler.PushMail(DSP_INIT, true);
m_mail_handler.PushMail(0xF3551111); // handshake
}
}
ZeldaUCode::~ZeldaUCode()
@ -108,6 +120,14 @@ void ZeldaUCode::HandleMail(u32 mail)
return;
}
if (m_flags & LIGHT_PROTOCOL)
HandleMailLight(mail);
else
HandleMailDefault(mail);
}
void ZeldaUCode::HandleMailDefault(u32 mail)
{
switch (m_mail_current_state)
{
case MailState::WAITING:
@ -191,6 +211,65 @@ void ZeldaUCode::HandleMail(u32 mail)
}
}
void ZeldaUCode::HandleMailLight(u32 mail)
{
switch (m_mail_current_state)
{
case MailState::WAITING:
if (!(mail & 0x80000000))
PanicAlert("Mail received in waiting state has MSB=0: %08x", mail);
// Start of a command. We have to hardcode the number of mails required
// for each command - the alternative is to rewrite command handling as
// an asynchronous procedure, and we wouldn't want that, would we?
Write32(mail);
switch ((mail >> 24) & 0x7F)
{
case 0: m_mail_expected_cmd_mails = 0; break;
case 1: m_mail_expected_cmd_mails = 4; break;
case 2: m_mail_expected_cmd_mails = 2; break;
default:
PanicAlert("Received unknown command in light protocol: %08x", mail);
break;
}
if (m_mail_expected_cmd_mails)
{
SetMailState(MailState::WRITING_CMD);
}
else
{
m_pending_commands_count += 1;
RunPendingCommands();
}
break;
case MailState::WRITING_CMD:
Write32(mail);
if (--m_mail_expected_cmd_mails == 0)
{
m_pending_commands_count += 1;
SetMailState(MailState::WAITING);
RunPendingCommands();
}
break;
case MailState::RENDERING:
if (mail != 0)
PanicAlert("Sync mail is not zero: %08x", mail);
// No per-voice syncing in the light protocol.
m_sync_max_voice_id = 0xFFFFFFFF;
m_sync_voice_skip_flags.fill(0xFFFFFFFF);
RenderAudio();
DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
break;
case MailState::HALTED:
WARN_LOG(DSPHLE, "Received mail %08x while we're halted.", mail);
break;
}
}
void ZeldaUCode::RunPendingCommands()
{
if (RenderingInProgress() || !m_cmd_can_execute)
@ -286,7 +365,16 @@ void ZeldaUCode::RunPendingCommands()
m_rendering_curr_frame = 0;
m_rendering_curr_voice = 0;
RenderAudio();
if (m_flags & LIGHT_PROTOCOL)
{
SendCommandAck(CommandAck::STANDARD, m_rendering_requested_frames);
SetMailState(MailState::RENDERING);
}
else
{
RenderAudio();
}
return;
// Command 0D: TODO: find a name and implement.
@ -315,17 +403,25 @@ void ZeldaUCode::RunPendingCommands()
void ZeldaUCode::SendCommandAck(CommandAck ack_type, u16 sync_value)
{
u32 ack_mail = 0;
switch (ack_type)
if (m_flags & LIGHT_PROTOCOL)
{
case CommandAck::STANDARD: ack_mail = DSP_SYNC; break;
case CommandAck::DONE_RENDERING: ack_mail = DSP_FRAME_END; break;
// The light protocol uses the address of the command handler in the
// DSP code instead of the command id... go figure.
sync_value = 2 * ((sync_value >> 8) & 0x7F) + 0x62;
m_mail_handler.PushMail(0x80000000 | sync_value);
}
m_mail_handler.PushMail(ack_mail, true);
if (ack_type == CommandAck::STANDARD)
else
{
m_mail_handler.PushMail(0xF3550000 | sync_value);
u32 ack_mail = 0;
switch (ack_type)
{
case CommandAck::STANDARD: ack_mail = DSP_SYNC; break;
case CommandAck::DONE_RENDERING: ack_mail = DSP_FRAME_END; break;
}
m_mail_handler.PushMail(ack_mail, true);
if (ack_type == CommandAck::STANDARD)
m_mail_handler.PushMail(0xF3550000 | sync_value);
}
}
@ -358,7 +454,8 @@ void ZeldaUCode::RenderAudio()
m_rendering_curr_voice++;
}
SendCommandAck(CommandAck::STANDARD, 0xFF00 | m_rendering_curr_frame);
if (!(m_flags & LIGHT_PROTOCOL))
SendCommandAck(CommandAck::STANDARD, 0xFF00 | m_rendering_curr_frame);
m_renderer.FinalizeFrame();
@ -367,8 +464,15 @@ void ZeldaUCode::RenderAudio()
m_rendering_curr_frame++;
}
SendCommandAck(CommandAck::DONE_RENDERING, 0);
m_cmd_can_execute = false; // Block command execution until ACK is received.
if (!(m_flags & LIGHT_PROTOCOL))
{
SendCommandAck(CommandAck::DONE_RENDERING, 0);
m_cmd_can_execute = false; // Block command execution until ACK is received.
}
else
{
SetMailState(MailState::WAITING);
}
}
// Utility to define 32 bit accessors/modifiers methods based on two 16 bit

View File

@ -198,6 +198,10 @@ private:
// list and explanation.
u32 m_flags;
// Different mail handlers for different protocols.
void HandleMailDefault(u32 mail);
void HandleMailLight(u32 mail);
// UCode state machine. The control flow in the Zelda UCode family is quite
// complex, using interrupt handlers heavily to handle incoming messages
// which, depending on the type, get handled immediately or are queued in a