summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/hle/service/mii/mii_database_manager.cpp420
-rw-r--r--src/core/hle/service/mii/mii_database_manager.h58
3 files changed, 480 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 9d22cc945..46e2dc839 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -598,6 +598,8 @@ add_library(core STATIC
598 hle/service/mii/mii.h 598 hle/service/mii/mii.h
599 hle/service/mii/mii_database.cpp 599 hle/service/mii/mii_database.cpp
600 hle/service/mii/mii_database.h 600 hle/service/mii/mii_database.h
601 hle/service/mii/mii_database_manager.cpp
602 hle/service/mii/mii_database_manager.h
601 hle/service/mii/mii_manager.cpp 603 hle/service/mii/mii_manager.cpp
602 hle/service/mii/mii_manager.h 604 hle/service/mii/mii_manager.h
603 hle/service/mii/mii_result.h 605 hle/service/mii/mii_result.h
diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp
new file mode 100644
index 000000000..63c411690
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database_manager.cpp
@@ -0,0 +1,420 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/assert.h"
5#include "common/fs/file.h"
6#include "common/fs/fs.h"
7#include "common/fs/path_util.h"
8#include "common/logging/log.h"
9#include "common/string_util.h"
10
11#include "core/hle/service/mii/mii_database_manager.h"
12#include "core/hle/service/mii/mii_result.h"
13#include "core/hle/service/mii/mii_util.h"
14#include "core/hle/service/mii/types/char_info.h"
15#include "core/hle/service/mii/types/store_data.h"
16
17namespace Service::Mii {
18constexpr std::string DbFileName = "MiiDatabase.dat";
19
20DatabaseManager::DatabaseManager() {}
21
22Result DatabaseManager::MountSaveData() {
23 if (!is_save_data_mounted) {
24 system_save_dir =
25 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000030";
26 if (is_test_db) {
27 system_save_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
28 "system/save/8000000000000031";
29 }
30
31 // mount point should be "mii:"
32
33 if (!Common::FS::CreateDirs(system_save_dir)) {
34 return ResultUnknown;
35 }
36 }
37
38 is_save_data_mounted = true;
39 return ResultSuccess;
40}
41
42Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken) {
43 is_database_broken = false;
44 if (!is_save_data_mounted) {
45 return ResultInvalidArgument;
46 }
47
48 database.CleanDatabase();
49 update_counter++;
50 metadata.update_counter = update_counter;
51
52 const Common::FS::IOFile db_file{system_save_dir / DbFileName, Common::FS::FileAccessMode::Read,
53 Common::FS::FileType::BinaryFile};
54
55 if (!db_file.IsOpen()) {
56 return SaveDatabase();
57 }
58
59 if (Common::FS::GetSize(system_save_dir / DbFileName) != sizeof(NintendoFigurineDatabase)) {
60 is_database_broken = true;
61 }
62
63 if (db_file.Read(database) != 1) {
64 is_database_broken = true;
65 }
66
67 if (is_database_broken) {
68 // Dragons happen here for simplicity just clean the database
69 LOG_ERROR(Service_Mii, "Mii database is corrupted");
70 database.CleanDatabase();
71 return ResultUnknown;
72 }
73
74 const auto result = database.CheckIntegrity();
75
76 if (result.IsError()) {
77 LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw);
78 database.CleanDatabase();
79 return ResultSuccess;
80 }
81
82 LOG_INFO(Service_Mii, "Successfully loaded mii database. size={}",
83 database.GetDatabaseLength());
84 return ResultSuccess;
85}
86
87bool DatabaseManager::IsFullDatabase() const {
88 return database.GetDatabaseLength() == MaxDatabaseLength;
89}
90
91bool DatabaseManager::IsModified() const {
92 return is_moddified;
93}
94
95u64 DatabaseManager::GetUpdateCounter() const {
96 return update_counter;
97}
98
99u32 DatabaseManager::GetCount(const DatabaseSessionMetadata& metadata) const {
100 const u32 database_size = database.GetDatabaseLength();
101 if (metadata.magic == MiiMagic) {
102 return database_size;
103 }
104
105 // Special mii can't be used. Skip those.
106
107 u32 mii_count{};
108 for (std::size_t index = 0; index < database_size; ++index) {
109 const auto& store_data = database.Get(index);
110 if (store_data.IsSpecial()) {
111 continue;
112 }
113 mii_count++;
114 }
115
116 return mii_count;
117}
118
119void DatabaseManager::Get(StoreData& out_store_data, std::size_t index,
120 const DatabaseSessionMetadata& metadata) const {
121 if (metadata.magic == MiiMagic) {
122 out_store_data = database.Get(index);
123 return;
124 }
125
126 // The index refeers to the mii index without special mii.
127 // Search on the database until we find it
128
129 u32 virtual_index = 0;
130 const u32 database_size = database.GetDatabaseLength();
131 for (std::size_t i = 0; i < database_size; ++i) {
132 const auto& store_data = database.Get(i);
133 if (store_data.IsSpecial()) {
134 continue;
135 }
136 if (virtual_index == index) {
137 out_store_data = store_data;
138 return;
139 }
140 virtual_index++;
141 }
142
143 // This function doesn't fail. It returns the first mii instead
144 out_store_data = database.Get(0);
145}
146
147Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id,
148 bool is_special) const {
149 u32 index{};
150 const bool is_found = database.GetIndexByCreatorId(index, create_id);
151
152 if (!is_found) {
153 return ResultNotFound;
154 }
155
156 if (is_special) {
157 out_index = index;
158 return ResultSuccess;
159 }
160
161 if (database.Get(index).IsSpecial()) {
162 return ResultNotFound;
163 }
164
165 out_index = 0;
166
167 if (index < 1) {
168 return ResultSuccess;
169 }
170
171 for (std::size_t i = 0; i <= index; ++i) {
172 if (database.Get(i).IsSpecial()) {
173 continue;
174 }
175 out_index++;
176 }
177 return ResultSuccess;
178}
179
180Result DatabaseManager::FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
181 const Common::UUID& create_id) const {
182 u32 index{};
183 const bool is_found = database.GetIndexByCreatorId(index, create_id);
184
185 if (!is_found) {
186 return ResultNotFound;
187 }
188
189 if (metadata.magic == MiiMagic) {
190 out_index = index;
191 return ResultSuccess;
192 }
193
194 if (database.Get(index).IsSpecial()) {
195 return ResultNotFound;
196 }
197
198 out_index = 0;
199
200 if (index < 1) {
201 return ResultSuccess;
202 }
203
204 // The index refeers to the mii index without special mii.
205 // Search on the database until we find it
206
207 for (std::size_t i = 0; i <= index; ++i) {
208 const auto& store_data = database.Get(i);
209 if (store_data.IsSpecial()) {
210 continue;
211 }
212 out_index++;
213 }
214 return ResultSuccess;
215}
216
217Result DatabaseManager::FindMoveIndex(u32& out_index, u32 new_index,
218 const Common::UUID& create_id) const {
219 const auto database_size = database.GetDatabaseLength();
220
221 if (database_size >= 1) {
222 u32 virtual_index{};
223 for (std::size_t i = 0; i < database_size; ++i) {
224 const StoreData& store_data = database.Get(i);
225 if (store_data.IsSpecial()) {
226 continue;
227 }
228 if (virtual_index == new_index) {
229 const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
230 if (!is_found) {
231 return ResultNotFound;
232 }
233 if (store_data.IsSpecial()) {
234 return ResultInvalidOperation;
235 }
236 return ResultSuccess;
237 }
238 virtual_index++;
239 }
240 }
241
242 const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
243 if (!is_found) {
244 return ResultNotFound;
245 }
246 const StoreData& store_data = database.Get(out_index);
247 if (store_data.IsSpecial()) {
248 return ResultInvalidOperation;
249 }
250 return ResultSuccess;
251}
252
253Result DatabaseManager::Move(DatabaseSessionMetadata& metadata, u32 new_index,
254 const Common::UUID& create_id) {
255 u32 current_index{};
256 if (metadata.magic == MiiMagic) {
257 const bool is_found = database.GetIndexByCreatorId(current_index, create_id);
258 if (!is_found) {
259 return ResultNotFound;
260 }
261 } else {
262 const auto result = FindMoveIndex(current_index, new_index, create_id);
263 if (result.IsError()) {
264 return result;
265 }
266 }
267
268 const auto result = database.Move(current_index, new_index);
269 if (result.IsFailure()) {
270 return result;
271 }
272
273 is_moddified = true;
274 update_counter++;
275 metadata.update_counter = update_counter;
276 return ResultSuccess;
277}
278
279Result DatabaseManager::AddOrReplace(DatabaseSessionMetadata& metadata,
280 const StoreData& store_data) {
281 if (store_data.IsValid() != ValidationResult::NoErrors) {
282 return ResultInvalidStoreData;
283 }
284 if (metadata.magic != MiiMagic && store_data.IsSpecial()) {
285 return ResultInvalidOperation;
286 }
287
288 u32 index{};
289 const bool is_found = database.GetIndexByCreatorId(index, store_data.GetCreateId());
290 if (is_found) {
291 const StoreData& old_store_data = database.Get(index);
292
293 if (store_data.IsSpecial() != old_store_data.IsSpecial()) {
294 return ResultInvalidOperation;
295 }
296
297 database.Replace(index, store_data);
298 } else {
299 if (database.IsFull()) {
300 return ResultDatabaseFull;
301 }
302
303 database.Add(store_data);
304 }
305
306 is_moddified = true;
307 update_counter++;
308 metadata.update_counter = update_counter;
309 return ResultSuccess;
310}
311
312Result DatabaseManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
313 u32 index{};
314 const bool is_found = database.GetIndexByCreatorId(index, create_id);
315 if (!is_found) {
316 return ResultNotFound;
317 }
318
319 if (metadata.magic != MiiMagic) {
320 const auto& store_data = database.Get(index);
321 if (store_data.IsSpecial()) {
322 return ResultInvalidOperation;
323 }
324 }
325
326 database.Delete(index);
327
328 is_moddified = true;
329 update_counter++;
330 metadata.update_counter = update_counter;
331 return ResultSuccess;
332}
333
334Result DatabaseManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
335 if (char_info.Verify() != ValidationResult::NoErrors) {
336 return ResultInvalidCharInfo2;
337 }
338 if (char_info.GetType() == 1) {
339 return ResultInvalidCharInfoType;
340 }
341
342 u32 index{};
343 StoreData store_data{};
344
345 // Loop until the mii we created is not on the database
346 do {
347 store_data.BuildWithCharInfo(char_info);
348 } while (database.GetIndexByCreatorId(index, store_data.GetCreateId()));
349
350 const Result result = store_data.Restore();
351
352 if (result.IsSuccess() || result == ResultNotUpdated) {
353 return AddOrReplace(metadata, store_data);
354 }
355
356 return result;
357}
358
359Result DatabaseManager::DestroyFile(DatabaseSessionMetadata& metadata) {
360 database.CorruptCrc();
361
362 is_moddified = true;
363 update_counter++;
364 metadata.update_counter = update_counter;
365
366 const auto result = SaveDatabase();
367 database.CleanDatabase();
368
369 return result;
370}
371
372Result DatabaseManager::DeleteFile() {
373 const bool result = Common::FS::RemoveFile(system_save_dir / DbFileName);
374 // Return proper FS error here
375 return result ? ResultSuccess : ResultUnknown;
376}
377
378void DatabaseManager::Format(DatabaseSessionMetadata& metadata) {
379 database.CleanDatabase();
380 is_moddified = true;
381 update_counter++;
382 metadata.update_counter = update_counter;
383}
384
385Result DatabaseManager::SaveDatabase() {
386 // TODO: Replace unknown error codes with proper FS error codes when available
387
388 if (!Common::FS::Exists(system_save_dir / DbFileName)) {
389 if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
390 LOG_ERROR(Service_Mii, "Failed to create mii database");
391 return ResultUnknown;
392 }
393 }
394
395 const auto file_size = Common::FS::GetSize(system_save_dir / DbFileName);
396 if (file_size != 0 && file_size != sizeof(NintendoFigurineDatabase)) {
397 if (!Common::FS::RemoveFile(system_save_dir / DbFileName)) {
398 LOG_ERROR(Service_Mii, "Failed to delete mii database");
399 return ResultUnknown;
400 }
401 if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
402 LOG_ERROR(Service_Mii, "Failed to create mii database");
403 return ResultUnknown;
404 }
405 }
406
407 const Common::FS::IOFile db_file{system_save_dir / DbFileName,
408 Common::FS::FileAccessMode::ReadWrite,
409 Common::FS::FileType::BinaryFile};
410
411 if (db_file.Write(database) != 1) {
412 LOG_ERROR(Service_Mii, "Failed to save mii database");
413 return ResultUnknown;
414 }
415
416 is_moddified = false;
417 return ResultSuccess;
418}
419
420} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii_database_manager.h b/src/core/hle/service/mii/mii_database_manager.h
new file mode 100644
index 000000000..52c32be82
--- /dev/null
+++ b/src/core/hle/service/mii/mii_database_manager.h
@@ -0,0 +1,58 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/fs/fs.h"
7#include "core/hle/result.h"
8#include "core/hle/service/mii/mii_database.h"
9
10namespace Service::Mii {
11class CharInfo;
12class StoreData;
13
14class DatabaseManager {
15public:
16 DatabaseManager();
17 Result MountSaveData();
18 Result Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken);
19
20 bool IsFullDatabase() const;
21 bool IsModified() const;
22 u64 GetUpdateCounter() const;
23
24 void Get(StoreData& out_store_data, std::size_t index,
25 const DatabaseSessionMetadata& metadata) const;
26 u32 GetCount(const DatabaseSessionMetadata& metadata) const;
27
28 Result FindIndex(s32& out_index, const Common::UUID& create_id, bool is_special) const;
29 Result FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
30 const Common::UUID& create_id) const;
31 Result FindMoveIndex(u32& out_index, u32 new_index, const Common::UUID& create_id) const;
32
33 Result Move(DatabaseSessionMetadata& metadata, u32 current_index,
34 const Common::UUID& create_id);
35 Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& out_store_data);
36 Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
37 Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
38
39 Result DestroyFile(DatabaseSessionMetadata& metadata);
40 Result DeleteFile();
41 void Format(DatabaseSessionMetadata& metadata);
42
43 Result SaveDatabase();
44
45private:
46 // This is the global value of
47 // nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
48 bool is_test_db{};
49
50 bool is_moddified{};
51 bool is_save_data_mounted{};
52 u64 update_counter{};
53 NintendoFigurineDatabase database{};
54
55 std::filesystem::path system_save_dir{};
56};
57
58}; // namespace Service::Mii