NDS support

This commit is contained in:
Michael Theall 2020-04-17 15:32:39 -05:00
parent fc02e1ee38
commit 214ab229c6
31 changed files with 1006 additions and 112 deletions

6
.gitignore vendored
View File

@ -1,9 +1,12 @@
*.nds
*.nds.xz
*.3dsx
*.3dsx.xz
*.cia
*.cia.xz
*.elf
*.nacp
*.nds
*.nro
*.nro.xz
*.nso
@ -11,9 +14,12 @@
*.smdh
.gdb_history
3ds/build
3ds-classic/build
3ds/romfs/*.t3x
linux/build
linux/ftpd
nds/build
switch/build
switch-classic/build
switch/romfs/*.zst
switch/romfs/shaders/*.dksh

116
Makefile
View File

@ -1,24 +1,20 @@
.PHONY: all nro 3dsx cia clean linux 3dslink nxlink format release release-3dsx release-cia release-3ds release-nro
.PHONY: all all-classic format clean
.PHONY: dslink 3dslink 3dslink-classic nxlink-classic
.PHONY: nds 3dsx cia nro linux
.PHONY: 3dsx-classic cia-classic nro-classic
.PHONY: release release-nds release-3dsx release-cia release-nro
.PHONY: release-3dsx-classic release-cia-classic release-nro-classic
TARGET := $(notdir $(CURDIR))
export GITREV := $(shell git rev-parse HEAD 2>/dev/null | cut -c1-8)
export GITREV := $(shell git rev-parse HEAD 2>/dev/null | cut -c1-6)
export VERSION_MAJOR := 3
export VERSION_MINOR := 0
export VERSION_MICRO := 0
export VERSION := $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_MICRO)-rc3
export VERSION := $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_MICRO)
ifneq ($(strip $(GITREV)),)
export VERSION := $(VERSION)-$(GITREV)
endif
###########################################################################
all: nds 3dsx nro linux
all: 3dsx nro linux
nxlink:
@$(MAKE) -f Makefile.switch nxlink
3dslink:
@$(MAKE) -f Makefile.3ds 3dslink
all-classic: nds 3dsx-classic nro-classic linux
format:
@clang-format -style=file -i $(filter-out \
@ -40,40 +36,86 @@ format:
source/imgui/imgui_internal.h, \
$(shell find source include -type f -name \*.c -o -name \*.cpp -o -name \*.h))
release: release-3ds release-nro
@xz -c <3ds/$(TARGET).3dsx >ftpd.3dsx.xz
@xz -c <3ds/$(TARGET).cia >ftpd.cia.xz
@xz -c <switch/$(TARGET).nro >ftpd.nro.xz
clean:
@$(MAKE) -f Makefile.nds clean
@$(MAKE) -f Makefile.3ds clean
@$(MAKE) -f Makefile.3ds clean CLASSIC="-DCLASSIC"
@$(MAKE) -f Makefile.switch clean
@$(MAKE) -f Makefile.switch clean CLASSIC="-DCLASSIC"
@$(MAKE) -f Makefile.linux clean
@$(RM) ftpd.nds.xz ftpd*.3dsx.xz ftpd*.cia.xz ftpd*.nro.xz
nro:
@$(MAKE) -f Makefile.switch all
###########################################################################
dslink:
@$(MAKE) -f Makefile.nds dslink
release-nro:
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.switch all
3dslink:
@$(MAKE) -f Makefile.3ds 3dslink
3dslink-classic:
@$(MAKE) -f Makefile.3ds 3dslink CLASSIC="-DCLASSIC"
nxlink:
@$(MAKE) -f Makefile.switch nxlink
nxlink-classic:
@$(MAKE) -f Makefile.switch nxlink CLASSIC="-DCLASSIC"
###########################################################################
nds:
@$(MAKE) -f Makefile.nds CLASSIC="-DCLASSIC"
3dsx:
@$(MAKE) -f Makefile.3ds 3dsx
release-3dsx:
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds 3dsx
3dsx-classic:
@$(MAKE) -f Makefile.3ds 3dsx CLASSIC="-DCLASSIC"
cia: 3dsx
@$(MAKE) -f Makefile.3ds cia
release-cia: release-3dsx
@$(MAKE) DEFINES=-NDEBUG -f Makefile.3ds cia
cia-classic: 3dsx-classic
@$(MAKE) -f Makefile.3ds cia CLASSIC="-DCLASSIC"
release-3ds:
# can't let these run in parallel with each other due to using same
# .elf file name
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds 3dsx
@$(MAKE) DEFINES=-DNDEBUG -f Makefile.3ds cia
nro:
@$(MAKE) -f Makefile.switch all
nro-classic:
@$(MAKE) -f Makefile.switch all CLASSIC="-DCLASSIC"
linux:
@$(MAKE) -f Makefile.linux
clean:
@$(MAKE) -f Makefile.switch clean
@$(MAKE) -f Makefile.3ds clean
@$(MAKE) -f Makefile.linux clean
@$(RM) ftpd.3dsx.xz ftpd.cia.xz ftpd.nro.xz
###########################################################################
release: release-nds \
release-3dsx release-3dsx-classic \
release-cia release-cia-classic \
release-nro release-nro-classic
@xz -c <nds/ftpd.nds >ftpd.nds.xz
@xz -c <3ds/ftpd.3dsx >ftpd.3dsx.xz
@xz -c <3ds-classic/ftpd-classic.3dsx >ftpd-classic.3dsx.xz
@xz -c <3ds/ftpd.cia >ftpd.cia.xz
@xz -c <3ds-classic/ftpd-classic.cia >ftpd-classic.cia.xz
@xz -c <switch/ftpd.nro >ftpd.nro.xz
@xz -c <switch-classic/ftpd-classic.nro >ftpd-classic.nro.xz
release-nds:
@$(MAKE) -f Makefile.nds DEFINES=-DNDEBUG
release-3dsx:
@$(MAKE) -f Makefile.3ds 3dsx DEFINES=-DNDEBUG
release-3dsx-classic:
@$(MAKE) -f Makefile.3ds 3dsx DEFINES=-DNDEBUG CLASSIC="-DCLASSIC"
release-cia: release-3dsx
@$(MAKE) -f Makefile.3ds cia DEFINES=-DNDEBUG
release-cia-classic: release-3dsx-classic
@$(MAKE) -f Makefile.3ds cia DEFINES=-DNDEBUG CLASSIC="-DCLASSIC"
release-nro:
@$(MAKE) -f Makefile.switch all DEFINES=-DNDEBUG
release-nro-classic:
@$(MAKE) -f Makefile.switch all DEFINES=-DNDEBUG CLASSIC="-DCLASSIC"

View File

@ -31,16 +31,24 @@ include $(DEVKITARM)/3ds_rules
# - icon.png
# - <libctru folder>/default_icon.png
#---------------------------------------------------------------------------------
TARGET := 3ds/$(notdir $(CURDIR))
BUILD := 3ds/build
SOURCES := source source/3ds source/imgui
SOURCES := source source/3ds
DATA := data
INCLUDES := include
GRAPHICS := 3ds/gfx
ROMFS := 3ds/romfs
GFXBUILD := $(ROMFS)
APP_TITLE := ftpd
ifeq ($(strip $(CLASSIC)),)
APP_TITLE := ftpd pro
TARGET := 3ds/ftpd
BUILD := 3ds/build
SOURCES += source/imgui
GRAPHICS := 3ds/gfx
ROMFS := 3ds/romfs
GFXBUILD := $(ROMFS)
else
APP_TITLE := ftpd classic
TARGET := 3ds-classic/ftpd-classic
BUILD := 3ds-classic/build
endif
APP_DESCRIPTION := v$(VERSION)
APP_AUTHOR := mtheall
@ -52,16 +60,17 @@ RSF_FILE := meta/ftpd-cia.rsf
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
OPTIMIZE := -O2
OPTIMIZE := -O0
ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft
CFLAGS := -g -Wall $(OPTIMIZE) -mword-relocations \
-fomit-frame-pointer -ffunction-sections -fdata-sections \
$(ARCH) $(DEFINES)
$(ARCH) $(DEFINES) $(CLASSIC)
CFLAGS += $(INCLUDE) -DARM11 -D_3DS \
-DSTATUS_STRING="\"ftpd v$(VERSION)\"" \
-DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1
-DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \
-DNO_IPV6
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
@ -96,11 +105,14 @@ 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)))
PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica)))
SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist)))
GFXFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.t3s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
ifeq ($(strip $(CLASSIC)),)
PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica)))
endif
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------

View File

@ -2,8 +2,9 @@ TARGET := linux/ftpd
BUILD := linux/build
CFILES := $(wildcard source/linux/*.c)
OFILES := $(patsubst source/%,$(BUILD)/%,$(CFILES:.c=.c.o))
CXXFILES := $(wildcard source/*.cpp source/imgui/*.cpp source/linux/*.cpp)
OFILES := $(patsubst source/%,$(BUILD)/%,$(CFILES:.c=.c.o))
OXXFILES := $(patsubst source/%,$(BUILD)/%,$(CXXFILES:.cpp=.cpp.o))
CPPFLAGS := -g -Wall -pthread -Iinclude -Isource/linux \

219
Makefile.nds Normal file
View File

@ -0,0 +1,219 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
include $(DEVKITARM)/ds_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
# INCLUDES is a list of directories containing extra header files
# DATA is a list of directories containing binary files embedded using bin2o
# GRAPHICS is a list of directories containing image files to be converted with grit
# AUDIO is a list of directories containing audio to be converted by maxmod
# ICON is the image used to create the game icon, leave blank to use default rule
# NITRO is a directory that will be accessible via NitroFS
#---------------------------------------------------------------------------------
TARGET := nds/ftpd
BUILD := nds/build
SOURCES := source source/nds
INCLUDES := include
DATA :=
GRAPHICS :=
AUDIO :=
ICON :=
# specify a directory which contains the nitro filesystem
# this is relative to the Makefile
NITRO :=
# These set the information text in the nds file
GAME_TITLE := ftpd classic
GAME_SUBTITLE1 := v$(VERSION)
GAME_SUBTITLE2 := (c) mtheall
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -marm -mthumb-interwork -march=armv5te -mtune=arm946e-s
CFLAGS := -g -Wall -Os \
$(ARCH) $(INCLUDE) -DARM9 -DNDS \
-DSTATUS_STRING="\"ftpd v$(VERSION)\"" \
-DIMGUI_DISABLE_INCLUDE_IMCONFIG_H=1 \
-DNO_IPV6 -DCLASSIC
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project (order is important)
#---------------------------------------------------------------------------------
LIBS := -lfat -ldswifi9 -lnds9
# automatigically add libraries for NitroFS
ifneq ($(strip $(NITRO)),)
LIBS := -lfilesystem -lfat $(LIBS)
endif
# automagically add maxmod library
ifneq ($(strip $(AUDIO)),)
LIBS := -lmm9 $(LIBS)
endif
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBNDS) $(PORTLIBS)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(TOPDIR)/$(BUILD),$(CURDIR))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(CURDIR)/$(subst /,,$(dir $(ICON)))\
$(foreach dir,$(SOURCES),$(CURDIR)/$(dir))\
$(foreach dir,$(DATA),$(CURDIR)/$(dir))\
$(foreach dir,$(GRAPHICS),$(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)))
PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
# prepare NitroFS directory
ifneq ($(strip $(NITRO)),)
export NITRO_FILES := $(CURDIR)/$(NITRO)
endif
# get audio list for maxmod
ifneq ($(strip $(AUDIO)),)
export MODFILES := $(foreach dir,$(notdir $(wildcard $(AUDIO)/*.*)),$(CURDIR)/$(AUDIO)/$(dir))
# place the soundbank file in NitroFS if using it
ifneq ($(strip $(NITRO)),)
export SOUNDBANK := $(NITRO_FILES)/soundbank.bin
# otherwise, needs to be loaded from memory
else
export SOUNDBANK := soundbank.bin
BINFILES += $(SOUNDBANK)
endif
endif
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES))\
$(PNGFILES:.png=.o)\
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir))\
$(foreach dir,$(LIBDIRS),-I$(dir)/include)\
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
ifeq ($(strip $(ICON)),)
icons := $(wildcard *.bmp)
ifneq (,$(findstring $(TARGET).bmp,$(icons)))
export GAME_ICON := $(CURDIR)/$(TARGET).bmp
else
ifneq (,$(findstring icon.bmp,$(icons)))
export GAME_ICON := $(CURDIR)/icon.bmp
endif
endif
else
ifeq ($(suffix $(ICON)), .grf)
export GAME_ICON := $(CURDIR)/$(ICON)
else
export GAME_ICON := $(CURDIR)/$(BUILD)/$(notdir $(basename $(ICON))).grf
endif
endif
.PHONY: $(BUILD) clean dslink
#---------------------------------------------------------------------------------
$(BUILD):
@mkdir -p $@
@$(MAKE) -C $(BUILD) -f $(CURDIR)/Makefile.nds
dslink: $(BUILD)
@dslink $(OUTPUT).nds
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds $(SOUNDBANK)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(OUTPUT).nds: $(OUTPUT).elf $(GAME_ICON)
$(OUTPUT).elf: $(OFILES)
# need to build soundbank first
$(OFILES): $(SOUNDBANK)
#---------------------------------------------------------------------------------
# rule to build solution from music files
#---------------------------------------------------------------------------------
$(SOUNDBANK) : $(MODFILES)
#---------------------------------------------------------------------------------
mmutil $^ -d -o$@ -hsoundbank.h
#---------------------------------------------------------------------------------
%.bin.o: %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
$(bin2o)
#---------------------------------------------------------------------------------
# This rule creates assembly source files using grit
# grit takes an image file and a .grit describing how the file is to be processed
# add additional rules like this for each image extension
# you use in the graphics folders
#---------------------------------------------------------------------------------
%.s %.h: %.png %.grit
#---------------------------------------------------------------------------------
grit $< -fts -o$*
#---------------------------------------------------------------------------------
# Convert non-GRF game icon to GRF if needed
#---------------------------------------------------------------------------------
$(GAME_ICON): $(notdir $(ICON))
#---------------------------------------------------------------------------------
@echo convert $(notdir $<)
@grit $< -g -gt -gB4 -gT FF00FF -m! -p -pe 16 -fh! -ftr
-include $(DEPSDIR)/*.d
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

View File

@ -37,18 +37,26 @@ include $(DEVKITPRO)/libnx/switch_rules
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
# NACP building is skipped as well.
#---------------------------------------------------------------------------------
APP_TITLE := ftpd snap! $(VERSION)
APP_AUTHOR := mtheall, TuxSH, WinterMute
ICON := meta/ftpd.jpg
APP_VERSION := $(VERSION)
TARGET := switch/$(notdir $(CURDIR))
BUILD := switch/build
SOURCES := source source/imgui source/switch
SOURCES := source source/switch
DATA := data
INCLUDES := include
ifeq ($(strip $(CLASSIC)),)
APP_TITLE := ftpd pro $(VERSION)
TARGET := switch/ftpd
BUILD := switch/build
SOURCES += source/imgui
GRAPHICS := switch/gfx
ROMFS := switch/romfs
else
APP_TITLE := ftpd classic $(VERSION)
TARGET := switch-classic/ftpd-classic
BUILD := switch-classic/build
endif
# Output folders for autogenerated files in romfs
OUT_SHADERS := shaders
@ -61,7 +69,7 @@ ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -Wno-narrowing $(OPTIMIZE) \
-ffunction-sections -fdata-sections \
$(ARCH) $(DEFINES)
$(ARCH) $(DEFINES) $(CLASSIC)
CFLAGS += $(INCLUDE) -D__SWITCH__ \
-DSTATUS_STRING="\"ftpd v$(VERSION)\"" \

View File

@ -9,6 +9,7 @@ FTP Server for 3DS/Switch/Linux.
- Cutting-edge graphics.
## Latest Builds
NDS: https://mtheall.com/~mtheall/ftpd.nds
CIA: https://mtheall.com/~mtheall/ftpd.cia
@ -18,12 +19,35 @@ NRO: https://mtheall.com/~mtheall/ftpd.nro
CIA QR Code
![ftpd.cia](https://github.com/mtheall/ftpd/raw/master/ftpd_qr.png)
![ftpd.cia](https://github.com/mtheall/ftpd/raw/feature/v3.0.0/ftpd-qr.png)
## Classic Builds
CIA: https://mtheall.com/~mtheall/ftpd-classic.cia
3DSX: https://mtheall.com/~mtheall/ftpd-classic.3dsx
NRO: https://mtheall.com/~mtheall/ftpd-classic.nro
CIA QR Code
![ftpd-classic.cia](https://github.com/mtheall/ftpd/raw/feature/v3.0.0/ftpd-classic-qr.png)
## Build and install
You must set up the [development environment](https://devkitpro.org/wiki/Getting_Started).
### NDS
The following pacman packages are required to build `nds/ftpd.nds`:
devkitARM
dswifi
libfat-nds
libnds
They are available as part of the `nds-dev` meta-package.
### 3DSX
The following pacman packages are required to build `3ds/ftpd.3dsx`:

BIN
ftpd-classic-qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
ftpd-qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 B

View File

@ -69,11 +69,13 @@ private:
/// \brief Thread entry point
void threadFunc ();
#ifndef NDS
/// \brief Thread
platform::Thread m_thread;
/// \brief Mutex
platform::Mutex m_lock;
#endif
/// \brief Listen socket
UniqueSocket m_socket;

View File

@ -58,16 +58,30 @@ private:
/// \brief Command buffer size
constexpr static auto COMMAND_BUFFERSIZE = 4096;
#ifdef NDS
/// \brief Response buffer size
constexpr static auto RESPONSE_BUFFERSIZE = 4096;
/// \brief Transfer buffersize
constexpr static auto XFER_BUFFERSIZE = 8192;
#else
/// \brief Response buffer size
constexpr static auto RESPONSE_BUFFERSIZE = 32768;
/// \brief Transfer buffersize
constexpr static auto XFER_BUFFERSIZE = 65536;
#endif
/// \brief File buffersize
constexpr static auto FILE_BUFFERSIZE = 4 * XFER_BUFFERSIZE;
#ifdef _3DS
#if defined(NDS)
/// \brief Socket buffer size
constexpr static auto SOCK_BUFFERSIZE = 4096;
/// \brief Amount of file position history to keep
constexpr static auto POSITION_HISTORY = 60;
#elif defined(_3DS)
/// \brief Socket buffer size
constexpr static auto SOCK_BUFFERSIZE = 32768;
@ -184,8 +198,10 @@ private:
/// \brief Transfer upload
bool storeTransfer ();
#ifndef NDS
/// \brief Mutex
platform::Mutex m_lock;
#endif
/// \brief Command socket
SharedSocket m_commandSocket;

View File

@ -20,8 +20,12 @@
#pragma once
#ifdef _3DS
#if defined(NDS)
#include <nds.h>
#elif defined(_3DS)
#include <3ds.h>
#elif defined(__SWITCH__)
#include <switch.h>
#endif
#include <chrono>
@ -29,6 +33,12 @@
#include <functional>
#include <memory>
#ifdef CLASSIC
extern PrintConsole g_statusConsole;
extern PrintConsole g_logConsole;
extern PrintConsole g_sessionConsole;
#endif
namespace platform
{
/// \brief Initialize platform
@ -66,16 +76,14 @@ struct steady_clock
constexpr static bool is_steady = true;
/// \brief Current timestamp
static time_point now () noexcept
{
return time_point (duration (svcGetSystemTick ()));
}
static time_point now () noexcept;
};
#else
/// \brief Steady clock
using steady_clock = std::chrono::steady_clock;
#endif
#ifndef NDS
/// \brief Platform thread
class Thread
{
@ -132,4 +140,5 @@ private:
/// \brief pimpl
std::unique_ptr<privateData_t> m_d;
};
#endif
}

View File

@ -25,6 +25,14 @@
#include <cstdint>
#ifdef NDS
struct sockaddr_storage
{
unsigned short ss_family;
char ss_data[sizeof (struct sockaddr_in) - sizeof (unsigned short)];
};
#endif
/// \brief Socket address
class SockAddr
{

View File

@ -26,6 +26,28 @@
#include <chrono>
#include <memory>
#ifdef NDS
struct pollfd
{
int fd;
int events;
int revents;
};
using socklen_t = int;
using nfds_t = unsigned int;
extern "C" int poll (struct pollfd *fds_, nfds_t nfds_, int timeout_);
#define POLLIN (1 << 0)
#define POLLPRI (1 << 1)
#define POLLOUT (1 << 2)
#define POLLERR (1 << 3)
#define POLLHUP (1 << 4)
#else
#include <poll.h>
#endif
class Socket;
using UniqueSocket = std::unique_ptr<Socket>;
using SharedSocket = std::shared_ptr<Socket>;

View File

@ -18,6 +18,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef CLASSIC
#include "imgui_citro3d.h"
#include <citro3d.h>
@ -692,3 +693,4 @@ void imgui::citro3d::render (C3D_RenderTarget *const top_, C3D_RenderTarget *con
}
}
}
#endif

View File

@ -20,6 +20,7 @@
#pragma once
#ifndef CLASSIC
#include <citro3d.h>
namespace imgui
@ -35,3 +36,4 @@ void exit ();
void render (C3D_RenderTarget *top_, C3D_RenderTarget *bottom_);
}
}
#endif

View File

@ -18,6 +18,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef CLASSIC
#include "imgui_ctru.h"
#include "imgui.h"
@ -25,8 +26,6 @@
#include "fs.h"
#include "platform.h"
#include <3ds.h>
#include <chrono>
#include <cstring>
#include <functional>
@ -174,3 +173,4 @@ void imgui::ctru::newFrame ()
updateTouch (io);
updateGamepads (io);
}
#endif

View File

@ -20,6 +20,7 @@
#pragma once
#ifndef CLASSIC
namespace imgui
{
namespace ctru
@ -31,3 +32,4 @@ bool init ();
void newFrame ();
}
}
#endif

View File

@ -28,20 +28,29 @@
#include "imgui.h"
#include <3ds.h>
#include <citro3d.h>
#include <tex3ds.h>
#ifndef CLASSIC
#include "gfx.h"
#endif
#include <arpa/inet.h>
#include <malloc.h>
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstring>
#include <ctime>
#include <mutex>
#ifdef CLASSIC
PrintConsole g_statusConsole;
PrintConsole g_logConsole;
PrintConsole g_sessionConsole;
#endif
namespace
{
/// \brief Thread stack size
@ -60,6 +69,9 @@ u32 *s_socuBuffer = nullptr;
/// \brief ac:u fence
platform::Mutex s_acuFence;
#ifdef CLASSIC
in_addr_t s_addr = 0;
#else
/// \brief Clear color
constexpr auto CLEAR_COLOR = 0x204B7AFF;
@ -103,6 +115,7 @@ C3D_RenderTarget *s_bottom = nullptr;
C3D_Tex s_gfxTexture;
/// \brief Texture atlas metadata
Tex3DS_Texture s_gfxT3x;
#endif
/// \brief Get network visibility
bool getNetworkVisibility ()
@ -113,7 +126,20 @@ bool getNetworkVisibility ()
// get wifi status
std::uint32_t wifi = 0;
if (R_FAILED (ACU_GetWifiStatus (&wifi)) || !wifi)
{
#ifdef CLASSIC
s_addr = 0;
#endif
return false;
}
#ifdef CLASSIC
if (!s_addr)
s_addr = gethostid ();
if (s_addr == INADDR_BROADCAST)
s_addr = 0;
#endif
return true;
}
@ -146,6 +172,7 @@ void startNetwork ()
/// \brief Draw citro3d logo
void drawLogo ()
{
#ifndef CLASSIC
// get citro3d logo subtexture
auto subTex = Tex3DS_GetSubTexture (s_gfxT3x, gfx_c3dlogo_idx);
@ -176,11 +203,13 @@ void drawLogo ()
ImVec2 (x2, y2 + screenHeight * 0.5f),
uv1,
uv2);
#endif
}
/// \brief Draw status
void drawStatus ()
{
#ifndef CLASSIC
constexpr unsigned batteryLevels[] = {
gfx_battery0_idx,
gfx_battery0_idx,
@ -250,13 +279,37 @@ void drawStatus ()
// draw wifi icon
ImGui::GetForegroundDrawList ()->AddImage (
&s_gfxTexture, p3, p4, uv3, uv4, ImGui::GetColorU32 (ImGuiCol_Text));
#endif
// draw current timestamp
char buffer[64];
char timeBuffer[16];
auto const now = std::time (nullptr);
std::strftime (buffer, sizeof (buffer), "%H:%M:%S", std::localtime (&now));
ImGui::GetForegroundDrawList ()->AddText (
ImVec2 (p3.x - 65.0f, style.FramePadding.y), ImGui::GetColorU32 (ImGuiCol_Text), buffer);
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 (p3.x - 65.0f, style.FramePadding.y),
ImGui::GetColorU32 (ImGuiCol_Text),
timeBuffer);
#endif
}
}
@ -267,16 +320,28 @@ bool platform::init ()
acInit ();
ptmuInit ();
#ifndef CLASSIC
romfsInit ();
#endif
gfxInitDefault ();
gfxSet3D (false);
sdmcWriteSafe (false);
#ifdef CLASSIC
consoleInit (GFX_TOP, &g_statusConsole);
consoleInit (GFX_TOP, &g_logConsole);
consoleInit (GFX_BOTTOM, &g_sessionConsole);
consoleSetWindow (&g_statusConsole, 0, 0, 50, 1);
consoleSetWindow (&g_logConsole, 0, 1, 50, 29);
consoleSetWindow (&g_sessionConsole, 0, 0, 40, 30);
#endif
#ifndef NDEBUG
consoleDebugInit (debugDevice_SVC);
std::setvbuf (stderr, nullptr, _IOLBF, 0);
#endif
#ifndef CLASSIC
// initialize citro3d
C3D_Init (C3D_DEFAULT_CMDBUF_SIZE);
@ -317,6 +382,7 @@ bool platform::init ()
// citro3d logo doesn't quite show with the default transparency
style.Colors[ImGuiCol_WindowBg].w = 0.8f;
style.ScaleAllSizes (0.5f);
#endif
return true;
}
@ -342,6 +408,7 @@ bool platform::loop ()
if (hidKeysDown () & KEY_START)
return false;
#ifndef CLASSIC
auto &io = ImGui::GetIO ();
// setup display metrics
@ -350,6 +417,7 @@ bool platform::loop ()
imgui::ctru::newFrame ();
ImGui::NewFrame ();
#endif
return true;
}
@ -359,6 +427,12 @@ void platform::render ()
drawLogo ();
drawStatus ();
#ifdef CLASSIC
drawLog ();
gfxFlushBuffers ();
gspWaitForVBlank ();
gfxSwapBuffers ();
#else
ImGui::Render ();
C3D_FrameBegin (C3D_FRAME_SYNCDRAW);
@ -370,10 +444,12 @@ void platform::render ()
imgui::citro3d::render (s_top, s_bottom);
C3D_FrameEnd (0);
#endif
}
void platform::exit ()
{
#ifndef CLASSIC
imgui::citro3d::exit ();
// free graphics
@ -386,6 +462,7 @@ void platform::exit ()
// deinitialize citro3d
C3D_Fini ();
#endif
if (s_socuActive)
socExit ();
@ -393,11 +470,19 @@ void platform::exit ()
std::free (s_socuBuffer);
gfxExit ();
#ifndef CLASSIC
romfsExit ();
#endif
ptmuExit ();
acExit ();
}
///////////////////////////////////////////////////////////////////////////
platform::steady_clock::time_point platform::steady_clock::now () noexcept
{
return time_point (duration (svcGetSystemTick ()));
}
///////////////////////////////////////////////////////////////////////////
/// \brief Platform thread pimpl
class platform::Thread::privateData_t

View File

@ -23,11 +23,15 @@
#include "fs.h"
#include "log.h"
#include "platform.h"
#include "socket.h"
#include "imgui.h"
#ifdef NDS
#include <dswifi9.h>
#endif
#include <arpa/inet.h>
#include <poll.h>
#include <unistd.h>
#include <chrono>
@ -37,20 +41,26 @@
#include <thread>
using namespace std::chrono_literals;
#ifdef NDS
#define LOCKED(x) x
#else
#define LOCKED(x) \
do \
{ \
auto const lock = std::scoped_lock (m_lock); \
x; \
} while (0)
#endif
namespace
{
/// \brief Application start time
auto const s_startTime = std::time (nullptr);
#ifndef NDS
/// \brief Mutex for s_freeSpace
platform::Mutex s_lock;
#endif
/// \brief Free space string
std::string s_freeSpace;
@ -61,16 +71,54 @@ FtpServer::~FtpServer ()
{
m_quit = true;
#ifndef NDS
m_thread.join ();
#endif
}
FtpServer::FtpServer (std::uint16_t const port_) : m_port (port_), m_quit (false)
{
#ifndef NDS
m_thread = platform::Thread (std::bind (&FtpServer::threadFunc, this));
#endif
}
void FtpServer::draw ()
{
#ifdef NDS
loop ();
#endif
#ifdef CLASSIC
{
#ifndef NDS
auto const lock = std::scoped_lock (s_lock);
#endif
if (!s_freeSpace.empty ())
{
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);
}
}
{
#ifndef NDS
auto lock = std::scoped_lock (m_lock);
#endif
consoleSelect (&g_sessionConsole);
std::fputs ("\x1b[2J", stdout);
for (auto &session : m_sessions)
{
session->draw ();
if (&session != &m_sessions.back ())
std::fputc ('\n', stdout);
std::fflush (stdout);
}
}
#else
auto const &io = ImGui::GetIO ();
auto const width = io.DisplaySize.x;
auto const height = io.DisplaySize.y;
@ -135,6 +183,7 @@ void FtpServer::draw ()
}
ImGui::End ();
#endif
}
UniqueFtpServer FtpServer::create (std::uint16_t const port_)
@ -150,8 +199,11 @@ void FtpServer::updateFreeSpace ()
if (::statvfs ("sdmc:/", &st) != 0)
return;
auto freeSpace = fs::printSize (static_cast<std::uint64_t> (st.f_bsize) * st.f_bfree);
auto const lock = std::scoped_lock (s_lock);
s_freeSpace = fs::printSize (static_cast<std::uint64_t> (st.f_bsize) * st.f_bfree);
if (freeSpace != s_freeSpace)
s_freeSpace = std::move (freeSpace);
#endif
}
@ -164,7 +216,9 @@ void FtpServer::handleNetworkFound ()
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
#if defined(_3DS) || defined(__SWITCH__)
#if defined(NDS)
addr.sin_addr = Wifi_GetIPInfo (nullptr, nullptr, nullptr, nullptr);
#elif defined(_3DS) || defined(__SWITCH__)
addr.sin_addr.s_addr = gethostid ();
#else
addr.sin_addr.s_addr = INADDR_ANY;
@ -234,8 +288,10 @@ void FtpServer::loop ()
std::vector<UniqueFtpSession> deadSessions;
{
// remove dead sessions
#ifndef NDS
auto lock = std::scoped_lock (m_lock);
auto it = std::begin (m_sessions);
#endif
auto it = std::begin (m_sessions);
while (it != std::end (m_sessions))
{
auto &session = *it;
@ -256,9 +312,11 @@ void FtpServer::loop ()
if (!FtpSession::poll (m_sessions))
handleNetworkLost ();
}
#ifndef NDS
// avoid busy polling in background thread
else
platform::Thread::sleep (16ms);
#endif
}
void FtpServer::threadFunc ()

View File

@ -21,21 +21,12 @@
#include "ftpSession.h"
#include "ftpServer.h"
#include "log.h"
#include "platform.h"
#include "imgui.h"
#ifdef _3DS
#include <3ds.h>
#endif
#ifdef __SWITCH__
#include <switch.h>
#endif
#include <arpa/inet.h>
#include <poll.h>
#include <sys/stat.h>
#include <unistd.h>
@ -49,16 +40,20 @@
#include <mutex>
using namespace std::chrono_literals;
#if defined(_3DS) || defined(__SWITCH__)
#if defined(NDS) || defined(_3DS) || defined(__SWITCH__)
#define lstat stat
#endif
#ifdef NDS
#define LOCKED(x) x
#else
#define LOCKED(x) \
do \
{ \
auto const lock = std::scoped_lock (m_lock); \
x; \
} while (0)
#endif
namespace
{
@ -297,7 +292,9 @@ FtpSession::FtpSession (UniqueSocket commandSocket_)
bool FtpSession::dead ()
{
#ifndef NDS
auto const lock = std::scoped_lock (m_lock);
#endif
if (m_commandSocket || m_pasvSocket || m_dataSocket)
return false;
@ -306,8 +303,19 @@ bool FtpSession::dead ()
void FtpSession::draw ()
{
#ifndef NDS
auto const lock = std::scoped_lock (m_lock);
#endif
#ifdef CLASSIC
if (m_filePosition)
{
std::fputs (fs::printSize (m_filePosition).c_str (), stdout);
std::fputc (' ', stdout);
}
std::fputs (m_workItem.empty () ? m_cwd.c_str () : m_workItem.c_str (), stdout);
#else
#ifdef _3DS
ImGui::BeginChild (m_windowName.c_str (), ImVec2 (0.0f, 45.0f), true);
#else
@ -362,6 +370,7 @@ void FtpSession::draw ()
}
ImGui::EndChild ();
#endif
}
UniqueFtpSession FtpSession::create (UniqueSocket commandSocket_)
@ -571,7 +580,9 @@ void FtpSession::setState (State const state_, bool const closePasv_, bool const
if (state_ == State::COMMAND)
{
{
#ifndef NDS
auto lock = std::scoped_lock (m_lock);
#endif
m_restartPosition = 0;
m_fileSize = 0;
@ -1294,6 +1305,7 @@ void FtpSession::xferDir (char const *const args_, XferDirMode const mode_, bool
void FtpSession::readCommand (int const events_)
{
#ifndef NDS
// check out-of-band data
if (events_ & POLLPRI)
{
@ -1333,6 +1345,7 @@ void FtpSession::readCommand (int const events_)
m_commandBuffer.clear ();
return;
}
#endif
if (events_ & POLLIN)
{
@ -1591,9 +1604,9 @@ bool FtpSession::listTransfer ()
auto const dp = static_cast<DIR *> (m_dir);
auto const magic = *reinterpret_cast<u32 *> (dp->dirData->dirStruct);
if (magic == SDMC_DIRITER_MAGIC)
if (magic == ARCHIVE_DIRITER_MAGIC)
{
auto const dir = reinterpret_cast<sdmc_dir_t const *> (dp->dirData->dirStruct);
auto const dir = reinterpret_cast<archive_dir_t const *> (dp->dirData->dirStruct);
auto const entry = &dir->entry_data[dir->index];
if (entry->attributes & FS_ATTRIBUTE_DIRECTORY)
@ -1619,7 +1632,7 @@ bool FtpSession::listTransfer ()
if (getmtime)
{
std::uint64_t mtime = 0;
auto const rc = sdmc_getmtime (fullPath.c_str (), &mtime);
auto const rc = archive_getmtime (fullPath.c_str (), &mtime);
if (rc != 0)
error ("sdmc_getmtime %s 0x%lx\n", fullPath.c_str (), rc);
else
@ -2060,7 +2073,7 @@ void FtpSession::PASV (char const *args_)
// create an address to bind
struct sockaddr_in addr = m_commandSocket->sockName ();
#ifdef _3DS
#if defined(NDS) || defined(_3DS)
static std::uint16_t ephemeralPort = 5001;
if (ephemeralPort > 10000)
ephemeralPort = 5001;
@ -2256,7 +2269,7 @@ void FtpSession::RMD (char const *args_)
// remove the directory
if (::rmdir (path.c_str ()) != 0)
{
sendResponse ("550 %s\r\n", std::strerror (errno));
sendResponse ("550 %d %s\r\n", __LINE__, std::strerror (errno));
return;
}

View File

@ -37,6 +37,10 @@ constexpr auto MAX_LOGS = 250;
constexpr auto MAX_LOGS = 10000;
#endif
#ifdef CLASSIC
bool s_logUpdated = true;
#endif
/// \brief Message prefix
static char const *const s_prefix[] = {
[DEBUG] = "[DEBUG]",
@ -66,21 +70,62 @@ struct Message
/// \brief Log messages
std::vector<Message> s_messages;
#ifndef NDS
/// \brief Log lock
platform::Mutex s_lock;
#endif
}
void drawLog ()
{
#ifndef NDS
auto const lock = std::scoped_lock (s_lock);
#endif
if (s_messages.size () > MAX_LOGS)
#ifdef CLASSIC
if (!s_logUpdated)
return;
s_logUpdated = false;
#endif
auto const maxLogs =
#ifdef CLASSIC
g_logConsole.windowHeight;
#else
MAX_LOGS;
#endif
if (s_messages.size () > static_cast<unsigned> (maxLogs))
{
auto const begin = std::begin (s_messages);
auto const end = std::next (begin, s_messages.size () - MAX_LOGS);
auto const end = std::next (begin, s_messages.size () - maxLogs);
s_messages.erase (begin, end);
}
#ifdef CLASSIC
char const *const s_colors[] = {
[DEBUG] = "\x1b[33;1m", // yellow
[INFO] = "\x1b[37;1m", // white
[ERROR] = "\x1b[31;1m", // red
[COMMAND] = "\x1b[32;1m", // green
[RESPONSE] = "\x1b[36;1m", // cyan
};
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);
consoleSelect (&g_logConsole);
while (it != std::end (s_messages))
{
std::fputs (s_colors[it->level], stdout);
std::fputs (it->message.c_str (), stdout);
++it;
}
std::fflush (stdout);
s_messages.clear ();
#else
ImVec4 const s_colors[] = {
[DEBUG] = ImVec4 (1.0f, 1.0f, 0.4f, 1.0f), // yellow
[INFO] = ImGui::GetStyleColorVec4 (ImGuiCol_Text), // normal
@ -101,6 +146,7 @@ void drawLog ()
// auto-scroll if scroll bar is at end
if (ImGui::GetScrollY () >= ImGui::GetScrollMaxY ())
ImGui::SetScrollHereY (1.0f);
#endif
}
void debug (char const *const fmt_, ...)
@ -157,17 +203,25 @@ void addLog (LogLevel const level_, char const *const fmt_, va_list ap_)
return;
#endif
thread_local static char buffer[1024];
#ifndef NDS
thread_local
#endif
static char buffer[1024];
std::vsnprintf (buffer, sizeof (buffer), fmt_, ap_);
buffer[sizeof (buffer) - 1] = '\0';
#ifndef NDS
auto const lock = std::scoped_lock (s_lock);
#endif
#ifndef NDEBUG
std::fprintf (stderr, "%s", s_prefix[level_]);
std::fputs (buffer, stderr);
// std::fprintf (stderr, "%s", s_prefix[level_]);
// std::fputs (buffer, stderr);
#endif
s_messages.emplace_back (level_, buffer);
#ifdef CLASSIC
s_logUpdated = true;
#endif
}
void addLog (LogLevel const level_, std::string_view const message_)
@ -185,10 +239,15 @@ void addLog (LogLevel const level_, std::string_view const message_)
c = '?';
}
#ifndef NDS
auto const lock = std::scoped_lock (s_lock);
#endif
#ifndef NDEBUG
std::fprintf (stderr, "%s", s_prefix[level_]);
std::fwrite (msg.data (), 1, msg.size (), stderr);
// std::fprintf (stderr, "%s", s_prefix[level_]);
// std::fwrite (msg.data (), 1, msg.size (), stderr);
#endif
s_messages.emplace_back (level_, msg);
#ifdef CLASSIC
s_logUpdated = true;
#endif
}

View File

@ -29,19 +29,25 @@
int main (int argc_, char *argv_[])
{
#ifndef CLASSIC
IMGUI_CHECKVERSION ();
ImGui::CreateContext ();
#endif
if (!platform::init ())
{
#ifndef CLASSIC
ImGui::DestroyContext ();
#endif
return EXIT_FAILURE;
}
#ifndef CLASSIC
auto &style = ImGui::GetStyle ();
// turn off window rounding
style.WindowRounding = 0.0f;
#endif
auto server = FtpServer::create (5000);
@ -56,5 +62,8 @@ int main (int argc_, char *argv_[])
server.reset ();
platform::exit ();
#ifndef CLASSIC
ImGui::DestroyContext ();
#endif
}

130
source/nds/platform.cpp Normal file
View File

@ -0,0 +1,130 @@
// 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 "log.h"
#include <dswifi9.h>
#include <fat.h>
#include <netinet/in.h>
#include <cstring>
#ifndef CLASSIC
#error "NDS must be built in classic mode"
#endif
PrintConsole g_statusConsole;
PrintConsole g_logConsole;
PrintConsole g_sessionConsole;
namespace
{
struct in_addr s_addr = {0};
}
bool platform::networkVisible ()
{
switch (Wifi_AssocStatus ())
{
case ASSOCSTATUS_DISCONNECTED:
case ASSOCSTATUS_CANNOTCONNECT:
s_addr.s_addr = 0;
Wifi_AutoConnect ();
break;
case ASSOCSTATUS_SEARCHING:
case ASSOCSTATUS_AUTHENTICATING:
case ASSOCSTATUS_ASSOCIATING:
case ASSOCSTATUS_ACQUIRINGDHCP:
s_addr.s_addr = 0;
break;
case ASSOCSTATUS_ASSOCIATED:
if (!s_addr.s_addr)
s_addr = Wifi_GetIPInfo (nullptr, nullptr, nullptr, nullptr);
return true;
}
return false;
}
bool platform::init ()
{
sassert (fatInitDefault (), "Failed to initialize fat");
videoSetMode (MODE_0_2D);
videoSetModeSub (MODE_0_2D);
vramSetBankA (VRAM_A_MAIN_BG);
vramSetBankC (VRAM_C_SUB_BG);
consoleInit (&g_statusConsole, 0, BgType_Text4bpp, BgSize_T_256x256, 4, 0, true, true);
g_logConsole = g_statusConsole;
consoleInit (&g_sessionConsole, 0, BgType_Text4bpp, BgSize_T_256x256, 4, 0, false, true);
consoleSetWindow (&g_statusConsole, 0, 0, 32, 1);
consoleSetWindow (&g_logConsole, 0, 1, 32, 23);
consoleSetWindow (&g_sessionConsole, 0, 0, 32, 24);
consoleDebugInit (DebugDevice_NOCASH);
std::setvbuf (stderr, nullptr, _IONBF, 0);
Wifi_InitDefault (INIT_ONLY);
Wifi_AutoConnect ();
return true;
}
bool platform::loop ()
{
scanKeys ();
if (keysDown () & KEY_START)
return false;
return true;
}
void platform::render ()
{
swiWaitForVBlank ();
consoleSelect (&g_statusConsole);
std::printf ("\n%s %s%s",
STATUS_STRING,
s_addr.s_addr ? inet_ntoa (s_addr) : "Waiting on WiFi",
s_addr.s_addr ? ":5000" : "");
std::fflush (stdout);
drawLog ();
}
void platform::exit ()
{
info ("Press any key to exit\n");
render ();
do
{
swiWaitForVBlank ();
scanKeys ();
} while (!keysDown ());
}

View File

@ -47,7 +47,7 @@ SockAddr::SockAddr (struct sockaddr const &addr_)
std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in));
break;
#ifndef _3DS
#ifndef NO_IPV6
case AF_INET6:
std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in6));
break;
@ -114,7 +114,7 @@ std::uint16_t SockAddr::port () const
case AF_INET:
return ntohs (reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_port);
#ifndef _3DS
#ifndef NO_IPV6
case AF_INET6:
return ntohs (reinterpret_cast<struct sockaddr_in6 const *> (&m_addr)->sin6_port);
#endif
@ -130,12 +130,16 @@ char const *SockAddr::name (char *buffer_, std::size_t size_) const
switch (m_addr.ss_family)
{
case AF_INET:
#ifdef NDS
return inet_ntoa (reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_addr);
#else
return inet_ntop (AF_INET,
&reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_addr,
buffer_,
size_);
#endif
#ifndef _3DS
#ifndef NO_IPV6
case AF_INET6:
return inet_ntop (AF_INET6,
&reinterpret_cast<struct sockaddr_in6 const *> (&m_addr)->sin6_addr,
@ -151,11 +155,15 @@ char const *SockAddr::name (char *buffer_, std::size_t size_) const
char const *SockAddr::name () const
{
#if defined(_3DS)
#ifdef NDS
return inet_ntoa (reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_addr);
#else
#ifdef NO_IPV6
thread_local static char buffer[INET_ADDRSTRLEN];
#else
thread_local static char buffer[INET6_ADDRSTRLEN];
#endif
return name (buffer, sizeof (buffer));
#endif
}

View File

@ -26,8 +26,6 @@
#include <sys/socket.h>
#include <unistd.h>
#include <poll.h>
#include <cassert>
#include <cerrno>
#include <cstdio>
@ -42,8 +40,13 @@ Socket::~Socket ()
if (m_connected)
info ("Closing connection to [%s]:%u\n", m_peerName.name (), m_peerName.port ());
#ifdef NDS
if (::closesocket (m_fd) != 0)
error ("closesocket: %s\n", std::strerror (errno));
#else
if (::close (m_fd) != 0)
error ("close: %s\n", std::strerror (errno));
#endif
}
Socket::Socket (int const fd_) : m_fd (fd_), m_listening (false), m_connected (false)
@ -77,11 +80,17 @@ UniqueSocket Socket::accept ()
int Socket::atMark ()
{
#ifdef NDS
errno = ENOSYS;
return -1;
#else
auto const rc = ::sockatmark (m_fd);
if (rc < 0)
error ("sockatmark: %s\n", std::strerror (errno));
return rc;
#endif
}
bool Socket::bind (SockAddr const &addr_)
@ -96,7 +105,7 @@ bool Socket::bind (SockAddr const &addr_)
}
break;
#ifndef _3DS
#ifndef NO_IPV6
case AF_INET6:
if (::bind (m_fd, addr_, sizeof (struct sockaddr_in6)) != 0)
{
@ -171,11 +180,16 @@ bool Socket::shutdown (int const how_)
bool Socket::setLinger (bool const enable_, std::chrono::seconds const time_)
{
#ifdef NDS
errno = ENOSYS;
return -1;
#else
struct linger linger;
linger.l_onoff = enable_;
linger.l_linger = time_.count ();
if (::setsockopt (m_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof (linger)) != 0)
auto const rc = ::setsockopt (m_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof (linger));
if (rc != 0)
{
error ("setsockopt(SO_LINGER, %s, %lus): %s\n",
enable_ ? "on" : "off",
@ -185,10 +199,21 @@ bool Socket::setLinger (bool const enable_, std::chrono::seconds const time_)
}
return true;
#endif
}
bool Socket::setNonBlocking (bool const nonBlocking_)
{
#ifdef NDS
unsigned long enable = nonBlocking_;
auto const rc = ::ioctl (m_fd, FIONBIO, &enable);
if (rc != 0)
{
error ("fcntl(FIONBIO, %d): %s\n", nonBlocking_, std::strerror (errno));
return false;
}
#else
auto flags = ::fcntl (m_fd, F_GETFL, 0);
if (flags == -1)
{
@ -206,6 +231,7 @@ bool Socket::setNonBlocking (bool const nonBlocking_)
error ("fcntl(F_SETFL, %d): %s\n", flags, std::strerror (errno));
return false;
}
#endif
return true;
}
@ -335,3 +361,61 @@ int Socket::poll (PollInfo *const info_,
return rc;
}
#ifdef NDS
extern "C" int poll (struct pollfd *const fds_, nfds_t const nfds_, int const timeout_)
{
fd_set readFds;
fd_set writeFds;
fd_set exceptFds;
FD_ZERO (&readFds);
FD_ZERO (&writeFds);
FD_ZERO (&exceptFds);
for (nfds_t i = 0; i < nfds_; ++i)
{
if (fds_[i].events & POLLIN)
FD_SET (fds_[i].fd, &readFds);
if (fds_[i].events & POLLOUT)
FD_SET (fds_[i].fd, &writeFds);
}
struct timeval tv;
tv.tv_sec = timeout_ / 1000;
tv.tv_usec = (timeout_ % 1000) * 1000;
auto const rc = ::select (nfds_, &readFds, &writeFds, &exceptFds, &tv);
if (rc < 0)
return rc;
int count = 0;
for (nfds_t i = 0; i < nfds_; ++i)
{
bool counted = false;
fds_[i].revents = 0;
if (FD_ISSET (fds_[i].fd, &readFds))
{
counted = true;
fds_[i].revents |= POLLIN;
}
if (FD_ISSET (fds_[i].fd, &writeFds))
{
counted = true;
fds_[i].revents |= POLLOUT;
}
if (FD_ISSET (fds_[i].fd, &exceptFds))
{
counted = true;
fds_[i].revents |= POLLERR;
}
if (counted)
++count;
}
return count;
}
#endif

View File

@ -18,6 +18,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef CLASSIC
#include "imgui_deko3d.h"
#include "fs.h"
@ -528,3 +529,4 @@ void imgui::deko3d::render (dk::UniqueDevice &device_,
// submit final commands
queue_.submitCommands (cmdBuf_.finishList ());
}
#endif

View File

@ -18,6 +18,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef CLASSIC
#include "imgui_nx.h"
#include "imgui.h"
@ -25,8 +26,6 @@
#include "fs.h"
#include "platform.h"
#include <switch.h>
#include <chrono>
#include <cstring>
#include <functional>
@ -1512,3 +1511,4 @@ void imgui::nx::exit ()
// deinitialize applet hooks
appletUnhook (&s_appletHookCookie);
}
#endif

View File

@ -63,7 +63,7 @@ void userAppInit ()
return;
#ifndef NDEBUG
s_fd = nxlinkStdio ();
// s_fd = nxlinkStdioForDebug ();
#endif
}

View File

@ -21,6 +21,7 @@
#include "platform.h"
#include "fs.h"
#include "log.h"
#include "imgui_deko3d.h"
#include "imgui_nx.h"
@ -29,8 +30,7 @@
#include <zstd.h>
#include <switch.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <cerrno>
@ -41,8 +41,17 @@
#include <numeric>
#include <thread>
#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
{
@ -385,26 +394,52 @@ void deko3dExit ()
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 buffer[64];
char timeBuffer[64];
auto const now = std::time (nullptr);
std::strftime (buffer, sizeof (buffer), "%H:%M:%S", std::localtime (&now));
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),
buffer);
timeBuffer);
#endif
}
/// \brief Draw network status
void drawNetworkStatus ()
{
#ifndef CLASSIC
TextureIndex netIcon = AIRPLANE_ICON;
NifmInternetConnectionType type;
@ -447,11 +482,13 @@ void drawNetworkStatus ()
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);
@ -480,8 +517,10 @@ void drawPowerStatus ()
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
@ -495,10 +534,21 @@ void drawStatus ()
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;
@ -511,6 +561,7 @@ bool platform::init ()
s_imageDescriptors[0],
dkMakeTextureHandle (0, 0),
FB_NUM);
#endif
return true;
}
@ -523,6 +574,11 @@ bool platform::networkVisible ()
if (R_FAILED (nifmGetInternetConnectionStatus (&type, &wifi, &status)))
return false;
#ifdef CLASSIC
if (!s_addr)
s_addr = gethostid ();
#endif
return status == NifmInternetConnectionStatus_Connected;
}
@ -537,6 +593,7 @@ bool platform::loop ()
if (keysDown & KEY_PLUS)
return false;
#ifndef CLASSIC
imgui::nx::newFrame ();
ImGui::NewFrame ();
@ -554,6 +611,7 @@ bool platform::loop ()
imgui::deko3d::makeTextureID (dkMakeTextureHandle (1, 1)),
ImVec2 (x1, y1),
ImVec2 (x2, y2));
#endif
drawStatus ();
@ -562,6 +620,12 @@ bool platform::loop ()
void platform::render ()
{
#ifdef CLASSIC
drawLog ();
consoleUpdate (&g_statusConsole);
consoleUpdate (&g_logConsole);
consoleUpdate (&g_sessionConsole);
#else
ImGui::Render ();
auto &io = ImGui::GetIO ();
@ -595,10 +659,16 @@ void platform::render ()
// 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
@ -606,6 +676,7 @@ void platform::exit ()
imgui::deko3d::exit ();
deko3dExit ();
#endif
}
///////////////////////////////////////////////////////////////////////////