From 214ab229c6571d06fc2f4eb433c3fcb8b260c603 Mon Sep 17 00:00:00 2001 From: Michael Theall Date: Fri, 17 Apr 2020 15:32:39 -0500 Subject: [PATCH] NDS support --- .gitignore | 6 + Makefile | 116 +++++++++++------ Makefile.3ds | 34 +++-- Makefile.linux | 3 +- Makefile.nds | 219 +++++++++++++++++++++++++++++++++ Makefile.switch | 18 ++- README.md | 26 +++- ftpd-classic-qr.png | Bin 0 -> 1064 bytes ftpd-qr.png | Bin 0 -> 1057 bytes ftpd_qr.png | Bin 621 -> 0 bytes include/ftpServer.h | 2 + include/ftpSession.h | 18 ++- include/platform.h | 19 ++- include/sockAddr.h | 8 ++ include/socket.h | 22 ++++ source/3ds/imgui_citro3d.cpp | 2 + source/3ds/imgui_citro3d.h | 2 + source/3ds/imgui_ctru.cpp | 4 +- source/3ds/imgui_ctru.h | 2 + source/3ds/platform.cpp | 97 ++++++++++++++- source/ftpServer.cpp | 66 +++++++++- source/ftpSession.cpp | 45 ++++--- source/log.cpp | 73 +++++++++-- source/main.cpp | 9 ++ source/nds/platform.cpp | 130 +++++++++++++++++++ source/sockAddr.cpp | 16 ++- source/socket.cpp | 92 +++++++++++++- source/switch/imgui_deko3d.cpp | 2 + source/switch/imgui_nx.cpp | 4 +- source/switch/init.c | 2 +- source/switch/platform.cpp | 81 +++++++++++- 31 files changed, 1006 insertions(+), 112 deletions(-) create mode 100644 Makefile.nds create mode 100644 ftpd-classic-qr.png create mode 100644 ftpd-qr.png delete mode 100644 ftpd_qr.png create mode 100644 source/nds/platform.cpp diff --git a/.gitignore b/.gitignore index 01f69b5..2b1276b 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile index df511e1..1096abd 100644 --- a/Makefile +++ b/Makefile @@ -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 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 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 ftpd.nro.xz + @xz -c 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" diff --git a/Makefile.3ds b/Makefile.3ds index 9110e30..69ee017 100644 --- a/Makefile.3ds +++ b/Makefile.3ds @@ -31,16 +31,24 @@ include $(DEVKITARM)/3ds_rules # - icon.png # - /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 #--------------------------------------------------------------------------------- diff --git a/Makefile.linux b/Makefile.linux index f76a6a7..bd484a1 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -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 \ diff --git a/Makefile.nds b/Makefile.nds new file mode 100644 index 0000000..4c84ee5 --- /dev/null +++ b/Makefile.nds @@ -0,0 +1,219 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=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 +#--------------------------------------------------------------------------------------- diff --git a/Makefile.switch b/Makefile.switch index 4f0a5e4..fb1774e 100644 --- a/Makefile.switch +++ b/Makefile.switch @@ -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)\"" \ diff --git a/README.md b/README.md index 083aa7f..e11bc15 100644 --- a/README.md +++ b/README.md @@ -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`: diff --git a/ftpd-classic-qr.png b/ftpd-classic-qr.png new file mode 100644 index 0000000000000000000000000000000000000000..d0653cedcbe5bc8cc3764647be384c2ca1dcfe2e GIT binary patch literal 1064 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST~P>f%(0si(^Q|t+#g%_RTfmVZ9La z@&A6G>34a8oZsGPE54>HToJ<>=~onUz4R|r-Iv?S42&3n1qlA#{`tgS_PF7rXJNk$ z-oINNZJ&O{BE0_i?$`Uj)*3W7K!JmD`jexpR@tr&ow8z;cZg`l*I$9>Eq7~~K1EV= zfZ=(c{iU^9o1*rvTotzV-|wrxXT_!7GORP^fB}=jx{u3Nhg~XteRtNTrb|3bFi^9( z>*`voDY3k#-+eV|Zt&M}00V`))3mNdm%GRBz5nLbwJGyfdtNm_GEFe*=&Yi!*ll@x zqi;U0|NJ|F4d%&4HM!7@%hu~e%hqrI-79te%Vv(V3NY|N@%YyTEP+>(gRjoMl52Cf zYS*UY!8?PHoa39bGAVVv%j#8^TK;kz4p(qH`OdJqs217kU5hrXiV6z-y6RPG*RHzl z&m~>;#eN)N0z05l+)Dd@#rApYUOs+P79aR{<065Yq(*R9GE}dMxVy?BxN7^NdCph! zz6CzMAGj9iV^}~J_pge$8QOaFwf{@OU6W%?SH1G=ma2dHI}^!kPT!9LrT)IyR$X~K z>&&UrRb{jLSN)3&}u08SkTD0iWtoSvnzMn$GC^I++MY>nn`L1tEjo*K}F8f%%1}i(^Q|t+#g_ixxZZuwF>) z`W=6Br$fYvi4$1fuT9T+@gqEV)ievc`RAX@e^~v08UqUk5CDQ7n?KL~Um|n7Yj*d% z!+UnE&wOsVd7bgE@;$%)EdM9>Mgay4PR#t77<+YIhN$Z5SCc|nSM59Z;%iFn`)T)^ zW#I;kS-k%-DKm6p#@Do{ty#Y%H~WSfS#!VuPvO3g%a)g3N)1h3baj=k?36N)1Hj%8fPx0^__;@4StVaCHPfuJy?XtA z>9WOiLO3(2z!GQCUc}G{(ZI8WX z{btqb=*ZWXLv3#xiLd@up25xt1&xk*FE!>|dl`0@^M>iyy}v%szx6T`AK9K?X=(PY znpLkVW3KL(Pe+)&=R_vwr_|8x>wIRPx@YRIvpOps5gr>Fw64jAga__A*Wz literal 0 HcmV?d00001 diff --git a/ftpd_qr.png b/ftpd_qr.png deleted file mode 100644 index 9d04fbb4d091ec7abca2cd136902a1b6d9b1d291..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 621 zcmV-z0+RiSP)|Nf&uC5B8!tvi=`(|XTtJ4P8gjAQHweg8gfw|OX_a|D#i!&40jWVO zZ#9$WgTxw@^nyMt6+l2LkO(K;u^gRtc}wznKL-d%3-TsxfF&7if9?eWQjd5h+thwU z%H(psr<~p(AZ^G@HkNjYdg#pvU8@#L1rU(m5=zPDuRPNhb<5P+r?ljFJqH14K}eD| zW$+M5o7R@L%abxXMoSQo2E+?QdiP!3QX(%nK|mUil%GEDvwho5-mQLlp7%jOsu3Mi zSMsTS;;37$=gmj`2@sG5WcE`T+I6$BG#}OQM$dT=kX9s;)oVd)8T@A62LY*1SX-@L zNw(ieWb?hpBRzZt0ck=~e%@m9kzZ;`s}-DvBKbi;Y7@KbF_z~oHDay0gMj>rP?RWr zV5G8Ylt)V)S3yAPlc(_GKpcgk#YRo1ARsNs>nSy({=m^+edNwexfKMYDzQIePrN7) z-7+oq#6dtBkn$gMiUN_w(x1{C1mp^0343P`$~%)Q2*@Rbqd>&lGv(7R4+uytVwpSI z7&?{*+Y;sUoCyS^6^Zanj>AH%n~$2wOYv|JklF-+_;>LQ2aQe;Q^u5Z00000NkvXX Hu0mjfw=WLz diff --git a/include/ftpServer.h b/include/ftpServer.h index 578f249..13a6899 100644 --- a/include/ftpServer.h +++ b/include/ftpServer.h @@ -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; diff --git a/include/ftpSession.h b/include/ftpSession.h index 2a66f83..237656a 100644 --- a/include/ftpSession.h +++ b/include/ftpSession.h @@ -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; diff --git a/include/platform.h b/include/platform.h index fd8c7d3..3e28020 100644 --- a/include/platform.h +++ b/include/platform.h @@ -20,8 +20,12 @@ #pragma once -#ifdef _3DS +#if defined(NDS) +#include +#elif defined(_3DS) #include <3ds.h> +#elif defined(__SWITCH__) +#include #endif #include @@ -29,6 +33,12 @@ #include #include +#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 m_d; }; +#endif } diff --git a/include/sockAddr.h b/include/sockAddr.h index aed0b43..07e151a 100644 --- a/include/sockAddr.h +++ b/include/sockAddr.h @@ -25,6 +25,14 @@ #include +#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 { diff --git a/include/socket.h b/include/socket.h index 12b2849..80d1304 100644 --- a/include/socket.h +++ b/include/socket.h @@ -26,6 +26,28 @@ #include #include +#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 +#endif + class Socket; using UniqueSocket = std::unique_ptr; using SharedSocket = std::shared_ptr; diff --git a/source/3ds/imgui_citro3d.cpp b/source/3ds/imgui_citro3d.cpp index 40bdfc1..e8e2274 100644 --- a/source/3ds/imgui_citro3d.cpp +++ b/source/3ds/imgui_citro3d.cpp @@ -18,6 +18,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef CLASSIC #include "imgui_citro3d.h" #include @@ -692,3 +693,4 @@ void imgui::citro3d::render (C3D_RenderTarget *const top_, C3D_RenderTarget *con } } } +#endif diff --git a/source/3ds/imgui_citro3d.h b/source/3ds/imgui_citro3d.h index 7e6d5c6..07a4dd5 100644 --- a/source/3ds/imgui_citro3d.h +++ b/source/3ds/imgui_citro3d.h @@ -20,6 +20,7 @@ #pragma once +#ifndef CLASSIC #include namespace imgui @@ -35,3 +36,4 @@ void exit (); void render (C3D_RenderTarget *top_, C3D_RenderTarget *bottom_); } } +#endif diff --git a/source/3ds/imgui_ctru.cpp b/source/3ds/imgui_ctru.cpp index bab26d4..7e7f497 100644 --- a/source/3ds/imgui_ctru.cpp +++ b/source/3ds/imgui_ctru.cpp @@ -18,6 +18,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef CLASSIC #include "imgui_ctru.h" #include "imgui.h" @@ -25,8 +26,6 @@ #include "fs.h" #include "platform.h" -#include <3ds.h> - #include #include #include @@ -174,3 +173,4 @@ void imgui::ctru::newFrame () updateTouch (io); updateGamepads (io); } +#endif diff --git a/source/3ds/imgui_ctru.h b/source/3ds/imgui_ctru.h index 983a06e..db2ccf0 100644 --- a/source/3ds/imgui_ctru.h +++ b/source/3ds/imgui_ctru.h @@ -20,6 +20,7 @@ #pragma once +#ifndef CLASSIC namespace imgui { namespace ctru @@ -31,3 +32,4 @@ bool init (); void newFrame (); } } +#endif diff --git a/source/3ds/platform.cpp b/source/3ds/platform.cpp index e20205f..90ad19a 100644 --- a/source/3ds/platform.cpp +++ b/source/3ds/platform.cpp @@ -28,20 +28,29 @@ #include "imgui.h" -#include <3ds.h> #include #include +#ifndef CLASSIC #include "gfx.h" +#endif +#include #include #include #include #include +#include #include #include +#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 diff --git a/source/ftpServer.cpp b/source/ftpServer.cpp index e9318dc..b88963f 100644 --- a/source/ftpServer.cpp +++ b/source/ftpServer.cpp @@ -23,11 +23,15 @@ #include "fs.h" #include "log.h" #include "platform.h" +#include "socket.h" #include "imgui.h" +#ifdef NDS +#include +#endif + #include -#include #include #include @@ -37,20 +41,26 @@ #include 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 (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 (st.f_bsize) * st.f_bfree); + auto const lock = std::scoped_lock (s_lock); - s_freeSpace = fs::printSize (static_cast (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 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 () diff --git a/source/ftpSession.cpp b/source/ftpSession.cpp index af18636..8da0c00 100644 --- a/source/ftpSession.cpp +++ b/source/ftpSession.cpp @@ -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 -#endif - #include -#include #include #include @@ -49,16 +40,20 @@ #include 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 (m_dir); auto const magic = *reinterpret_cast (dp->dirData->dirStruct); - if (magic == SDMC_DIRITER_MAGIC) + if (magic == ARCHIVE_DIRITER_MAGIC) { - auto const dir = reinterpret_cast (dp->dirData->dirStruct); + auto const dir = reinterpret_cast (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; } diff --git a/source/log.cpp b/source/log.cpp index de8f17a..7d4474b 100644 --- a/source/log.cpp +++ b/source/log.cpp @@ -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 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 (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 (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 } diff --git a/source/main.cpp b/source/main.cpp index 4be8e84..96318ed 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -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 } diff --git a/source/nds/platform.cpp b/source/nds/platform.cpp new file mode 100644 index 0000000..1b48bf6 --- /dev/null +++ b/source/nds/platform.cpp @@ -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 . + +#include "platform.h" + +#include "log.h" + +#include +#include + +#include + +#include + +#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 ()); +} diff --git a/source/sockAddr.cpp b/source/sockAddr.cpp index 9a86b72..b3b7e58 100644 --- a/source/sockAddr.cpp +++ b/source/sockAddr.cpp @@ -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 (&m_addr)->sin_port); -#ifndef _3DS +#ifndef NO_IPV6 case AF_INET6: return ntohs (reinterpret_cast (&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 (&m_addr)->sin_addr); +#else return inet_ntop (AF_INET, &reinterpret_cast (&m_addr)->sin_addr, buffer_, size_); +#endif -#ifndef _3DS +#ifndef NO_IPV6 case AF_INET6: return inet_ntop (AF_INET6, &reinterpret_cast (&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 (&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 } diff --git a/source/socket.cpp b/source/socket.cpp index c074d09..942f469 100644 --- a/source/socket.cpp +++ b/source/socket.cpp @@ -26,8 +26,6 @@ #include #include -#include - #include #include #include @@ -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 diff --git a/source/switch/imgui_deko3d.cpp b/source/switch/imgui_deko3d.cpp index 835ffa7..741175b 100644 --- a/source/switch/imgui_deko3d.cpp +++ b/source/switch/imgui_deko3d.cpp @@ -18,6 +18,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#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 diff --git a/source/switch/imgui_nx.cpp b/source/switch/imgui_nx.cpp index 0ea0f3b..b9246d7 100644 --- a/source/switch/imgui_nx.cpp +++ b/source/switch/imgui_nx.cpp @@ -18,6 +18,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#ifndef CLASSIC #include "imgui_nx.h" #include "imgui.h" @@ -25,8 +26,6 @@ #include "fs.h" #include "platform.h" -#include - #include #include #include @@ -1512,3 +1511,4 @@ void imgui::nx::exit () // deinitialize applet hooks appletUnhook (&s_appletHookCookie); } +#endif diff --git a/source/switch/init.c b/source/switch/init.c index 5a02d34..9e64420 100644 --- a/source/switch/init.c +++ b/source/switch/init.c @@ -63,7 +63,7 @@ void userAppInit () return; #ifndef NDEBUG - s_fd = nxlinkStdio (); + // s_fd = nxlinkStdioForDebug (); #endif } diff --git a/source/switch/platform.cpp b/source/switch/platform.cpp index 5d26913..d740ed6 100644 --- a/source/switch/platform.cpp +++ b/source/switch/platform.cpp @@ -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 -#include - +#include #include #include @@ -41,8 +41,17 @@ #include #include +#ifdef CLASSIC +PrintConsole g_statusConsole; +PrintConsole g_logConsole; +PrintConsole g_sessionConsole; +#endif + namespace { +#ifdef CLASSIC +in_addr_t s_addr = 0; +#else /// \brief Texture index enum TextureIndex { @@ -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 } ///////////////////////////////////////////////////////////////////////////