Use UTF-8 exclusively internally; replace iconv with OSUTF

Filenames are now unconditionally encoded/decoded as
Shift-JIS, which provides a reasonable fallback to
7-bit ASCII.
This commit is contained in:
Luke Street 2021-06-22 15:16:56 -04:00
parent a525f60775
commit c1635245b8
27 changed files with 3855 additions and 749 deletions

4
.gitignore vendored
View File

@ -4,3 +4,7 @@ version.h
.DS_Store
*.autosave
docs/*
.idea/
cmake-build-*
build/
out/

View File

@ -9,7 +9,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
_SCL_SECURE_NO_DEPRECATE=1 _CRT_NONSTDC_NO_WARNINGS=1
_ENABLE_EXTENDED_ALIGNED_STORAGE=1 NOMINMAX=1)
add_compile_options(/IGNORE:4221 /wd4018 /wd4800 /wd4005 /wd4311 /wd4068
/wd4267 /wd4244 /wd4200 /wd4305 /wd4067 /wd4146 /wd4309 /wd4805 ${VS_OPTIONS})
/wd4267 /wd4244 /wd4200 /wd4305 /wd4067 /wd4146 /wd4309 /wd4805 /utf-8 ${VS_OPTIONS})
add_compile_options(
# Disable exceptions

View File

@ -34,7 +34,7 @@ a content pipeline using the `nod::DiscBuilderBase` interface.
```cpp
/* Sample logging lambda for progress feedback */
size_t lastIdx = -1;
auto progFunc = [&](size_t idx, const nod::SystemString& name, size_t bytes)
auto progFunc = [&](size_t idx, const std::string& name, size_t bytes)
{
if (idx != lastIdx)
{

View File

@ -3,9 +3,11 @@ add_executable(nodtool main.cpp)
target_link_libraries(nodtool nod logvisor)
if (NOT WIN32)
target_link_libraries(nodtool pthread)
if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
target_link_libraries(nodtool execinfo)
else()
else ()
target_link_libraries(nodtool dl)
endif()
endif()
endif ()
else ()
target_sources(nodtool PRIVATE app.manifest)
endif ()

9
driver/app.manifest Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity type="win32" name="..." version="6.0.0.0"/>
<application>
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
</assembly>

View File

@ -15,16 +15,8 @@
#include <nod/DiscWii.hpp>
#include <nod/nod.hpp>
constexpr std::array<nod::Codepage_t, 3> codepages = {
CP_US_ASCII,
CP_UTF8,
CP_SHIFT_JIS,
};
static void printHelp() {
fmt::print(stderr, FMT_STRING(_SYS_STR(
fmt::print(stderr, FMT_STRING(
"Usage:\n"
" nodtool extract [options] <image-in> [<dir-out>]\n"
" nodtool makegcn [options] <fsroot-in> [<image-out>]\n"
@ -33,65 +25,40 @@ static void printHelp() {
" nodtool mergewii [options] <fsroot-in> <image-in> [<image-out>]\n"
"Options:\n"
" -f Force (extract only)\n"
" -v Verbose details (extract only).\n"
" -c <val> Set multi-byte character set of image(s).\n"
"Available codepage values:\n"
" 0 7-bit ASCII (default)\n"
" 1 UTF-8\n"
" 2 Shift-JIS\n")));
" -v Verbose details (extract only).\n"));
}
#if NOD_UCS2
#ifdef strcasecmp
#undef strcasecmp
#endif
#define strcasecmp _wcsicmp
#if _MSC_VER
#include <nowide/args.hpp>
#define PRISize "Iu"
int wmain(int argc, wchar_t* argv[])
int main(int argc, char* argv[]) {
nowide::args _(argc, argv);
#else
#define PRISize "zu"
int main(int argc, char* argv[])
int main(int argc, char* argv[]) {
#endif
{
/* Enable logging to console */
logvisor::RegisterStandardExceptions();
logvisor::RegisterConsoleLogger();
#if _WIN32
_setmode(_fileno(stdin), _O_U16TEXT);
_setmode(_fileno(stdout), _O_U16TEXT);
_setmode(_fileno(stderr), _O_U16TEXT);
#endif
int argidx = 1;
nod::SystemString errand;
std::string errand;
bool verbose = false;
nod::Codepage_t discLocale = CP_US_ASCII;
nod::ExtractionContext ctx = {true, [&](nod::SystemStringView str, float c) {
nod::ExtractionContext ctx = {true, [&](std::string_view str, float c) {
if (verbose)
fmt::print(stderr, FMT_STRING(_SYS_STR("Current node: {}, Extraction {:g}% Complete\n")),
fmt::print(stderr, FMT_STRING("Current node: {}, Extraction {:g}% Complete\n"),
str, c * 100.f);
}};
while (argidx < argc) {
if (!strcasecmp(argv[argidx], _SYS_STR("-f"))) {
if (!nod::StrCaseCmp(argv[argidx], "-f")) {
ctx.force = true;
++argidx;
continue;
} else if (!strcasecmp(argv[argidx], _SYS_STR("-v"))) {
} else if (!nod::StrCaseCmp(argv[argidx], "-v")) {
verbose = true;
++argidx;
continue;
} else if (!strcasecmp(argv[argidx], _SYS_STR("-c"))) {
if (argidx+1 < argc) {
unsigned long cpidx = nod::StrToUL(argv[argidx + 1], NULL, 0);
if (cpidx > codepages.size() - 1)
nod::LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("Unavailable codepage: {}")), cpidx);
discLocale = codepages[cpidx];
} else {
printHelp();
return 1;
}
argidx += 2;
continue;
} else if (errand.empty()) {
errand = argv[argidx];
++argidx;
@ -106,18 +73,18 @@ int main(int argc, char* argv[])
return 1;
}
auto progFunc = [&](float prog, nod::SystemStringView name, size_t bytes) {
fmt::print(FMT_STRING(_SYS_STR("\r ")));
auto progFunc = [&](float prog, std::string_view name, size_t bytes) {
fmt::print(FMT_STRING("\r "));
if (bytes != SIZE_MAX)
fmt::print(FMT_STRING(_SYS_STR("\r{:g}% {} {} B")), prog * 100.f, name, bytes);
fmt::print(FMT_STRING("\r{:g}% {} {} B"), prog * 100.f, name, bytes);
else
fmt::print(FMT_STRING(_SYS_STR("\r{:g}% {}")), prog * 100.f, name);
fmt::print(FMT_STRING("\r{:g}% {}"), prog * 100.f, name);
fflush(stdout);
};
if (errand == _SYS_STR("extract")) {
nod::SystemString imageIn;
nod::SystemString dirOut;
if (errand == "extract") {
std::string imageIn;
std::string dirOut;
while (argidx < argc) {
if (imageIn.empty()) {
imageIn = argv[argidx];
@ -133,10 +100,10 @@ int main(int argc, char* argv[])
}
}
if (dirOut.empty())
dirOut = _SYS_STR(".");
dirOut = ".";
bool isWii;
std::unique_ptr<nod::DiscBase> disc = nod::OpenDiscFromImage(imageIn, isWii, discLocale);
std::unique_ptr<nod::DiscBase> disc = nod::OpenDiscFromImage(imageIn, isWii);
if (!disc)
return 1;
@ -148,9 +115,9 @@ int main(int argc, char* argv[])
if (!dataPart->extractToDirectory(dirOut, ctx))
return 1;
} else if (errand == _SYS_STR("makegcn")) {
nod::SystemString fsrootIn;
nod::SystemString imageOut;
} else if (errand == "makegcn") {
std::string fsrootIn;
std::string imageOut;
while (argidx < argc) {
if (fsrootIn.empty()) {
fsrootIn = argv[argidx];
@ -166,29 +133,29 @@ int main(int argc, char* argv[])
}
}
if (imageOut.empty())
imageOut = fsrootIn + _SYS_STR(".gcm");
imageOut = fsrootIn + ".gcm";
/* Pre-validate path */
nod::Sstat theStat;
if (nod::Stat(fsrootIn.c_str(), &theStat) || !S_ISDIR(theStat.st_mode)) {
nod::LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("unable to stat {} as directory")), fsrootIn);
nod::LogModule.report(logvisor::Error, FMT_STRING("unable to stat {} as directory"), fsrootIn);
return 1;
}
if (!nod::DiscBuilderGCN::CalculateTotalSizeRequired(fsrootIn, discLocale))
if (!nod::DiscBuilderGCN::CalculateTotalSizeRequired(fsrootIn))
return 1;
nod::EBuildResult ret;
nod::DiscBuilderGCN b(imageOut, progFunc, discLocale);
nod::DiscBuilderGCN b(imageOut, progFunc);
ret = b.buildFromDirectory(fsrootIn);
fmt::print(FMT_STRING(_SYS_STR("\n")));
fmt::print(FMT_STRING("\n"));
if (ret != nod::EBuildResult::Success)
return 1;
} else if (errand == _SYS_STR("makewii")) {
nod::SystemString fsrootIn;
nod::SystemString imageOut;
} else if (errand == "makewii") {
std::string fsrootIn;
std::string imageOut;
while (argidx < argc) {
if (fsrootIn.empty()) {
fsrootIn = argv[argidx];
@ -204,31 +171,31 @@ int main(int argc, char* argv[])
}
}
if (imageOut.empty())
imageOut = fsrootIn + _SYS_STR(".iso");
imageOut = fsrootIn + ".iso";
/* Pre-validate path */
nod::Sstat theStat;
if (nod::Stat(fsrootIn.c_str(), &theStat) || !S_ISDIR(theStat.st_mode)) {
nod::LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("unable to stat {} as directory")), fsrootIn);
nod::LogModule.report(logvisor::Error, FMT_STRING("unable to stat {} as directory"), fsrootIn);
return 1;
}
bool dual = false;
if (!nod::DiscBuilderWii::CalculateTotalSizeRequired(fsrootIn, dual, discLocale))
if (!nod::DiscBuilderWii::CalculateTotalSizeRequired(fsrootIn, dual))
return 1;
nod::EBuildResult ret;
nod::DiscBuilderWii b(imageOut, dual, progFunc, discLocale);
nod::DiscBuilderWii b(imageOut, dual, progFunc);
ret = b.buildFromDirectory(fsrootIn);
fmt::print(FMT_STRING(_SYS_STR("\n")));
fmt::print(FMT_STRING("\n"));
if (ret != nod::EBuildResult::Success)
return 1;
} else if (errand == _SYS_STR("mergegcn")) {
nod::SystemString fsrootIn;
nod::SystemString imageIn;
nod::SystemString imageOut;
} else if (errand == "mergegcn") {
std::string fsrootIn;
std::string imageIn;
std::string imageOut;
while (argidx < argc) {
if (fsrootIn.empty()) {
fsrootIn = argv[argidx];
@ -248,45 +215,45 @@ int main(int argc, char* argv[])
}
}
if (imageOut.empty())
imageOut = fsrootIn + _SYS_STR(".gcm");
imageOut = fsrootIn + ".gcm";
/* Pre-validate paths */
nod::Sstat theStat;
if (nod::Stat(fsrootIn.c_str(), &theStat) || !S_ISDIR(theStat.st_mode)) {
nod::LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("unable to stat {} as directory")), fsrootIn);
nod::LogModule.report(logvisor::Error, FMT_STRING("unable to stat {} as directory"), fsrootIn);
return 1;
}
if (nod::Stat(imageIn.c_str(), &theStat) || !S_ISREG(theStat.st_mode)) {
nod::LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("unable to stat {} as file")), imageIn);
nod::LogModule.report(logvisor::Error, FMT_STRING("unable to stat {} as file"), imageIn);
return 1;
}
bool isWii;
std::unique_ptr<nod::DiscBase> disc = nod::OpenDiscFromImage(imageIn, isWii, discLocale);
std::unique_ptr<nod::DiscBase> disc = nod::OpenDiscFromImage(imageIn, isWii);
if (!disc) {
nod::LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("unable to open image {}")), imageIn);
nod::LogModule.report(logvisor::Error, FMT_STRING("unable to open image {}"), imageIn);
return 1;
}
if (isWii) {
nod::LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("Wii images should be merged with 'mergewii'")));
nod::LogModule.report(logvisor::Error, FMT_STRING("Wii images should be merged with 'mergewii'"));
return 1;
}
if (!nod::DiscMergerGCN::CalculateTotalSizeRequired(static_cast<nod::DiscGCN&>(*disc), fsrootIn, discLocale))
if (!nod::DiscMergerGCN::CalculateTotalSizeRequired(static_cast<nod::DiscGCN&>(*disc), fsrootIn))
return 1;
nod::EBuildResult ret;
nod::DiscMergerGCN b(imageOut, static_cast<nod::DiscGCN&>(*disc), progFunc, discLocale);
nod::DiscMergerGCN b(imageOut, static_cast<nod::DiscGCN&>(*disc), progFunc);
ret = b.mergeFromDirectory(fsrootIn);
fmt::print(FMT_STRING(_SYS_STR("\n")));
fmt::print(FMT_STRING("\n"));
if (ret != nod::EBuildResult::Success)
return 1;
} else if (errand == _SYS_STR("mergewii")) {
nod::SystemString fsrootIn;
nod::SystemString imageIn;
nod::SystemString imageOut;
} else if (errand == "mergewii") {
std::string fsrootIn;
std::string imageIn;
std::string imageOut;
while (argidx < argc) {
if (fsrootIn.empty()) {
fsrootIn = argv[argidx];
@ -306,47 +273,47 @@ int main(int argc, char* argv[])
}
}
if (imageOut.empty())
imageOut = fsrootIn + _SYS_STR(".iso");
imageOut = fsrootIn + ".iso";
/* Pre-validate paths */
nod::Sstat theStat;
if (nod::Stat(fsrootIn.c_str(), &theStat) || !S_ISDIR(theStat.st_mode)) {
nod::LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("unable to stat {} as directory")), fsrootIn);
nod::LogModule.report(logvisor::Error, FMT_STRING("unable to stat {} as directory"), fsrootIn);
return 1;
}
if (nod::Stat(imageIn.c_str(), &theStat) || !S_ISREG(theStat.st_mode)) {
nod::LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("unable to stat {} as file")), imageIn);
nod::LogModule.report(logvisor::Error, FMT_STRING("unable to stat {} as file"), imageIn);
return 1;
}
bool isWii;
std::unique_ptr<nod::DiscBase> disc = nod::OpenDiscFromImage(imageIn, isWii, discLocale);
std::unique_ptr<nod::DiscBase> disc = nod::OpenDiscFromImage(imageIn, isWii);
if (!disc) {
nod::LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("unable to open image {}")), argv[3]);
nod::LogModule.report(logvisor::Error, FMT_STRING("unable to open image {}"), argv[3]);
return 1;
}
if (!isWii) {
nod::LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("GameCube images should be merged with 'mergegcn'")));
nod::LogModule.report(logvisor::Error, FMT_STRING("GameCube images should be merged with 'mergegcn'"));
return 1;
}
bool dual = false;
if (!nod::DiscMergerWii::CalculateTotalSizeRequired(static_cast<nod::DiscWii&>(*disc), fsrootIn, dual, discLocale))
if (!nod::DiscMergerWii::CalculateTotalSizeRequired(static_cast<nod::DiscWii&>(*disc), fsrootIn, dual))
return 1;
nod::EBuildResult ret;
nod::DiscMergerWii b(imageOut, static_cast<nod::DiscWii&>(*disc), dual, progFunc, discLocale);
nod::DiscMergerWii b(imageOut, static_cast<nod::DiscWii&>(*disc), dual, progFunc);
ret = b.mergeFromDirectory(fsrootIn);
fmt::print(FMT_STRING(_SYS_STR("\n")));
fmt::print(FMT_STRING("\n"));
if (ret != nod::EBuildResult::Success)
return 1;
} else {
printHelp();
return 1;
}
nod::LogModule.report(logvisor::Info, FMT_STRING(_SYS_STR("Success!")));
nod::LogModule.report(logvisor::Info, FMT_STRING("Success!"));
return 0;
}

View File

@ -9,46 +9,44 @@
namespace nod {
/**
* @brief Case-insensitive comparator for std::map sorting
*/
struct CaseInsensitiveCompare {
// Allow heterogeneous lookup with maps that use this comparator.
using is_transparent = void;
bool operator()(std::string_view lhs, std::string_view rhs) const {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), [](char lhs, char rhs) {
return std::tolower(static_cast<unsigned char>(lhs)) < std::tolower(static_cast<unsigned char>(rhs));
});
}
#if _WIN32
bool operator()(std::wstring_view lhs, std::wstring_view rhs) const {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), [](wchar_t lhs, wchar_t rhs) {
return std::towlower(lhs) < std::towlower(rhs);
});
}
#endif
};
class DirectoryEnumerator {
public:
enum class Mode { Native, DirsSorted, FilesSorted, DirsThenFilesSorted };
struct Entry {
SystemString m_path;
SystemString m_name;
std::string m_path;
std::string m_name;
size_t m_fileSz;
bool m_isDir;
Entry(const SystemString& path, const SystemChar* name, size_t sz, bool isDir)
: m_path(path), m_name(name), m_fileSz(sz), m_isDir(isDir) {}
Entry(std::string path, std::string name, size_t sz, bool isDir)
: m_path(std::move(path)), m_name(std::move(name)), m_fileSz(sz), m_isDir(isDir) {}
};
private:
std::vector<Entry> m_entries;
public:
DirectoryEnumerator(SystemStringView path, Mode mode = Mode::DirsThenFilesSorted, bool sizeSort = false,
DirectoryEnumerator(std::string_view path, Mode mode = Mode::DirsThenFilesSorted, bool sizeSort = false,
bool reverse = false, bool noHidden = false);
operator bool() const { return m_entries.size() != 0; }
size_t size() const { return m_entries.size(); }
std::vector<Entry>::const_iterator begin() const { return m_entries.cbegin(); }
std::vector<Entry>::const_iterator end() const { return m_entries.cend(); }
explicit operator bool() const { return !m_entries.empty(); }
[[nodiscard]] size_t size() const { return m_entries.size(); }
[[nodiscard]] std::vector<Entry>::const_iterator begin() const { return m_entries.cbegin(); }
[[nodiscard]] std::vector<Entry>::const_iterator end() const { return m_entries.cend(); }
};
} // namespace nod

View File

@ -12,16 +12,17 @@
#include "nod/IDiscIO.hpp"
#include "nod/IFileIO.hpp"
#include "nod/OSUTF.h"
#include "nod/Util.hpp"
namespace nod {
using FProgress = std::function<void(float totalProg, SystemStringView fileName, size_t fileBytesXfered)>;
using FProgress = std::function<void(float totalProg, std::string_view fileName, size_t fileBytesXfered)>;
enum class EBuildResult { Success, Failed, DiskFull };
enum class PartitionKind : uint32_t { Data, Update, Channel };
const SystemChar* getKindString(PartitionKind kind);
const char* getKindString(PartitionKind kind);
class FSTNode {
uint32_t typeAndNameOffset;
@ -236,7 +237,7 @@ public:
return end();
}
bool extractToDirectory(SystemStringView basePath, const ExtractionContext& ctx, Codepage_t codepage = CP_US_ASCII) const;
bool extractToDirectory(std::string_view basePath, const ExtractionContext& ctx) const;
};
class IPartition {
@ -275,7 +276,6 @@ protected:
PartitionKind m_kind;
uint64_t m_offset;
bool m_isWii;
Codepage_t m_codepage;
public:
mutable size_t m_curNodeIdx = 0;
@ -290,8 +290,8 @@ public:
return m_curNodeIdx / float(getNodeCount());
}
IPartition(const DiscBase& parent, PartitionKind kind, bool isWii, uint64_t offset, Codepage_t codepage)
: m_parent(parent), m_kind(kind), m_offset(offset), m_isWii(isWii), m_codepage(codepage) {}
IPartition(const DiscBase& parent, PartitionKind kind, bool isWii, uint64_t offset)
: m_parent(parent), m_kind(kind), m_offset(offset), m_isWii(isWii) {}
virtual uint64_t normalizeOffset(uint64_t anOffset) const { return anOffset; }
PartitionKind getKind() const { return m_kind; }
bool isWii() const { return m_isWii; }
@ -308,7 +308,7 @@ public:
}
const Node& getFSTRoot() const { return m_nodes[0]; }
Node& getFSTRoot() { return m_nodes[0]; }
bool extractToDirectory(SystemStringView path, const ExtractionContext& ctx);
bool extractToDirectory(std::string_view path, const ExtractionContext& ctx);
uint64_t getDOLSize() const { return m_dolSz; }
std::unique_ptr<uint8_t[]> getDOLBuf() const {
@ -334,8 +334,8 @@ public:
size_t getNodeCount() const { return m_nodes.size(); }
const Header& getHeader() const { return m_header; }
const BI2Header& getBI2() const { return m_bi2Header; }
virtual bool extractCryptoFiles(SystemStringView path, const ExtractionContext& ctx) const { return true; }
bool extractSysFiles(SystemStringView path, const ExtractionContext& ctx) const;
virtual bool extractCryptoFiles(std::string_view path, const ExtractionContext& ctx) const { return true; }
bool extractSysFiles(std::string_view path, const ExtractionContext& ctx) const;
};
class DiscBase {
@ -373,12 +373,12 @@ public:
return nullptr;
}
void extractToDirectory(SystemStringView path, const ExtractionContext& ctx) {
void extractToDirectory(std::string_view path, const ExtractionContext& ctx) {
for (std::unique_ptr<IPartition>& part : m_partitions)
part->extractToDirectory(path, ctx);
}
virtual bool extractDiscHeaderFiles(SystemStringView path, const ExtractionContext& ctx) const = 0;
virtual bool extractDiscHeaderFiles(std::string_view path, const ExtractionContext& ctx) const = 0;
};
class DiscBuilderBase {
@ -390,30 +390,30 @@ public:
virtual ~PartitionBuilderBase() = default;
protected:
std::unordered_map<SystemString, std::pair<uint64_t, uint64_t>> m_fileOffsetsSizes;
std::unordered_map<std::string, std::pair<uint64_t, uint64_t>> m_fileOffsetsSizes;
std::vector<FSTNode> m_buildNodes;
std::vector<std::string> m_buildNames;
size_t m_buildNameOff = 0;
virtual uint64_t userAllocate(uint64_t reqSz, IPartWriteStream& ws) = 0;
virtual uint32_t packOffset(uint64_t offset) const = 0;
void recursiveBuildNodesPre(SystemStringView dirIn);
bool recursiveBuildNodes(IPartWriteStream& ws, bool system, SystemStringView dirIn);
void recursiveBuildNodesPre(std::string_view dirIn);
bool recursiveBuildNodes(IPartWriteStream& ws, bool system, std::string_view dirIn);
bool recursiveBuildFST(SystemStringView dirIn, std::function<void(void)> incParents, size_t parentDirIdx);
bool recursiveBuildFST(std::string_view dirIn, std::function<void(void)> incParents, size_t parentDirIdx);
void recursiveMergeNodesPre(const Node* nodeIn, SystemStringView dirIn);
bool recursiveMergeNodes(IPartWriteStream& ws, bool system, const Node* nodeIn, SystemStringView dirIn,
SystemStringView keyPath);
bool recursiveMergeFST(const Node* nodeIn, SystemStringView dirIn, std::function<void(void)> incParents,
size_t parentDirIdx, SystemStringView keyPath);
void recursiveMergeNodesPre(const Node* nodeIn, std::string_view dirIn);
bool recursiveMergeNodes(IPartWriteStream& ws, bool system, const Node* nodeIn, std::string_view dirIn,
std::string_view keyPath);
bool recursiveMergeFST(const Node* nodeIn, std::string_view dirIn, std::function<void(void)> incParents,
size_t parentDirIdx, std::string_view keyPath);
static bool RecursiveCalculateTotalSize(uint64_t& totalSz, const Node* nodeIn, SystemStringView dirIn, Codepage_t codepage);
static bool RecursiveCalculateTotalSize(uint64_t& totalSz, const Node* nodeIn, std::string_view dirIn);
void addBuildName(SystemStringView str) {
SystemToDiscLocConv nameView(str, m_codepage);
m_buildNames.emplace_back(nameView.disc_str());
m_buildNameOff += nameView.disc_str().size() + 1;
void addBuildName(std::string_view str) {
UTF8ToSJIS nameView(str);
m_buildNames.emplace_back(nameView.str());
m_buildNameOff += nameView.str().size() + 1;
}
DiscBuilderBase& m_parent;
@ -421,20 +421,19 @@ public:
uint64_t m_dolOffset = 0;
uint64_t m_dolSize = 0;
bool m_isWii;
Codepage_t m_codepage;
public:
PartitionBuilderBase(DiscBuilderBase& parent, PartitionKind kind, bool isWii, Codepage_t codepage)
: m_parent(parent), m_kind(kind), m_isWii(isWii), m_codepage(codepage) {}
PartitionBuilderBase(DiscBuilderBase& parent, PartitionKind kind, bool isWii)
: m_parent(parent), m_kind(kind), m_isWii(isWii) {}
virtual std::unique_ptr<IPartWriteStream> beginWriteStream(uint64_t offset) = 0;
bool buildFromDirectory(IPartWriteStream& ws, SystemStringView dirIn);
static std::optional<uint64_t> CalculateTotalSizeBuild(SystemStringView dirIn, PartitionKind kind, bool isWii, Codepage_t codepage);
bool mergeFromDirectory(IPartWriteStream& ws, const IPartition* partIn, SystemStringView dirIn);
static std::optional<uint64_t> CalculateTotalSizeMerge(const IPartition* partIn, SystemStringView dirIn, Codepage_t codepage);
bool buildFromDirectory(IPartWriteStream& ws, std::string_view dirIn);
static std::optional<uint64_t> CalculateTotalSizeBuild(std::string_view dirIn, PartitionKind kind, bool isWii);
bool mergeFromDirectory(IPartWriteStream& ws, const IPartition* partIn, std::string_view dirIn);
static std::optional<uint64_t> CalculateTotalSizeMerge(const IPartition* partIn, std::string_view dirIn);
};
protected:
SystemString m_outPath;
std::string m_outPath;
std::unique_ptr<IFileIO> m_fileIO;
std::vector<std::unique_ptr<PartitionBuilderBase>> m_partitions;
int64_t m_discCapacity;
@ -457,7 +456,7 @@ public:
}
virtual ~DiscBuilderBase() = default;
DiscBuilderBase(SystemStringView outPath, int64_t discCapacity, FProgress progressCB)
DiscBuilderBase(std::string_view outPath, int64_t discCapacity, FProgress progressCB)
: m_outPath(outPath)
, m_fileIO(NewFileIO(outPath, discCapacity))
, m_discCapacity(discCapacity)

View File

@ -7,20 +7,20 @@ class DiscBuilderGCN;
class DiscGCN : public DiscBase {
friend class DiscMergerGCN;
DiscBuilderGCN makeMergeBuilder(SystemStringView outPath, FProgress progressCB, Codepage_t codepage);
DiscBuilderGCN makeMergeBuilder(std::string_view outPath, FProgress progressCB);
public:
DiscGCN(std::unique_ptr<IDiscIO>&& dio, bool& err, Codepage_t codepage = CP_US_ASCII);
bool extractDiscHeaderFiles(SystemStringView path, const ExtractionContext& ctx) const override;
DiscGCN(std::unique_ptr<IDiscIO>&& dio, bool& err);
bool extractDiscHeaderFiles(std::string_view path, const ExtractionContext& ctx) const override;
};
class DiscBuilderGCN : public DiscBuilderBase {
friend class DiscMergerGCN;
public:
DiscBuilderGCN(SystemStringView outPath, FProgress progressCB, Codepage_t codepage = CP_US_ASCII);
EBuildResult buildFromDirectory(SystemStringView dirIn);
static std::optional<uint64_t> CalculateTotalSizeRequired(SystemStringView dirIn, Codepage_t codepage = CP_US_ASCII);
DiscBuilderGCN(std::string_view outPath, FProgress progressCB);
EBuildResult buildFromDirectory(std::string_view dirIn);
static std::optional<uint64_t> CalculateTotalSizeRequired(std::string_view dirIn);
};
class DiscMergerGCN {
@ -28,9 +28,9 @@ class DiscMergerGCN {
DiscBuilderGCN m_builder;
public:
DiscMergerGCN(SystemStringView outPath, DiscGCN& sourceDisc, FProgress progressCB, Codepage_t codepage = CP_US_ASCII);
EBuildResult mergeFromDirectory(SystemStringView dirIn);
static std::optional<uint64_t> CalculateTotalSizeRequired(DiscGCN& sourceDisc, SystemStringView dirIn, Codepage_t codepage = CP_US_ASCII);
DiscMergerGCN(std::string_view outPath, DiscGCN& sourceDisc, FProgress progressCB);
EBuildResult mergeFromDirectory(std::string_view dirIn);
static std::optional<uint64_t> CalculateTotalSizeRequired(DiscGCN& sourceDisc, std::string_view dirIn);
};
} // namespace nod

View File

@ -7,16 +7,16 @@ class DiscBuilderWii;
class DiscWii : public DiscBase {
public:
DiscWii(std::unique_ptr<IDiscIO>&& dio, bool& err, Codepage_t codepage = CP_US_ASCII);
DiscBuilderWii makeMergeBuilder(SystemStringView outPath, bool dualLayer, FProgress progressCB, Codepage_t codepage);
bool extractDiscHeaderFiles(SystemStringView path, const ExtractionContext& ctx) const override;
DiscWii(std::unique_ptr<IDiscIO>&& dio, bool& err);
DiscBuilderWii makeMergeBuilder(std::string_view outPath, bool dualLayer, FProgress progressCB);
bool extractDiscHeaderFiles(std::string_view path, const ExtractionContext& ctx) const override;
};
class DiscBuilderWii : public DiscBuilderBase {
public:
DiscBuilderWii(SystemStringView outPath, bool dualLayer, FProgress progressCB, Codepage_t codepage = CP_US_ASCII);
EBuildResult buildFromDirectory(SystemStringView dirIn);
static std::optional<uint64_t> CalculateTotalSizeRequired(SystemStringView dirIn, bool& dualLayer, Codepage_t codepage = CP_US_ASCII);
DiscBuilderWii(std::string_view outPath, bool dualLayer, FProgress progressCB);
EBuildResult buildFromDirectory(std::string_view dirIn);
static std::optional<uint64_t> CalculateTotalSizeRequired(std::string_view dirIn, bool& dualLayer);
};
class DiscMergerWii {
@ -24,9 +24,9 @@ class DiscMergerWii {
DiscBuilderWii m_builder;
public:
DiscMergerWii(SystemStringView outPath, DiscWii& sourceDisc, bool dualLayer, FProgress progressCB, Codepage_t codepage = CP_US_ASCII);
EBuildResult mergeFromDirectory(SystemStringView dirIn);
static std::optional<uint64_t> CalculateTotalSizeRequired(DiscWii& sourceDisc, SystemStringView dirIn, bool& dualLayer, Codepage_t codepage = CP_US_ASCII);
DiscMergerWii(std::string_view outPath, DiscWii& sourceDisc, bool dualLayer, FProgress progressCB);
EBuildResult mergeFromDirectory(std::string_view dirIn);
static std::optional<uint64_t> CalculateTotalSizeRequired(DiscWii& sourceDisc, std::string_view dirIn, bool& dualLayer);
};
} // namespace nod

View File

@ -69,6 +69,6 @@ public:
virtual std::unique_ptr<IReadStream> beginReadStream(uint64_t offset) const = 0;
};
std::unique_ptr<IFileIO> NewFileIO(SystemStringView path, int64_t maxWriteSize = -1);
std::unique_ptr<IFileIO> NewFileIO(std::string_view path, int64_t maxWriteSize = -1);
} // namespace nod

104
include/nod/OSUTF.h Normal file
View File

@ -0,0 +1,104 @@
#ifdef __cplusplus
extern "C" {
#define OS_CONSTEXPR constexpr
#else
#include <stdint.h>
#include <stdbool.h>
#define OS_CONSTEXPR
typedef uint8_t char8_t;
typedef uint16_t char16_t;
typedef uint32_t char32_t;
#endif
OS_CONSTEXPR inline bool IsSjisLeadByte(char8_t c) { return (c > 0x80 && c < 0xa0) || (c > 0xdf && c < 0xfd); }
OS_CONSTEXPR inline bool IsSjisTrailByte(char8_t c) { return c > 0x3f && c < 0xfd && c != 0x7f; }
char32_t OSSJISToUTF32(char16_t sjis);
char16_t OSUTF32ToSJIS(char32_t utf32);
char8_t OSUTF32ToAnsi(char32_t utf32);
char16_t* OSUTF32To16(char32_t utf32, char16_t* utf16);
char8_t* OSUTF32To8(char32_t utf32, char8_t* utf8);
const char16_t* OSUTF16To32(const char16_t* utf16, char32_t* utf32);
const char8_t* OSUTF8To32(const char8_t* utf8, char32_t* utf32);
#ifdef __cplusplus
}
class SJISToUTF8 {
private:
std::string out;
public:
SJISToUTF8(std::string_view sv) {
const auto* in = reinterpret_cast<const char8_t*>(sv.data());
const auto* end = in + sv.size();
std::array<char8_t, 4> u8arr{};
while (in < end) {
if (IsSjisLeadByte(*in)) {
char16_t sjis = static_cast<char16_t>(*in) << 8 | *(in + 1);
in += 2;
char32_t utf32 = OSSJISToUTF32(sjis);
if (utf32 == 0) {
continue;
}
char8_t* u8out = u8arr.data();
char8_t* u8end = OSUTF32To8(utf32, u8out);
if (u8end == nullptr) {
continue;
}
auto length = static_cast<size_t>(u8end - u8out);
out.append(std::string_view{reinterpret_cast<char*>(u8out), length});
} else {
out.push_back(static_cast<char>(*in++));
}
}
}
[[nodiscard]] const std::string& str() const { return out; }
[[nodiscard]] std::string& str() { return out; }
[[nodiscard]] const char* c_str() const { return out.c_str(); }
};
class UTF8ToSJIS {
private:
std::string out;
public:
UTF8ToSJIS(std::string_view sv) {
const auto* in = reinterpret_cast<const char8_t*>(sv.data());
const auto* end = in + sv.size();
while (in < end) {
char32_t utf32 = 0;
const char8_t* next = OSUTF8To32(in, &utf32);
if (next == nullptr) {
utf32 = *in;
in++;
} else {
in = next;
}
char16_t sjis = OSUTF32ToSJIS(utf32);
char8_t lead = (sjis >> 8) & 0xFF;
if (IsSjisLeadByte(lead)) {
out.push_back(static_cast<char>(lead));
}
out.push_back(static_cast<char>(sjis & 0xFF));
}
}
[[nodiscard]] const std::string& str() const { return out; }
[[nodiscard]] std::string& str() { return out; }
[[nodiscard]] const char* c_str() const { return out.c_str(); }
};
#endif

View File

@ -9,6 +9,7 @@
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <nowide/stackstring.hpp>
#include <windows.h>
#if defined(WINAPI_FAMILY) && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP
#define WINDOWS_STORE 1
@ -18,7 +19,6 @@
#else
#include <cctype>
#include <cerrno>
#include <iconv.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/statvfs.h>
@ -66,7 +66,9 @@ constexpr T max(T a, T b) {
/* template-based div for flexible typing and avoiding a library call */
template <typename T>
constexpr auto div(T a, T b) {
struct DivTp { T quot, rem; };
struct DivTp {
T quot, rem;
};
return DivTp{a / b, a % b};
}
@ -74,198 +76,27 @@ constexpr auto div(T a, T b) {
extern logvisor::Module LogModule;
/* filesystem char type */
#if _WIN32 && UNICODE
#define NOD_UCS2 1
typedef struct _stat64 Sstat;
static inline int Mkdir(const wchar_t* path, int) { return _wmkdir(path); }
static inline int Stat(const wchar_t* path, Sstat* statout) { return _wstati64(path, statout); }
#if _WIN32
static inline int Mkdir(const char* path, int) {
const nowide::wstackstring str(path);
return _wmkdir(str.get());
}
using Sstat = struct ::_stat64;
static inline int Stat(const char* path, Sstat* statout) {
const nowide::wstackstring wpath(path);
return _wstat64(wpath.get(), statout);
}
#else
static inline int Mkdir(const char* path, mode_t mode) { return CreateDirectoryA(path, mode); }
typedef struct stat Sstat;
static inline int Mkdir(const char* path, mode_t mode) { return mkdir(path, mode); }
static inline int Stat(const char* path, Sstat* statout) { return stat(path, statout); }
#endif
/* String-converting views */
#if NOD_UCS2
#define CP_US_ASCII 20127
#define CP_SHIFT_JIS 932
#define CP_GB_18030 54936
#ifndef _SYS_STR
#define _SYS_STR(val) L##val
#endif
typedef wchar_t SystemChar;
typedef std::wstring SystemString;
typedef std::wstring_view SystemStringView;
typedef UINT Codepage_t;
static inline void ToLower(SystemString& str) { std::transform(str.begin(), str.end(), str.begin(), towlower); }
static inline void ToUpper(SystemString& str) { std::transform(str.begin(), str.end(), str.begin(), towupper); }
static inline size_t StrLen(const SystemChar* str) { return wcslen(str); }
static inline unsigned long StrToUL(const SystemChar* str, SystemChar** endptr, int base) {return wcstoul(str, endptr, base); }
class SystemToDiscLocConv {
std::string m_disc_str;
public:
explicit SystemToDiscLocConv(SystemStringView str, Codepage_t codepage) {
if (!IsValidCodePage(codepage))
nod::LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("Invalid Codepage ({})")), codepage);
size_t len;
bool failureState = false;
switch (codepage) {
case CP_UTF8: case CP_GB_18030:
len = WideCharToMultiByte(codepage, WC_ERR_INVALID_CHARS, str.data(), str.size(), nullptr, 0, nullptr, nullptr);
if (GetLastError() == ERROR_NO_UNICODE_TRANSLATION)
failureState = true;
break;
case CP_UTF7:
// WideCharToMultiByte cannot use WC_ERR_INVALID_CHARS nor lpUsedDefaultChar to check for a bad conversion when converting to UTF-7.
// https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte
len = WideCharToMultiByte(codepage, 0, str.data(), str.size(), nullptr, 0, nullptr, nullptr);
break;
default:
BOOL lpUsedDefaultChar = false;
len = WideCharToMultiByte(codepage, 0, str.data(), str.size(), nullptr, 0, nullptr, &lpUsedDefaultChar);
if (lpUsedDefaultChar)
failureState = true;
break;
}
m_disc_str.assign(len, '\0');
WideCharToMultiByte(codepage, 0, str.data(), str.size(), &m_disc_str[0], len, nullptr, nullptr);
if (failureState)
nod::LogModule.report(logvisor::Warning, FMT_STRING(_SYS_STR("Bad conversion to codepage {}: \"{}\"")), codepage, str);
}
std::string_view disc_str() const { return m_disc_str; }
const char* c_str() const { return m_disc_str.c_str(); }
};
class DiscLocToSystemConv {
SystemString m_sys_str;
public:
explicit DiscLocToSystemConv(std::string_view str, Codepage_t codepage) {
if (!IsValidCodePage(codepage))
nod::LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("Invalid Codepage ({})")), codepage);
bool failureState = false;
size_t len = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, str.data(), str.size(), nullptr, 0);
if (GetLastError() == ERROR_NO_UNICODE_TRANSLATION)
failureState = true;
m_sys_str.assign(len, L'\0');
MultiByteToWideChar(codepage, 0, str.data(), str.size(), &m_sys_str[0], len);
if (failureState)
// This will probably never happen, but might as well check.
nod::LogModule.report(logvisor::Warning, FMT_STRING(_SYS_STR("Bad conversion from codepage {}: \"{}\"")), codepage, m_sys_str);
}
SystemStringView sys_str() const { return m_sys_str; }
const SystemChar* c_str() const { return m_sys_str.c_str(); }
};
#else
#define CP_US_ASCII "US-ASCII"
#define CP_UTF8 "UTF-8"
#define CP_SHIFT_JIS "SHIFT-JIS"
#ifndef _SYS_STR
#define _SYS_STR(val) val
#endif
typedef char SystemChar;
typedef std::string SystemString;
typedef std::string_view SystemStringView;
typedef const char* Codepage_t;
static inline void ToLower(SystemString& str) { std::transform(str.begin(), str.end(), str.begin(), tolower); }
static inline void ToUpper(SystemString& str) { std::transform(str.begin(), str.end(), str.begin(), toupper); }
static inline size_t StrLen(const SystemChar* str) { return strlen(str); }
static inline unsigned long StrToUL(const SystemChar* str, SystemChar** endptr, int base) {return strtoul(str, endptr, base); }
static inline bool CodepageConvert(const iconv_t convDesc, std::string_view input, std::string& output)
{
bool failureState = false;
size_t const inBytes = input.size();
size_t const outBufferSize = 4 * inBytes;
std::string outBuffer;
outBuffer.resize(outBufferSize);
auto srcBuffer = input.data();
size_t srcBytes = inBytes;
auto dstBuffer = outBuffer.data();
size_t dstBytes = outBuffer.size();
while (srcBytes != 0) {
size_t const iconvResult =
#if defined(__OpenBSD__) || defined(__NetBSD__)
iconv(convDesc, reinterpret_cast<const char**>(&srcBuffer), &srcBytes, &dstBuffer, &dstBytes);
#else
iconv(convDesc, const_cast<char**>(reinterpret_cast<const char**>(&srcBuffer)), &srcBytes, &dstBuffer, &dstBytes);
#endif
if ((size_t)-1 == iconvResult) {
failureState = true;
if (EILSEQ == errno || EINVAL == errno) {
// Try to skip the bad character
if (srcBytes != 0) {
--srcBytes;
++srcBuffer;
}
} else {
break;
}
}
}
outBuffer.resize(outBufferSize - dstBytes);
outBuffer.swap(output);
return failureState;
}
class SystemToDiscLocConv {
std::string m_disc_str;
public:
explicit SystemToDiscLocConv(SystemStringView str, Codepage_t codepage) {
const iconv_t convDesc = iconv_open(codepage, CP_UTF8);
if (convDesc == (iconv_t)-1)
nod::LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("Invalid Codepage \"{}\"")), codepage);
if (CodepageConvert(convDesc, str, m_disc_str) == true)
nod::LogModule.report(logvisor::Warning, FMT_STRING(_SYS_STR("Bad conversion to codepage \"{}\": \"{}\"")), codepage, str);
iconv_close(convDesc);
}
std::string_view disc_str() const { return m_disc_str; }
const char* c_str() const { return m_disc_str.data(); }
};
class DiscLocToSystemConv {
SystemString m_sys_str;
public:
explicit DiscLocToSystemConv(std::string_view str, Codepage_t codepage) {
const iconv_t convDesc = iconv_open(CP_UTF8, codepage);
if (convDesc == (iconv_t)-1)
nod::LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("Invalid Codepage \"{}\"")), codepage);
if (CodepageConvert(convDesc, str, m_sys_str) == true)
nod::LogModule.report(logvisor::Warning, FMT_STRING(_SYS_STR("Bad conversion from codepage \"{}\": \"{}\"")), codepage, m_sys_str);
iconv_close(convDesc);
}
SystemStringView sys_str() const { return m_sys_str; }
const SystemChar* c_str() const { return m_sys_str.data(); }
};
#endif
static inline void Unlink(const SystemChar* file) {
#if NOD_UCS2
_wunlink(file);
#else
unlink(file);
#endif
}
static inline int StrCmp(const SystemChar* str1, const SystemChar* str2) {
#if NOD_UCS2
return wcscmp(str1, str2);
#else
return strcmp(str1, str2);
#endif
}
static inline int StrCaseCmp(const SystemChar* str1, const SystemChar* str2) {
#if NOD_UCS2
return _wcsicmp(str1, str2);
static inline int StrCaseCmp(const char* str1, const char* str2) {
#ifdef _MSC_VER
return _stricmp(str1, str2);
#else
return strcasecmp(str1, str2);
#endif
@ -349,9 +180,11 @@ static inline uint64_t SBig(uint64_t val) { return val; }
#endif
enum class FileLockType { None = 0, Read, Write };
static inline FILE* Fopen(const SystemChar* path, const SystemChar* mode, FileLockType lock = FileLockType::None) {
#if NOD_UCS2
FILE* fp = _wfopen(path, mode);
static inline FILE* Fopen(const char* path, const char* mode, FileLockType lock = FileLockType::None) {
#if _MSC_VER
const nowide::wstackstring wpath(path);
const nowide::wshort_stackstring wmode(mode);
FILE* fp = _wfopen(wpath.get(), wmode.get());
if (!fp)
return nullptr;
#else
@ -394,20 +227,22 @@ static inline int64_t FTell(FILE* fp) {
#endif
}
static inline bool CheckFreeSpace(const SystemChar* path, size_t reqSz) {
static inline bool CheckFreeSpace(const char* path, size_t reqSz) {
#if _WIN32
ULARGE_INTEGER freeBytes;
wchar_t buf[1024];
wchar_t* end;
DWORD ret = GetFullPathNameW(path, 1024, buf, &end);
if (!ret || ret > 1024) {
LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("GetFullPathNameW {}")), path);
const nowide::wstackstring wpath(path);
std::array<wchar_t, 1024> buf{};
wchar_t* end = nullptr;
DWORD ret = GetFullPathNameW(wpath.get(), 1024, buf.data(), &end);
if (ret == 0 || ret > 1024) {
LogModule.report(logvisor::Error, FMT_STRING("GetFullPathNameA {}"), path);
return false;
}
if (end)
if (end != nullptr) {
end[0] = L'\0';
if (!GetDiskFreeSpaceExW(buf, &freeBytes, nullptr, nullptr)) {
LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("GetDiskFreeSpaceExW {}: {}")), path, GetLastError());
}
if (!GetDiskFreeSpaceExW(buf.data(), &freeBytes, nullptr, nullptr)) {
LogModule.report(logvisor::Error, FMT_STRING("GetDiskFreeSpaceExA {}: {}"), path, GetLastError());
return false;
}
return reqSz < freeBytes.QuadPart;

View File

@ -12,10 +12,10 @@ class DiscBase;
struct ExtractionContext final {
bool force : 1;
std::function<void(nod::SystemStringView, float)> progressCB;
std::function<void(std::string_view, float)> progressCB;
};
std::unique_ptr<DiscBase> OpenDiscFromImage(SystemStringView path);
std::unique_ptr<DiscBase> OpenDiscFromImage(SystemStringView path, bool& isWii, Codepage_t codepage = CP_US_ASCII);
std::unique_ptr<DiscBase> OpenDiscFromImage(std::string_view path);
std::unique_ptr<DiscBase> OpenDiscFromImage(std::string_view path, bool& isWii);
} // namespace nod

View File

@ -10,6 +10,7 @@ add_library(nod
DiscIOWBFS.cpp
DiscWii.cpp
nod.cpp
OSUTF.c
../include/nod/aes.hpp
../include/nod/DirectoryEnumerator.hpp
@ -21,13 +22,14 @@ add_library(nod
../include/nod/nod.hpp
../include/nod/sha1.h
../include/nod/Util.hpp
../include/nod/OSUTF.h
)
target_include_directories(nod PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
)
target_link_libraries(nod PUBLIC logvisor)
target_link_libraries(nod PUBLIC $<BUILD_INTERFACE:logvisor>)
if(WIN32)
target_sources(nod PRIVATE FileIOWin32.cpp)

View File

@ -9,127 +9,157 @@
#include <cstring>
#include <map>
#include <ranges>
namespace nod {
DirectoryEnumerator::DirectoryEnumerator(SystemStringView path, Mode mode, bool sizeSort, bool reverse, bool noHidden) {
DirectoryEnumerator::DirectoryEnumerator(std::string_view path, Mode mode, bool sizeSort, bool reverse, bool noHidden) {
Sstat theStat;
if (Stat(path.data(), &theStat) || !S_ISDIR(theStat.st_mode))
if (Stat(path.data(), &theStat) || !S_ISDIR(theStat.st_mode)) {
return;
}
#if _WIN32
SystemString wc(path);
wc += _SYS_STR("/*");
std::wstring wc = nowide::widen(path);
wc += L"/*";
WIN32_FIND_DATAW d;
HANDLE dir = FindFirstFileW(wc.c_str(), &d);
if (dir == INVALID_HANDLE_VALUE)
if (dir == INVALID_HANDLE_VALUE) {
return;
}
switch (mode) {
case Mode::Native:
do {
if (!wcscmp(d.cFileName, _SYS_STR(".")) || !wcscmp(d.cFileName, _SYS_STR("..")))
if (!wcscmp(d.cFileName, L".") || !wcscmp(d.cFileName, L"..")) {
continue;
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
}
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0)) {
continue;
SystemString fp(path);
fp += _SYS_STR('/');
fp += d.cFileName;
}
std::string fileName = nowide::narrow(d.cFileName);
std::string fp(path);
fp += '/';
fp += fileName;
Sstat st;
if (Stat(fp.c_str(), &st))
continue;
size_t sz = 0;
bool isDir = false;
if (S_ISDIR(st.st_mode))
if (S_ISDIR(st.st_mode)) {
isDir = true;
else if (S_ISREG(st.st_mode))
} else if (S_ISREG(st.st_mode)) {
sz = st.st_size;
else
} else {
continue;
}
m_entries.push_back(Entry(fp, d.cFileName, sz, isDir));
m_entries.emplace_back(fp, fileName, sz, isDir);
} while (FindNextFileW(dir, &d));
break;
case Mode::DirsThenFilesSorted:
case Mode::DirsSorted: {
std::map<SystemString, Entry, CaseInsensitiveCompare> sort;