// ftpd is a server implementation based on the following: // - RFC 959 (https://tools.ietf.org/html/rfc959) // - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // // Copyright (C) 2020 Michael Theall // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "platform.h" #include "fs.h" #include "log.h" #include "imgui_deko3d.h" #include "imgui_nx.h" #include "imgui.h" #include #include #include #include #include #include #include #include #include #include #ifdef CLASSIC PrintConsole g_statusConsole; PrintConsole g_logConsole; PrintConsole g_sessionConsole; #endif namespace { #ifdef CLASSIC in_addr_t s_addr = 0; #else /// \brief Texture index enum TextureIndex { DEKO3D_LOGO = 1, BATTERY_ICON, CHARGING_ICON, ETH_NONE_ICON, ETH_ICON, AIRPLANE_ICON, WIFI_NONE_ICON, WIFI1_ICON, WIFI2_ICON, WIFI3_ICON, MAX_TEXTURE, }; /// \brief deko3d logo width constexpr auto LOGO_WIDTH = 500; /// \brief deko3d logo height constexpr auto LOGO_HEIGHT = 493; /// \brief icon width constexpr auto ICON_WIDTH = 24; /// \brief icon height constexpr auto ICON_HEIGHT = 24; /// \brief Maximum number of samplers constexpr auto MAX_SAMPLERS = 2; /// \brief Maximum number of images constexpr auto MAX_IMAGES = MAX_TEXTURE; /// \brief Number of framebuffers constexpr auto FB_NUM = 2u; /// \brief Command buffer size constexpr auto CMDBUF_SIZE = 1024 * 1024; /// \brief Framebuffer width unsigned s_width = 1920; /// \brief Framebuffer height unsigned s_height = 1080; /// \brief deko3d device dk::UniqueDevice s_device; /// \brief Depth buffer memblock dk::UniqueMemBlock s_depthMemBlock; /// \brief Depth buffer image dk::Image s_depthBuffer; /// \brief Framebuffer memblock dk::UniqueMemBlock s_fbMemBlock; /// \brief Framebuffer images dk::Image s_frameBuffers[FB_NUM]; /// \brief Command buffer memblock dk::UniqueMemBlock s_cmdMemBlock[FB_NUM]; /// \brief Command buffers dk::UniqueCmdBuf s_cmdBuf[FB_NUM]; /// \brief Image memblock dk::UniqueMemBlock s_imageMemBlock; /// \brief Image/Sampler descriptor memblock dk::UniqueMemBlock s_descriptorMemBlock; /// \brief Sample descriptors dk::SamplerDescriptor *s_samplerDescriptors = nullptr; /// \brief Image descriptors dk::ImageDescriptor *s_imageDescriptors = nullptr; /// \brief deko3d queue dk::UniqueQueue s_queue; /// \brief deko3d swapchain dk::UniqueSwapchain s_swapchain; /// \brief Rebuild swapchain /// \param width_ Framebuffer width /// \param height_ Framebuffer height /// \note This assumes the first call is the largest a framebuffer will ever be void rebuildSwapchain (unsigned const width_, unsigned const height_) { // destroy old swapchain s_swapchain = nullptr; // create new depth buffer image layout dk::ImageLayout depthLayout; dk::ImageLayoutMaker{s_device} .setFlags (DkImageFlags_UsageRender | DkImageFlags_HwCompression) .setFormat (DkImageFormat_Z24S8) .setDimensions (width_, height_) .initialize (depthLayout); auto const depthAlign = depthLayout.getAlignment (); auto const depthSize = depthLayout.getSize (); // create depth buffer memblock if (!s_depthMemBlock) { s_depthMemBlock = dk::MemBlockMaker{s_device, imgui::deko3d::align ( depthSize, std::max (depthAlign, DK_MEMBLOCK_ALIGNMENT))} .setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) .create (); } s_depthBuffer.initialize (depthLayout, s_depthMemBlock, 0); // create framebuffer image layout dk::ImageLayout fbLayout; dk::ImageLayoutMaker{s_device} .setFlags ( DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression) .setFormat (DkImageFormat_RGBA8_Unorm) .setDimensions (width_, height_) .initialize (fbLayout); auto const fbAlign = fbLayout.getAlignment (); auto const fbSize = fbLayout.getSize (); // create framebuffer memblock if (!s_fbMemBlock) { s_fbMemBlock = dk::MemBlockMaker{s_device, imgui::deko3d::align ( FB_NUM * fbSize, std::max (fbAlign, DK_MEMBLOCK_ALIGNMENT))} .setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) .create (); } // initialize swapchain images std::array swapchainImages; for (unsigned i = 0; i < FB_NUM; ++i) { swapchainImages[i] = &s_frameBuffers[i]; s_frameBuffers[i].initialize (fbLayout, s_fbMemBlock, i * fbSize); } // create swapchain s_swapchain = dk::SwapchainMaker{s_device, nwindowGetDefault (), swapchainImages}.create (); } /// \brief Initialize deko3d void deko3dInit () { // create deko3d device s_device = dk::DeviceMaker{}.create (); // initialize swapchain with maximum resolution rebuildSwapchain (1920, 1080); // create memblocks for each image slot for (std::size_t i = 0; i < FB_NUM; ++i) { // create command buffer memblock s_cmdMemBlock[i] = dk::MemBlockMaker{s_device, imgui::deko3d::align (CMDBUF_SIZE, DK_MEMBLOCK_ALIGNMENT)} .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .create (); // create command buffer s_cmdBuf[i] = dk::CmdBufMaker{s_device}.create (); s_cmdBuf[i].addMemory (s_cmdMemBlock[i], 0, s_cmdMemBlock[i].getSize ()); } // create image/sampler memblock static_assert (sizeof (dk::ImageDescriptor) == DK_IMAGE_DESCRIPTOR_ALIGNMENT); static_assert (sizeof (dk::SamplerDescriptor) == DK_SAMPLER_DESCRIPTOR_ALIGNMENT); static_assert (DK_IMAGE_DESCRIPTOR_ALIGNMENT == DK_SAMPLER_DESCRIPTOR_ALIGNMENT); s_descriptorMemBlock = dk::MemBlockMaker{s_device, imgui::deko3d::align ( (MAX_SAMPLERS + MAX_IMAGES) * sizeof (dk::ImageDescriptor), DK_MEMBLOCK_ALIGNMENT)} .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .create (); // get cpu address for descriptors s_samplerDescriptors = static_cast (s_descriptorMemBlock.getCpuAddr ()); s_imageDescriptors = reinterpret_cast (&s_samplerDescriptors[MAX_SAMPLERS]); // create queue s_queue = dk::QueueMaker{s_device}.setFlags (DkQueueFlags_Graphics).create (); auto &cmdBuf = s_cmdBuf[0]; // bind image/sampler descriptors cmdBuf.bindSamplerDescriptorSet (s_descriptorMemBlock.getGpuAddr (), MAX_SAMPLERS); cmdBuf.bindImageDescriptorSet ( s_descriptorMemBlock.getGpuAddr () + MAX_SAMPLERS * sizeof (dk::SamplerDescriptor), MAX_IMAGES); s_queue.submitCommands (cmdBuf.finishList ()); s_queue.waitIdle (); cmdBuf.clear (); } /// \brief Load textures void loadTextures () { struct TextureInfo { TextureInfo (char const *const path_, unsigned const width_, unsigned const height_) : path (path_), width (width_), height (height_) { } char const *const path; unsigned width; unsigned height; }; TextureInfo textureInfos[] = {TextureInfo ("romfs:/deko3d.rgba.zst", LOGO_WIDTH, LOGO_HEIGHT), TextureInfo ("romfs:/battery_icon.rgba.zst", ICON_WIDTH, ICON_HEIGHT), TextureInfo ("romfs:/charging_icon.rgba.zst", ICON_WIDTH, ICON_HEIGHT), TextureInfo ("romfs:/eth_none_icon.rgba.zst", ICON_WIDTH, ICON_HEIGHT), TextureInfo ("romfs:/eth_icon.rgba.zst", ICON_WIDTH, ICON_HEIGHT), TextureInfo ("romfs:/airplane_icon.rgba.zst", ICON_WIDTH, ICON_HEIGHT), TextureInfo ("romfs:/wifi_none_icon.rgba.zst", ICON_WIDTH, ICON_HEIGHT), TextureInfo ("romfs:/wifi1_icon.rgba.zst", ICON_WIDTH, ICON_HEIGHT), TextureInfo ("romfs:/wifi2_icon.rgba.zst", ICON_WIDTH, ICON_HEIGHT), TextureInfo ("romfs:/wifi3_icon.rgba.zst", ICON_WIDTH, ICON_HEIGHT)}; // create memblock for transfer (large enough for the largest source file) dk::UniqueMemBlock memBlock = dk::MemBlockMaker{s_device, imgui::deko3d::align (1048576, DK_MEMBLOCK_ALIGNMENT)} .setFlags (DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached) .create (); // create image memblock (large enough to hold all the images) s_imageMemBlock = dk::MemBlockMaker{s_device, imgui::deko3d::align (1048576 + (MAX_TEXTURE - 2) * 4096, DK_MEMBLOCK_ALIGNMENT)} .setFlags (DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) .create (); auto &cmdBuf = s_cmdBuf[0]; unsigned imageIndex = 1; unsigned imageOffset = 0; for (auto const &textureInfo : textureInfos) { struct stat st; if (::stat (textureInfo.path, &st) != 0) { std::fprintf (stderr, "stat(%s): %s\n", textureInfo.path, std::strerror (errno)); std::abort (); } fs::File fp; if (!fp.open (textureInfo.path)) { std::fprintf (stderr, "open(%s): %s\n", textureInfo.path, std::strerror (errno)); std::abort (); } // read file into memory std::vector buffer (st.st_size); if (!fp.readAll (buffer.data (), buffer.size ())) { std::fprintf (stderr, "read(%s): %s\n", textureInfo.path, std::strerror (errno)); std::abort (); } // get uncompressed size auto const size = ZSTD_getFrameContentSize (buffer.data (), buffer.size ()); if (ZSTD_isError (size)) { std::fprintf (stderr, "ZSTD_getFrameContentSize: %s\n", ZSTD_getErrorName (size)); std::abort (); } assert (size <= memBlock.getSize ()); // wait for previous transfer to complete s_queue.waitIdle (); // decompress into transfer memblock auto const decoded = ZSTD_decompress (memBlock.getCpuAddr (), size, buffer.data (), buffer.size ()); if (ZSTD_isError (decoded)) { std::fprintf (stderr, "ZSTD_decompress: %s\n", ZSTD_getErrorName (decoded)); std::abort (); } // initialize texture image layout dk::ImageLayout layout; dk::ImageLayoutMaker{s_device} .setFlags (0) .setFormat (DkImageFormat_RGBA8_Unorm) .setDimensions (textureInfo.width, textureInfo.height) .initialize (layout); // calculate image offset imageOffset = imgui::deko3d::align (imageOffset, layout.getAlignment ()); assert (imageOffset < s_imageMemBlock.getSize ()); assert (s_imageMemBlock.getSize () - imageOffset >= layout.getSize ()); // initialize image descriptor dk::Image image; image.initialize (layout, s_imageMemBlock, imageOffset); s_imageDescriptors[imageIndex++].initialize (image); // copy texture to image dk::ImageView imageView (image); cmdBuf.copyBufferToImage ({memBlock.getGpuAddr ()}, imageView, {0, 0, 0, textureInfo.width, textureInfo.height, 1}); s_queue.submitCommands (cmdBuf.finishList ()); imageOffset += imgui::deko3d::align (layout.getSize (), layout.getAlignment ()); } // initialize sampler descriptor s_samplerDescriptors[1].initialize ( dk::Sampler{} .setFilter (DkFilter_Linear, DkFilter_Linear) .setWrapMode (DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge)); // wait for commands to complete before releasing memblocks s_queue.waitIdle (); } /// \brief Deinitialize deko3d void deko3dExit () { // clean up all of the deko3d objects s_imageMemBlock = nullptr; s_descriptorMemBlock = nullptr; for (unsigned i = 0; i < FB_NUM; ++i) { s_cmdBuf[i] = nullptr; s_cmdMemBlock[i] = nullptr; } s_queue = nullptr; s_swapchain = nullptr; s_fbMemBlock = nullptr; s_depthMemBlock = nullptr; s_device = nullptr; } #endif /// \brief Draw time status void drawTimeStatus () { #ifndef CLASSIC auto const &io = ImGui::GetIO (); auto const &style = ImGui::GetStyle (); #endif // draw current timestamp char timeBuffer[64]; auto const now = std::time (nullptr); std::strftime (timeBuffer, sizeof (timeBuffer), "%H:%M:%S", std::localtime (&now)); #ifdef CLASSIC static std::string statusString; std::string newStatusString (256, '\0'); newStatusString.resize (std::sprintf (&newStatusString[0], "\x1b[0;0H\x1b[32;1m%s \x1b[36;1m%s%s \x1b[37;1m%s\x1b[K", STATUS_STRING, s_addr ? inet_ntoa (in_addr{s_addr}) : "Waiting", s_addr ? ":5000" : "", timeBuffer)); if (newStatusString != statusString) { statusString = std::move (newStatusString); consoleSelect (&g_statusConsole); std::fputs (statusString.c_str (), stdout); std::fflush (stdout); } #else ImGui::GetForegroundDrawList ()->AddText ( ImVec2 (io.DisplaySize.x - 240.0f, style.FramePadding.y), ImGui::GetColorU32 (ImGuiCol_Text), timeBuffer); #endif } /// \brief Draw network status void drawNetworkStatus () { #ifndef CLASSIC TextureIndex netIcon = AIRPLANE_ICON; NifmInternetConnectionType type; std::uint32_t wifiStrength; NifmInternetConnectionStatus status; if (R_SUCCEEDED (nifmGetInternetConnectionStatus (&type, &wifiStrength, &status))) { if (type == NifmInternetConnectionType_Ethernet) { if (status == NifmInternetConnectionStatus_Connected) netIcon = ETH_ICON; else netIcon = ETH_NONE_ICON; } else { if (wifiStrength >= 3) netIcon = WIFI3_ICON; else if (wifiStrength == 2) netIcon = WIFI3_ICON; else if (wifiStrength == 1) netIcon = WIFI3_ICON; else if (wifiStrength == 0) netIcon = WIFI_NONE_ICON; } } auto const &io = ImGui::GetIO (); auto const &style = ImGui::GetStyle (); auto const x1 = io.DisplaySize.x - ICON_WIDTH - 100.0f; auto const x2 = x1 + ICON_WIDTH; auto const y1 = style.FramePadding.y; auto const y2 = y1 + ICON_HEIGHT; ImGui::GetForegroundDrawList ()->AddImage ( imgui::deko3d::makeTextureID (dkMakeTextureHandle (netIcon, 1)), ImVec2 (x1, y1), ImVec2 (x2, y2), ImVec2 (0, 0), ImVec2 (1, 1), ImGui::GetColorU32 (ImGuiCol_Text)); #endif } /// \brief Draw power status void drawPowerStatus () { #ifndef CLASSIC std::uint32_t batteryCharge = 0; psmGetBatteryChargePercentage (&batteryCharge); ChargerType charger = ChargerType_None; psmGetChargerType (&charger); TextureIndex powerIcon = BATTERY_ICON; if (charger != ChargerType_None) powerIcon = CHARGING_ICON; auto const &io = ImGui::GetIO (); auto const &style = ImGui::GetStyle (); auto const x1 = io.DisplaySize.x - ICON_WIDTH - style.FramePadding.x; auto const x2 = x1 + ICON_WIDTH; auto const y1 = style.FramePadding.y; auto const y2 = y1 + ICON_HEIGHT; ImGui::GetForegroundDrawList ()->AddImage ( imgui::deko3d::makeTextureID (dkMakeTextureHandle (powerIcon, 1)), ImVec2 (x1, y1), ImVec2 (x2, y2), ImVec2 (0, 0), ImVec2 (1, 1), ImGui::GetColorU32 (ImGuiCol_Text)); char buffer[16]; std::sprintf (buffer, "%3u%%", batteryCharge); ImGui::GetForegroundDrawList ()->AddText ( ImVec2 (x1 - 70.0f, y1), ImGui::GetColorU32 (ImGuiCol_Text), buffer); #endif } /// \brief Draw status void drawStatus () { drawTimeStatus (); drawNetworkStatus (); drawPowerStatus (); } } bool platform::init () { #ifdef CLASSIC consoleInit (&g_statusConsole); consoleInit (&g_logConsole); consoleInit (&g_sessionConsole); consoleSetWindow (&g_statusConsole, 0, 0, 80, 1); consoleSetWindow (&g_logConsole, 0, 1, 80, 29); consoleSetWindow (&g_sessionConsole, 0, 30, 80, 15); #endif #ifndef NDEBUG std::setvbuf (stderr, nullptr, _IOLBF, 0); #endif #ifndef CLASSIC if (!imgui::nx::init ()) return false; deko3dInit (); loadTextures (); imgui::deko3d::init (s_device, s_queue, s_cmdBuf[0], s_samplerDescriptors[0], s_imageDescriptors[0], dkMakeTextureHandle (0, 0), FB_NUM); #endif return true; } bool platform::networkVisible () { NifmInternetConnectionType type; std::uint32_t wifi; NifmInternetConnectionStatus status; if (R_FAILED (nifmGetInternetConnectionStatus (&type, &wifi, &status))) return false; #ifdef CLASSIC if (!s_addr) s_addr = gethostid (); #endif return status == NifmInternetConnectionStatus_Connected; } bool platform::loop () { if (!appletMainLoop ()) return false; hidScanInput (); auto const keysDown = hidKeysDown (CONTROLLER_P1_AUTO); if (keysDown & KEY_PLUS) return false; #ifndef CLASSIC imgui::nx::newFrame (); ImGui::NewFrame (); auto const &io = ImGui::GetIO (); auto const width = io.DisplaySize.x; auto const height = io.DisplaySize.y; auto const x1 = (width - LOGO_WIDTH) * 0.5f; auto const x2 = x1 + LOGO_WIDTH; auto const y1 = (height - LOGO_HEIGHT) * 0.5f; auto const y2 = y1 + LOGO_HEIGHT; ImGui::GetBackgroundDrawList ()->AddImage ( imgui::deko3d::makeTextureID (dkMakeTextureHandle (1, 1)), ImVec2 (x1, y1), ImVec2 (x2, y2)); #endif drawStatus (); return true; } void platform::render () { #ifdef CLASSIC drawLog (); consoleUpdate (&g_statusConsole); consoleUpdate (&g_logConsole); consoleUpdate (&g_sessionConsole); #else ImGui::Render (); auto &io = ImGui::GetIO (); if (s_width != io.DisplaySize.x || s_height != io.DisplaySize.y) { s_width = io.DisplaySize.x; s_height = io.DisplaySize.y; rebuildSwapchain (s_width, s_height); } // get image from queue auto const slot = s_queue.acquireImage (s_swapchain); auto &cmdBuf = s_cmdBuf[slot]; cmdBuf.clear (); // bind frame/depth buffers and clear them dk::ImageView colorTarget{s_frameBuffers[slot]}; dk::ImageView depthTarget{s_depthBuffer}; cmdBuf.bindRenderTargets (&colorTarget, &depthTarget); cmdBuf.clearColor (0, DkColorMask_RGBA, 0.125f, 0.294f, 0.478f, 1.0f); cmdBuf.clearDepthStencil (true, 1.0f, 0xFF, 0); s_queue.submitCommands (cmdBuf.finishList ()); imgui::deko3d::render (s_device, s_queue, cmdBuf, slot); // wait for fragments to be completed before discarding depth/stencil buffer cmdBuf.barrier (DkBarrier_Fragments, 0); cmdBuf.discardDepthStencil (); // present image s_queue.presentImage (s_swapchain, slot); #endif } void platform::exit () { #ifdef CLASSIC consoleExit (&g_sessionConsole); consoleExit (&g_logConsole); consoleExit (&g_statusConsole); #else imgui::nx::exit (); // wait for queue to be idle s_queue.waitIdle (); imgui::deko3d::exit (); deko3dExit (); #endif } /////////////////////////////////////////////////////////////////////////// /// \brief Platform thread pimpl class platform::Thread::privateData_t { public: privateData_t () = default; /// \brief Parameterized constructor /// \param func_ Thread entry point privateData_t (std::function &&func_) : thread (std::move (func_)) { } /// \brief Underlying thread std::thread thread; }; /////////////////////////////////////////////////////////////////////////// platform::Thread::~Thread () = default; platform::Thread::Thread () : m_d (new privateData_t ()) { } platform::Thread::Thread (std::function &&func_) : m_d (new privateData_t (std::move (func_))) { } platform::Thread::Thread (Thread &&that_) : m_d (new privateData_t ()) { std::swap (m_d, that_.m_d); } platform::Thread &platform::Thread::operator= (Thread &&that_) { std::swap (m_d, that_.m_d); return *this; } void platform::Thread::join () { m_d->thread.join (); } void platform::Thread::sleep (std::chrono::milliseconds const timeout_) { std::this_thread::sleep_for (timeout_); } /////////////////////////////////////////////////////////////////////////// #define USE_STD_MUTEX 1 /// \brief Platform mutex pimpl class platform::Mutex::privateData_t { public: #if USE_STD_MUTEX /// \brief Underlying mutex std::mutex mutex; #else /// \brief Underlying mutex ::Mutex mutex; #endif }; /////////////////////////////////////////////////////////////////////////// platform::Mutex::~Mutex () = default; platform::Mutex::Mutex () : m_d (new privateData_t ()) { #if !USE_STD_MUTEX mutexInit (&m_d->mutex); #endif } void platform::Mutex::lock () { #if USE_STD_MUTEX m_d->mutex.lock (); #else mutexLock (&m_d->mutex); #endif } void platform::Mutex::unlock () { #if USE_STD_MUTEX m_d->mutex.unlock (); #else mutexUnlock (&m_d->mutex); #endif }