Initial Wii U port

This commit is contained in:
Maschell 2023-11-19 13:55:58 +01:00
parent fec8332df6
commit d9235a259b
17 changed files with 773 additions and 38 deletions

4
.gitignore vendored
View File

@ -23,3 +23,7 @@ switch/build
switch-classic/build
switch/romfs/*.zst
switch/romfs/shaders/*.dksh
.idea/
build/
*.rpx
*.wuhb

View File

@ -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"

181
Makefile.wiiu Normal file
View File

@ -0,0 +1,181 @@
#-------------------------------------------------------------------------------
.SUFFIXES:
#-------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/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
#-------------------------------------------------------------------------------

View File

@ -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;

View File

@ -28,6 +28,9 @@
#include <3ds.h>
#elif defined(__SWITCH__)
#include <switch.h>
#elif defined(__WIIU__)
#include <wut.h>
#include <coreinit/debug.h>
#endif
#include <chrono>
@ -35,7 +38,7 @@
#include <functional>
#include <memory>
#ifdef CLASSIC
#if defined(CLASSIC) && !defined(__WIIU__)
extern PrintConsole g_statusConsole;
extern PrintConsole g_logConsole;
extern PrintConsole g_sessionConsole;

View File

@ -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);

197
source/IOAbstraction.cpp Normal file
View File

@ -0,0 +1,197 @@
#include "IOAbstraction.h"
#include <algorithm>
#include <map>
#include <memory>
#include <sys/dirent.h>
#include <vector>
class VirtualDirectory
{
public:
VirtualDirectory (const std::vector<std::string> &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<std::string> mDirectories;
struct dirent mDir = {};
std::vector<std::string>::iterator mCurIterator{};
};
std::vector<std::unique_ptr<VirtualDirectory>> sOpenVirtualDirectories;
std::mutex sOpenVirtualDirectoriesMutex;
std::map<std::string, std::vector<std::string>> sVirtualDirs;
template <typename Container, typename Predicate>
typename std::enable_if<std::is_same<Container, std::vector<typename Container::value_type>>::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 <typename Container, typename Predicate>
bool remove_locked_first_if (std::mutex &mutex, Container &container, Predicate pred)
{
std::lock_guard<std::mutex> lock (mutex);
return remove_first_if (container, pred);
}
static DIR *getVirtualDir (const std::vector<std::string> &subDirectories)
{
auto virtDir = std::make_unique<VirtualDirectory> (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<std::string> &subDirectories)
{
sVirtualDirs.insert (std::make_pair (virtualPath, subDirectories));
}

34
source/IOAbstraction.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <mutex>
#include <string>
#include <sys/dirent.h>
#include <vector>
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<std::string> &subDirectories);
};

View File

@ -19,13 +19,14 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "fs.h"
#include "IOAbstraction.h"
#include <cassert>
#include <cinttypes>
#include <cstdio>
#include <string>
#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<std::size_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<std::size_t> fs::File::read (void *const buffer_, std::size_t const size_)
@ -145,7 +146,7 @@ std::make_signed_t<std::size_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<std::size_t> fs::File::read (IOBuffer &buffer_)
@ -206,7 +207,7 @@ std::make_signed_t<std::size_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<std::size_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<DIR, int (*) (DIR *)> (dp, &::closedir);
m_dp = std::unique_ptr<DIR, int (*) (DIR *)> (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 ());
}

View File

@ -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<unsigned> (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 ();

View File

@ -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;

View File

@ -26,7 +26,7 @@
#include <zstd.h>
#endif
#if !defined(NDS) && !defined(__3DS__) && !defined(__SWITCH__)
#if !defined(NDS) && !defined(__3DS__) && !defined(__SWITCH__) && !defined(__WIIU__)
#include <GLFW/glfw3.h>
#endif

View File

@ -28,6 +28,10 @@
#include <ranges>
#include <vector>
#ifdef __WIIU__
#include <coreinit/debug.h>
#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<unsigned> (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);

View File

@ -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

View File

@ -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<struct sockaddr const &> (addr_))
{
@ -84,7 +84,7 @@ SockAddr::operator struct sockaddr_in const & () const
return reinterpret_cast<struct sockaddr_in const &> (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<struct sockaddr_in const *> (&m_addr)->sin_addr);
#else
#ifdef NO_IPV6

View File

@ -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_;

251
source/wiiu/platform.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
#include "platform.h"
#include "../IOAbstraction.h"
#include "log.h"
#include <mocha/mocha.h>
#include <nn/ac.h>
#include <thread>
#include <coreinit/thread.h>
#include <sys/unistd.h>
#include <whb/proc.h>
#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<std::string> 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<std::string>{"vol"});
IOAbstraction::addVirtualPath ("fs:/vol", std::vector<std::string>{"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<void ()> &&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<void ()> &&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
}