mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Correct host0: mount
This commit is contained in:
1 parent
b437ba0fc6
commit
786a1530de
13 files changed
+61
-45
No files matched your search
@@ -1443,7 +1443,7 @@ uint8_t *ReadLocalFile(const Path &filename, size_t *size) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
Fseek(file, 0, SEEK_SET);
|
Fseek(file, 0, SEEK_SET);
|
||||||
// NOTE: If you find ~10 memory leaks from here, with very varying sizes, it might be the VFPU LUTs.
|
// NOTE: If you find up to ~10-ish memory leaks from here, with very varying sizes, it might be the VFPU LUTs.
|
||||||
uint8_t *contents = new uint8_t[f_size + 1];
|
uint8_t *contents = new uint8_t[f_size + 1];
|
||||||
if (fread(contents, 1, f_size, file) != f_size) {
|
if (fread(contents, 1, f_size, file) != f_size) {
|
||||||
delete[] contents;
|
delete[] contents;
|
||||||
|
|||||||
@@ -59,21 +59,24 @@ void SetupCallbacks(AllowedCallback allowed, MessageCallback message) {
|
|||||||
|
|
||||||
void ReportMessage(const char *message, ...) {
|
void ReportMessage(const char *message, ...) {
|
||||||
const int MESSAGE_BUFFER_SIZE = 65536;
|
const int MESSAGE_BUFFER_SIZE = 65536;
|
||||||
char *temp = new char [MESSAGE_BUFFER_SIZE];
|
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, message);
|
va_start(args, message);
|
||||||
|
char *temp = new char[MESSAGE_BUFFER_SIZE];
|
||||||
vsnprintf(temp, MESSAGE_BUFFER_SIZE - 1, message, args);
|
vsnprintf(temp, MESSAGE_BUFFER_SIZE - 1, message, args);
|
||||||
temp[MESSAGE_BUFFER_SIZE - 1] = '\0';
|
temp[MESSAGE_BUFFER_SIZE - 1] = '\0';
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
if (!allowedCallback || !messageCallback) {
|
if (!allowedCallback || !messageCallback) {
|
||||||
ERROR_LOG(Log::System, "Reporting not initialized, skipping: %s", temp);
|
ERROR_LOG(Log::System, "Reporting not initialized, skipping: %s", temp);
|
||||||
|
delete[] temp;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allowedCallback())
|
if (!allowedCallback()) {
|
||||||
|
delete[] temp;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
messageCallback(message, temp);
|
messageCallback(message, temp);
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -177,7 +177,7 @@ static const CommandLineParam g_autoParams[] = {
|
|||||||
{POFF(escapeExit), CmdParamType::Bool, "escape-exit", '\0', "Escape key exits the application", CmdLineMode::Application},
|
{POFF(escapeExit), CmdParamType::Bool, "escape-exit", '\0', "Escape key exits the application", CmdLineMode::Application},
|
||||||
{POFF(pauseMenuExit), CmdParamType::Bool, "pause-menu-exit", '\0', "Change \"Exit to menu\" in pause menu to \"Exit\"", CmdLineMode::Application},
|
{POFF(pauseMenuExit), CmdParamType::Bool, "pause-menu-exit", '\0', "Change \"Exit to menu\" in pause menu to \"Exit\"", CmdLineMode::Application},
|
||||||
{POFF(appendConfig), CmdParamType::String, "appendconfig", '\0', "Merge config FILE into the current configuration"},
|
{POFF(appendConfig), CmdParamType::String, "appendconfig", '\0', "Merge config FILE into the current configuration"},
|
||||||
{POFF(root), CmdParamType::String, "root", 'r', "Mount root directory"},
|
{POFF(root), CmdParamType::String, "root", 'r', "Mount directory as the root of host0:/."},
|
||||||
{POFF(memStick), CmdParamType::String, "memstick", '\0', "Memory stick root directory (contains PSP/GAME etc)"},
|
{POFF(memStick), CmdParamType::String, "memstick", '\0', "Memory stick root directory (contains PSP/GAME etc)"},
|
||||||
{POFF(stateToLoad), CmdParamType::String, "state", '\0', "Load state from specified file"},
|
{POFF(stateToLoad), CmdParamType::String, "state", '\0', "Load state from specified file"},
|
||||||
{POFF(compare), CmdParamType::Bool, "compare", 'c', "Enable comparison mode", CmdLineMode::Headless},
|
{POFF(compare), CmdParamType::Bool, "compare", 'c', "Enable comparison mode", CmdLineMode::Headless},
|
||||||
@@ -538,7 +538,8 @@ void CommandLineOptions::ApplyToConfig() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (root.has_value()) {
|
if (root.has_value()) {
|
||||||
g_Config.DoNotSaveSetting(&g_Config.mountRoot);
|
// No DoNotSaveSetting() here - mountRoot isn't an ordinary setting and never gets
|
||||||
|
// written to ppsspp.ini, since the ini itself lives inside the memory stick directory.
|
||||||
g_Config.mountRoot = Path(root.value());
|
g_Config.mountRoot = Path(root.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -57,7 +57,8 @@ struct CommandLineOptions {
|
|||||||
std::optional<bool> bootVSH;
|
std::optional<bool> bootVSH;
|
||||||
|
|
||||||
std::optional<std::string> appendConfig;
|
std::optional<std::string> appendConfig;
|
||||||
std::optional<std::string> root; // mount root, needs more explanation
|
std::optional<std::string> root; // This is supposed to configure host0:.
|
||||||
|
|
||||||
// Memory stick root (the directory containing PSP/GAME, PSP/SYSTEM, ...). Mainly for headless,
|
// Memory stick root (the directory containing PSP/GAME, PSP/SYSTEM, ...). Mainly for headless,
|
||||||
// which otherwise always uses "memstick" next to the executable - so testing a real game there
|
// which otherwise always uses "memstick" next to the executable - so testing a real game there
|
||||||
// meant copying it in. Points at the same layout the app uses, so the two can share one.
|
// meant copying it in. Points at the same layout the app uses, so the two can share one.
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ struct CoreParameter {
|
|||||||
|
|
||||||
Path fileToStart;
|
Path fileToStart;
|
||||||
Path mountIso; // If non-empty, and fileToStart is an ELF or PBP, will mount this ISO in the background to umd1:.
|
Path mountIso; // If non-empty, and fileToStart is an ELF or PBP, will mount this ISO in the background to umd1:.
|
||||||
Path mountRoot; // If non-empty, and fileToStart is an ELF or PBP, mount this as host0: / umd0:.
|
Path mountRoot; // If non-empty, and fileToStart is an ELF or PBP, mount this as host0:.
|
||||||
|
|
||||||
std::string errorString;
|
std::string errorString;
|
||||||
bool loadGameConfigs = true;
|
bool loadGameConfigs = true;
|
||||||
|
|
||||||
|
|||||||
@@ -466,8 +466,10 @@ size_t DirectoryFileHandle::Seek(s32 position, FileMove type)
|
|||||||
|
|
||||||
LARGE_INTEGER distance;
|
LARGE_INTEGER distance;
|
||||||
distance.QuadPart = position;
|
distance.QuadPart = position;
|
||||||
LARGE_INTEGER cursor;
|
LARGE_INTEGER cursor{};
|
||||||
SetFilePointerEx(hFile, distance, &cursor, moveMethod);
|
if (!SetFilePointerEx(hFile, distance, &cursor, moveMethod)) {
|
||||||
|
ERROR_LOG(Log::IO, "DirectoryFileHandle::Seek(%d, %d) failed", position, (int)type);
|
||||||
|
}
|
||||||
result = (size_t)cursor.QuadPart;
|
result = (size_t)cursor.QuadPart;
|
||||||
#else
|
#else
|
||||||
int moveMethod = 0;
|
int moveMethod = 0;
|
||||||
|
|||||||
@@ -194,48 +194,34 @@ int MetaFileSystem::MapFilePath(std::string_view _inpath, std::string *outpath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special handling: host0:command.txt (as seen in Super Monkey Ball Adventures, for example)
|
// Special handling: host0:command.txt (as seen in Super Monkey Ball Adventures, for example)
|
||||||
// appears to mean the current directory on the UMD. Let's just assume the current directory.
|
// appears to mean the current directory on the UMD.
|
||||||
if (strncasecmp(inpath.c_str(), "host0:", strlen("host0:")) == 0) {
|
if (startsWithNoCase(inpath.c_str(), "host0:") && !host0Mapped_) {
|
||||||
INFO_LOG(Log::FileSystem, "Host0 path detected, stripping: %s", inpath.c_str());
|
|
||||||
// However, this causes trouble when running tests, since our test framework uses host0:.
|
|
||||||
// Maybe it's really just supposed to map to umd0 or something?
|
|
||||||
if (PSP_CoreParameter().headLess) {
|
|
||||||
inpath = "umd0:" + inpath.substr(strlen("host0:"));
|
inpath = "umd0:" + inpath.substr(strlen("host0:"));
|
||||||
} else {
|
|
||||||
inpath = inpath.substr(strlen("host0:"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string *currentDirectory = &startingDirectory;
|
const std::string *currentDirectory = &startingDirectory;
|
||||||
|
|
||||||
|
// Hm, does this make sense? Doesn't each drive has its own currentDir per thread, or maybe not?
|
||||||
int currentThread = __KernelGetCurThread();
|
int currentThread = __KernelGetCurThread();
|
||||||
currentDir_t::iterator it = currentDir.find(currentThread);
|
currentDir_t::iterator it = currentDir.find(currentThread);
|
||||||
if (it == currentDir.end())
|
if (it == currentDir.end()) {
|
||||||
{
|
// Attempt to emulate SCE_KERNEL_ERROR_NOCWD / 8002032C: may break things requiring fixes elsewhere
|
||||||
//Attempt to emulate SCE_KERNEL_ERROR_NOCWD / 8002032C: may break things requiring fixes elsewhere
|
if (inpath.find(':') == std::string::npos /* means path is relative */) {
|
||||||
if (inpath.find(':') == std::string::npos /* means path is relative */)
|
|
||||||
{
|
|
||||||
error = SCE_KERNEL_ERROR_NOCWD;
|
error = SCE_KERNEL_ERROR_NOCWD;
|
||||||
WARN_LOG(Log::FileSystem, "Path is relative, but current directory not set for thread %i. returning 8002032C(SCE_KERNEL_ERROR_NOCWD) instead.", currentThread);
|
WARN_LOG(Log::FileSystem, "Path is relative, but current directory not set for thread %i. returning 8002032C(SCE_KERNEL_ERROR_NOCWD) instead.", currentThread);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
currentDirectory = &(it->second);
|
currentDirectory = &(it->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RealPath(*currentDirectory, inpath, realpath))
|
if (RealPath(*currentDirectory, inpath, realpath)) {
|
||||||
{
|
|
||||||
std::string prefix = realpath;
|
std::string prefix = realpath;
|
||||||
size_t prefixPos = realpath.find(':');
|
size_t prefixPos = realpath.find(':');
|
||||||
if (prefixPos != realpath.npos)
|
if (prefixPos != realpath.npos)
|
||||||
prefix = NormalizePrefix(realpath.substr(0, prefixPos + 1));
|
prefix = NormalizePrefix(realpath.substr(0, prefixPos + 1));
|
||||||
|
|
||||||
for (size_t i = 0; i < fileSystems.size(); i++)
|
for (size_t i = 0; i < fileSystems.size(); i++) {
|
||||||
{
|
if (equalsNoCase(fileSystems[i].prefix, prefix)) {
|
||||||
size_t prefLen = fileSystems[i].prefix.size();
|
|
||||||
if (strncasecmp(fileSystems[i].prefix.c_str(), prefix.c_str(), prefLen) == 0)
|
|
||||||
{
|
|
||||||
// Map into the underlying filesystem. If the mount specifies a subDir,
|
// Map into the underlying filesystem. If the mount specifies a subDir,
|
||||||
// join that with the path inside the device.
|
// join that with the path inside the device.
|
||||||
std::string basePath = realpath.substr(prefixPos + 1); // may be empty or start with '/'
|
std::string basePath = realpath.substr(prefixPos + 1); // may be empty or start with '/'
|
||||||
@@ -297,6 +283,10 @@ void MetaFileSystem::Mount(std::string_view prefix, std::shared_ptr<IFileSystem>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (equalsNoCase(prefix, "host0:")) {
|
||||||
|
host0Mapped_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Prefix not yet mounted, do so.
|
// Prefix not yet mounted, do so.
|
||||||
MountPoint x;
|
MountPoint x;
|
||||||
x.prefix = prefix;
|
x.prefix = prefix;
|
||||||
@@ -309,10 +299,14 @@ void MetaFileSystem::Mount(std::string_view prefix, std::shared_ptr<IFileSystem>
|
|||||||
void MetaFileSystem::UnmountAll() {
|
void MetaFileSystem::UnmountAll() {
|
||||||
fileSystems.clear();
|
fileSystems.clear();
|
||||||
currentDir.clear();
|
currentDir.clear();
|
||||||
|
host0Mapped_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetaFileSystem::Unmount(std::string_view prefix) {
|
void MetaFileSystem::Unmount(std::string_view prefix) {
|
||||||
std::lock_guard<std::recursive_mutex> guard(lock);
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
||||||
|
if (equalsNoCase(prefix, "host0:")) {
|
||||||
|
host0Mapped_ = false;
|
||||||
|
}
|
||||||
for (auto iter = fileSystems.begin(); iter != fileSystems.end(); iter++) {
|
for (auto iter = fileSystems.begin(); iter != fileSystems.end(); iter++) {
|
||||||
if (iter->prefix == prefix) {
|
if (iter->prefix == prefix) {
|
||||||
fileSystems.erase(iter);
|
fileSystems.erase(iter);
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ private:
|
|||||||
std::string startingDirectory;
|
std::string startingDirectory;
|
||||||
mutable std::recursive_mutex lock; // must be recursive. TODO: fix that
|
mutable std::recursive_mutex lock; // must be recursive. TODO: fix that
|
||||||
|
|
||||||
|
// For the UMD host0 hack.
|
||||||
|
bool host0Mapped_ = false;
|
||||||
|
|
||||||
// Assumes the lock is held
|
// Assumes the lock is held
|
||||||
void Reset() {
|
void Reset() {
|
||||||
// This used to be 6, probably an attempt to replicate PSP handles.
|
// This used to be 6, probably an attempt to replicate PSP handles.
|
||||||
|
|||||||
@@ -1816,7 +1816,6 @@ static void __KernelStartModule(PSPModule *m, int args, const char *argp, SceKer
|
|||||||
__KernelSetThreadRA(threadID, NID_MODULERETURN);
|
__KernelSetThreadRA(threadID, NID_MODULERETURN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
u32 __KernelGetModuleGP(SceUID uid) {
|
u32 __KernelGetModuleGP(SceUID uid) {
|
||||||
u32 error;
|
u32 error;
|
||||||
PSPModule *module = kernelObjects.Get<PSPModule>(uid, error);
|
PSPModule *module = kernelObjects.Get<PSPModule>(uid, error);
|
||||||
|
|||||||
@@ -1243,8 +1243,12 @@ static int _PsmfPlayerSetPsmfOffset(u32 psmfPlayer, const char *filename, int of
|
|||||||
return hleDelayResult(hleLogError(Log::ME, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid file data or does not exist"), "psmfplayer set", delayUs);
|
return hleDelayResult(hleLogError(Log::ME, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid file data or does not exist"), "psmfplayer set", delayUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offset != 0)
|
if (offset < 0) {
|
||||||
|
return hleDelayResult(hleLogError(Log::ME, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid file data or does not exist"), "psmfplayer set", delayUs);
|
||||||
|
}
|
||||||
|
|
||||||
pspFileSystem.SeekFile(psmfplayer->filehandle, offset, FILEMOVE_BEGIN);
|
pspFileSystem.SeekFile(psmfplayer->filehandle, offset, FILEMOVE_BEGIN);
|
||||||
|
|
||||||
u8 *buf = psmfplayer->tempbuf;
|
u8 *buf = psmfplayer->tempbuf;
|
||||||
int tempbufSize = (int)sizeof(psmfplayer->tempbuf);
|
int tempbufSize = (int)sizeof(psmfplayer->tempbuf);
|
||||||
int size = (int)pspFileSystem.ReadFile(psmfplayer->filehandle, buf, 2048);
|
int size = (int)pspFileSystem.ReadFile(psmfplayer->filehandle, buf, 2048);
|
||||||
|
|||||||
+13
-10
@@ -374,17 +374,17 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string_view discId, bool load
|
|||||||
path = AndroidContentURI(full_path.GetDirectory()).FilePath();
|
path = AndroidContentURI(full_path.GetDirectory()).FilePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: More robust check.
|
||||||
size_t pos = path.find("PSP/GAME/");
|
size_t pos = path.find("PSP/GAME/");
|
||||||
std::string ms_path;
|
std::string ms_path;
|
||||||
if (pos != std::string::npos) {
|
if (pos != std::string::npos) {
|
||||||
ms_path = "ms0:/" + path.substr(pos) + "/";
|
ms_path = "ms0:/" + path.substr(pos) + "/";
|
||||||
} else {
|
} else {
|
||||||
// This is wrong, but it's better than not having a working directory at all.
|
// We map host0: to the containing directory, see below. This will also be set as the current dir
|
||||||
// Note that umd0:/ is actually the writable containing directory, in this case.
|
ms_path = "host0:/";
|
||||||
ms_path = "umd0:/";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Path dir;
|
Path host0Dir;
|
||||||
if (!PSP_CoreParameter().mountRoot.empty()) {
|
if (!PSP_CoreParameter().mountRoot.empty()) {
|
||||||
// We don't want to worry about .. and cwd and such.
|
// We don't want to worry about .. and cwd and such.
|
||||||
const Path rootNorm = NormalizePath(PSP_CoreParameter().mountRoot);
|
const Path rootNorm = NormalizePath(PSP_CoreParameter().mountRoot);
|
||||||
@@ -425,28 +425,31 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string_view discId, bool load
|
|||||||
file = filepath + "/" + file;
|
file = filepath + "/" + file;
|
||||||
path = rootNorm.ToString();
|
path = rootNorm.ToString();
|
||||||
pspFileSystem.SetStartingDirectory(filepath);
|
pspFileSystem.SetStartingDirectory(filepath);
|
||||||
dir = Path(path);
|
host0Dir = Path(path);
|
||||||
} else {
|
} else {
|
||||||
pspFileSystem.SetStartingDirectory(ms_path);
|
pspFileSystem.SetStartingDirectory(ms_path);
|
||||||
dir = full_path.NavigateUp();
|
host0Dir = full_path.NavigateUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto fs = std::make_shared<DirectoryFileSystem>(&pspFileSystem, dir, FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD);
|
auto fs = std::make_shared<DirectoryFileSystem>(&pspFileSystem, host0Dir, FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD);
|
||||||
pspFileSystem.Mount("umd0:", fs);
|
pspFileSystem.Mount("host0:", fs);
|
||||||
|
|
||||||
std::string finalName = ms_path + file;
|
std::string finalName = ms_path + file;
|
||||||
|
|
||||||
std::string homebrewName = PSP_CoreParameter().fileToStart.ToVisualString();
|
std::string homebrewName = PSP_CoreParameter().fileToStart.ToVisualString();
|
||||||
std::size_t lslash = homebrewName.find_last_of('/');
|
std::size_t lslash = homebrewName.find_last_of('/');
|
||||||
std::size_t rslash = homebrewName.find_last_of('\\');
|
std::size_t rslash = homebrewName.find_last_of('\\');
|
||||||
if (lslash != homebrewName.npos)
|
if (lslash != homebrewName.npos) {
|
||||||
homebrewName = homebrewName.substr(lslash + 1);
|
homebrewName = homebrewName.substr(lslash + 1);
|
||||||
if (rslash != homebrewName.npos)
|
}
|
||||||
|
if (rslash != homebrewName.npos) {
|
||||||
homebrewName = homebrewName.substr(rslash + 1);
|
homebrewName = homebrewName.substr(rslash + 1);
|
||||||
|
}
|
||||||
std::string discID = g_paramSFO.GetDiscID();
|
std::string discID = g_paramSFO.GetDiscID();
|
||||||
std::string discVersion = g_paramSFO.GetValueString("DISC_VERSION");
|
std::string discVersion = g_paramSFO.GetValueString("DISC_VERSION");
|
||||||
std::string madeUpID = g_paramSFO.GenerateFakeID(Path());
|
std::string madeUpID = g_paramSFO.GenerateFakeID(Path());
|
||||||
|
|
||||||
|
// TODO: This was long enough ago that I think this can be safely removed.
|
||||||
// Migrate old save states from old versions of fake game IDs.
|
// Migrate old save states from old versions of fake game IDs.
|
||||||
// Ugh, this might actually be slow on Android.
|
// Ugh, this might actually be slow on Android.
|
||||||
// The strings here are attacker-controlled (from PARAM.SFO / filenames), so
|
// The strings here are attacker-controlled (from PARAM.SFO / filenames), so
|
||||||
|
|||||||
@@ -324,6 +324,9 @@ static void MountFileSystems() {
|
|||||||
|
|
||||||
pspFileSystem.Mount("flash0:", flash0System);
|
pspFileSystem.Mount("flash0:", flash0System);
|
||||||
|
|
||||||
|
// NOTE: We don't handle the host0: mount here, it's in Load_PSP_ELF_PBP.
|
||||||
|
// Additionally, host0: is remapped to umd0: if there is an UMD inserted (old hack in MetaFileSystem).
|
||||||
|
|
||||||
if (g_RemasterMode) {
|
if (g_RemasterMode) {
|
||||||
const std::string gameId = g_paramSFO.GetDiscID();
|
const std::string gameId = g_paramSFO.GetDiscID();
|
||||||
const Path exdataPath = GetSysDirectory(DIRECTORY_EXDATA) / gameId;
|
const Path exdataPath = GetSysDirectory(DIRECTORY_EXDATA) / gameId;
|
||||||
|
|||||||
@@ -887,6 +887,8 @@ int main(int argc, const char* argv[]) {
|
|||||||
|
|
||||||
graphicsContext->ShutdownAPI();
|
graphicsContext->ShutdownAPI();
|
||||||
|
|
||||||
|
delete graphicsContext;
|
||||||
|
|
||||||
if (cmdLineOptions.debuggerPort.has_value()) {
|
if (cmdLineOptions.debuggerPort.has_value()) {
|
||||||
ShutdownWebServer();
|
ShutdownWebServer();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user