mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 00:38:58 +00:00
Add support for config savegame
This commit is contained in:
@@ -34,10 +34,10 @@ If you are wishing to use threeSD with a portable install of Citra (i.e. that ha
|
|||||||
You will need to run a GodMode9 script. If you are unsure about the script's safety (which is good!), check the source code yourself [here](https://github.com/zhaowenlan1779/threeSD/blob/master/dist/threeSDumper.gm9).
|
You will need to run a GodMode9 script. If you are unsure about the script's safety (which is good!), check the source code yourself [here](https://github.com/zhaowenlan1779/threeSD/blob/master/dist/threeSDumper.gm9).
|
||||||
|
|
||||||
1. Copy the gm9 script (`threeSDumper.gm9`) in `dist` to the `gm9/scripts` folder on your SD card.
|
1. Copy the gm9 script (`threeSDumper.gm9`) in `dist` to the `gm9/scripts` folder on your SD card.
|
||||||
1. Launch GodMode9 on your 3DS (you will need to hold a button corresponding to your `firm` file's name, or hold `START` to enter the chainloader menu). Press the `Home` button to bring up GodMode9's `HOME Menu`. Use the d-pad and the `A` button to select `Scripts...`.
|
1. Launch GodMode9 on your 3DS (you will need to hold a button corresponding to your `firm` file's name, or hold `START` to enter the chainloader menu). Press the `Home` button to bring up GodMode9's `HOME menu`. Use the D-pad and the `A` button to select `Scripts...`.
|
||||||
1. Use the d-pad and the `A` button to select `threeSDumper`. You will be prompted with a question "Execute threeSD Dumper?". Press `A` to confirm.
|
1. Use the D-pad and the `A` button to select `threeSDumper`. You will be prompted with a question "Execute threeSD Dumper?". Press `A` to confirm.
|
||||||
1. After a few seconds, you will see the message "Successfully dumped necessary files for threeSD." Your 3DS SD card is now prepared for use with threeSD and Citra. Press `A` to exit the script.
|
1. After a few seconds, you will see the message "Successfully dumped necessary files for threeSD." Your 3DS SD card is now prepared for use with threeSD and Citra. Press `A` to exit the script.
|
||||||
1. Power off your 3DS with `R+START`. Remove the SD card from your 3DS and insert it into your PC (with a card reader).
|
1. Power off your 3DS with GodMode9's `HOME menu`. Remove the SD card from your 3DS and insert it into your PC (with a card reader).
|
||||||
|
|
||||||
### On your PC
|
### On your PC
|
||||||
|
|
||||||
@@ -46,22 +46,19 @@ Make sure the SD card is properly recognized and shows up as a disk.
|
|||||||
1. Launch threeSD. You should see a small dialog, which has your SD card as an auto-detected configuration.
|
1. Launch threeSD. You should see a small dialog, which has your SD card as an auto-detected configuration.
|
||||||
* If it does not show up and the combo box says `None`, you should check if you can really find your SD card in the explorer (aka. `My Computer`), whether the drive for your SD card is accessible, and whether it contains the `Nintendo 3DS` and `threeSD` folders.
|
* If it does not show up and the combo box says `None`, you should check if you can really find your SD card in the explorer (aka. `My Computer`), whether the drive for your SD card is accessible, and whether it contains the `Nintendo 3DS` and `threeSD` folders.
|
||||||
1. Click `OK`. After a few seconds of loading, you should see the `Select Contents` dialog. Select the contents you would like to import. By default, contents that do not currently exist is selected. Make sure the total size of your selected contents do not exceed the available space on your disk.
|
1. Click `OK`. After a few seconds of loading, you should see the `Select Contents` dialog. Select the contents you would like to import. By default, contents that do not currently exist is selected. Make sure the total size of your selected contents do not exceed the available space on your disk.
|
||||||
* You can select between `Title View` which organizes contents by title, and `Group View` which organizes contents by type (application, save data, etc).
|
* You can switch between `Title View` which organizes contents by title, and `Group View` which organizes contents by type (application, save data, etc).
|
||||||
* The `System Archive` and `System Data` groups contains important data that is necessary for your imported games to run. You should definitely import the contents there, if they do not exist yet.
|
* The `System Archive` and `System Data` groups contains important data that is necessary for your imported games to run. You should definitely import the contents there, if they do not exist yet.
|
||||||
1. After you've finished your selection, click `OK`. You should now see a progress dialog; wait a while until your contents are imported.
|
1. After you've finished your selection, click `OK`. You should now see a progress dialog; wait a while until your contents are imported.
|
||||||
* The time will depend on how big your contents are, as well as your CPU processing power and (mainly) disk I/O speeds.
|
* The time will depend on how big your contents are, as well as your CPU processing power and (mainly) disk I/O speeds.
|
||||||
|
|
||||||
### What to do next
|
### What to do next
|
||||||
|
|
||||||
You can now enjoy your games with Citra, at high resolutions, with custom controllers, and the (now in Canary) Custom Textures feature!
|
You can now enjoy your games with Citra, at high resolutions, with custom controllers and cheats, and the (now in Canary) custom textures!
|
||||||
|
|
||||||
It is recommended that you also optionally [dump your config savegame](https://citra-emu.org/wiki/dumping-config-savegame-from-a-3ds-console) if you come across problems, for the best experience while enjoying Citra.
|
|
||||||
|
|
||||||
If you have any game cartidges, and would like to dump them as well, visit [this tutorial](https://citra-emu.org/wiki/dumping-game-cartridges).
|
If you have any game cartidges, and would like to dump them as well, visit [this tutorial](https://citra-emu.org/wiki/dumping-game-cartridges).
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* Config savegame
|
|
||||||
* UI improvements
|
* UI improvements
|
||||||
* Better error messages
|
* Better error messages
|
||||||
* Beautiful icons
|
* Beautiful icons
|
||||||
|
|||||||
Vendored
+3
@@ -95,6 +95,9 @@ find 1:/title/000400db/00010302/content/*.app APP
|
|||||||
cp -w -n $[APP] $[OUT]/sysarchives/000400db/00010302.app
|
cp -w -n $[APP] $[OUT]/sysarchives/000400db/00010302.app
|
||||||
decrypt $[OUT]/sysarchives/000400db/00010302.app
|
decrypt $[OUT]/sysarchives/000400db/00010302.app
|
||||||
|
|
||||||
|
# Config savegame
|
||||||
|
cp -w -n 1:/data/$[SYSID0]/sysdata/00010017/00000000 $[OUT]/config.sav
|
||||||
|
|
||||||
set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779\n \nSuccess!"
|
set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779\n \nSuccess!"
|
||||||
echo "Successfully dumped necessary\nfiles for threeSD."
|
echo "Successfully dumped necessary\nfiles for threeSD."
|
||||||
|
|
||||||
|
|||||||
@@ -239,6 +239,35 @@ bool SDMCImporter::ImportSysdata(u64 id, [[maybe_unused]] const ProgressCallback
|
|||||||
file.WriteString("slot0x25KeyX=" + Key::KeyToString(Key::GetKeyX(0x25)) + "\n");
|
file.WriteString("slot0x25KeyX=" + Key::KeyToString(Key::GetKeyX(0x25)) + "\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 5: { // Config savegame
|
||||||
|
FileUtil::IOFile file(config.config_savegame_path, "rb");
|
||||||
|
if (!file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> data(file.GetSize());
|
||||||
|
if (file.ReadBytes(data.data(), data.size()) != data.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataContainer container(data);
|
||||||
|
if (!container.IsGood()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDSavegame save(std::move(container.GetIVFCLevel4Data()));
|
||||||
|
if (!save.IsGood()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto target_path =
|
||||||
|
fmt::format("{}data/00000000000000000000000000000000/sysdata/00010017/00000000/",
|
||||||
|
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
|
||||||
|
if (!FileUtil::CreateFullPath(target_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return save.ExtractDirectory(target_path, 1); // 1 = root
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
UNREACHABLE_MSG("Unexpected sysdata id {}", id);
|
UNREACHABLE_MSG("Unexpected sysdata id {}", id);
|
||||||
}
|
}
|
||||||
@@ -445,6 +474,10 @@ void SDMCImporter::ListSysdata(std::vector<ContentSpecifier>& out) const {
|
|||||||
out.push_back(
|
out.push_back(
|
||||||
{ContentType::Sysdata, 4, FileUtil::Exists(sysdata_path + AES_KEYS), 47, AES_KEYS});
|
{ContentType::Sysdata, 4, FileUtil::Exists(sysdata_path + AES_KEYS), 47, AES_KEYS});
|
||||||
}
|
}
|
||||||
|
CHECK_CONTENT(5, config.config_savegame_path,
|
||||||
|
fmt::format("{}data/00000000000000000000000000000000/sysdata/00010017/",
|
||||||
|
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
|
||||||
|
"Config savegame");
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef CHECK_CONTENT
|
#undef CHECK_CONTENT
|
||||||
@@ -545,6 +578,11 @@ void SDMCImporter::DeleteSysdata(u64 id) const {
|
|||||||
case 4: { // aes_keys.txt
|
case 4: { // aes_keys.txt
|
||||||
FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + AES_KEYS);
|
FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + AES_KEYS);
|
||||||
}
|
}
|
||||||
|
case 5: { // Config savegame
|
||||||
|
FileUtil::DeleteDirRecursively(
|
||||||
|
fmt::format("{}data/00000000000000000000000000000000/sysdata/00010017/",
|
||||||
|
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
UNREACHABLE_MSG("Unexpected sysdata id {}", id);
|
UNREACHABLE_MSG("Unexpected sysdata id {}", id);
|
||||||
}
|
}
|
||||||
@@ -575,6 +613,7 @@ std::vector<Config> LoadPresetConfig(std::string mount_point) {
|
|||||||
LOAD_DATA(safe_mode_firm_path, "firm/");
|
LOAD_DATA(safe_mode_firm_path, "firm/");
|
||||||
LOAD_DATA(seed_db_path, SEED_DB);
|
LOAD_DATA(seed_db_path, SEED_DB);
|
||||||
LOAD_DATA(secret_sector_path, SECRET_SECTOR);
|
LOAD_DATA(secret_sector_path, SECRET_SECTOR);
|
||||||
|
LOAD_DATA(config_savegame_path, "config.sav");
|
||||||
LOAD_DATA(system_archives_path, "sysarchives/");
|
LOAD_DATA(system_archives_path, "sysarchives/");
|
||||||
#undef LOAD_DATA
|
#undef LOAD_DATA
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -57,9 +57,10 @@ struct Config {
|
|||||||
std::string safe_mode_firm_path; ///< Path to safe mode firm (A folder) (Sysdata 1)
|
std::string safe_mode_firm_path; ///< Path to safe mode firm (A folder) (Sysdata 1)
|
||||||
std::string seed_db_path; ///< Path to seeddb.bin (Sysdata 2)
|
std::string seed_db_path; ///< Path to seeddb.bin (Sysdata 2)
|
||||||
std::string secret_sector_path; ///< Path to secret sector (New3DS only) (Sysdata 3)
|
std::string secret_sector_path; ///< Path to secret sector (New3DS only) (Sysdata 3)
|
||||||
std::string system_archives_path; ///< Path to system archives.
|
// Note: Sysdata 4 is aes_keys.txt (slot0x25KeyX)
|
||||||
|
std::string config_savegame_path; ///< Path to config savegame (Sysdata 5)
|
||||||
|
|
||||||
// Sysdata 4 is aes_keys.db (slot0x25KeyX)
|
std::string system_archives_path; ///< Path to system archives.
|
||||||
};
|
};
|
||||||
|
|
||||||
class SDMCImporter {
|
class SDMCImporter {
|
||||||
|
|||||||
@@ -121,12 +121,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual bool Extract(std::string path) const = 0;
|
virtual bool Extract(std::string path) const = 0;
|
||||||
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* Gets the ArchiveFormatInfo of this archive, used for writing the archive metadata.
|
|
||||||
*/
|
|
||||||
virtual ArchiveFormatInfo GetFormatInfo() const = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the index-th file in the file entry table to a certain path. (The path does not
|
* Extracts the index-th file in the file entry table to a certain path. (The path does not
|
||||||
* contain the file name).
|
* contain the file name).
|
||||||
@@ -146,6 +140,12 @@ protected:
|
|||||||
*/
|
*/
|
||||||
bool WriteMetadata(const std::string& path) const;
|
bool WriteMetadata(const std::string& path) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Gets the ArchiveFormatInfo of this archive, used for writing the archive metadata.
|
||||||
|
*/
|
||||||
|
virtual ArchiveFormatInfo GetFormatInfo() const = 0;
|
||||||
|
|
||||||
bool is_good = false;
|
bool is_good = false;
|
||||||
FATHeader header;
|
FATHeader header;
|
||||||
FileSystemInformation fs_info;
|
FileSystemInformation fs_info;
|
||||||
|
|||||||
Reference in New Issue
Block a user