From cb45d2bcfb2a30cb1290c31505ca23a3d8060871 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Tue, 8 Aug 2023 19:03:40 +1000 Subject: [PATCH] Broken Xbox stuff --- src/Core.h | 10 +- src/Platform_Xbox.c | 541 ++++++++++++++++++++++++++++++++++++++++++++ src/Window_Xbox.c | 124 ++++++++++ 3 files changed, 674 insertions(+), 1 deletion(-) create mode 100644 src/Platform_Xbox.c create mode 100644 src/Window_Xbox.c diff --git a/src/Core.h b/src/Core.h index 9e633752f..fb43b6ae3 100644 --- a/src/Core.h +++ b/src/Core.h @@ -121,7 +121,15 @@ typedef cc_uint8 cc_bool; #define CC_BUILD_FREETYPE /*#define CC_BUILD_GL11*/ #ifndef CC_BUILD_MANUAL -#if defined _WIN32 +#if defined NXDK + /* XBox also defines _WIN32 */ + #define CC_BUILD_XBOX + #define CC_BUILD_NOMUSIC + #define CC_BUILD_NOSOUNDS + #define CC_BUILD_LOWMEM + #define CC_BUILD_HTTPCLIENT + #undef CC_BUILD_FREETYPE +#elif defined _WIN32 #define CC_BUILD_WIN #define CC_BUILD_D3D9 #define CC_BUILD_WINGUI diff --git a/src/Platform_Xbox.c b/src/Platform_Xbox.c new file mode 100644 index 000000000..7719a8797 --- /dev/null +++ b/src/Platform_Xbox.c @@ -0,0 +1,541 @@ +#include "Core.h" +#if defined CC_BUILD_XBOX +#include "_PlatformBase.h" +#include "Stream.h" +#include "Funcs.h" +#include "Utils.h" +#include "Errors.h" + +#include +#include +#include +#include +#include +#include +#include + +static HANDLE heap; +const cc_result ReturnCode_FileShareViolation = ERROR_SHARING_VIOLATION; +const cc_result ReturnCode_FileNotFound = ERROR_FILE_NOT_FOUND; +const cc_result ReturnCode_SocketInProgess = WSAEINPROGRESS; +const cc_result ReturnCode_SocketWouldBlock = WSAEWOULDBLOCK; +const cc_result ReturnCode_DirectoryExists = ERROR_ALREADY_EXISTS; + +/*########################################################################################################################* +*---------------------------------------------------------Memory----------------------------------------------------------* +*#########################################################################################################################*/ +void Mem_Set(void* dst, cc_uint8 value, cc_uint32 numBytes) { memset(dst, value, numBytes); } +void Mem_Copy(void* dst, const void* src, cc_uint32 numBytes) { memcpy(dst, src, numBytes); } + +void* Mem_TryAlloc(cc_uint32 numElems, cc_uint32 elemsSize) { + cc_uint32 size = CalcMemSize(numElems, elemsSize); + return size ? malloc(size) : NULL; +} + +void* Mem_TryAllocCleared(cc_uint32 numElems, cc_uint32 elemsSize) { + return calloc(numElems, elemsSize); +} + +void* Mem_TryRealloc(void* mem, cc_uint32 numElems, cc_uint32 elemsSize) { + cc_uint32 size = CalcMemSize(numElems, elemsSize); + return size ? realloc(mem, size) : NULL; +} + +void Mem_Free(void* mem) { + if (mem) free(mem); +} + + +/*########################################################################################################################* +*------------------------------------------------------Logging/Time-------------------------------------------------------* +*#########################################################################################################################*/ +void Platform_Log(const char* msg, int len) { + char tmp[2048 + 1]; + len = min(len, 2048); + Mem_Copy(tmp, msg, len); tmp[len] = '\0'; + + debugPrint(tmp); + debugPrint("\n"); +} + +#define FILETIME_EPOCH 50491123200000ULL +#define FILETIME_UNIX_EPOCH 11644473600LL +#define FileTime_TotalMS(time) ((time / 10000) + FILETIME_EPOCH) +#define FileTime_UnixTime(time) ((time / 10000000) - FILETIME_UNIX_EPOCH) +TimeMS DateTime_CurrentUTC_MS(void) { + LARGE_INTEGER ft; + + KeQuerySystemTime(&ft); + /* in 100 nanosecond units, since Jan 1 1601 */ + return FileTime_TotalMS(ft.QuadPart); +} + +void DateTime_CurrentLocal(struct DateTime* t) { + SYSTEMTIME localTime; + GetLocalTime(&localTime); + + t->year = localTime.wYear; + t->month = localTime.wMonth; + t->day = localTime.wDay; + t->hour = localTime.wHour; + t->minute = localTime.wMinute; + t->second = localTime.wSecond; +} + +/* TODO: check this is actually accurate */ +static cc_uint64 sw_freqMul = 1, sw_freqDiv = 1; +cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) { + if (end < beg) return 0; + return ((end - beg) * sw_freqMul) / sw_freqDiv; +} + +cc_uint64 Stopwatch_Measure(void) { + return KeQueryPerformanceCounter(); +} + +static void Platform_InitStopwatch(void) { + ULONGLONG freq = KeQueryPerformanceFrequency(); + + sw_freqMul = 1000 * 1000; + sw_freqDiv = freq; +} + + +/*########################################################################################################################* +*-----------------------------------------------------Directory/File------------------------------------------------------* +*#########################################################################################################################*/ +void Directory_GetCachePath(cc_string* path) { } + +static void GetNativePath(char* str, const cc_string* src) { + if (src->length > FILENAME_SIZE) Logger_Abort("String too long to expand"); + + for (int i = 0; i < src->length; i++) + { + *str++ = (char)src->buffer[i]; + } + *str = '\0'; +} + +cc_result Directory_Create(const cc_string* path) { + char str[NATIVE_STR_LEN]; + cc_result res; + + GetNativePath(str, path); + return CreateDirectoryA(str, NULL) ? 0 : GetLastError(); +} + +int File_Exists(const cc_string* path) { + char str[NATIVE_STR_LEN]; + DWORD attribs; + + GetNativePath(str, path); + attribs = GetFileAttributesA(str); + + return attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY); +} + +static cc_result Directory_EnumCore(const cc_string* dirPath, const cc_string* file, DWORD attribs, + void* obj, Directory_EnumCallback callback) { + cc_string path; char pathBuffer[MAX_PATH + 10]; + /* ignore . and .. entry */ + if (file->length == 1 && file->buffer[0] == '.') return 0; + if (file->length == 2 && file->buffer[0] == '.' && file->buffer[1] == '.') return 0; + + String_InitArray(path, pathBuffer); + String_Format2(&path, "%s/%s", dirPath, file); + + if (attribs & FILE_ATTRIBUTE_DIRECTORY) return Directory_Enum(&path, obj, callback); + callback(&path, obj); + return 0; +} + +cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCallback callback) { + cc_string path; char pathBuffer[MAX_PATH + 10]; + WIN32_FIND_DATAA eA; + char str[NATIVE_STR_LEN]; + HANDLE find; + cc_result res; + + /* Need to append \* to search for files in directory */ + String_InitArray(path, pathBuffer); + String_Format1(&path, "%s\\*", dirPath); + GetNativePath(str, &path); + + find = FindFirstFileA(str, &eA); + if (find == INVALID_HANDLE_VALUE) return GetLastError(); + + do { + path.length = 0; + for (int i = 0; i < MAX_PATH && eA.cFileName[i]; i++) + { + String_Append(&path, Convert_CodepointToCP437(eA.cFileName[i])); + } + if ((res = Directory_EnumCore(dirPath, &path, eA.dwFileAttributes, obj, callback))) return res; + } while (FindNextFileA(find, &eA)); + + res = GetLastError(); /* return code from FindNextFile */ + FindClose(find); + return res == ERROR_NO_MORE_FILES ? 0 : res; +} + +static cc_result DoFile(cc_file* file, const cc_string* path, DWORD access, DWORD createMode) { + char str[NATIVE_STR_LEN]; + GetNativePath(str, path); + cc_result res; + + *file = CreateFileA(str, access, FILE_SHARE_READ, NULL, createMode, 0, NULL); + return *file != INVALID_HANDLE_VALUE ? 0 : GetLastError(); +} + +cc_result File_Open(cc_file* file, const cc_string* path) { + return DoFile(file, path, GENERIC_READ, OPEN_EXISTING); +} +cc_result File_Create(cc_file* file, const cc_string* path) { + return DoFile(file, path, GENERIC_WRITE | GENERIC_READ, CREATE_ALWAYS); +} +cc_result File_OpenOrCreate(cc_file* file, const cc_string* path) { + return DoFile(file, path, GENERIC_WRITE | GENERIC_READ, OPEN_ALWAYS); +} + +cc_result File_Read(cc_file file, void* data, cc_uint32 count, cc_uint32* bytesRead) { + BOOL success = ReadFile(file, data, count, bytesRead, NULL); + return success ? 0 : GetLastError(); +} + +cc_result File_Write(cc_file file, const void* data, cc_uint32 count, cc_uint32* bytesWrote) { + BOOL success = WriteFile(file, data, count, bytesWrote, NULL); + return success ? 0 : GetLastError(); +} + +cc_result File_Close(cc_file file) { + return CloseHandle(file) ? 0 : GetLastError(); +} + +cc_result File_Seek(cc_file file, int offset, int seekType) { + static cc_uint8 modes[3] = { FILE_BEGIN, FILE_CURRENT, FILE_END }; + DWORD pos = SetFilePointer(file, offset, NULL, modes[seekType]); + return pos != INVALID_SET_FILE_POINTER ? 0 : GetLastError(); +} + +cc_result File_Position(cc_file file, cc_uint32* pos) { + *pos = SetFilePointer(file, 0, NULL, FILE_CURRENT); + return *pos != INVALID_SET_FILE_POINTER ? 0 : GetLastError(); +} + +cc_result File_Length(cc_file file, cc_uint32* len) { + *len = GetFileSize(file, NULL); + return *len != INVALID_FILE_SIZE ? 0 : GetLastError(); +} + + +/*########################################################################################################################* +*--------------------------------------------------------Threading--------------------------------------------------------* +*#############################################################################################################p############*/ +void Thread_Sleep(cc_uint32 milliseconds) { Sleep(milliseconds); } +static DWORD WINAPI ExecThread(void* param) { + Thread_StartFunc func = (Thread_StartFunc)param; + func(); + return 0; +} + +void* Thread_Create(Thread_StartFunc func) { + DWORD threadID; + void* handle = CreateThread(NULL, 0, ExecThread, (void*)func, CREATE_SUSPENDED, &threadID); + if (!handle) { + Logger_Abort2(GetLastError(), "Creating thread"); + } + return handle; +} + +void Thread_Start2(void* handle, Thread_StartFunc func) { + NtResumeThread((HANDLE)handle, NULL); +} + +void Thread_Detach(void* handle) { + if (!CloseHandle((HANDLE)handle)) { + Logger_Abort2(GetLastError(), "Freeing thread handle"); + } +} + +void Thread_Join(void* handle) { + WaitForSingleObject((HANDLE)handle, INFINITE); + Thread_Detach(handle); +} + +void* Mutex_Create(void) { + CRITICAL_SECTION* ptr = (CRITICAL_SECTION*)Mem_Alloc(1, sizeof(CRITICAL_SECTION), "mutex"); + RtlInitializeCriticalSection(ptr); + return ptr; +} + +void Mutex_Free(void* handle) { + RtlDeleteCriticalSection((CRITICAL_SECTION*)handle); + Mem_Free(handle); +} +void Mutex_Lock(void* handle) { RtlEnterCriticalSection((CRITICAL_SECTION*)handle); } +void Mutex_Unlock(void* handle) { RtlLeaveCriticalSection((CRITICAL_SECTION*)handle); } + +void* Waitable_Create(void) { + void* handle = CreateEventA(NULL, false, false, NULL); + if (!handle) { + Logger_Abort2(GetLastError(), "Creating waitable"); + } + return handle; +} + +void Waitable_Free(void* handle) { + if (!CloseHandle((HANDLE)handle)) { + Logger_Abort2(GetLastError(), "Freeing waitable"); + } +} + +void Waitable_Signal(void* handle) { NtSetEvent((HANDLE)handle, NULL); } +void Waitable_Wait(void* handle) { + WaitForSingleObject((HANDLE)handle, INFINITE); +} + +void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { + WaitForSingleObject((HANDLE)handle, milliseconds); +} + + +/*########################################################################################################################* +*--------------------------------------------------------Font/Text--------------------------------------------------------* +*#########################################################################################################################*/ +void Platform_LoadSysFonts(void) { } + + +/*########################################################################################################################* +*---------------------------------------------------------Socket----------------------------------------------------------* +*#########################################################################################################################*/ +union SocketAddress { + struct sockaddr raw; + struct sockaddr_in v4; +}; + +static int ParseHost(union SocketAddress* addr, const char* host) { + struct addrinfo hints = { 0 }; + struct addrinfo* result; + struct addrinfo* cur; + int family = 0, res; + + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + res = getaddrinfo(host, NULL, &hints, &result); + if (res) return 0; + + for (cur = result; cur; cur = cur->ai_next) { + if (cur->ai_family != AF_INET) continue; + family = AF_INET; + + Mem_Copy(addr, cur->ai_addr, cur->ai_addrlen); + break; + } + + freeaddrinfo(result); + return family; +} + +static int ParseAddress(union SocketAddress* addr, const cc_string* address) { + char str[NATIVE_STR_LEN]; + String_EncodeUtf8(str, address); + + if (inet_pton(AF_INET, str, &addr->v4.sin_addr) > 0) return AF_INET; + return ParseHost(addr, str); +} + +int Socket_ValidAddress(const cc_string* address) { + union SocketAddress addr; + return ParseAddress(&addr, address); +} + +cc_result Socket_Connect(cc_socket* s, const cc_string* address, int port, cc_bool nonblocking) { + int family, addrSize = 0; + union SocketAddress addr; + cc_result res; + + *s = -1; + if (!(family = ParseAddress(&addr, address))) + return ERR_INVALID_ARGUMENT; + + *s = socket(family, SOCK_STREAM, IPPROTO_TCP); + if (*s == -1) return errno; + + if (nonblocking) { + int blocking_raw = -1; /* non-blocking mode */ + ioctl(*s, FIONBIO, &blocking_raw); + } + + addr.v4.sin_family = AF_INET; + addr.v4.sin_port = htons(port); + addrSize = sizeof(addr.v4); + + res = connect(*s, &addr.raw, addrSize); + return res == -1 ? errno : 0; +} + +cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) { + int recvCount = recv(s, data, count, 0); + if (recvCount != -1) { *modified = recvCount; return 0; } + *modified = 0; return errno; +} + +cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) { + int sentCount = send(s, data, count, 0); + if (sentCount != -1) { *modified = sentCount; return 0; } + *modified = 0; return errno; +} + +void Socket_Close(cc_socket s) { + shutdown(s, SHUT_RDWR); + close(s); +} + +static cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) { + struct pollfd pfd; + int flags; + + pfd.fd = s; + pfd.events = mode == SOCKET_POLL_READ ? POLLIN : POLLOUT; + if (poll(&pfd, 1, 0) == -1) { *success = false; return errno; } + + /* to match select, closed socket still counts as readable */ + flags = mode == SOCKET_POLL_READ ? (POLLIN | POLLHUP) : POLLOUT; + *success = (pfd.revents & flags) != 0; + return 0; +} + +cc_result Socket_CheckReadable(cc_socket s, cc_bool* readable) { + return Socket_Poll(s, SOCKET_POLL_READ, readable); +} + +cc_result Socket_CheckWritable(cc_socket s, cc_bool* writable) { + socklen_t resultSize = sizeof(socklen_t); + cc_result res = Socket_Poll(s, SOCKET_POLL_WRITE, writable); + if (res || *writable) return res; + + /* https://stackoverflow.com/questions/29479953/so-error-value-after-successful-socket-operation */ + getsockopt(s, SOL_SOCKET, SO_ERROR, &res, &resultSize); + return res; +} + + +/*########################################################################################################################* +*-----------------------------------------------------Process/Module------------------------------------------------------* +*#########################################################################################################################*/ +static char gameArgs[GAME_MAX_CMDARGS][STRING_SIZE]; +static int gameNumArgs; +static cc_bool gameHasArgs; + +cc_result Process_StartGame2(const cc_string* args, int numArgs) { + for (int i = 0; i < numArgs; i++) + { + String_CopyToRawArray(gameArgs[i], &args[i]); + } + + Platform_LogConst("START GAME"); + gameHasArgs = true; + gameNumArgs = numArgs; + return 0; +} + +static int GetGameArgs(cc_string* args) { + int count = gameNumArgs; + for (int i = 0; i < count; i++) + { + args[i] = String_FromRawArray(gameArgs[i]); + } + + // clear arguments so after game is closed, launcher is started + gameNumArgs = 0; + return count; +} + +int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) { + if (gameHasArgs) return GetGameArgs(args); + // 3DS *sometimes* doesn't use argv[0] for program name and so argc will be 0 + // (e.g. when running from Citra) + if (!argc) return 0; + + argc--; argv++; // skip executable path argument + + int count = min(argc, GAME_MAX_CMDARGS); + Platform_Log1("ARGS: %i", &count); + + for (int i = 0; i < count; i++) { + args[i] = String_FromReadonly(argv[i]); + Platform_Log2(" ARG %i = %c", &i, argv[i]); + } + return count; +} + +cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) { + return 0; +} + +void Process_Exit(cc_result code) { exit(code); } + +cc_result Process_StartOpen(const cc_string* args) { + return ERR_NOT_SUPPORTED; +} + + +/*########################################################################################################################* +*--------------------------------------------------------Updater----------------------------------------------------------* +*#########################################################################################################################*/ +const char* const Updater_D3D9 = NULL; +cc_bool Updater_Clean(void) { return true; } + +const struct UpdaterInfo Updater_Info = { "&eCompile latest source code to update", 0 }; + +cc_result Updater_Start(const char** action) { *action = "Starting"; return ERR_NOT_SUPPORTED; } + +cc_result Updater_GetBuildTime(cc_uint64* timestamp) { return ERR_NOT_SUPPORTED; } + +cc_result Updater_MarkExecutable(void) { return ERR_NOT_SUPPORTED; } + +cc_result Updater_SetNewBuildTime(cc_uint64 timestamp) { return ERR_NOT_SUPPORTED; } + + +/*########################################################################################################################* +*-------------------------------------------------------Dynamic lib-------------------------------------------------------* +*#########################################################################################################################*/ +const cc_string DynamicLib_Ext = String_FromConst(".so"); + +void* DynamicLib_Load2(const cc_string* path) { return NULL; } + +void* DynamicLib_Get2(void* lib, const char* name) { return NULL; } + +cc_bool DynamicLib_DescribeError(cc_string* dst) { + String_AppendConst(dst, "Dynamic linking unsupported"); + return true; +} + + +/*########################################################################################################################* +*--------------------------------------------------------Platform---------------------------------------------------------* +*#########################################################################################################################*/ +void Platform_Init(void) { + Platform_InitStopwatch(); +} + +void Platform_Free(void) { +} + +cc_bool Platform_DescribeError(cc_result res, cc_string* dst) { + return false; +} + + +/*########################################################################################################################* +*-------------------------------------------------------Encryption--------------------------------------------------------* +*#########################################################################################################################*/ +cc_result Platform_Encrypt(const void* data, int len, cc_string* dst) { + return ERR_NOT_SUPPORTED; +} +cc_result Platform_Decrypt(const void* data, int len, cc_string* dst) { + return ERR_NOT_SUPPORTED; +} +#endif diff --git a/src/Window_Xbox.c b/src/Window_Xbox.c new file mode 100644 index 000000000..ffd0052ed --- /dev/null +++ b/src/Window_Xbox.c @@ -0,0 +1,124 @@ +#include "Core.h" +#if defined CC_BUILD_XBOX +#include "Window.h" +#include "Platform.h" +#include "Input.h" +#include "Event.h" +#include "Graphics.h" +#include "String.h" +#include "Funcs.h" +#include "Bitmap.h" +#include "Errors.h" +#include "ExtMath.h" +#include + +static cc_bool launcherMode; + +struct _DisplayData DisplayInfo; +struct _WinData WindowInfo; +// no DPI scaling on Xbox +int Display_ScaleX(int x) { return x; } +int Display_ScaleY(int y) { return y; } + +void Window_Init(void) { + XVideoSetMode(640, 480, 32, REFRESH_DEFAULT); // TODO not call + VIDEO_MODE mode = XVideoGetMode(); + + DisplayInfo.Width = mode.width; + DisplayInfo.Height = mode.height; + DisplayInfo.Depth = 4; // 32 bit + DisplayInfo.ScaleX = 1; + DisplayInfo.ScaleY = 1; + + WindowInfo.Width = mode.width; + WindowInfo.Height = mode.height; + WindowInfo.Focused = true; + WindowInfo.Exists = true; +} + +void Window_Create2D(int width, int height) { launcherMode = true; } +void Window_Create3D(int width, int height) { launcherMode = false; } + +void Window_SetTitle(const cc_string* title) { } +void Clipboard_GetText(cc_string* value) { } +void Clipboard_SetText(const cc_string* value) { } + +int Window_GetWindowState(void) { return WINDOW_STATE_FULLSCREEN; } +cc_result Window_EnterFullscreen(void) { return 0; } +cc_result Window_ExitFullscreen(void) { return 0; } +int Window_IsObscured(void) { return 0; } + +void Window_Show(void) { } +void Window_SetSize(int width, int height) { } + +void Window_Close(void) { + /* TODO implement */ +} + + +/*########################################################################################################################* +*----------------------------------------------------Input processing-----------------------------------------------------* +*#########################################################################################################################*/ +void Window_ProcessEvents(double delta) { + +} + +void Cursor_SetPosition(int x, int y) { } // Makes no sense for 3DS + +void Window_EnableRawMouse(void) { Input.RawMode = true; } +void Window_DisableRawMouse(void) { Input.RawMode = false; } +void Window_UpdateRawMouse(void) { } + + +/*########################################################################################################################* +*------------------------------------------------------Framebuffer--------------------------------------------------------* +*#########################################################################################################################*/ +static struct Bitmap fb_bmp; +void Window_AllocFramebuffer(struct Bitmap* bmp) { + bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels"); + fb_bmp = *bmp; +} + +void Window_DrawFramebuffer(Rect2D r) { + void* fb = XVideoGetFB(); + XVideoWaitForVBlank(); + + cc_uint32* src = (cc_uint32*)fb_bmp.scan0 + r.X; + cc_uint32* dst = (cc_uint32*)fb + r.X; + + for (int y = r.Y; y < r.Y + r.Height; y++) { + Mem_Copy(dst + y * fb_bmp.width, src + y * fb_bmp.width, r.Width * 4); + } +} + +void Window_FreeFramebuffer(struct Bitmap* bmp) { + Mem_Free(bmp->scan0); +} + + +/*########################################################################################################################* +*------------------------------------------------------Soft keyboard------------------------------------------------------* +*#########################################################################################################################*/ +void Window_OpenKeyboard(struct OpenKeyboardArgs* args) { } +void Window_SetKeyboardText(const cc_string* text) { } +void Window_CloseKeyboard(void) { /* TODO implement */ } + + +/*########################################################################################################################* +*-------------------------------------------------------Misc/Other--------------------------------------------------------* +*#########################################################################################################################*/ +void Window_ShowDialog(const char* title, const char* msg) { + /* TODO implement */ + Platform_LogConst(title); + Platform_LogConst(msg); +} + +cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { + return ERR_NOT_SUPPORTED; +} + +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + + return ERR_NOT_SUPPORTED; +} +#endif