mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2024-12-29 11:06:07 +00:00
kernel: It builds
This commit is contained in:
parent
c878e69270
commit
00b84b2c7f
|
@ -1,18 +1,14 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer.h"
|
||||
|
||||
#include "avplayer_impl.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/avplayer/avplayer.h"
|
||||
#include "core/libraries/avplayer/avplayer_impl.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
using namespace Kernel;
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerAddSource(SceAvPlayerHandle handle, const char* filename) {
|
||||
LOG_TRACE(Lib_AvPlayer, "filename = {}", filename);
|
||||
if (handle == nullptr) {
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
#include "common/types.h"
|
||||
|
||||
#include <stdarg.h> // va_list
|
||||
#include <stddef.h> // size_t
|
||||
#include <cstdarg> // va_list
|
||||
#include <cstddef> // size_t
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
|
|
|
@ -1,24 +1,16 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_common.h"
|
||||
#include <algorithm> // std::equal
|
||||
#include <cctype> // std::tolower
|
||||
|
||||
#include <algorithm> // std::equal
|
||||
#include <cctype> // std::tolower
|
||||
#include <string_view> // std::string_view
|
||||
#include "core/libraries/avplayer/avplayer.h"
|
||||
#include "core/libraries/avplayer/avplayer_common.h"
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
using namespace Kernel;
|
||||
|
||||
static bool ichar_equals(char a, char b) {
|
||||
return std::tolower(static_cast<unsigned char>(a)) ==
|
||||
std::tolower(static_cast<unsigned char>(b));
|
||||
}
|
||||
|
||||
static bool iequals(std::string_view l, std::string_view r) {
|
||||
return std::ranges::equal(l, r, ichar_equals);
|
||||
return std::ranges::equal(l, r, [](u8 a, u8 b) { return std::tolower(a) == std::tolower(b); });
|
||||
}
|
||||
|
||||
SceAvPlayerSourceType GetSourceType(std::string_view path) {
|
||||
|
|
|
@ -3,16 +3,14 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <queue>
|
||||
|
||||
#include "core/libraries/avplayer/avplayer.h"
|
||||
|
||||
#define AVPLAYER_IS_ERROR(x) ((x) < 0)
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_file_streamer.h"
|
||||
|
||||
#include "avplayer_common.h"
|
||||
|
||||
#include <algorithm> // std::max, std::min
|
||||
#include <magic_enum.hpp>
|
||||
#include "core/libraries/avplayer/avplayer_file_streamer.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/avio.h>
|
||||
}
|
||||
|
||||
#include <algorithm> // std::max, std::min
|
||||
|
||||
#define AVPLAYER_AVIO_BUFFER_SIZE 4096
|
||||
constexpr u32 AVPLAYER_AVIO_BUFFER_SIZE = 4096;
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
|
|
|
@ -3,11 +3,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include "core/libraries/avplayer/avplayer.h"
|
||||
#include "core/libraries/avplayer/avplayer_data_streamer.h"
|
||||
|
||||
struct AVIOContext;
|
||||
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_common.h"
|
||||
#include "avplayer_file_streamer.h"
|
||||
#include "avplayer_impl.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/avplayer/avplayer_common.h"
|
||||
#include "core/libraries/avplayer/avplayer_impl.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/libkernel.h"
|
||||
#include "core/linker.h"
|
||||
|
||||
using namespace Libraries::Kernel;
|
||||
#include "core/tls.h"
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
|
@ -19,32 +12,28 @@ void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) {
|
|||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto allocate = self->m_init_data_original.memory_replacement.allocate;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
return linker->ExecuteGuest(allocate, ptr, alignment, size);
|
||||
return Core::ExecuteGuest(allocate, ptr, alignment, size);
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) {
|
||||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto deallocate = self->m_init_data_original.memory_replacement.deallocate;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
return linker->ExecuteGuest(deallocate, ptr, memory);
|
||||
return Core::ExecuteGuest(deallocate, ptr, memory);
|
||||
}
|
||||
|
||||
void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) {
|
||||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
return linker->ExecuteGuest(allocate, ptr, alignment, size);
|
||||
return Core::ExecuteGuest(allocate, ptr, alignment, size);
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) {
|
||||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
return linker->ExecuteGuest(deallocate, ptr, memory);
|
||||
return Core::ExecuteGuest(deallocate, ptr, memory);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) {
|
||||
|
@ -53,8 +42,7 @@ int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) {
|
|||
|
||||
const auto open = self->m_init_data_original.file_replacement.open;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
return linker->ExecuteGuest(open, ptr, filename);
|
||||
return Core::ExecuteGuest(open, ptr, filename);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) {
|
||||
|
@ -63,8 +51,7 @@ int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) {
|
|||
|
||||
const auto close = self->m_init_data_original.file_replacement.close;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
return linker->ExecuteGuest(close, ptr);
|
||||
return Core::ExecuteGuest(close, ptr);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) {
|
||||
|
@ -73,8 +60,7 @@ int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position
|
|||
|
||||
const auto read_offset = self->m_init_data_original.file_replacement.readOffset;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
return linker->ExecuteGuest(read_offset, ptr, buffer, position, length);
|
||||
return Core::ExecuteGuest(read_offset, ptr, buffer, position, length);
|
||||
}
|
||||
|
||||
u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) {
|
||||
|
@ -83,8 +69,7 @@ u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) {
|
|||
|
||||
const auto size = self->m_init_data_original.file_replacement.size;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
return linker->ExecuteGuest(size, ptr);
|
||||
return Core::ExecuteGuest(size, ptr);
|
||||
}
|
||||
|
||||
SceAvPlayerInitData AvPlayer::StubInitData(const SceAvPlayerInitData& data) {
|
||||
|
|
|
@ -3,11 +3,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
#include "avplayer_state.h"
|
||||
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
#include "core/libraries/avplayer/avplayer.h"
|
||||
#include "core/libraries/avplayer/avplayer_state.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
|
@ -17,7 +14,6 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_source.h"
|
||||
|
||||
#include "avplayer_file_streamer.h"
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/kernel/time_management.h"
|
||||
#include "core/libraries/avplayer/avplayer_file_streamer.h"
|
||||
#include "core/libraries/avplayer/avplayer_source.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
|
@ -35,8 +31,6 @@ av_always_inline std::string av_err2string(int errnum) {
|
|||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
using namespace Kernel;
|
||||
|
||||
AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state, bool use_vdec2)
|
||||
: m_state(state), m_use_vdec2(use_vdec2) {}
|
||||
|
||||
|
|
|
@ -3,20 +3,18 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_common.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "core/libraries/avplayer/avplayer.h"
|
||||
#include "core/libraries/avplayer/avplayer_common.h"
|
||||
#include "core/libraries/avplayer/avplayer_data_streamer.h"
|
||||
|
||||
struct AVCodecContext;
|
||||
struct AVFormatContext;
|
||||
|
@ -139,8 +137,6 @@ public:
|
|||
bool IsActive();
|
||||
|
||||
private:
|
||||
using ScePthread = Kernel::ScePthread;
|
||||
|
||||
static void ReleaseAVPacket(AVPacket* packet);
|
||||
static void ReleaseAVFrame(AVFrame* frame);
|
||||
static void ReleaseAVCodecContext(AVCodecContext* context);
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_file_streamer.h"
|
||||
#include "avplayer_source.h"
|
||||
#include "avplayer_state.h"
|
||||
|
||||
#include "common/singleton.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/libraries/avplayer/avplayer_source.h"
|
||||
#include "core/libraries/avplayer/avplayer_state.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/time_management.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/tls.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
using namespace Kernel;
|
||||
|
||||
void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, SceAvPlayerEvents event_id,
|
||||
s32 source_id, void* event_data) {
|
||||
auto const self = reinterpret_cast<AvPlayerState*>(opaque);
|
||||
|
@ -96,8 +91,7 @@ void AvPlayerState::DefaultEventCallback(void* opaque, SceAvPlayerEvents event_i
|
|||
const auto callback = self->m_event_replacement.event_callback;
|
||||
const auto ptr = self->m_event_replacement.object_ptr;
|
||||
if (callback != nullptr) {
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
linker->ExecuteGuest(callback, ptr, event_id, 0, event_data);
|
||||
Core::ExecuteGuest(callback, ptr, event_id, 0, event_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,17 +3,14 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
#include "avplayer_source.h"
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "core/libraries/avplayer/avplayer.h"
|
||||
#include "core/libraries/avplayer/avplayer_source.h"
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
class Stream;
|
||||
|
|
|
@ -4,10 +4,9 @@
|
|||
#include "fiber.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/tls.h"
|
||||
|
||||
#ifdef _WIN64
|
||||
#include <windows.h>
|
||||
|
@ -31,9 +30,7 @@ void FiberEntry(void* param) {
|
|||
argRun = *fiber->pArgRun;
|
||||
}
|
||||
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
linker->ExecuteGuest(fiber->entry, fiber->argOnInitialize, argRun);
|
||||
|
||||
Core::ExecuteGuest(fiber->entry, fiber->argOnInitialize, argRun);
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
|
@ -281,4 +278,4 @@ void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("JzyT91ucGDc", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberRename);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Fiber
|
||||
} // namespace Libraries::Fiber
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "common/singleton.h"
|
||||
#include "core/libraries/ime/ime_dialog.h"
|
||||
#include "core/libraries/ime/ime_dialog_ui.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/tls.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
|
||||
using namespace ImGui;
|
||||
|
@ -124,9 +124,8 @@ bool ImeDialogState::CallTextFilter() {
|
|||
return false;
|
||||
}
|
||||
|
||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
int ret =
|
||||
linker->ExecuteGuest(text_filter, out_text, &out_text_length, src_text, src_text_length);
|
||||
Core::ExecuteGuest(text_filter, out_text, &out_text_length, src_text, src_text_length);
|
||||
|
||||
if (ret != 0) {
|
||||
return false;
|
||||
|
@ -147,15 +146,12 @@ bool ImeDialogState::CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16*
|
|||
return true;
|
||||
}
|
||||
|
||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
int ret = linker->ExecuteGuest(keyboard_filter, src_keycode, out_keycode, out_status, nullptr);
|
||||
|
||||
int ret = Core::ExecuteGuest(keyboard_filter, src_keycode, out_keycode, out_status, nullptr);
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
bool ImeDialogState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len,
|
||||
char* utf8_text, std::size_t utf8_text_len) {
|
||||
|
||||
std::fill(utf8_text, utf8_text + utf8_text_len, '\0');
|
||||
const ImWchar* orbis_text_ptr = reinterpret_cast<const ImWchar*>(orbis_text);
|
||||
ImTextStrToUtf8(utf8_text, utf8_text_len, orbis_text_ptr, orbis_text_ptr + orbis_text_len);
|
||||
|
@ -165,7 +161,6 @@ bool ImeDialogState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t
|
|||
|
||||
bool ImeDialogState::ConvertUTF8ToOrbis(const char* utf8_text, std::size_t utf8_text_len,
|
||||
char16_t* orbis_text, std::size_t orbis_text_len) {
|
||||
|
||||
std::fill(orbis_text, orbis_text + orbis_text_len, u'\0');
|
||||
ImTextStrFromUtf8(reinterpret_cast<ImWchar*>(orbis_text), orbis_text_len, utf8_text, nullptr);
|
||||
|
||||
|
@ -387,4 +382,4 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Libraries::ImeDialog
|
||||
} // namespace Libraries::ImeDialog
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
std::vector<Core::FileSys::DirEntry> GetDirectoryEntries(const std::filesystem::path& path) {
|
||||
auto GetDirectoryEntries(const std::filesystem::path& path) {
|
||||
std::vector<Core::FileSys::DirEntry> files;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(path)) {
|
||||
auto& dir_entry = files.emplace_back();
|
||||
|
|
|
@ -65,11 +65,6 @@ constexpr int ORBIS_KERNEL_O_DSYNC = 0x1000;
|
|||
constexpr int ORBIS_KERNEL_O_DIRECT = 0x00010000;
|
||||
constexpr int ORBIS_KERNEL_O_DIRECTORY = 0x00020000;
|
||||
|
||||
int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, /* SceKernelMode*/ u16 mode);
|
||||
|
||||
int PS4_SYSV_ABI posix_open(const char* path, int flags, /* SceKernelMode*/ u16 mode);
|
||||
s64 PS4_SYSV_ABI lseek(int d, s64 offset, int whence);
|
||||
|
||||
void RegisterFileSystem(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
#include <objbase.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#ifdef __APPLE__
|
||||
#include <date/tz.h>
|
||||
#endif
|
||||
|
|
|
@ -7,6 +7,20 @@
|
|||
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_attr_init(PthreadAttrT* attr);
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAttrT* attr,
|
||||
PthreadEntryFunc start_routine, void* arg,
|
||||
const char* name);
|
||||
|
||||
PthreadT LaunchThread(PthreadEntryFunc start_routine, void* arg, const char* name) {
|
||||
PthreadT thread{};
|
||||
PthreadAttrT attr{};
|
||||
posix_pthread_attr_init(&attr);
|
||||
posix_pthread_create_name_np(&thread, &attr, start_routine, arg, name);
|
||||
return thread;
|
||||
}
|
||||
|
||||
void RegisterThreads(Core::Loader::SymbolsResolver* sym) {
|
||||
RegisterMutex(sym);
|
||||
RegisterCond(sym);
|
||||
|
@ -14,6 +28,8 @@ void RegisterThreads(Core::Loader::SymbolsResolver* sym) {
|
|||
RegisterSemaphore(sym);
|
||||
RegisterSpec(sym);
|
||||
RegisterThreadAttr(sym);
|
||||
RegisterThread(sym);
|
||||
RegisterRtld(sym);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
|
|
@ -3,223 +3,15 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
#define ORBIS_PTHREAD_MUTEX_ADAPTIVE_INITIALIZER (reinterpret_cast<ScePthreadMutex>(1))
|
||||
#include "core/libraries/kernel/threads/threads.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
constexpr int ORBIS_KERNEL_PRIO_FIFO_DEFAULT = 700;
|
||||
constexpr int ORBIS_KERNEL_PRIO_FIFO_HIGHEST = 256;
|
||||
constexpr int ORBIS_KERNEL_PRIO_FIFO_LOWEST = 767;
|
||||
constexpr int ORBIS_KERNEL_SEM_VALUE_MAX = 0x7FFFFFFF;
|
||||
|
||||
constexpr int ORBIS_PTHREAD_MUTEX_ERRORCHECK = 1;
|
||||
constexpr int ORBIS_PTHREAD_MUTEX_RECURSIVE = 2;
|
||||
constexpr int ORBIS_PTHREAD_MUTEX_NORMAL = 3;
|
||||
constexpr int ORBIS_PTHREAD_MUTEX_ADAPTIVE = 4;
|
||||
|
||||
struct PthreadInternal;
|
||||
struct PthreadAttrInternal;
|
||||
struct PthreadMutexInternal;
|
||||
struct PthreadMutexattrInternal;
|
||||
struct PthreadCondInternal;
|
||||
struct PthreadCondAttrInternal;
|
||||
struct PthreadRwInternal;
|
||||
struct PthreadRwLockAttrInternal;
|
||||
class PthreadKeys;
|
||||
|
||||
using SceKernelSchedParam = ::sched_param;
|
||||
using ScePthread = PthreadInternal*;
|
||||
using ScePthreadAttr = PthreadAttrInternal*;
|
||||
using ScePthreadMutex = PthreadMutexInternal*;
|
||||
using ScePthreadMutexattr = PthreadMutexattrInternal*;
|
||||
using ScePthreadCond = PthreadCondInternal*;
|
||||
using ScePthreadCondattr = PthreadCondAttrInternal*;
|
||||
using OrbisPthreadRwlock = PthreadRwInternal*;
|
||||
using OrbisPthreadRwlockattr = PthreadRwLockAttrInternal*;
|
||||
using OrbisPthreadKey = u32;
|
||||
|
||||
using PthreadKeyDestructor = PS4_SYSV_ABI void (*)(void*);
|
||||
using PthreadEntryFunc = PS4_SYSV_ABI void* (*)(void*);
|
||||
|
||||
struct PthreadInternal {
|
||||
u8 reserved[4096];
|
||||
std::string name;
|
||||
pthread_t pth;
|
||||
ScePthreadAttr attr;
|
||||
PthreadEntryFunc entry;
|
||||
void* arg;
|
||||
std::atomic_bool is_started;
|
||||
std::atomic_bool is_detached;
|
||||
std::atomic_bool is_almost_done;
|
||||
std::atomic_bool is_free;
|
||||
using Destructor = std::pair<OrbisPthreadKey, PthreadKeyDestructor>;
|
||||
std::vector<Destructor> key_destructors;
|
||||
int prio;
|
||||
};
|
||||
|
||||
struct PthreadAttrInternal {
|
||||
u8 reserved[64];
|
||||
u64 affinity;
|
||||
size_t guard_size;
|
||||
int policy;
|
||||
bool detached;
|
||||
pthread_attr_t pth_attr;
|
||||
};
|
||||
|
||||
struct PthreadMutexInternal {
|
||||
u8 reserved[256];
|
||||
std::string name;
|
||||
pthread_mutex_t pth_mutex;
|
||||
};
|
||||
|
||||
struct PthreadMutexattrInternal {
|
||||
u8 reserved[64];
|
||||
pthread_mutexattr_t pth_mutex_attr;
|
||||
int pprotocol;
|
||||
};
|
||||
|
||||
struct PthreadCondInternal {
|
||||
u8 reserved[256];
|
||||
std::string name;
|
||||
pthread_cond_t cond;
|
||||
};
|
||||
|
||||
struct PthreadCondAttrInternal {
|
||||
u8 reserved[64];
|
||||
pthread_condattr_t cond_attr;
|
||||
clockid_t clock;
|
||||
};
|
||||
|
||||
struct PthreadRwLockAttrInternal {
|
||||
u8 reserved[64];
|
||||
pthread_rwlockattr_t attr_rwlock;
|
||||
int type;
|
||||
};
|
||||
|
||||
struct PthreadRwInternal {
|
||||
pthread_rwlock_t pth_rwlock;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct PthreadSemInternal {
|
||||
std::counting_semaphore<ORBIS_KERNEL_SEM_VALUE_MAX> semaphore;
|
||||
std::atomic<s32> value;
|
||||
};
|
||||
|
||||
class PThreadPool {
|
||||
public:
|
||||
ScePthread Create(const char* name);
|
||||
|
||||
private:
|
||||
std::vector<ScePthread> m_threads;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
class PThreadCxt {
|
||||
public:
|
||||
ScePthreadMutexattr* getDefaultMutexattr() {
|
||||
return &m_default_mutexattr;
|
||||
}
|
||||
void setDefaultMutexattr(ScePthreadMutexattr attr) {
|
||||
m_default_mutexattr = attr;
|
||||
}
|
||||
ScePthreadMutexattr* getAdaptiveMutexattr() {
|
||||
return &m_adaptive_mutexattr;
|
||||
}
|
||||
void setAdaptiveMutexattr(ScePthreadMutexattr attr) {
|
||||
m_adaptive_mutexattr = attr;
|
||||
}
|
||||
ScePthreadCondattr* getDefaultCondattr() {
|
||||
return &m_default_condattr;
|
||||
}
|
||||
void setDefaultCondattr(ScePthreadCondattr attr) {
|
||||
m_default_condattr = attr;
|
||||
}
|
||||
ScePthreadAttr* GetDefaultAttr() {
|
||||
return &m_default_attr;
|
||||
}
|
||||
void SetDefaultAttr(ScePthreadAttr attr) {
|
||||
m_default_attr = attr;
|
||||
}
|
||||
PThreadPool* GetPthreadPool() {
|
||||
return m_pthread_pool;
|
||||
}
|
||||
void SetPthreadPool(PThreadPool* pool) {
|
||||
m_pthread_pool = pool;
|
||||
}
|
||||
OrbisPthreadRwlockattr* getDefaultRwattr() {
|
||||
return &m_default_Rwattr;
|
||||
}
|
||||
void setDefaultRwattr(OrbisPthreadRwlockattr attr) {
|
||||
m_default_Rwattr = attr;
|
||||
}
|
||||
|
||||
private:
|
||||
ScePthreadMutexattr m_default_mutexattr = nullptr;
|
||||
ScePthreadMutexattr m_adaptive_mutexattr = nullptr;
|
||||
ScePthreadCondattr m_default_condattr = nullptr;
|
||||
ScePthreadAttr m_default_attr = nullptr;
|
||||
PThreadPool* m_pthread_pool = nullptr;
|
||||
OrbisPthreadRwlockattr m_default_Rwattr = nullptr;
|
||||
};
|
||||
|
||||
void init_pthreads();
|
||||
void pthreadInitSelfMainThread();
|
||||
|
||||
int PS4_SYSV_ABI scePthreadAttrInit(ScePthreadAttr* attr);
|
||||
int PS4_SYSV_ABI scePthreadAttrSetdetachstate(ScePthreadAttr* attr, int detachstate);
|
||||
int PS4_SYSV_ABI scePthreadAttrSetinheritsched(ScePthreadAttr* attr, int inheritSched);
|
||||
int PS4_SYSV_ABI scePthreadAttrSetschedparam(ScePthreadAttr* attr,
|
||||
const SceKernelSchedParam* param);
|
||||
int PS4_SYSV_ABI scePthreadAttrSetschedpolicy(ScePthreadAttr* attr, int policy);
|
||||
ScePthread PS4_SYSV_ABI scePthreadSelf();
|
||||
int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr,
|
||||
const /*SceKernelCpumask*/ u64 mask);
|
||||
int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpumask*/ u64 mask);
|
||||
int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u64* mask);
|
||||
int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr,
|
||||
PthreadEntryFunc start_routine, void* arg, const char* name);
|
||||
|
||||
int PS4_SYSV_ABI scePthreadSetprio(ScePthread thread, int prio);
|
||||
|
||||
/***
|
||||
* Mutex calls
|
||||
*/
|
||||
int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr,
|
||||
const char* name);
|
||||
int PS4_SYSV_ABI scePthreadMutexattrInit(ScePthreadMutexattr* attr);
|
||||
int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type);
|
||||
int PS4_SYSV_ABI scePthreadMutexattrSetprotocol(ScePthreadMutexattr* attr, int protocol);
|
||||
int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex);
|
||||
int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex);
|
||||
/****
|
||||
* Cond calls
|
||||
*/
|
||||
int PS4_SYSV_ABI scePthreadCondInit(ScePthreadCond* cond, const ScePthreadCondattr* attr,
|
||||
const char* name);
|
||||
int PS4_SYSV_ABI scePthreadCondattrInit(ScePthreadCondattr* attr);
|
||||
int PS4_SYSV_ABI scePthreadCondBroadcast(ScePthreadCond* cond);
|
||||
int PS4_SYSV_ABI scePthreadCondWait(ScePthreadCond* cond, ScePthreadMutex* mutex);
|
||||
/****
|
||||
* Posix calls
|
||||
*/
|
||||
int PS4_SYSV_ABI posix_pthread_mutex_init(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr);
|
||||
int PS4_SYSV_ABI posix_pthread_mutex_lock(ScePthreadMutex* mutex);
|
||||
int PS4_SYSV_ABI posix_pthread_mutex_unlock(ScePthreadMutex* mutex);
|
||||
int PS4_SYSV_ABI posix_pthread_cond_broadcast(ScePthreadCond* cond);
|
||||
PthreadT LaunchThread(PthreadEntryFunc start_routine, void* arg, const char* name);
|
||||
|
||||
void RegisterThreads(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
|
|
|
@ -224,6 +224,7 @@ int PS4_SYSV_ABI posix_pthread_attr_get_np(PthreadT pthread, PthreadAttrT* dstat
|
|||
if (True(pthread->flags & ThreadFlags::Detached)) {
|
||||
attr.flags |= PthreadAttrFlags::Detached;
|
||||
}
|
||||
pthread->lock->unlock();
|
||||
if (ret == 0) {
|
||||
memcpy(dst, &attr, sizeof(PthreadAttr));
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma clang optimize off
|
||||
#include <cstring>
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/libkernel.h"
|
||||
|
@ -68,6 +68,7 @@ static int InitStatic(Pthread* thread, PthreadCondT* cond) {
|
|||
} else if (cvp == THR_COND_DESTROYED) { \
|
||||
return POSIX_EINVAL; \
|
||||
} \
|
||||
cvp = *cond; \
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_cond_init(PthreadCondT* cond, const PthreadCondAttrT* cond_attr) {
|
||||
|
|
|
@ -9,22 +9,188 @@
|
|||
#include "core/libraries/kernel/threads/thread_state.h"
|
||||
#include "core/libraries/kernel/threads/threads.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
constexpr int PthreadInheritSched = 4;
|
||||
|
||||
constexpr int ORBIS_KERNEL_PRIO_FIFO_DEFAULT = 700;
|
||||
constexpr int ORBIS_KERNEL_PRIO_FIFO_HIGHEST = 256;
|
||||
constexpr int ORBIS_KERNEL_PRIO_FIFO_LOWEST = 767;
|
||||
|
||||
extern PthreadAttr PthreadAttrDefault;
|
||||
|
||||
using PthreadEntryFunc = void* (*)(void*);
|
||||
void _thread_cleanupspecific();
|
||||
|
||||
void PS4_SYSV_ABI posix_pthread_exit(void* status);
|
||||
static void ExitThread() {
|
||||
Pthread* curthread = g_curthread;
|
||||
|
||||
/* Check if there is thread specific data: */
|
||||
if (curthread->specific != nullptr) {
|
||||
/* Run the thread-specific data destructors: */
|
||||
_thread_cleanupspecific();
|
||||
}
|
||||
|
||||
auto* thread_state = ThrState::Instance();
|
||||
ASSERT(thread_state->active_threads.fetch_sub(1) != 1);
|
||||
|
||||
curthread->lock->lock();
|
||||
curthread->state = PthreadState::Dead;
|
||||
ASSERT(False(curthread->flags & ThreadFlags::NeedSuspend));
|
||||
|
||||
/*
|
||||
* Thread was created with initial refcount 1, we drop the
|
||||
* reference count to allow it to be garbage collected.
|
||||
*/
|
||||
curthread->refcount--;
|
||||
thread_state->TryCollect(curthread, curthread); /* thread lock released */
|
||||
|
||||
/*
|
||||
* Kernel will do wakeup at the address, so joiner thread
|
||||
* will be resumed if it is sleeping at the address.
|
||||
*/
|
||||
curthread->tid.store(TidTerminated);
|
||||
curthread->tid.notify_all();
|
||||
|
||||
pthread_exit(nullptr);
|
||||
UNREACHABLE();
|
||||
/* Never reach! */
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI posix_pthread_exit(void* status) {
|
||||
Pthread* curthread = g_curthread;
|
||||
|
||||
/* Check if this thread is already in the process of exiting: */
|
||||
ASSERT_MSG(!curthread->cancelling, "Thread {} has called pthread_exit from a destructor",
|
||||
fmt::ptr(curthread));
|
||||
|
||||
/* Flag this thread as exiting. */
|
||||
curthread->cancelling = 1;
|
||||
curthread->no_cancel = 1;
|
||||
curthread->cancel_async = 0;
|
||||
curthread->cancel_point = 0;
|
||||
|
||||
/* Save the return value: */
|
||||
curthread->ret = status;
|
||||
while (!curthread->cleanup.empty()) {
|
||||
PthreadCleanup* old = curthread->cleanup.front();
|
||||
curthread->cleanup.pop_front();
|
||||
old->routine(old->routine_arg);
|
||||
if (old->onheap) {
|
||||
free(old);
|
||||
}
|
||||
}
|
||||
|
||||
ExitThread();
|
||||
}
|
||||
|
||||
static int JoinThread(PthreadT pthread, void** thread_return, const OrbisKernelTimespec* abstime) {
|
||||
Pthread* curthread = g_curthread;
|
||||
|
||||
if (pthread == nullptr) {
|
||||
return POSIX_EINVAL;
|
||||
}
|
||||
|
||||
if (pthread == curthread) {
|
||||
return POSIX_EDEADLK;
|
||||
}
|
||||
|
||||
auto* thread_state = ThrState::Instance();
|
||||
if (int ret = thread_state->FindThread(pthread, 1); ret != 0) {
|
||||
return POSIX_ESRCH;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
if (True(pthread->flags & ThreadFlags::Detached)) {
|
||||
ret = POSIX_EINVAL;
|
||||
} else if (pthread->joiner != nullptr) {
|
||||
/* Multiple joiners are not supported. */
|
||||
ret = POSIX_ENOTSUP;
|
||||
}
|
||||
if (ret) {
|
||||
pthread->lock->unlock();
|
||||
return ret;
|
||||
}
|
||||
/* Set the running thread to be the joiner: */
|
||||
pthread->joiner = curthread;
|
||||
pthread->lock->unlock();
|
||||
|
||||
const auto backout_join = [](void* arg) {
|
||||
Pthread* pthread = (Pthread*)arg;
|
||||
std::scoped_lock lk{*pthread->lock};
|
||||
pthread->joiner = nullptr;
|
||||
};
|
||||
|
||||
PthreadCleanup cup{backout_join, pthread, 0};
|
||||
curthread->cleanup.push_front(&cup);
|
||||
|
||||
//_thr_cancel_enter(curthread);
|
||||
|
||||
const int tid = pthread->tid;
|
||||
while (pthread->tid.load() != TidTerminated) {
|
||||
//_thr_testcancel(curthread);
|
||||
ASSERT(abstime == nullptr);
|
||||
pthread->tid.wait(tid);
|
||||
}
|
||||
|
||||
//_thr_cancel_leave(curthread, 0);
|
||||
curthread->cleanup.pop_front();
|
||||
|
||||
if (ret == POSIX_ETIMEDOUT) {
|
||||
backout_join(pthread);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* tmp = pthread->ret;
|
||||
pthread->lock->lock();
|
||||
pthread->flags |= ThreadFlags::Detached;
|
||||
pthread->joiner = nullptr;
|
||||
thread_state->TryCollect(curthread, pthread); /* thread lock released */
|
||||
if (thread_return != nullptr) {
|
||||
*thread_return = tmp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return) {
|
||||
return JoinThread(pthread, thread_return, NULL);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_timedjoin_np(PthreadT pthread, void** thread_return,
|
||||
const OrbisKernelTimespec* abstime) {
|
||||
if (abstime == nullptr || abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
|
||||
abstime->tv_nsec >= 1000000000) {
|
||||
return POSIX_EINVAL;
|
||||
}
|
||||
|
||||
return JoinThread(pthread, thread_return, abstime);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_detach(PthreadT pthread) {
|
||||
if (pthread == nullptr) {
|
||||
return POSIX_EINVAL;
|
||||
}
|
||||
|
||||
auto* thread_state = ThrState::Instance();
|
||||
if (int ret = thread_state->FindThread(pthread, 1); ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Check if the thread is already detached or has a joiner. */
|
||||
if (True(pthread->flags & ThreadFlags::Detached) || (pthread->joiner != NULL)) {
|
||||
pthread->lock->unlock();
|
||||
return POSIX_EINVAL;
|
||||
}
|
||||
|
||||
/* Flag the thread as detached. */
|
||||
pthread->flags |= ThreadFlags::Detached;
|
||||
thread_state->TryCollect(g_curthread, pthread); /* thread lock released */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void RunThread(Pthread* curthread) {
|
||||
g_curthread = curthread;
|
||||
|
@ -32,8 +198,7 @@ static void RunThread(Pthread* curthread) {
|
|||
DebugState.AddCurrentThreadToGuestList();
|
||||
|
||||
/* Run the current thread's start routine with argument: */
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
void* ret = linker->ExecuteGuest(curthread->start_routine, curthread->arg);
|
||||
void* ret = Core::ExecuteGuest(curthread->start_routine, curthread->arg);
|
||||
|
||||
/* Remove thread from tracking */
|
||||
DebugState.RemoveCurrentThreadFromGuestList();
|
||||
|
@ -56,7 +221,7 @@ int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAtt
|
|||
new_thread->attr = *(*attr);
|
||||
new_thread->attr.cpusetsize = 0;
|
||||
}
|
||||
if (new_thread->attr.sched_inherit == PTHREAD_INHERIT_SCHED) {
|
||||
if (new_thread->attr.sched_inherit == PthreadInheritSched) {
|
||||
if (True(curthread->attr.flags & PthreadAttrFlags::ScopeSystem)) {
|
||||
new_thread->attr.flags |= PthreadAttrFlags::ScopeSystem;
|
||||
} else {
|
||||
|
@ -85,7 +250,7 @@ int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAtt
|
|||
new_thread->cancel_async = 0;
|
||||
|
||||
auto* memory = Core::Memory::Instance();
|
||||
if (memory->IsValidAddress(name)) {
|
||||
if (name && (std::string_view{name} == "GAME_MainThread" || memory->IsValidAddress(name))) {
|
||||
new_thread->name = name;
|
||||
}
|
||||
|
||||
|
@ -109,6 +274,7 @@ int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAtt
|
|||
pthread_attr_setstack(&pattr, new_thread->attr.stackaddr_attr, new_thread->attr.stacksize_attr);
|
||||
pthread_t pthr;
|
||||
int ret = pthread_create(&pthr, &pattr, (PthreadEntryFunc)RunThread, new_thread);
|
||||
ASSERT_MSG(ret == 0, "Failed to create thread with error {}", ret);
|
||||
if (ret) {
|
||||
*thread = nullptr;
|
||||
}
|
||||
|
@ -203,6 +369,7 @@ void RegisterThread(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("m0iS6jNsXds", "libScePosix", 1, "libkernel", 1, 1, posix_sched_get_priority_min);
|
||||
LIB_FUNCTION("EotR8a3ASf4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_self);
|
||||
LIB_FUNCTION("B5GmVDKwpn0", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_yield);
|
||||
LIB_FUNCTION("+U1R4WtXvoc", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_detach);
|
||||
|
||||
// Posix-Kernel
|
||||
LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", 1, 1, posix_pthread_self);
|
||||
|
@ -212,6 +379,7 @@ void RegisterThread(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("GBUY7ywdULE", "libkernel", 1, "libkernel", 1, 1, ORBIS(posix_pthread_rename_np));
|
||||
LIB_FUNCTION("6UgtwV+0zb4", "libkernel", 1, "libkernel", 1, 1,
|
||||
ORBIS(posix_pthread_create_name_np));
|
||||
LIB_FUNCTION("4qGrR6eoP9Y", "libkernel", 1, "libkernel", 1, 1, ORBIS(posix_pthread_detach));
|
||||
LIB_FUNCTION("aI+OeCz8xrQ", "libkernel", 1, "libkernel", 1, 1, posix_pthread_self);
|
||||
LIB_FUNCTION("3PtV6p3QNX4", "libkernel", 1, "libkernel", 1, 1, posix_pthread_equal);
|
||||
LIB_FUNCTION("T72hz6ffq08", "libkernel", 1, "libkernel", 1, 1, posix_pthread_yield);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/kernel/threads/threads.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/tls.h"
|
||||
|
||||
|
@ -16,11 +16,10 @@ static constexpr size_t TlsTcbAlign = 0x20;
|
|||
static std::shared_mutex RtldLock;
|
||||
|
||||
Core::Tcb* TcbCtor(Pthread* thread, int initial) {
|
||||
ASSERT(initial == 0);
|
||||
std::scoped_lock lk{RtldLock};
|
||||
|
||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
auto* addr_out = linker->AllocateTlsForThread(false);
|
||||
auto* addr_out = linker->AllocateTlsForThread(initial);
|
||||
ASSERT_MSG(addr_out, "Unable to allocate guest TCB");
|
||||
|
||||
// Initialize allocated memory and allocate DTV table.
|
||||
|
@ -43,15 +42,15 @@ Core::Tcb* TcbCtor(Pthread* thread, int initial) {
|
|||
auto* module = linker->GetModule(0);
|
||||
u8* dest = reinterpret_cast<u8*>(addr + static_tls_size - module->tls.offset);
|
||||
|
||||
if (module->tls.image_virtual_addr != 0) {
|
||||
const u8* src = reinterpret_cast<const u8*>(module->tls.image_virtual_addr);
|
||||
memcpy(dest, src, module->tls.init_image_size);
|
||||
if (module->tls.image_size != 0) {
|
||||
if (module->tls.image_virtual_addr != 0) {
|
||||
const u8* src = reinterpret_cast<const u8*>(module->tls.image_virtual_addr);
|
||||
memcpy(dest, src, module->tls.init_image_size);
|
||||
}
|
||||
ASSERT_MSG(module->tls.modid > 0 && module->tls.modid <= num_dtvs);
|
||||
tcb->tcb_dtv[module->tls.modid + 1].pointer = dest;
|
||||
}
|
||||
|
||||
// Initialize DTV entry of main module
|
||||
ASSERT_MSG(module->tls.modid > 0 && module->tls.modid <= num_dtvs);
|
||||
tcb->tcb_dtv[module->tls.modid + 1].pointer = dest;
|
||||
|
||||
if (tcb) {
|
||||
tcb->tcb_thread = thread;
|
||||
}
|
||||
|
@ -68,21 +67,12 @@ void TcbDtor(Core::Tcb* oldtls) {
|
|||
ASSERT_MSG(num_dtvs <= max_tls_index, "Out of bounds DTV access");
|
||||
|
||||
const u32 static_tls_size = linker->StaticTlsSize();
|
||||
const u8* tls_base = (const u8*)oldtls - Common::AlignUp(static_tls_size, tcbalign);
|
||||
const u8* tls_base = (const u8*)oldtls - static_tls_size;
|
||||
|
||||
for (int i = 1; i < num_dtvs; i++) {
|
||||
u8* dtv_ptr = dtv_table[i + 1].pointer;
|
||||
if (dtv_ptr && (dtv_ptr < tls_base || (const u8*)oldtls < dtv_ptr)) {
|
||||
bool is_occupied;
|
||||
|
||||
_is_occupied = IsDtvIndexOccupied ? ((ulong)tcb, next);
|
||||
if (_is_occupied != 0) {
|
||||
FreeMem(dtv_ptr);
|
||||
return;
|
||||
}
|
||||
(**(code**)(LibcHeapApiPtr + 8))(dtv_ptr);
|
||||
FreeIfOccupied(tcb, dtv_addr & 0xffffffffffff, mod_index);
|
||||
dtv[modid + 1] = (ulong) * (ushort*)((long)dtv + modid * 8 + 0xe) << 0x30;
|
||||
linker->FreeTlsForNonPrimaryThread(dtv_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,4 +89,8 @@ void* PS4_SYSV_ABI __tls_get_addr(TlsIndex* index) {
|
|||
return linker->TlsGetAddr(index->ti_module, index->ti_offset);
|
||||
}
|
||||
|
||||
void RegisterRtld(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("vNe1w4diLCs", "libkernel", 1, "libkernel", 1, 1, __tls_get_addr);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
|
|
@ -7,175 +7,4 @@
|
|||
#include "core/libraries/kernel/threads/thread_state.h"
|
||||
#include "core/libraries/kernel/threads/threads.h"
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
void _thread_cleanupspecific();
|
||||
|
||||
static void ExitThread() {
|
||||
Pthread* curthread = g_curthread;
|
||||
|
||||
/* Check if there is thread specific data: */
|
||||
if (curthread->specific != nullptr) {
|
||||
/* Run the thread-specific data destructors: */
|
||||
_thread_cleanupspecific();
|
||||
}
|
||||
|
||||
auto* thread_state = ThrState::Instance();
|
||||
ASSERT(thread_state->active_threads.fetch_sub(1) != 1);
|
||||
|
||||
curthread->lock->lock();
|
||||
curthread->state = PthreadState::Dead;
|
||||
ASSERT(False(curthread->flags & ThreadFlags::NeedSuspend));
|
||||
|
||||
/*
|
||||
* Thread was created with initial refcount 1, we drop the
|
||||
* reference count to allow it to be garbage collected.
|
||||
*/
|
||||
curthread->refcount--;
|
||||
thread_state->TryCollect(curthread, curthread); /* thread lock released */
|
||||
|
||||
/*
|
||||
* Kernel will do wakeup at the address, so joiner thread
|
||||
* will be resumed if it is sleeping at the address.
|
||||
*/
|
||||
curthread->tid.store(TidTerminated);
|
||||
curthread->tid.notify_all();
|
||||
|
||||
pthread_exit(nullptr);
|
||||
UNREACHABLE();
|
||||
/* Never reach! */
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI posix_pthread_exit(void* status) {
|
||||
Pthread* curthread = g_curthread;
|
||||
|
||||
/* Check if this thread is already in the process of exiting: */
|
||||
ASSERT_MSG(!curthread->cancelling, "Thread {} has called pthread_exit from a destructor",
|
||||
fmt::ptr(curthread));
|
||||
|
||||
/* Flag this thread as exiting. */
|
||||
curthread->cancelling = 1;
|
||||
curthread->no_cancel = 1;
|
||||
curthread->cancel_async = 0;
|
||||
curthread->cancel_point = 0;
|
||||
|
||||
/* Save the return value: */
|
||||
curthread->ret = status;
|
||||
while (!curthread->cleanup.empty()) {
|
||||
PthreadCleanup* old = curthread->cleanup.front();
|
||||
curthread->cleanup.pop_front();
|
||||
old->routine(old->routine_arg);
|
||||
if (old->onheap) {
|
||||
free(old);
|
||||
}
|
||||
}
|
||||
|
||||
ExitThread();
|
||||
}
|
||||
|
||||
static int JoinThread(PthreadT pthread, void** thread_return, const OrbisKernelTimespec* abstime) {
|
||||
Pthread* curthread = g_curthread;
|
||||
|
||||
if (pthread == nullptr) {
|
||||
return POSIX_EINVAL;
|
||||
}
|
||||
|
||||
if (pthread == curthread) {
|
||||
return POSIX_EDEADLK;
|
||||
}
|
||||
|
||||
auto* thread_state = ThrState::Instance();
|
||||
if (int ret = thread_state->FindThread(pthread, 1); ret != 0) {
|
||||
return POSIX_ESRCH;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
if (True(pthread->flags & ThreadFlags::Detached)) {
|
||||
ret = POSIX_EINVAL;
|
||||
} else if (pthread->joiner != nullptr) {
|
||||
/* Multiple joiners are not supported. */
|
||||
ret = POSIX_ENOTSUP;
|
||||
}
|
||||
if (ret) {
|
||||
pthread->lock->unlock();
|
||||
return ret;
|
||||
}
|
||||
/* Set the running thread to be the joiner: */
|
||||
pthread->joiner = curthread;
|
||||
pthread->lock->unlock();
|
||||
|
||||
const auto backout_join = [](void* arg) {
|
||||
Pthread* pthread = (Pthread*)arg;
|
||||
std::scoped_lock lk{*pthread->lock};
|
||||
pthread->joiner = nullptr;
|
||||
};
|
||||
|
||||
PthreadCleanup cup{backout_join, pthread, 0};
|
||||
curthread->cleanup.push_front(&cup);
|
||||
|
||||
//_thr_cancel_enter(curthread);
|
||||
|
||||
const int tid = pthread->tid;
|
||||
while (pthread->tid.load() != TidTerminated) {
|
||||
//_thr_testcancel(curthread);
|
||||
ASSERT(abstime == nullptr);
|
||||
pthread->tid.wait(tid);
|
||||
}
|
||||
|
||||
//_thr_cancel_leave(curthread, 0);
|
||||
curthread->cleanup.pop_front();
|
||||
|
||||
if (ret == POSIX_ETIMEDOUT) {
|
||||
backout_join(pthread);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* tmp = pthread->ret;
|
||||
pthread->lock->lock();
|
||||
pthread->flags |= ThreadFlags::Detached;
|
||||
pthread->joiner = nullptr;
|
||||
thread_state->TryCollect(curthread, pthread); /* thread lock released */
|
||||
if (thread_return != nullptr) {
|
||||
*thread_return = tmp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return) {
|
||||
return JoinThread(pthread, thread_return, NULL);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_timedjoin_np(PthreadT pthread, void** thread_return,
|
||||
const OrbisKernelTimespec* abstime) {
|
||||
if (abstime == nullptr || abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
|
||||
abstime->tv_nsec >= 1000000000) {
|
||||
return POSIX_EINVAL;
|
||||
}
|
||||
|
||||
return JoinThread(pthread, thread_return, abstime);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_detach(PthreadT pthread) {
|
||||
if (pthread == nullptr) {
|
||||
return POSIX_EINVAL;
|
||||
}
|
||||
|
||||
auto* thread_state = ThrState::Instance();
|
||||
if (int ret = thread_state->FindThread(pthread, 1); ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Check if the thread is already detached or has a joiner. */
|
||||
if (True(pthread->flags & ThreadFlags::Detached) || (pthread->joiner != NULL)) {
|
||||
pthread->lock->unlock();
|
||||
return POSIX_EINVAL;
|
||||
}
|
||||
|
||||
/* Flag the thread as detached. */
|
||||
pthread->flags |= ThreadFlags::Detached;
|
||||
thread_state->TryCollect(g_curthread, pthread); /* thread lock released */
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
namespace Libraries::Kernel {} // namespace Libraries::Kernel
|
||||
|
|
|
@ -99,7 +99,7 @@ int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex) {
|
|||
return POSIX_EINVAL;
|
||||
}
|
||||
if (m->m_owner != nullptr) {
|
||||
return EBUSY;
|
||||
return POSIX_EBUSY;
|
||||
}
|
||||
*mutex = THR_MUTEX_DESTROYED;
|
||||
std::destroy_at(m);
|
||||
|
|
|
@ -178,7 +178,17 @@ int PS4_SYSV_ABI posix_pthread_rwlock_unlock(PthreadRwlockT* rwlock) {
|
|||
return POSIX_EINVAL;
|
||||
}
|
||||
|
||||
s32 state = prwlock->lock.rw_state;
|
||||
if (prwlock->owner == curthread) {
|
||||
prwlock->lock.unlock();
|
||||
prwlock->owner = nullptr;
|
||||
} else {
|
||||
prwlock->lock.unlock_shared();
|
||||
if (prwlock->owner == nullptr) {
|
||||
curthread->rdlock_count--;
|
||||
}
|
||||
}
|
||||
|
||||
/*s32 state = prwlock->lock.rw_state;
|
||||
if (state & URWLOCK_WRITE_OWNER) {
|
||||
if (prwlock->owner != curthread) [[unlikely]] {
|
||||
return POSIX_EPERM;
|
||||
|
@ -189,7 +199,7 @@ int PS4_SYSV_ABI posix_pthread_rwlock_unlock(PthreadRwlockT* rwlock) {
|
|||
prwlock->lock.unlock();
|
||||
if ((state & URWLOCK_WRITE_OWNER) == 0) {
|
||||
curthread->rdlock_count--;
|
||||
}
|
||||
}*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -140,20 +140,6 @@ const void* PS4_SYSV_ABI posix_pthread_getspecific(PthreadKeyT key) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void _thr_tsd_unload(struct dl_phdr_info* phdr_info) {
|
||||
std::scoped_lock lk{KeytableLock};
|
||||
for (int key = 0; key < PTHREAD_KEYS_MAX; key++) {
|
||||
if (!ThreadKeytable[key].allocated) {
|
||||
continue;
|
||||
}
|
||||
const auto destructor = ThreadKeytable[key].destructor;
|
||||
if (destructor != nullptr) {
|
||||
if (__elf_phdr_match_addr(phdr_info, destructor))
|
||||
ThreadKeytable[key].destructor = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterSpec(Core::Loader::SymbolsResolver* sym) {
|
||||
// Posix
|
||||
LIB_FUNCTION("mqULNdimTn0", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_key_create);
|
||||
|
@ -162,8 +148,7 @@ void RegisterSpec(Core::Loader::SymbolsResolver* sym) {
|
|||
|
||||
// Orbis
|
||||
LIB_FUNCTION("geDaqgH9lTg", "libkernel", 1, "libkernel", 1, 1, ORBIS(posix_pthread_key_create));
|
||||
LIB_FUNCTION("eoht7mQOCmo", "libkernel", 1, "libkernel", 1, 1,
|
||||
ORBIS(posix_pthread_getspecific));
|
||||
LIB_FUNCTION("eoht7mQOCmo", "libkernel", 1, "libkernel", 1, 1, posix_pthread_getspecific);
|
||||
LIB_FUNCTION("+BzXYkqYeLE", "libkernel", 1, "libkernel", 1, 1,
|
||||
ORBIS(posix_pthread_setspecific));
|
||||
}
|
||||
|
|
|
@ -78,7 +78,8 @@ int ThreadState::CreateStack(PthreadAttr* attr) {
|
|||
|
||||
/* Allocate a stack from usrstack. */
|
||||
if (last_stack == 0) {
|
||||
last_stack = _usrstack - ThrStackInitial - ThrGuardDefault;
|
||||
static constexpr VAddr UsrStack = 0x7EFFF8000ULL;
|
||||
last_stack = UsrStack - ThrStackInitial - ThrGuardDefault;
|
||||
}
|
||||
|
||||
/* Allocate a new stack. */
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
Pthread* g_curthread{};
|
||||
|
||||
Core::Tcb* TcbCtor(Pthread* thread, int initial);
|
||||
void TcbDtor(Core::Tcb* oldtls);
|
||||
|
||||
|
@ -85,11 +87,11 @@ Pthread* ThreadState::Alloc(Pthread* curthread) {
|
|||
tcb = TcbCtor(thread, 1 /* initial tls */);
|
||||
}
|
||||
if (tcb != nullptr) {
|
||||
memset(thread, 0, sizeof(*thread));
|
||||
thread->tcb = tcb;
|
||||
// thread->sleepqueue = _sleepq_alloc();
|
||||
// thread->wake_addr = _thr_alloc_wake_addr();
|
||||
} else {
|
||||
std::destroy_at(thread);
|
||||
free(thread);
|
||||
total_threads.fetch_sub(1);
|
||||
thread = nullptr;
|
||||
|
@ -108,6 +110,7 @@ void ThreadState::Free(Pthread* curthread, Pthread* thread) {
|
|||
if (free_threads.size() >= MaxCachedThreads) {
|
||||
//_sleepq_free(thread->sleepqueue);
|
||||
//_thr_release_wake_addr(thread->wake_addr);
|
||||
std::destroy_at(thread);
|
||||
free(thread);
|
||||
total_threads.fetch_sub(1);
|
||||
} else {
|
||||
|
|
|
@ -194,6 +194,8 @@ enum class ThreadListFlags : u32 {
|
|||
InGcList = 4,
|
||||
};
|
||||
|
||||
using PthreadEntryFunc = void* (*)(void*);
|
||||
|
||||
constexpr u32 TidTerminated = 1;
|
||||
|
||||
struct Pthread {
|
||||
|
@ -272,15 +274,6 @@ void RegisterSemaphore(Core::Loader::SymbolsResolver* sym);
|
|||
void RegisterSpec(Core::Loader::SymbolsResolver* sym);
|
||||
void RegisterThreadAttr(Core::Loader::SymbolsResolver* sym);
|
||||
void RegisterThread(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
inline void RegisterThreads(Core::Loader::SymbolsResolver* sym) {
|
||||
RegisterMutex(sym);
|
||||
RegisterCond(sym);
|
||||
RegisterRwlock(sym);
|
||||
RegisterSemaphore(sym);
|
||||
RegisterSpec(sym);
|
||||
RegisterThreadAttr(sym);
|
||||
RegisterThread(sym);
|
||||
}
|
||||
void RegisterRtld(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
|
|
@ -49,11 +49,9 @@ namespace Libraries {
|
|||
|
||||
void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
LOG_INFO(Lib_Kernel, "Initializing HLE libraries");
|
||||
Libraries::Kernel::LibKernel_Register(sym);
|
||||
Libraries::Kernel::RegisterKernel(sym);
|
||||
Libraries::GnmDriver::RegisterlibSceGnmDriver(sym);
|
||||
Libraries::VideoOut::RegisterLib(sym);
|
||||
|
||||
// New libraries folder from autogen
|
||||
Libraries::UserService::RegisterlibSceUserService(sym);
|
||||
Libraries::SystemService::RegisterlibSceSystemService(sym);
|
||||
Libraries::CommonDialog::RegisterlibSceCommonDialog(sym);
|
||||
|
|
|
@ -1,80 +1,62 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/singleton.h"
|
||||
#include "core/linker.h"
|
||||
#include "net_ctl_codes.h"
|
||||
#include "net_ctl_obj.h"
|
||||
#include "core/libraries/network/net_ctl_codes.h"
|
||||
#include "core/libraries/network/net_ctl_obj.h"
|
||||
#include "core/tls.h"
|
||||
|
||||
Libraries::NetCtl::NetCtlInternal::NetCtlInternal() {
|
||||
callbacks.fill({nullptr, nullptr});
|
||||
nptoolCallbacks.fill({nullptr, nullptr});
|
||||
}
|
||||
namespace Libraries::NetCtl {
|
||||
|
||||
Libraries::NetCtl::NetCtlInternal::~NetCtlInternal() {}
|
||||
NetCtlInternal::NetCtlInternal() = default;
|
||||
|
||||
s32 Libraries::NetCtl::NetCtlInternal::registerCallback(OrbisNetCtlCallback func, void* arg) {
|
||||
std::unique_lock lock{m_mutex};
|
||||
NetCtlInternal::~NetCtlInternal() = default;
|
||||
|
||||
s32 NetCtlInternal::RegisterCallback(OrbisNetCtlCallback func, void* arg) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
// Find the next available slot
|
||||
int next_id = 0;
|
||||
for (const auto& callback : callbacks) {
|
||||
if (callback.func == nullptr) {
|
||||
break;
|
||||
}
|
||||
next_id++;
|
||||
}
|
||||
|
||||
if (next_id == 8) {
|
||||
const auto it = std::ranges::find(callbacks, nullptr, &NetCtlCallback::func);
|
||||
if (it == callbacks.end()) {
|
||||
return ORBIS_NET_CTL_ERROR_CALLBACK_MAX;
|
||||
}
|
||||
|
||||
const int next_id = std::distance(callbacks.begin(), it);
|
||||
callbacks[next_id].func = func;
|
||||
callbacks[next_id].arg = arg;
|
||||
return next_id;
|
||||
}
|
||||
|
||||
s32 Libraries::NetCtl::NetCtlInternal::registerNpToolkitCallback(
|
||||
OrbisNetCtlCallbackForNpToolkit func, void* arg) {
|
||||
|
||||
std::unique_lock lock{m_mutex};
|
||||
s32 NetCtlInternal::RegisterNpToolkitCallback(OrbisNetCtlCallbackForNpToolkit func, void* arg) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
// Find the next available slot
|
||||
int next_id = 0;
|
||||
for (const auto& callback : nptoolCallbacks) {
|
||||
if (callback.func == nullptr) {
|
||||
break;
|
||||
}
|
||||
next_id++;
|
||||
}
|
||||
|
||||
if (next_id == 8) {
|
||||
const auto it = std::ranges::find(nptool_callbacks, nullptr, &NetCtlCallbackForNpToolkit::func);
|
||||
if (it == nptool_callbacks.end()) {
|
||||
return ORBIS_NET_CTL_ERROR_CALLBACK_MAX;
|
||||
}
|
||||
|
||||
nptoolCallbacks[next_id].func = func;
|
||||
nptoolCallbacks[next_id].arg = arg;
|
||||
const int next_id = std::distance(nptool_callbacks.begin(), it);
|
||||
nptool_callbacks[next_id].func = func;
|
||||
nptool_callbacks[next_id].arg = arg;
|
||||
return next_id;
|
||||
}
|
||||
|
||||
void Libraries::NetCtl::NetCtlInternal::checkCallback() {
|
||||
std::unique_lock lock{m_mutex};
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
for (auto& callback : callbacks) {
|
||||
if (callback.func != nullptr) {
|
||||
linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED,
|
||||
callback.arg);
|
||||
void NetCtlInternal::CheckCallback() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
for (const auto [func, arg] : callbacks) {
|
||||
if (func != nullptr) {
|
||||
Core::ExecuteGuest(func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Libraries::NetCtl::NetCtlInternal::checkNpToolkitCallback() {
|
||||
std::unique_lock lock{m_mutex};
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
for (auto& callback : nptoolCallbacks) {
|
||||
if (callback.func != nullptr) {
|
||||
linker->ExecuteGuest(callback.func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED,
|
||||
callback.arg);
|
||||
void NetCtlInternal::CheckNpToolkitCallback() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
for (const auto [func, arg] : nptool_callbacks) {
|
||||
if (func != nullptr) {
|
||||
Core::ExecuteGuest(func, ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libraries::NetCtl
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Libraries::NetCtl {
|
||||
|
@ -25,16 +23,17 @@ struct NetCtlCallbackForNpToolkit {
|
|||
|
||||
class NetCtlInternal {
|
||||
public:
|
||||
NetCtlInternal();
|
||||
explicit NetCtlInternal();
|
||||
~NetCtlInternal();
|
||||
s32 registerCallback(OrbisNetCtlCallback func, void* arg);
|
||||
s32 registerNpToolkitCallback(OrbisNetCtlCallbackForNpToolkit func, void* arg);
|
||||
void checkCallback();
|
||||
void checkNpToolkitCallback();
|
||||
|
||||
s32 RegisterCallback(OrbisNetCtlCallback func, void* arg);
|
||||
s32 RegisterNpToolkitCallback(OrbisNetCtlCallbackForNpToolkit func, void* arg);
|
||||
void CheckCallback();
|
||||
void CheckNpToolkitCallback();
|
||||
|
||||
public:
|
||||
std::array<NetCtlCallback, 8> nptoolCallbacks;
|
||||
std::array<NetCtlCallbackForNpToolkit, 8> callbacks;
|
||||
std::array<NetCtlCallbackForNpToolkit, 8> nptool_callbacks{};
|
||||
std::array<NetCtlCallback, 8> callbacks{};
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
} // namespace Libraries::NetCtl
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#endif
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/network/net_ctl_codes.h"
|
||||
|
@ -21,6 +20,8 @@
|
|||
|
||||
namespace Libraries::NetCtl {
|
||||
|
||||
static NetCtlInternal netctl;
|
||||
|
||||
int PS4_SYSV_ABI sceNetBweCheckCallbackIpcInt() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
|
@ -92,8 +93,7 @@ int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlCheckCallback() {
|
||||
auto* netctl = Common::Singleton<Libraries::NetCtl::NetCtlInternal>::Instance();
|
||||
netctl->checkCallback();
|
||||
netctl.CheckCallback();
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -298,8 +298,7 @@ int PS4_SYSV_ABI sceNetCtlRegisterCallback(OrbisNetCtlCallback func, void* arg,
|
|||
if (!func || !cid) {
|
||||
return ORBIS_NET_CTL_ERROR_INVALID_ADDR;
|
||||
}
|
||||
auto* netctl = Common::Singleton<Libraries::NetCtl::NetCtlInternal>::Instance();
|
||||
s32 result = netctl->registerCallback(func, arg);
|
||||
s32 result = netctl.RegisterCallback(func, arg);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
} else {
|
||||
|
@ -374,8 +373,7 @@ int PS4_SYSV_ABI Func_D8DCB6973537A3DC() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlCheckCallbackForNpToolkit() {
|
||||
auto* netctl = Common::Singleton<Libraries::NetCtl::NetCtlInternal>::Instance();
|
||||
netctl->checkNpToolkitCallback();
|
||||
netctl.CheckNpToolkitCallback();
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -389,8 +387,7 @@ int PS4_SYSV_ABI sceNetCtlRegisterCallbackForNpToolkit(OrbisNetCtlCallbackForNpT
|
|||
if (!func || !cid) {
|
||||
return ORBIS_NET_CTL_ERROR_INVALID_ADDR;
|
||||
}
|
||||
auto* netctl = Common::Singleton<Libraries::NetCtl::NetCtlInternal>::Instance();
|
||||
s32 result = netctl->registerNpToolkitCallback(func, arg);
|
||||
s32 result = netctl.RegisterNpToolkitCallback(func, arg);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
} else {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "net_ctl_obj.h"
|
||||
#include "core/libraries/network/net_ctl_obj.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <common/singleton.h>
|
||||
#include <core/linker.h>
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "np_manager.h"
|
||||
#include "core/libraries/np_manager/np_manager.h"
|
||||
#include "core/tls.h"
|
||||
|
||||
namespace Libraries::NpManager {
|
||||
|
||||
|
@ -2519,10 +2518,7 @@ struct NpStateCallbackForNpToolkit {
|
|||
NpStateCallbackForNpToolkit NpStateCbForNp;
|
||||
|
||||
int PS4_SYSV_ABI sceNpCheckCallbackForLib() {
|
||||
// LOG_ERROR(Lib_NpManager, "(STUBBED) called");
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
linker->ExecuteGuest(NpStateCbForNp.func, 1, ORBIS_NP_STATE_SIGNED_OUT,
|
||||
NpStateCbForNp.userdata);
|
||||
Core::ExecuteGuest(NpStateCbForNp.func, 1, ORBIS_NP_STATE_SIGNED_OUT, NpStateCbForNp.userdata);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,30 +8,26 @@
|
|||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/aerolib/aerolib.h"
|
||||
#include "core/aerolib/stubs.h"
|
||||
#include "core/cpu_patches.h"
|
||||
#include "core/libraries/kernel/memory_management.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/tls.h"
|
||||
#include "core/virtual_memory.h"
|
||||
#include "debug_state.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
using ExitFunc = PS4_SYSV_ABI void (*)();
|
||||
|
||||
static PS4_SYSV_ABI void ProgramExitFunc() {
|
||||
fmt::print("exit function called\n");
|
||||
LOG_ERROR(Core_Linker, "Exit function called");
|
||||
}
|
||||
|
||||
#ifdef ARCH_X86_64
|
||||
static PS4_SYSV_ABI void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc exit_func) {
|
||||
// reinterpret_cast<entry_func_t>(addr)(params, exit_func); // can't be used, stack has to have
|
||||
// a specific layout
|
||||
static PS4_SYSV_ABI void* RunMainEntry [[noreturn]] (void* arg) {
|
||||
EntryParams* params = (EntryParams*)arg;
|
||||
// Start shared library modules
|
||||
params->linker->LoadSharedLibraries();
|
||||
asm volatile("andq $-16, %%rsp\n" // Align to 16 bytes
|
||||
"subq $8, %%rsp\n" // videoout_basic expects the stack to be misaligned
|
||||
|
||||
|
@ -47,8 +43,9 @@ static PS4_SYSV_ABI void RunMainEntry(VAddr addr, EntryParams* params, ExitFunc
|
|||
"jmp *%0\n" // can't use call here, as that would mangle the prepared stack.
|
||||
// there's no coming back
|
||||
:
|
||||
: "r"(addr), "r"(params), "r"(exit_func)
|
||||
: "r"(params->entry_addr), "r"(params), "r"(params->exit_func)
|
||||
: "rax", "rsi", "rdi");
|
||||
UNREACHABLE();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -62,10 +59,8 @@ void Linker::Execute() {
|
|||
}
|
||||
|
||||
// Calculate static TLS size.
|
||||
for (const auto& module : m_modules) {
|
||||
static_tls_size += module->tls.image_size;
|
||||
module->tls.offset = static_tls_size;
|
||||
}
|
||||
Module* module = m_modules[0].get();
|
||||
static_tls_size = module->tls.offset = module->tls.image_size;
|
||||
|
||||
// Relocate all modules
|
||||
for (const auto& m : m_modules) {
|
||||
|
@ -87,36 +82,14 @@ void Linker::Execute() {
|
|||
}
|
||||
}
|
||||
|
||||
// Init primary thread.
|
||||
Common::SetCurrentThreadName("GAME_MainThread");
|
||||
DebugState.AddCurrentThreadToGuestList();
|
||||
Libraries::Kernel::pthreadInitSelfMainThread();
|
||||
EnsureThreadInitialized(true);
|
||||
|
||||
// Start shared library modules
|
||||
for (auto& m : m_modules) {
|
||||
if (m->IsSharedLib()) {
|
||||
m->Start(0, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Start main module.
|
||||
EntryParams p{};
|
||||
p.argc = 1;
|
||||
p.argv[0] = "eboot.bin";
|
||||
|
||||
for (auto& m : m_modules) {
|
||||
if (!m->IsSharedLib()) {
|
||||
#ifdef ARCH_X86_64
|
||||
ExecuteGuest(RunMainEntry, m->GetEntryAddress(), &p, ProgramExitFunc);
|
||||
#else
|
||||
UNIMPLEMENTED_MSG(
|
||||
"Missing guest entrypoint implementation for target CPU architecture.");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SetTcbBase(nullptr);
|
||||
EntryParams* params = new EntryParams;
|
||||
params->argc = 1;
|
||||
params->argv[0] = "eboot.bin";
|
||||
params->entry_addr = module->GetEntryAddress();
|
||||
params->exit_func = ProgramExitFunc;
|
||||
params->linker = this;
|
||||
Libraries::Kernel::LaunchThread(RunMainEntry, params, "GAME_MainThread");
|
||||
}
|
||||
|
||||
s32 Linker::LoadModule(const std::filesystem::path& elf_name, bool is_dynamic) {
|
||||
|
@ -337,17 +310,6 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) {
|
|||
return addr + offset;
|
||||
}
|
||||
|
||||
thread_local std::once_flag init_tls_flag;
|
||||
|
||||
void Linker::EnsureThreadInitialized(bool is_primary) const {
|
||||
std::call_once(init_tls_flag, [this, is_primary] {
|
||||
#ifdef ARCH_X86_64
|
||||
InitializeThreadPatchStack();
|
||||
#endif
|
||||
InitTlsForThread(is_primary);
|
||||
});
|
||||
}
|
||||
|
||||
void* Linker::AllocateTlsForThread(bool is_primary) {
|
||||
static constexpr size_t TcbSize = 0x40;
|
||||
static constexpr size_t TlsAllocAlign = 0x20;
|
||||
|
@ -369,7 +331,7 @@ void* Linker::AllocateTlsForThread(bool is_primary) {
|
|||
ASSERT_MSG(ret == 0, "Unable to allocate TLS+TCB for the primary thread");
|
||||
} else {
|
||||
if (heap_api) {
|
||||
addr_out = heap_api->heap_malloc(total_tls_size);
|
||||
addr_out = Core::ExecuteGuest(heap_api->heap_malloc, total_tls_size);
|
||||
} else {
|
||||
addr_out = std::malloc(total_tls_size);
|
||||
}
|
||||
|
@ -377,22 +339,31 @@ void* Linker::AllocateTlsForThread(bool is_primary) {
|
|||
return addr_out;
|
||||
}
|
||||
|
||||
void Linker::FreeTlsForNonPrimaryThread(void* pointer) {
|
||||
if (heap_api) {
|
||||
Core::ExecuteGuest(heap_api->heap_free, pointer);
|
||||
} else {
|
||||
std::free(pointer);
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::DebugDump() {
|
||||
const auto& log_dir = Common::FS::GetUserPath(Common::FS::PathType::LogDir);
|
||||
const std::filesystem::path debug(log_dir / "debugdump");
|
||||
std::filesystem::create_directory(debug);
|
||||
for (const auto& m : m_modules) {
|
||||
// TODO make a folder with game id for being more unique?
|
||||
const std::filesystem::path filepath(debug / m.get()->file.stem());
|
||||
Module* module = m.get();
|
||||
auto& elf = module->elf;
|
||||
const std::filesystem::path filepath(debug / module->file.stem());
|
||||
std::filesystem::create_directory(filepath);
|
||||
m.get()->import_sym.DebugDump(filepath / "imports.txt");
|
||||
m.get()->export_sym.DebugDump(filepath / "exports.txt");
|
||||
if (m.get()->elf.IsSelfFile()) {
|
||||
m.get()->elf.SelfHeaderDebugDump(filepath / "selfHeader.txt");
|
||||
m.get()->elf.SelfSegHeaderDebugDump(filepath / "selfSegHeaders.txt");
|
||||
module->import_sym.DebugDump(filepath / "imports.txt");
|
||||
module->export_sym.DebugDump(filepath / "exports.txt");
|
||||
if (elf.IsSelfFile()) {
|
||||
elf.SelfHeaderDebugDump(filepath / "selfHeader.txt");
|
||||
elf.SelfSegHeaderDebugDump(filepath / "selfSegHeaders.txt");
|
||||
}
|
||||
m.get()->elf.ElfHeaderDebugDump(filepath / "elfHeader.txt");
|
||||
m.get()->elf.PHeaderDebugDump(filepath / "elfPHeaders.txt");
|
||||
elf.ElfHeaderDebugDump(filepath / "elfHeader.txt");
|
||||
elf.PHeaderDebugDump(filepath / "elfPHeaders.txt");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,10 +40,17 @@ struct OrbisProcParam {
|
|||
u64 unknown1;
|
||||
};
|
||||
|
||||
using ExitFunc = PS4_SYSV_ABI void (*)();
|
||||
|
||||
class Linker;
|
||||
|
||||
struct EntryParams {
|
||||
int argc;
|
||||
u32 padding;
|
||||
const char* argv[3];
|
||||
VAddr entry_addr;
|
||||
ExitFunc exit_func;
|
||||
Linker* linker;
|
||||
};
|
||||
|
||||
struct HeapAPI {
|
||||
|
@ -101,6 +108,14 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void LoadSharedLibraries() {
|
||||
for (auto& module : m_modules) {
|
||||
if (module->IsSharedLib()) {
|
||||
module->Start(0, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetHeapAPI(void* func[]) {
|
||||
heap_api = reinterpret_cast<AppHeapAPI>(func);
|
||||
}
|
||||
|
@ -111,6 +126,7 @@ public:
|
|||
|
||||
void* TlsGetAddr(u64 module_index, u64 offset);
|
||||
void* AllocateTlsForThread(bool is_primary);
|
||||
void FreeTlsForNonPrimaryThread(void* pointer);
|
||||
|
||||
s32 LoadModule(const std::filesystem::path& elf_name, bool is_dynamic = false);
|
||||
Module* FindByAddress(VAddr address);
|
||||
|
@ -121,24 +137,8 @@ public:
|
|||
void Execute();
|
||||
void DebugDump();
|
||||
|
||||
template <class ReturnType, class... FuncArgs, class... CallArgs>
|
||||
ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...),
|
||||
CallArgs&&... args) const {
|
||||
// Make sure TLS is initialized for the thread before entering guest.
|
||||
EnsureThreadInitialized();
|
||||
return ExecuteGuestWithoutTls(func, args...);
|
||||
}
|
||||
|
||||
private:
|
||||
const Module* FindExportedModule(const ModuleInfo& m, const LibraryInfo& l);
|
||||
void EnsureThreadInitialized(bool is_primary = false) const;
|
||||
void InitTlsForThread(bool is_primary) const;
|
||||
|
||||
template <class ReturnType, class... FuncArgs, class... CallArgs>
|
||||
ReturnType ExecuteGuestWithoutTls(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...),
|
||||
CallArgs&&... args) const {
|
||||
return func(std::forward<CallArgs>(args)...);
|
||||
}
|
||||
|
||||
MemoryManager* memory;
|
||||
std::mutex mutex;
|
||||
|
|
|
@ -148,7 +148,7 @@ public:
|
|||
VAddr SystemReservedVirtualBase() noexcept {
|
||||
return impl.SystemReservedVirtualBase();
|
||||
}
|
||||
|
||||
|
||||
bool IsValidAddress(const void* addr) const noexcept {
|
||||
const VAddr virtual_addr = reinterpret_cast<VAddr>(addr);
|
||||
const auto end_it = std::prev(vma_map.end());
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
#include "common/string_util.h"
|
||||
#include "core/aerolib/aerolib.h"
|
||||
#include "core/cpu_patches.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/loader/dwarf.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/module.h"
|
||||
#include "core/tls.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
|
@ -70,9 +70,8 @@ Module::~Module() = default;
|
|||
|
||||
s32 Module::Start(size_t args, const void* argp, void* param) {
|
||||
LOG_INFO(Core_Linker, "Module started : {}", name);
|
||||
const auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
const VAddr addr = dynamic_info.init_virtual_addr + GetBaseAddress();
|
||||
return linker->ExecuteGuest(reinterpret_cast<EntryFunc>(addr), args, argp, param);
|
||||
return ExecuteGuest(reinterpret_cast<EntryFunc>(addr), args, argp, param);
|
||||
}
|
||||
|
||||
void Module::LoadModuleToMemory(u32& max_tls_index) {
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include "common/arch.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
#include "core/cpu_patches.h"
|
||||
#include "core/libraries/kernel/threads/threads.h"
|
||||
#include "core/tls.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -181,4 +183,15 @@ Tcb* GetTcbBase() {
|
|||
|
||||
#endif
|
||||
|
||||
thread_local std::once_flag init_tls_flag;
|
||||
|
||||
void EnsureThreadInitialized() {
|
||||
std::call_once(init_tls_flag, [] {
|
||||
#ifdef ARCH_X86_64
|
||||
InitializeThreadPatchStack();
|
||||
#endif
|
||||
SetTcbBase(Libraries::Kernel::g_curthread->tcb);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
|
|
@ -12,7 +12,7 @@ class CodeGenerator;
|
|||
namespace Core {
|
||||
|
||||
union DtvEntry {
|
||||
size_t counter;
|
||||
std::size_t counter;
|
||||
u8* pointer;
|
||||
};
|
||||
|
||||
|
@ -33,4 +33,13 @@ void SetTcbBase(void* image_address);
|
|||
/// Retrieves Tcb structure for the calling thread.
|
||||
Tcb* GetTcbBase();
|
||||
|
||||
/// Makes sure TLS is initialized for the thread before entering guest.
|
||||
void EnsureThreadInitialized();
|
||||
|
||||
template <class ReturnType, class... FuncArgs, class... CallArgs>
|
||||
ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) {
|
||||
EnsureThreadInitialized();
|
||||
return func(std::forward<CallArgs>(args)...);
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/disc_map/disc_map.h"
|
||||
#include "core/libraries/fiber/fiber.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
#include "core/libraries/libc_internal/libc_internal.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/ngs2/ngs2.h"
|
||||
|
@ -222,7 +221,6 @@ void Emulator::Run(const std::filesystem::path& file) {
|
|||
VideoCore::SetOutputDir(mount_captures_dir, id);
|
||||
|
||||
// Initialize kernel and library facilities.
|
||||
Libraries::Kernel::init_pthreads();
|
||||
Libraries::InitHLELibs(&linker->GetHLESymbols());
|
||||
|
||||
// Load the module with the linker
|
||||
|
@ -257,9 +255,7 @@ void Emulator::Run(const std::filesystem::path& file) {
|
|||
}
|
||||
#endif
|
||||
|
||||
// start execution
|
||||
std::jthread mainthread =
|
||||
std::jthread([this](std::stop_token stop_token) { linker->Execute(); });
|
||||
linker->Execute();
|
||||
|
||||
window->initTimers();
|
||||
while (window->isOpen()) {
|
||||
|
|
Loading…
Reference in a new issue