Skip to content

Commit

Permalink
[FS] Proper 8.3 filename conversion hopefully
Browse files Browse the repository at this point in the history
  • Loading branch information
wheremyfoodat committed Jul 16, 2023
1 parent eb90151 commit 50742f7
Showing 1 changed file with 86 additions and 11 deletions.
97 changes: 86 additions & 11 deletions src/core/kernel/directory_operations.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#include <array>
#include <cctype>
#include <filesystem>
#include <string>
#include <utility>

#include "kernel.hpp"

Expand All @@ -9,6 +13,79 @@ namespace DirectoryOps {
};
}

// Helper to convert std::string to an 8.3 filename to mimic how Directory::Read works
using ShortFilename = std::array<char, 9>;
using ShortExtension = std::array<char, 4>;
using Filename83 = std::pair<ShortFilename, ShortExtension>;

// The input string should be the stem and extension together, not separately
// Eg something like "boop.png", "panda.txt", etc
Filename83 convertTo83(const std::string& path) {
ShortFilename filename;
ShortExtension extension;

// Convert a character to add it to the 8.3 name
// "Characters such as + are changed to the underscore _, and letters are put in uppercase"
// For now we put letters in uppercase until we find out what is supposed to be converted to _ and so on
auto convertCharacter = [](char c) { return (char) std::toupper(c); };

// List of forbidden character for 8.3 filenames, from Citra
// TODO: Use constexpr when C++20 support is solid
const std::string forbiddenChars = ".\"/\\[]:;=, ";

// By default space-initialize the whole name, append null terminator in the end for both the filename and extension
filename.fill(' ');
extension.fill(' ');
filename[filename.size() - 1] = '\0';
extension[extension.size() - 1] = '\0';

// Find the position of the dot in the string
auto dotPos = path.rfind('.');
// Wikipedia: If a file name has no extension, a trailing . has no effect
// Thus check if the last character is a dot and ignore it, prefering the previous dot if it exists
if (dotPos == path.size() - 1) {
dotPos = path.rfind('.', dotPos); // Get previous dot
}

// If pointPos is not npos we have a valid dot character, and as such an extension
bool haveExtension = dotPos != std::string::npos;
int validCharacterCount = 0;
bool filenameTooBig = false;

// Parse characters until we're done OR until we reach 9 characters, in which case according to Wikipedia we must truncate to 6 letters
// And append ~1 in the end
for (auto c : path.substr(0, dotPos)) {
// Character is forbidden, we must ignore it
if (forbiddenChars.find(c) != std::string::npos) {
continue;
}

// We already have capped the amount of characters, thus our filename is too big
if (validCharacterCount == filename.size()) {
filenameTooBig = true;
break;
}
filename[validCharacterCount++] = convertCharacter(c); // Append character to filename
}

// Truncate name to 6 characters and denote that it is too big
// TODO: Wikipedia says we should also do this if the filename contains an invalid character, including spaces. Must test
if (filenameTooBig) {
filename[6] = '~';
filename[7] = '1';
}

if (haveExtension) {
int extensionLen = 0;
// Copy up to 3 characters from the dot onwards to the extension
for (auto c : path.substr(dotPos + 1, 3)) {
extension[extensionLen++] = convertCharacter(c);
}
}

return {filename, extension};
}

void Kernel::handleDirectoryOperation(u32 messagePointer, Handle directory) {
const u32 cmd = mem.read32(messagePointer);
switch (cmd) {
Expand All @@ -30,7 +107,6 @@ void Kernel::closeDirectory(u32 messagePointer, Handle directory) {
mem.write32(messagePointer + 4, Result::Success);
}


void Kernel::readDirectory(u32 messagePointer, Handle directory) {
const u32 entryCount = mem.read32(messagePointer + 4);
const u32 outPointer = mem.read32(messagePointer + 12);
Expand All @@ -52,13 +128,12 @@ void Kernel::readDirectory(u32 messagePointer, Handle directory) {
while (count < entryCount && session->currentEntry < session->entries.size()) {
const auto& entry = session->entries[session->currentEntry];
std::filesystem::path path = entry.path;
std::filesystem::path extension = path.extension();
std::filesystem::path filename = path.filename();

std::filesystem::path relative = path.lexically_relative(dirPath);
bool isDirectory = std::filesystem::is_directory(relative);

std::u16string nameU16 = relative.u16string();
std::string nameString = relative.string();
std::string extensionString = extension.string();

const u32 entryPointer = outPointer + (count * 0x228); // 0x228 is the size of a single entry
u32 utfPointer = entryPointer;
Expand All @@ -67,27 +142,27 @@ void Kernel::readDirectory(u32 messagePointer, Handle directory) {
u32 attributePointer = entryPointer + 0x21C;
u32 sizePointer = entryPointer + 0x220;

std::string filenameString = filename.string();
auto [shortFilename, shortExtension] = convertTo83(filenameString);

for (auto c : nameU16) {
mem.write16(utfPointer, u16(c));
utfPointer += sizeof(u16);
}
mem.write16(utfPointer, 0); // Null terminate the UTF16 name

for (auto c : nameString) {
//if (c == '.') continue; // Ignore initial dot

// Write 8.3 filename-extension
for (auto c : shortFilename) {
mem.write8(namePointer, u8(c));
namePointer += sizeof(u8);
}
mem.write8(namePointer, 0); // Null terminate 8.3 name

for (auto c : extensionString) {
for (auto c : shortExtension) {
mem.write8(extensionPointer, u8(c));
extensionPointer += sizeof(u8);
}
mem.write8(extensionPointer, 0); // Null terminate 8.3 extension
mem.write8(outPointer + 0x21A, 1); // Always 1 according to 3DBrew

mem.write8(outPointer + 0x21A, 1); // Always 1 according to 3DBrew
mem.write8(attributePointer, entry.isDirectory ? 1 : 0); // "Is directory" attribute

count++; // Increment number of read directories
Expand Down

0 comments on commit 50742f7

Please sign in to comment.