GCMemcard: Change behavior of TitlePresent() to more closely resemble how saves are actually identified.
This modifies GCMemcard::TitlePresent() to match my findings of how the GC BIOS and various games behave when you alter the fields in the directory entry. It looks like for a save to be recognized by a game, the following have to be true: - Game code and maker code must exactly match what the game expects. - Filename is only checked up to the first null byte. All bytes afterwards can be whatever. The BIOS itself does a full compare of the filename when checking for whether it should allow copying a file from one card to another, but behaves oddly in some cases when there's non-null bytes after the first null. See the big comment in `HasSameIdentity()` for details.
This commit is contained in:
parent
1ab37990b1
commit
556e93f357
|
@ -226,6 +226,8 @@ add_library(core
|
||||||
HW/GCMemcard/GCMemcardDirectory.h
|
HW/GCMemcard/GCMemcardDirectory.h
|
||||||
HW/GCMemcard/GCMemcardRaw.cpp
|
HW/GCMemcard/GCMemcardRaw.cpp
|
||||||
HW/GCMemcard/GCMemcardRaw.h
|
HW/GCMemcard/GCMemcardRaw.h
|
||||||
|
HW/GCMemcard/GCMemcardUtils.cpp
|
||||||
|
HW/GCMemcard/GCMemcardUtils.h
|
||||||
HW/GCPad.cpp
|
HW/GCPad.cpp
|
||||||
HW/GCPad.h
|
HW/GCPad.h
|
||||||
HW/GCPadEmu.cpp
|
HW/GCPadEmu.cpp
|
||||||
|
|
|
@ -158,6 +158,7 @@
|
||||||
<ClCompile Include="HW\GCMemcard\GCMemcard.cpp" />
|
<ClCompile Include="HW\GCMemcard\GCMemcard.cpp" />
|
||||||
<ClCompile Include="HW\GCMemcard\GCMemcardDirectory.cpp" />
|
<ClCompile Include="HW\GCMemcard\GCMemcardDirectory.cpp" />
|
||||||
<ClCompile Include="HW\GCMemcard\GCMemcardRaw.cpp" />
|
<ClCompile Include="HW\GCMemcard\GCMemcardRaw.cpp" />
|
||||||
|
<ClCompile Include="HW\GCMemcard\GCMemcardUtils.cpp" />
|
||||||
<ClCompile Include="HW\GCPad.cpp" />
|
<ClCompile Include="HW\GCPad.cpp" />
|
||||||
<ClCompile Include="HW\GCPadEmu.cpp" />
|
<ClCompile Include="HW\GCPadEmu.cpp" />
|
||||||
<ClCompile Include="HW\GPFifo.cpp" />
|
<ClCompile Include="HW\GPFifo.cpp" />
|
||||||
|
@ -517,6 +518,7 @@
|
||||||
<ClInclude Include="HW\GCMemcard\GCMemcardBase.h" />
|
<ClInclude Include="HW\GCMemcard\GCMemcardBase.h" />
|
||||||
<ClInclude Include="HW\GCMemcard\GCMemcardDirectory.h" />
|
<ClInclude Include="HW\GCMemcard\GCMemcardDirectory.h" />
|
||||||
<ClInclude Include="HW\GCMemcard\GCMemcardRaw.h" />
|
<ClInclude Include="HW\GCMemcard\GCMemcardRaw.h" />
|
||||||
|
<ClInclude Include="HW\GCMemcard\GCMemcardUtils.h" />
|
||||||
<ClInclude Include="HW\GCPad.h" />
|
<ClInclude Include="HW\GCPad.h" />
|
||||||
<ClInclude Include="HW\GCPadEmu.h" />
|
<ClInclude Include="HW\GCPadEmu.h" />
|
||||||
<ClInclude Include="HW\GPFifo.h" />
|
<ClInclude Include="HW\GPFifo.h" />
|
||||||
|
|
|
@ -481,6 +481,9 @@
|
||||||
<ClCompile Include="HW\GCMemcard\GCMemcardRaw.cpp">
|
<ClCompile Include="HW\GCMemcard\GCMemcardRaw.cpp">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="HW\GCMemcard\GCMemcardUtils.cpp">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="HW\GCPad.cpp">
|
<ClCompile Include="HW\GCPad.cpp">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\GCPad</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\GCPad</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -1254,6 +1257,9 @@
|
||||||
<ClInclude Include="HW\GCMemcard\GCMemcardRaw.h">
|
<ClInclude Include="HW\GCMemcard\GCMemcardRaw.h">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="HW\GCMemcard\GCMemcardUtils.h">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="HW\GCPadEmu.h">
|
<ClInclude Include="HW\GCPadEmu.h">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\GCPad</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\GCPad</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
#include "Common/Swap.h"
|
#include "Common/Swap.h"
|
||||||
|
|
||||||
|
#include "Core/HW/GCMemcard/GCMemcardUtils.h"
|
||||||
|
|
||||||
static constexpr std::optional<u64> BytesToMegabits(u64 bytes)
|
static constexpr std::optional<u64> BytesToMegabits(u64 bytes)
|
||||||
{
|
{
|
||||||
const u64 factor = ((1024 * 1024) / 8);
|
const u64 factor = ((1024 * 1024) / 8);
|
||||||
|
@ -405,22 +407,19 @@ u16 GCMemcard::GetFreeBlocks() const
|
||||||
return GetActiveBat().m_free_blocks;
|
return GetActiveBat().m_free_blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
u8 GCMemcard::TitlePresent(const DEntry& d) const
|
std::optional<u8> GCMemcard::TitlePresent(const DEntry& d) const
|
||||||
{
|
{
|
||||||
if (!m_valid)
|
if (!m_valid)
|
||||||
return DIRLEN;
|
return std::nullopt;
|
||||||
|
|
||||||
u8 i = 0;
|
const Directory& dir = GetActiveDirectory();
|
||||||
while (i < DIRLEN)
|
for (u8 i = 0; i < DIRLEN; ++i)
|
||||||
{
|
{
|
||||||
if (GetActiveDirectory().m_dir_entries[i].m_gamecode == d.m_gamecode &&
|
if (HasSameIdentity(dir.m_dir_entries[i], d))
|
||||||
GetActiveDirectory().m_dir_entries[i].m_filename == d.m_filename)
|
return i;
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
return i;
|
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GCMemcard::GCI_FileName(u8 index, std::string& filename) const
|
bool GCMemcard::GCI_FileName(u8 index, std::string& filename) const
|
||||||
|
@ -853,7 +852,7 @@ GCMemcardImportFileRetVal GCMemcard::ImportFile(const DEntry& direntry,
|
||||||
{
|
{
|
||||||
return GCMemcardImportFileRetVal::OUTOFBLOCKS;
|
return GCMemcardImportFileRetVal::OUTOFBLOCKS;
|
||||||
}
|
}
|
||||||
if (TitlePresent(direntry) != DIRLEN)
|
if (TitlePresent(direntry))
|
||||||
{
|
{
|
||||||
return GCMemcardImportFileRetVal::TITLEPRESENT;
|
return GCMemcardImportFileRetVal::TITLEPRESENT;
|
||||||
}
|
}
|
||||||
|
|
|
@ -460,8 +460,9 @@ public:
|
||||||
// get the free blocks from bat
|
// get the free blocks from bat
|
||||||
u16 GetFreeBlocks() const;
|
u16 GetFreeBlocks() const;
|
||||||
|
|
||||||
// If title already on memcard returns index, otherwise returns -1
|
// Returns index of the save with the same identity as the given DEntry, or nullopt if no save
|
||||||
u8 TitlePresent(const DEntry& d) const;
|
// with that identity exists in this card.
|
||||||
|
std::optional<u8> TitlePresent(const DEntry& d) const;
|
||||||
|
|
||||||
bool GCI_FileName(u8 index, std::string& filename) const;
|
bool GCI_FileName(u8 index, std::string& filename) const;
|
||||||
// DEntry functions, all take u8 index < DIRLEN (127)
|
// DEntry functions, all take u8 index < DIRLEN (127)
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
#include "Core/HW/GCMemcard/GCMemcardUtils.h"
|
||||||
|
|
||||||
|
#include "Core/HW/GCMemcard/GCMemcard.h"
|
||||||
|
|
||||||
|
namespace Memcard
|
||||||
|
{
|
||||||
|
bool HasSameIdentity(const DEntry& lhs, const DEntry& rhs)
|
||||||
|
{
|
||||||
|
// The Gamecube BIOS identifies two files as being 'the same' (that is, disallows copying from one
|
||||||
|
// card to another when both contain a file like it) when the full array of all of m_gamecode,
|
||||||
|
// m_makercode, and m_filename match between them.
|
||||||
|
|
||||||
|
// However, despite that, it seems like the m_filename should be treated as a nullterminated
|
||||||
|
// string instead, because:
|
||||||
|
// - Games seem to identify their saves regardless of what bytes appear after the first null byte.
|
||||||
|
// - If you have two files that match except for bytes after the first null in m_filename, the
|
||||||
|
// BIOS behaves oddly if you attempt to copy the files, as it seems to clear out those extra
|
||||||
|
// non-null bytes. See below for details.
|
||||||
|
|
||||||
|
// Specifically, the following chain of actions fails with a rather vague 'The data may not have
|
||||||
|
// been copied.' error message:
|
||||||
|
// - Have two memory cards with one save file each.
|
||||||
|
// - The two save files should have identical gamecode and makercode, as well as an equivalent
|
||||||
|
// filename up until and including the first null byte.
|
||||||
|
// - On Card A have all remaining bytes of the filename also be null.
|
||||||
|
// - On Card B have at least one of the remaining bytes be non-null.
|
||||||
|
// - Copy the file on Card B to Card A.
|
||||||
|
// The BIOS will abort halfway through the copy process and declare Card B as unusable until you
|
||||||
|
// eject and reinsert it, and leave a "Broken File000" file on Card A, though note that the file
|
||||||
|
// is not visible and will be cleaned up when reinserting the card while still within the BIOS.
|
||||||
|
// Additionally, either during or after the copy process, the bytes after the first null on Card B
|
||||||
|
// are changed to null, which is presumably why the copy process ends up failing as Card A would
|
||||||
|
// then have two identical files. For reference, the Wii System Menu behaves exactly the same.
|
||||||
|
|
||||||
|
// With all that in mind, even if it mismatches the comparison behavior of the BIOS, we treat
|
||||||
|
// m_filename as a nullterminated string for determining if two files identify as the same, as not
|
||||||
|
// doing so would cause more harm and confusion that good in practice.
|
||||||
|
|
||||||
|
if (lhs.m_gamecode != rhs.m_gamecode)
|
||||||
|
return false;
|
||||||
|
if (lhs.m_makercode != rhs.m_makercode)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < lhs.m_filename.size(); ++i)
|
||||||
|
{
|
||||||
|
const u8 a = lhs.m_filename[i];
|
||||||
|
const u8 b = rhs.m_filename[i];
|
||||||
|
if (a == 0)
|
||||||
|
return b == 0;
|
||||||
|
if (a != b)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace Memcard
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright 2020 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Memcard
|
||||||
|
{
|
||||||
|
struct DEntry;
|
||||||
|
|
||||||
|
bool HasSameIdentity(const DEntry& lhs, const DEntry& rhs);
|
||||||
|
} // namespace Memcard
|
Loading…
Reference in New Issue