mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-25 07:09:48 -06:00
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:
@ -55,6 +55,7 @@ UCodeInterface* UCodeFactory(u32 crc, DSPHLE* dsphle, bool wii)
|
|||||||
case 0x86840740: // Zelda WW - US
|
case 0x86840740: // Zelda WW - US
|
||||||
case 0x6ca33a6d: // Zelda TP GC - US
|
case 0x6ca33a6d: // Zelda TP GC - US
|
||||||
case 0xd643001f: // Super Mario Galaxy - US
|
case 0xd643001f: // Super Mario Galaxy - US
|
||||||
|
case 0x6ba3b3ea: // GC IPL - US
|
||||||
return new ZeldaUCode(dsphle, crc);
|
return new ZeldaUCode(dsphle, crc);
|
||||||
|
|
||||||
case 0x2ea36ce6: // Some Wii demos
|
case 0x2ea36ce6: // Some Wii demos
|
||||||
|
@ -19,6 +19,10 @@ enum ZeldaUCodeFlag
|
|||||||
// Multiply by two the computed Dolby positional volumes. Some UCodes do
|
// Multiply by two the computed Dolby positional volumes. Some UCodes do
|
||||||
// not do that (Zelda TWW for example), others do (Zelda TP, SMG).
|
// not do that (Zelda TWW for example), others do (Zelda TP, SMG).
|
||||||
MAKE_DOLBY_LOUDER = 0x00000002,
|
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 = {
|
static const std::map<u32, u32> UCODE_FLAGS = {
|
||||||
@ -28,11 +32,12 @@ static const std::map<u32, u32> UCODE_FLAGS = {
|
|||||||
{ 0x6CA33A6D, MAKE_DOLBY_LOUDER },
|
{ 0x6CA33A6D, MAKE_DOLBY_LOUDER },
|
||||||
// Super Mario Galaxy.
|
// Super Mario Galaxy.
|
||||||
{ 0xD643001F, NO_ARAM | MAKE_DOLBY_LOUDER },
|
{ 0xD643001F, NO_ARAM | MAKE_DOLBY_LOUDER },
|
||||||
|
// GameCube IPL/BIOS.
|
||||||
|
{ 0x6BA3B3EA, LIGHT_PROTOCOL },
|
||||||
|
|
||||||
// TODO: Other games that use this UCode (exhaustive list):
|
// TODO: Other games that use this UCode (exhaustive list):
|
||||||
// * Animal Crossing (type ????, CRC ????)
|
// * Animal Crossing (type ????, CRC ????)
|
||||||
// * Donkey Kong Jungle Beat (type ????, CRC ????)
|
// * Donkey Kong Jungle Beat (type ????, CRC ????)
|
||||||
// * IPL (type ????, CRC ????)
|
|
||||||
// * Luigi's Mansion (type ????, CRC ????)
|
// * Luigi's Mansion (type ????, CRC ????)
|
||||||
// * Mario Kart: Double Dash!! (type ????, CRC ????)
|
// * Mario Kart: Double Dash!! (type ????, CRC ????)
|
||||||
// * Pikmin (type ????, CRC ????)
|
// * Pikmin (type ????, CRC ????)
|
||||||
@ -46,15 +51,22 @@ static const std::map<u32, u32> UCODE_FLAGS = {
|
|||||||
ZeldaUCode::ZeldaUCode(DSPHLE *dsphle, u32 crc)
|
ZeldaUCode::ZeldaUCode(DSPHLE *dsphle, u32 crc)
|
||||||
: UCodeInterface(dsphle, crc)
|
: UCodeInterface(dsphle, crc)
|
||||||
{
|
{
|
||||||
m_mail_handler.PushMail(DSP_INIT, true);
|
|
||||||
m_mail_handler.PushMail(0xF3551111); // handshake
|
|
||||||
|
|
||||||
auto it = UCODE_FLAGS.find(crc);
|
auto it = UCODE_FLAGS.find(crc);
|
||||||
if (it == UCODE_FLAGS.end())
|
if (it == UCODE_FLAGS.end())
|
||||||
PanicAlert("No flags definition found for Zelda CRC %08x", crc);
|
PanicAlert("No flags definition found for Zelda CRC %08x", crc);
|
||||||
|
|
||||||
m_flags = it->second;
|
m_flags = it->second;
|
||||||
m_renderer.SetFlags(m_flags);
|
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()
|
ZeldaUCode::~ZeldaUCode()
|
||||||
@ -108,6 +120,14 @@ void ZeldaUCode::HandleMail(u32 mail)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_flags & LIGHT_PROTOCOL)
|
||||||
|
HandleMailLight(mail);
|
||||||
|
else
|
||||||
|
HandleMailDefault(mail);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZeldaUCode::HandleMailDefault(u32 mail)
|
||||||
|
{
|
||||||
switch (m_mail_current_state)
|
switch (m_mail_current_state)
|
||||||
{
|
{
|
||||||
case MailState::WAITING:
|
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()
|
void ZeldaUCode::RunPendingCommands()
|
||||||
{
|
{
|
||||||
if (RenderingInProgress() || !m_cmd_can_execute)
|
if (RenderingInProgress() || !m_cmd_can_execute)
|
||||||
@ -286,7 +365,16 @@ void ZeldaUCode::RunPendingCommands()
|
|||||||
|
|
||||||
m_rendering_curr_frame = 0;
|
m_rendering_curr_frame = 0;
|
||||||
m_rendering_curr_voice = 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;
|
return;
|
||||||
|
|
||||||
// Command 0D: TODO: find a name and implement.
|
// Command 0D: TODO: find a name and implement.
|
||||||
@ -315,17 +403,25 @@ void ZeldaUCode::RunPendingCommands()
|
|||||||
|
|
||||||
void ZeldaUCode::SendCommandAck(CommandAck ack_type, u16 sync_value)
|
void ZeldaUCode::SendCommandAck(CommandAck ack_type, u16 sync_value)
|
||||||
{
|
{
|
||||||
u32 ack_mail = 0;
|
if (m_flags & LIGHT_PROTOCOL)
|
||||||
switch (ack_type)
|
|
||||||
{
|
{
|
||||||
case CommandAck::STANDARD: ack_mail = DSP_SYNC; break;
|
// The light protocol uses the address of the command handler in the
|
||||||
case CommandAck::DONE_RENDERING: ack_mail = DSP_FRAME_END; break;
|
// 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);
|
else
|
||||||
|
|
||||||
if (ack_type == CommandAck::STANDARD)
|
|
||||||
{
|
{
|
||||||
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++;
|
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();
|
m_renderer.FinalizeFrame();
|
||||||
|
|
||||||
@ -367,8 +464,15 @@ void ZeldaUCode::RenderAudio()
|
|||||||
m_rendering_curr_frame++;
|
m_rendering_curr_frame++;
|
||||||
}
|
}
|
||||||
|
|
||||||
SendCommandAck(CommandAck::DONE_RENDERING, 0);
|
if (!(m_flags & LIGHT_PROTOCOL))
|
||||||
m_cmd_can_execute = false; // Block command execution until ACK is received.
|
{
|
||||||
|
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
|
// Utility to define 32 bit accessors/modifiers methods based on two 16 bit
|
||||||
|
@ -198,6 +198,10 @@ private:
|
|||||||
// list and explanation.
|
// list and explanation.
|
||||||
u32 m_flags;
|
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
|
// UCode state machine. The control flow in the Zelda UCode family is quite
|
||||||
// complex, using interrupt handlers heavily to handle incoming messages
|
// complex, using interrupt handlers heavily to handle incoming messages
|
||||||
// which, depending on the type, get handled immediately or are queued in a
|
// which, depending on the type, get handled immediately or are queued in a
|
||||||
|
Reference in New Issue
Block a user