Skip to content

Commit ef4e50c

Browse files
committed
Memcard: Add automated rolling local backup system
1 parent c5bfaa2 commit ef4e50c

23 files changed

Lines changed: 1069 additions & 82 deletions

pcsx2-qt/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ target_sources(pcsx2-qt PRIVATE
146146
Settings/MemoryCardCreateDialog.cpp
147147
Settings/MemoryCardCreateDialog.h
148148
Settings/MemoryCardCreateDialog.ui
149+
Settings/MemoryCardBackupModel.cpp
150+
Settings/MemoryCardBackupModel.h
151+
Settings/MemoryCardBackupWidget.cpp
152+
Settings/MemoryCardBackupWidget.h
153+
Settings/MemoryCardBackupWidget.ui
149154
Settings/MemoryCardSettingsWidget.cpp
150155
Settings/MemoryCardSettingsWidget.h
151156
Settings/MemoryCardSettingsWidget.ui
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
2+
// SPDX-License-Identifier: GPL-3.0+
3+
4+
#include "MemoryCardBackupModel.h"
5+
6+
#include "Config.h"
7+
8+
MemoryCardBackupModel::MemoryCardBackupModel(QObject* parent)
9+
: QAbstractTableModel(parent)
10+
{
11+
refresh();
12+
}
13+
14+
int MemoryCardBackupModel::rowCount(const QModelIndex& parent) const
15+
{
16+
return static_cast<int>(m_backups.size());
17+
}
18+
19+
int MemoryCardBackupModel::columnCount(const QModelIndex& parent) const
20+
{
21+
return COLUMN_COUNT;
22+
}
23+
24+
QVariant MemoryCardBackupModel::data(const QModelIndex& index, int role) const
25+
{
26+
size_t row = static_cast<size_t>(index.row());
27+
if (row >= m_backups.size())
28+
return QVariant();
29+
30+
if (role != Qt::DisplayRole)
31+
return QVariant();
32+
33+
const MemoryCardBackup::BackupMetadata& backup = m_backups[row];
34+
35+
switch (index.column())
36+
{
37+
case CARD_NAME:
38+
return QString::fromStdString(backup.name);
39+
case CREATION_TIME:
40+
return QString::number(backup.creation_time);
41+
case COMPRESSED_SIZE:
42+
return QString::number(backup.backup_size);
43+
}
44+
45+
return QVariant();
46+
}
47+
48+
QVariant MemoryCardBackupModel::headerData(int section, Qt::Orientation orientation, int role) const
49+
{
50+
switch (section)
51+
{
52+
case CARD_NAME:
53+
return tr("Card Name");
54+
case CREATION_TIME:
55+
return tr("Creation Time");
56+
case COMPRESSED_SIZE:
57+
return tr("Compressed Size");
58+
}
59+
60+
return QVariant();
61+
}
62+
63+
void MemoryCardBackupModel::refresh()
64+
{
65+
beginResetModel();
66+
m_backups = MemoryCardBackup::EnumerateBackups(EmuFolders::MemoryCardBackups);
67+
endResetModel();
68+
}
69+
70+
#include "moc_MemoryCardBackupModel.cpp"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
2+
// SPDX-License-Identifier: GPL-3.0+
3+
4+
#pragma once
5+
6+
#include "SIO/Memcard/MemoryCardBackup.h"
7+
8+
#include <QtCore/QAbstractTableModel>
9+
10+
class MemoryCardBackupModel : public QAbstractTableModel
11+
{
12+
Q_OBJECT
13+
14+
public:
15+
MemoryCardBackupModel(QObject* parent = nullptr);
16+
17+
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
18+
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
19+
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
20+
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
21+
22+
void refresh();
23+
24+
private:
25+
enum Columnn
26+
{
27+
CARD_NAME,
28+
CREATION_TIME,
29+
COMPRESSED_SIZE,
30+
COLUMN_COUNT
31+
};
32+
33+
std::vector<MemoryCardBackup::BackupMetadata> m_backups;
34+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
2+
// SPDX-License-Identifier: GPL-3.0+
3+
4+
#include "MemoryCardBackupWidget.h"
5+
6+
#include "MemoryCardBackupModel.h"
7+
8+
MemoryCardBackupWidget::MemoryCardBackupWidget(QWidget* parent)
9+
: QWidget(parent)
10+
{
11+
m_ui.setupUi(this);
12+
13+
m_model = new MemoryCardBackupModel(m_ui.backupsTable);
14+
m_ui.backupsTable->setModel(m_model);
15+
}
16+
#include "moc_MemoryCardBackupWidget.cpp"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
2+
// SPDX-License-Identifier: GPL-3.0+
3+
4+
#pragma once
5+
6+
#include <QtWidgets/QWidget>
7+
8+
#include "ui_MemoryCardBackupWidget.h"
9+
10+
class MemoryCardBackupModel;
11+
12+
class MemoryCardBackupWidget : public QWidget
13+
{
14+
Q_OBJECT
15+
16+
public:
17+
MemoryCardBackupWidget(QWidget* parent = nullptr);
18+
19+
private:
20+
Ui::MemoryCardBackupWidget m_ui;
21+
22+
MemoryCardBackupModel* m_model;
23+
};
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>MemoryCardBackupWidget</class>
4+
<widget class="QWidget" name="MemoryCardBackupWidget">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>700</width>
10+
<height>600</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Form</string>
15+
</property>
16+
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,1">
17+
<item>
18+
<widget class="QLabel" name="descriptionLabel">
19+
<property name="text">
20+
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;PCSX2 features an automated rolling local backup system for memory cards. Older backups will be deleted to make space for new backups, according to the configured minimum and maximum numbers of backups to keep, and the drive space usage limit.&lt;/p&gt;&lt;p&gt;This system protects your save games in the case that a game crashes or is forcibly shutdown while saving, but NOT in the case that the drive used to store the memory card files is damaged, so this does NOT replace a traditional backup system.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
21+
</property>
22+
<property name="wordWrap">
23+
<bool>true</bool>
24+
</property>
25+
</widget>
26+
</item>
27+
<item>
28+
<widget class="QGroupBox" name="optionsGroup">
29+
<property name="title">
30+
<string>Options</string>
31+
</property>
32+
<layout class="QGridLayout" name="gridLayout_2">
33+
<item row="1" column="0">
34+
<widget class="QCheckBox" name="enableRollingBackups">
35+
<property name="text">
36+
<string>Enable Automatic Backups</string>
37+
</property>
38+
</widget>
39+
</item>
40+
<item row="1" column="1">
41+
<widget class="QCheckBox" name="checkBox_2">
42+
<property name="text">
43+
<string>Enable Advanced Compression (Zstandard)</string>
44+
</property>
45+
</widget>
46+
</item>
47+
</layout>
48+
</widget>
49+
</item>
50+
<item>
51+
<widget class="QGroupBox" name="driveSpaceGroup">
52+
<property name="title">
53+
<string>Storage</string>
54+
</property>
55+
<layout class="QGridLayout" name="gridLayout">
56+
<item row="3" column="0">
57+
<widget class="QLabel" name="driveSpaceLimitLabel">
58+
<property name="text">
59+
<string>Drive Space Usage Limit:</string>
60+
</property>
61+
</widget>
62+
</item>
63+
<item row="3" column="1">
64+
<layout class="QHBoxLayout" name="driveSpaceLayout">
65+
<item>
66+
<widget class="QSlider" name="driveSpaceSlider">
67+
<property name="orientation">
68+
<enum>Qt::Orientation::Horizontal</enum>
69+
</property>
70+
</widget>
71+
</item>
72+
<item>
73+
<widget class="QSpinBox" name="driveSpaceSpinBox">
74+
<property name="suffix">
75+
<string>MiB</string>
76+
</property>
77+
</widget>
78+
</item>
79+
</layout>
80+
</item>
81+
<item row="2" column="0">
82+
<widget class="QLabel" name="backupsToKeepLabel">
83+
<property name="text">
84+
<string>Number Of Backups To Keep:</string>
85+
</property>
86+
</widget>
87+
</item>
88+
<item row="0" column="1">
89+
<layout class="QHBoxLayout" name="horizontalLayout">
90+
<item>
91+
<widget class="QLineEdit" name="folderLineEdit"/>
92+
</item>
93+
<item>
94+
<widget class="QPushButton" name="folderBrowseButton">
95+
<property name="text">
96+
<string>Browse...</string>
97+
</property>
98+
</widget>
99+
</item>
100+
<item>
101+
<widget class="QPushButton" name="folderOpenButton">
102+
<property name="text">
103+
<string>Open...</string>
104+
</property>
105+
</widget>
106+
</item>
107+
<item>
108+
<widget class="QPushButton" name="folderResetButton">
109+
<property name="text">
110+
<string>Reset</string>
111+
</property>
112+
</widget>
113+
</item>
114+
</layout>
115+
</item>
116+
<item row="0" column="0">
117+
<widget class="QLabel" name="folderLabel">
118+
<property name="text">
119+
<string>Backup Folder:</string>
120+
</property>
121+
</widget>
122+
</item>
123+
<item row="2" column="1">
124+
<layout class="QHBoxLayout" name="backupsToKeepLayout" stretch="0,1,0,1">
125+
<item>
126+
<widget class="QLabel" name="minimumToKeepLabel">
127+
<property name="text">
128+
<string>Minimum:</string>
129+
</property>
130+
</widget>
131+
</item>
132+
<item>
133+
<widget class="QSpinBox" name="minimumToKeepSpinBox"/>
134+
</item>
135+
<item>
136+
<widget class="QLabel" name="maximumToKeepLabel">
137+
<property name="text">
138+
<string>Maximum:</string>
139+
</property>
140+
</widget>
141+
</item>
142+
<item>
143+
<widget class="QSpinBox" name="maximumToKeepSpinBox"/>
144+
</item>
145+
</layout>
146+
</item>
147+
</layout>
148+
</widget>
149+
</item>
150+
<item>
151+
<widget class="QGroupBox" name="backupsGroup">
152+
<property name="title">
153+
<string>Backups</string>
154+
</property>
155+
<layout class="QVBoxLayout" name="verticalLayout_2">
156+
<item>
157+
<layout class="QHBoxLayout" name="filterLayout" stretch="0,1">
158+
<item>
159+
<widget class="QLabel" name="filterLabel">
160+
<property name="text">
161+
<string>Filter:</string>
162+
</property>
163+
</widget>
164+
</item>
165+
<item>
166+
<widget class="QComboBox" name="filterComboBox"/>
167+
</item>
168+
</layout>
169+
</item>
170+
<item>
171+
<widget class="QTableView" name="backupsTable"/>
172+
</item>
173+
<item>
174+
<layout class="QHBoxLayout" name="backupsButtonLayout">
175+
<item>
176+
<widget class="QPushButton" name="refreshButton">
177+
<property name="text">
178+
<string>Refresh</string>
179+
</property>
180+
</widget>
181+
</item>
182+
<item>
183+
<spacer name="horizontalSpacer">
184+
<property name="orientation">
185+
<enum>Qt::Orientation::Horizontal</enum>
186+
</property>
187+
<property name="sizeHint" stdset="0">
188+
<size>
189+
<width>40</width>
190+
<height>20</height>
191+
</size>
192+
</property>
193+
</spacer>
194+
</item>
195+
<item>
196+
<widget class="QPushButton" name="restoreButton">
197+
<property name="text">
198+
<string>Restore</string>
199+
</property>
200+
</widget>
201+
</item>
202+
</layout>
203+
</item>
204+
</layout>
205+
</widget>
206+
</item>
207+
</layout>
208+
</widget>
209+
<resources/>
210+
<connections/>
211+
</ui>

pcsx2-qt/Settings/MemoryCardSettingsWidget.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include "common/StringUtil.h"
1212

13+
#include "MemoryCardBackupWidget.h"
1314
#include "MemoryCardConvertDialog.h"
1415
#include "MemoryCardCreateDialog.h"
1516
#include "MemoryCardSettingsWidget.h"
@@ -32,7 +33,13 @@ MemoryCardSettingsWidget::MemoryCardSettingsWidget(SettingsWindow* settings_dial
3233
{
3334
SettingsInterface* sif = dialog()->getSettingsInterface();
3435

35-
setupTab(m_ui);
36+
setupTab(m_ui, tr("General"));
37+
38+
if (!settings_dialog->isPerGameSettings())
39+
{
40+
m_backup_widget = new MemoryCardBackupWidget(this);
41+
addTab(tr("Backups"), m_backup_widget, false);
42+
}
3643

3744
// this is a bit lame, but resizeEvent() isn't good enough to autosize our columns,
3845
// since the group box hasn't been resized at that point.

0 commit comments

Comments
 (0)