GCMemcard: Check if the Directory's number-of-blocks claim for files matches the BAT, and report an error if it doesn't.

This commit is contained in:
Admiral H. Curtiss 2019-08-15 02:06:29 +02:00
parent 7d4cabea07
commit ba8ffd9391
2 changed files with 70 additions and 5 deletions

View File

@ -43,7 +43,8 @@ bool GCMemcardErrorCode::HasCriticalErrors() const
Test(GCMemcardValidityIssues::INVALID_CARD_SIZE) ||
Test(GCMemcardValidityIssues::INVALID_CHECKSUM) ||
Test(GCMemcardValidityIssues::MISMATCHED_CARD_SIZE) ||
Test(GCMemcardValidityIssues::FREE_BLOCK_MISMATCH);
Test(GCMemcardValidityIssues::FREE_BLOCK_MISMATCH) ||
Test(GCMemcardValidityIssues::DIR_BAT_INCONSISTENT);
}
bool GCMemcardErrorCode::Test(GCMemcardValidityIssues code) const
@ -265,10 +266,12 @@ std::pair<GCMemcardErrorCode, std::optional<GCMemcard>> GCMemcard::Open(std::str
else
card.m_active_bat = 1;
// TODO: Do a card-wide corruption check here, such as cross-checking the active Dir's files with
// the active BAT's allocated sectors. The GC BIOS does at the very least recognize if a file's
// number of used blocks is inconsistent between the two, and will count a card as corrupted if
// that is the case.
// check for consistency between the active Dir and BAT
const GCMemcardErrorCode dir_bat_consistency_error_code =
card.GetActiveDirectory().CheckForErrorsWithBat(card.GetActiveBat());
error_code |= dir_bat_consistency_error_code;
if (error_code.HasCriticalErrors())
return std::make_pair(error_code, std::nullopt);
card.m_valid = true;
@ -1623,3 +1626,60 @@ GCMemcardErrorCode Directory::CheckForErrors() const
return error_code;
}
GCMemcardErrorCode Directory::CheckForErrorsWithBat(const BlockAlloc& bat) const
{
GCMemcardErrorCode error_code;
for (u8 i = 0; i < DIRLEN; ++i)
{
const DEntry& entry = m_dir_entries[i];
if (entry.m_gamecode == DEntry::UNINITIALIZED_GAMECODE)
continue;
// check if we end up with the same number of blocks when traversing through the BAT using the
// given first block
const u16 dir_number_of_blocks = entry.m_block_count;
const u16 dir_first_block = entry.m_first_block;
bool bat_block_count_matches = false;
{
u16 remaining_blocks = dir_number_of_blocks;
u16 current_block = dir_first_block;
while (true)
{
if (remaining_blocks == 0)
{
// we should be at the last block but haven't seen the last-block BAT indicator yet, file
// is larger according to BAT, so we're inconsistent
break;
}
--remaining_blocks;
const u16 next_block = bat.GetNextBlock(current_block);
if (next_block == 0)
{
// current block is out-of-range or next block is unallocated, this is definitely wrong
break;
}
if (next_block == 0xFFFF)
{
// we're at the final block according to the BAT
// if there are zero remaining blocks according to the directory we're consistent,
// otherwise the file is smaller according to the BAT and we're inconsistent
bat_block_count_matches = remaining_blocks == 0;
break;
}
current_block = next_block;
}
}
if (!bat_block_count_matches)
{
error_code.Set(GCMemcardValidityIssues::DIR_BAT_INCONSISTENT);
break;
}
}
// TODO: We could also check if every allocated BAT block is actually reachable with the files.
return error_code;
}

View File

@ -88,6 +88,7 @@ enum class GCMemcardValidityIssues
INVALID_CHECKSUM,
MISMATCHED_CARD_SIZE,
FREE_BLOCK_MISMATCH,
DIR_BAT_INCONSISTENT,
DATA_IN_UNUSED_AREA,
COUNT
};
@ -302,6 +303,8 @@ struct DEntry
};
static_assert(sizeof(DEntry) == DENTRY_SIZE);
struct BlockAlloc;
struct Directory
{
// 127 files of 0x40 bytes each
@ -330,6 +333,8 @@ struct Directory
std::pair<u16, u16> CalculateChecksums() const;
GCMemcardErrorCode CheckForErrors() const;
GCMemcardErrorCode CheckForErrorsWithBat(const BlockAlloc& bat) const;
};
static_assert(sizeof(Directory) == BLOCK_SIZE);