CDROM: Add SubQ Skew option
Fixes corrupted boss sprites in Captain Commando. One day I'll refactor things to fix this properly.
This commit is contained in:
parent
3081c4f5cd
commit
060146a37a
|
@ -22079,11 +22079,13 @@ SLUS-01476:
|
||||||
SLPS-01567:
|
SLPS-01567:
|
||||||
name: "Captain Commando (Japan)"
|
name: "Captain Commando (Japan)"
|
||||||
compatibility:
|
compatibility:
|
||||||
rating: GraphicalAudioIssues
|
rating: NoIssues
|
||||||
versionTested: "0.1-2433-g9089c973"
|
|
||||||
upscalingIssues: "There's garbage in right of the screen and the first boss sprite is corrupted."
|
|
||||||
controllers:
|
controllers:
|
||||||
- DigitalController
|
- DigitalController
|
||||||
|
traits:
|
||||||
|
- DisablePGXP # 2D, PGXP is not beneficial.
|
||||||
|
- DisableWidescreen # GTE is not used, no effect.
|
||||||
|
- ForceCDROMSubQSkew # Fixes boss sprites.
|
||||||
settings:
|
settings:
|
||||||
displayActiveStartOffset: -62
|
displayActiveStartOffset: -62
|
||||||
displayActiveEndOffset: 51
|
displayActiveEndOffset: 51
|
||||||
|
|
|
@ -3067,6 +3067,32 @@ void CDROM::DoSectorRead()
|
||||||
if (subq_valid)
|
if (subq_valid)
|
||||||
{
|
{
|
||||||
s_state.last_subq = subq;
|
s_state.last_subq = subq;
|
||||||
|
if (g_settings.cdrom_subq_skew) [[unlikely]]
|
||||||
|
{
|
||||||
|
// SubQ Skew Hack. It's horrible. Needed for Captain Commando.
|
||||||
|
// Here's my previous rambling about the game:
|
||||||
|
//
|
||||||
|
// So, there's two Getloc commands on the PS1 to retrieve the most-recent-read sector:
|
||||||
|
// GetlocL, which returns the timecode based on the data sector header, and GetlocP, which gets it from subq.
|
||||||
|
// Captain Commando would always corrupt the first boss sprite.
|
||||||
|
//
|
||||||
|
// What the game does, is repeat the tile/texture data throughout the audio sectors for the background
|
||||||
|
// music when you reach the boss part of the level, it looks for a specific subq timecode coming in (by spamming
|
||||||
|
// GetlocP) then DMA's the data sector interleaved with the audio sectors out at the last possible moment
|
||||||
|
//
|
||||||
|
// So, they hard coded it to look for a sector timecode +2 from the sector they actually wanted, then DMA that
|
||||||
|
// data out they do perform some validation on the data itself, so if you're not offsetting the timecode query,
|
||||||
|
// it never gets the right sector, and just keeps reading forever. Hence why the boss tiles are broken, because
|
||||||
|
// it never gets the data to upload. The most insane part is they should have just done what every other game
|
||||||
|
// does: use the raw read mode (2352 instead of 2048), and look at the data sector header. Instead they do this
|
||||||
|
// nonsense of repeating the data throughout the audio, and racing the DMA at the last possible minute.
|
||||||
|
//
|
||||||
|
// This hack just generates synthetic SubQ with a +2 offset. I'd planned on refactoring the CDImage interface
|
||||||
|
// so that multiple sectors could be read in one back, in which case we could just "look ahead" to grab the
|
||||||
|
// subq, but I haven't got around to it. It'll break libcrypt, but CC doesn't use it. One day I'll get around to
|
||||||
|
// doing the refactor.... but given this is the only game that relies on it, priorities.
|
||||||
|
s_reader.GetMedia()->GenerateSubChannelQ(&s_state.last_subq, s_state.current_lba + 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace GameDatabase {
|
||||||
enum : u32
|
enum : u32
|
||||||
{
|
{
|
||||||
GAME_DATABASE_CACHE_SIGNATURE = 0x45434C48,
|
GAME_DATABASE_CACHE_SIGNATURE = 0x45434C48,
|
||||||
GAME_DATABASE_CACHE_VERSION = 15,
|
GAME_DATABASE_CACHE_VERSION = 16,
|
||||||
};
|
};
|
||||||
|
|
||||||
static Entry* GetMutableEntry(std::string_view serial);
|
static Entry* GetMutableEntry(std::string_view serial);
|
||||||
|
@ -101,6 +101,7 @@ static constexpr const std::array<const char*, static_cast<u32>(GameDatabase::Tr
|
||||||
"ForceRecompilerMemoryExceptions",
|
"ForceRecompilerMemoryExceptions",
|
||||||
"ForceRecompilerICache",
|
"ForceRecompilerICache",
|
||||||
"ForceRecompilerLUTFastmem",
|
"ForceRecompilerLUTFastmem",
|
||||||
|
"ForceCDROMSubQSkew",
|
||||||
"IsLibCryptProtected",
|
"IsLibCryptProtected",
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
@ -130,6 +131,7 @@ static constexpr const std::array<const char*, static_cast<u32>(GameDatabase::Tr
|
||||||
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Recompiler Memory Exceptions", "GameDatabase::Trait"),
|
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Recompiler Memory Exceptions", "GameDatabase::Trait"),
|
||||||
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Recompiler ICache", "GameDatabase::Trait"),
|
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Recompiler ICache", "GameDatabase::Trait"),
|
||||||
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Recompiler LUT Fastmem", "GameDatabase::Trait"),
|
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Recompiler LUT Fastmem", "GameDatabase::Trait"),
|
||||||
|
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force CD-ROM SubQ Skew", "GameDatabase::Trait"),
|
||||||
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Is LibCrypt Protected", "GameDatabase::Trait"),
|
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Is LibCrypt Protected", "GameDatabase::Trait"),
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
@ -610,6 +612,12 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
|
||||||
settings.cpu_fastmem_mode = CPUFastmemMode::LUT;
|
settings.cpu_fastmem_mode = CPUFastmemMode::LUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (HasTrait(Trait::ForceCDROMSubQSkew))
|
||||||
|
{
|
||||||
|
WARNING_LOG("CD-ROM SubQ Skew forced by compatibility settings.");
|
||||||
|
settings.cdrom_subq_skew = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!messages.empty())
|
if (!messages.empty())
|
||||||
{
|
{
|
||||||
Host::AddIconOSDMessage(
|
Host::AddIconOSDMessage(
|
||||||
|
|
|
@ -53,6 +53,7 @@ enum class Trait : u32
|
||||||
ForceRecompilerMemoryExceptions,
|
ForceRecompilerMemoryExceptions,
|
||||||
ForceRecompilerICache,
|
ForceRecompilerICache,
|
||||||
ForceRecompilerLUTFastmem,
|
ForceRecompilerLUTFastmem,
|
||||||
|
ForceCDROMSubQSkew,
|
||||||
IsLibCryptProtected,
|
IsLibCryptProtected,
|
||||||
|
|
||||||
Count
|
Count
|
||||||
|
|
|
@ -329,6 +329,7 @@ void Settings::Load(SettingsInterface& si, SettingsInterface& controller_si)
|
||||||
si.GetStringValue("CDROM", "MechaconVersion", GetCDROMMechVersionName(DEFAULT_CDROM_MECHACON_VERSION)).c_str())
|
si.GetStringValue("CDROM", "MechaconVersion", GetCDROMMechVersionName(DEFAULT_CDROM_MECHACON_VERSION)).c_str())
|
||||||
.value_or(DEFAULT_CDROM_MECHACON_VERSION);
|
.value_or(DEFAULT_CDROM_MECHACON_VERSION);
|
||||||
cdrom_region_check = si.GetBoolValue("CDROM", "RegionCheck", false);
|
cdrom_region_check = si.GetBoolValue("CDROM", "RegionCheck", false);
|
||||||
|
cdrom_subq_skew = si.GetBoolValue("CDROM", "SubQSkew", false);
|
||||||
cdrom_load_image_to_ram = si.GetBoolValue("CDROM", "LoadImageToRAM", false);
|
cdrom_load_image_to_ram = si.GetBoolValue("CDROM", "LoadImageToRAM", false);
|
||||||
cdrom_load_image_patches = si.GetBoolValue("CDROM", "LoadImagePatches", false);
|
cdrom_load_image_patches = si.GetBoolValue("CDROM", "LoadImagePatches", false);
|
||||||
cdrom_mute_cd_audio = si.GetBoolValue("CDROM", "MuteCDAudio", false);
|
cdrom_mute_cd_audio = si.GetBoolValue("CDROM", "MuteCDAudio", false);
|
||||||
|
@ -615,6 +616,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
||||||
si.SetIntValue("CDROM", "ReadaheadSectors", cdrom_readahead_sectors);
|
si.SetIntValue("CDROM", "ReadaheadSectors", cdrom_readahead_sectors);
|
||||||
si.SetStringValue("CDROM", "MechaconVersion", GetCDROMMechVersionName(cdrom_mechacon_version));
|
si.SetStringValue("CDROM", "MechaconVersion", GetCDROMMechVersionName(cdrom_mechacon_version));
|
||||||
si.SetBoolValue("CDROM", "RegionCheck", cdrom_region_check);
|
si.SetBoolValue("CDROM", "RegionCheck", cdrom_region_check);
|
||||||
|
si.SetBoolValue("CDROM", "SubQSkew", cdrom_subq_skew);
|
||||||
si.SetBoolValue("CDROM", "LoadImageToRAM", cdrom_load_image_to_ram);
|
si.SetBoolValue("CDROM", "LoadImageToRAM", cdrom_load_image_to_ram);
|
||||||
si.SetBoolValue("CDROM", "LoadImagePatches", cdrom_load_image_patches);
|
si.SetBoolValue("CDROM", "LoadImagePatches", cdrom_load_image_patches);
|
||||||
si.SetBoolValue("CDROM", "MuteCDAudio", cdrom_mute_cd_audio);
|
si.SetBoolValue("CDROM", "MuteCDAudio", cdrom_mute_cd_audio);
|
||||||
|
|
|
@ -180,6 +180,7 @@ struct Settings
|
||||||
u8 cdrom_readahead_sectors = DEFAULT_CDROM_READAHEAD_SECTORS;
|
u8 cdrom_readahead_sectors = DEFAULT_CDROM_READAHEAD_SECTORS;
|
||||||
CDROMMechaconVersion cdrom_mechacon_version = DEFAULT_CDROM_MECHACON_VERSION;
|
CDROMMechaconVersion cdrom_mechacon_version = DEFAULT_CDROM_MECHACON_VERSION;
|
||||||
bool cdrom_region_check : 1 = false;
|
bool cdrom_region_check : 1 = false;
|
||||||
|
bool cdrom_subq_skew : 1 = false;
|
||||||
bool cdrom_load_image_to_ram : 1 = false;
|
bool cdrom_load_image_to_ram : 1 = false;
|
||||||
bool cdrom_load_image_patches : 1 = false;
|
bool cdrom_load_image_patches : 1 = false;
|
||||||
bool cdrom_mute_cd_audio : 1 = false;
|
bool cdrom_mute_cd_audio : 1 = false;
|
||||||
|
|
|
@ -4697,6 +4697,9 @@ void System::WarnAboutUnsafeSettings()
|
||||||
TRANSLATE_STR("System", "Compatibility settings are not enabled. Some games may not function correctly."));
|
TRANSLATE_STR("System", "Compatibility settings are not enabled. Some games may not function correctly."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (g_settings.cdrom_subq_skew)
|
||||||
|
append(ICON_EMOJI_WARNING, TRANSLATE_SV("System", "CD-ROM SubQ Skew is enabled. This will break games."));
|
||||||
|
|
||||||
if (!messages.empty())
|
if (!messages.empty())
|
||||||
{
|
{
|
||||||
if (messages.back() == '\n')
|
if (messages.back() == '\n')
|
||||||
|
|
|
@ -261,6 +261,7 @@ void AdvancedSettingsWidget::addTweakOptions()
|
||||||
Settings::GetCDROMMechVersionDisplayName, static_cast<u8>(CDROMMechaconVersion::Count),
|
Settings::GetCDROMMechVersionDisplayName, static_cast<u8>(CDROMMechaconVersion::Count),
|
||||||
Settings::DEFAULT_CDROM_MECHACON_VERSION);
|
Settings::DEFAULT_CDROM_MECHACON_VERSION);
|
||||||
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Region Check"), "CDROM", "RegionCheck", false);
|
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Region Check"), "CDROM", "RegionCheck", false);
|
||||||
|
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM SubQ Skew"), "CDROM", "SubQSkew", false);
|
||||||
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Allow Booting Without SBI File"), "CDROM",
|
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Allow Booting Without SBI File"), "CDROM",
|
||||||
"AllowBootingWithoutSBIFile", false);
|
"AllowBootingWithoutSBIFile", false);
|
||||||
|
|
||||||
|
@ -297,6 +298,7 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
|
||||||
setChoiceTweakOption(m_ui.tweakOptionTable, i++,
|
setChoiceTweakOption(m_ui.tweakOptionTable, i++,
|
||||||
Settings::DEFAULT_CDROM_MECHACON_VERSION); // CDROM Mechacon Version
|
Settings::DEFAULT_CDROM_MECHACON_VERSION); // CDROM Mechacon Version
|
||||||
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM Region Check
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM Region Check
|
||||||
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM SubQ Skew
|
||||||
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Allow booting without SBI file
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Allow booting without SBI file
|
||||||
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Export Shared Memory
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Export Shared Memory
|
||||||
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PCDRV
|
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PCDRV
|
||||||
|
@ -325,6 +327,7 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
|
||||||
sif->DeleteValue("CPU", "FastmemMode");
|
sif->DeleteValue("CPU", "FastmemMode");
|
||||||
sif->DeleteValue("CDROM", "MechaconVersion");
|
sif->DeleteValue("CDROM", "MechaconVersion");
|
||||||
sif->DeleteValue("CDROM", "RegionCheck");
|
sif->DeleteValue("CDROM", "RegionCheck");
|
||||||
|
sif->DeleteValue("CDROM", "SubQSkew");
|
||||||
sif->DeleteValue("CDROM", "AllowBootingWithoutSBIFile");
|
sif->DeleteValue("CDROM", "AllowBootingWithoutSBIFile");
|
||||||
sif->DeleteValue("PCDrv", "Enabled");
|
sif->DeleteValue("PCDrv", "Enabled");
|
||||||
sif->DeleteValue("PCDrv", "EnableWrites");
|
sif->DeleteValue("PCDrv", "EnableWrites");
|
||||||
|
|
|
@ -456,7 +456,7 @@ void CDImage::CopyTOC(const CDImage* image)
|
||||||
m_position_on_disc = 0;
|
m_position_on_disc = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CDImage::Index* CDImage::GetIndexForDiscPosition(LBA pos)
|
const CDImage::Index* CDImage::GetIndexForDiscPosition(LBA pos) const
|
||||||
{
|
{
|
||||||
for (const Index& index : m_indices)
|
for (const Index& index : m_indices)
|
||||||
{
|
{
|
||||||
|
@ -473,7 +473,7 @@ const CDImage::Index* CDImage::GetIndexForDiscPosition(LBA pos)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CDImage::Index* CDImage::GetIndexForTrackPosition(u32 track_number, LBA track_pos)
|
const CDImage::Index* CDImage::GetIndexForTrackPosition(u32 track_number, LBA track_pos) const
|
||||||
{
|
{
|
||||||
if (track_number < 1 || track_number > m_tracks.size())
|
if (track_number < 1 || track_number > m_tracks.size())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -485,18 +485,18 @@ const CDImage::Index* CDImage::GetIndexForTrackPosition(u32 track_number, LBA tr
|
||||||
return GetIndexForDiscPosition(track.start_lba + track_pos);
|
return GetIndexForDiscPosition(track.start_lba + track_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba)
|
bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba) const
|
||||||
{
|
{
|
||||||
const Index* index = GetIndexForDiscPosition(lba);
|
const Index* index = GetIndexForDiscPosition(lba);
|
||||||
if (!index)
|
if (!index)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const u32 index_offset = index->start_lba_on_disc - lba;
|
const u32 index_offset = lba - index->start_lba_on_disc;
|
||||||
GenerateSubChannelQ(subq, *index, index_offset);
|
GenerateSubChannelQ(subq, *index, index_offset);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset)
|
void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset) const
|
||||||
{
|
{
|
||||||
subq->control_bits = index.control.bits;
|
subq->control_bits = index.control.bits;
|
||||||
subq->track_number_bcd = (index.track_number <= m_tracks.size() ? BinaryToBCD(static_cast<u8>(index.track_number)) :
|
subq->track_number_bcd = (index.track_number <= m_tracks.size() ? BinaryToBCD(static_cast<u8>(index.track_number)) :
|
||||||
|
|
|
@ -301,6 +301,12 @@ public:
|
||||||
// Read a single raw sector, and subchannel from the current LBA.
|
// Read a single raw sector, and subchannel from the current LBA.
|
||||||
bool ReadRawSector(void* buffer, SubChannelQ* subq);
|
bool ReadRawSector(void* buffer, SubChannelQ* subq);
|
||||||
|
|
||||||
|
/// Generates sub-channel Q given the specified position.
|
||||||
|
bool GenerateSubChannelQ(SubChannelQ* subq, LBA lba) const;
|
||||||
|
|
||||||
|
/// Generates sub-channel Q from the given index and index-offset.
|
||||||
|
void GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset) const;
|
||||||
|
|
||||||
// Reads sub-channel Q for the specified index+LBA.
|
// Reads sub-channel Q for the specified index+LBA.
|
||||||
virtual bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index);
|
virtual bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index);
|
||||||
|
|
||||||
|
@ -340,14 +346,8 @@ protected:
|
||||||
void ClearTOC();
|
void ClearTOC();
|
||||||
void CopyTOC(const CDImage* image);
|
void CopyTOC(const CDImage* image);
|
||||||
|
|
||||||
const Index* GetIndexForDiscPosition(LBA pos);
|
const Index* GetIndexForDiscPosition(LBA pos) const;
|
||||||
const Index* GetIndexForTrackPosition(u32 track_number, LBA track_pos);
|
const Index* GetIndexForTrackPosition(u32 track_number, LBA track_pos) const;
|
||||||
|
|
||||||
/// Generates sub-channel Q given the specified position.
|
|
||||||
bool GenerateSubChannelQ(SubChannelQ* subq, LBA lba);
|
|
||||||
|
|
||||||
/// Generates sub-channel Q from the given index and index-offset.
|
|
||||||
void GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset);
|
|
||||||
|
|
||||||
/// Synthesis of lead-out data.
|
/// Synthesis of lead-out data.
|
||||||
void AddLeadOutIndex();
|
void AddLeadOutIndex();
|
||||||
|
|
Loading…
Reference in New Issue