Compare commits

..

8 Commits

Author SHA1 Message Date
ZeroSkill
4058c9c5f3
Fix install destination detection for CXI-based game files (#953)
Fixces #952
* Fix install destination detection
* fix NCCH extheader reading logic
* check for empty mountpath on title uninstallation
2026-05-24 13:40:59 +02:00
ZeroSkill
f477da087c
Add support for decrypting gamecart saves (#963)
This also fixes #958
Original commits:
* initial work on cart save crypto/wearleveling
* add support for n3ds only save crypto
* use existing crc16 impl for save blockmap
* fix missing aeskey select for card2 save decrypt
* fix old/new cart media being swapped
* fix 1 MiB save chips / blockmap type 2
* move cart save wearleveling/crypto to separate file
* try 2nd wearlevel header on fail + minor fixes

* save_ctr: zero out savectx before reading fallback header
2026-05-23 12:13:22 +02:00
GitHub Actions
bba9f054da Automatic translation import 2026-05-15 02:26:22 +00:00
d0k3
6097fa2754 Fix sporadic issues in RemoveBDRIEntry
This fixes #857
2026-05-10 12:30:00 +02:00
Pk11
321d14ce7b CI: Fix a couple of minor bugs 2026-05-09 12:25:41 +02:00
rosaage
e7fba495dc Change order of issuers for DEV. Fixes #959 2026-05-09 12:23:49 +02:00
GitHub Actions
5793231566 Automatic translation import 2026-04-15 01:46:15 +00:00
GitHub Actions
e53b03b854 Automatic translation import 2026-04-01 01:50:52 +00:00
18 changed files with 887 additions and 190 deletions

View File

@ -8,7 +8,12 @@ jobs:
container: devkitpro/devkitarm container: devkitpro/devkitarm
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup environment
run: git config --global safe.directory '*'
- name: Fix apt sources - name: Fix apt sources
run: | run: |

View File

@ -22,7 +22,7 @@ jobs:
- name: Install tools - name: Install tools
run: | run: |
apt-get update apt-get update
apt-get install python3 python3-pip -y apt-get install python3 python3-pip p7zip-full -y
python3 -m pip install --upgrade pip setuptools --break-system-packages python3 -m pip install --upgrade pip setuptools --break-system-packages
python3 -m pip install cryptography git+https://github.com/TuxSH/firmtool.git --break-system-packages python3 -m pip install cryptography git+https://github.com/TuxSH/firmtool.git --break-system-packages

View File

@ -40,6 +40,8 @@ extern "C" {
#define AES_CNT_FLUSH_WRITE 0x00000400u #define AES_CNT_FLUSH_WRITE 0x00000400u
#define AES_CNT_CTRNAND_MODE (AES_CTR_MODE | AES_CNT_INPUT_ORDER | AES_CNT_OUTPUT_ORDER | AES_CNT_INPUT_ENDIAN | AES_CNT_OUTPUT_ENDIAN) #define AES_CNT_CTRNAND_MODE (AES_CTR_MODE | AES_CNT_INPUT_ORDER | AES_CNT_OUTPUT_ORDER | AES_CNT_INPUT_ENDIAN | AES_CNT_OUTPUT_ENDIAN)
// gamecard save crypto and NAND crypto use the same options for AES-CTR.
#define AES_CNT_CART_SAVE_MODE AES_CNT_CTRNAND_MODE
#define AES_CNT_TWLNAND_MODE AES_CTR_MODE #define AES_CNT_TWLNAND_MODE AES_CTR_MODE
#define AES_CNT_TITLEKEY_DECRYPT_MODE (AES_CBC_DECRYPT_MODE | AES_CNT_INPUT_ORDER | AES_CNT_OUTPUT_ORDER | AES_CNT_INPUT_ENDIAN | AES_CNT_OUTPUT_ENDIAN) #define AES_CNT_TITLEKEY_DECRYPT_MODE (AES_CBC_DECRYPT_MODE | AES_CNT_INPUT_ORDER | AES_CNT_OUTPUT_ORDER | AES_CNT_INPUT_ENDIAN | AES_CNT_OUTPUT_ENDIAN)
#define AES_CNT_TITLEKEY_ENCRYPT_MODE (AES_CBC_ENCRYPT_MODE | AES_CNT_INPUT_ORDER | AES_CNT_OUTPUT_ORDER | AES_CNT_INPUT_ENDIAN | AES_CNT_OUTPUT_ENDIAN) #define AES_CNT_TITLEKEY_ENCRYPT_MODE (AES_CBC_ENCRYPT_MODE | AES_CNT_INPUT_ORDER | AES_CNT_OUTPUT_ORDER | AES_CNT_INPUT_ENDIAN | AES_CNT_OUTPUT_ENDIAN)

View File

@ -288,15 +288,17 @@ static u32 RemoveBDRIEntry(const BDRIFsHeader* fs_header, const u32 fs_header_of
if (BDRIWrite(fht_offset + hash_bucket * sizeof(u32), sizeof(u32), &(file_entry.hash_bucket_next_index)) != FR_OK) if (BDRIWrite(fht_offset + hash_bucket * sizeof(u32), sizeof(u32), &(file_entry.hash_bucket_next_index)) != FR_OK)
return 1; return 1;
} else { } else {
u32 prev_hash_index = 0;
do { do {
if (index_hash == 0) // This shouldn't happen if the entry was properly added if (index_hash == 0) // This shouldn't happen if the entry was properly added
break; break;
prev_hash_index = index_hash;
if (BDRIRead(fet_offset + index_hash * sizeof(TdbFileEntry) + 0x28, sizeof(u32), &index_hash) != FR_OK) if (BDRIRead(fet_offset + index_hash * sizeof(TdbFileEntry) + 0x28, sizeof(u32), &index_hash) != FR_OK)
return 1; return 1;
} while (index_hash != index); } while (index_hash != index);
if ((index_hash != 0) && BDRIWrite(fet_offset + index_hash * sizeof(TdbFileEntry) + 0x28, sizeof(u32), &(file_entry.hash_bucket_next_index)) != FR_OK) if ((prev_hash_index != 0) && BDRIWrite(fet_offset + prev_hash_index * sizeof(TdbFileEntry) + 0x28, sizeof(u32), &(file_entry.hash_bucket_next_index)) != FR_OK)
return 1; return 1;
} }
@ -322,16 +324,26 @@ static u32 RemoveBDRIEntry(const BDRIFsHeader* fs_header, const u32 fs_header_of
return 1; return 1;
} while (getfatindex(fat_entry[1]) != 0); } while (getfatindex(fat_entry[1]) != 0);
fat_entry[1] |= next_free_index; // Bug fix: use buildfatuv to explicitly clear Bit 31 (the multi-block flag).
// If the tail of the freed chain is a multi-block node start, Bit 31 is already
// set in fat_entry[1]. A plain |= would keep it set, corrupting the free list.
fat_entry[1] = buildfatuv(next_free_index, false);
if ((BDRIWrite(fat_offset + fat_index * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK) || if (BDRIWrite(fat_offset + fat_index * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
(BDRIRead(fat_offset + next_free_index * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK)) return 1;
// Bug fix: guard against next_free_index == 0 (freed entry was the last free
// block). Without this guard, FAT[0] (the free-list sentinel) gets corrupted
// by a stray back-pointer write.
if (next_free_index != 0) {
if (BDRIRead(fat_offset + next_free_index * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
return 1; return 1;
fat_entry[0] = buildfatuv(fat_index, false); fat_entry[0] = buildfatuv(fat_index, false);
if (BDRIWrite(fat_offset + next_free_index * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK) if (BDRIWrite(fat_offset + next_free_index * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
return 1; return 1;
}
return 0; return 0;
} }

View File

@ -61,7 +61,7 @@ u32 BuildCiaCert(u8* ciacert) {
}; };
static const char* const retail_issuers[] = {"Root-CA00000003", "Root-CA00000003-XS0000000c", "Root-CA00000003-CP0000000b"}; static const char* const retail_issuers[] = {"Root-CA00000003", "Root-CA00000003-XS0000000c", "Root-CA00000003-CP0000000b"};
static const char* const dev_issuers[] = {"Root-CA00000004", "Root-CA00000004-XS00000009", "Root-CA00000004-CP0000000a"}; static const char* const dev_issuers[] = {"Root-CA00000004", "Root-CA00000004-CP0000000a", "Root-CA00000004-XS00000009"};
size_t size = CIA_CERT_SIZE; size_t size = CIA_CERT_SIZE;
if (BuildRawCertBundleFromCertDb(ciacert, &size, !IS_DEVKIT ? retail_issuers : dev_issuers, 3) || if (BuildRawCertBundleFromCertDb(ciacert, &size, !IS_DEVKIT ? retail_issuers : dev_issuers, 3) ||

View File

@ -33,7 +33,8 @@ typedef struct {
u8 sector_zero_offset[0x4]; u8 sector_zero_offset[0x4];
u8 partition_flags[8]; u8 partition_flags[8];
u8 partitionId_table[8][8]; u8 partitionId_table[8][8];
u8 reserved[0x30]; u8 reserved[0x2F];
u8 extra_save_keysel;
} PACKED_STRUCT NcsdHeader; } PACKED_STRUCT NcsdHeader;
u32 ValidateNcsdHeader(NcsdHeader* header); u32 ValidateNcsdHeader(NcsdHeader* header);

View File

@ -80,8 +80,9 @@ u32 GetCartInfoString(char* info, size_t info_size, CartData* cdata) {
"Product Code : %.10s\n" "Product Code : %.10s\n"
"Revision : %lu\n" "Revision : %lu\n"
"Cart ID : %08lX\n" "Cart ID : %08lX\n"
"Cart ID2 : %08lX\n"
"Platform : %s\n", "Platform : %s\n",
ncsd->mediaId, ncch->productcode, cdata_i->rom_version, cdata_i->cart_id, ncsd->mediaId, ncch->productcode, cdata_i->rom_version, cdata_i->cart_id, cdata_i->cart_id2,
(ncch->flags[4] == 0x2) ? "N3DS" : "O3DS"); (ncch->flags[4] == 0x2) ? "N3DS" : "O3DS");
} else if (cdata->cart_type & CART_NTR) { } else if (cdata->cart_type & CART_NTR) {
CartDataNtrTwl* cdata_i = (CartDataNtrTwl*)cdata; CartDataNtrTwl* cdata_i = (CartDataNtrTwl*)cdata;
@ -220,9 +221,10 @@ u32 InitCartRead(CartData* cdata) {
memcpy(priv_header + 0x44, &(cdata->cart_id2), 4); memcpy(priv_header + 0x44, &(cdata->cart_id2), 4);
memset(priv_header + 0x48, 0xFF, 8); memset(priv_header + 0x48, 0xFF, 8);
// save data bool is_card2 = cdata->cart_id & 0x8000000;
u32 card2_offset = getle32(cdata->header + 0x200); u32 card2_offset = getle32(cdata->header + 0x200);
if (card2_offset != 0xFFFFFFFF) {
if (is_card2 && card2_offset != 0xFFFFFFFF) {
cdata->save_type = CARD_SAVE_CARD2; cdata->save_type = CARD_SAVE_CARD2;
cdata->save_size = GetCtrCartSaveSize(cdata); cdata->save_size = GetCtrCartSaveSize(cdata);
// Sanity checks // Sanity checks
@ -433,11 +435,11 @@ u32 ReadCartPrivateHeader(void* buffer, u64 offset, u64 count, CartData* cdata)
} }
u32 ReadCartInfo(u8* buffer, u64 offset, u64 count, CartData* cdata) { u32 ReadCartInfo(u8* buffer, u64 offset, u64 count, CartData* cdata) {
char info[256]; char info[301];
u32 len; u32 len;
GetCartInfoString(info, sizeof(info), cdata); GetCartInfoString(info, sizeof(info), cdata);
len = strnlen(info, 255); len = strnlen(info, 300);
if (offset >= len) return 0; if (offset >= len) return 0;
if (offset + count > len) count = len - offset; if (offset + count > len) count = len - offset;

View File

@ -211,10 +211,12 @@ void Cart_Secure_Init(u32 *buf, u32 *out)
CTR_SendCommand(C5_cmd, 0, 1, 0x100002C, NULL); CTR_SendCommand(C5_cmd, 0, 1, 0x100002C, NULL);
} }
for (int i = 0; i < 5; ++i) { // Process9 (or at least the latest version of it, in NATIVE_FIRM v32673) does not use this in its init routine.
CTR_SendCommand(A2_cmd, 4, 1, 0x701002C, &test); // leaving this in adds an unnecessary 2.4-2.6s of init time.
ARM_WaitCycles(0xF0000 * 8); //for (int i = 0; i < 5; ++i) {
} // CTR_SendCommand(A2_cmd, 4, 1, 0x701002C, &test);
// ARM_WaitCycles(0xF0000 * 8);
//}
} }
void Cart_Dummy(void) { void Cart_Dummy(void) {

View File

@ -0,0 +1,490 @@
#include "aes.h"
#include "gamecart.h"
#include "ncch.h"
#include "ncsd.h"
#include "crc16.h"
#include "sha.h"
#include "types.h"
#include "unittype.h"
#include <string.h>
typedef struct SaveBlockmapHeader {
u32 flushcount; // how many times the blockmap was entirely rewritten
u32 common_min_remap_count; // every physical sector in the flash has been rewritten at least `common_min_remap_count` times
// that is, the total remap count for physical sector x representing virtual sector n = blockmap[n].remap_count + common_min_remap_count
} SaveBlockmapHeader;
typedef struct SaveBlockmapEntryV1 {
struct {
u8 phys_sec : 7; // physical sector representing this virtual sector
u8 used : 1; // whether or not this virtual sector is in use
};
u8 remap_count; // amount of times phys_sec has been rewritten
u8 checksums[8]; // checksum values (if applicable) for each 0x200 sized sector in the full 0x1000-sized physical flash sector this structure covers
} SaveBlockmapEntryV1;
typedef struct SaveBlockmapEntryV2 {
struct {
u8 remap_count : 7; // amount of times phys_sec has been rewritten
u8 used : 1; // whether or not this virtual sector is in use
};
u8 phys_sec; // physical sector representing this virtual sector
} SaveBlockmapEntryV2;
typedef struct SaveBlockmapEntry {
u8 remap_count; // how many times phys_sec was remapped (written to)
u8 used; // whether or not this virtual sector is used
u8 phys_sec; // the physical sector this virtual sector is mapped to
u8 checksums[8]; // checksum values (if applicable) for each 0x200 sized sector in the full 0x1000-sized physical flash sector this structure covers
} SaveBlockmapEntry;
typedef struct SaveJournalEntryData
{
u8 src_virt_sec; // virtual sector that is being remapped
u8 dst_virt_sec; // virtual sector whose corresponding physical sector was used to remap src_virt_sec to
u8 dst_phys_sec; // the physical sector of dst_virt_sec the data of src_virt_sec now resides in
u8 src_phys_sec; // the previous physical sector where the data in src_virt_sec resided in before the remapping
u8 new_dst_remap_count; // the new remap count (including this remap write) of the destination physical sector
u8 src_remap_count; // the remap count of the source physical sector (unchanged)
u8 new_dst_checksums[8]; // checksums for the data that was written to the destination physical sector, pending to be written to blockmap
} SaveJournalEntryData;
typedef struct SaveJournalEntry
{
SaveJournalEntryData main;
SaveJournalEntryData backup; // compared to main to check for integrity before applying to blockmap
u32 pad;
} SaveJournalEntry;
typedef enum CardSaveWearLevelingType {
CARD_SAVE_WEAR_LEVELING_V1 = 10,
CARD_SAVE_WEAR_LEVELING_V2 = 2,
CARD_SAVE_WEAR_LEVELING_NONE = -1,
} CardSaveWearLevelingType;
typedef enum CardSaveCryptoType {
CARD_SAVE_CRYPTO_V0 = 0,
CARD_SAVE_CRYPTO_V1 = 1,
CARD_SAVE_CRYPTO_V1_N3DS = 2,
CARD_SAVE_CRYPTO_V2 = 3,
CARD_SAVE_CRYPTO_INVALID = 0x7FFFFFFF,
} CardSaveCryptoType;
typedef enum CardSaveCryptoKeyslot {
CARD_SAVE_CMAC_KEYSLOT_O3DS = 0x33,
CARD_SAVE_CMAC_KEYSLOT_N3DS = 0x19,
CARD_SAVE_CRYPTO_KEYSLOT_O3DS = 0x37,
CARD_SAVE_CRYPTO_KEYSLOT_N3DS = 0x1A,
} CardSaveCryptoKeyslot;
typedef struct CartSaveContext {
// wear leveling
/* min. 87, max 118 journal entries */
SaveJournalEntry journal[118];
/* max case is header + 127 v1 blockmap entries + crc16 */
u8 blockmap[sizeof(SaveBlockmapHeader) + 127 * sizeof(SaveBlockmapEntryV1) + 2];
CardSaveWearLevelingType type;
u32 num_journal_entries; // number of journal entries
u32 num_blockmap_sectors; // number of sectors covered by the blockmap (nsectors - 1)
u32 blockmap_size; // size of the blockmap, in bytes
u32 logical_size; // actual number of available data sectors for the DISA save image (nsectors - 2)
// crypto
u32 crypto_type;
bool repeating_ctr;
} CartSaveContext;
static CartSaveContext savectx;
// wear leveling
static void SetBlockmapEntry(u32 index, SaveBlockmapEntry *in_entry) {
u8 *base_ptr = &savectx.blockmap[sizeof(SaveBlockmapHeader)];
if (savectx.type == CARD_SAVE_WEAR_LEVELING_V2) {
SaveBlockmapEntryV2 *entry = &((SaveBlockmapEntryV2 *)base_ptr)[index];
entry->phys_sec = in_entry->phys_sec;
entry->remap_count = in_entry->remap_count;
entry->used = in_entry->used;
} else {
SaveBlockmapEntryV1 *entry = &((SaveBlockmapEntryV1 *)base_ptr)[index];
entry->phys_sec = in_entry->phys_sec;
entry->remap_count = in_entry->remap_count;
entry->used = in_entry->used;
memcpy(entry->checksums, in_entry->checksums, sizeof(entry->checksums));
}
}
static SaveBlockmapEntry GetBlockmapEntry(u32 index) {
u8 *base_ptr = &savectx.blockmap[sizeof(SaveBlockmapHeader)];
if (savectx.type == CARD_SAVE_WEAR_LEVELING_V2) {
SaveBlockmapEntryV2 *entries = (SaveBlockmapEntryV2 *)base_ptr;
return (SaveBlockmapEntry) { .checksums = { 0 }, .phys_sec = entries[index].phys_sec, .remap_count = entries[index].remap_count, .used = entries[index].used };
} else {
SaveBlockmapEntryV1 *entries = (SaveBlockmapEntryV1 *)base_ptr;
SaveBlockmapEntry out = (SaveBlockmapEntry) { .phys_sec = entries[index].phys_sec, .remap_count = entries[index].remap_count, .used = entries[index].used };
memcpy(out.checksums, entries[index].checksums, sizeof(out.checksums));
return out;
}
}
static void ApplyJournalEntryToBlockmap(SaveJournalEntry *entry) {
SaveBlockmapEntry ent = {
.used = 1,
.phys_sec = entry->main.dst_phys_sec,
.remap_count = entry->main.new_dst_remap_count
};
memcpy(ent.checksums, entry->main.new_dst_checksums, sizeof(ent.checksums));
// remap the virtual sector to the new physical sector
SetBlockmapEntry(entry->main.src_virt_sec, &ent);
// unless an actual remap didn't occur, we must mark (the physical sector
// whose virtual sector number we used for remapping this virtual sector to)
// as free and ready to use for another remap
if (entry->main.dst_phys_sec != entry->main.src_phys_sec) {
ent.used = 0;
ent.phys_sec = entry->main.src_phys_sec;
ent.remap_count = entry->main.src_remap_count;
memset(ent.checksums, 0, sizeof(ent.checksums));
SetBlockmapEntry(entry->main.dst_virt_sec, &ent);
}
}
static u32 InitSaveWearLeveling(CartData *cdata, u32 header_offset) {
// CARD2 does not implement wear leveling for the writable portion of the "ROM"
if (cdata->cart_id & 0x8000000) {
savectx.type = CARD_SAVE_WEAR_LEVELING_NONE;
return 0;
}
// SPI flash savedata implements wear leveling
u32 num_physical_sectors = cdata->save_size / 0x1000;
// for some reason, the blockmap also covers the sector for the backup blockmap + journal header,
// despite the logical size calculation (see below) removing this sector from the available logical space
savectx.num_blockmap_sectors = num_physical_sectors - 1;
// the blockmap + journal header (combined size 0x1000) exists twice, the first being the main one and the second being the "backup" one.
// therefore, the actual number of usable data sectors for save data is num_sectors - 2.
savectx.logical_size = (num_physical_sectors - 2) * 0x1000;
if (num_physical_sectors > 128) {
// V2, header + blockmap_entries_v2[511] + crc
savectx.blockmap_size = 0x400; // fixed size
savectx.type = CARD_SAVE_WEAR_LEVELING_V2;
} else {
// V1, header + blockmap_entries_v1[n] + crc
savectx.blockmap_size = sizeof(SaveBlockmapHeader) + savectx.num_blockmap_sectors * sizeof(SaveBlockmapEntryV1) + sizeof(u16);
savectx.type = CARD_SAVE_WEAR_LEVELING_V1;
}
// TODO: add CMAC verification ability for DISA
if (ReadCartSave(savectx.blockmap, header_offset, savectx.blockmap_size, cdata) != 0)
return 1;
u32 journal_size = 0x1000 - savectx.blockmap_size;
savectx.num_journal_entries = journal_size / sizeof(SaveJournalEntry);
if (ReadCartSave((u8 *)savectx.journal, header_offset + savectx.blockmap_size, journal_size, cdata) != 0)
return 1;
u8 *crcLoc = &savectx.blockmap[savectx.blockmap_size - 2];
u16 expected_bmap_crc = crcLoc[0] | crcLoc[1] << 8;
u16 calc_bmap_crc = crc16_quick(savectx.blockmap, savectx.blockmap_size - 2);
if (expected_bmap_crc != calc_bmap_crc) {
return 1;
}
u32 journal_idx = 0;
for (; journal_idx < savectx.num_journal_entries; journal_idx++) {
SaveJournalEntry *ent = &savectx.journal[journal_idx];
// journal entry should point to a valid physical sector
if (ent->main.dst_phys_sec) {
// reached the end of the journal
if (ent->main.src_virt_sec >= savectx.num_blockmap_sectors)
break;
ApplyJournalEntryToBlockmap(ent);
}
}
u8 blank[sizeof(SaveJournalEntry)];
memset(blank, 0xFF, sizeof(blank));
u32 num_bad_journal_entries = 0;
for (; journal_idx < savectx.num_journal_entries; journal_idx++) {
num_bad_journal_entries += memcmp((u8 *)&savectx.journal[journal_idx], blank, sizeof(SaveJournalEntry)) != 0;
}
if (num_bad_journal_entries) {
return 1;
}
return 0;
}
// crypto
static u32 InitCtrCardSaveCryptoKey(CartData *cdata) {
NcsdHeader *ncsd = (NcsdHeader *) (void *) cdata->header;
// save data crypto (if supported)
u32 save_media_old = ncsd->partition_flags[7];
u32 save_media_new = ncsd->partition_flags[3];
u8 save_crypto_keysel_base = ncsd->partition_flags[1];
u8 save_crypto_keysel_extra = ncsd->extra_save_keysel;
s32 save_crypto_keysel = 0;
bool is_card2 = cdata->cart_id & 0x8000000;
bool supported_save_crypto =
(save_media_old == 0 && save_media_new == 0) || /* SPI save flash (very old carts) */
(save_media_old == 1) || /* old SPI flash */
(save_media_old == 0 && save_media_new == 1) || /* newer SPI flash */
is_card2 /* CARD2 */;
// skip everything if there isn't save data or its crypto is not supported
if (cdata->save_type == CARD_SAVE_NONE || !supported_save_crypto) {
savectx.crypto_type = CARD_SAVE_CRYPTO_INVALID;
return 1;
}
if (save_media_old == 0 && save_media_new == 0 && !is_card2)
savectx.repeating_ctr = true;
if (is_card2)
save_crypto_keysel = save_crypto_keysel_base + save_crypto_keysel_extra;
else if (save_media_old != 0)
save_crypto_keysel = 0;
else if (save_media_new != 0)
save_crypto_keysel = save_crypto_keysel_base + save_crypto_keysel_extra;
else
save_crypto_keysel = -1;
save_crypto_keysel += 1;
u8 save_key_y[16] = { 0 };
struct {
NcchHeader ncch;
NcchExtHeader exthdr;
} hdrs;
ReadCartBytes(&hdrs, ncsd->partitions[0].offset * NCSD_MEDIA_UNIT, sizeof(hdrs), cdata, false);
DecryptNcch(&hdrs.exthdr, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), &hdrs.ncch, NULL);
u8 *cart_unique_id = cdata->header + 0x4000;
switch (save_crypto_keysel) {
case 0: // "backup security version" -1
memcpy(save_key_y, hdrs.exthdr.signature, 8);
memcpy(&save_key_y[8], &cdata->cart_id, 4);
memcpy(&save_key_y[12], &cdata->cart_id2, 4);
savectx.crypto_type = CARD_SAVE_CRYPTO_V0;
break;
case 1: // "backup security version" 0
case 11: // "backup security version" 10
{
// version 10 (11) is not supported on O3DS
if (IS_O3DS && save_crypto_keysel == 11) {
savectx.crypto_type = CARD_SAVE_CRYPTO_INVALID;
return 1;
}
u8 tmpbuf[0x48];
u8 hash[0x20];
memcpy(tmpbuf, hdrs.exthdr.signature, 8);
memcpy(&tmpbuf[8], cart_unique_id, 0x40);
sha_quick(hash, tmpbuf, 0x48, SHA256_MODE);
memcpy(save_key_y, hash, 16);
savectx.crypto_type = save_crypto_keysel == 11 ? CARD_SAVE_CRYPTO_V1_N3DS : CARD_SAVE_CRYPTO_V1;
break;
}
case 2: // "backup security version" 1
{
static bool save60KeyYSetup = false;
if (!save60KeyYSetup) {
static const u8 save60KeyY[16] = { 0xC3, 0x69, 0xBA, 0xA2, 0x1E, 0x18, 0x8A, 0x88, 0xA9, 0xAA, 0x94, 0xE5, 0x50, 0x6A, 0x9F, 0x16 };
setup_aeskeyY(0x2F, save60KeyY);
save60KeyYSetup = true;
}
u8 hash[0x20];
u8 tmpbuf[0x70];
u32 in_ncch_offset = hdrs.ncch.offset_exefs * NCCH_MEDIA_UNIT + 0x1E0;
ReadCartBytes(&hash, ncsd->partitions[0].offset * NCSD_MEDIA_UNIT + in_ncch_offset, sizeof(hash), cdata, false);
DecryptNcch(hash, in_ncch_offset, sizeof(hash), &hdrs.ncch, NULL);
memcpy(tmpbuf, hdrs.exthdr.signature, 8);
memcpy(&tmpbuf[8], cart_unique_id, 0x40);
memcpy(&tmpbuf[0x48], &hdrs.ncch.programId, 8);
memcpy(&tmpbuf[0x50], hash, 0x20);
sha_quick(hash, tmpbuf, 0x70, SHA256_MODE);
use_aeskey(0x2F);
aes_cmac(hash, save_key_y, 2);
savectx.crypto_type = CARD_SAVE_CRYPTO_V2;
break;
}
default:
savectx.crypto_type = CARD_SAVE_CRYPTO_INVALID;
return 1;
};
u32 cmac_keyslot = savectx.crypto_type == CARD_SAVE_CRYPTO_V1_N3DS ? CARD_SAVE_CMAC_KEYSLOT_N3DS : CARD_SAVE_CMAC_KEYSLOT_O3DS;
u32 crypto_keyslot = savectx.crypto_type == CARD_SAVE_CRYPTO_V1_N3DS ? CARD_SAVE_CRYPTO_KEYSLOT_N3DS : CARD_SAVE_CRYPTO_KEYSLOT_O3DS;
setup_aeskeyY(cmac_keyslot, save_key_y); // savedata CMAC key
setup_aeskeyY(crypto_keyslot, save_key_y); // savedata crypto key
return 0;
}
u32 InitCtrCardSave(CartData *cdata) {
// the wear leveling header exists twice:
// the one at 0x0 is the main one
// the one at 0x1000 is used as a failsafe if the one above is corrupt
if (InitSaveWearLeveling(cdata, 0) != 0) {
memset(&savectx, 0, sizeof(savectx));
if (InitSaveWearLeveling(cdata, 0x1000) != 0)
return 1;
}
if (InitCtrCardSaveCryptoKey(cdata) != 0)
return 1;
return 0;
}
static u32 ReadDecryptedCard1Save(u8 *buffer, u64 offset, u64 count, CartData* cdata) {
if (offset >= savectx.logical_size) return 1;
if (offset + count > savectx.logical_size) return 1;
u32 first_sector = offset / 0x1000;
u32 last_sector = (offset + count - 1) / 0x1000;
u32 outbuf_offset = 0;
SaveBlockmapEntry ent;
u8 sectorbuf[0x1000];
u8 ctr[16];
memset(ctr, 0, sizeof(ctr));
u32 crypto_keyslot = savectx.crypto_type == CARD_SAVE_CRYPTO_V1_N3DS ? CARD_SAVE_CRYPTO_KEYSLOT_N3DS : CARD_SAVE_CRYPTO_KEYSLOT_O3DS;
use_aeskey(crypto_keyslot);
// the minimum one can read from the flash is 4K sectors anyway, and blockmap scatters it, so we do it sector by sector
for (u32 cur_sector = first_sector; cur_sector < last_sector + 1; cur_sector++) {
ent = GetBlockmapEntry(cur_sector);
// where in this sector we need to start reading data from
u32 in_sector_start = cur_sector == first_sector ? offset % 0x1000 : 0;
// how much data we can read in this sector assuming the start offset above
u32 in_sector_size = 0x1000 - in_sector_start;
// how much data we actually need to read from the sector
u32 chunksize = min(count, in_sector_size);
if (!ent.used) {
memset(&sectorbuf, 0xFF, sizeof(sectorbuf));
} else {
if (ReadCartSave(sectorbuf, ent.phys_sec * 0x1000, sizeof(sectorbuf), cdata) != 0)
return 1;
}
if (savectx.repeating_ctr) {
for (u32 cursect = 0; cursect < 8; cursect++) {
memset(ctr, 0, sizeof(ctr));
ctr_decrypt_byte(&sectorbuf[0x200 * cursect], &sectorbuf[0x200 * cursect], 0x200, 0, AES_CNT_CART_SAVE_MODE, ctr);
}
} else {
ctr_decrypt_byte(sectorbuf, sectorbuf, sizeof(sectorbuf), cur_sector * 0x1000, AES_CNT_CART_SAVE_MODE, ctr);
}
memcpy(&buffer[outbuf_offset], &sectorbuf[in_sector_start], chunksize);
outbuf_offset += chunksize;
count -= chunksize;
offset += chunksize;
}
return 0;
}
static u32 ReadDecryptedCard2Save(u8 *buffer, u64 offset, u64 count, CartData* cdata) {
if (offset >= cdata->save_size) return 1;
if (offset + count > cdata->save_size) count = cdata->save_size - offset;
u32 first_sector = offset / 0x200;
u32 outbuf_offset = 0;
u8 ctr[16];
memset(ctr, 0, sizeof(ctr));
u32 crypto_keyslot = savectx.crypto_type == CARD_SAVE_CRYPTO_V1_N3DS ? CARD_SAVE_CRYPTO_KEYSLOT_N3DS : CARD_SAVE_CRYPTO_KEYSLOT_O3DS;
use_aeskey(crypto_keyslot);
u8 sector_tmp[0x200];
// handle misalignment at the start
u32 in_first_sector_offset = offset % 0x200;
if (in_first_sector_offset) {
u32 sector_remain = 0x200 - in_first_sector_offset;
u32 misalignsize = min(sector_remain, count);
if (ReadCartSave(sector_tmp, first_sector * 0x200, sizeof(sector_tmp), cdata) != 0)
return 1;
ctr_decrypt_byte(sector_tmp, sector_tmp, sizeof(sector_tmp), first_sector * 0x200, AES_CNT_CART_SAVE_MODE, ctr);
memcpy(buffer, &sector_tmp[in_first_sector_offset], misalignsize);
outbuf_offset += misalignsize;
offset += misalignsize;
count -= misalignsize;
}
// offset is now aligned, size may still not be, though
u32 aligned_size = count & ~(0x200-1);
if (aligned_size) {
if (ReadCartSave(&buffer[outbuf_offset], offset, aligned_size, cdata) != 0)
return 1;
ctr_decrypt_byte(&buffer[outbuf_offset], &buffer[outbuf_offset], aligned_size, offset, AES_CNT_CART_SAVE_MODE, ctr);
count -= aligned_size;
offset += aligned_size;
outbuf_offset += aligned_size;
}
// only ending misalignment remains, if applicable
if (count) {
if (ReadCartSave(sector_tmp, offset, sizeof(sector_tmp), cdata) != 0)
return 1;
ctr_decrypt_byte(sector_tmp, sector_tmp, sizeof(sector_tmp), offset, AES_CNT_CART_SAVE_MODE, ctr);
memcpy(&buffer[outbuf_offset], &sector_tmp, count);
}
return 0;
}
u32 ReadDecryptedCtrCardSave(u8* buffer, u64 offset, u64 count, CartData* cdata) {
switch (cdata->save_type) {
case CARD_SAVE_SPI:
return ReadDecryptedCard1Save(buffer, offset, count, cdata);
break;
case CARD_SAVE_CARD2:
return ReadDecryptedCard2Save(buffer, offset, count, cdata);
break;
default:
return 1;
}
}

View File

@ -0,0 +1,6 @@
#pragma once
#include "gamecart.h"
u32 InitCtrCardSave(CartData *);
u32 ReadDecryptedCtrCardSave(u8* buffer, u64 offset, u64 count, CartData* cdata);

View File

@ -3079,8 +3079,13 @@ u32 GodMode(int entrypoint) {
if (!InitVCartDrive() && (pad_state & CART_INSERT) && if (!InitVCartDrive() && (pad_state & CART_INSERT) &&
(curr_drvtype & DRV_CART)) // reinit virtual cart drive (curr_drvtype & DRV_CART)) // reinit virtual cart drive
ShowPrompt(false, "%s", STR_CART_INIT_FAILED); ShowPrompt(false, "%s", STR_CART_INIT_FAILED);
if (!(*current_path) || (curr_drvtype & DRV_CART)) // unmount any gamecart image if the gamecart was removed
if (pad_state & CART_EJECT && *GetMountPath() && DriveType(GetMountPath()) & DRV_CART) {
InitImgFS(NULL);
}
if (!(*current_path) || (curr_drvtype & DRV_CART)) {
GetDirContents(current_dir, current_path); // refresh dir contents GetDirContents(current_dir, current_path); // refresh dir contents
}
} else if (pad_state & SD_INSERT) { } else if (pad_state & SD_INSERT) {
while (!InitSDCardFS() && ShowPrompt(true, "%s", STR_INITIALIZING_SD_FAILED_RETRY)); while (!InitSDCardFS() && ShowPrompt(true, "%s", STR_INITIALIZING_SD_FAILED_RETRY));
ClearScreenF(true, true, COLOR_STD_BG); ClearScreenF(true, true, COLOR_STD_BG);

View File

@ -1046,6 +1046,143 @@ u32 VerifyTicketFile(const char* path) {
return res; return res;
} }
u32 LoadNcchFromTmdFile(const char *path, NcchHeader *ncch, NcchExtHeader *extheader) {
char pathstr[UTF_BUFFER_BYTESIZE(32)];
TruncateString(pathstr, path, 32, 8);
// content path
char path_content[256];
char *name_content; // points to content file name (%08lx)
strncpy(path_content, path, 256);
path_content[255] = '\0';
name_content = strrchr(path_content, '/');
if (!name_content) return 1;
name_content++;
TitleMetaData *tmd = (TitleMetaData*) malloc(TMD_SIZE_MAX);
TmdContentChunk *content_list = (TmdContentChunk*) (tmd + 1);
if ((LoadTmdFile(tmd, path) != 0) || (VerifyTmd(tmd) != 0)) {
ShowPrompt(false, "%s\n%s", pathstr, STR_ERROR_TMD_PROBABLY_CORRUPTED);
free(tmd);
return 1;
}
u8 titlekey[0x10] = { 0xFF };
Ticket* ticket = NULL;
if (!((LoadCdnTicketFile(&ticket, path) == 0) ||
((ticket = (Ticket*)malloc(TICKET_COMMON_SIZE), ticket != NULL) &&
(BuildFakeTicket(ticket, tmd->title_id) == 0) &&
(FindTitleKey(ticket, tmd->title_id) == 0))) ||
(GetTitleKey(titlekey, ticket) != 0)) {
ShowPrompt(false, "%s\n%s", pathstr, STR_ERROR_CDN_TITLEKEY_NOT_FOUND);
free(ticket);
free(tmd);
return 1;
}
free(ticket);
u32 content_count = getbe16(tmd->content_count);
u32 res = 0;
bool found = false;
TmdContentChunk chunk = { 0 };
for (u32 i = 0; !res && i < content_count && (i < TMD_MAX_CONTENTS); i++) {
TmdContentChunk *tmp = &(content_list[i]);
if (getbe16(tmp->index) == 0) {
found = true;
memcpy(&chunk, tmp, sizeof(TmdContentChunk));
break;
}
}
free(tmd);
if (!found) {
return 1;
}
// path to content with index 0
snprintf(name_content, 256 - (name_content - path_content), "%08lx", getbe32(chunk.id));
bool encrypted = getbe16(chunk.type) & 0x1;
u8 ctr[16];
GetTmdCtr(ctr, &chunk);
if (fvx_qread(path_content, ncch, 0, sizeof(NcchHeader), NULL) != FR_OK ||
(extheader && (fvx_qread(path_content, extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), NULL) != FR_OK)))
return 1;
if (encrypted) {
DecryptCiaContentSequential(ncch, sizeof(NcchHeader), ctr, titlekey);
if (extheader)
DecryptCiaContentSequential(extheader, sizeof(NcchExtHeader), ctr, titlekey);
}
if (ValidateNcchHeader(ncch) != 0 ||
(extheader && (DecryptNcch(extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), ncch, NULL) != 0)))
return 1;
return 0;
}
u32 LoadNcchFromGameFile(const char* path, NcchHeader* ncch, NcchExtHeader* extheader) {
u64 filetype = IdentifyFileType(path);
if (filetype & GAME_NCCH) {
if ((fvx_qread(path, ncch, 0, sizeof(NcchHeader), NULL) == FR_OK) &&
(ValidateNcchHeader(ncch) == 0) &&
(!extheader || ((fvx_qread(path, extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), NULL) == FR_OK) &&
DecryptNcch(extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), ncch, NULL) == 0))) return 0;
} else if (filetype & GAME_NCSD) {
if ((fvx_qread(path, ncch, NCSD_CNT0_OFFSET, sizeof(NcchHeader), NULL) == FR_OK) &&
(ValidateNcchHeader(ncch) == 0) &&
(!extheader || ((fvx_qread(path, extheader, NCSD_CINFO_OFFSET + NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), NULL) == 0) &&
DecryptNcch(extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), ncch, NULL) == 0))) return 0;
} else if (filetype & GAME_CDNTMD) {
return LoadNcchFromTmdFile(path, ncch, extheader);
} else if (filetype & GAME_CIA) {
CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub));
CiaInfo info;
// load CIA stub from path
if ((LoadCiaStub(cia, path) != 0) ||
(GetCiaInfo(&info, &(cia->header)) != 0)) {
free(cia);
return 1;
}
// decrypt / load NCCH header from first CIA content
u32 ret = 1;
if (getbe16(cia->tmd.content_count)) {
TmdContentChunk* chunk = cia->content_list;
if ((getbe64(chunk->size) < sizeof(NcchHeader)) ||
(fvx_qread(path, ncch, info.offset_content, sizeof(NcchHeader), NULL) != FR_OK) ||
(extheader && (fvx_qread(path, extheader, info.offset_content + NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), NULL) != FR_OK))) {
free(cia);
return 1;
}
if (getbe16(chunk->type) & 0x1) { // decrypt first content header
u8 titlekey[16];
u8 ctr[16];
GetTmdCtr(ctr, chunk);
if (GetTitleKey(titlekey, (Ticket*)&(cia->ticket)) != 0) {
free(cia);
return 1;
}
DecryptCiaContentSequential((void*) ncch, sizeof(NcchHeader), ctr, titlekey);
if (extheader)
DecryptCiaContentSequential((void*) extheader, sizeof(NcchExtHeader), ctr, titlekey);
}
if (ValidateNcchHeader(ncch) == 0 &&
(!extheader || DecryptNcch(extheader, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), ncch, NULL) == 0)) ret = 0;
}
free(cia);
return ret;
}
return 1;
}
u32 VerifyGameFile(const char* path, bool sig_check) { u32 VerifyGameFile(const char* path, bool sig_check) {
u64 filetype = IdentifyFileType(path); u64 filetype = IdentifyFileType(path);
if (filetype & GAME_CIA) if (filetype & GAME_CIA)
@ -1509,7 +1646,28 @@ u32 CryptGameFile(const char* path, bool inplace, bool encrypt, bool restore) {
return ret; return ret;
} }
u32 GetInstallDataDrive(char* drv, u64 tid64, bool to_emunand) { u32 GetTwlInstallDataDrive(char *drv, bool to_emunand) {
drv[0] = to_emunand ? '5' : '2';
drv[1] = ':';
drv[2] = '\0';
return 0;
}
u32 GetSdInstallDataDrive(char *drv, bool to_emunand) {
drv[0] = to_emunand ? 'B' : 'A';
drv[1] = ':';
drv[2] = '\0';
return 0;
}
u32 GetNandInstallDataDrive(char *drv, bool to_emunand) {
drv[0] = to_emunand ? '4' : '1';
drv[1] = ':';
drv[2] = '\0';
return 0;
}
u32 GetInstallDataDriveGeneric(char* drv, u64 tid64, bool to_emunand) {
// check the title id // check the title id
bool to_twl = ((tid64 >> 32) & 0x8000); bool to_twl = ((tid64 >> 32) & 0x8000);
bool to_sd = (!to_twl && !((tid64 >> 32) & 0x10)); bool to_sd = (!to_twl && !((tid64 >> 32) & 0x10));
@ -1517,14 +1675,8 @@ u32 GetInstallDataDrive(char* drv, u64 tid64, bool to_emunand) {
// sanity // sanity
if (!tid64) return 1; if (!tid64) return 1;
// determine the correct drive return to_twl ? GetTwlInstallDataDrive(drv, to_emunand) :
drv[0] = to_emunand ? to_sd ? GetSdInstallDataDrive(drv, to_emunand) : GetNandInstallDataDrive(drv, to_emunand);
(to_twl ? '5' : to_sd ? 'B' : '4') :
(to_twl ? '2' : to_sd ? 'A' : '1');
drv[1] = ':';
drv[2] = '\0';
return 0;
} }
u32 GetInstallDbsPath(char* path, const char* drv, const char* str) { u32 GetInstallDbsPath(char* path, const char* drv, const char* str) {
@ -1628,14 +1780,9 @@ u32 CreateSaveData(const char* drv, u64 tid64, const char* name, u32 save_size,
return 0; return 0;
} }
u32 UninstallGameData(u64 tid64, bool remove_tie, bool remove_ticket, bool remove_save, bool from_emunand) { u32 UninstallGameData(const char *drv, u64 tid64, bool remove_tie, bool remove_ticket, bool remove_save) {
char drv[3];
// check permissions for SysNAND (this includes everything we need) // check permissions for SysNAND (this includes everything we need)
if (!CheckWritePermissions(from_emunand ? "4:" : "1:")) return 1; if (!CheckWritePermissions(drv)) return 1;
// determine the drive
if (GetInstallDataDrive(drv, tid64, from_emunand) != 0) return 1;
// remove data path // remove data path
char path_data[256]; char path_data[256];
@ -1694,7 +1841,7 @@ u32 UninstallGameDataTie(const char* path, bool remove_tie, bool remove_ticket,
u64 tid64; u64 tid64;
const char* mntpath = GetMountPath(); const char* mntpath = GetMountPath();
if (!mntpath) return 1; if (!mntpath || !*mntpath) return 1;
// title.db from emunand? // title.db from emunand?
if ((strncasecmp(mntpath, "B:/dbs/title.db", 16) == 0) || if ((strncasecmp(mntpath, "B:/dbs/title.db", 16) == 0) ||
@ -1705,7 +1852,18 @@ u32 UninstallGameDataTie(const char* path, bool remove_tie, bool remove_ticket,
if (sscanf(path, "T:/%016llx", &tid64) != 1) if (sscanf(path, "T:/%016llx", &tid64) != 1)
return 1; return 1;
return UninstallGameData(tid64, remove_tie, remove_ticket, remove_save, from_emunand); // tid here is in the 00048000... format if it's a TWL title
bool is_twl = ((tid64 >> 32) & 0x8000) == 0x8000;
char drv[3] = { 0xFF, ':', '\0' };
// in all cases except for TWL titles, the uninstall drive is the same as the drive
// containing the .db file that is mounted.
if (is_twl)
drv[0] = from_emunand ? '5' : '2';
else
drv[0] = mntpath[0];
return UninstallGameData(drv, tid64, remove_tie, remove_ticket, remove_save);
} }
u32 LoadEncryptedIconFromCiaTmd(const char* path, void* output, void* hdr, bool cia_meta) { u32 LoadEncryptedIconFromCiaTmd(const char* path, void* output, void* hdr, bool cia_meta) {
@ -2869,14 +3027,51 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) {
return ret; return ret;
} }
u32 GetInstallDataDriveForGameFile(const char *path, u64 filetype, u64 titleid, char *drv, bool to_emunand) {
// CTR filetypes
if (filetype & (GAME_CIA | GAME_NCSD | GAME_NCCH | GAME_CDNTMD)) {
if ((filetype & GAME_CIA) && (titleid >> 32) & 0x8000) {
// only CIAs may be either CTR or TWL titles
GetTwlInstallDataDrive(drv, to_emunand);
} else if ((titleid >> 32) == 0x0004008C || (filetype & GAME_NCSD)) {
// DLC, although it does not contain a CXI, must be installed to SD always
// gamecarts, and backups thereof, are also always installed to SD
GetSdInstallDataDrive(drv, to_emunand);
} else {
// definitely a CTR title
NcchHeader ncch;
NcchExtHeader extheader;
if (LoadNcchFromGameFile(path, &ncch, &extheader) != 0)
return 1;
if (!NCCH_IS_CXI(&ncch)) {
// for CFA, we don't have much to go off of. but CFAs can just be separated by the system title bit,
// as most CFAs are either part of system titles (NAND) or instruction manuals (SD).
if (GetInstallDataDriveGeneric(drv, titleid, to_emunand) != 0)
return 1;
} else {
// for files that are CXIs or files whose first content is a CXI, we can check the exheader for the SD bit
if (extheader.flag & BIT(1))
GetSdInstallDataDrive(drv, to_emunand);
else
GetNandInstallDataDrive(drv, to_emunand);
}
}
} else { // TWL file types (GAME_NDS, GAME_TWLTMD)
GetTwlInstallDataDrive(drv, to_emunand);
}
return 0;
}
u32 InstallGameFile(const char* path, bool to_emunand) { u32 InstallGameFile(const char* path, bool to_emunand) {
char drv[3]; char drv[3];
u64 filetype = IdentifyFileType(path); u64 filetype = IdentifyFileType(path);
u64 tid64 = GetGameFileTitleId(path);
u32 ret = 0; u32 ret = 0;
// find out the destination if (GetInstallDataDriveForGameFile(path, filetype, tid64, drv, to_emunand) != 0)
u64 tid64 = GetGameFileTitleId(path);
if (GetInstallDataDrive(drv, tid64, to_emunand) != 0)
return 1; return 1;
// check dbs // check dbs
@ -2893,7 +3088,7 @@ u32 InstallGameFile(const char* path, bool to_emunand) {
// cleanup content folder before starting install // cleanup content folder before starting install
ShowProgress(0, 0, path); ShowProgress(0, 0, path);
UninstallGameData(tid64, false, false, false, to_emunand); UninstallGameData(drv, tid64, false, false, false);
// install game file // install game file
if (filetype & GAME_CIA) if (filetype & GAME_CIA)
@ -2909,7 +3104,7 @@ u32 InstallGameFile(const char* path, bool to_emunand) {
else ret = 1; else ret = 1;
// cleanup on failed installs, but leave ticket and save untouched // cleanup on failed installs, but leave ticket and save untouched
if (ret != 0) UninstallGameData(tid64, true, false, false, to_emunand); if (ret != 0) UninstallGameData(drv, tid64, true, false, false);
return ret; return ret;
} }
@ -4008,54 +4203,6 @@ u32 BuildSeedInfo(const char* path, bool dump) {
return 0; return 0;
} }
u32 LoadNcchFromGameFile(const char* path, NcchHeader* ncch) {
u64 filetype = IdentifyFileType(path);
if (filetype & GAME_NCCH) {
if ((fvx_qread(path, ncch, 0, sizeof(NcchHeader), NULL) == FR_OK) &&
(ValidateNcchHeader(ncch) == 0)) return 0;
} else if (filetype & GAME_NCSD) {
if ((fvx_qread(path, ncch, NCSD_CNT0_OFFSET, sizeof(NcchHeader), NULL) == FR_OK) &&
(ValidateNcchHeader(ncch) == 0)) return 0;
} else if (filetype & GAME_CIA) {
CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub));
CiaInfo info;
// load CIA stub from path
if ((LoadCiaStub(cia, path) != 0) ||
(GetCiaInfo(&info, &(cia->header)) != 0)) {
free(cia);
return 1;
}
// decrypt / load NCCH header from first CIA content
u32 ret = 1;
if (getbe16(cia->tmd.content_count)) {
TmdContentChunk* chunk = cia->content_list;
if ((getbe64(chunk->size) < sizeof(NcchHeader)) ||
(fvx_qread(path, ncch, info.offset_content, sizeof(NcchHeader), NULL) != FR_OK)) {
free(cia);
return 1;
}
if (getbe16(chunk->type) & 0x1) { // decrypt first content header
u8 titlekey[16];
u8 ctr[16];
GetTmdCtr(ctr, chunk);
if (GetTitleKey(titlekey, (Ticket*)&(cia->ticket)) != 0) {
free(cia);
return 1;
}
DecryptCiaContentSequential((void*) ncch, sizeof(NcchHeader), ctr, titlekey);
}
if (ValidateNcchHeader(ncch) == 0) ret = 0;
}
free(cia);
return ret;
}
return 1;
}
u32 GetGoodName(char* name, const char* path, bool quick) { u32 GetGoodName(char* name, const char* path, bool quick) {
// name should be 128+1 byte // name should be 128+1 byte
@ -4117,7 +4264,7 @@ u32 GetGoodName(char* name, const char* path, bool quick) {
if (LoadTwlMetaData(path_donor, (TwlHeader*) header, if (LoadTwlMetaData(path_donor, (TwlHeader*) header,
quick ? NULL : (TwlIconData*) icon) != 0) type_donor = 0; quick ? NULL : (TwlIconData*) icon) != 0) type_donor = 0;
} else if (type_donor & (GAME_NCSD|GAME_NCCH)) { // CTR (data from NCCH) } else if (type_donor & (GAME_NCSD|GAME_NCCH)) { // CTR (data from NCCH)
if (LoadNcchFromGameFile(path_donor, (NcchHeader*) header) != 0) type_donor = 0; if (LoadNcchFromGameFile(path_donor, (NcchHeader*) header, NULL) != 0) type_donor = 0;
if (!quick && (LoadSmdhFromGameFile(path_donor, (Smdh*) icon) != 0)) quick = true; if (!quick && (LoadSmdhFromGameFile(path_donor, (Smdh*) icon) != 0)) quick = true;
} else if (type_donor & (GAME_CIA|GAME_CDNTMD|GAME_TWLTMD)) { // encrypted } else if (type_donor & (GAME_CIA|GAME_CDNTMD|GAME_TWLTMD)) { // encrypted
type_donor = LoadEncryptedIconFromCiaTmd(path_donor, icon, header, false); type_donor = LoadEncryptedIconFromCiaTmd(path_donor, icon, header, false);

View File

@ -1,7 +1,12 @@
#include "vcart.h" #include "vcart.h"
#include "fsdrive.h"
#include "fsinit.h"
#include "gamecart.h" #include "gamecart.h"
#include "image.h"
#include "save_ctr.h"
#define FAT_LIMIT 0x100000000 #define FAT_LIMIT 0x100000000
#define VFLAG_DECRYPTED_SAVEGAME (1UL<<27)
#define VFLAG_SECURE_AREA_ENC (1UL<<28) #define VFLAG_SECURE_AREA_ENC (1UL<<28)
#define VFLAG_GAMECART_NFO (1UL<<29) #define VFLAG_GAMECART_NFO (1UL<<29)
#define VFLAG_SAVEGAME (1UL<<30) #define VFLAG_SAVEGAME (1UL<<30)
@ -10,6 +15,7 @@
static CartData* cdata = NULL; static CartData* cdata = NULL;
static bool cart_init = false; static bool cart_init = false;
static bool cart_checked = false; static bool cart_checked = false;
static bool enable_dec_ctr_save = false;
u32 InitVCartDrive(void) { u32 InitVCartDrive(void) {
if (!cart_checked) cart_checked = true; if (!cart_checked) cart_checked = true;
@ -19,6 +25,10 @@ u32 InitVCartDrive(void) {
free(cdata); free(cdata);
cdata = NULL; cdata = NULL;
} }
if (cart_init && (cdata->cart_type & CART_CTR)) {
// for compatibility purposes save crypto and wear leveling init are optional
enable_dec_ctr_save = InitCtrCardSave(cdata) == 0;
}
return cart_init ? cdata->cart_id : 0; return cart_init ? cdata->cart_id : 0;
} }
@ -34,7 +44,7 @@ bool ReadVCartDir(VirtualFile* vfile, VirtualDir* vdir) {
vfile->keyslot = 0xFF; // unused vfile->keyslot = 0xFF; // unused
vfile->flags = VFLAG_READONLY; vfile->flags = VFLAG_READONLY;
while (++vdir->index <= 9) { while (++vdir->index <= 10) {
if ((vdir->index == 0) && (cdata->data_size < FAT_LIMIT)) { // standard full rom if ((vdir->index == 0) && (cdata->data_size < FAT_LIMIT)) { // standard full rom
snprintf(vfile->name, 32, "%s.%s", name, ext); snprintf(vfile->name, 32, "%s.%s", name, ext);
vfile->size = cdata->cart_size; vfile->size = cdata->cart_size;
@ -73,11 +83,16 @@ bool ReadVCartDir(VirtualFile* vfile, VirtualDir* vdir) {
vfile->flags |= VFLAG_READONLY; vfile->flags |= VFLAG_READONLY;
} }
return true; return true;
} else if (vdir->index == 8) { // gamecart info } else if ((vdir->index == 8) && enable_dec_ctr_save) {
char info[256]; snprintf(vfile->name, 32, "%s.dec.sav", name);
vfile->size = (cdata->cart_id & 0x8000000) /* card2 */ ? cdata->save_size : cdata->save_size - 0x2000;
vfile->flags = VFLAG_DECRYPTED_SAVEGAME | VFLAG_READONLY /* for now */;
return true;
} else if (vdir->index == 9) { // gamecart info
char info[301];
GetCartInfoString(info, sizeof(info), cdata); GetCartInfoString(info, sizeof(info), cdata);
snprintf(vfile->name, 32, "%s.txt", name); snprintf(vfile->name, 32, "%s.txt", name);
vfile->size = strnlen(info, 255); vfile->size = strnlen(info, 300);
vfile->flags |= VFLAG_GAMECART_NFO; vfile->flags |= VFLAG_GAMECART_NFO;
return true; return true;
} }
@ -94,6 +109,8 @@ int ReadVCartFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count)
return ReadCartPrivateHeader(buffer, foffset, count, cdata); return ReadCartPrivateHeader(buffer, foffset, count, cdata);
else if (vfile->flags & VFLAG_SAVEGAME) else if (vfile->flags & VFLAG_SAVEGAME)
return ReadCartSave(buffer, foffset, count, cdata); return ReadCartSave(buffer, foffset, count, cdata);
else if (vfile->flags & VFLAG_DECRYPTED_SAVEGAME)
return ReadDecryptedCtrCardSave(buffer, foffset, count, cdata);
else if (vfile->flags & VFLAG_GAMECART_NFO) else if (vfile->flags & VFLAG_GAMECART_NFO)
return ReadCartInfo(buffer, foffset, count, cdata); return ReadCartInfo(buffer, foffset, count, cdata);
@ -104,7 +121,15 @@ int ReadVCartFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count)
int WriteVCartFile(const VirtualFile* vfile, const void* buffer, u64 offset, u64 count) { int WriteVCartFile(const VirtualFile* vfile, const void* buffer, u64 offset, u64 count) {
if (!cdata) return -1; if (!cdata) return -1;
if (vfile->flags & VFLAG_SAVEGAME) { if (vfile->flags & VFLAG_SAVEGAME) {
return WriteCartSave(buffer, offset, count, cdata); int res = WriteCartSave(buffer, offset, count, cdata);
if (cdata->cart_type & CART_CTR) {
enable_dec_ctr_save = InitCtrCardSave(cdata) == 0;
if (*GetMountPath() && !enable_dec_ctr_save && (DriveType(GetMountPath()) & DRV_CART) && strstr(GetMountPath(), ".sav")) {
// unmount the virtual DISA archive if user invalidated the encrypted and wear-leveled source
InitImgFS(NULL);
}
}
return res;
} }
return -1; return -1;
} }

View File

@ -191,7 +191,7 @@
"CALCULATE_SHA256": "SHA-256 berechnen", "CALCULATE_SHA256": "SHA-256 berechnen",
"CALCULATE_SHA1": "SHA-1 berechnen", "CALCULATE_SHA1": "SHA-1 berechnen",
"SHOW_FILE_INFO": "Datei-Info anzeigen", "SHOW_FILE_INFO": "Datei-Info anzeigen",
"SHOW_IN_TEXTVIEWER": "Show in Text Editor", "SHOW_IN_TEXTVIEWER": "Im Texteditor anzeigen",
"CALCULATE_CMAC": "CMAC berechnen", "CALCULATE_CMAC": "CMAC berechnen",
"COPY_TO_OUT": "Nach %s kopieren", "COPY_TO_OUT": "Nach %s kopieren",
"DUMP_TO_OUT": "Unter %s dumpen", "DUMP_TO_OUT": "Unter %s dumpen",
@ -778,7 +778,7 @@
"SCRIPTERR_APPLY_IPS_FAILD": "IPS anwenden fehlgeschlagen", "SCRIPTERR_APPLY_IPS_FAILD": "IPS anwenden fehlgeschlagen",
"SCRIPTERR_APPLY_BPS_FAILED": "BPS anwenden fehlgeschlagen", "SCRIPTERR_APPLY_BPS_FAILED": "BPS anwenden fehlgeschlagen",
"SCRIPTERR_APPLY_BPM_FAILED": "BPM anwenden fehlgeschlagen", "SCRIPTERR_APPLY_BPM_FAILED": "BPM anwenden fehlgeschlagen",
"SCRIPTERR_TEXTVIEWER_FAILED": "text editor failed", "SCRIPTERR_TEXTVIEWER_FAILED": "Texteditor fehlgeschlagen",
"SCRIPTERR_BAD_DUMPSIZE": "schlechte Dumpinggröße", "SCRIPTERR_BAD_DUMPSIZE": "schlechte Dumpinggröße",
"SCRIPTERR_CART_INIT_FAIL": "karteninitialisierung fehlgeschlagen", "SCRIPTERR_CART_INIT_FAIL": "karteninitialisierung fehlgeschlagen",
"SCRIPTERR_CART_DUMP_FAILED": "karteninitialisierung fehlgeschlagen", "SCRIPTERR_CART_DUMP_FAILED": "karteninitialisierung fehlgeschlagen",
@ -792,12 +792,12 @@
"SCRIPTERR_UNCLOSED_CONDITIONAL": "ungeschlossene Bedingung", "SCRIPTERR_UNCLOSED_CONDITIONAL": "ungeschlossene Bedingung",
"SCRIPTERR_ERROR_MESSAGE_FAIL": "Fehlermeldung fehlgeschlagen", "SCRIPTERR_ERROR_MESSAGE_FAIL": "Fehlermeldung fehlgeschlagen",
"ERROR_INVALID_TEXT_DATA": "Fehler: Ungültige Textdaten", "ERROR_INVALID_TEXT_DATA": "Fehler: Ungültige Textdaten",
"ERROR_TEXT_FILE_TOO_BIG": "Error: Text file is too large.\nText file size is %u bytes.\nMax file size is %i bytes.", "ERROR_TEXT_FILE_TOO_BIG": "Fehler: Textdatei ist zu groß.\nTextdatei ist %u Bytes.\nMaximale Dateigröße ist %i Bytes.",
"TEXTVIEWER_CONTROLS_DETAILS": "Textviewer-Steuerelemente:\n \n↑↓→←(+R) - Scrollen\nR+Y - Wortumbruch umschalten\nR+X - Gehe zu Zeile #\nB - Beenden\n", "TEXTVIEWER_CONTROLS_DETAILS": "Textviewer-Steuerelemente:\n \n↑↓→←(+R) - Scrollen\nR+Y - Wortumbruch umschalten\nR+X - Gehe zu Zeile #\nB - Beenden\n",
"TEXTEDITOR_CONTROLS_DETAILS": "Text Editor Controls:\n \n↑↓→←(+R) - Scroll\nR+Y - Toggle wordwrap\nR+X - Goto line #\nA - Enter edit mode\nB - Exit\n", "TEXTEDITOR_CONTROLS_DETAILS": "Text-Editor-Steuerelemente:\n \n↑↓→←(+R) - Scrollen\nR+Y - Wortumbruch umschalten\nR+X - zu Zeile #\nA - Bearbeitungsmodus aufrufen\nB - Beenden\n",
"TEXTEDITOR_CONTROLS_KEYBOARD": "Text Editor Controls:\n \n↑↓→←(+R) - Move cursor\nX - Delete char\nA - Insert newline\nL+↑↓→← - Select text\nY - COPY / [+R] CUT\nB - Enter view mode\n", "TEXTEDITOR_CONTROLS_KEYBOARD": "Text-Editor-Bedienelemente:\n \n↑↓→←(+R) - Cursor bewegen\nX - Zeichen löschen\nA - Zeilenumbruch einfügen\nL+↑↓→← - Text auswählen\nY - KOPIEREN / [+R] SCHNEIDEN\nB - Ansichtsmodus aufrufen\n",
"TEXTEDITOR_CONTROLS_CLIPBOARD": "Text Editor Controls:\n \n↑↓→←(+R) - Move cursor\nX - Delete char\nA - Insert newline\nL+↑↓→← - Select text\nY - PASTE / [+R] CLEAR\nB - Enter view mode\n", "TEXTEDITOR_CONTROLS_CLIPBOARD": "Text-Editor-Bedienelemente:\n \n↑↓→←(+R) - Cursor bewegen\nX - Zeichen löschen\nA - Zeilenumbruch einfügen\nL+↑↓→← - Text auswählen\nY - EINFÜGEN / [+R] LÖSCHEN\nB - Ansichtsmodus aufrufen\n",
"TEXT_EDITS_SAVE_CHANGES": "You made text edits.\nWrite changes to file?", "TEXT_EDITS_SAVE_CHANGES": "Sie haben Text bearbeitet.\nÄnderungen in die Datei schreiben?",
"CURRENT_LINE_N_ENTER_NEW_LINE_BELOW": "Aktuelle Zeile: %i\nGeben Sie unten eine neue Zeile ein.", "CURRENT_LINE_N_ENTER_NEW_LINE_BELOW": "Aktuelle Zeile: %i\nGeben Sie unten eine neue Zeile ein.",
"PREVIEW_DISABLED": "(Vorschau deaktiviert)", "PREVIEW_DISABLED": "(Vorschau deaktiviert)",
"PATH_LINE_N_ERR_LINE": "%s\nZeile %lu: %s\n%s", "PATH_LINE_N_ERR_LINE": "%s\nZeile %lu: %s\n%s",

View File

@ -191,7 +191,7 @@
"CALCULATE_SHA256": "Calcular SHA-256", "CALCULATE_SHA256": "Calcular SHA-256",
"CALCULATE_SHA1": "Calcular SHA-1", "CALCULATE_SHA1": "Calcular SHA-1",
"SHOW_FILE_INFO": "Mostrar información del archivo", "SHOW_FILE_INFO": "Mostrar información del archivo",
"SHOW_IN_TEXTVIEWER": "Show in Text Editor", "SHOW_IN_TEXTVIEWER": "Mostrar en el Editor de Texto",
"CALCULATE_CMAC": "Calcular CMAC", "CALCULATE_CMAC": "Calcular CMAC",
"COPY_TO_OUT": "Copiar en %s", "COPY_TO_OUT": "Copiar en %s",
"DUMP_TO_OUT": "Volcar en %s", "DUMP_TO_OUT": "Volcar en %s",
@ -778,7 +778,7 @@
"SCRIPTERR_APPLY_IPS_FAILD": "error al aplicar IPS", "SCRIPTERR_APPLY_IPS_FAILD": "error al aplicar IPS",
"SCRIPTERR_APPLY_BPS_FAILED": "error al aplicar BPS", "SCRIPTERR_APPLY_BPS_FAILED": "error al aplicar BPS",
"SCRIPTERR_APPLY_BPM_FAILED": "error al aplicar BPM", "SCRIPTERR_APPLY_BPM_FAILED": "error al aplicar BPM",
"SCRIPTERR_TEXTVIEWER_FAILED": "text editor failed", "SCRIPTERR_TEXTVIEWER_FAILED": "Error en el editor de texto",
"SCRIPTERR_BAD_DUMPSIZE": "volcado incorrecto", "SCRIPTERR_BAD_DUMPSIZE": "volcado incorrecto",
"SCRIPTERR_CART_INIT_FAIL": "error al iniciar el cartucho", "SCRIPTERR_CART_INIT_FAIL": "error al iniciar el cartucho",
"SCRIPTERR_CART_DUMP_FAILED": "error al volcar cartucho", "SCRIPTERR_CART_DUMP_FAILED": "error al volcar cartucho",
@ -792,12 +792,12 @@
"SCRIPTERR_UNCLOSED_CONDITIONAL": "condicional no cerrado", "SCRIPTERR_UNCLOSED_CONDITIONAL": "condicional no cerrado",
"SCRIPTERR_ERROR_MESSAGE_FAIL": "mensaje de error fallido", "SCRIPTERR_ERROR_MESSAGE_FAIL": "mensaje de error fallido",
"ERROR_INVALID_TEXT_DATA": "Error: datos de texto no válidos", "ERROR_INVALID_TEXT_DATA": "Error: datos de texto no válidos",
"ERROR_TEXT_FILE_TOO_BIG": "Error: Text file is too large.\nText file size is %u bytes.\nMax file size is %i bytes.", "ERROR_TEXT_FILE_TOO_BIG": "Error: Archivo de texto muy grande.\nTamaño del archivo de texto: %u bytes.\nTamaño máximo del archivo: %i bytes.",
"TEXTVIEWER_CONTROLS_DETAILS": "Controles de Textviewer:\n \n↑↓→←(+R) - Desplazarse\nR+Y - Cambiar ajuste de palabras\nR+X - Ir a línea #\nB - Salir\n", "TEXTVIEWER_CONTROLS_DETAILS": "Controles de Textviewer:\n \n↑↓→←(+R) - Desplazarse\nR+Y - Cambiar ajuste de palabras\nR+X - Ir a línea #\nB - Salir\n",
"TEXTEDITOR_CONTROLS_DETAILS": "Text Editor Controls:\n \n↑↓→←(+R) - Scroll\nR+Y - Toggle wordwrap\nR+X - Goto line #\nA - Enter edit mode\nB - Exit\n", "TEXTEDITOR_CONTROLS_DETAILS": "Controles del editor de texto:\n \n↑↓→←(+R) - Desplazarse\nR+Y - Ajuste de línea\nR+X - Ir a línea #\nA - Modo edición\nB - Salir\n",
"TEXTEDITOR_CONTROLS_KEYBOARD": "Text Editor Controls:\n \n↑↓→←(+R) - Move cursor\nX - Delete char\nA - Insert newline\nL+↑↓→← - Select text\nY - COPY / [+R] CUT\nB - Enter view mode\n", "TEXTEDITOR_CONTROLS_KEYBOARD": "Controles del editor de texto:\n \n↑↓→←(+R) - Mover cursor\nX - Borrar carácter\nA - Insertar nueva línea\nL+↑↓→← - Seleccionar texto\nY - COPIAR / [+R] CORTAR\nB - Entrar en modo vista\n",
"TEXTEDITOR_CONTROLS_CLIPBOARD": "Text Editor Controls:\n \n↑↓→←(+R) - Move cursor\nX - Delete char\nA - Insert newline\nL+↑↓→← - Select text\nY - PASTE / [+R] CLEAR\nB - Enter view mode\n", "TEXTEDITOR_CONTROLS_CLIPBOARD": "Controles del editor de texto:\n \n↑↓→←(+R) - Mover cursor\nX - Borrar carácter\nA - Insertar nueva línea\nL+↑↓→← - Seleccionar texto\nY - PEGAR / [+R] BORRAR\nB - Entrar en modo vista\n",
"TEXT_EDITS_SAVE_CHANGES": "You made text edits.\nWrite changes to file?", "TEXT_EDITS_SAVE_CHANGES": "Has editado el texto.\n¿Escribir los cambios en el archivo?",
"CURRENT_LINE_N_ENTER_NEW_LINE_BELOW": "Línea actual: %i\nIngresa una nueva línea a continuación.", "CURRENT_LINE_N_ENTER_NEW_LINE_BELOW": "Línea actual: %i\nIngresa una nueva línea a continuación.",
"PREVIEW_DISABLED": "(vista previa desactivada)", "PREVIEW_DISABLED": "(vista previa desactivada)",
"PATH_LINE_N_ERR_LINE": "%s\nlínea %lu: %s\n%s", "PATH_LINE_N_ERR_LINE": "%s\nlínea %lu: %s\n%s",
@ -805,7 +805,7 @@
"END_OF_SCRIPT_UNRESOLVED_FOR": "fin del script: 'for' sin resolver", "END_OF_SCRIPT_UNRESOLVED_FOR": "fin del script: 'for' sin resolver",
"SYSINFO_MODEL": "Modelo: %s (%s)\n", "SYSINFO_MODEL": "Modelo: %s (%s)\n",
"SYSINFO_SERIAL": "Serie: %s\n", "SYSINFO_SERIAL": "Serie: %s\n",
"SYSINFO_GYRO_MODEL": "Gyro model: %u\r\n", "SYSINFO_GYRO_MODEL": "Modelo de giroscopio: %u\n",
"SYSINFO_REGION_SYSTEM": "Región (actual): %s\n", "SYSINFO_REGION_SYSTEM": "Región (actual): %s\n",
"SYSINFO_REGION_SALES": "Región (original): %s\n", "SYSINFO_REGION_SALES": "Región (original): %s\n",
"SYSINFO_SOC_MANUFACTURING_DATE": "Fecha de fabricación: %s\n", "SYSINFO_SOC_MANUFACTURING_DATE": "Fecha de fabricación: %s\n",
@ -819,11 +819,11 @@
"SYSINFO_SYSTEM_ID1": "ID1: %s\n", "SYSINFO_SYSTEM_ID1": "ID1: %s\n",
"SORTING_TICKETS_PLEASE_WAIT": "Ordenando tickets...", "SORTING_TICKETS_PLEASE_WAIT": "Ordenando tickets...",
"LUA_NOT_INCLUDED": "Este GodMode9 fue compilado\nsin el soporte de Lua.", "LUA_NOT_INCLUDED": "Este GodMode9 fue compilado\nsin el soporte de Lua.",
"ERROR_SIGNATURE_CHECK_FAILED": "Error: Signature check failed", "ERROR_SIGNATURE_CHECK_FAILED": "Error: Error en la comprobación de la firma",
"USE_SIGNATURE_VERIFICATION": "Use signature verification?", "USE_SIGNATURE_VERIFICATION": "¿Usar verificación de firma?",
"IGNORE_SIGNATURES": "Ignore signatures", "IGNORE_SIGNATURES": "Ignorar firmas",
"VERIFY_SIGNATURES": "Verify signatures", "VERIFY_SIGNATURES": "Verificar firmas",
"STANDARD_CRYPTO": "Standard encryption", "STANDARD_CRYPTO": "Cifrado estándar",
"ORIGINAL_CRYPTO": "Original encryption", "ORIGINAL_CRYPTO": "Cifrado original",
"SELECT_TYPE_OF_ENCRYPTION": "Select type of encryption" "SELECT_TYPE_OF_ENCRYPTION": "Seleccione el tipo de cifrado"
} }

View File

@ -2,15 +2,15 @@
"GM9_LANGUAGE": "Italiano", "GM9_LANGUAGE": "Italiano",
"GM9_TRANS_VER": 2, "GM9_TRANS_VER": 2,
"DATE_TIME_FORMAT": "%4$02lX/%3$02lX/%1$s%2$02lX %5$02lX:%6$02lX", "DATE_TIME_FORMAT": "%4$02lX/%3$02lX/%1$s%2$02lX %5$02lX:%6$02lX",
"DECIMAL_SEPARATOR": "\n,", "DECIMAL_SEPARATOR": ",",
"THOUSAND_SEPARATOR": "\n.", "THOUSAND_SEPARATOR": ".",
"FIRM_TOO_BIG": "FIRM troppo grande, avvio non riuscito", "FIRM_TOO_BIG": "FIRM troppo grande, avvio non riuscito",
"PATH_DO_NOT_BOOT_UNTRUSTED": "%s (%dkB)\nAttenzione: non avviare FIRM\nda fonti non attendibili.\n\nAvviare FIRM?", "PATH_DO_NOT_BOOT_UNTRUSTED": "%s (%dkB)\nAttenzione: non avviare FIRM\nda fonti non attendibili.\n\nAvviare FIRM?",
"NOT_BOOTABLE_FIRM": "Non è un FIRM eseguibile.", "NOT_BOOTABLE_FIRM": "Non è un FIRM eseguibile.",
"FIRM_ENCRYPTED": "FIRM è criptato.\n\nDecriptare prima dell'avvio?", "FIRM_ENCRYPTED": "FIRM è criptato.\n\nDecriptare prima dell'avvio?",
"MAKE_COPY_AT_OUT_TEMP_FIRM": "Fare una copia in %s/temp.firm", "MAKE_COPY_AT_OUT_TEMP_FIRM": "Fai una copia su %s/temp.firm",
"TRY_BOOT_ANYWAYS": "Provare comunque ad avviare il sistema", "TRY_BOOT_ANYWAYS": "Avvia il sistema comunque",
"WARNING_BOOT_UNSUPPORTED_LOCATION": "Attenzione: Tentativo di avvio da una posizione\nnon supportata.", "WARNING_BOOT_UNSUPPORTED_LOCATION": "Attenzione: Tentativo di avvio\nda una posizione non supportata.",
"ROOT": "[radice]", "ROOT": "[radice]",
"LOADING": "CARICAMENTO...", "LOADING": "CARICAMENTO...",
"PANE_N": "PANELLO #%lu", "PANE_N": "PANELLO #%lu",
@ -31,7 +31,7 @@
"VRAM_VIRTUAL": "(VRAM virtuale)", "VRAM_VIRTUAL": "(VRAM virtuale)",
"SEARCH": "(Ricerca)", "SEARCH": "(Ricerca)",
"TITLEMANAGER_VIRTUAL": "(Gestore titoli virtuale)", "TITLEMANAGER_VIRTUAL": "(Gestore titoli virtuale)",
"LAB_SDCARD": "SDCARD", "LAB_SDCARD": "SCHEDA SD",
"LAB_SYSNAND_CTRNAND": "SYSNAND CTRNAND", "LAB_SYSNAND_CTRNAND": "SYSNAND CTRNAND",
"LAB_SYSNAND_TWLN": "SYSNAND TWLN", "LAB_SYSNAND_TWLN": "SYSNAND TWLN",
"LAB_SYSNAND_TWLP": "SYSNAND TWLP", "LAB_SYSNAND_TWLP": "SYSNAND TWLP",
@ -58,7 +58,7 @@
"LAB_FAT_IMAGE": "IMMAGINE FAT", "LAB_FAT_IMAGE": "IMMAGINE FAT",
"LAB_BONUS_DRIVE": "DISCO EXTRA", "LAB_BONUS_DRIVE": "DISCO EXTRA",
"LAB_RAMDRIVE": "DISCO RAM", "LAB_RAMDRIVE": "DISCO RAM",
"LAB_NOLABEL": "SENZA ETICHETTA", "LAB_NOLABEL": "SENZA NOME",
"N_BYTE": "%s Byte", "N_BYTE": "%s Byte",
"BYTE": " Byte", "BYTE": " Byte",
"KB": " kB", "KB": " kB",
@ -94,7 +94,7 @@
"FORMAT_SD_CHOOSE_EMUNAND": "Formattare scheda SD (%lluMB)?\nScegli dimensione EmuNAND:", "FORMAT_SD_CHOOSE_EMUNAND": "Formattare scheda SD (%lluMB)?\nScegli dimensione EmuNAND:",
"SD_SIZE_IS_ENTER_EMUNAND_SIZE": "Dimensione scheda SD: %lluMB.\nInserisci la dimensione di EmuNAND (MB) qui:", "SD_SIZE_IS_ENTER_EMUNAND_SIZE": "Dimensione scheda SD: %lluMB.\nInserisci la dimensione di EmuNAND (MB) qui:",
"FORMAT_SD_CHOOSE_CLUSTER": "Formattare scheda SD (%lluMB)?\nScegli la dimensione del cluster:", "FORMAT_SD_CHOOSE_CLUSTER": "Formattare scheda SD (%lluMB)?\nScegli la dimensione del cluster:",
"FORMAT_SD_ENTER_LABEL": "Formattare SD card (%lluMB)?\nInserisci come chiamarla:", "FORMAT_SD_ENTER_LABEL": "Formattare scheda SD (%lluMB)?\nInserisci come chiamarla:",
"FORMAT_SD_FAILED": "Formattazione non riuscita!", "FORMAT_SD_FAILED": "Formattazione non riuscita!",
"REDNAND_TYPE": "Tipo di RedNAND", "REDNAND_TYPE": "Tipo di RedNAND",
"REDNAND_TYPE_MULTI": "Tipo di RedNAND (multi)", "REDNAND_TYPE_MULTI": "Tipo di RedNAND (multi)",
@ -102,7 +102,7 @@
"GW_EMUNAND_TYPE": "Tipo GW EmuNAND", "GW_EMUNAND_TYPE": "Tipo GW EmuNAND",
"DONT_SET_UP": "Non impostare", "DONT_SET_UP": "Non impostare",
"CHOOSE_EMUNAND_TYPE": "Scegliere il tipo di EmuNAND da impostare:", "CHOOSE_EMUNAND_TYPE": "Scegliere il tipo di EmuNAND da impostare:",
"CLONE_SYSNAND_TO_REDNAND": "Clonare SysNAND in RedNAND?", "CLONE_SYSNAND_TO_REDNAND": "Clonare SysNAND su RedNAND?",
"CLONING_SYSNAND_TO_EMUNAND_FAILED": "Clonazione di SysNAND su EmuNAND: fallita!", "CLONING_SYSNAND_TO_EMUNAND_FAILED": "Clonazione di SysNAND su EmuNAND: fallita!",
"PRESS_A_TO_CONTINUE": "Premi <A> per continuare", "PRESS_A_TO_CONTINUE": "Premi <A> per continuare",
"HEXEDITOR_CONTROLS": "Controlli dell'editor:\n \n↑↓→←(+R) - Scorri\nR+Y - Cambia vista\nX - Cerca / Vai a...\nA - Modalità di modifica\nA+↑↓→← - Modifica valore\nB - Esci\n", "HEXEDITOR_CONTROLS": "Controlli dell'editor:\n \n↑↓→←(+R) - Scorri\nR+Y - Cambia vista\nX - Cerca / Vai a...\nA - Modalità di modifica\nA+↑↓→← - Modifica valore\nB - Esci\n",
@ -142,7 +142,7 @@
"N_FILES_N_SUBDIRS_TOTAL_SIZE_FREE_USED_TOTAL": "%lu file e %lu sottocartelle\ndimensione totale: %s\n \nspazio libero: %s\nspazio usato: %s\nspazio totale: %s", "N_FILES_N_SUBDIRS_TOTAL_SIZE_FREE_USED_TOTAL": "%lu file e %lu sottocartelle\ndimensione totale: %s\n \nspazio libero: %s\nspazio usato: %s\nspazio totale: %s",
"N_FILES_N_SUBDIRS_TOTAL_SIZE": "%lu file e %lu sottocartelle\ndimensione totale: %s", "N_FILES_N_SUBDIRS_TOTAL_SIZE": "%lu file e %lu sottocartelle\ndimensione totale: %s",
"FILESIZE_X": "dimensione: %s", "FILESIZE_X": "dimensione: %s",
"READONLY_HIDDEN_SYSTEM_ARCHIVE_VIRTUAL": " \n[%c] %ssola lettura [%c] %snascosto\n[%c] %ssistema [%c] %sarchivio\n[%c] %svirtuale\n%s", "READONLY_HIDDEN_SYSTEM_ARCHIVE_VIRTUAL": " \n[%c] %ssola lettura [%c] %snascosto\n[%c] %sdi sistema [%c] %sarchivio\n[%c] %svirtuale\n%s",
"UDRL_CHANGE_ATTRIBUTES": " \n(↑↓→← per modificare gli attributi)\n", "UDRL_CHANGE_ATTRIBUTES": " \n(↑↓→← per modificare gli attributi)\n",
"A_TO_CONTINUE": "(<A> per continuare)", "A_TO_CONTINUE": "(<A> per continuare)",
"A_APPLY_B_CANCEL": "(<A> per applicare, <B> per annullare)", "A_APPLY_B_CANCEL": "(<A> per applicare, <B> per annullare)",
@ -200,7 +200,7 @@
"OPEN_CONTAINING_FOLDER": "Apri cartella contenitore", "OPEN_CONTAINING_FOLDER": "Apri cartella contenitore",
"OPEN_TITLE_FOLDER": "Apri cartella titolo", "OPEN_TITLE_FOLDER": "Apri cartella titolo",
"PATH_N_FILES_SELECTED": "%s\n(%lu file selezionati)", "PATH_N_FILES_SELECTED": "%s\n(%lu file selezionati)",
"CHECK_CURRENT_CMAC_ONLY": "Controlla solo CMAC corrente", "CHECK_CURRENT_CMAC_ONLY": "Controlla solo CMAC attuale",
"VERIFY_CMAC_FOR_ALL": "Verifica il CMAC per tutti", "VERIFY_CMAC_FOR_ALL": "Verifica il CMAC per tutti",
"FIX_CMAC_FOR_ALL": "Sistema il CMAC per tutti", "FIX_CMAC_FOR_ALL": "Sistema il CMAC per tutti",
"N_N_N_FILES_OK_FIXED_TOTAL_N_OF_N_HAVE_NO_CMAC": "%lu/%lu/%lu file ok/sistemati/totali\n\"%lu/%lu\" non hanno CMAC", "N_N_N_FILES_OK_FIXED_TOTAL_N_OF_N_HAVE_NO_CMAC": "%lu/%lu/%lu file ok/sistemati/totali\n\"%lu/%lu\" non hanno CMAC",
@ -229,7 +229,7 @@
"VERIFY_FILE": "Verifica il file", "VERIFY_FILE": "Verifica il file",
"TRANSFER_IMAGE_TO_CTRNAND": "Trasferisci l'immagine nella CTRNAND", "TRANSFER_IMAGE_TO_CTRNAND": "Trasferisci l'immagine nella CTRNAND",
"INJECT_TO_H_AND_S": "Inietta su SeS", "INJECT_TO_H_AND_S": "Inietta su SeS",
"TRIM_FILE": "Trimma file", "TRIM_FILE": "Esegui trim del file",
"RENAME_FILE": "Rinomina file", "RENAME_FILE": "Rinomina file",
"BUILD_XORPADS_SD": "Crea XORpads (Uscita SD)", "BUILD_XORPADS_SD": "Crea XORpads (Uscita SD)",
"BUILD_XORPADS_INPLACE": "Crea XORpads (qui)", "BUILD_XORPADS_INPLACE": "Crea XORpads (qui)",
@ -316,13 +316,13 @@
"BUILD_DATABASE_SUCCESS": "Creazione database riuscita.", "BUILD_DATABASE_SUCCESS": "Creazione database riuscita.",
"BUILD_DATABASE_FAILED": "Creazione database fallita.", "BUILD_DATABASE_FAILED": "Creazione database fallita.",
"TRY_TO_TRIM_N_SELECTED_FILES": "Trimmare tutti i %lu file selezionati?", "TRY_TO_TRIM_N_SELECTED_FILES": "Trimmare tutti i %lu file selezionati?",
"TRIMMING_FAILED_CONTINUE": "Trimmaggio fallito\n \nContinuare?", "TRIMMING_FAILED_CONTINUE": "Trim fallito\n \nContinuare?",
"N_OF_N_FILES_TRIMMED_N_OF_N_NOT_OF_SAME_TYPE_X_SAVED": "%lu/%lu file trimmati ok\n%lu/%lu non dello stesso tipo\n%s salvato", "N_OF_N_FILES_TRIMMED_N_OF_N_NOT_OF_SAME_TYPE_X_SAVED": "%lu/%lu file trimmati ok\n%lu/%lu non dello stesso tipo\n%s salvato",
"N_OF_N_FILES_TRIMMED_X_SAVED": "%lu/%lu file trimmati ok\n%s salvato", "N_OF_N_FILES_TRIMMED_X_SAVED": "%lu/%lu file trimmati ok\n%s salvato",
"FILE_CANT_BE_TRIMMED": "Il file non può essere trimmato.", "FILE_CANT_BE_TRIMMED": "Il file non può essere trimmato.",
"FILE_ALREADY_TRIMMED": "Il file è già trimmato.", "FILE_ALREADY_TRIMMED": "Il file è già trimmato.",
"PATH_CURRENT_SIZE_TRIMMED_SIZE_DIFFERENCE_TRIM_FILE": "%s\nDimensione attuale: %s\nDimensione trimmata: %s\nDifferenza: %s\n \nTrimmare questo file?", "PATH_CURRENT_SIZE_TRIMMED_SIZE_DIFFERENCE_TRIM_FILE": "%s\nDimensione attuale: %s\nDimensione trimmata: %s\nDifferenza: %s\n \nTrimmare questo file?",
"TRIMMING_FAILED": "Trimmaggio fallito.", "TRIMMING_FAILED": "Trim fallito.",
"PATH_TRIMMED_BY_X": "%s\nTrimmato da %s.", "PATH_TRIMMED_BY_X": "%s\nTrimmato da %s.",
"TRY_TO_RENAME_N_SELECTED_FILES": "Rinominare tutti i %lu file selezionati?", "TRY_TO_RENAME_N_SELECTED_FILES": "Rinominare tutti i %lu file selezionati?",
"N_OF_N_RENAMED": "%lu/%lu rinominati ok", "N_OF_N_RENAMED": "%lu/%lu rinominati ok",
@ -387,7 +387,7 @@
"SHOW_README": "Mostra ReadMe", "SHOW_README": "Mostra ReadMe",
"INITIALIZING_SD_FAILED_RETRY": "Inizializzazione scheda SD fallita! Riprovare?", "INITIALIZING_SD_FAILED_RETRY": "Inizializzazione scheda SD fallita! Riprovare?",
"SETUP_FAILED": "Installazione fallita!", "SETUP_FAILED": "Installazione fallita!",
"CURRENT_EMUNAND_OFFSET_IS_N_SWITCH_TO_NEXT": "L'offset EmuNAND corrente è %06lX.\nPassare all'offset successivo?", "CURRENT_EMUNAND_OFFSET_IS_N_SWITCH_TO_NEXT": "L'offset EmuNAND attuale è %06lX.\nPassare all'offset successivo?",
"BUILT_IN_OUT_STATUSES": "Creato su %s:\n \n%-18.18s %s\n%-18.18s %s\n%-18.18s %s", "BUILT_IN_OUT_STATUSES": "Creato su %s:\n \n%-18.18s %s\n%-18.18s %s\n%-18.18s %s",
"OK_SYS_EMU": "OK (Sys e Emu)", "OK_SYS_EMU": "OK (Sys e Emu)",
"OK_SYS": "OK (Sys)", "OK_SYS": "OK (Sys)",

View File

@ -191,7 +191,7 @@
"CALCULATE_SHA256": "SHA-256 계산", "CALCULATE_SHA256": "SHA-256 계산",
"CALCULATE_SHA1": "SHA-1 계산", "CALCULATE_SHA1": "SHA-1 계산",
"SHOW_FILE_INFO": "파일 정보 보기", "SHOW_FILE_INFO": "파일 정보 보기",
"SHOW_IN_TEXTVIEWER": "Show in Text Editor", "SHOW_IN_TEXTVIEWER": "텍스트 편집기에서 보기",
"CALCULATE_CMAC": "CMAC 계산", "CALCULATE_CMAC": "CMAC 계산",
"COPY_TO_OUT": "%s(으)로 복사", "COPY_TO_OUT": "%s(으)로 복사",
"DUMP_TO_OUT": "%s(으)로 덤프", "DUMP_TO_OUT": "%s(으)로 덤프",
@ -778,7 +778,7 @@
"SCRIPTERR_APPLY_IPS_FAILD": "IPS 적용 실패", "SCRIPTERR_APPLY_IPS_FAILD": "IPS 적용 실패",
"SCRIPTERR_APPLY_BPS_FAILED": "BPS 적용 실패", "SCRIPTERR_APPLY_BPS_FAILED": "BPS 적용 실패",
"SCRIPTERR_APPLY_BPM_FAILED": "BPM 적용 실패", "SCRIPTERR_APPLY_BPM_FAILED": "BPM 적용 실패",
"SCRIPTERR_TEXTVIEWER_FAILED": "text editor failed", "SCRIPTERR_TEXTVIEWER_FAILED": "텍스트 편집기 오류 발생",
"SCRIPTERR_BAD_DUMPSIZE": "잘못된 덤프 크기", "SCRIPTERR_BAD_DUMPSIZE": "잘못된 덤프 크기",
"SCRIPTERR_CART_INIT_FAIL": "cart init 실패", "SCRIPTERR_CART_INIT_FAIL": "cart init 실패",
"SCRIPTERR_CART_DUMP_FAILED": "카트리지 덤프 실패", "SCRIPTERR_CART_DUMP_FAILED": "카트리지 덤프 실패",
@ -792,12 +792,12 @@
"SCRIPTERR_UNCLOSED_CONDITIONAL": "닫히지 않은 조건문", "SCRIPTERR_UNCLOSED_CONDITIONAL": "닫히지 않은 조건문",
"SCRIPTERR_ERROR_MESSAGE_FAIL": "오류 메시지 실패", "SCRIPTERR_ERROR_MESSAGE_FAIL": "오류 메시지 실패",
"ERROR_INVALID_TEXT_DATA": "오류: 잘못된 텍스트 데이터", "ERROR_INVALID_TEXT_DATA": "오류: 잘못된 텍스트 데이터",
"ERROR_TEXT_FILE_TOO_BIG": "Error: Text file is too large.\nText file size is %u bytes.\nMax file size is %i bytes.", "ERROR_TEXT_FILE_TOO_BIG": "오류: 텍스트 파일 크기가 너무 큽니다.\n텍스트 파일 크기는 %u 바이트입니다.\n최대 파일 크기는 %i 바이트입니다.",
"TEXTVIEWER_CONTROLS_DETAILS": "텍스트 뷰어 조작법:\n \n↑↓→←(+R) - 스크롤\nR+Y - 줄 바꿈 전환\nR+X - #번째 줄로 이동\nB - 나가기", "TEXTVIEWER_CONTROLS_DETAILS": "텍스트 뷰어 조작법:\n \n↑↓→←(+R) - 스크롤\nR+Y - 줄 바꿈 전환\nR+X - #번째 줄로 이동\nB - 나가기",
"TEXTEDITOR_CONTROLS_DETAILS": "Text Editor Controls:\n \n↑↓→←(+R) - Scroll\nR+Y - Toggle wordwrap\nR+X - Goto line #\nA - Enter edit mode\nB - Exit\n", "TEXTEDITOR_CONTROLS_DETAILS": "텍스트 편집기 조작:\n\n↑↓→←(+R) - 스크롤\nR+Y - 줄 바꿈 토글\nR+X - 이전 줄로 이동\nA - 편집 모드 진입\nB - 종료\n",
"TEXTEDITOR_CONTROLS_KEYBOARD": "Text Editor Controls:\n \n↑↓→←(+R) - Move cursor\nX - Delete char\nA - Insert newline\nL+↑↓→← - Select text\nY - COPY / [+R] CUT\nB - Enter view mode\n", "TEXTEDITOR_CONTROLS_KEYBOARD": "텍스트 편집기 조작법:\n\n↑↓→←(+R) - 커서 이동\nX - 문자 삭제\nA - 줄 바꿈\nL+↑↓→← - 텍스트 선택\nY - 복사 / [+R] 잘라내기\nB - 보기 모드 진입\n",
"TEXTEDITOR_CONTROLS_CLIPBOARD": "Text Editor Controls:\n \n↑↓→←(+R) - Move cursor\nX - Delete char\nA - Insert newline\nL+↑↓→← - Select text\nY - PASTE / [+R] CLEAR\nB - Enter view mode\n", "TEXTEDITOR_CONTROLS_CLIPBOARD": "텍스트 편집기 조작법:\n\n↑↓→←(+R) - 커서 이동\nX - 문자 삭제\nA - 줄 바꿈\nL+↑↓→← - 텍스트 선택\nY - 붙여넣기 / [+R] - 지우기\nB - 보기 모드 진입\n",
"TEXT_EDITS_SAVE_CHANGES": "You made text edits.\nWrite changes to file?", "TEXT_EDITS_SAVE_CHANGES": "텍스트를 수정하셨습니다.\n파일에 변경 사항을 저장하시겠습니까?",
"CURRENT_LINE_N_ENTER_NEW_LINE_BELOW": "현재 줄: %i\n아래에 새 줄을 입력하세요.", "CURRENT_LINE_N_ENTER_NEW_LINE_BELOW": "현재 줄: %i\n아래에 새 줄을 입력하세요.",
"PREVIEW_DISABLED": "(미리보기 비활성화됨)", "PREVIEW_DISABLED": "(미리보기 비활성화됨)",
"PATH_LINE_N_ERR_LINE": "%s\nline %lu: %s\n%s", "PATH_LINE_N_ERR_LINE": "%s\nline %lu: %s\n%s",
@ -805,7 +805,7 @@
"END_OF_SCRIPT_UNRESOLVED_FOR": "스크립트 끝: 해결되지 않은 'for'", "END_OF_SCRIPT_UNRESOLVED_FOR": "스크립트 끝: 해결되지 않은 'for'",
"SYSINFO_MODEL": "모델: %s (%s)\n", "SYSINFO_MODEL": "모델: %s (%s)\n",
"SYSINFO_SERIAL": "일련 번호: %s\n", "SYSINFO_SERIAL": "일련 번호: %s\n",
"SYSINFO_GYRO_MODEL": "Gyro model: %u\r\n", "SYSINFO_GYRO_MODEL": "자이로 모델: %u\n",
"SYSINFO_REGION_SYSTEM": "시스템 지역: %s\n", "SYSINFO_REGION_SYSTEM": "시스템 지역: %s\n",
"SYSINFO_REGION_SALES": "판매 지역: %s\n", "SYSINFO_REGION_SALES": "판매 지역: %s\n",
"SYSINFO_SOC_MANUFACTURING_DATE": "SoC 제조 날짜: %s\n", "SYSINFO_SOC_MANUFACTURING_DATE": "SoC 제조 날짜: %s\n",

View File

@ -61,7 +61,7 @@
"LAB_NOLABEL": "BEZ ETYKIETY", "LAB_NOLABEL": "BEZ ETYKIETY",
"N_BYTE": "%s B", "N_BYTE": "%s B",
"BYTE": " B", "BYTE": " B",
"KB": " kB", "KB": " KB",
"MB": " MB", "MB": " MB",
"GB": " GB", "GB": " GB",
"CLIPBOARD": "[SCHOWEK]", "CLIPBOARD": "[SCHOWEK]",
@ -191,13 +191,13 @@
"CALCULATE_SHA256": "Oblicz SHA-256", "CALCULATE_SHA256": "Oblicz SHA-256",
"CALCULATE_SHA1": "Oblicz SHA-1", "CALCULATE_SHA1": "Oblicz SHA-1",
"SHOW_FILE_INFO": "Informacje o pliku", "SHOW_FILE_INFO": "Informacje o pliku",
"SHOW_IN_TEXTVIEWER": "Show in Text Editor", "SHOW_IN_TEXTVIEWER": "Pokaż w tekstedytorze",
"CALCULATE_CMAC": "Oblicz CMAC", "CALCULATE_CMAC": "Oblicz CMAC",
"COPY_TO_OUT": "Kopiuj do %s", "COPY_TO_OUT": "Kopiuj do %s",
"DUMP_TO_OUT": "Zrzut do %s", "DUMP_TO_OUT": "Zrzut do %s",
"INJECT_DATA_AT_OFFSET": "Inject data @offset", "INJECT_DATA_AT_OFFSET": "Inject data @offset",
"OPEN_THIS_FOLDER": "Otwórz ten folder", "OPEN_THIS_FOLDER": "Otwórz ten folder",
"OPEN_CONTAINING_FOLDER": "Open containing folder", "OPEN_CONTAINING_FOLDER": "Otwórz lokalizację pliku",
"OPEN_TITLE_FOLDER": "Otwórz folder aplikacji", "OPEN_TITLE_FOLDER": "Otwórz folder aplikacji",
"PATH_N_FILES_SELECTED": "%s\n(zaznaczono %s)", "PATH_N_FILES_SELECTED": "%s\n(zaznaczono %s)",
"CHECK_CURRENT_CMAC_ONLY": "Sprawdź tylko aktualny CMAC", "CHECK_CURRENT_CMAC_ONLY": "Sprawdź tylko aktualny CMAC",
@ -308,9 +308,9 @@
"PATH_TICKET_DUMPED_TO_OUT": "%s\nTicket dumped to %s", "PATH_TICKET_DUMPED_TO_OUT": "%s\nTicket dumped to %s",
"LEGIT_TICKET_NOT_FOUND_DUMP_ANYWAYS": "%s\nLegit ticket not found.\n \nDump anyways?", "LEGIT_TICKET_NOT_FOUND_DUMP_ANYWAYS": "%s\nLegit ticket not found.\n \nDump anyways?",
"DUMP_TICKET_FAILED": "Niepowodzenie zrzutu ticketa!", "DUMP_TICKET_FAILED": "Niepowodzenie zrzutu ticketa!",
"BUILDING_X": "Building %s...", "BUILDING_X": "Kompilowanie %s...",
"BUILDING_X_SYSNAND": "Building %s (SysNAND)...", "BUILDING_X_SYSNAND": "Kompilowanie %s (SysNAND)...",
"BUILDING_X_EMUNAND": "Building %s (EmuNAND)...", "BUILDING_X_EMUNAND": "Kompilowanie %s (EmuNAND)...",
"PATH_N_OF_N_FILES_PROCESSED_N_OF_N_FILES_IGNORED": "%s\n%lu/%lu files processed\n%lu/%lu files ignored", "PATH_N_OF_N_FILES_PROCESSED_N_OF_N_FILES_IGNORED": "%s\n%lu/%lu files processed\n%lu/%lu files ignored",
"PATH_N_OF_N_FILES_PROCESSED": "%s\n%lu/%lu files processed", "PATH_N_OF_N_FILES_PROCESSED": "%s\n%lu/%lu files processed",
"BUILD_DATABASE_SUCCESS": "Kompilacja bazy danych powiodła się.", "BUILD_DATABASE_SUCCESS": "Kompilacja bazy danych powiodła się.",
@ -362,7 +362,7 @@
"INSTALL_TO_BOTH": "Install to both", "INSTALL_TO_BOTH": "Install to both",
"PATH_N_KB_INSTALL_TO_SYSNAND": "%s (%dkB)\nInstall to SysNAND?", "PATH_N_KB_INSTALL_TO_SYSNAND": "%s (%dkB)\nInstall to SysNAND?",
"PATH_N_KB_INSTALL_SUCCESS": "%s (%dkB)\nInstall success", "PATH_N_KB_INSTALL_SUCCESS": "%s (%dkB)\nInstall success",
"PATH_N_KB_INSTALL_FAILED": "%s (%dkB)\nInstall failed", "PATH_N_KB_INSTALL_FAILED": "%s (%dKB)\nInstalacja nie powiodła się",
"WARNING_DO_NOT_RUN_UNTRUSTED_SCRIPTS": "Warning: Do not run scripts\nfrom untrusted sources.\n \nExecute script?", "WARNING_DO_NOT_RUN_UNTRUSTED_SCRIPTS": "Warning: Do not run scripts\nfrom untrusted sources.\n \nExecute script?",
"SCRIPT_EXECUTE_SUCCESS": "Script execute success", "SCRIPT_EXECUTE_SUCCESS": "Script execute success",
"SCRIPT_EXECUTE_FAILURE": "Script execute failure", "SCRIPT_EXECUTE_FAILURE": "Script execute failure",
@ -428,19 +428,19 @@
"FIX_CMACS_FOR_DRIVE_FINISHED": "Fix CMACs for drive finished.", "FIX_CMACS_FOR_DRIVE_FINISHED": "Fix CMACs for drive finished.",
"FAILED_TO_ANALYZE_DRIVE": "Failed to analyze drive\n", "FAILED_TO_ANALYZE_DRIVE": "Failed to analyze drive\n",
"FAILED_TO_ANALYZE_DIR": "Niepowodzenie analizy katalogu\n", "FAILED_TO_ANALYZE_DIR": "Niepowodzenie analizy katalogu\n",
"NOT_ALLOWED_IN_VIRTUAL_PATH": "Not allowed in virtual path", "NOT_ALLOWED_IN_VIRTUAL_PATH": "Niedozwolone w ścieżce wirtualnej",
"DELETE_N_PATHS": "Delete %lu path(s)?", "DELETE_N_PATHS": "Ścieżek: %lu - usunąć?",
"DELETING_FILES_PLEASE_WAIT": "Usuwanie plików, proszę czekać...", "DELETING_FILES_PLEASE_WAIT": "Usuwanie plików, proszę czekać...",
"FAILED_DELETING_N_OF_N_PATHS": "Failed deleting %lu/%lu path(s)", "FAILED_DELETING_N_OF_N_PATHS": "Failed deleting %lu/%lu path(s)",
"DELETE_FILE": "Usunąć \"%s\"?", "DELETE_FILE": "Usunąć \"%s\"?",
"FAILED_DELETING_PATH": "Failed deleting:\n%s", "FAILED_DELETING_PATH": "Failed deleting:\n%s",
"NOT_ALLOWED_IN_SEARCH_DRIVE": "Niedozwolone w dysku wyszukiwania", "NOT_ALLOWED_IN_SEARCH_DRIVE": "Niedozwolone w dysku wyszukiwania",
"NOT_ALLOWED_IN_VIRTUAL_GAME_PATH": "Not allowed in virtual game path", "NOT_ALLOWED_IN_VIRTUAL_GAME_PATH": "Niedozwolone w ścieżce wirtualnej gry",
"NOT_ALLOWED_IN_XORPAD_DRIVE": "Not allowed in XORpad drive", "NOT_ALLOWED_IN_XORPAD_DRIVE": "Not allowed in XORpad drive",
"NOT_ALLOWED_IN_GAMECART_DRIVE": "Not allowed in gamecart drive", "NOT_ALLOWED_IN_GAMECART_DRIVE": "Not allowed in gamecart drive",
"NOT_ALLOWED_IN_ALIAS_PATH": "Not allowed in alias path", "NOT_ALLOWED_IN_ALIAS_PATH": "Niedozwolone w ścieżce aliasu",
"COPY_PATHS": "Copy path(s)", "COPY_PATHS": "Kopiuj ścieżkę/-i",
"MOVE_PATHS": "Move path(s)", "MOVE_PATHS": "Przenieś ścieżkę/-i",
"PASTE_FILE_HERE": "Wkleić \"%s\" tutaj?", "PASTE_FILE_HERE": "Wkleić \"%s\" tutaj?",
"PASTE_N_PATHS_HERE": "Paste %lu paths here?", "PASTE_N_PATHS_HERE": "Paste %lu paths here?",
"FAILED_COPYING_PATH_PROCESS_REMAINING": "Failed copying path:\n%s\nProcess remaining?", "FAILED_COPYING_PATH_PROCESS_REMAINING": "Failed copying path:\n%s\nProcess remaining?",
@ -731,7 +731,7 @@
"SCRIPTERR_OUT_OF_MEMORY": "out of memory", "SCRIPTERR_OUT_OF_MEMORY": "out of memory",
"SCRIPTERR_VAR_FAIL": "błąd zmiennej", "SCRIPTERR_VAR_FAIL": "błąd zmiennej",
"SCRIPTERR_FORBIDDEN_DRIVE": "forbidden drive", "SCRIPTERR_FORBIDDEN_DRIVE": "forbidden drive",
"SCRIPTERR_INVALID_PATH": "invalid path", "SCRIPTERR_INVALID_PATH": "błędna ścieżka",
"SCRIPTERR_FILESELECT_ABORT": "fileselect abort", "SCRIPTERR_FILESELECT_ABORT": "fileselect abort",
"SCRIPTERR_DIRSELECT_ABORT": "dirselect abort", "SCRIPTERR_DIRSELECT_ABORT": "dirselect abort",
"SCRIPTERR_SET_FAIL": "błąd ustawiania", "SCRIPTERR_SET_FAIL": "błąd ustawiania",
@ -792,7 +792,7 @@
"SCRIPTERR_UNCLOSED_CONDITIONAL": "unclosed conditional", "SCRIPTERR_UNCLOSED_CONDITIONAL": "unclosed conditional",
"SCRIPTERR_ERROR_MESSAGE_FAIL": "błąd wyświetlania błędu", "SCRIPTERR_ERROR_MESSAGE_FAIL": "błąd wyświetlania błędu",
"ERROR_INVALID_TEXT_DATA": "Error: Invalid text data", "ERROR_INVALID_TEXT_DATA": "Error: Invalid text data",
"ERROR_TEXT_FILE_TOO_BIG": "Error: Text file is too large.\nText file size is %u bytes.\nMax file size is %i bytes.", "ERROR_TEXT_FILE_TOO_BIG": "Błąd: Plik tekstowy jest zbyt duży.\nRozmiar pliku: %u B\nRozmiar maksymalny: %i B",
"TEXTVIEWER_CONTROLS_DETAILS": "Instrukcja tekstprzeglądarki:\n \n↑↓→←(+R) - Przewijanie\nR+Y - Przełącz zawijanie wierszy\nR+X - Przejdź do linii #\nB - Wyjście\n", "TEXTVIEWER_CONTROLS_DETAILS": "Instrukcja tekstprzeglądarki:\n \n↑↓→←(+R) - Przewijanie\nR+Y - Przełącz zawijanie wierszy\nR+X - Przejdź do linii #\nB - Wyjście\n",
"TEXTEDITOR_CONTROLS_DETAILS": "Text Editor Controls:\n \n↑↓→←(+R) - Scroll\nR+Y - Toggle wordwrap\nR+X - Goto line #\nA - Enter edit mode\nB - Exit\n", "TEXTEDITOR_CONTROLS_DETAILS": "Text Editor Controls:\n \n↑↓→←(+R) - Scroll\nR+Y - Toggle wordwrap\nR+X - Goto line #\nA - Enter edit mode\nB - Exit\n",
"TEXTEDITOR_CONTROLS_KEYBOARD": "Text Editor Controls:\n \n↑↓→←(+R) - Move cursor\nX - Delete char\nA - Insert newline\nL+↑↓→← - Select text\nY - COPY / [+R] CUT\nB - Enter view mode\n", "TEXTEDITOR_CONTROLS_KEYBOARD": "Text Editor Controls:\n \n↑↓→←(+R) - Move cursor\nX - Delete char\nA - Insert newline\nL+↑↓→← - Select text\nY - COPY / [+R] CUT\nB - Enter view mode\n",