Question Regarding Archive Bits on Switch

  • Thread starter Thread starter littlesmurf
  • Start date Start date
  • Views Views 2,354
  • Replies Replies 21

littlesmurf

Well-Known Member
Member
Joined
Dec 5, 2024
Messages
121
Reaction score
154
Trophies
0
Age
53
XP
281
Country
United States
On Nintendo Switch if you copy a files/folder over from an old MAC the switch has issues if the archive bit is set wrongly. This bit is stored not on the files but in the file system metadata. So why is it when you send this to the switch the metadata is also being sent? As far as I can remember this makes the file on a switch look like a folder.
In fat32 and exfat we can also set an archive bit by using a meta data file e.g., icon.jpg - icon.jpg.metadata. The meta data contains this - A=1 if the bit is set to on and A=0 if the bit is set to off.

No I know we can just use hekate to fix this, but what if we want to change a files meta data manually, how can we do this?

I made a small program for testing this:
Code:
#include <switch.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>

#define METADATA_EXT ".metadata"  // Metadata file extension
#define TARGET_DIR "/switch/test" // Directory to scan

// Function to get the metadata filename
void get_metadata_filename(const char* filepath, char* metadata_path, size_t size) {
    snprintf(metadata_path, size, "%s%s", filepath, METADATA_EXT);
}

// Function to check if a file is a metadata file
bool is_metadata_file(const char* filename) {
    size_t len = strlen(filename);
    size_t ext_len = strlen(METADATA_EXT);

    if (len > ext_len && strcmp(filename + (len - ext_len), METADATA_EXT) == 0) {
        return true; // This is a .metadata file, so ignore it
    }
    return false;
}

// Function to set the archive bit in metadata
void set_archive_bit(const char* filepath, bool enable) {
    if (is_metadata_file(filepath)) return; // Skip processing metadata files

    char metadata_path[PATH_MAX];
    get_metadata_filename(filepath, metadata_path, sizeof(metadata_path));

    FILE* meta_file = fopen(metadata_path, "w");
    if (!meta_file) {
        printf("[ERROR] Failed to open metadata file: %s\n", metadata_path);
        return;
    }

    fprintf(meta_file, "A=%d\n", enable ? 1 : 0);
    fclose(meta_file);

    printf("[INFO] Archive bit %s for: %s\n", enable ? "enabled" : "disabled", filepath);
}

// Function to get the archive bit status from metadata
bool is_archive_bit_set(const char* filepath) {
    if (is_metadata_file(filepath)) return false; // Ignore metadata files

    char metadata_path[PATH_MAX];
    get_metadata_filename(filepath, metadata_path, sizeof(metadata_path));

    FILE* meta_file = fopen(metadata_path, "r");
    if (!meta_file) {
        return false; // Assume archive bit is not set if metadata is missing
    }

    int archive_flag = 0;
    fscanf(meta_file, "A=%d\n", &archive_flag);
    fclose(meta_file);

    return archive_flag == 1;
}

// Function to recursively process directories and apply the archive bit
void process_directory(const char* directory, bool enable) {
    DIR* dir = opendir(directory);
    if (!dir) {
        printf("[ERROR] Failed to open directory: %s\n", directory);
        return;
    }

    struct dirent* entry;
    char path[PATH_MAX];

    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 || is_metadata_file(entry->d_name)) {
            continue; // Skip "." and ".." and metadata files
        }

        snprintf(path, sizeof(path), "%s/%s", directory, entry->d_name);

        struct stat st;
        if (stat(path, &st) == 0) {
            if (S_ISDIR(st.st_mode)) {
                // Recursively process subdirectories
                process_directory(path, enable);
            }
            else {
                // Set archive bit on file
                set_archive_bit(path, enable);
            }
        }
        else {
            printf("[ERROR] Could not stat: %s\n", path);
        }
    }

    closedir(dir);
}

// Function to display files and their archive status
void display_directory_status(const char* directory) {
    DIR* dir = opendir(directory);
    if (!dir) {
        printf("[ERROR] Failed to open directory: %s\n", directory);
        return;
    }

    struct dirent* entry;
    char path[PATH_MAX];

    printf("[INFO] Listing files in: %s\n", directory);

    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 || is_metadata_file(entry->d_name)) {
            continue; // Skip "." and ".." and metadata files
        }

        snprintf(path, sizeof(path), "%s/%s", directory, entry->d_name);

        if (is_archive_bit_set(path)) {
            printf("[ARCHIVE ON] %s\n", path);
        }
        else {
            printf("[ARCHIVE OFF] %s\n", path);
        }
    }

    closedir(dir);
}

// Main entry point
int main(int argc, char* argv[]) {
    consoleInit(NULL);

    printf("Nintendo Switch Archive Bit Manager (Fixed Version)\n");
    printf("Press A to set archive bit, B to clear it, X to list files, + to exit.\n");

    padConfigureInput(1, HidNpadStyleSet_NpadStandard);
    PadState pad;
    padInitializeDefault(&pad);

    while (appletMainLoop()) {
        padUpdate(&pad);
        u64 kDown = padGetButtonsDown(&pad);

        if (kDown & HidNpadButton_A) {
            printf("[INFO] Setting archive bit recursively in: %s\n", TARGET_DIR);
            process_directory(TARGET_DIR, true);
        }

        if (kDown & HidNpadButton_B) {
            printf("[INFO] Clearing archive bit recursively in: %s\n", TARGET_DIR);
            process_directory(TARGET_DIR, false);
        }

        if (kDown & HidNpadButton_X) {
            display_directory_status(TARGET_DIR);
        }

        if (kDown & HidNpadButton_Plus) break; // Exit program

        consoleUpdate(NULL);
    }

    consoleExit(NULL);
    return 0;

Now I tested it with files in the switch/test folder, it creates these metadata files properly - but the switch doesn't see these files as folders if the bit is set to on so I must be missing something. Does anyone have any Idea what I am missing?
 
Does anyone have any Idea what I am missing?
On FAT32 and exFAT file attributes not a files, but a bitfields in the FAT records.
Post automatically merged:

Also, switch FS API does not provide access for them, so no homebrew workikng in hos userspace can manage attributes.
 
  • Like
Reactions: Nephiel
On FAT32 and exFAT file attributes not a files, but a bitfields in the FAT records.
Post automatically merged:

Also, switch FS API does not provide access for them, so no homebrew workikng in hos userspace can manage attributes.
Some homebrew running on switch can change the archive bit for files/folders. Some file manager homebrews have this. Also I know that file attributes are stored in Fat records, but can also be set in meta files.

In windows you can check the archive bit by:
Code:
attrib +A myfile.txt  # Set archive bit
attrib -A myfile.txt  # Clear archive bit
attrib myfile.txt     # Check attributes

We can also set these
R=0 #read only
H=0 #Hidden
T=1707654321 #Timestamp

In libNX can't we check/set file attributes with:
https://switchbrew.github.io/libnx/fs_8h.html
fsFsSetConcatenationFileAttribute() - to get and set the archive bit.

However I am unfamiliar with the Nintendo Switch and programming stuff for it or how the inner workings of libnx works or the switch filesystem, hence asking here about archive bit.
 
Last edited by littlesmurf,
  • Like
Reactions: Nephiel
In fat32 and exfat we can also set an archive bit by using a meta data file e.g., icon.jpg - icon.jpg.metadata.
This is interesting. As far as I know, handling file attributes as .metadata files is not a native feature of either filesystem. Maybe it's some library that does interpret those files and set the bits accordingly, but you'd need to call it to apply changes... any info on this?
 
This is interesting. As far as I know, handling file attributes as .metadata files is not a native feature of either filesystem. Maybe it's some library that does interpret those files and set the bits accordingly, but you'd need to call it to apply changes... any info on this?
Using the example I posted above to make the metadatafiles, then in windows you can do this:

Modify Archive Bit on Windows:

If you transfer your files to Windows and want to update the actual archive bit, you can use a PowerShell script to read our .metadata file and apply the real archive bit:

Code:
$targetFile = "myfile.txt"
$metaFile = "$targetFile.metadata"

if (Test-Path $metaFile) {
    $archiveBit = Get-Content $metaFile | Select-String "A="
    if ($archiveBit -match "A=1") {
        attrib +A $targetFile  # Set the archive bit
        Write-Host "Archive bit set for $targetFile"
    } else {
        attrib -A $targetFile  # Clear the archive bit
        Write-Host "Archive bit cleared for $targetFile"
    }
} else {
    Write-Host "Metadata file not found. No changes made."
}

You should be carefull with do stuff like this though as you can mess up your files easy.
 
On Nintendo Switch if you copy a files/folder over from an old MAC the switch has issues if the archive bit is set wrongly.
This is because the archive bit is an archaic remnant of the DOS/9.x era and it hasn't been relevant for anything but the Switch in decades. It is honestly purely incidental (and due to Microsoft's obsession with never breaking old software, i guess) that NTFS still stores it - but if you actually rely on the bit in your archiving tool, abandon all hope.

I really wouldn't mind if atmosphere hardcoded nca's as always having archive bit in fs.
 
  • Like
Reactions: Nephiel
This is because the archive bit is an archaic remnant of the DOS/9.x era and it hasn't been relevant for anything but the Switch in decades. It is honestly purely incidental (and due to Microsoft's obsession with never breaking old software, i guess) that NTFS still stores it - but if you actually rely on the bit in your archiving tool, abandon all hope.

I really wouldn't mind if atmosphere hardcoded nca's as always having archive bit in fs.
It would be handy wouldn't it.

I'm making a ftp server program for the switch just now. I've almost completed it. Everything works apart from CHMOD which is not a fault with the program, but switch use fat/exfat which doesn't support CHMOD. I was going to use the CHMO commands to do extra stuff though so as a client just sends commands such as CHMOD 755, I was going to write in some code so that when that gets issued it can scan the file/directory you selected on to fix the archive bit. That would be handy for those people that needed to fix stuff if they had used a mac to upload a game or something to the switch sd card.

Anyway I've nearly figured out the way to change the archive bit now thanks to that homebrew link I posted above, I'll have that working in a couple of hours as I need to go out now.
 
is all we really need.
No. Actually you need a way to CLEAR that bit, because all of the issues are caused by wrongly SET that flag, not it absense. And there are no way to clear that flag.
Post automatically merged:

ix stuff if they had used a mac to upload a game
Why ever waste twice space and time to upload game files and only after that install it when you can install it directly?
 
Why ever waste twice space and time to upload game files and only after that install it when you can install it directly?
DDR200 and other proprietary fast SD card reader shenanigans. Personally I've had this migrating NCAs at rest to a larger card too. But yeah, it would be much more useful if we had the Switch equivalent of custom-install.
 
Why ever waste twice space and time to upload game files and only after that install it when you can install it directly?
I think that when those mac users tried to install homebrew files or update hekate/atmospherenx or some other stuff apart from games they were still having issues.

Also when a user in the settings page archives a game it still exists on the sd card (until something else overwrites the data) but doesn't show up (but the icon remains on the screen), they can use hekate to change the archive bit for it to show up again as far as I understand. Can the same be done for example if they forgot to update sigpatches and tried to run a game and got the corrupt error shown? can they fix that error by modding the archive bit or is that done elsewhere? Surely a flag is set on some meta file or something?
 
Also when a user in the settings page archives a game it still exists on the sd card (until something else overwrites the data) but doesn't show up (but the icon remains on the screen), they can use hekate to change the archive bit for it to show up again as far as I understand.
No. You do not understand archiving game at all. When archiving HOS deletes nca and change app install type to archived.
Post automatically merged:

Can the same be done for example if they forgot to update sigpatches and tried to run a game and got the corrupt error shown? can they fix that error by modding the archive bit or is that done elsewhere? Surely a flag is set on some meta file or something?
No. Corrupted error also triggered by app install type and can easily be changed through appropriate ns call
 
  • Like
Reactions: littlesmurf
After getting an AI to read through the libnx api, it reported back that Since libnx does not support setting real archive bits, we will simulate it by modifying the file’s last modified time.

This is the program it gave me to solve the problem:

Code:
#include <switch.h>
#include <stdio.h>
#include <string.h>

#define TARGET_FILE "/switch/test/myfile.txt"  // Change this to your file

// Function to modify the file timestamp (workaround for archive bit)
Result SetArchiveBit(const char* path, bool enable) {
    Result rc = 0;
    FsFileSystem sdmc;
    FsFile file;
    s64 fileSize;

    // Open SD card file system
    rc = fsOpenSdCardFileSystem(&sdmc);
    if (R_FAILED(rc)) {
        printf("[ERROR] Failed to open SD card file system!\n");
        return rc;
    }

    // Try opening the file
    rc = fsFsOpenFile(&sdmc, path, FsOpenMode_Read | FsOpenMode_Write, &file);
    if (R_FAILED(rc)) {
        printf("[ERROR] Failed to open file: %s\n", path);
        fsFsClose(&sdmc);
        return rc;
    }

    // Get file size
    rc = fsFileGetSize(&file, &fileSize);
    if (R_FAILED(rc)) {
        printf("[ERROR] Failed to get file size: %s\n", path);
        fsFileClose(&file);
        fsFsClose(&sdmc);
        return rc;
    }

    // Modify the file to simulate an archive bit (writing last byte to itself)
    if (fileSize > 0) {
        u8 lastByte;
        u64 offset = fileSize - 1;
        u64 bytesRead;

        // Read last byte
        rc = fsFileRead(&file, offset, &lastByte, 1, FsReadOption_None, &bytesRead);
        if (R_SUCCEEDED(rc) && bytesRead == 1) {
            // Write the same last byte back to update the timestamp
            rc = fsFileWrite(&file, offset, &lastByte, 1, FsWriteOption_None);
            if (R_SUCCEEDED(rc)) {
                printf("[INFO] Archive bit %s for: %s (timestamp updated)\n", enable ? "enabled" : "disabled", path);
            }
            else {
                printf("[ERROR] Failed to modify file to simulate archive bit: %s\n", path);
            }
        }
    }

    // Close file and filesystem
    fsFileClose(&file);
    fsFsClose(&sdmc);
    return rc;
}

// Main entry point
int main(int argc, char* argv[]) {
    consoleInit(NULL);

    printf("Nintendo Switch Archive Bit Setter (Workaround Version)\n");
    printf("Press A to set archive bit, B to clear it, + to exit.\n");

    padConfigureInput(1, HidNpadStyleSet_NpadStandard);
    PadState pad;
    padInitializeDefault(&pad);

    while (appletMainLoop()) {
        padUpdate(&pad);
        u64 kDown = padGetButtonsDown(&pad);

        if (kDown & HidNpadButton_A) {
            printf("[INFO] Setting archive bit...\n");
            SetArchiveBit(TARGET_FILE, true);
        }

        if (kDown & HidNpadButton_B) {
            printf("[INFO] Clearing archive bit...\n");
            SetArchiveBit(TARGET_FILE, false);
        }

        if (kDown & HidNpadButton_Plus) break; // Exit program

        consoleUpdate(NULL);
    }

    consoleExit(NULL);
    return 0;
}

It compiles fine and seems to work. Maybe someone with a mac and a switch can test it. I'll post the nro if anyone wants to test.
 
OK. I hope all your software will be written by AI.
I don't care how it's written, I can write part of it and AI can write another part. As long as it does what I want is all I care about. Now I can review the new code it just gave me with added stuff from what's above and I can implement that into other software I am making. I just saved myself hours of reading and the AI gave me a workaround I never thought about.
 
Yeah, I don't think it does. The archive bit is not the read only bit. It's the "I am not yet archived by an archive tool written in the 90s" bit.
I can have a look at that and see what I can come up with, AI is making a GUI just now using plutonium and recursive directory stuff.

Just out of interest here's a good explanation from chatGPT about the issue:

The Archive Bit Issue on Nintendo Switch

When you copy homebrew files (such as .nro or .nsp) from a Mac to a microSD card, the files sometimes don't work until they are "fixed" using Hekate or another homebrew tool. The reason is related to a missing archive bit in the file system.

What Bit Gets Changed?

Hekate fixes this by setting the "archive" attribute on the affected files. This is a file attribute used in FAT32/exFAT file systems to indicate that a file has been modified and should be backed up.
  • On Windows, this archive bit is typically set automatically when a file is created or modified.
  • On macOS, when you copy files to an exFAT or FAT32 formatted microSD card, macOS does not always set this archive bit correctly.
The Nintendo Switch's homebrew loader (part of Atmosphère's hbloader or hbmenu) expects this archive bit to be set. If it’s missing, the Switch may refuse to recognize or execute the .nro files.

How Does Hekate Fix It?

Hekate includes a function that can scan the microSD card and set the archive bit on all files and folders. This makes them properly recognized by the Switch.

How to Fix It Without Hekate

If you want to avoid this issue without using Hekate, you can manually set the archive bit from a different system:

On Windows (Command Prompt)

  1. Open Command Prompt (cmd).
  2. Navigate to your microSD card drive (e.g., E:).
  3. Run:
    sh
    CopyEdit
    attrib +A /S /D
    This sets the archive bit on all files and folders.

On macOS (Terminal)

macOS doesn’t provide an easy way to set the archive bit directly, but a workaround is to copy files using rsync:
sh
CopyEdit
rsync -r --archive --chmod=ugo+rwx /source/path /destination/path
Or you can copy files using a Windows or Linux system, which should preserve the attribute.

Conclusion

The archive bit is a legacy FAT32/exFAT file attribute that macOS does not always set correctly. The Switch's homebrew loader requires it to be set to recognize homebrew apps. Hekate and similar tools fix this by manually setting the archive bit for all files on the microSD card.
Let me know if you need more details!

4o
 
Last edited by littlesmurf,

Site & Scene News

Popular threads in this forum