#include "prx/libSceNKWebKitRequirements/include/Sqlite.hpp" #include "prx/libc/include/General.hpp" #include #include #include #include #include #include #include #include #include namespace { constexpr int SQLITE_OK = 0; constexpr int SQLITE_ERROR = 1; constexpr int SQLITE_BUSY = 5; constexpr int SQLITE_INTERRUPT = 9; constexpr int SQLITE_MISUSE = 21; constexpr int SQLITE_AUTH = 23; constexpr int SQLITE_ROW = 100; constexpr int SQLITE_DONE = 101; constexpr int SQLITE_CONSTRAINT_UNIQUE = 2067; constexpr int SQLITE_OPEN_READWRITE = 0x2; constexpr int SQLITE_OPEN_CREATE = 0x4; constexpr int SQLITE_OPEN_URI = 0x40; constexpr int SQLITE_OPEN_MEMORY = 0x80; constexpr int SQLITE_UTF8 = 1; constexpr int SQLITE_UTF16LE = 2; constexpr int SQLITE_INTEGER = 1; constexpr int SQLITE_FLOAT = 2; constexpr int SQLITE_TEXT = 3; constexpr int SQLITE_BLOB = 4; constexpr int SQLITE_NULL = 5; constexpr int SQLITE_DENY = 1; constexpr int SQLITE_DELETE = 9; constexpr int SQLITE_CHECKPOINT_PASSIVE = 0; const auto SQLITE_STATIC = reinterpret_cast(std::intptr_t{0}); const auto SQLITE_TRANSIENT = reinterpret_cast(std::intptr_t{-1}); void Require(bool value, const char* message) { if (value) return; std::fprintf(stderr, "NKWebKit SQLite assertion failed: %s\n", message); std::abort(); } int destroyedBuffers = 0; int destroyedCollations = 0; int functionCalls = 0; int stepCalls = 0; int finalCalls = 0; int busyCalls = 0; int walCalls = 0; double lastFunctionValue = 0; void* lastFunctionApplication = nullptr; int walFrames = 0; std::string walSchema; sqlite3* interruptTarget = nullptr; void APS5_VABI FreeBuffer(void* buffer) { ++destroyedBuffers; std::memset(buffer, 0x5A, 8); std::free(buffer); } int APS5_VABI ReverseCompare(void* argument, int leftSize, const void* left, int rightSize, const void* right) { Require(*static_cast(argument) == 0x51, "collation argument"); const int common = leftSize < rightSize ? leftSize : rightSize; const int order = std::memcmp(left, right, static_cast(common)); return order ? -order : rightSize - leftSize; } void APS5_VABI DestroyCollation(void* argument) { Require(*static_cast(argument) == 0x51, "collation destroy argument"); ++destroyedCollations; } void APS5_VABI Probe(sqlite3_context* context, int count, sqlite3_value** values) { ++functionCalls; Require(count == 1, "function argument count"); lastFunctionApplication = sqlite3_user_data_nid_postfix(context); if (sqlite3_value_type_nid_postfix(values[0]) == SQLITE_TEXT) { sqlite3_result_error_nid_postfix(context, "probe rejects text", -1); return; } lastFunctionValue = sqlite3_value_double_nid_postfix(values[0]); } void APS5_VABI Interrupt(sqlite3_context*, int, sqlite3_value**) { sqlite3_interrupt_nid_postfix(interruptTarget); } void APS5_VABI CountStep(sqlite3_context* context, int, sqlite3_value**) { Require(sqlite3_user_data_nid_postfix(context) == &stepCalls, "aggregate application"); ++stepCalls; } void APS5_VABI CountFinal(sqlite3_context*) { ++finalCalls; } int APS5_VABI DenyDelete(void* argument, int action, const char*, const char*, const char*, const char*) { Require(*static_cast(argument) == 0xA7, "authorizer argument"); return action == SQLITE_DELETE ? SQLITE_DENY : SQLITE_OK; } int APS5_VABI AllowAll(void*, int, const char*, const char*, const char*, const char*) { return SQLITE_OK; } int APS5_VABI GiveUp(void* argument, int count) { Require(*static_cast(argument) == 0xB5, "busy argument"); Require(count == busyCalls, "busy count"); ++busyCalls; return 0; } int APS5_VABI RecordWal(void* argument, sqlite3*, const char* schema, int frames) { Require(*static_cast(argument) == 0xC3, "wal argument"); ++walCalls; walSchema = schema; walFrames = frames; return SQLITE_OK; } sqlite3* Open(const std::string& name) { sqlite3* db = nullptr; const int result = sqlite3_open_v2_nid_postfix(name.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); if (result != SQLITE_OK) { std::fprintf(stderr, "open %s: %d %s\n", name.c_str(), result, db ? sqlite3_errmsg_nid_postfix(db) : ""); } Require(result == SQLITE_OK, "open"); Require(db != nullptr, "open handle"); return db; } sqlite3_stmt* Prepare(sqlite3* db, const char* sql) { sqlite3_stmt* statement = nullptr; const char* tail = nullptr; Require(sqlite3_prepare_v2_nid_postfix(db, sql, -1, &statement, &tail) == SQLITE_OK, sql); Require(statement && tail && *tail == '\0', "prepare tail"); return statement; } void Execute(sqlite3* db, const char* sql) { sqlite3_stmt* statement = Prepare(db, sql); int result = SQLITE_ROW; while (result == SQLITE_ROW) { result = sqlite3_step_nid_postfix(statement); } Require(result == SQLITE_DONE, sql); Require(sqlite3_finalize_nid_postfix(statement) == SQLITE_OK, "finalize"); } void CheckStatements(sqlite3* db) { Execute(db, "CREATE TABLE t(a INTEGER, b REAL, c TEXT, d BLOB)"); sqlite3_stmt* insert = Prepare(db, "INSERT INTO t VALUES(?, ?, ?, ?)"); Require(sqlite3_bind_parameter_count_nid_postfix(insert) == 4, "parameter count"); Require(sqlite3_stmt_busy_nid_postfix(insert) == 0, "idle statement"); Require(sqlite3_bind_int64_nid_postfix(insert, 1, 0x123456789AB) == SQLITE_OK, "bind int64"); Require(sqlite3_bind_double_nid_postfix(insert, 2, 2.5) == SQLITE_OK, "bind double"); auto* text = static_cast(std::malloc(16)); std::memcpy(text, u"hé€", 8); Require(sqlite3_bind_text16_nid_postfix(insert, 3, text, 6, FreeBuffer) == SQLITE_OK, "bind text16"); Require(destroyedBuffers == 1, "text16 destructor runs after the copy"); auto* blob = static_cast(std::malloc(8)); std::memcpy(blob, "\x01\x02\x03\x00\x05\x06\x07\x08", 8); Require(sqlite3_bind_blob_nid_postfix(insert, 4, blob, 8, FreeBuffer) == SQLITE_OK, "bind blob"); Require(destroyedBuffers == 2, "blob destructor runs after the copy"); Require(sqlite3_step_nid_postfix(insert) == SQLITE_DONE, "insert"); Require(sqlite3_last_insert_rowid_nid_postfix(db) == 1, "last rowid"); Require(sqlite3_reset_nid_postfix(insert) == SQLITE_OK, "reset"); Require(sqlite3_clear_bindings_nid_postfix(insert) == SQLITE_OK, "clear bindings"); static const unsigned char fixed[3] = {9, 8, 7}; Require(sqlite3_bind_int_nid_postfix(insert, 1, -7) == SQLITE_OK, "bind int"); Require(sqlite3_bind_null_nid_postfix(insert, 2) == SQLITE_OK, "bind null"); Require(sqlite3_bind_text16_nid_postfix(insert, 3, u"z", -1, SQLITE_TRANSIENT) == SQLITE_OK, "transient"); Require(sqlite3_bind_blob_nid_postfix(insert, 4, fixed, 3, SQLITE_STATIC) == SQLITE_OK, "static"); Require(destroyedBuffers == 2, "static data is not released"); Require(sqlite3_bind_blob_nid_postfix(insert, 5, std::malloc(8), 8, FreeBuffer) != SQLITE_OK, "bad index"); Require(destroyedBuffers == 3, "destructor runs when the bind fails"); Require(sqlite3_step_nid_postfix(insert) == SQLITE_DONE, "second insert"); Require(sqlite3_total_changes_nid_postfix(db) == 2, "total changes"); Require(sqlite3_get_autocommit_nid_postfix(db) == 1, "autocommit"); Require(sqlite3_next_stmt_nid_postfix(db, nullptr) == insert, "next statement"); Require(sqlite3_finalize_nid_postfix(insert) == SQLITE_OK, "finalize insert"); const char* query = "SELECT a, b, c, d FROM t ORDER BY rowid"; sqlite3_stmt* select = Prepare(db, query); Require(std::strcmp(sqlite3_sql_nid_postfix(select), query) == 0, "statement text"); Require(sqlite3_data_count_nid_postfix(select) == 0, "no row yet"); Require(sqlite3_step_nid_postfix(select) == SQLITE_ROW, "first row"); Require(sqlite3_stmt_busy_nid_postfix(select) != 0, "busy statement"); Require(sqlite3_data_count_nid_postfix(select) == 4, "data count"); Require(sqlite3_column_type_nid_postfix(select, 0) == SQLITE_INTEGER, "integer type"); Require(sqlite3_column_int64_nid_postfix(select, 0) == 0x123456789AB, "column int64"); Require(sqlite3_column_int_nid_postfix(select, 0) == static_cast(0x456789AB), "column int"); Require(sqlite3_column_type_nid_postfix(select, 1) == SQLITE_FLOAT, "float type"); Require(sqlite3_column_double_nid_postfix(select, 1) == 2.5, "column double"); Require(sqlite3_column_type_nid_postfix(select, 2) == SQLITE_TEXT, "text type"); const auto* column = static_cast(sqlite3_column_text16_nid_postfix(select, 2)); Require(std::memcmp(column, u"hé€", 8) == 0, "column text16"); Require(sqlite3_column_bytes16_nid_postfix(select, 2) == 6, "column bytes16"); Require(sqlite3_column_type_nid_postfix(select, 3) == SQLITE_BLOB, "blob type"); Require(sqlite3_column_bytes_nid_postfix(select, 3) == 8, "column bytes"); Require(std::memcmp(sqlite3_column_blob_nid_postfix(select, 3), "\x01\x02\x03\x00\x05\x06\x07\x08", 8) == 0, "column blob"); Require(std::strcmp(sqlite3_column_decltype_nid_postfix(select, 0), "INTEGER") == 0, "decltype"); Require(std::memcmp(sqlite3_column_name16_nid_postfix(select, 2), u"c", 4) == 0, "column name16"); sqlite3_value* value = sqlite3_column_value_nid_postfix(select, 1); Require(sqlite3_value_type_nid_postfix(value) == SQLITE_FLOAT, "value type"); Require(sqlite3_value_double_nid_postfix(value) == 2.5, "value double"); Require(std::memcmp(sqlite3_value_text16_nid_postfix(value), u"2.5", 8) == 0, "value text16"); Require(sqlite3_step_nid_postfix(select) == SQLITE_ROW, "second row"); Require(sqlite3_column_int_nid_postfix(select, 0) == -7, "negative int"); Require(sqlite3_column_type_nid_postfix(select, 1) == SQLITE_NULL, "null type"); Require(std::memcmp(sqlite3_column_text16_nid_postfix(select, 2), u"z", 4) == 0, "transient text"); Require(std::memcmp(sqlite3_column_blob_nid_postfix(select, 3), fixed, 3) == 0, "static blob"); Require(sqlite3_step_nid_postfix(select) == SQLITE_DONE, "end of rows"); Require(sqlite3_finalize_nid_postfix(select) == SQLITE_OK, "finalize select"); } void CheckFunctions(sqlite3* db) { static int application = 0x42; Require(sqlite3_create_function_nid_postfix(db, "probe", 1, SQLITE_UTF8, &application, Probe, nullptr, nullptr) == SQLITE_OK, "create function"); Execute(db, "SELECT probe(4.25)"); Require(functionCalls == 1 && lastFunctionValue == 4.25, "function value"); Require(lastFunctionApplication == &application, "function application"); sqlite3_stmt* rejected = Prepare(db, "SELECT probe('x')"); Require(sqlite3_step_nid_postfix(rejected) == SQLITE_ERROR, "function error"); Require(std::strcmp(sqlite3_errmsg_nid_postfix(db), "probe rejects text") == 0, "function message"); Require(sqlite3_errcode_nid_postfix(db) == SQLITE_ERROR, "function error code"); Require(sqlite3_finalize_nid_postfix(rejected) == SQLITE_ERROR, "finalize after error"); Require(sqlite3_create_function_nid_postfix(db, "counted", 1, SQLITE_UTF8, &stepCalls, nullptr, CountStep, CountFinal) == SQLITE_OK, "create aggregate"); Execute(db, "SELECT counted(a) FROM t"); Require(stepCalls == 2 && finalCalls == 1, "aggregate calls"); Require(sqlite3_create_function_nid_postfix(db, "counted", 1, SQLITE_UTF8, nullptr, nullptr, nullptr, nullptr) == SQLITE_OK, "delete aggregate"); sqlite3_stmt* missing = nullptr; Require(sqlite3_prepare_v2_nid_postfix(db, "SELECT counted(a) FROM t", -1, &missing, nullptr) == SQLITE_ERROR, "deleted aggregate"); Require(missing == nullptr, "no statement for deleted aggregate"); interruptTarget = db; Require(sqlite3_create_function_nid_postfix(db, "stop", 0, SQLITE_UTF8, nullptr, Interrupt, nullptr, nullptr) == SQLITE_OK, "create interrupt"); sqlite3_stmt* endless = Prepare(db, "WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM n) SELECT stop() FROM n"); int result = SQLITE_ROW; while (result == SQLITE_ROW) { result = sqlite3_step_nid_postfix(endless); } Require(result == SQLITE_INTERRUPT, "interrupt"); sqlite3_finalize_nid_postfix(endless); } void CheckCollation(sqlite3* db) { static int argument = 0x51; Require(sqlite3_create_collation_v2_nid_postfix(db, "reverse", SQLITE_UTF8, &argument, ReverseCompare, DestroyCollation) == SQLITE_OK, "create collation"); Execute(db, "CREATE TABLE words(w TEXT)"); Execute(db, "INSERT INTO words VALUES('alpha'), ('gamma'), ('beta')"); sqlite3_stmt* ordered = Prepare(db, "SELECT w FROM words ORDER BY w COLLATE reverse"); const char16_t* expected[] = {u"gamma", u"beta", u"alpha"}; for (const char16_t* word : expected) { Require(sqlite3_step_nid_postfix(ordered) == SQLITE_ROW, "collation row"); const auto* text = static_cast(sqlite3_column_text16_nid_postfix(ordered, 0)); Require(std::u16string(text) == word, "collation order"); } Require(sqlite3_finalize_nid_postfix(ordered) == SQLITE_OK, "finalize collation"); Require(sqlite3_create_collation_v2_nid_postfix(db, "reverse", SQLITE_UTF16LE, &argument, ReverseCompare, DestroyCollation) == SQLITE_OK, "second encoding"); Require(sqlite3_create_collation_v2_nid_postfix(db, "invalid", 99, &argument, ReverseCompare, DestroyCollation) == SQLITE_MISUSE, "invalid collation encoding"); Require(destroyedCollations == 0, "collations live until close"); } void CheckAuthorizer(sqlite3* db) { static int argument = 0xA7; Require(sqlite3_set_authorizer_nid_postfix(db, DenyDelete, &argument) == SQLITE_OK, "set authorizer"); sqlite3_stmt* denied = nullptr; Require(sqlite3_prepare_v2_nid_postfix(db, "DELETE FROM words", -1, &denied, nullptr) == SQLITE_AUTH, "authorizer denies"); Require(denied == nullptr, "no denied statement"); Require(std::strcmp(sqlite3_errstr_nid_postfix(SQLITE_AUTH), "authorization denied") == 0, "errstr"); Require(sqlite3_set_authorizer_nid_postfix(db, nullptr, nullptr) == SQLITE_OK, "clear authorizer"); Execute(db, "DELETE FROM words WHERE w = 'beta'"); } void CheckExtendedCodes(sqlite3* db) { Execute(db, "CREATE TABLE unique_values(v INTEGER UNIQUE)"); Execute(db, "INSERT INTO unique_values VALUES(1)"); Require(sqlite3_extended_result_codes_nid_postfix(db, 1) == SQLITE_OK, "extended codes"); sqlite3_stmt* duplicate = Prepare(db, "INSERT INTO unique_values VALUES(1)"); Require(sqlite3_step_nid_postfix(duplicate) == SQLITE_CONSTRAINT_UNIQUE, "extended step code"); Require(sqlite3_errcode_nid_postfix(db) == SQLITE_CONSTRAINT_UNIQUE, "extended errcode"); sqlite3_finalize_nid_postfix(duplicate); } void CheckFileDatabase(const std::filesystem::path& directory) { const std::string name = "/wal.db"; sqlite3* writer = Open(name); Execute(writer, "PRAGMA journal_mode = WAL"); Execute(writer, "CREATE TABLE log(v INTEGER)"); static int walArgument = 0xC3; void* previous = sqlite3_wal_hook_nid_postfix(writer, RecordWal, &walArgument); Require(reinterpret_cast(previous) == 1000, "default autocheckpoint hook"); Execute(writer, "INSERT INTO log VALUES(1)"); Require(walCalls == 1 && walSchema == "main" && walFrames > 0, "wal hook"); Require(sqlite3_wal_hook_nid_postfix(writer, RecordWal, &walArgument) == &walArgument, "previous wal argument"); int logFrames = -1; int checkpointed = -1; Require(sqlite3_wal_checkpoint_v2_nid_postfix(writer, "main", SQLITE_CHECKPOINT_PASSIVE, &logFrames, &checkpointed) == SQLITE_OK, "checkpoint"); Require(logFrames >= 1 && checkpointed == logFrames, "checkpoint frames"); sqlite3* reader = Open(name); static int busyArgument = 0xB5; Require(sqlite3_busy_handler_nid_postfix(reader, GiveUp, &busyArgument) == SQLITE_OK, "busy handler"); Execute(writer, "BEGIN EXCLUSIVE"); Require(sqlite3_get_autocommit_nid_postfix(writer) == 0, "transaction open"); sqlite3_stmt* blocked = Prepare(reader, "INSERT INTO log VALUES(2)"); Require(sqlite3_step_nid_postfix(blocked) == SQLITE_BUSY, "busy"); Require(busyCalls == 1, "busy handler called"); sqlite3_finalize_nid_postfix(blocked); Require(sqlite3_busy_timeout_nid_postfix(reader, 10) == SQLITE_OK, "busy timeout"); Execute(writer, "COMMIT"); Execute(reader, "INSERT INTO log VALUES(2)"); Require(busyCalls == 1, "timeout replaces the guest handler"); Require(sqlite3_close_nid_postfix(reader) == SQLITE_OK, "close reader"); Require(sqlite3_close_nid_postfix(writer) == SQLITE_OK, "close writer"); } } int main() { Require(sqlite3_initialize_nid_postfix() == SQLITE_OK, "initialize"); sqlite3* db = Open(":memory:"); CheckStatements(db); CheckFunctions(db); CheckCollation(db); CheckAuthorizer(db); CheckExtendedCodes(db); Require(sqlite3_close_nid_postfix(db) == SQLITE_OK, "close"); Require(destroyedCollations == 2, "collation destructors on close"); sqlite3* memory = nullptr; Require(sqlite3_open_v2_nid_postfix("file::memory:", &memory, SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI, nullptr) == SQLITE_OK, "memory URI"); Execute(memory, "CREATE TABLE t(v)"); Require(sqlite3_close_nid_postfix(memory) == SQLITE_OK, "close memory URI"); std::random_device random; const auto directory = std::filesystem::temp_directory_path() / ("nk_sqlite_" + std::to_string(random())); std::filesystem::create_directories(directory); std::filesystem::current_path(directory); constexpr int uriFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI; for (const char* name : {"file:mode=memory.db?mode=rwc", "file:mode=memory.db", "file::memory:x", "file:later.db?mode=memory&mode=rwc", "file:encoded.db?mode=memory&mod%65=rwc", "file:m1.db?mode=rwc", "file:plain.db"}) { for (const int flags : {uriFlags, uriFlags | SQLITE_OPEN_MEMORY}) { sqlite3* host = nullptr; try { sqlite3_open_v2_nid_postfix(name, &host, flags, nullptr); } catch (const std::exception&) { } if (host != nullptr) sqlite3_close_nid_postfix(host); Require(std::filesystem::is_empty(directory), "a URI that only mentions mode=memory in its path creates no host file"); } } for (const char* name : {"file:one?mode=memory", "file:two?cache=shared&mode=memory", "file::memory:?cache=private", "file:three?mode=rwc&mode=memory"}) { sqlite3* inMemory = nullptr; Require(sqlite3_open_v2_nid_postfix(name, &inMemory, uriFlags, nullptr) == SQLITE_OK, "memory URI parameter"); Execute(inMemory, "CREATE TABLE t(v)"); Require(sqlite3_close_nid_postfix(inMemory) == SQLITE_OK, "close memory URI parameter"); Require(std::filesystem::is_empty(directory), "a memory URI creates no host file"); } const auto refused = [](sqlite3* connection, const char* sql) { sqlite3_stmt* statement = nullptr; const char* tail = nullptr; int result = sqlite3_prepare_v2_nid_postfix(connection, sql, -1, &statement, &tail); if (result == SQLITE_OK) { do { result = sqlite3_step_nid_postfix(statement); } while (result == SQLITE_ROW); } if (statement != nullptr) sqlite3_finalize_nid_postfix(statement); return result != SQLITE_OK && result != SQLITE_DONE; }; sqlite3* guard = Open(":memory:"); Require(refused(guard, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused on an in-memory connection"); Require(refused(guard, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused on an in-memory connection"); Require(std::filesystem::is_empty(directory), "ATTACH and VACUUM INTO create no host file"); Execute(guard, "VACUUM"); Require(sqlite3_close_nid_postfix(guard) == SQLITE_OK, "close in-memory guard"); sqlite3* limited = nullptr; Require(sqlite3_open_v2_nid_postfix("limited.db", &limited, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr) == SQLITE_OK, "open limited.db"); Require(refused(limited, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused on a file connection"); Require(refused(limited, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused on a file connection"); Require(!std::filesystem::exists(directory / "attached.db") && !std::filesystem::exists(directory / "vacuumed.db"), "ATTACH and VACUUM INTO create no host file from a file connection"); Execute(limited, "VACUUM"); Execute(limited, "ATTACH DATABASE '' AS scratch"); Execute(limited, "DETACH DATABASE scratch"); Require(sqlite3_set_authorizer_nid_postfix(limited, AllowAll, nullptr) == SQLITE_OK, "set a permissive guest authorizer"); Require(refused(limited, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused with a guest authorizer set"); Require(refused(limited, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused with a guest authorizer set"); Require(sqlite3_set_authorizer_nid_postfix(limited, nullptr, nullptr) == SQLITE_OK, "clear the guest authorizer"); Require(refused(limited, "ATTACH DATABASE 'attached.db' AS other"), "ATTACH is refused after the guest clears its authorizer"); Require(refused(limited, "VACUUM INTO 'vacuumed.db'"), "VACUUM INTO is refused after the guest clears its authorizer"); Require(!std::filesystem::exists(directory / "attached.db") && !std::filesystem::exists(directory / "vacuumed.db"), "no host file after the guest sets and clears an authorizer"); Require(sqlite3_close_nid_postfix(limited) == SQLITE_OK, "close limited.db"); std::filesystem::remove(directory / "limited.db"); CheckFileDatabase(directory); Require(std::filesystem::is_regular_file(directory / "wal.db"), "guest path maps into the root"); const auto written = WrittenPaths_nid_no_patch(); for (const char* name : {"wal.db", "wal.db-wal", "wal.db-shm", "wal.db-journal"}) { Require(std::any_of(written.begin(), written.end(), [&](const std::filesystem::path& path) { return path.filename() == name && std::filesystem::equivalent(path.parent_path(), directory); }), "database files recorded for sceKernelSync"); } std::filesystem::current_path(directory.parent_path()); std::filesystem::remove_all(directory); return 0; }