mirror of https://github.com/PCSX2/pcsx2.git
MemoryCard: Full initial implementation of the FolderMemoryCard.
FileMemoryCard: Log reads and writes so I know what kind of commands I have to deal with. FolderMemoryCard: Create basic class/method outline based on FileMemoryCard. FolderMemoryCard: Add a FolderMemoryCardAggregator so I don't have to write every method in a way that has to handle more than one memory ca Also shuffle around the location of code because C++ cares about stuff needing to be defined before they're usable. FolderMemoryCard: Implement Open(). FolderMemoryCard: Implement GetSizeInfo(). FolderMemoryCard: Implement some basic structure of Read() FolderMemoryCard: Implement parts of Read() and Save(). Shouldn't it be Write() or Load()? Anyway, this doesn't work yet, but it gets part of the formatting procedure done which is at least something! FolderMemoryCard: Add method to calculate ECC. FolderMemoryCard: Start implementing the FAT. MemoryCard: More logging. FolderMemoryCard: Formatting works now! Formatted memory card isn't actually recognized as formatted yet because I don't store folder metadata yet, but we're getting there! FolderMemoryCard: Recognize when it's trying to access a data cluster. FolderMemoryCard: Add directory/file entry support. On further inspection this might not a be a good way to handle erasing. FolderMemoryCard: Method to get a file entry and file path from a file's data cluster. FolderMemoryCard: wxDirName is garbage, let's just use wxFileName for the folder too... FolderMemoryCard: Fix Erase method. FolderMemoryCard: Start implementing file writes. This is still quite broken but we're getting somewhere here! FolderMemoryCard: Load the data from the host file system into the memory card on emulation start. Also store superblock to host file system on end. FolderMemoryCard: Fix a few warnings. FolderMemoryCard: Implement file reads. FolderMemoryCard: Proper ECC reads. FolderMemoryCard: Reads to unmapped locations should return all 0xFF. FolderMemoryCard: Some sort of working WriteToFile. (Note: Doesn't always work depending on what order data gets written...) FolderMemoryCard: Forgot a 'b' for reading files in binary mode. Whoops. FolderMemoryCard: Load timestamps from the host filesystem. FolderMemoryCard: r+b needs the file to exist so create if it doesn't. FolderMemoryCard: Failsafe to allow non-sequential writes. FolderMemoryCard: Use a cache for writes. Does not flush to host FS yet! FolderMemoryCard: Flush the data written to the cache to the host file system on exit. FolderMemoryCard: Since we have a cache now, remove code related to formatting, it's no longer needed. FolderMemoryCard: More binary file mode mistakes... FolderMemoryCard: Make it actually possible to disable/eject cards. FileMemoryCard: Revert changes made for logging data. FolderMemoryCard: Remove excessive logging. MemoryCard: Note that the superblock struct is no longer unused. FolderMemoryCard: A disabled card shouldn't try writing data on exit. FolderMemoryCard: Log when flushing data. FolderMemoryCard: Replace plain constants with const variables. Should make it easier in the future to change the memory card size, if needed. FolderMemoryCard: Sort of handle the case when the total size of files in the memory card folder exceed the size of the card. Not elegant but prevents ugly errors. The file that caused the card to "overflow" will be seen as corrupted data by the PS2 browser. FolderMemoryCard: Some sanity checks. FolderMemoryCard: superBlock member really should have that m_ too to be consistent. MemoryCard: Switch back to FileMemoryCard for merging. FolderMemoryCard: Implement GetCRC() via a timestamp of the last memory card write. Reasoning: Regarding auto-ejecting on save load, I see that the current implementation checks that by comparing memory card CRC and reinserting if it mismatches. Since it's actually just about seeing if the memory card state of the savestate and the current state match, my GetCRC() now returns a millisecond timestamp of the last time the card was written to. This should work out to the intended result, though I had to use wxGetLocalTimeMillis() instead of wxGetUTCTimeMillis() since the latter isn't available for some reason. Fix GCC warnings and error. MemoryCard: Switch implementations via a #define. FolderMemoryCard: Add a NextFrame() method that should be called once per frame. Flushes written data to the host file system after a certain amout of frames have passed without any writes (currently 60). MemoryCard: Add the NextFrame() method to the plugin API. Counters: If the FolderMemoryCard is selected, inform it every frame in VSyncEnd() that a frame has passed. VSyncEnd: Probably better to inform the memory card before the frame limiting. Fix error when using wxWidgets >= 3.0. FolderMemoryCard: Extract into its own .h/.cpp files. FolderMemoryCard: Change cache to a map to reduce memory usage. FolderMemoryCard: More gracefully handle lack of space when adding files.
This commit is contained in:
parent
875be67a7e
commit
1d46800888
|
@ -1104,7 +1104,12 @@ typedef struct _PS2E_ComponentAPI_Mcd
|
||||||
|
|
||||||
u64 (PS2E_CALLBACK* McdGetCRC)( PS2E_THISPTR thisptr, uint port, uint slot );
|
u64 (PS2E_CALLBACK* McdGetCRC)( PS2E_THISPTR thisptr, uint port, uint slot );
|
||||||
|
|
||||||
void* reserved[8];
|
// McdNextFrame
|
||||||
|
// Inform the memory card that a frame of emulation time has passed.
|
||||||
|
// Used by the FolderMemoryCard to find a good time to flush written data to the host file system.
|
||||||
|
void (PS2E_CALLBACK* McdNextFrame)( PS2E_THISPTR thisptr, uint port, uint slot );
|
||||||
|
|
||||||
|
void* reserved[7];
|
||||||
|
|
||||||
} PS2E_ComponentAPI_Mcd;
|
} PS2E_ComponentAPI_Mcd;
|
||||||
|
|
||||||
|
|
|
@ -296,6 +296,7 @@ set(pcsx2GuiSources
|
||||||
gui/MainFrame.cpp
|
gui/MainFrame.cpp
|
||||||
gui/MainMenuClicks.cpp
|
gui/MainMenuClicks.cpp
|
||||||
gui/MemoryCardFile.cpp
|
gui/MemoryCardFile.cpp
|
||||||
|
gui/MemoryCardFolder.cpp
|
||||||
gui/Panels/BaseApplicableConfigPanel.cpp
|
gui/Panels/BaseApplicableConfigPanel.cpp
|
||||||
gui/Panels/MemoryCardListPanel.cpp
|
gui/Panels/MemoryCardListPanel.cpp
|
||||||
gui/MessageBoxes.cpp
|
gui/MessageBoxes.cpp
|
||||||
|
@ -348,6 +349,7 @@ set(pcsx2GuiHeaders
|
||||||
gui/IsoDropTarget.h
|
gui/IsoDropTarget.h
|
||||||
gui/MainFrame.h
|
gui/MainFrame.h
|
||||||
gui/MemoryCardFile.h
|
gui/MemoryCardFile.h
|
||||||
|
gui/MemoryCardFolder.h
|
||||||
gui/MSWstuff.h
|
gui/MSWstuff.h
|
||||||
gui/Panels/ConfigurationPanels.h
|
gui/Panels/ConfigurationPanels.h
|
||||||
gui/Panels/LogOptionsPanels.h
|
gui/Panels/LogOptionsPanels.h
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
|
|
||||||
#include "ps2/HwInternal.h"
|
#include "ps2/HwInternal.h"
|
||||||
|
|
||||||
|
#include "Sio.h"
|
||||||
|
|
||||||
using namespace Threading;
|
using namespace Threading;
|
||||||
|
|
||||||
extern u8 psxhblankgate;
|
extern u8 psxhblankgate;
|
||||||
|
@ -428,6 +430,12 @@ static __fi void VSyncEnd(u32 sCycle)
|
||||||
hwIntcIrq(INTC_VBLANK_E); // HW Irq
|
hwIntcIrq(INTC_VBLANK_E); // HW Irq
|
||||||
psxVBlankEnd(); // psxCounters vBlank End
|
psxVBlankEnd(); // psxCounters vBlank End
|
||||||
if (gates) rcntEndGate(true, sCycle); // Counters End Gate Code
|
if (gates) rcntEndGate(true, sCycle); // Counters End Gate Code
|
||||||
|
|
||||||
|
#ifdef MEMORYCARD_USE_FOLDER
|
||||||
|
// FolderMemoryCard needs information on how much time has passed since the last write
|
||||||
|
sioNextFrame();
|
||||||
|
#endif
|
||||||
|
|
||||||
frameLimit(); // limit FPS
|
frameLimit(); // limit FPS
|
||||||
|
|
||||||
//Do this here, breaks Dynasty Warriors otherwise.
|
//Do this here, breaks Dynasty Warriors otherwise.
|
||||||
|
|
|
@ -66,6 +66,9 @@ u64 SysPluginBindings::McdGetCRC( uint port, uint slot )
|
||||||
return Mcd->McdGetCRC( (PS2E_THISPTR) Mcd, port, slot );
|
return Mcd->McdGetCRC( (PS2E_THISPTR) Mcd, port, slot );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SysPluginBindings::McdNextFrame( uint port, uint slot ) {
|
||||||
|
Mcd->McdNextFrame( (PS2E_THISPTR) Mcd, port, slot );
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Yay, order of this array shouldn't be important. :)
|
// Yay, order of this array shouldn't be important. :)
|
||||||
|
|
|
@ -241,6 +241,7 @@ public:
|
||||||
void McdSave( uint port, uint slot, const u8 *src, u32 adr, int size );
|
void McdSave( uint port, uint slot, const u8 *src, u32 adr, int size );
|
||||||
void McdEraseBlock( uint port, uint slot, u32 adr );
|
void McdEraseBlock( uint port, uint slot, u32 adr );
|
||||||
u64 McdGetCRC( uint port, uint slot );
|
u64 McdGetCRC( uint port, uint slot );
|
||||||
|
void McdNextFrame( uint port, uint slot );
|
||||||
|
|
||||||
friend class SysCorePlugins;
|
friend class SysCorePlugins;
|
||||||
};
|
};
|
||||||
|
|
|
@ -871,6 +871,14 @@ void SIODMAWrite(u8 value)
|
||||||
sioWrite8inl(value);
|
sioWrite8inl(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sioNextFrame() {
|
||||||
|
for ( uint port = 0; port < 2; ++port ) {
|
||||||
|
for ( uint slot = 0; slot < 4; ++slot ) {
|
||||||
|
mcds[port][slot].NextFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SaveStateBase::sioFreeze()
|
void SaveStateBase::sioFreeze()
|
||||||
{
|
{
|
||||||
// CRCs for memory cards.
|
// CRCs for memory cards.
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
// Games are highly unlikely to need timed IRQ's for PAD and MemoryCard handling anyway (rama).
|
// Games are highly unlikely to need timed IRQ's for PAD and MemoryCard handling anyway (rama).
|
||||||
#define SIO_INLINE_IRQS
|
#define SIO_INLINE_IRQS
|
||||||
|
|
||||||
|
#include "MemoryCardFile.h"
|
||||||
|
|
||||||
struct _mcd
|
struct _mcd
|
||||||
{
|
{
|
||||||
u8 term; // terminator value;
|
u8 term; // terminator value;
|
||||||
|
@ -80,6 +82,10 @@ struct _mcd
|
||||||
{
|
{
|
||||||
return SysPlugins.McdGetCRC(port, slot);
|
return SysPlugins.McdGetCRC(port, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NextFrame() {
|
||||||
|
SysPlugins.McdNextFrame( port, slot );
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct _sio
|
struct _sio
|
||||||
|
@ -117,3 +123,4 @@ extern void sioWriteCtrl16(u16 value);
|
||||||
extern void sioInterrupt();
|
extern void sioInterrupt();
|
||||||
extern void InitializeSIO(u8 value);
|
extern void InitializeSIO(u8 value);
|
||||||
extern void SetForceMcdEjectTimeoutNow();
|
extern void SetForceMcdEjectTimeoutNow();
|
||||||
|
extern void sioNextFrame();
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
#include "PrecompiledHeader.h"
|
#include "PrecompiledHeader.h"
|
||||||
#include "Utilities/SafeArray.inl"
|
#include "Utilities/SafeArray.inl"
|
||||||
#include <wx/file.h>
|
#include <wx/file.h>
|
||||||
|
#include <wx/dir.h>
|
||||||
#include "MemoryCardFile.h"
|
#include <wx/stopwatch.h>
|
||||||
|
|
||||||
// IMPORTANT! If this gets a macro redefinition error it means PluginCallbacks.h is included
|
// IMPORTANT! If this gets a macro redefinition error it means PluginCallbacks.h is included
|
||||||
// in a global-scope header, and that's a BAD THING. Include it only into modules that need
|
// in a global-scope header, and that's a BAD THING. Include it only into modules that need
|
||||||
|
@ -26,12 +26,16 @@
|
||||||
struct Component_FileMcd;
|
struct Component_FileMcd;
|
||||||
#define PS2E_THISPTR Component_FileMcd*
|
#define PS2E_THISPTR Component_FileMcd*
|
||||||
|
|
||||||
|
#include "MemoryCardFile.h"
|
||||||
|
#include "MemoryCardFolder.h"
|
||||||
|
|
||||||
#include "System.h"
|
#include "System.h"
|
||||||
#include "AppConfig.h"
|
#include "AppConfig.h"
|
||||||
|
|
||||||
#include "svnrev.h"
|
#include "svnrev.h"
|
||||||
|
|
||||||
#include <wx/ffile.h>
|
#include <wx/ffile.h>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
static const int MCD_SIZE = 1024 * 8 * 16; // Legacy PSX card default size
|
static const int MCD_SIZE = 1024 * 8 * 16; // Legacy PSX card default size
|
||||||
|
|
||||||
|
@ -404,7 +408,11 @@ u64 FileMemoryCard::GetCRC( uint slot )
|
||||||
struct Component_FileMcd
|
struct Component_FileMcd
|
||||||
{
|
{
|
||||||
PS2E_ComponentAPI_Mcd api; // callbacks the plugin provides back to the emulator
|
PS2E_ComponentAPI_Mcd api; // callbacks the plugin provides back to the emulator
|
||||||
|
#ifdef MEMORYCARD_USE_FOLDER
|
||||||
|
FolderMemoryCardAggregator impl;
|
||||||
|
#else
|
||||||
FileMemoryCard impl; // class-based implementations we refer to when API is invoked
|
FileMemoryCard impl; // class-based implementations we refer to when API is invoked
|
||||||
|
#endif
|
||||||
|
|
||||||
Component_FileMcd();
|
Component_FileMcd();
|
||||||
};
|
};
|
||||||
|
@ -461,6 +469,12 @@ static u64 PS2E_CALLBACK FileMcd_GetCRC( PS2E_THISPTR thisptr, uint port, uint s
|
||||||
return thisptr->impl.GetCRC( FileMcd_ConvertToSlot( port, slot ) );
|
return thisptr->impl.GetCRC( FileMcd_ConvertToSlot( port, slot ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void PS2E_CALLBACK FileMcd_NextFrame( PS2E_THISPTR thisptr, uint port, uint slot ) {
|
||||||
|
#ifdef MEMORYCARD_USE_FOLDER
|
||||||
|
thisptr->impl.NextFrame( FileMcd_ConvertToSlot( port, slot ) );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
Component_FileMcd::Component_FileMcd()
|
Component_FileMcd::Component_FileMcd()
|
||||||
{
|
{
|
||||||
memzero( api );
|
memzero( api );
|
||||||
|
@ -475,6 +489,7 @@ Component_FileMcd::Component_FileMcd()
|
||||||
api.McdSave = FileMcd_Save;
|
api.McdSave = FileMcd_Save;
|
||||||
api.McdEraseBlock = FileMcd_EraseBlock;
|
api.McdEraseBlock = FileMcd_EraseBlock;
|
||||||
api.McdGetCRC = FileMcd_GetCRC;
|
api.McdGetCRC = FileMcd_GetCRC;
|
||||||
|
api.McdNextFrame = FileMcd_NextFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -547,32 +562,6 @@ extern "C" const PS2E_LibraryAPI* FileMcd_InitAPI( const PS2E_EmulatorInfo* emui
|
||||||
return &FileMcd_Library;
|
return &FileMcd_Library;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------
|
|
||||||
// Currently Unused Superblock Header Struct
|
|
||||||
// --------------------------------------------------------------------------------------
|
|
||||||
// (provided for reference purposes)
|
|
||||||
|
|
||||||
struct superblock
|
|
||||||
{
|
|
||||||
char magic[28]; // 0x00
|
|
||||||
char version[12]; // 0x1c
|
|
||||||
u16 page_len; // 0x28
|
|
||||||
u16 pages_per_cluster; // 0x2a
|
|
||||||
u16 pages_per_block; // 0x2c
|
|
||||||
u16 unused; // 0x2e
|
|
||||||
u32 clusters_per_card; // 0x30
|
|
||||||
u32 alloc_offset; // 0x34
|
|
||||||
u32 alloc_end; // 0x38
|
|
||||||
u32 rootdir_cluster; // 0x3c
|
|
||||||
u32 backup_block1; // 0x40
|
|
||||||
u32 backup_block2; // 0x44
|
|
||||||
u32 ifc_list[32]; // 0x50
|
|
||||||
u32 bad_block_list[32]; // 0xd0
|
|
||||||
u8 card_type; // 0x150
|
|
||||||
u8 card_flags; // 0x151
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
//Tests if a string is a valid name for a new file within a specified directory.
|
//Tests if a string is a valid name for a new file within a specified directory.
|
||||||
//returns true if:
|
//returns true if:
|
||||||
// - the file name has a minimum length of minNumCharacters chars (default is 5 chars: at least 1 char + '.' + 3-chars extension)
|
// - the file name has a minimum length of minNumCharacters chars (default is 5 chars: at least 1 char + '.' + 3-chars extension)
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
// define this to use the FolderMemoryCard implementation instead of the regular FileMemoryCard one
|
||||||
|
//#define MEMORYCARD_USE_FOLDER
|
||||||
|
|
||||||
// NOTICE! This file is intended as a temporary placebo only, until such time that the
|
// NOTICE! This file is intended as a temporary placebo only, until such time that the
|
||||||
// memorycard system is properly extracted into a plugin system (which would make it a
|
// memorycard system is properly extracted into a plugin system (which would make it a
|
||||||
// separate project file).
|
// separate project file).
|
||||||
|
|
|
@ -0,0 +1,961 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PrecompiledHeader.h"
|
||||||
|
#include "Utilities/SafeArray.inl"
|
||||||
|
|
||||||
|
#include "MemoryCardFile.h"
|
||||||
|
#include "MemoryCardFolder.h"
|
||||||
|
|
||||||
|
#include "System.h"
|
||||||
|
#include "AppConfig.h"
|
||||||
|
|
||||||
|
#include "svnrev.h"
|
||||||
|
|
||||||
|
FolderMemoryCard::FolderMemoryCard() {
|
||||||
|
m_slot = 0;
|
||||||
|
m_isEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::InitializeInternalData() {
|
||||||
|
memset( &m_superBlock, 0xFF, sizeof( m_superBlock ) );
|
||||||
|
memset( &m_indirectFat, 0xFF, sizeof( m_indirectFat ) );
|
||||||
|
memset( &m_fat, 0xFF, sizeof( m_fat ) );
|
||||||
|
memset( &m_backupBlock1, 0xFF, sizeof( m_backupBlock1 ) );
|
||||||
|
memset( &m_backupBlock2, 0xFF, sizeof( m_backupBlock2 ) );
|
||||||
|
m_cache.clear();
|
||||||
|
m_timeLastWritten = 0;
|
||||||
|
m_isEnabled = false;
|
||||||
|
m_framesUntilFlush = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FolderMemoryCard::IsFormatted() {
|
||||||
|
// this should be a good enough arbitrary check, if someone can think of a case where this doesn't work feel free to change
|
||||||
|
return m_superBlock.raw[0x16] == 0x6F;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::Open() {
|
||||||
|
InitializeInternalData();
|
||||||
|
|
||||||
|
wxFileName configuredFileName( g_Conf->FullpathToMcd( m_slot ) );
|
||||||
|
folderName = wxFileName( configuredFileName.GetFullPath() + L"/" );
|
||||||
|
wxString str( configuredFileName.GetFullPath() );
|
||||||
|
bool disabled = false;
|
||||||
|
|
||||||
|
if ( g_Conf->Mcd[m_slot].Enabled && g_Conf->Mcd[m_slot].Type == MemoryCardType::MemoryCard_Folder ) {
|
||||||
|
if ( configuredFileName.GetFullName().IsEmpty() ) {
|
||||||
|
str = L"[empty filename]";
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
if ( !disabled && configuredFileName.FileExists() ) {
|
||||||
|
str = L"[is file, should be folder]";
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if nothing exists at a valid location, create a directory for the memory card
|
||||||
|
if ( !disabled && !folderName.DirExists() ) {
|
||||||
|
if ( !folderName.Mkdir() ) {
|
||||||
|
str = L"[couldn't create folder]";
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
str = L"[disabled]";
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLn( disabled ? Color_Gray : Color_Green, L"McdSlot %u: [Folder] " + str, m_slot );
|
||||||
|
if ( disabled ) return;
|
||||||
|
|
||||||
|
m_isEnabled = true;
|
||||||
|
LoadMemoryCardData();
|
||||||
|
|
||||||
|
SetTimeLastWrittenToNow();
|
||||||
|
m_framesUntilFlush = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::Close() {
|
||||||
|
if ( !m_isEnabled ) { return; }
|
||||||
|
|
||||||
|
Flush();
|
||||||
|
|
||||||
|
wxFileName superBlockFileName( folderName.GetPath(), L"_pcsx2_superblock" );
|
||||||
|
wxFFile superBlockFile( superBlockFileName.GetFullPath().c_str(), L"wb" );
|
||||||
|
if ( superBlockFile.IsOpened() ) {
|
||||||
|
superBlockFile.Write( &m_superBlock.raw, sizeof( m_superBlock.raw ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::LoadMemoryCardData() {
|
||||||
|
bool formatted = false;
|
||||||
|
|
||||||
|
// read superblock if it exists
|
||||||
|
wxFileName superBlockFileName( folderName.GetPath(), L"_pcsx2_superblock" );
|
||||||
|
if ( superBlockFileName.FileExists() ) {
|
||||||
|
wxFFile superBlockFile( superBlockFileName.GetFullPath().c_str(), L"rb" );
|
||||||
|
if ( superBlockFile.IsOpened() && superBlockFile.Read( &m_superBlock.raw, sizeof( m_superBlock.raw ) ) >= sizeof( m_superBlock.data ) ) {
|
||||||
|
formatted = IsFormatted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if superblock was valid, load folders and files
|
||||||
|
if ( formatted ) {
|
||||||
|
CreateFat();
|
||||||
|
CreateRootDir();
|
||||||
|
MemoryCardFileEntry* const rootDirEntry = &m_fileEntryDict[m_superBlock.data.rootdir_cluster].entries[0];
|
||||||
|
AddFolder( rootDirEntry, folderName.GetPath() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::CreateFat() {
|
||||||
|
const u32 totalClusters = m_superBlock.data.clusters_per_card;
|
||||||
|
const u32 clusterSize = m_superBlock.data.page_len * m_superBlock.data.pages_per_cluster;
|
||||||
|
const u32 fatEntriesPerCluster = clusterSize / 4;
|
||||||
|
const u32 countFatClusters = ( totalClusters % fatEntriesPerCluster ) != 0 ? ( totalClusters / fatEntriesPerCluster + 1 ) : ( totalClusters / fatEntriesPerCluster );
|
||||||
|
const u32 countDataClusters = m_superBlock.data.alloc_end;
|
||||||
|
|
||||||
|
// create indirect FAT
|
||||||
|
for ( unsigned int i = 0; i < countFatClusters; ++i ) {
|
||||||
|
m_indirectFat.data[0][i] = GetFreeSystemCluster();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill FAT with default values
|
||||||
|
for ( unsigned int i = 0; i < countDataClusters; ++i ) {
|
||||||
|
m_fat.data[0][0][i] = 0x7FFFFFFFu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::CreateRootDir() {
|
||||||
|
MemoryCardFileEntryCluster* const rootCluster = &m_fileEntryDict[m_superBlock.data.rootdir_cluster];
|
||||||
|
memset( &rootCluster->entries[0].entry.raw[0], 0x00, 0x200 );
|
||||||
|
rootCluster->entries[0].entry.data.mode = 0x8427;
|
||||||
|
rootCluster->entries[0].entry.data.length = 2;
|
||||||
|
rootCluster->entries[0].entry.data.name[0] = '.';
|
||||||
|
|
||||||
|
memset( &rootCluster->entries[1].entry.raw[0], 0x00, 0x200 );
|
||||||
|
rootCluster->entries[1].entry.data.mode = 0xA426;
|
||||||
|
rootCluster->entries[1].entry.data.name[0] = '.';
|
||||||
|
rootCluster->entries[1].entry.data.name[1] = '.';
|
||||||
|
|
||||||
|
// mark root dir cluster as used
|
||||||
|
m_fat.data[0][0][m_superBlock.data.rootdir_cluster] = 0xFFFFFFFFu;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 FolderMemoryCard::GetFreeSystemCluster() {
|
||||||
|
// first block is reserved for superblock
|
||||||
|
u32 highestUsedCluster = ( m_superBlock.data.pages_per_block / m_superBlock.data.pages_per_cluster ) - 1;
|
||||||
|
|
||||||
|
// can't use any of the indirect fat clusters
|
||||||
|
for ( int i = 0; i < IndirectFatClusterCount; ++i ) {
|
||||||
|
highestUsedCluster = std::max( highestUsedCluster, m_superBlock.data.ifc_list[i] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// or fat clusters
|
||||||
|
for ( int i = 0; i < IndirectFatClusterCount; ++i ) {
|
||||||
|
for ( int j = 0; j < ClusterSize / 4; ++j ) {
|
||||||
|
if ( m_indirectFat.data[i][j] != 0xFFFFFFFFu ) {
|
||||||
|
highestUsedCluster = std::max( highestUsedCluster, m_indirectFat.data[i][j] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return highestUsedCluster + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 FolderMemoryCard::GetFreeDataCluster() {
|
||||||
|
// BIOS reports different cluster values than what the memory card actually has, match that when adding files
|
||||||
|
// 8mb card -> BIOS: 7999 clusters / Superblock: 8135 clusters
|
||||||
|
// 16mb card -> BIOS: 15999 clusters / Superblock: 16295 clusters
|
||||||
|
// 32mb card -> BIOS: 31999 clusters / Superblock: 32615 clusters
|
||||||
|
// 64mb card -> BIOS: 64999 clusters / Superblock: 65255 clusters
|
||||||
|
const u32 countDataClusters = ( m_superBlock.data.alloc_end / 1000 ) * 1000 - 1;
|
||||||
|
|
||||||
|
for ( unsigned int i = 0; i < countDataClusters; ++i ) {
|
||||||
|
const u32 cluster = m_fat.data[0][0][i];
|
||||||
|
|
||||||
|
if ( ( cluster & 0x80000000 ) == 0 ) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 FolderMemoryCard::GetAmountFreeDataClusters() {
|
||||||
|
const u32 countDataClusters = ( m_superBlock.data.alloc_end / 1000 ) * 1000 - 1;
|
||||||
|
u32 countFreeDataClusters = 0;
|
||||||
|
|
||||||
|
for ( unsigned int i = 0; i < countDataClusters; ++i ) {
|
||||||
|
const u32 cluster = m_fat.data[0][0][i];
|
||||||
|
|
||||||
|
if ( ( cluster & 0x80000000 ) == 0 ) {
|
||||||
|
++countFreeDataClusters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return countFreeDataClusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 FolderMemoryCard::GetLastClusterOfData( const u32 cluster ) {
|
||||||
|
u32 entryCluster;
|
||||||
|
u32 nextCluster = cluster;
|
||||||
|
do {
|
||||||
|
entryCluster = nextCluster;
|
||||||
|
nextCluster = m_fat.data[0][0][entryCluster] & 0x7FFFFFFF;
|
||||||
|
} while ( nextCluster != 0x7FFFFFFF );
|
||||||
|
return entryCluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 FolderMemoryCard::ConvertToMemoryCardTimestamp( const wxDateTime& time ) {
|
||||||
|
if ( !time.IsValid() ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
union {
|
||||||
|
MemoryCardFileEntryDateTime data;
|
||||||
|
u64 value;
|
||||||
|
} t;
|
||||||
|
|
||||||
|
wxDateTime::Tm tm = time.GetTm( wxDateTime::GMT9 );
|
||||||
|
|
||||||
|
t.data.unused = 0;
|
||||||
|
t.data.second = tm.sec;
|
||||||
|
t.data.minute = tm.min;
|
||||||
|
t.data.hour = tm.hour;
|
||||||
|
t.data.day = tm.mday;
|
||||||
|
t.data.month = tm.mon + 1;
|
||||||
|
t.data.year = tm.year;
|
||||||
|
|
||||||
|
return t.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryCardFileEntry* FolderMemoryCard::AppendFileEntryToDir( MemoryCardFileEntry* const dirEntry ) {
|
||||||
|
u32 entryCluster = GetLastClusterOfData( dirEntry->entry.data.cluster );
|
||||||
|
|
||||||
|
MemoryCardFileEntry* newFileEntry;
|
||||||
|
if ( dirEntry->entry.data.length % 2 == 0 ) {
|
||||||
|
// need new cluster
|
||||||
|
u32 newCluster = GetFreeDataCluster();
|
||||||
|
if ( newCluster == 0xFFFFFFFFu ) { return nullptr; }
|
||||||
|
m_fat.data[0][0][entryCluster] = newCluster | 0x80000000;
|
||||||
|
m_fat.data[0][0][newCluster] = 0xFFFFFFFF;
|
||||||
|
newFileEntry = &m_fileEntryDict[newCluster].entries[0];
|
||||||
|
} else {
|
||||||
|
// can use last page of existing clusters
|
||||||
|
newFileEntry = &m_fileEntryDict[entryCluster].entries[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return newFileEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath ) {
|
||||||
|
wxDir dir( dirPath );
|
||||||
|
if ( dir.IsOpened() ) {
|
||||||
|
Console.WriteLn( L"(FolderMcd) Adding folder: %s", WX_STR( dirPath ) );
|
||||||
|
|
||||||
|
const u32 dirStartCluster = dirEntry->entry.data.cluster;
|
||||||
|
|
||||||
|
wxString fileName;
|
||||||
|
bool hasNext;
|
||||||
|
|
||||||
|
int entryNumber = 2; // include . and ..
|
||||||
|
hasNext = dir.GetFirst( &fileName );
|
||||||
|
while ( hasNext ) {
|
||||||
|
wxFileName fileInfo( dirPath, fileName );
|
||||||
|
bool isFile = wxFile::Exists( fileInfo.GetFullPath() );
|
||||||
|
|
||||||
|
if ( isFile ) {
|
||||||
|
if ( !fileName.StartsWith( L"_pcsx2_" ) ) {
|
||||||
|
if ( AddFile( dirEntry, dirPath, fileName ) ) {
|
||||||
|
++entryNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// make sure we have enough space on the memcard for the directory
|
||||||
|
const u32 newNeededClusters = ( dirEntry->entry.data.length % 2 ) == 0 ? 2 : 1;
|
||||||
|
if ( newNeededClusters > GetAmountFreeDataClusters() ) {
|
||||||
|
Console.Warning( GetCardFullMessage( fileName ) );
|
||||||
|
hasNext = dir.GetNext( &fileName );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is a subdirectory
|
||||||
|
wxDateTime creationTime, modificationTime;
|
||||||
|
fileInfo.AppendDir( fileInfo.GetFullName() );
|
||||||
|
fileInfo.SetName( L"" );
|
||||||
|
fileInfo.ClearExt();
|
||||||
|
fileInfo.GetTimes( NULL, &modificationTime, &creationTime );
|
||||||
|
|
||||||
|
// add entry for subdir in parent dir
|
||||||
|
MemoryCardFileEntry* newDirEntry = AppendFileEntryToDir( dirEntry );
|
||||||
|
dirEntry->entry.data.length++;
|
||||||
|
newDirEntry->entry.data.mode = 0x8427;
|
||||||
|
newDirEntry->entry.data.length = 2;
|
||||||
|
newDirEntry->entry.data.timeCreated.value = ConvertToMemoryCardTimestamp( creationTime );
|
||||||
|
newDirEntry->entry.data.timeModified.value = ConvertToMemoryCardTimestamp( modificationTime );
|
||||||
|
strcpy( (char*)&newDirEntry->entry.data.name[0], fileName.mbc_str() );
|
||||||
|
|
||||||
|
// create new cluster for . and .. entries
|
||||||
|
u32 newCluster = GetFreeDataCluster();
|
||||||
|
m_fat.data[0][0][newCluster] = 0xFFFFFFFF;
|
||||||
|
newDirEntry->entry.data.cluster = newCluster;
|
||||||
|
|
||||||
|
MemoryCardFileEntryCluster* const subDirCluster = &m_fileEntryDict[newCluster];
|
||||||
|
memset( &subDirCluster->entries[0].entry.raw[0], 0x00, 0x200 );
|
||||||
|
subDirCluster->entries[0].entry.data.mode = 0x8427;
|
||||||
|
subDirCluster->entries[0].entry.data.dirEntry = entryNumber;
|
||||||
|
subDirCluster->entries[0].entry.data.name[0] = '.';
|
||||||
|
|
||||||
|
memset( &subDirCluster->entries[1].entry.raw[0], 0x00, 0x200 );
|
||||||
|
subDirCluster->entries[1].entry.data.mode = 0x8427;
|
||||||
|
subDirCluster->entries[1].entry.data.name[0] = '.';
|
||||||
|
subDirCluster->entries[1].entry.data.name[1] = '.';
|
||||||
|
|
||||||
|
++entryNumber;
|
||||||
|
|
||||||
|
// and add all files in subdir
|
||||||
|
AddFolder( newDirEntry, fileInfo.GetFullPath() );
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNext = dir.GetNext( &fileName );
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName ) {
|
||||||
|
wxFileName relativeFilePath( dirPath, fileName );
|
||||||
|
relativeFilePath.MakeRelativeTo( folderName.GetPath() );
|
||||||
|
Console.WriteLn( L"(FolderMcd) Adding file: %s", WX_STR( relativeFilePath.GetFullPath() ) );
|
||||||
|
|
||||||
|
wxFileName fileInfo( dirPath, fileName );
|
||||||
|
wxFFile file( fileInfo.GetFullPath(), L"rb" );
|
||||||
|
if ( file.IsOpened() ) {
|
||||||
|
// make sure we have enough space on the memcard to hold the data
|
||||||
|
const u32 clusterSize = m_superBlock.data.pages_per_cluster * m_superBlock.data.page_len;
|
||||||
|
const u32 filesize = file.Length();
|
||||||
|
const u32 countClusters = ( filesize % clusterSize ) != 0 ? ( filesize / clusterSize + 1 ) : ( filesize / clusterSize );
|
||||||
|
const u32 newNeededClusters = ( dirEntry->entry.data.length % 2 ) == 0 ? countClusters + 1 : countClusters;
|
||||||
|
if ( newNeededClusters > GetAmountFreeDataClusters() ) {
|
||||||
|
Console.Warning( GetCardFullMessage( relativeFilePath.GetFullPath() ) );
|
||||||
|
file.Close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryCardFileEntry* newFileEntry = AppendFileEntryToDir( dirEntry );
|
||||||
|
wxDateTime creationTime, modificationTime;
|
||||||
|
fileInfo.GetTimes( NULL, &modificationTime, &creationTime );
|
||||||
|
|
||||||
|
// set file entry data
|
||||||
|
memset( &newFileEntry->entry.raw[0], 0x00, 0x200 );
|
||||||
|
newFileEntry->entry.data.mode = 0x8497;
|
||||||
|
newFileEntry->entry.data.length = filesize;
|
||||||
|
newFileEntry->entry.data.timeCreated.value = ConvertToMemoryCardTimestamp( creationTime );
|
||||||
|
newFileEntry->entry.data.timeModified.value = ConvertToMemoryCardTimestamp( modificationTime );
|
||||||
|
u32 fileDataStartingCluster = GetFreeDataCluster();
|
||||||
|
newFileEntry->entry.data.cluster = fileDataStartingCluster;
|
||||||
|
strcpy( (char*)&newFileEntry->entry.data.name[0], fileName.mbc_str() );
|
||||||
|
|
||||||
|
// mark the appropriate amount of clusters as used
|
||||||
|
u32 dataCluster = fileDataStartingCluster;
|
||||||
|
m_fat.data[0][0][dataCluster] = 0xFFFFFFFF;
|
||||||
|
for ( unsigned int i = 0; i < countClusters - 1; ++i ) {
|
||||||
|
u32 newCluster = GetFreeDataCluster();
|
||||||
|
m_fat.data[0][0][dataCluster] = newCluster | 0x80000000;
|
||||||
|
m_fat.data[0][0][newCluster] = 0xFFFFFFFF;
|
||||||
|
dataCluster = newCluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Close();
|
||||||
|
} else {
|
||||||
|
Console.WriteLn( L"(FolderMcd) Could not open file: %s", WX_STR( relativeFilePath.GetFullPath() ) );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// and finally, increase file count in the directory entry
|
||||||
|
dirEntry->entry.data.length++;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 FolderMemoryCard::IsPresent() {
|
||||||
|
return m_isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::GetSizeInfo( PS2E_McdSizeInfo& outways ) {
|
||||||
|
outways.SectorSize = PageSize;
|
||||||
|
outways.EraseBlockSizeInSectors = BlockSize / PageSize;
|
||||||
|
outways.McdSizeInSectors = TotalPages;
|
||||||
|
|
||||||
|
u8 *pdata = (u8*)&outways.McdSizeInSectors;
|
||||||
|
outways.Xor = 18;
|
||||||
|
outways.Xor ^= pdata[0] ^ pdata[1] ^ pdata[2] ^ pdata[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FolderMemoryCard::IsPSX() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8* FolderMemoryCard::GetSystemBlockPointer( const u32 adr ) {
|
||||||
|
const u32 block = adr / BlockSizeRaw;
|
||||||
|
const u32 page = adr / PageSizeRaw;
|
||||||
|
const u32 offset = adr % PageSizeRaw;
|
||||||
|
const u32 cluster = adr / ClusterSizeRaw;
|
||||||
|
|
||||||
|
const u32 startDataCluster = m_superBlock.data.alloc_offset;
|
||||||
|
const u32 endDataCluster = startDataCluster + m_superBlock.data.alloc_end;
|
||||||
|
if ( cluster >= startDataCluster && cluster < endDataCluster ) {
|
||||||
|
// trying to access a file entry?
|
||||||
|
const u32 fatCluster = cluster - m_superBlock.data.alloc_offset;
|
||||||
|
return GetFileEntryPointer( m_superBlock.data.rootdir_cluster, fatCluster, page % 2, offset );
|
||||||
|
}
|
||||||
|
|
||||||
|
u8* src = nullptr;
|
||||||
|
if ( block == 0 ) {
|
||||||
|
src = &m_superBlock.raw[page * PageSize + offset];
|
||||||
|
} else if ( block == m_superBlock.data.backup_block1 ) {
|
||||||
|
src = &m_backupBlock1[( page % 16 ) * PageSize + offset];
|
||||||
|
} else if ( block == m_superBlock.data.backup_block2 ) {
|
||||||
|
src = &m_backupBlock2[( page % 16 ) * PageSize + offset];
|
||||||
|
} else {
|
||||||
|
// trying to access indirect FAT?
|
||||||
|
for ( int i = 0; i < IndirectFatClusterCount; ++i ) {
|
||||||
|
if ( cluster == m_superBlock.data.ifc_list[i] ) {
|
||||||
|
src = &m_indirectFat.raw[i][( page % 2 ) * PageSize + offset];
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// trying to access FAT?
|
||||||
|
for ( int i = 0; i < IndirectFatClusterCount; ++i ) {
|
||||||
|
for ( int j = 0; j < ClusterSize / 4; ++j ) {
|
||||||
|
const u32 fatCluster = m_indirectFat.data[i][j];
|
||||||
|
if ( fatCluster != 0xFFFFFFFFu && fatCluster == cluster ) {
|
||||||
|
src = &m_fat.raw[i][j][( page % 2 ) * PageSize + offset];
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8* FolderMemoryCard::GetFileEntryPointer( const u32 currentCluster, const u32 searchCluster, const u32 entryNumber, const u32 offset ) {
|
||||||
|
// we found the correct cluster, return pointer to it
|
||||||
|
if ( currentCluster == searchCluster ) {
|
||||||
|
return &m_fileEntryDict[currentCluster].entries[entryNumber].entry.raw[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
// check other clusters of this directory
|
||||||
|
const u32 nextCluster = m_fat.data[0][0][currentCluster] & 0x7FFFFFFF;
|
||||||
|
if ( nextCluster != 0x7FFFFFFF ) {
|
||||||
|
u8* ptr = GetFileEntryPointer( nextCluster, searchCluster, entryNumber, offset );
|
||||||
|
if ( ptr != nullptr ) { return ptr; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// check subdirectories
|
||||||
|
for ( int i = 0; i < 2; ++i ) {
|
||||||
|
MemoryCardFileEntry* const entry = &m_fileEntryDict[currentCluster].entries[i];
|
||||||
|
if ( entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) {
|
||||||
|
u8* ptr = GetFileEntryPointer( entry->entry.data.cluster, searchCluster, entryNumber, offset );
|
||||||
|
if ( ptr != nullptr ) { return ptr; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryCardFileEntry* FolderMemoryCard::GetFileEntryFromFileDataCluster( const u32 currentCluster, const u32 searchCluster, wxFileName* fileName, const size_t originalDirCount, u32* outClusterNumber ) {
|
||||||
|
// check both entries of the current cluster if they're the file we're searching for, and if yes return it
|
||||||
|
for ( int i = 0; i < 2; ++i ) {
|
||||||
|
MemoryCardFileEntry* const entry = &m_fileEntryDict[currentCluster].entries[i];
|
||||||
|
if ( entry->IsUsed() && entry->IsFile() ) {
|
||||||
|
u32 fileCluster = entry->entry.data.cluster;
|
||||||
|
u32 clusterNumber = 0;
|
||||||
|
do {
|
||||||
|
if ( fileCluster == searchCluster ) {
|
||||||
|
fileName->SetName( wxString::FromAscii( (const char*)entry->entry.data.name ) );
|
||||||
|
*outClusterNumber = clusterNumber;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
++clusterNumber;
|
||||||
|
} while ( ( fileCluster = m_fat.data[0][0][fileCluster] & 0x7FFFFFFF ) != 0x7FFFFFFF );
|
||||||
|
// There's a lot of optimization work that can be done here, looping through all clusters of every single file
|
||||||
|
// is not very efficient, especially since files are going to be accessed from the start and in-order the vast
|
||||||
|
// majority of the time. You can probably cut a lot of the work by remembering the state of the last access
|
||||||
|
// and only checking if the current access is either the same or the next cluster according to the FAT.
|
||||||
|
//} while ( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check other clusters of this directory
|
||||||
|
// this can probably be solved more efficiently by looping through nextClusters instead of recursively calling
|
||||||
|
const u32 nextCluster = m_fat.data[0][0][currentCluster] & 0x7FFFFFFF;
|
||||||
|
if ( nextCluster != 0x7FFFFFFF ) {
|
||||||
|
MemoryCardFileEntry* ptr = GetFileEntryFromFileDataCluster( nextCluster, searchCluster, fileName, originalDirCount, outClusterNumber );
|
||||||
|
if ( ptr != nullptr ) { return ptr; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// check subdirectories
|
||||||
|
for ( int i = 0; i < 2; ++i ) {
|
||||||
|
MemoryCardFileEntry* const entry = &m_fileEntryDict[currentCluster].entries[i];
|
||||||
|
if ( entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) {
|
||||||
|
MemoryCardFileEntry* ptr = GetFileEntryFromFileDataCluster( entry->entry.data.cluster, searchCluster, fileName, originalDirCount, outClusterNumber );
|
||||||
|
if ( ptr != nullptr ) {
|
||||||
|
fileName->InsertDir( originalDirCount, wxString::FromAscii( (const char*)entry->entry.data.name ) );
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FolderMemoryCard::ReadFromFile( u8 *dest, u32 adr, u32 dataLength ) {
|
||||||
|
const u32 page = adr / PageSizeRaw;
|
||||||
|
const u32 offset = adr % PageSizeRaw;
|
||||||
|
const u32 cluster = adr / ClusterSizeRaw;
|
||||||
|
const u32 fatCluster = cluster - m_superBlock.data.alloc_offset;
|
||||||
|
|
||||||
|
// figure out which file to read from
|
||||||
|
wxFileName fileName( folderName );
|
||||||
|
u32 clusterNumber;
|
||||||
|
const MemoryCardFileEntry* const entry = GetFileEntryFromFileDataCluster( m_superBlock.data.rootdir_cluster, fatCluster, &fileName, fileName.GetDirCount(), &clusterNumber );
|
||||||
|
if ( entry != nullptr ) {
|
||||||
|
if ( !fileName.DirExists() ) {
|
||||||
|
fileName.Mkdir();
|
||||||
|
}
|
||||||
|
wxFFile file( fileName.GetFullPath(), L"rb" );
|
||||||
|
if ( file.IsOpened() ) {
|
||||||
|
const u32 clusterOffset = ( page % 2 ) * PageSize + offset;
|
||||||
|
const u32 fileOffset = clusterNumber * ClusterSize + clusterOffset;
|
||||||
|
|
||||||
|
file.Seek( fileOffset );
|
||||||
|
size_t bytesRead = file.Read( dest, dataLength );
|
||||||
|
|
||||||
|
// if more bytes were requested than actually exist, fill the rest with 0xFF
|
||||||
|
if ( bytesRead < dataLength ) {
|
||||||
|
memset( &dest[bytesRead], 0xFF, dataLength - bytesRead );
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Close();
|
||||||
|
|
||||||
|
return bytesRead > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 FolderMemoryCard::Read( u8 *dest, u32 adr, int size ) {
|
||||||
|
const u32 block = adr / BlockSizeRaw;
|
||||||
|
const u32 page = adr / PageSizeRaw;
|
||||||
|
const u32 offset = adr % PageSizeRaw;
|
||||||
|
const u32 cluster = adr / ClusterSizeRaw;
|
||||||
|
const u32 end = offset + size;
|
||||||
|
|
||||||
|
if ( end > PageSizeRaw ) {
|
||||||
|
// is trying to read more than one page at a time
|
||||||
|
// do this recursively so that each function call only has to care about one page
|
||||||
|
const u32 toNextPage = PageSizeRaw - offset;
|
||||||
|
Read( dest + toNextPage, adr + toNextPage, size - toNextPage );
|
||||||
|
size = toNextPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( offset < PageSize ) {
|
||||||
|
// is trying to read (part of) an actual data block
|
||||||
|
const u32 dataOffset = 0;
|
||||||
|
const u32 dataLength = std::min( (u32)size, (u32)( PageSize - offset ) );
|
||||||
|
|
||||||
|
// if we have a cache for this page, just load from that
|
||||||
|
auto it = m_cache.find( page );
|
||||||
|
if ( it != m_cache.end() ) {
|
||||||
|
memcpy( dest, &it->second.raw[offset], dataLength );
|
||||||
|
} else {
|
||||||
|
u8* src = GetSystemBlockPointer( adr );
|
||||||
|
if ( src != nullptr ) {
|
||||||
|
memcpy( dest, src, dataLength );
|
||||||
|
} else {
|
||||||
|
if ( !ReadFromFile( dest, adr, dataLength ) ) {
|
||||||
|
memset( dest, 0xFF, dataLength );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( end > PageSize ) {
|
||||||
|
// is trying to (partially) read the ECC
|
||||||
|
const u32 eccOffset = PageSize - offset;
|
||||||
|
const u32 eccLength = std::min( (u32)( size - offset ), (u32)EccSize );
|
||||||
|
const u32 adrStart = page * 0x210u;
|
||||||
|
|
||||||
|
u8 data[PageSize];
|
||||||
|
Read( data, adrStart, PageSize );
|
||||||
|
|
||||||
|
u8 ecc[EccSize];
|
||||||
|
memset( ecc, 0xFF, EccSize );
|
||||||
|
|
||||||
|
for ( int i = 0; i < PageSize / 0x80; ++i ) {
|
||||||
|
FolderMemoryCard::CalculateECC( ecc + ( i * 3 ), &data[i * 0x80] );
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy( dest + eccOffset, ecc, eccLength );
|
||||||
|
}
|
||||||
|
|
||||||
|
// return 0 on fail, 1 on success?
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 FolderMemoryCard::Save( const u8 *src, u32 adr, int size ) {
|
||||||
|
const u32 block = adr / BlockSizeRaw;
|
||||||
|
const u32 cluster = adr / ClusterSizeRaw;
|
||||||
|
const u32 page = adr / PageSizeRaw;
|
||||||
|
const u32 offset = adr % PageSizeRaw;
|
||||||
|
const u32 end = offset + size;
|
||||||
|
|
||||||
|
if ( end > PageSizeRaw ) {
|
||||||
|
// is trying to store more than one page at a time
|
||||||
|
// do this recursively so that each function call only has to care about one page
|
||||||
|
const u32 toNextPage = PageSizeRaw - offset;
|
||||||
|
Save( src + toNextPage, adr + toNextPage, size - toNextPage );
|
||||||
|
size = toNextPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( offset < PageSize ) {
|
||||||
|
// is trying to store (part of) an actual data block
|
||||||
|
const u32 dataLength = std::min( (u32)size, PageSize - offset );
|
||||||
|
|
||||||
|
// if cache page has not yet been touched, fill it with the data from our memory card
|
||||||
|
auto it = m_cache.find( page );
|
||||||
|
MemoryCardPage* cachePage;
|
||||||
|
if ( it == m_cache.end() ) {
|
||||||
|
cachePage = &m_cache[page];
|
||||||
|
const u32 adrLoad = page * PageSizeRaw;
|
||||||
|
Read( &cachePage->raw[0], adrLoad, PageSize );
|
||||||
|
} else {
|
||||||
|
cachePage = &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// then just write to the cache
|
||||||
|
memcpy( &cachePage->raw[offset], src, dataLength );
|
||||||
|
|
||||||
|
SetTimeLastWrittenToNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::NextFrame() {
|
||||||
|
if ( m_framesUntilFlush > 0 && --m_framesUntilFlush == 0 ) {
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::Flush() {
|
||||||
|
Console.WriteLn( L"(FolderMcd) Writing data for slot %u to file system.", m_slot );
|
||||||
|
|
||||||
|
// first write the superblock if necessary
|
||||||
|
Flush( 0 );
|
||||||
|
|
||||||
|
if ( !IsFormatted() ) { return; }
|
||||||
|
|
||||||
|
// then write the indirect FAT
|
||||||
|
for ( int i = 0; i < IndirectFatClusterCount; ++i ) {
|
||||||
|
const u32 cluster = m_superBlock.data.ifc_list[i];
|
||||||
|
if ( cluster > 0 && cluster < TotalClusters ) {
|
||||||
|
const u32 page = cluster * 2;
|
||||||
|
Flush( page );
|
||||||
|
Flush( page + 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// and the FAT
|
||||||
|
for ( int i = 0; i < IndirectFatClusterCount; ++i ) {
|
||||||
|
for ( int j = 0; j < ClusterSize / 4; ++j ) {
|
||||||
|
const u32 cluster = m_indirectFat.data[i][j];
|
||||||
|
if ( cluster > 0 && cluster < TotalClusters ) {
|
||||||
|
const u32 page = cluster * 2;
|
||||||
|
Flush( page );
|
||||||
|
Flush( page + 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// then all directory and file entries
|
||||||
|
const u32 rootDirCluster = m_superBlock.data.rootdir_cluster;
|
||||||
|
const u32 rootDirPage = ( rootDirCluster + m_superBlock.data.alloc_offset ) * 2;
|
||||||
|
Flush( rootDirPage );
|
||||||
|
MemoryCardFileEntryCluster* rootEntries = &m_fileEntryDict[rootDirCluster];
|
||||||
|
if ( rootEntries->entries[0].IsValid() && rootEntries->entries[0].IsUsed() ) {
|
||||||
|
FlushFileEntries( rootDirCluster, rootEntries->entries[0].entry.data.length );
|
||||||
|
}
|
||||||
|
|
||||||
|
// and finally, flush everything that hasn't been flushed yet
|
||||||
|
for ( int i = 0; i < TotalPages; ++i ) {
|
||||||
|
Flush( i );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::Flush( const u32 page ) {
|
||||||
|
if ( page >= TotalPages ) { return; }
|
||||||
|
auto it = m_cache.find( page );
|
||||||
|
if ( it != m_cache.end() ) {
|
||||||
|
WriteWithoutCache( &it->second.raw[0], page * PageSizeRaw, PageSize );
|
||||||
|
m_cache.erase( it );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remainingFiles ) {
|
||||||
|
// flush the current cluster
|
||||||
|
const u32 page = ( dirCluster + m_superBlock.data.alloc_offset ) * 2;
|
||||||
|
Flush( page );
|
||||||
|
Flush( page + 1 );
|
||||||
|
|
||||||
|
// if either of the current entries is a subdir, flush that too
|
||||||
|
MemoryCardFileEntryCluster* entries = &m_fileEntryDict[dirCluster];
|
||||||
|
const u32 filesInThisCluster = std::min( remainingFiles, 2u );
|
||||||
|
for ( unsigned int i = 0; i < filesInThisCluster; ++i ) {
|
||||||
|
MemoryCardFileEntry* entry = &entries->entries[i];
|
||||||
|
if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() ) {
|
||||||
|
const u32 cluster = entry->entry.data.cluster;
|
||||||
|
if ( cluster > 0 ) {
|
||||||
|
FlushFileEntries( cluster, entry->entry.data.length );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue to the next cluster of this directory
|
||||||
|
const u32 nextCluster = m_fat.data[0][0][dirCluster];
|
||||||
|
if ( nextCluster != 0xFFFFFFFF ) {
|
||||||
|
FlushFileEntries( nextCluster & 0x7FFFFFFF, remainingFiles - 2 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 FolderMemoryCard::WriteWithoutCache( const u8 *src, u32 adr, int size ) {
|
||||||
|
const u32 block = adr / BlockSizeRaw;
|
||||||
|
const u32 cluster = adr / ClusterSizeRaw;
|
||||||
|
const u32 page = adr / PageSizeRaw;
|
||||||
|
const u32 offset = adr % PageSizeRaw;
|
||||||
|
const u32 end = offset + size;
|
||||||
|
|
||||||
|
if ( end > PageSizeRaw ) {
|
||||||
|
// is trying to store more than one page at a time
|
||||||
|
// do this recursively so that each function call only has to care about one page
|
||||||
|
const u32 toNextPage = PageSizeRaw - offset;
|
||||||
|
Save( src + toNextPage, adr + toNextPage, size - toNextPage );
|
||||||
|
size = toNextPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( offset < PageSize ) {
|
||||||
|
// is trying to store (part of) an actual data block
|
||||||
|
const u32 dataLength = std::min( (u32)size, PageSize - offset );
|
||||||
|
|
||||||
|
u8* dest = GetSystemBlockPointer( adr );
|
||||||
|
if ( dest != nullptr ) {
|
||||||
|
memcpy( dest, src, dataLength );
|
||||||
|
} else {
|
||||||
|
WriteToFile( src, adr, dataLength );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( end > PageSize ) {
|
||||||
|
// is trying to store ECC
|
||||||
|
// simply ignore this, is automatically generated when reading
|
||||||
|
}
|
||||||
|
|
||||||
|
// return 0 on fail, 1 on success?
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) {
|
||||||
|
const u32 cluster = adr / ClusterSizeRaw;
|
||||||
|
const u32 page = adr / PageSizeRaw;
|
||||||
|
const u32 offset = adr % PageSizeRaw;
|
||||||
|
const u32 fatCluster = cluster - m_superBlock.data.alloc_offset;
|
||||||
|
|
||||||
|
// figure out which file to write to
|
||||||
|
wxFileName fileName( folderName );
|
||||||
|
u32 clusterNumber;
|
||||||
|
const MemoryCardFileEntry* const entry = GetFileEntryFromFileDataCluster( m_superBlock.data.rootdir_cluster, fatCluster, &fileName, fileName.GetDirCount(), &clusterNumber );
|
||||||
|
if ( entry != nullptr ) {
|
||||||
|
if ( !fileName.DirExists() ) {
|
||||||
|
fileName.Mkdir();
|
||||||
|
}
|
||||||
|
if ( !fileName.FileExists() ) {
|
||||||
|
wxFFile createEmptyFile( fileName.GetFullPath(), L"wb" );
|
||||||
|
createEmptyFile.Close();
|
||||||
|
}
|
||||||
|
wxFFile file( fileName.GetFullPath(), L"r+b" );
|
||||||
|
if ( file.IsOpened() ) {
|
||||||
|
const u32 clusterOffset = ( page % 2 ) * PageSize + offset;
|
||||||
|
const u32 fileSize = entry->entry.data.length;
|
||||||
|
const u32 fileOffsetStart = std::min( clusterNumber * ClusterSize + clusterOffset, fileSize );;
|
||||||
|
const u32 fileOffsetEnd = std::min( fileOffsetStart + dataLength, fileSize );
|
||||||
|
const u32 bytesToWrite = fileOffsetEnd - fileOffsetStart;
|
||||||
|
|
||||||
|
wxFileOffset actualFileSize = file.Length();
|
||||||
|
if ( actualFileSize < fileOffsetStart ) {
|
||||||
|
const u32 diff = fileOffsetStart - actualFileSize;
|
||||||
|
u8 temp = 0xFF;
|
||||||
|
for ( u32 i = 0; i < diff; ++i ) {
|
||||||
|
file.Write( &temp, 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Seek( fileOffsetStart );
|
||||||
|
if ( bytesToWrite > 0 ) {
|
||||||
|
file.Write( src, bytesToWrite );
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 FolderMemoryCard::EraseBlock( u32 adr ) {
|
||||||
|
const u32 block = adr / BlockSizeRaw;
|
||||||
|
|
||||||
|
u8 eraseData[PageSize];
|
||||||
|
memset( eraseData, 0xFF, PageSize );
|
||||||
|
for ( int page = 0; page < 16; ++page ) {
|
||||||
|
const u32 adr = block * BlockSizeRaw + page * PageSizeRaw;
|
||||||
|
Save( eraseData, adr, PageSize );
|
||||||
|
}
|
||||||
|
|
||||||
|
// return 0 on fail, 1 on success?
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 FolderMemoryCard::GetCRC() {
|
||||||
|
// Since this is just used as integrity check for savestate loading,
|
||||||
|
// give a timestamp of the last time the memory card was written to
|
||||||
|
return m_timeLastWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::SetSlot( uint slot ) {
|
||||||
|
pxAssert( slot < 8 );
|
||||||
|
m_slot = slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCard::SetTimeLastWrittenToNow() {
|
||||||
|
m_timeLastWritten = wxGetLocalTimeMillis().GetValue();
|
||||||
|
m_framesUntilFlush = FramesAfterWriteUntilFlush;
|
||||||
|
}
|
||||||
|
|
||||||
|
// from http://www.oocities.org/siliconvalley/station/8269/sma02/sma02.html#ECC
|
||||||
|
void FolderMemoryCard::CalculateECC( u8* ecc, const u8* data ) {
|
||||||
|
static const u8 Table[] = {
|
||||||
|
0x00, 0x87, 0x96, 0x11, 0xa5, 0x22, 0x33, 0xb4, 0xb4, 0x33, 0x22, 0xa5, 0x11, 0x96, 0x87, 0x00,
|
||||||
|
0xc3, 0x44, 0x55, 0xd2, 0x66, 0xe1, 0xf0, 0x77, 0x77, 0xf0, 0xe1, 0x66, 0xd2, 0x55, 0x44, 0xc3,
|
||||||
|
0xd2, 0x55, 0x44, 0xc3, 0x77, 0xf0, 0xe1, 0x66, 0x66, 0xe1, 0xf0, 0x77, 0xc3, 0x44, 0x55, 0xd2,
|
||||||
|
0x11, 0x96, 0x87, 0x00, 0xb4, 0x33, 0x22, 0xa5, 0xa5, 0x22, 0x33, 0xb4, 0x00, 0x87, 0x96, 0x11,
|
||||||
|
0xe1, 0x66, 0x77, 0xf0, 0x44, 0xc3, 0xd2, 0x55, 0x55, 0xd2, 0xc3, 0x44, 0xf0, 0x77, 0x66, 0xe1,
|
||||||
|
0x22, 0xa5, 0xb4, 0x33, 0x87, 0x00, 0x11, 0x96, 0x96, 0x11, 0x00, 0x87, 0x33, 0xb4, 0xa5, 0x22,
|
||||||
|
0x33, 0xb4, 0xa5, 0x22, 0x96, 0x11, 0x00, 0x87, 0x87, 0x00, 0x11, 0x96, 0x22, 0xa5, 0xb4, 0x33,
|
||||||
|
0xf0, 0x77, 0x66, 0xe1, 0x55, 0xd2, 0xc3, 0x44, 0x44, 0xc3, 0xd2, 0x55, 0xe1, 0x66, 0x77, 0xf0,
|
||||||
|
0xf0, 0x77, 0x66, 0xe1, 0x55, 0xd2, 0xc3, 0x44, 0x44, 0xc3, 0xd2, 0x55, 0xe1, 0x66, 0x77, 0xf0,
|
||||||
|
0x33, 0xb4, 0xa5, 0x22, 0x96, 0x11, 0x00, 0x87, 0x87, 0x00, 0x11, 0x96, 0x22, 0xa5, 0xb4, 0x33,
|
||||||
|
0x22, 0xa5, 0xb4, 0x33, 0x87, 0x00, 0x11, 0x96, 0x96, 0x11, 0x00, 0x87, 0x33, 0xb4, 0xa5, 0x22,
|
||||||
|
0xe1, 0x66, 0x77, 0xf0, 0x44, 0xc3, 0xd2, 0x55, 0x55, 0xd2, 0xc3, 0x44, 0xf0, 0x77, 0x66, 0xe1,
|
||||||
|
0x11, 0x96, 0x87, 0x00, 0xb4, 0x33, 0x22, 0xa5, 0xa5, 0x22, 0x33, 0xb4, 0x00, 0x87, 0x96, 0x11,
|
||||||
|
0xd2, 0x55, 0x44, 0xc3, 0x77, 0xf0, 0xe1, 0x66, 0x66, 0xe1, 0xf0, 0x77, 0xc3, 0x44, 0x55, 0xd2,
|
||||||
|
0xc3, 0x44, 0x55, 0xd2, 0x66, 0xe1, 0xf0, 0x77, 0x77, 0xf0, 0xe1, 0x66, 0xd2, 0x55, 0x44, 0xc3,
|
||||||
|
0x00, 0x87, 0x96, 0x11, 0xa5, 0x22, 0x33, 0xb4, 0xb4, 0x33, 0x22, 0xa5, 0x11, 0x96, 0x87, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
int i, c;
|
||||||
|
|
||||||
|
ecc[0] = ecc[1] = ecc[2] = 0;
|
||||||
|
|
||||||
|
for ( i = 0; i < 0x80; i++ ) {
|
||||||
|
c = Table[data[i]];
|
||||||
|
|
||||||
|
ecc[0] ^= c;
|
||||||
|
if ( c & 0x80 ) {
|
||||||
|
ecc[1] ^= ~i;
|
||||||
|
ecc[2] ^= i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ecc[0] = ~ecc[0];
|
||||||
|
ecc[0] &= 0x77;
|
||||||
|
|
||||||
|
ecc[1] = ~ecc[1];
|
||||||
|
ecc[1] &= 0x7f;
|
||||||
|
|
||||||
|
ecc[2] = ~ecc[2];
|
||||||
|
ecc[2] &= 0x7f;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FolderMemoryCardAggregator::FolderMemoryCardAggregator() {
|
||||||
|
for ( uint i = 0; i < totalCardSlots; ++i ) {
|
||||||
|
m_cards[i].SetSlot( i );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCardAggregator::Open() {
|
||||||
|
for ( int i = 0; i < totalCardSlots; ++i ) {
|
||||||
|
m_cards[i].Open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCardAggregator::Close() {
|
||||||
|
for ( int i = 0; i < totalCardSlots; ++i ) {
|
||||||
|
m_cards[i].Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 FolderMemoryCardAggregator::IsPresent( uint slot ) {
|
||||||
|
return m_cards[slot].IsPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCardAggregator::GetSizeInfo( uint slot, PS2E_McdSizeInfo& outways ) {
|
||||||
|
m_cards[slot].GetSizeInfo( outways );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FolderMemoryCardAggregator::IsPSX( uint slot ) {
|
||||||
|
return m_cards[slot].IsPSX();
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 FolderMemoryCardAggregator::Read( uint slot, u8 *dest, u32 adr, int size ) {
|
||||||
|
return m_cards[slot].Read( dest, adr, size );
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 FolderMemoryCardAggregator::Save( uint slot, const u8 *src, u32 adr, int size ) {
|
||||||
|
return m_cards[slot].Save( src, adr, size );
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 FolderMemoryCardAggregator::EraseBlock( uint slot, u32 adr ) {
|
||||||
|
return m_cards[slot].EraseBlock( adr );
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 FolderMemoryCardAggregator::GetCRC( uint slot ) {
|
||||||
|
return m_cards[slot].GetCRC();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FolderMemoryCardAggregator::NextFrame( uint slot ) {
|
||||||
|
m_cards[slot].NextFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,313 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <wx/file.h>
|
||||||
|
#include <wx/dir.h>
|
||||||
|
#include <wx/stopwatch.h>
|
||||||
|
#include <wx/ffile.h>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "PluginCallbacks.h"
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
// Superblock Header Struct
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct superblock {
|
||||||
|
char magic[28]; // 0x00
|
||||||
|
char version[12]; // 0x1c
|
||||||
|
u16 page_len; // 0x28
|
||||||
|
u16 pages_per_cluster; // 0x2a
|
||||||
|
u16 pages_per_block; // 0x2c
|
||||||
|
u16 unused; // 0x2e
|
||||||
|
u32 clusters_per_card; // 0x30
|
||||||
|
u32 alloc_offset; // 0x34
|
||||||
|
u32 alloc_end; // 0x38
|
||||||
|
u32 rootdir_cluster; // 0x3c
|
||||||
|
u32 backup_block1; // 0x40
|
||||||
|
u32 backup_block2; // 0x44
|
||||||
|
u64 padding0x48; // 0x48
|
||||||
|
u32 ifc_list[32]; // 0x50
|
||||||
|
u32 bad_block_list[32]; // 0xd0
|
||||||
|
u8 card_type; // 0x150
|
||||||
|
u8 card_flags; // 0x151
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct MemoryCardFileEntryDateTime {
|
||||||
|
u8 unused;
|
||||||
|
u8 second;
|
||||||
|
u8 minute;
|
||||||
|
u8 hour;
|
||||||
|
u8 day;
|
||||||
|
u8 month;
|
||||||
|
u16 year;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
// MemoryCardFileEntry
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
// Structure for directory and file relationships as stored on memory cards
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct MemoryCardFileEntry {
|
||||||
|
union {
|
||||||
|
struct MemoryCardFileEntryData {
|
||||||
|
u32 mode;
|
||||||
|
u32 length; // number of bytes for file, number of files for dir
|
||||||
|
union {
|
||||||
|
MemoryCardFileEntryDateTime data;
|
||||||
|
u64 value;
|
||||||
|
u8 raw[8];
|
||||||
|
} timeCreated;
|
||||||
|
u32 cluster; // cluster the start of referred file or folder can be found in
|
||||||
|
u32 dirEntry; // parent directory entry number, only used if "." entry of subdir
|
||||||
|
union {
|
||||||
|
MemoryCardFileEntryDateTime data;
|
||||||
|
u64 value;
|
||||||
|
u8 raw[8];
|
||||||
|
} timeModified;
|
||||||
|
u32 attr;
|
||||||
|
u8 padding[0x1C];
|
||||||
|
u8 name[0x20];
|
||||||
|
u8 unused[0x1A0];
|
||||||
|
} data;
|
||||||
|
u8 raw[0x200];
|
||||||
|
} entry;
|
||||||
|
|
||||||
|
bool IsFile() { return !!( entry.data.mode & 0x0010 ); }
|
||||||
|
bool IsDir() { return !!( entry.data.mode & 0x0020 ); }
|
||||||
|
bool IsUsed() { return !!( entry.data.mode & 0x8000 ); }
|
||||||
|
bool IsValid() { return entry.data.mode != 0xFFFFFFFF; }
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct MemoryCardFileEntryCluster {
|
||||||
|
MemoryCardFileEntry entries[2];
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct MemoryCardPage {
|
||||||
|
static const int PageSize = 0x200;
|
||||||
|
u8 raw[PageSize];
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
// FolderMemoryCard
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
// Fakes a memory card using a regular folder/file structure in the host file system
|
||||||
|
class FolderMemoryCard {
|
||||||
|
protected:
|
||||||
|
wxFileName folderName;
|
||||||
|
|
||||||
|
// a few constants so we could in theory change the memory card size without too much effort
|
||||||
|
static const int IndirectFatClusterCount = 1; // should be 32 but only 1 is ever used
|
||||||
|
static const int PageSize = MemoryCardPage::PageSize;
|
||||||
|
static const int ClusterSize = PageSize * 2;
|
||||||
|
static const int BlockSize = ClusterSize * 8;
|
||||||
|
static const int EccSize = 0x10;
|
||||||
|
static const int PageSizeRaw = PageSize + EccSize;
|
||||||
|
static const int ClusterSizeRaw = PageSizeRaw * 2;
|
||||||
|
static const int BlockSizeRaw = ClusterSizeRaw * 8;
|
||||||
|
static const int TotalPages = 0x4000;
|
||||||
|
static const int TotalClusters = TotalPages / 2;
|
||||||
|
static const int TotalBlocks = TotalClusters / 8;
|
||||||
|
|
||||||
|
static const int FramesAfterWriteUntilFlush = 60;
|
||||||
|
|
||||||
|
union superBlockUnion {
|
||||||
|
superblock data;
|
||||||
|
u8 raw[BlockSize];
|
||||||
|
} m_superBlock;
|
||||||
|
union indirectFatUnion {
|
||||||
|
u32 data[IndirectFatClusterCount][ClusterSize / 4];
|
||||||
|
u8 raw[IndirectFatClusterCount][ClusterSize];
|
||||||
|
} m_indirectFat;
|
||||||
|
union fatUnion {
|
||||||
|
u32 data[IndirectFatClusterCount][ClusterSize / 4][ClusterSize / 4];
|
||||||
|
u8 raw[IndirectFatClusterCount][ClusterSize / 4][ClusterSize];
|
||||||
|
} m_fat;
|
||||||
|
u8 m_backupBlock1[BlockSize];
|
||||||
|
u8 m_backupBlock2[BlockSize];
|
||||||
|
|
||||||
|
std::map<u32, MemoryCardFileEntryCluster> m_fileEntryDict;
|
||||||
|
|
||||||
|
// holds a copy of modified areas of the memory card, in page-sized chunks
|
||||||
|
std::map<u32, MemoryCardPage> m_cache;
|
||||||
|
|
||||||
|
uint m_slot;
|
||||||
|
bool m_isEnabled;
|
||||||
|
u64 m_timeLastWritten;
|
||||||
|
int m_framesUntilFlush;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FolderMemoryCard();
|
||||||
|
virtual ~FolderMemoryCard() throw() {}
|
||||||
|
|
||||||
|
void Lock();
|
||||||
|
void Unlock();
|
||||||
|
|
||||||
|
void Open();
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
s32 IsPresent();
|
||||||
|
void GetSizeInfo( PS2E_McdSizeInfo& outways );
|
||||||
|
bool IsPSX();
|
||||||
|
s32 Read( u8 *dest, u32 adr, int size );
|
||||||
|
s32 Save( const u8 *src, u32 adr, int size );
|
||||||
|
s32 EraseBlock( u32 adr );
|
||||||
|
u64 GetCRC();
|
||||||
|
|
||||||
|
void SetSlot( uint slot );
|
||||||
|
|
||||||
|
// called once per frame, used for flushing data after FramesAfterWriteUntilFlush frames of no writes
|
||||||
|
void NextFrame();
|
||||||
|
|
||||||
|
static void CalculateECC( u8* ecc, const u8* data );
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// initializes memory card data, as if it was fresh from the factory
|
||||||
|
void InitializeInternalData();
|
||||||
|
|
||||||
|
bool IsFormatted();
|
||||||
|
|
||||||
|
// returns the in-memory address of data the given memory card adr corresponds to
|
||||||
|
// returns nullptr if adr corresponds to a folder or file entry
|
||||||
|
u8* GetSystemBlockPointer( const u32 adr );
|
||||||
|
|
||||||
|
// returns in-memory address of file or directory metadata searchCluster corresponds to
|
||||||
|
// returns nullptr if searchCluster contains something else
|
||||||
|
// originally call by passing:
|
||||||
|
// - currentCluster: the root directory cluster as indicated in the superblock
|
||||||
|
// - searchCluster: the cluster that is being accessed, relative to alloc_offset in the superblock
|
||||||
|
// - entryNumber: page of cluster
|
||||||
|
// - offset: offset of page
|
||||||
|
u8* GetFileEntryPointer( const u32 currentCluster, const u32 searchCluster, const u32 entryNumber, const u32 offset );
|
||||||
|
|
||||||
|
// returns file entry of the file at the given searchCluster
|
||||||
|
// the passed fileName will be filled with a path to the file being accessed
|
||||||
|
// returns nullptr if searchCluster contains no file
|
||||||
|
// call by passing:
|
||||||
|
// - currentCluster: the root directory cluster as indicated in the superblock
|
||||||
|
// - searchCluster: the cluster that is being accessed, relative to alloc_offset in the superblock
|
||||||
|
// - fileName: wxFileName of the root directory of the memory card folder in the host file system (filename part doesn't matter)
|
||||||
|
// - originalDirCount: the point in fileName where to insert the found folder path, usually fileName->GetDirCount()
|
||||||
|
// - outClusterNumber: the cluster's sequential number of the file will be written to this pointer,
|
||||||
|
// which can be used to calculate the in-file offset of the address being accessed
|
||||||
|
MemoryCardFileEntry* GetFileEntryFromFileDataCluster( const u32 currentCluster, const u32 searchCluster, wxFileName* fileName, const size_t originalDirCount, u32* outClusterNumber );
|
||||||
|
|
||||||
|
|
||||||
|
// loads files and folders from the host file system if a superblock exists in the root directory
|
||||||
|
void LoadMemoryCardData();
|
||||||
|
|
||||||
|
// creates the FAT and indirect FAT
|
||||||
|
void CreateFat();
|
||||||
|
|
||||||
|
// creates file entries for the root directory
|
||||||
|
void CreateRootDir();
|
||||||
|
|
||||||
|
|
||||||
|
// returns the system cluster past the highest used one (will be the lowest free one under normal use)
|
||||||
|
// this is used for creating the FAT, don't call otherwise unless you know exactly what you're doing
|
||||||
|
u32 GetFreeSystemCluster();
|
||||||
|
|
||||||
|
// returns the lowest unused data cluster, relative to alloc_offset in the superblock
|
||||||
|
// returns 0xFFFFFFFFu when the memory card is full
|
||||||
|
u32 GetFreeDataCluster();
|
||||||
|
|
||||||
|
// returns the amount of unused data clusters
|
||||||
|
u32 GetAmountFreeDataClusters();
|
||||||
|
|
||||||
|
// returns the final cluster of the file or directory which is (partially) stored in the given cluster
|
||||||
|
u32 GetLastClusterOfData( const u32 cluster );
|
||||||
|
|
||||||
|
u64 ConvertToMemoryCardTimestamp( const wxDateTime& time );
|
||||||
|
|
||||||
|
|
||||||
|
// creates and returns a new file entry in the given directory entry, ready to be filled
|
||||||
|
// returns nullptr when the memory card is full
|
||||||
|
MemoryCardFileEntry* AppendFileEntryToDir( MemoryCardFileEntry* const dirEntry );
|
||||||
|
|
||||||
|
// adds a folder in the host file system to the memory card, including all files and subdirectories
|
||||||
|
// - dirEntry: the entry of the directory in the parent directory, or the root "." entry
|
||||||
|
// - dirPath: the full path to the directory in the host file system
|
||||||
|
bool AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath );
|
||||||
|
|
||||||
|
// adds a file in the host file sytem to the memory card
|
||||||
|
// - dirEntry: the entry of the directory in the parent directory, or the root "." entry
|
||||||
|
// - dirPath: the full path to the directory containing the file in the host file system
|
||||||
|
// - fileName: the name of the file, without path
|
||||||
|
bool AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName );
|
||||||
|
|
||||||
|
|
||||||
|
bool ReadFromFile( u8 *dest, u32 adr, u32 dataLength );
|
||||||
|
bool WriteToFile( const u8* src, u32 adr, u32 dataLength );
|
||||||
|
|
||||||
|
|
||||||
|
// flush the whole cache to the internal data and/or host file system
|
||||||
|
void Flush();
|
||||||
|
|
||||||
|
// flush a single page of the cache to the internal data and/or host file system
|
||||||
|
void Flush( const u32 page );
|
||||||
|
|
||||||
|
// flush a directory's file entries and all its subdirectories to the internal data
|
||||||
|
void FlushFileEntries( const u32 dirCluster, const u32 remainingFiles );
|
||||||
|
|
||||||
|
// write data as Save() normally would, but ignore the cache; used for flushing
|
||||||
|
s32 WriteWithoutCache( const u8 *src, u32 adr, int size );
|
||||||
|
|
||||||
|
void SetTimeLastWrittenToNow();
|
||||||
|
|
||||||
|
wxString GetDisabledMessage( uint slot ) const {
|
||||||
|
return wxsFormat( pxE( L"The PS2-slot %d has been automatically disabled. You can correct the problem\nand re-enable it at any time using Config:Memory cards from the main menu."
|
||||||
|
), slot//TODO: translate internal slot index to human-readable slot description
|
||||||
|
);
|
||||||
|
}
|
||||||
|
wxString GetCardFullMessage( const wxString& filePath ) const {
|
||||||
|
return wxsFormat( pxE( L"(FolderMcd) Memory Card is full, could not add: %s" ), WX_STR( filePath ) );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
// FolderMemoryCardAggregator
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
// Forwards the API's requests for specific memory card slots to the correct FolderMemoryCard.
|
||||||
|
class FolderMemoryCardAggregator {
|
||||||
|
protected:
|
||||||
|
static const int totalCardSlots = 8;
|
||||||
|
FolderMemoryCard m_cards[totalCardSlots];
|
||||||
|
|
||||||
|
public:
|
||||||
|
FolderMemoryCardAggregator();
|
||||||
|
virtual ~FolderMemoryCardAggregator() throw( ) {}
|
||||||
|
|
||||||
|
void Open();
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
s32 IsPresent( uint slot );
|
||||||
|
void GetSizeInfo( uint slot, PS2E_McdSizeInfo& outways );
|
||||||
|
bool IsPSX( uint slot );
|
||||||
|
s32 Read( uint slot, u8 *dest, u32 adr, int size );
|
||||||
|
s32 Save( uint slot, const u8 *src, u32 adr, int size );
|
||||||
|
s32 EraseBlock( uint slot, u32 adr );
|
||||||
|
u64 GetCRC( uint slot );
|
||||||
|
void NextFrame( uint slot );
|
||||||
|
};
|
|
@ -636,6 +636,7 @@
|
||||||
<ClCompile Include="..\..\gui\MainFrame.cpp" />
|
<ClCompile Include="..\..\gui\MainFrame.cpp" />
|
||||||
<ClCompile Include="..\..\gui\MainMenuClicks.cpp" />
|
<ClCompile Include="..\..\gui\MainMenuClicks.cpp" />
|
||||||
<ClCompile Include="..\..\gui\MemoryCardFile.cpp" />
|
<ClCompile Include="..\..\gui\MemoryCardFile.cpp" />
|
||||||
|
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp" />
|
||||||
<ClCompile Include="..\..\gui\MessageBoxes.cpp" />
|
<ClCompile Include="..\..\gui\MessageBoxes.cpp" />
|
||||||
<ClCompile Include="..\..\gui\MSWstuff.cpp" />
|
<ClCompile Include="..\..\gui\MSWstuff.cpp" />
|
||||||
<ClCompile Include="..\..\gui\pxLogTextCtrl.cpp" />
|
<ClCompile Include="..\..\gui\pxLogTextCtrl.cpp" />
|
||||||
|
|
|
@ -635,6 +635,9 @@
|
||||||
<ClCompile Include="..\..\gui\MemoryCardFile.cpp">
|
<ClCompile Include="..\..\gui\MemoryCardFile.cpp">
|
||||||
<Filter>AppHost</Filter>
|
<Filter>AppHost</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp">
|
||||||
|
<Filter>AppHost</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\gui\MessageBoxes.cpp">
|
<ClCompile Include="..\..\gui\MessageBoxes.cpp">
|
||||||
<Filter>AppHost</Filter>
|
<Filter>AppHost</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|
|
@ -636,6 +636,7 @@
|
||||||
<ClCompile Include="..\..\gui\MainFrame.cpp" />
|
<ClCompile Include="..\..\gui\MainFrame.cpp" />
|
||||||
<ClCompile Include="..\..\gui\MainMenuClicks.cpp" />
|
<ClCompile Include="..\..\gui\MainMenuClicks.cpp" />
|
||||||
<ClCompile Include="..\..\gui\MemoryCardFile.cpp" />
|
<ClCompile Include="..\..\gui\MemoryCardFile.cpp" />
|
||||||
|
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp" />
|
||||||
<ClCompile Include="..\..\gui\MessageBoxes.cpp" />
|
<ClCompile Include="..\..\gui\MessageBoxes.cpp" />
|
||||||
<ClCompile Include="..\..\gui\MSWstuff.cpp" />
|
<ClCompile Include="..\..\gui\MSWstuff.cpp" />
|
||||||
<ClCompile Include="..\..\gui\pxLogTextCtrl.cpp" />
|
<ClCompile Include="..\..\gui\pxLogTextCtrl.cpp" />
|
||||||
|
|
|
@ -635,6 +635,9 @@
|
||||||
<ClCompile Include="..\..\gui\MemoryCardFile.cpp">
|
<ClCompile Include="..\..\gui\MemoryCardFile.cpp">
|
||||||
<Filter>AppHost</Filter>
|
<Filter>AppHost</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp">
|
||||||
|
<Filter>AppHost</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\gui\MessageBoxes.cpp">
|
<ClCompile Include="..\..\gui\MessageBoxes.cpp">
|
||||||
<Filter>AppHost</Filter>
|
<Filter>AppHost</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|
Loading…
Reference in New Issue