diff --git a/.gitignore b/.gitignore index 2b1276b..3a4297b 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ switch/build switch-classic/build switch/romfs/*.zst switch/romfs/shaders/*.dksh +.idea/ +build/ +*.rpx +*.wuhb diff --git a/Makefile b/Makefile index 92512c4..e7ff403 100644 --- a/Makefile +++ b/Makefile @@ -12,9 +12,9 @@ export VERSION_MICRO := 0 export VERSION := $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_MICRO) ########################################################################### -all: nds 3dsx nro linux +all: wiiu nds 3dsx nro linux -all-classic: nds 3dsx-classic nro-classic linux +all-classic: wiiu nds 3dsx-classic nro-classic linux format: @clang-format -style=file -i $(filter-out \ @@ -36,6 +36,7 @@ format: , $(shell find source include -type f -name \*.c -o -name \*.cpp -o -name \*.h)) clean: + @$(MAKE) -f Makefile.wiiu clean @$(MAKE) -f Makefile.nds clean @$(MAKE) -f Makefile.3ds clean @$(MAKE) -f Makefile.3ds clean CLASSIC="-DCLASSIC" @@ -61,6 +62,9 @@ nxlink-classic: @$(MAKE) -f Makefile.switch nxlink CLASSIC="-DCLASSIC" ########################################################################### +wiiu: + @$(MAKE) -f Makefile.wiiu CLASSIC="-DCLASSIC" + nds: @$(MAKE) -f Makefile.nds CLASSIC="-DCLASSIC" diff --git a/Makefile.wiiu b/Makefile.wiiu new file mode 100644 index 0000000..5e787b1 --- /dev/null +++ b/Makefile.wiiu @@ -0,0 +1,181 @@ +#------------------------------------------------------------------------------- +.SUFFIXES: +#------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) + +#------------------------------------------------------------------------------- +# APP_NAME sets the long name of the application +# APP_SHORTNAME sets the short name of the application +# APP_AUTHOR sets the author of the application +#------------------------------------------------------------------------------- +#APP_NAME := Application Name +#APP_SHORTNAME := App Name +#APP_AUTHOR := Built with devkitPPC & wut + +include $(DEVKITPRO)/wut/share/wut_rules + +#------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# CONTENT is the path to the bundled folder that will be mounted as /vol/content/ +# ICON is the game icon, leave blank to use default rule +# TV_SPLASH is the image displayed during bootup on the TV, leave blank to use default rule +# DRC_SPLASH is the image displayed during bootup on the DRC, leave blank to use default rule +#------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source source/wiiu +DATA := data +INCLUDES := include +CONTENT := +ICON := +TV_SPLASH := +DRC_SPLASH := + +#------------------------------------------------------------------------------- +# options for code generation +#------------------------------------------------------------------------------- +CFLAGS := -g -Wall -O0 -ffunction-sections \ + $(MACHDEP) + +CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -DSTATUS_STRING="\"ftpd v$(VERSION)\"" \ + -DNO_IPV6 -DCLASSIC -DNO_CONSOLE -DFTPDCONFIG="\"/config/ftpd/ftpd.cfg\"" + +CXXFLAGS := $(CFLAGS) -std=c++20 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) + +LIBS := -lwutd -lmocha + +#------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level +# containing include and lib +#------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(WUT_ROOT) $(WUT_ROOT)/usr + +#------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#------------------------------------------------------------------------------- + export LD := $(CC) +#------------------------------------------------------------------------------- +else +#------------------------------------------------------------------------------- + export LD := $(CXX) +#------------------------------------------------------------------------------- +endif +#------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifneq (,$(strip $(CONTENT))) + export APP_CONTENT := $(TOPDIR)/$(CONTENT) +endif + +ifneq (,$(strip $(ICON))) + export APP_ICON := $(TOPDIR)/$(ICON) +else ifneq (,$(wildcard $(TOPDIR)/$(TARGET).png)) + export APP_ICON := $(TOPDIR)/$(TARGET).png +else ifneq (,$(wildcard $(TOPDIR)/icon.png)) + export APP_ICON := $(TOPDIR)/icon.png +endif + +ifneq (,$(strip $(TV_SPLASH))) + export APP_TV_SPLASH := $(TOPDIR)/$(TV_SPLASH) +else ifneq (,$(wildcard $(TOPDIR)/tv-splash.png)) + export APP_TV_SPLASH := $(TOPDIR)/tv-splash.png +else ifneq (,$(wildcard $(TOPDIR)/splash.png)) + export APP_TV_SPLASH := $(TOPDIR)/splash.png +endif + +ifneq (,$(strip $(DRC_SPLASH))) + export APP_DRC_SPLASH := $(TOPDIR)/$(DRC_SPLASH) +else ifneq (,$(wildcard $(TOPDIR)/drc-splash.png)) + export APP_DRC_SPLASH := $(TOPDIR)/drc-splash.png +else ifneq (,$(wildcard $(TOPDIR)/splash.png)) + export APP_DRC_SPLASH := $(TOPDIR)/splash.png +endif + +.PHONY: $(BUILD) clean all + +#------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @$(shell [ ! -d $(BUILD) ] && mkdir -p $(BUILD)) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile.wiiu + +#------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).wuhb $(TARGET).rpx $(TARGET).elf + +#------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#------------------------------------------------------------------------------- +# main targets +#------------------------------------------------------------------------------- +all : $(OUTPUT).wuhb + +$(OUTPUT).wuhb : $(OUTPUT).rpx +$(OUTPUT).rpx : $(OUTPUT).elf +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#------------------------------------------------------------------------------- +endif +#------------------------------------------------------------------------------- diff --git a/include/ftpSession.h b/include/ftpSession.h index d5b5450..b3478a2 100644 --- a/include/ftpSession.h +++ b/include/ftpSession.h @@ -60,7 +60,13 @@ private: /// \brief Command buffer size constexpr static auto COMMAND_BUFFERSIZE = 4096; -#ifdef NDS +#ifdef __WIIU__ + /// \brief Response buffer size + constexpr static auto RESPONSE_BUFFERSIZE = 128 * 1024; + + /// \brief Transfer buffersize + constexpr static auto XFER_BUFFERSIZE = 128 * 1024; +#elif defined(NDS) /// \brief Response buffer size constexpr static auto RESPONSE_BUFFERSIZE = 4096; @@ -238,7 +244,7 @@ private: SockAddr m_portAddr; /// \brief Current working directory - std::string m_cwd = "/"; + std::string m_cwd = "/fs/vol/external01/"; /// \brief List working directory std::string m_lwd; diff --git a/include/platform.h b/include/platform.h index e34041a..fef3b93 100644 --- a/include/platform.h +++ b/include/platform.h @@ -28,6 +28,9 @@ #include <3ds.h> #elif defined(__SWITCH__) #include +#elif defined(__WIIU__) +#include +#include #endif #include @@ -35,7 +38,7 @@ #include #include -#ifdef CLASSIC +#if defined(CLASSIC) && !defined(__WIIU__) extern PrintConsole g_statusConsole; extern PrintConsole g_logConsole; extern PrintConsole g_sessionConsole; diff --git a/include/socket.h b/include/socket.h index a2d2504..7effc1d 100644 --- a/include/socket.h +++ b/include/socket.h @@ -102,6 +102,8 @@ public: /// \param nonBlocking_ Whether to set non-blocking bool setNonBlocking (bool nonBlocking_ = true); + bool setWinScale (const int val); + /// \brief Set reuse address in subsequent bind /// \param reuse_ Whether to reuse address bool setReuseAddress (bool reuse_ = true); diff --git a/source/IOAbstraction.cpp b/source/IOAbstraction.cpp new file mode 100644 index 0000000..93bde66 --- /dev/null +++ b/source/IOAbstraction.cpp @@ -0,0 +1,197 @@ +#include "IOAbstraction.h" +#include +#include +#include +#include +#include + +class VirtualDirectory +{ +public: + VirtualDirectory (const std::vector &directories) + { + mDirectories.push_back ("."); + mDirectories.push_back (".."); + mDirectories.insert (mDirectories.end (), directories.begin (), directories.end ()); + + mCurIterator = mDirectories.begin (); + } + + [[nodiscard]] DIR *getAsDir () const + { + return (DIR *)this; + } + + struct dirent *readdir () + { + if (mCurIterator == mDirectories.end ()) + { + return nullptr; + } + mDir = {}; + snprintf (mDir.d_name, sizeof (mDir.d_name), "%s", mCurIterator->c_str ()); + mCurIterator++; + return &mDir; + } + +private: + std::vector mDirectories; + struct dirent mDir = {}; + std::vector::iterator mCurIterator{}; +}; + +std::vector> sOpenVirtualDirectories; +std::mutex sOpenVirtualDirectoriesMutex; +std::map> sVirtualDirs; + +template +typename std::enable_if>::value, + bool>::type + remove_first_if (Container &container, Predicate pred) +{ + auto it = container.begin (); + while (it != container.end ()) + { + if (pred (*it)) + { + container.erase (it); + return true; + } + ++it; + } + + return false; +} + +template +bool remove_locked_first_if (std::mutex &mutex, Container &container, Predicate pred) +{ + std::lock_guard lock (mutex); + return remove_first_if (container, pred); +} + +static DIR *getVirtualDir (const std::vector &subDirectories) +{ + auto virtDir = std::make_unique (subDirectories); + auto *result = virtDir->getAsDir (); + std::lock_guard lock (sOpenVirtualDirectoriesMutex); + sOpenVirtualDirectories.push_back (std::move (virtDir)); + return result; +} + +std::string IOAbstraction::convertPath (std::string_view inPath) +{ +#ifdef __WIIU__ + if (!inPath.starts_with ('/') || inPath.find (':') != std::string::npos) + { + return std::string (inPath); + } + std::string path = std::string (inPath); + size_t secondSlashPos = path.find ('/', 1); + if (secondSlashPos != std::string::npos) + { + // Extract the substring between the first and second slashes + std::string prefix = path.substr (1, secondSlashPos - 1); + std::string suffix = path.substr (secondSlashPos); + + // Concatenate the modified prefix and suffix + path = prefix + ":" + suffix; + } + else + { + path = std::string (inPath.substr (1)) + ":/"; + } + return path; +#else + return std::string (inPath); +#endif +} + +int IOAbstraction::closedir (DIR *dirp) +{ + { + std::lock_guard lock (sOpenVirtualDirectoriesMutex); + if (remove_locked_first_if (sOpenVirtualDirectoriesMutex, + sOpenVirtualDirectories, + [dirp] (auto &cur) { return cur->getAsDir () == dirp; })) + { + return 0; + } + } + return ::closedir (dirp); +} + +DIR *IOAbstraction::opendir (const char *dirname) +{ + auto convertedPath = convertPath (dirname); + if (sVirtualDirs.count (convertedPath) > 0) + { + return getVirtualDir (sVirtualDirs[convertedPath]); + } + + return ::opendir (convertedPath.c_str ()); +} + +FILE *IOAbstraction::fopen (const char *_name, const char *_type) +{ + return std::fopen (convertPath (_name).c_str (), _type); +} + +int IOAbstraction::fseek (FILE *f, long pos, int origin) +{ + return std::fseek (f, pos, origin); +} + +size_t IOAbstraction::fread (void *buffer, size_t _size, size_t _n, FILE *f) +{ + return std::fread (buffer, _size, _n, f); +} + +size_t IOAbstraction::fwrite (const void *buffer, size_t _size, size_t _n, FILE *f) +{ + return std::fwrite (buffer, _size, _n, f); +} + +struct dirent *IOAbstraction::readdir (DIR *dirp) +{ + { + std::lock_guard lock (sOpenVirtualDirectoriesMutex); + auto itr = std::find_if (sOpenVirtualDirectories.begin (), + sOpenVirtualDirectories.end (), + [dirp] (auto &cur) { return cur->getAsDir () == dirp; }); + if (itr != sOpenVirtualDirectories.end ()) + { + return (*itr)->readdir (); + } + } + + return ::readdir (dirp); +} + +int IOAbstraction::stat (const char *path, struct stat *sbuf) +{ + auto convertedPath = convertPath (path); + auto r = ::stat (convertedPath.c_str (), sbuf); + if (r < 0) + { + if (sVirtualDirs.contains (convertedPath)) + { + *sbuf = {}; + // TODO: init other values? + sbuf->st_mode = _IFDIR; + return 0; + } + } + return r; +} + +int IOAbstraction::lstat (const char *path, struct stat *buf) +{ + return IOAbstraction::stat (path, buf); +} + +void IOAbstraction::addVirtualPath (const std::string &virtualPath, + const std::vector &subDirectories) +{ + sVirtualDirs.insert (std::make_pair (virtualPath, subDirectories)); +} diff --git a/source/IOAbstraction.h b/source/IOAbstraction.h new file mode 100644 index 0000000..7ad9199 --- /dev/null +++ b/source/IOAbstraction.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + +class IOAbstraction +{ +public: + static std::string convertPath (std::string_view inPath); + + static FILE *fopen (const char *_name, const char *_type); + + static int fseek (FILE *f, long pos, int origin); + + static size_t fread (void *buffer, size_t _size, size_t _n, FILE *f); + + static size_t fwrite (const void *buffer, size_t _size, size_t _n, FILE *f); + + static int closedir (DIR *dirp); + + + static DIR *opendir (const char *dirname); + + static struct dirent *readdir (DIR *dirp); + + static int stat (const char *path, struct stat *sbuf); + + static int lstat (const char *path, struct stat *buf); + + static void addVirtualPath (const std::string &virtualPath, + const std::vector &subDirectories); +}; diff --git a/source/fs.cpp b/source/fs.cpp index 4c0efa9..98c6865 100644 --- a/source/fs.cpp +++ b/source/fs.cpp @@ -19,13 +19,14 @@ // along with this program. If not, see . #include "fs.h" +#include "IOAbstraction.h" #include #include #include #include -#if defined(NDS) || defined(__3DS__) || defined(__SWITCH__) +#if defined(NDS) || defined(__3DS__) || defined(__SWITCH__) || defined(__WIIU__) #define getline __getline #endif @@ -118,7 +119,7 @@ void fs::File::setBufferSize (std::size_t const size_) bool fs::File::open (char const *const path_, char const *const mode_) { - auto const fp = std::fopen (path_, mode_); + auto const fp = IOAbstraction::fopen (path_, mode_); if (!fp) return false; @@ -137,7 +138,7 @@ void fs::File::close () std::make_signed_t fs::File::seek (std::size_t const pos_, int const origin_) { - return std::fseek (m_fp.get (), pos_, origin_); + return IOAbstraction::fseek (m_fp.get (), pos_, origin_); } std::make_signed_t fs::File::read (void *const buffer_, std::size_t const size_) @@ -145,7 +146,7 @@ std::make_signed_t fs::File::read (void *const buffer_, std::size_t assert (buffer_); assert (size_ > 0); - return std::fread (buffer_, 1, size_, m_fp.get ()); + return IOAbstraction::fread (buffer_, 1, size_, m_fp.get ()); } std::make_signed_t fs::File::read (IOBuffer &buffer_) @@ -206,7 +207,7 @@ std::make_signed_t fs::File::write (void const *const buffer_, std: assert (buffer_); assert (size_ > 0); - return std::fwrite (buffer_, 1, size_, m_fp.get ()); + return IOAbstraction::fwrite (buffer_, 1, size_, m_fp.get ()); } std::make_signed_t fs::File::write (IOBuffer &buffer_) @@ -262,11 +263,11 @@ fs::Dir::operator DIR * () const bool fs::Dir::open (char const *const path_) { - auto const dp = ::opendir (path_); + auto const dp = IOAbstraction::opendir (path_); if (!dp) return false; - m_dp = std::unique_ptr (dp, &::closedir); + m_dp = std::unique_ptr (dp, &IOAbstraction::closedir); return true; } @@ -278,5 +279,5 @@ void fs::Dir::close () struct dirent *fs::Dir::read () { errno = 0; - return ::readdir (m_dp.get ()); + return IOAbstraction::readdir (m_dp.get ()); } diff --git a/source/ftpServer.cpp b/source/ftpServer.cpp index 30d358b..d480893 100644 --- a/source/ftpServer.cpp +++ b/source/ftpServer.cpp @@ -202,11 +202,13 @@ void FtpServer::draw () if (m_socket) std::sprintf (port, ":%u", m_socket->sockName ().port ()); +#ifndef NO_CONSOLE consoleSelect (&g_statusConsole); std::printf ("\x1b[0;0H\x1b[32;1m%s \x1b[36;1m%s%s", STATUS_STRING, m_socket ? m_socket->sockName ().name () : "Waiting on WiFi", m_socket ? port : ""); +#endif #ifndef NDS char timeBuffer[16]; @@ -226,11 +228,14 @@ void FtpServer::draw () #endif if (!s_freeSpace.empty ()) { +#ifndef NO_CONSOLE consoleSelect (&g_statusConsole); std::printf ("\x1b[0;%uH\x1b[32;1m%s", static_cast (g_statusConsole.windowWidth - s_freeSpace.size () + 1), s_freeSpace.c_str ()); std::fflush (stdout); + +#endif } } @@ -238,6 +243,7 @@ void FtpServer::draw () #ifndef NDS auto const lock = std::scoped_lock (m_lock); #endif +#ifndef NO_CONSOLE consoleSelect (&g_sessionConsole); std::fputs ("\x1b[2J", stdout); for (auto &session : m_sessions) @@ -247,6 +253,7 @@ void FtpServer::draw () std::fputc ('\n', stdout); } std::fflush (stdout); +#endif } drawLog (); diff --git a/source/ftpSession.cpp b/source/ftpSession.cpp index f1bfe1b..295cd34 100644 --- a/source/ftpSession.cpp +++ b/source/ftpSession.cpp @@ -20,6 +20,7 @@ #include "ftpSession.h" +#include "IOAbstraction.h" #include "ftpServer.h" #include "log.h" #include "platform.h" @@ -169,7 +170,7 @@ std::string resolvePath (std::string_view const path_) // make sure parent is a directory struct stat st; - if (::stat (dirName (path_).c_str (), &st) != 0) + if (IOAbstraction::stat (dirName (path_).c_str (), &st) != 0) return {}; if (!S_ISDIR (st.st_mode)) @@ -640,7 +641,9 @@ void FtpSession::closeSocket (SharedSocket &socket_) if (socket_ && socket_.unique ()) { socket_->shutdown (SHUT_WR); +#ifndef __WIIU__ socket_->setLinger (true, 0s); +#endif LOCKED (m_pendingCloseSocket.emplace_back (std::move (socket_))); } else @@ -685,7 +688,7 @@ bool FtpSession::changeDir (char const *const args_) return false; struct stat st; - if (::stat (path.c_str (), &st) != 0) + if (IOAbstraction::stat (path.c_str (), &st) != 0) return false; if (!S_ISDIR (st.st_mode)) @@ -719,6 +722,9 @@ bool FtpSession::dataAccept () } #ifndef __3DS__ +#ifdef __WIIU__ + m_dataSocket->setWinScale (1); +#endif m_dataSocket->setRecvBufferSize (SOCK_BUFFERSIZE); m_dataSocket->setSendBufferSize (SOCK_BUFFERSIZE); #endif @@ -747,6 +753,9 @@ bool FtpSession::dataConnect () if (!m_dataSocket) return false; +#ifdef __WIIU__ + m_dataSocket->setWinScale (1); +#endif m_dataSocket->setRecvBufferSize (SOCK_BUFFERSIZE); m_dataSocket->setSendBufferSize (SOCK_BUFFERSIZE); @@ -793,7 +802,7 @@ int FtpSession::fillDirent (struct stat const &st_, std::string_view const path_ type_ = "file"; else if (S_ISDIR (st_.st_mode)) type_ = "dir"; -#if !defined(__3DS__) && !defined(__SWITCH__) +#if !defined(__3DS__) && !defined(__SWITCH__) && !defined(__WIIU__) else if (S_ISLNK (st_.st_mode)) type_ = "os.unix=symlink"; else if (S_ISCHR (st_.st_mode)) @@ -989,7 +998,7 @@ int FtpSession::fillDirent (struct stat const &st_, std::string_view const path_ // clang-format off S_ISREG (st_.st_mode) ? '-' : S_ISDIR (st_.st_mode) ? 'd' : -#if !defined(__3DS__) && !defined(__SWITCH__) +#if !defined(__3DS__) && !defined(__SWITCH__) && !defined(__WIIU__) S_ISLNK (st_.st_mode) ? 'l' : S_ISCHR (st_.st_mode) ? 'c' : S_ISBLK (st_.st_mode) ? 'b' : @@ -1054,7 +1063,7 @@ int FtpSession::fillDirent (struct stat const &st_, std::string_view const path_ int FtpSession::fillDirent (std::string const &path_, char const *type_) { struct stat st; - if (::stat (path_.c_str (), &st) != 0) + if (IOAbstraction::stat (path_.c_str (), &st) != 0) return errno; return fillDirent (st, encodePath (path_), type_); @@ -1081,7 +1090,7 @@ void FtpSession::xferFile (char const *const args_, XferFileMode const mode_) { // stat the file struct stat st; - if (::stat (path.c_str (), &st) != 0) + if (IOAbstraction::stat (path.c_str (), &st) != 0) { sendResponse ("450 %s\r\n", std::strerror (errno)); return; @@ -1219,7 +1228,7 @@ void FtpSession::xferDir (char const *const args_, XferDirMode const mode_, bool } struct stat st; - if (::stat (path.c_str (), &st) != 0) + if (IOAbstraction::stat (path.c_str (), &st) != 0) { sendResponse ("550 %s\r\n", std::strerror (errno)); setState (State::COMMAND, true, true); @@ -1719,7 +1728,7 @@ bool FtpSession::listTransfer () else #endif // lstat the entry - if (::lstat (fullPath.c_str (), &st) != 0) + if (IOAbstraction::lstat (fullPath.c_str (), &st) != 0) { #ifndef __SWITCH__ sendResponse ("550 %s\r\n", std::strerror (errno)); @@ -2255,7 +2264,10 @@ void FtpSession::PASV (char const *args_) return; } - // set the socket options + // set the socket option +#ifdef __WIIU__ + m_pasvSocket->setWinScale (1); +#endif m_pasvSocket->setRecvBufferSize (SOCK_BUFFERSIZE); m_pasvSocket->setSendBufferSize (SOCK_BUFFERSIZE); @@ -2516,7 +2528,7 @@ void FtpSession::RNFR (char const *args_) // make sure the path exists struct stat st; - if (::lstat (path.c_str (), &st) != 0) + if (IOAbstraction::lstat (path.c_str (), &st) != 0) { sendResponse ("450 %s\r\n", std::strerror (errno)); return; @@ -2711,7 +2723,7 @@ void FtpSession::SIZE (char const *args_) // stat the path struct stat st; - if (::stat (path.c_str (), &st) != 0) + if (IOAbstraction::stat (path.c_str (), &st) != 0) { sendResponse ("550 %s\r\n", std::strerror (errno)); return; diff --git a/source/licenses.cpp b/source/licenses.cpp index 6f1eea3..ac499e5 100644 --- a/source/licenses.cpp +++ b/source/licenses.cpp @@ -26,7 +26,7 @@ #include #endif -#if !defined(NDS) && !defined(__3DS__) && !defined(__SWITCH__) +#if !defined(NDS) && !defined(__3DS__) && !defined(__SWITCH__) && !defined(__WIIU__) #include #endif diff --git a/source/log.cpp b/source/log.cpp index 3b82ada..b8ef854 100644 --- a/source/log.cpp +++ b/source/log.cpp @@ -28,6 +28,10 @@ #include #include +#ifdef __WIIU__ +#include +#endif + namespace { #ifdef __3DS__ @@ -91,7 +95,9 @@ void drawLog () #endif auto const maxLogs = -#ifdef CLASSIC +#ifdef __WIIU__ + 1000; +#elif defined(CLASSIC) g_logConsole.windowHeight; #else MAX_LOGS; @@ -113,6 +119,12 @@ void drawLog () [RESPONSE] = "\x1b[36;1m", // cyan }; +#ifdef __WIIU__ + for (auto &cur : s_messages) + { + OSReport ("%s %s\x1b[0m", s_colors[cur.level], cur.message.c_str ()); + } +#else auto it = std::begin (s_messages); if (s_messages.size () > static_cast (g_logConsole.windowHeight)) it = std::next (it, s_messages.size () - g_logConsole.windowHeight); @@ -125,6 +137,7 @@ void drawLog () ++it; } std::fflush (stdout); +#endif s_messages.clear (); #else ImVec4 const s_colors[] = { @@ -236,17 +249,20 @@ void addLog (LogLevel const level_, char const *const fmt_, va_list ap_) if (level_ == DEBUG) return; #endif - -#ifndef NDS - thread_local -#endif - static char buffer[1024]; - - std::vsnprintf (buffer, sizeof (buffer), fmt_, ap_); - #ifndef NDS auto const lock = std::scoped_lock (s_lock); #endif + +#if !defined(NDS) && !defined(__WIIU__) + thread_local +#endif +#if !defined(__WIIU__) + static +#endif + char buffer[1024]; + + std::vsnprintf (buffer, sizeof (buffer), fmt_, ap_); + #ifndef NDEBUG // std::fprintf (stderr, "%s", s_prefix[level_]); // std::fputs (buffer, stderr); diff --git a/source/main.cpp b/source/main.cpp index f7f098c..6c4a362 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -21,6 +21,7 @@ #include "platform.h" #include "ftpServer.h" +#include "log.h" #include "imgui.h" @@ -58,9 +59,12 @@ int main (int argc_, char *argv_[]) while (platform::loop ()) { +#ifndef NO_CONSOLE server->draw (); - platform::render (); +#else + drawLog(); +#endif } // clean up resources before exiting switch/3ds services diff --git a/source/sockAddr.cpp b/source/sockAddr.cpp index 4acc2c1..a21c751 100644 --- a/source/sockAddr.cpp +++ b/source/sockAddr.cpp @@ -65,7 +65,7 @@ SockAddr::SockAddr (struct sockaddr_in const &addr_) assert (m_addr.ss_family == AF_INET); } -#ifndef __3DS__ +#if !defined(__3DS__) && !defined(__WIIU__) SockAddr::SockAddr (struct sockaddr_in6 const &addr_) : SockAddr (reinterpret_cast (addr_)) { @@ -84,7 +84,7 @@ SockAddr::operator struct sockaddr_in const & () const return reinterpret_cast (m_addr); } -#ifndef __3DS__ +#if !defined(__3DS__) && !defined(__WIIU__) SockAddr::operator struct sockaddr_in6 const & () const { assert (m_addr.ss_family == AF_INET6); @@ -175,7 +175,7 @@ char const *SockAddr::name (char *buffer_, std::size_t size_) const char const *SockAddr::name () const { -#ifdef NDS +#if defined(NDS) || defined(__WIIU__) return inet_ntoa (reinterpret_cast (&m_addr)->sin_addr); #else #ifdef NO_IPV6 diff --git a/source/socket.cpp b/source/socket.cpp index 36ae383..b16acb8 100644 --- a/source/socket.cpp +++ b/source/socket.cpp @@ -236,6 +236,19 @@ bool Socket::setNonBlocking (bool const nonBlocking_) return true; } +bool Socket::setWinScale (const int val) +{ + int const o = val; + + if (::setsockopt (m_fd, SOL_SOCKET, SO_WINSCALE, &o, sizeof (o)) < 0) + { + error ("setsockopt(SO_WINSCALE, %d): %s\n", val, std::strerror (errno)); + return false; + } + + return true; +} + bool Socket::setReuseAddress (bool const reuse_) { int const reuse = reuse_; diff --git a/source/wiiu/platform.cpp b/source/wiiu/platform.cpp new file mode 100644 index 0000000..aa1911b --- /dev/null +++ b/source/wiiu/platform.cpp @@ -0,0 +1,251 @@ +// 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 "../IOAbstraction.h" +#include "log.h" + +#include +#include +#include + +#include +#include +#include + +#ifndef CLASSIC +#error "Wii U must be built in classic mode" +#endif + +bool platform::networkVisible () +{ + return true; +} + +bool platform::networkAddress (SockAddr &addr_) +{ + struct sockaddr_in addr; + addr.sin_family = AF_INET; + nn::ac::GetAssignedAddress (&addr.sin_addr.s_addr); + addr_ = addr; + return true; +} + +MochaUtilsStatus MountWrapper (const char *mount, const char *dev, const char *mountTo) +{ + auto res = Mocha_MountFS (mount, dev, mountTo); + if (res == MOCHA_RESULT_ALREADY_EXISTS) + { + res = Mocha_MountFS (mount, nullptr, mountTo); + } + if (res == MOCHA_RESULT_SUCCESS) + { + std::string mountPath = std::string (mount) + ":/"; + debug ("Mounted %s", mountPath.c_str ()); + } + else + { + error ("Failed to mount %s: %s [%d]", mount, Mocha_GetStatusStr (res), res); + } + return res; +} + +bool platform::init () +{ + nn::ac::Initialize (); + nn::ac::ConnectAsync (); + WHBProcInit (); + + MochaUtilsStatus res; + if ((res = Mocha_InitLibrary ()) == MOCHA_RESULT_SUCCESS) + { + std::vector virtualDirsInRoot; + if (MountWrapper ("slccmpt01", "/dev/slccmpt01", "/vol/storage_slccmpt01") == + MOCHA_RESULT_SUCCESS) + { + virtualDirsInRoot.push_back ("slccmpt01"); + } + if (MountWrapper ("storage_odd_tickets", nullptr, "/vol/storage_odd01") == + MOCHA_RESULT_SUCCESS) + { + virtualDirsInRoot.push_back ("storage_odd_tickets"); + } + if (MountWrapper ("storage_odd_updates", nullptr, "/vol/storage_odd02") == + MOCHA_RESULT_SUCCESS) + { + virtualDirsInRoot.push_back ("storage_odd_updates"); + } + if (MountWrapper ("storage_odd_content", nullptr, "/vol/storage_odd03") == + MOCHA_RESULT_SUCCESS) + { + virtualDirsInRoot.push_back ("storage_odd_content"); + } + if (MountWrapper ("storage_odd_content2", nullptr, "/vol/storage_odd04") == + MOCHA_RESULT_SUCCESS) + { + virtualDirsInRoot.push_back ("storage_odd_content2"); + } + if (MountWrapper ("storage_slc", "/dev/slc01", "/vol/storage_slc01") == + MOCHA_RESULT_SUCCESS) + { + virtualDirsInRoot.push_back ("storage_slc"); + } + if (Mocha_MountFS ("storage_mlc", nullptr, "/vol/storage_mlc01") == MOCHA_RESULT_SUCCESS) + { + virtualDirsInRoot.push_back ("storage_mlc"); + } + if (Mocha_MountFS ("storage_usb", nullptr, "/vol/storage_usb01") == MOCHA_RESULT_SUCCESS) + { + virtualDirsInRoot.push_back ("storage_usb"); + } + virtualDirsInRoot.push_back ("fs"); + IOAbstraction::addVirtualPath (":/", virtualDirsInRoot); + IOAbstraction::addVirtualPath ("fs:/", std::vector{"vol"}); + IOAbstraction::addVirtualPath ("fs:/vol", std::vector{"external01"}); + IOAbstraction::addVirtualPath ("storage_odd_tickets:/", {}); + IOAbstraction::addVirtualPath ("storage_odd_updates:/", {}); + IOAbstraction::addVirtualPath ("storage_odd_content:/", {}); + IOAbstraction::addVirtualPath ("storage_odd_content2:/", {}); + IOAbstraction::addVirtualPath ("storage_usb:/", {}); + } + else + { + error ("Failed to init libmocha: %s [%d]", Mocha_GetStatusStr (res), res); + } + + ::chdir ("fs:/vol/external01"); + + return true; +} + +bool platform::loop () +{ + return WHBProcIsRunning (); +} + +void platform::render () +{ +} + +void platform::exit () +{ + WHBProcShutdown (); +} + +/////////////////////////////////////////////////////////////////////////// +/// \brief Platform thread pimpl +class platform::Thread::privateData_t +{ +public: + privateData_t () = default; + + /// \brief Parameterized constructor + /// \param func_ Thread entry point + explicit privateData_t (std::function &&func_) : thread (std::move (func_)) + { + auto nativeHandle = (OSThread *)thread.native_handle (); + OSSetThreadName (nativeHandle, "ftpd_server"); + while (!OSSetThreadAffinity (nativeHandle, OS_THREAD_ATTRIB_AFFINITY_CPU2)) + { + OSSleepTicks (OSMillisecondsToTicks (16)); + } + } + + /// \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 +}