diff --git a/.clang-format b/.clang-format index 5f9d444..b9adbd5 100644 Binary files a/.clang-format and b/.clang-format differ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1956ecb..7ebbe71 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,12 +25,14 @@ jobs: uses: actions/checkout@v2 with: submodules: recursive + - name: Set up Git safe directory run: git config --global --add safe.directory /__w/BCSTM-Player/BCSTM-Player + - name: Install tools run: | sudo apt-get update - sudo apt-get install p7zip-full g++ cmake -y + sudo apt-get install p7zip-full g++ cmake ninja-build -y git clone https://github.com/NPI-D7/bannertool cd bannertool cmake -B build @@ -46,46 +48,50 @@ jobs: - name: Build id: build run: | - make + mkdir -p build + cd build + cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="/opt/devkitpro/cmake/3DS.cmake" + ninja + cd .. mkdir -p ~/artifacts - cp BCSTM-Player.3dsx ~/artifacts - cp BCSTM-Player.cia ~/artifacts - echo ::set-output name=commit_tag::$(git describe --abbrev=0 --tags) - echo ::set-output name=commit_hash::$(git log --format=%h -1) - # Webhook info - echo ::set-output name=author_name::$(git log -1 "$GITHUB_SHA" --pretty="%aN") - echo ::set-output name=committer_name::$(git log -1 "$GITHUB_SHA" --pretty="%cN") - echo ::set-output name=commit_subject::$(git log -1 "$GITHUB_SHA" --pretty="%s") - echo ::set-output name=commit_message::$(git log -1 "$GITHUB_SHA" --pretty="%b") + cp bin/BCSTM-Player.3dsx ~/artifacts + cp bin/BCSTM-Player.cia ~/artifacts + cp bin/BCSTM-Player.elf ~/artifacts + echo "commit_tag=$(git describe --abbrev=0 --tags)" >> $GITHUB_OUTPUT + echo "commit_hash=$(git log --format=%h -1)" >> $GITHUB_OUTPUT + echo "author_name=$(git log -1 $GITHUB_SHA --pretty=%aN)" >> $GITHUB_OUTPUT + echo "committer_name=$(git log -1 $GITHUB_SHA --pretty=%cN)" >> $GITHUB_OUTPUT + echo "commit_subject=$(git log -1 $GITHUB_SHA --pretty=%s)" >> $GITHUB_OUTPUT + echo "commit_message=$(git log -1 $GITHUB_SHA --pretty=%b)" >> $GITHUB_OUTPUT + - name: Publish build to GH Actions uses: actions/upload-artifact@v4 with: path: ~/artifacts/* name: build - # Only run this for non-PR jobs. publish_build: runs-on: ubuntu-latest name: Publish build to NPI-D7/nightlys if: ${{ success() && !startsWith(github.ref, 'refs/pull') }} needs: build env: - COMMIT_TAG: ${{ needs.build.outputs.commit_tag }} - COMMIT_HASH: ${{ needs.build.outputs.commit_hash }} - AUTHOR_NAME: ${{ needs.build.outputs.author_name }} - COMMIT_MESSAGE: ${{ needs.build.outputs.commit_message }} + COMMIT_TAG: ${{ needs.build.commit_tag }} + COMMIT_HASH: ${{ needs.build.commit_hash }} + AUTHOR_NAME: ${{ needs.build.author_name }} + COMMIT_MESSAGE: ${{ needs.build.commit_message }} outputs: current_date: ${{ steps.commit.outputs.current_date }} steps: - name: Install tools - run: | - sudo apt-get update - sudo apt-get install qrencode -y + run: sudo apt-get update && sudo apt-get install qrencode -y + - name: Download artifacts uses: actions/download-artifact@v4 with: name: build path: build + - name: Upload to ${{ github.repository }} release if: ${{ startsWith(github.ref, 'refs/tags') }} run: | @@ -97,13 +103,14 @@ jobs: UPLOAD_URL="https://uploads.github.com/repos/${{ github.repository }}/releases/$ID/assets?name=$(basename $file)" curl -XPOST -H "$AUTH_HEADER" -H "$CONTENT_LENGTH" -H "$CONTENT_TYPE" --upload-file "$file" "$UPLOAD_URL" done + - name: Commit and push to NPI-D7/nightlys id: commit run: | CURRENT_DATE=$(date +"%Y%m%d-%H%M%S") - echo ::set-output name=current_date::$CURRENT_DATE + echo "current_date=$CURRENT_DATE" >> $GITHUB_OUTPUT git config --global user.email "tobid7@outlook.de" - git config --global user.name "Tobi-D7" + git config --global user.name "tobid7" git clone --depth 1 https://${{ secrets.TOKEN }}@github.com/NPI-D7/nightlys.git cd nightlys mkdir -p builds/BCSTM-Player/ @@ -111,10 +118,11 @@ jobs: qrencode -o BCSTM-Player.png https://github.com/NPI-D7/nightlys/raw/v$CURRENT_DATE/builds/BCSTM-Player/BCSTM-Player.cia qrencode -o BCSTM-Player-release.png https://github.com/NPI-D7/nightlys/releases/download/$COMMIT_TAG/BCSTM-Player.cia cp ${{ github.workspace }}/build/* . - git stage . + git add . git commit -m "BCSTM-Player | $COMMIT_HASH" git tag v$CURRENT_DATE git push origin master v$CURRENT_DATE + - name: Release to idk run: | ls ${{ github.workspace }}/build @@ -130,4 +138,4 @@ jobs: CONTENT_TYPE="Content-Type: application/7z-x-compressed" UPLOAD_URL="https://uploads.github.com/repos/NPI-D7/nightlys/releases/$ID/assets?name=$(basename $file)" curl -XPOST -H "$AUTH_HEADER" -H "$CONTENT_LENGTH" -H "$CONTENT_TYPE" --upload-file "$file" "$UPLOAD_URL" - done \ No newline at end of file + done diff --git a/.gitignore b/.gitignore index 118a7dd..6ef52e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build BCSTM-Player.* icon.bin -banner.bin \ No newline at end of file +banner.bin +compile_commands.json \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 8718e5f..4bbfe4e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "D7-Menu-Core"] - path = D7-Menu-Core - url = https://github.com/NPI-D7/D7-Menu-Core.git +[submodule "ctrff"] + path = ctrff + url = https://github.com/tobid7/ctrff [submodule "palladium"] path = palladium - url = https://github.com/tobid7/palladium + url = https://dev.npid7.de/tobid7/palladium diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 0d07302..1112c65 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,12 +1,22 @@ { "configurations": [ { - "name": "3DS", + "name": "3DS | Windows", "includePath": [ "${workspaceFolder}/**", "${workspaceFolder}/palladium/include/**", "C:/devkitpro/libctru/include/**", "C:/devkitpro/portlibs/3ds/include/**", + ], + "cStandard": "c17", + "cppStandard": "c++20", + "compileCommands": "${workspaceFolder}/build/compile_commands.json" + }, + { + "name": "3DS | Linux/Mac", + "includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/palladium/include/**", "/opt/devkitpro/libctru/include/**", "/opt/devkitpro/portlibs/3ds/include/**" ], diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a4de925 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "name": "3DS GDB", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/bin/BCSTM-Player.elf", + "miDebuggerServerAddress": "localhost:4003", + "miDebuggerPath": "C:\\devkitPro\\devkitARM\\bin\\arm-none-eabi-gdb.exe", + "cwd": "${workspaceFolder}", + "externalConsole": true, + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 17d6863..fcff0af 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "editor.formatOnSave": true, + "editor.formatOnSave": false, "files.associations": { "*.tcc": "cpp", "cctype": "cpp", @@ -98,6 +98,7 @@ "xlocmes": "cpp", "xlocmon": "cpp", "xloctime": "cpp", - "xtree": "cpp" + "xtree": "cpp", + "any": "cpp" } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..80e0a5c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,179 @@ +cmake_minimum_required(VERSION 3.18) + +# Setup Toolchain if not specified +# Could propably avoided by using arm-none-eabi-cmake +if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) + if(DEFINED ENV{DEVKITPRO}) + set(CMAKE_TOOLCHAIN_FILE "$ENV{DEVKITPRO}/cmake/3DS.cmake" CACHE PATH "toolchain file") + else() + message(FATAL_ERROR "Please define DEVKITPRO to point to your SDK path!") + endif() +endif() + +find_program(MAKEROM makerom) +find_program(BANNERTOOL bannertool) + +execute_process( + COMMAND git rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_SHORT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +execute_process( + COMMAND git rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +# Set Result specific Data +set(APP_NAME "BCSTM-Player") +set(APP_DESC "BCSTM Player for the 3ds") +set(APP_AUTHOR "tobid7") +set(APP_ICON "${CMAKE_SOURCE_DIR}/app/icon.png") +set(APP_ROMFS "${CMAKE_SOURCE_DIR}/romfs") + +set(APP_BANNER "${CMAKE_SOURCE_DIR}/app/banner.png") +set(APP_BANNERAUDIO "${CMAKE_SOURCE_DIR}/app/BannerAudio.wav") +set(APP_RSF "${CMAKE_SOURCE_DIR}/app/build-cia.rsf") +set(APP_LOGO "${CMAKE_SOURCE_DIR}/app/splash.lz") + +# Set 3ds IP for make send +set(3DS_IP "" CACHE STRING "3ds IP (make send)") + +# Set Project +project(BCSTM-Player LANGUAGES C CXX VERSION 2.0.0) + +# Enable Compile Command Export +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Force C++ 20 +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) + +# Better location to not always crash Citra / Azahar +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) + +add_subdirectory(palladium) +add_subdirectory(palladium/backends/3ds) +add_subdirectory(ctrff) + +# Set Executable and its sources +add_executable(BCSTM-Player + source/main.cpp + source/pd_ctr_ext.cpp + source/filebrowser.cpp + source/inspector_view.cpp + source/settings.cpp + source/stagemgr.cpp + #ource/app.cpp + #source/config.cpp + + source/bcstm/bcstmv2.cpp + source/bcstm/ctrff_decode.cpp + + source/flex/objects.cpp +) + +# Set dependencies, include dirs and definitions +target_link_libraries(BCSTM-Player PUBLIC m ctrff pd-backend-3ds palladium citro3d ctrud) +target_include_directories(BCSTM-Player PUBLIC + source + ${DEVKITPRO}/portlibs/3ds/include +) +target_compile_definitions(BCSTM-Player PUBLIC + -D_GNU_SOURCE=1 + -DVERSION="${PROJECT_VERSION}" + -DGIT_COMMIT="${GIT_SHORT_HASH}" + -DGIT_BRANCH="${GIT_BRANCH}" +) +target_compile_options(BCSTM-Player PUBLIC + -Wno-psabi +) + +if(${CMAKE_BUILD_TYPE} STREQUAL "Release") + target_compile_options(BCSTM-Player PUBLIC + -fno-rtti + ) +endif() + +# Command to send to console (make send) +add_custom_target( + send + COMMAND 3dslink -a "${3DS_IP}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.3dsx" +) + +# Generate 3DSX +ctr_generate_smdh( + ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.smdh + NAME "${APP_NAME}" + DESCRIPTION "${APP_DESC}" + AUTHOR "${APP_AUTHOR}" + ICON "${APP_ICON}" +) +ctr_create_3dsx( + BCSTM-Player + OUTPUT "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.3dsx" + SMDH "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.smdh" + ROMFS "${APP_ROMFS}" +) + +if(MAKEROM AND BANNERTOOL) + message(STATUS "Found Makerom and Bannertool. Building Cia...") + + add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.bnr + COMMAND ${BANNERTOOL} makebanner + -i ${APP_BANNER} + -a ${APP_BANNERAUDIO} + -o ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.bnr + DEPENDS ${APP_BANNER} ${APP_BANNERAUDIO} + COMMENT "Creating Banner for cia file..." + VERBATIM + ) + + add_custom_command( + OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.cia + COMMAND ${MAKEROM} + -f cia -target t -exefslogo + -o "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.cia" + -elf "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.elf" + -rsf ${APP_RSF} + -banner "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.bnr" + -icon "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.smdh" + -logo ${APP_LOGO} -DAPP_ROMFS="${APP_ROMFS}" + -major 1 -minor 0 -micro 0 + -DAPP_VERSION_MAJOR=1 + DEPENDS ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.elf ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.smdh + COMMENT "Creating Cia file..." + VERBATIM + ) + + add_custom_target(make_banner ALL + DEPENDS ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.bnr + ) + + add_custom_target(make_cia ALL + DEPENDS make_banner ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.elf ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.cia + ) +endif() + +# Add clang-format target if clang-format was found +find_program(CLANG_FORMAT clang-format) +if(CLANG_FORMAT) + message(STATUS "Clang Format found.") + set(SOURCES ${CMAKE_SOURCE_DIR}/source) + set(COMMENT "Formatting Project Sources") + if(WIN32) + add_custom_target(clang-format + COMMAND powershell.exe -Command "Get-ChildItem '${SOURCES}/*' -Include *.cpp,*.hpp -Recurse | Foreach {&'${CLANG_FORMAT}' -i $_.fullname}" + COMMENT ${CCOMMENT}) + else() + add_custom_target(clang-format + COMMAND find ${SOURCES} -iname *.hpp -o -iname *.cpp | xargs ${CLANG_FORMAT} -i + COMMENT ${COMMENT} + ) + endif() + unset(COMMENT) +endif() diff --git a/D7-Menu-Core b/D7-Menu-Core deleted file mode 160000 index 9fc05f4..0000000 --- a/D7-Menu-Core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9fc05f4b5e34ac4d5c45e3a40396b01ee8bd208a diff --git a/Makefile b/Makefile deleted file mode 100644 index 4e00db7..0000000 --- a/Makefile +++ /dev/null @@ -1,309 +0,0 @@ -#--------------------------------------------------------------------------------- -.SUFFIXES: -#nicetest -#--------------------------------------------------------------------------------- - -ifeq ($(strip $(DEVKITARM)),) -$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") -endif - -TOPDIR ?= $(CURDIR) -include $(DEVKITARM)/3ds_rules - -#--------------------------------------------------------------------------------- -# TARGET is the name of the output -# BUILD is the directory where object files & intermediate files will be placed -# SOURCES is a list of directories containing source code -# DATA is a list of directories containing data files -# INCLUDES is a list of directories containing header files -# GRAPHICS is a list of directories containing graphics files -# GFXBUILD is the directory where converted graphics files will be placed -# If set to $(BUILD), it will statically link in the converted -# files as if they were data files. -# -# NO_SMDH: if set to anything, no SMDH file is generated. -# ROMFS is the directory which contains the RomFS, relative to the Makefile (Optional) -# APP_TITLE is the name of the app stored in the SMDH file (Optional) -# APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) -# APP_AUTHOR is the author of the app stored in the SMDH file (Optional) -# ICON is the filename of the icon (.png), relative to the project folder. -# If not set, it attempts to use one of the following (in this order): -# - .png -# - icon.png -# - /default_icon.png - -#--------------------------------------------------------------------------------- -# External tools -#--------------------------------------------------------------------------------- -ifeq ($(OS),Windows_NT) -MAKEROM ?= C:/devkitpro/tools/bin/makerom.exe -BANNERTOOL ?= C:/devkitpro/tools/bin/bannertool.exe - -else -MAKEROM ?= makerom -BANNERTOOL ?= bannertool - -endif - -# If on a tagged commit, use the tag instead of the commit -ifneq ($(shell echo $(shell git tag -l --points-at HEAD) | head -c 1),) -GIT_VER := $(shell git tag -l --points-at HEAD) -else -GIT_VER := $(shell git rev-parse --short HEAD) -endif - -#--------------------------------------------------------------------------------- -# Version number -#--------------------------------------------------------------------------------- - -VERSION_MAJOR := 2 -VERSION_MINOR := 0 -VERSION_MICRO := 0 - -VERSION_STRING := $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_MICRO) - -#--------------------------------------------------------------------------------- -TARGET := BCSTM-Player -BUILD := build -SOURCES := source palladium/source palladium/source/base D7-Menu-Core source/scenes -DATA := data -INCLUDES := source palladium/include D7-Menu-Core source/scenes -GRAPHICS := gfx -#GFXBUILD := $(BUILD) -ROMFS := romfs -GFXBUILD := $(ROMFS)/gfx -APP_AUTHOR := NPI-D7 -APP_DESCRIPTION := BCSTM MusicPlayer for the 3ds. -ICON := app/icon.png -BNR_IMAGE := app/banner.png -BNR_AUDIO := app/BannerAudio.wav -RSF_FILE := app/build-cia.rsf - -#--------------------------------------------------------------------------------- -IP := 0.0.0.0 -#--------------------------------------------------------------------------------- -# options for code generation -#--------------------------------------------------------------------------------- -ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft - -CFLAGS := -g -Wall -Wno-psabi -O2 -mword-relocations \ - -DN_STRING=\"$(GIT_VER)\" \ - -DV_STRING=\"$(VERSION_STRING)\" \ - -fomit-frame-pointer -ffunction-sections \ - $(ARCH) - -CFLAGS += $(INCLUDE) -D__3DS__ -D_GNU_SOURCE=1 - -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++20 - -ASFLAGS := -g $(ARCH) -LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) - -LIBS := -lstdc++ -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lz -lm -lcitro2d -lcitro3d -lctru - -#--------------------------------------------------------------------------------- -# list of directories containing libraries, this must be the top level containing -# include and lib -#--------------------------------------------------------------------------------- -LIBDIRS := $(PORTLIBS) $(CTRULIB) - - -#--------------------------------------------------------------------------------- -# no real need to edit anything past this point unless you need to add additional -# rules for different file extensions -#--------------------------------------------------------------------------------- -ifneq ($(BUILD),$(notdir $(CURDIR))) -#--------------------------------------------------------------------------------- - -export OUTPUT := $(CURDIR)/$(TARGET) -export TOPDIR := $(CURDIR) - -export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ - $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) \ - $(foreach dir,$(DATA),$(CURDIR)/$(dir)) - -export DEPSDIR := $(CURDIR)/$(BUILD) - -CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) -CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) -SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) -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)/*.*))) - -#--------------------------------------------------------------------------------- -# use CXX for linking C++ projects, CC for standard C -#--------------------------------------------------------------------------------- -ifeq ($(strip $(CPPFILES)),) -#--------------------------------------------------------------------------------- - export LD := $(CC) -#--------------------------------------------------------------------------------- -else -#--------------------------------------------------------------------------------- - export LD := $(CXX) -#--------------------------------------------------------------------------------- -endif -#--------------------------------------------------------------------------------- - -#--------------------------------------------------------------------------------- -ifeq ($(GFXBUILD),$(BUILD)) -#--------------------------------------------------------------------------------- -export T3XFILES := $(GFXFILES:.t3s=.t3x) -#--------------------------------------------------------------------------------- -else -#--------------------------------------------------------------------------------- -export ROMFS_T3XFILES := $(patsubst %.t3s, $(GFXBUILD)/%.t3x, $(GFXFILES)) -export T3XHFILES := $(patsubst %.t3s, $(BUILD)/%.h, $(GFXFILES)) -#--------------------------------------------------------------------------------- -endif -#--------------------------------------------------------------------------------- - -export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) - -export OFILES_BIN := $(addsuffix .o,$(BINFILES)) \ - $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) - -export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) - -export HFILES := $(PICAFILES:.v.pica=_shbin.h) $(SHLISTFILES:.shlist=_shbin.h) \ - $(addsuffix .h,$(subst .,_,$(BINFILES))) - -export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ - $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ - -I$(CURDIR)/$(BUILD) - -export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) - -export _3DSXDEPS := $(if $(NO_SMDH),,$(OUTPUT).smdh) - -ifeq ($(strip $(ICON)),) - icons := $(wildcard *.png) - ifneq (,$(findstring $(TARGET).png,$(icons))) - export APP_ICON := $(TOPDIR)/$(TARGET).png - else - ifneq (,$(findstring icon.png,$(icons))) - export APP_ICON := $(TOPDIR)/icon.png - endif - endif -else - export APP_ICON := $(TOPDIR)/$(ICON) -endif - -ifeq ($(strip $(NO_SMDH)),) - export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh -endif - -ifneq ($(ROMFS),) - export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) -endif - -.PHONY: all clean - -#--------------------------------------------------------------------------------- -all: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES) - @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile - -#------------------------------------------------------------------------------ -clean: - @echo clean ... - @rm -fr $(BUILD) $(TARGET).elf $(TARGET).3dsx $(TARGET).cia $(TARGET).smdh app/*.bin - @rm -fr $(OUTDIR) - -#--------------------------------------------------------------------------------- -send: - @3dslink -a $(IP) $(TARGET).3dsx -#--------------------------------------------------------------------------------- -run: - @flatpak run org.citra_emu.citra $(TARGET).3dsx -#--------------------------------------------------------------------------------- -andsend: - @make all - @3dslink -a $(IP) $(TARGET).3dsx -#--------------------------------------------------------------------------------- -cia: $(BUILD) - @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile cia - -#--------------------------------------------------------------------------------- -3dsx: $(BUILD) - @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 3dsx - -#--------------------------------------------------------------------------------- -$(GFXBUILD)/%.t3x $(BUILD)/%.h : %.t3s -#--------------------------------------------------------------------------------- - @echo $(notdir $<) - $(DEVKITPRO)/tools/bin/tex3ds -i $< -H $(BUILD)/$*.h -d $(DEPSDIR)/$*.d -o $(GFXBUILD)/$*.t3x - -#--------------------------------------------------------------------------------- -$(BUILD): - @[ -d $@ ] || mkdir -p $@ - -#--------------------------------------------------------------------------------- -else - -#--------------------------------------------------------------------------------- -# main targets -#--------------------------------------------------------------------------------- -all: $(OUTPUT).cia $(OUTPUT).elf $(OUTPUT).3dsx - -$(OUTPUT).elf : $(OFILES) - -$(OUTPUT).cia : $(OUTPUT).elf $(OUTPUT).smdh - $(BANNERTOOL) makebanner -i "../app/banner.png" -a "../app/BannerAudio.wav" -o "../app/banner.bin" - - $(BANNERTOOL) makesmdh -i "../app/icon.png" -s "$(TARGET)" -l "$(APP_DESCRIPTION)" -p "$(APP_AUTHOR)" -o "../app/icon.bin" - - $(MAKEROM) -f cia -target t -exefslogo -o "../BCSTM-Player.cia" -elf "../BCSTM-Player.elf" -rsf "../app/build-cia.rsf" -banner "../app/banner.bin" -icon "../app/icon.bin" -logo "../app/splash.lz" -DAPP_ROMFS="$(TOPDIR)/$(ROMFS)" -major $(VERSION_MAJOR) -minor $(VERSION_MINOR) -micro $(VERSION_MICRO) -DAPP_VERSION_MAJOR="$(VERSION_MAJOR)" -#--------------------------------------------------------------------------------- -# you need a rule like this for each extension you use as binary data -#--------------------------------------------------------------------------------- -%.bin.o %_bin.h : %.bin -#--------------------------------------------------------------------------------- - @echo $(notdir $<) - @$(bin2o) - -#--------------------------------------------------------------------------------- -.PRECIOUS : %.t3x -#--------------------------------------------------------------------------------- -%.t3x.o %_t3x.h : %.t3x -#--------------------------------------------------------------------------------- - @echo $(notdir $<) - @$(bin2o) - -#--------------------------------------------------------------------------------- -# rules for assembling GPU shaders -#--------------------------------------------------------------------------------- -define shader-as - $(eval CURBIN := $*.shbin) - $(eval DEPSFILE := $(DEPSDIR)/$*.shbin.d) - echo "$(CURBIN).o: $< $1" > $(DEPSFILE) - echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(CURBIN) | tr . _)`.h - echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(CURBIN) | tr . _)`.h - echo "extern const u32" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(CURBIN) | tr . _)`.h - picasso -o $(CURBIN) $1 - bin2s $(CURBIN) | $(AS) -o $*.shbin.o -endef - -%.shbin.o %_shbin.h : %.v.pica %.g.pica - @echo $(notdir $^) - @$(call shader-as,$^) - -%.shbin.o %_shbin.h : %.v.pica - @echo $(notdir $<) - @$(call shader-as,$<) - -%.shbin.o %_shbin.h : %.shlist - @echo $(notdir $<) - @$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)$(file))) - -#--------------------------------------------------------------------------------- -%.t3x %.h : %.t3s -#--------------------------------------------------------------------------------- - @echo $(notdir $<) - @tex3ds -i $< -H $*.h -d $*.d -o $*.t3x - --include $(DEPSDIR)/*.d - -#--------------------------------------------------------------------------------------- -endif -#--------------------------------------------------------------------------------------- diff --git a/README.md b/README.md index 507c92e..714f90e 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ -# BCSTM-Player +# BCSTM-Player License: GPLv3   GitHub repo size +### if you find any bug, report it in an issue + +## Todo - ### if you find any bug, report it in an issue! -# Todo: -- support more channels - mount romfs from installed Titles / Gamecard using D7-Menu-Core. (works in citra but not on real hardware for some reason) -# Nightly builds +## Credits - +- [tobid7](https://github.com/tobid7): Lead Developer, author of palladium, ctrff +- [devkitpro](https://github.com/devkitpro): libctru, citro3d +- [cheuble](https://github.com/cheuble): Source of BCSTMV1 Decoding code (based on freeshop) +- [3dbrew](https://www.3dbrew.org/wiki/BCSTM): BCSTM Documentation diff --git a/ctrff b/ctrff new file mode 160000 index 0000000..fb8f275 --- /dev/null +++ b/ctrff @@ -0,0 +1 @@ +Subproject commit fb8f275ebbc02493ae984ae01772026fe49d1a69 diff --git a/palladium b/palladium index eac36bc..a0960bd 160000 --- a/palladium +++ b/palladium @@ -1 +1 @@ -Subproject commit eac36bcc6e2eab515cb65a6918b832f8b33d6e04 +Subproject commit a0960bd717bce6a659d17e157e59c9bf16486c04 diff --git a/romfs/fonts/ComicNeue.ttf b/romfs/fonts/ComicNeue.ttf new file mode 100644 index 0000000..dc9a6d5 Binary files /dev/null and b/romfs/fonts/ComicNeue.ttf differ diff --git a/romfs/gfx/.gitkeep b/romfs/gfx/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/romfs/license/ComicNeue.txt b/romfs/license/ComicNeue.txt new file mode 100644 index 0000000..80062cf --- /dev/null +++ b/romfs/license/ComicNeue.txt @@ -0,0 +1,93 @@ +Copyright 2014 The Comic Neue Project Authors (https://github.com/crozynski/comicneue) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/source/bcstm.cpp b/source/bcstm.cpp deleted file mode 100644 index f5101c6..0000000 --- a/source/bcstm.cpp +++ /dev/null @@ -1,257 +0,0 @@ -#include - -#include - -bool D7::BCSTM::LoadFile(const std::string &path) { - Stop(); - file.open(path, std::ios::in | std::ios::binary); - if (!file) { - err_msg = "Unable to load File!"; - return false; - } - is_little_endian = true; // default to true - - auto magic = read32(); - if (!(is_little_endian = (read16() == 0xFEFF))) { - magic = htonl(magic); - } - if (magic != 0x4D545343) { // CSTM - file.close(); - err_msg = "File is invalid!"; - return false; - } - file.seekg(0x10); - auto sbc = read16(); - read16(); - - for (unsigned short i = 0; i < sbc; i++) { - auto sec = read16(); - read16(); // padding - auto off = read32(); - read32(); // size - if (sec == InfoBlock) - info_offset = off; - else if (sec == DataBlock) - data_offset = off; - } - if (!info_offset || !data_offset) { - err_msg = "Data/Info Section not found!"; - return false; - } - - file.seekg((info_offset + 0x20)); - if (read8() != 2) { - err_msg = "Unknown Error!"; - return false; - } - is_looping = read8(); - channel_count = read8(); - if (channel_count > 2) { - err_msg = "File has not 2 channels!"; - return false; - } - file.seekg((info_offset + 0x24)); - sample_rate = read32(); - auto _loop_pos = read32(); - auto _loop_end = read32(); - num_blocks = read32(); - block_size = read32(); - block_samples = read32(); - read32(); // last block used bytes - last_block_samples = read32(); - last_block_size = read32(); - - loop_start = _loop_pos / block_samples; - loop_end = - (_loop_end % block_samples ? num_blocks : _loop_end / block_samples); - - while (read32() != ChannelInfo) { - // Find Channel Info - } - file_advance(read32() + channel_count * 8 - 12); - - // get adpcm data - for (unsigned int i = 0; i < channel_count; i++) { - file.read(reinterpret_cast(adpcm_coefs[i]), - sizeof(unsigned short) * 16); - // beginning context - file.read(reinterpret_cast(&adpcm_data[i][0]), - sizeof(ndspAdpcmData)); - // loop context - file.read(reinterpret_cast(&adpcm_data[i][1]), - sizeof(ndspAdpcmData)); - // skip padding - read16(); - } - - file.seekg((data_offset + 0x20)); - is_loaded = true; - return true; -} - -void D7::BCSTM::Update() { this->stream(); } - -void D7::BCSTM::Play() { - if (is_paused) { - for (unsigned int i = 0; i < channel_count; i++) { - ndspChnSetPaused(channel[i], false); - } - is_paused = false; - return; - } - if (is_streaming) return; - for (unsigned int i = 0; i < channel_count; i++) { - { - channel[i] = 0; - while (channel[i] < 24 && ((active_channels >> channel[i]) & 1)) { - channel[i]++; - } - if (channel[i] == 24) { - err_msg = "Current chennel equals 24!"; - return; - } - active_channels |= 1 << channel[i]; - ndspChnWaveBufClear(channel[i]); - } - static float mix[16]; - ndspChnSetFormat(channel[i], - NDSP_FORMAT_ADPCM | NDSP_3D_SURROUND_PREPROCESSED); - ndspChnSetRate(channel[i], sample_rate); - - if (channel_count == 1) { - mix[0] = mix[1] = 0.5f; - } else if (channel_count == 2) { - if (i == 0) { - mix[0] = 0.8f; - mix[1] = 0.0f; - mix[2] = 0.2f; - mix[3] = 0.0f; - } else { - mix[0] = 0.0f; - mix[1] = 0.8f; - mix[2] = 0.0f; - mix[3] = 0.2f; - } - } - ndspChnSetMix(channel[i], mix); - ndspChnSetAdpcmCoefs(channel[i], adpcm_coefs[i]); - - for (unsigned int j = 0; j < buffer_count; j++) { - memset(&wave_buf[i][j], 0, sizeof(ndspWaveBuf)); - wave_buf[i][j].status = NDSP_WBUF_DONE; - buffer_data[i][j].resize(block_size); - } - } - is_streaming = true; -} - -void D7::BCSTM::Pause() { - if (!is_streaming) return; - is_paused = true; - for (unsigned int i = 0; i < channel_count; i++) { - ndspChnSetPaused(channel[i], true); - } -} - -void D7::BCSTM::Stop() { - if (file) file.close(); - channel_count = 0; - sample_rate = 0; - loop_start = 0; - loop_end = 0; - num_blocks = 0; - block_size = 0; - block_samples = 0; - last_block_size = 0; - last_block_samples = 0; - current_block = 0; - info_offset = 0; - data_offset = 0; - active_channels = 0; - is_paused = false; - is_looping = false; - err_msg = "None"; - is_loaded = false; - if (!is_streaming) return; - is_streaming = false; - for (unsigned int i = 0; i < channel_count; i++) { - ndspChnWaveBufClear(channel[i]); - active_channels &= ~(1 << channel[i]); - } -} - -void D7::BCSTM::stream() { - current_time = svcGetSystemTick(); - if (current_time - last_time >= 100000000 && is_loaded) { - if (!is_streaming) return; - if (!is_paused) fill_buffers(); - last_time = current_time; - } -} - -void D7::BCSTM::fill_buffers() { - for (unsigned int bufIndex = 0; bufIndex < buffer_count; ++bufIndex) { - if (wave_buf[0][bufIndex].status != NDSP_WBUF_DONE) continue; - if (channel_count == 2 && wave_buf[1][bufIndex].status != NDSP_WBUF_DONE) - continue; - - if (is_looping && current_block == loop_end) { - current_block = loop_start; - file.seekg( - (data_offset + 0x20 + block_size * channel_count * loop_start)); - } - if (!is_looping && current_block == loop_end) { - this->Stop(); - } - - for (unsigned int channelIndex = 0; channelIndex < channel_count; - ++channelIndex) { - ndspWaveBuf *buf = &wave_buf[channelIndex][bufIndex]; - - memset(buf, 0, sizeof(ndspWaveBuf)); - buf->data_adpcm = buffer_data[channelIndex][bufIndex].data(); - file.read( - reinterpret_cast(buf->data_adpcm), - (current_block == num_blocks - 1) ? last_block_size : block_size); - DSP_FlushDataCache(buf->data_adpcm, block_size); - - if (current_block == 0) - buf->adpcm_data = &adpcm_data[channelIndex][0]; - else if (current_block == loop_start) - buf->adpcm_data = &adpcm_data[channelIndex][1]; - - if (current_block == num_blocks - 1) - buf->nsamples = last_block_samples; - else - buf->nsamples = block_samples; - - ndspChnWaveBufAdd(channel[channelIndex], buf); - } - - current_block++; - } -} - -unsigned int D7::BCSTM::read32() { - unsigned int ret; - file.read(reinterpret_cast(&ret), sizeof(ret)); - return (is_little_endian ? ret : htonl(ret)); -} - -unsigned short D7::BCSTM::read16() { - unsigned short ret; - file.read(reinterpret_cast(&ret), sizeof(ret)); - return (is_little_endian ? ret : htons(ret)); -} - -unsigned char D7::BCSTM::read8() { - unsigned char ret; - file.read(reinterpret_cast(&ret), sizeof(ret)); - return ret; -} - -bool D7::BCSTM::file_advance(unsigned long long bytes) { - size_t seek_pos = (size_t)file.tellg() + bytes; - file.seekg(seek_pos); - return (!file.fail() && file.tellg() == seek_pos); -} \ No newline at end of file diff --git a/source/bcstm.hpp b/source/bcstm.hpp deleted file mode 100644 index 9473d20..0000000 --- a/source/bcstm.hpp +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include -#include - -namespace D7 { -class BCSTM { - public: - BCSTM() {} - ~BCSTM() { Stop(); } - - bool LoadFile(const std::string& path); - void Update(); - void Play(); - void Pause(); - void Stop(); - - inline bool IsLooping() { return this->is_looping; } - inline bool IsLoaded() { return this->is_loaded; } - inline unsigned int GetLoopStart() { return this->loop_start; } - inline unsigned int GetLoopEnd() { return this->loop_end; } - inline unsigned int GetChannelCount() { return this->channel_count; } - inline unsigned int GetTotal() { return this->num_blocks; } - inline unsigned int GetCurrent() { return this->current_block; } - inline unsigned int GetSampleRate() { return this->sample_rate; } - inline std::string GetErrorMessage() { return this->err_msg; } - - private: - unsigned int read32(); - unsigned short read16(); - unsigned char read8(); - bool file_advance(unsigned long long bytes); - void stream(); - void fill_buffers(); - - enum RefType : uint16_t { - ByteTable = 0x0100, - ReferenceTable = 0x0101, - SampleData = 0x1F00, - DSPADPCMInfo = 0x0300, - InfoBlock = 0x4000, - SeekBlock = 0x4001, - DataBlock = 0x4002, - StreamInfo = 0x4100, - TrackInfo = 0x4101, - ChannelInfo = 0x4102, - }; - - static const int buffer_count = 20; - - std::string err_msg = ""; - - std::fstream file; - unsigned int current_time = 0; - unsigned int last_time = 0; - - bool is_loaded = false; - bool is_paused = false; - bool is_looping = false; - bool is_streaming = false; - bool is_little_endian; - - unsigned int channel_count = 0; - unsigned int sample_rate = 0; - - unsigned int loop_start = 0; - unsigned int loop_end = 0; - unsigned int num_blocks = 0; - unsigned int block_size = 0; - unsigned int block_samples = 0; - unsigned int last_block_size = 0; - unsigned int last_block_samples = 0; - unsigned short adpcm_coefs[2][16]; - - unsigned int current_block = 0; - unsigned int info_offset = 0; - unsigned int data_offset = 0; - - unsigned int active_channels = 0; - unsigned short channel[2]; - - ndspWaveBuf wave_buf[2][buffer_count]; - ndspAdpcmData adpcm_data[2][2]; - std::vector> - buffer_data[2][buffer_count]; -}; -} // namespace D7 \ No newline at end of file diff --git a/source/bcstm/base.hpp b/source/bcstm/base.hpp new file mode 100644 index 0000000..b083711 --- /dev/null +++ b/source/bcstm/base.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +/** + * Base class for In app switching between CTRFF and BCSTMV2 Decoders + */ + +namespace D7 { +class BCSTMPlayerBase { + public: + using Features = unsigned int; + enum Features_ : Features { + None = 0, + SingleChannel = 1 << 0, + DualChannel = 1 << 1, + QuadraChannel = 1 << 2, + HexaChannel = 1 << 3, + OktaChannel = 1 << 4, + AllChannels = + SingleChannel | DualChannel | QuadraChannel | HexaChannel | OktaChannel, + NonLoopEnd = 1 << 5, + FormatBCSTM = 1 << 6, + Default = + FormatBCSTM | SingleChannel | DualChannel, // all support these iirc + }; + BCSTMPlayerBase() = default; + BCSTMPlayerBase(const std::string& n, Features f) : pName(n), pFeatures(f) {} + virtual ~BCSTMPlayerBase() = default; + + virtual void LoadFile(const std::string& path) = 0; + virtual void Stream() = 0; + + virtual void CleanUp() = 0; + + virtual void Play() = 0; + virtual void Stop() = 0; + virtual void Pause() = 0; + + /** INFO */ + virtual bool IsLooping() { return false; } + virtual bool IsLoaded() { return false; } + virtual unsigned int GetLoopStart() { return 0; } + virtual unsigned int GetLoopEnd() { return 0; } + virtual unsigned int GetChannelCount() { return 0; } + virtual unsigned int GetTotal() { return 0; } + virtual unsigned int GetCurrent() { return 0; } + virtual unsigned int GetSampleRate() { return 0; } + virtual unsigned int GetSamples() const { return 0; } + virtual unsigned int GetBlcokSize() const { return 0; } + virtual unsigned int GetBlockSamples() const { return 0; } + + std::string GetName() const { return pName; } + + std::string pName = "Unknown"; + Features pFeatures = Default; +}; +} // namespace D7 \ No newline at end of file diff --git a/source/bcstm/bcstmv2.cpp b/source/bcstm/bcstmv2.cpp new file mode 100644 index 0000000..d1f1c27 --- /dev/null +++ b/source/bcstm/bcstmv2.cpp @@ -0,0 +1,275 @@ +#include + +#include + +void D7::BCSTM2::LoadFile(const std::string& path) { + Stop(); + pFile.open(path, std::ios::in | std::ios::binary); + if (!pFile) { + throw std::runtime_error("BCSTM [ERROR]: Unable to load File!"); + } + pBigEndian = false; + is_little_endian = true; // default to true + + auto magic = Read(); + if ((pBigEndian = (Read() != 0xFEFF))) { + magic = htonl(magic); + } + if (magic != 0x4D545343) { // CSTM + pFile.close(); + throw std::runtime_error("BCSTM [ERROR]: File is invalid!"); + } + pFile.seekg(0x10); + auto sbc = Read(); + Read(); + + for (unsigned short i = 0; i < sbc; i++) { + auto sec = Read(); + Read(); // padding + auto off = Read(); + Read(); // size + if (sec == InfoBlock) + info_offset = off; + else if (sec == DataBlock) + data_offset = off; + } + if (!info_offset || !data_offset) { + throw std::runtime_error("BCSTM [ERROR]: Data/Info Section not found"); + } + + pFile.seekg((info_offset + 0x20)); + if (Read() != 2) { + throw std::runtime_error( + "BCSTM [ERROR]: Only DSP ADPCM Format is supported!"); + } + is_looping = Read(); + channel_count = Read(); + /*if (channel_count > 2) { + throw std::runtime_error("BCSTM [ERROR]: File has not 2 channels!"); + }*/ + pFile.seekg((info_offset + 0x24)); + sample_rate = Read(); + auto _loop_pos = Read(); + auto _loop_end = Read(); + num_blocks = Read(); + block_size = Read(); + block_samples = Read(); + Read(); // last block used bytes + last_block_samples = Read(); + last_block_size = Read(); + + loop_start = _loop_pos / block_samples; + loop_end = + (_loop_end % block_samples ? num_blocks : _loop_end / block_samples); + + while (Read() != ChannelInfo) { + // Find Channel Info + } + file_advance(Read() + channel_count * 8 - 12); + // get adpcm data + for (unsigned int i = 0; i < channel_count; i++) { + pFile.read(reinterpret_cast(adpcm_coefs[i]), + sizeof(unsigned short) * 16); + // beginning context + pFile.read(reinterpret_cast(&adpcm_data[i][0]), + sizeof(ndspAdpcmData)); + // loop context + pFile.read(reinterpret_cast(&adpcm_data[i][1]), + sizeof(ndspAdpcmData)); + // skip padding + Read(); + } + + pFile.seekg((data_offset + 0x20)); + is_loaded = true; +} + +void D7::BCSTM2::Stream() { this->stream(); } + +void D7::BCSTM2::Play() { + if (is_paused) { + for (unsigned int i = 0; i < channel_count; i++) { + ndspChnSetPaused(channel[i], false); + } + is_paused = false; + return; + } + if (is_streaming) return; + for (unsigned int i = 0; i < channel_count; i++) { + { + channel[i] = 0; + while (channel[i] < 24 && ((active_channels >> channel[i]) & 1)) { + channel[i]++; + } + if (channel[i] == 24) { + std::cout << "BCSTM [ERROR]: Current chennel equals 24!" << std::endl; + return; + } + active_channels |= 1 << channel[i]; + ndspChnWaveBufClear(channel[i]); + } + static float mix[12]; + ndspChnSetFormat(channel[i], + NDSP_FORMAT_ADPCM | NDSP_3D_SURROUND_PREPROCESSED); + ndspChnSetRate(channel[i], sample_rate); + + /** Make sure they are 0 */ + for (int j = 0; j < 12; j++) { + mix[j] = 0.f; + } + /** Leave 1 and 2 Channels as they are */ + if (channel_count == 1) { + mix[0] = mix[1] = 0.5f; + } else if (channel_count == 2) { + if (i == 0) { + mix[0] = 0.8f; + mix[2] = 0.2f; + } else { + mix[1] = 0.8f; + mix[3] = 0.2f; + } + /** Dont know if this is the finished code here */ + } else if (channel_count == 4) { + mix[i] = 0.8f; + if (i > 0) mix[i - 1] += 0.2f; + if (i < 3) mix[i + 1] += 0.2f; + } else if (channel_count == 6) { + mix[i] = 0.7f; + if (i > 0) mix[i - 1] += 0.15f; + if (i < 5) mix[i + 1] += 0.15f; + } else if (channel_count == 8) { + mix[i] = 0.6f; + if (i > 0) mix[i - 1] += 0.1f; + if (i < 7) mix[i + 1] += 0.1f; + } + + ndspChnSetMix(channel[i], mix); + ndspChnSetAdpcmCoefs(channel[i], adpcm_coefs[i]); + + for (unsigned int j = 0; j < buffer_count; j++) { + memset(&wave_buf[i][j], 0, sizeof(ndspWaveBuf)); + wave_buf[i][j].status = NDSP_WBUF_DONE; + buffer_data[i][j].Resize(block_size); + } + } + is_streaming = true; +} + +void D7::BCSTM2::Pause() { + if (!is_streaming) return; + is_paused = true; + for (unsigned int i = 0; i < channel_count; i++) { + ndspChnSetPaused(channel[i], true); + } +} + +void D7::BCSTM2::Stop() { + if (pFile) pFile.close(); + /** Move this loop up (logical issue in < v2.0.0) */ + for (unsigned int i = 0; i < channel_count; i++) { + ndspChnWaveBufClear(channel[i]); + active_channels &= ~(1 << channel[i]); + } + channel_count = 0; + sample_rate = 0; + loop_start = 0; + loop_end = 0; + num_blocks = 0; + block_size = 0; + block_samples = 0; + last_block_size = 0; + last_block_samples = 0; + current_block = 0; + info_offset = 0; + data_offset = 0; + active_channels = 0; + is_paused = false; + is_looping = false; + is_loaded = false; + m_is_ending = false; + if (!is_streaming) return; + is_streaming = false; +} + +void D7::BCSTM2::stream() { + current_time = svcGetSystemTick(); + if (current_time - last_time >= 100000000 && is_loaded) { + if (m_is_ending) { + bool all_done = true; + for (PD::u32 buf = 0; buf < buffer_count; buf++) { + for (PD::u8 chn = 0; chn < channel_count; chn++) { + if (wave_buf[chn][buf].status != NDSP_WBUF_DONE) { + all_done = false; + break; + } + } + } + if (all_done) { + Stop(); + } + return; + } + if (!is_streaming) return; + if (!is_paused) fill_buffers(); + last_time = current_time; + } +} + +void D7::BCSTM2::fill_buffers() { + for (unsigned int bufIndex = 0; bufIndex < buffer_count; ++bufIndex) { + /*if (wave_buf[0][bufIndex].status != NDSP_WBUF_DONE) continue; + if (channel_count == 2 && wave_buf[1][bufIndex].status != NDSP_WBUF_DONE) + continue;*/ + + bool all_ready = true; + for (unsigned int ch = 0; ch < channel_count; ++ch) { + if (wave_buf[ch][bufIndex].status != NDSP_WBUF_DONE) { + all_ready = false; + break; + } + } + if (!all_ready) continue; + + if (is_looping && current_block == loop_end) { + current_block = loop_start; + pFile.seekg( + (data_offset + 0x20 + block_size * channel_count * loop_start)); + } + if (!is_looping && current_block == loop_end) { + m_is_ending = true; + return; + } + + for (unsigned int channelIndex = 0; channelIndex < channel_count; + ++channelIndex) { + ndspWaveBuf* buf = &wave_buf[channelIndex][bufIndex]; + + memset(buf, 0, sizeof(ndspWaveBuf)); + buf->data_adpcm = buffer_data[channelIndex][bufIndex].Data(); + pFile.read( + reinterpret_cast(buf->data_adpcm), + (current_block == num_blocks - 1) ? last_block_size : block_size); + DSP_FlushDataCache(buf->data_adpcm, block_size); + + if (current_block == 0) + buf->adpcm_data = &adpcm_data[channelIndex][0]; + else if (current_block == loop_start) + buf->adpcm_data = &adpcm_data[channelIndex][1]; + + if (current_block == num_blocks - 1) + buf->nsamples = last_block_samples; + else + buf->nsamples = block_samples; + + ndspChnWaveBufAdd(channel[channelIndex], buf); + } + + current_block++; + } +} + +bool D7::BCSTM2::file_advance(unsigned long long bytes) { + size_t seek_pos = (size_t)pFile.tellg() + bytes; + pFile.seekg(seek_pos); + return (!pFile.fail() && pFile.tellg() == seek_pos); +} \ No newline at end of file diff --git a/source/bcstm/bcstmv2.hpp b/source/bcstm/bcstmv2.hpp new file mode 100644 index 0000000..c6a90de --- /dev/null +++ b/source/bcstm/bcstmv2.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include <3ds.h> + +#include +#include +#include +#include +#include + +namespace D7 { +class BCSTM2 : public BCSTMPlayerBase { + public: + BCSTM2() : BCSTMPlayerBase("BCSTMV2", FormatBCSTM | AllChannels) {} + ~BCSTM2() { Stop(); } + + template + void Read(T& v) { + // Check if Value could be Read + static_assert(std::is_integral::value, "Cannot Read type T"); + v = 0; // Set value to 0 (most cases a windows problem) + std::vector buf(sizeof(T), 0); // declare buffer + // Read data into buffer + pFile.read(reinterpret_cast(buf.data()), sizeof(T)); + // Loop or in be reverse loop and chift the values + for (size_t i = 0; i < sizeof(T); i++) { + v |= static_cast(buf[pBigEndian ? sizeof(T) - 1 - i : i]) << (8 * i); + } + } + template + void Write(const T& v) { + // Check if Value could Write + static_assert(std::is_integral::value, "Cannot Write type T"); + std::vector buf(sizeof(T), 0); // declare buffer + // Loop or in be reverse loop and write the values + for (size_t i = 0; i < sizeof(T); i++) { + buf[(pBigEndian ? sizeof(T) - 1 - i : i)] = + buf[pBigEndian ? sizeof(T) - 1 - i : i] = + static_cast((v >> (8 * i)) & 0xFF); + } + // Write buffer into file + pFile.write(reinterpret_cast(buf.data()), sizeof(T)); + } + template + T Read() { + T o; + Read(o); + return o; + } + + void LoadFile(const std::string& path); + void Stream(); + void Play(); + void Pause(); + void Stop(); + void CleanUp() { Stop(); } + + bool IsLooping() { return this->is_looping; } + bool IsLoaded() { return this->is_loaded; } + unsigned int GetLoopStart() { return this->loop_start; } + unsigned int GetLoopEnd() { return this->loop_end; } + unsigned int GetChannelCount() { return this->channel_count; } + unsigned int GetTotal() { return this->num_blocks; } + unsigned int GetCurrent() { return this->current_block; } + unsigned int GetSampleRate() { return this->sample_rate; } + unsigned int GetSamples() const { + return (block_size * num_blocks) / sample_rate; + } + unsigned int GetBlcokSize() const { return block_size; } + unsigned int GetBlockSamples() const { return block_samples; } + + bool pBigEndian = false; + std::fstream pFile; + + private: + unsigned int read32(); + unsigned short read16(); + unsigned char read8(); + bool file_advance(unsigned long long bytes); + void stream(); + void fill_buffers(); + + enum RefType : uint16_t { + ByteTable = 0x0100, + ReferenceTable = 0x0101, + SampleData = 0x1F00, + DSPADPCMInfo = 0x0300, + InfoBlock = 0x4000, + SeekBlock = 0x4001, + DataBlock = 0x4002, + StreamInfo = 0x4100, + TrackInfo = 0x4101, + ChannelInfo = 0x4102, + }; + + static const int buffer_count = 20; + static const int max_channels = 8; + + unsigned int current_time = 0; + unsigned int last_time = 0; + + bool is_loaded = false; + bool is_paused = false; + bool is_looping = false; + bool is_streaming = false; + bool m_is_ending = false; + bool is_little_endian; + + unsigned int channel_count = 0; + unsigned int sample_rate = 0; + + unsigned int loop_start = 0; + unsigned int loop_end = 0; + unsigned int num_blocks = 0; + unsigned int block_size = 0; + unsigned int block_samples = 0; + unsigned int last_block_size = 0; + unsigned int last_block_samples = 0; + unsigned short adpcm_coefs[max_channels][16]; + + unsigned int current_block = 0; + unsigned int info_offset = 0; + unsigned int data_offset = 0; + + unsigned int active_channels = 0; + unsigned short channel[max_channels]; + + ndspWaveBuf wave_buf[max_channels][buffer_count]; + ndspAdpcmData adpcm_data[max_channels][2]; + PD::Vec> buffer_data[max_channels][buffer_count]; +}; +} // namespace D7 \ No newline at end of file diff --git a/source/bcstm/ctrff_decode.cpp b/source/bcstm/ctrff_decode.cpp new file mode 100644 index 0000000..637a51e --- /dev/null +++ b/source/bcstm/ctrff_decode.cpp @@ -0,0 +1,202 @@ +#include +#include /** std::memset :( */ + +namespace D7 { +void CTRFFDec::LoadFile(const std::string &path) { + CleanUp(); + pCurrentFile.LoadFile(path); + + /** Resize the Player Internal Data holders */ + /** Using this allows to not have to much memory allocated */ + pChannels.Resize(pCurrentFile.GetNumChannels()); + pWaveBuf.resize(pCurrentFile.GetNumChannels()); + pBufferData.resize(pCurrentFile.GetNumChannels()); + for (PD::u8 i = 0; i < pCurrentFile.GetNumChannels(); i++) { + pWaveBuf[i].resize(BufferCount); + pBufferData[i].resize(BufferCount); + } + pIsLoaded = true; +} + +void CTRFFDec::Play() { + if (pIsPaused) { + for (PD::u8 i = 0; i < pCurrentFile.GetNumChannels(); i++) { + ndspChnSetPaused(pChannels[i], false); + } + pIsPaused = false; + return; + } + if (pIsStreaming) { + return; + } + for (PD::u32 i = 0; i < pCurrentFile.GetNumChannels(); i++) { + { + pChannels[i] = 0; + while (pChannels[i] < 24 && ((pActiveChannels >> pChannels[i]) & 1)) { + pChannels[i]++; + } + if (pChannels[i] == 24) { + throw std::runtime_error( + "BCSTM Player: Out of Range (channel == 24) detected!"); + } + pActiveChannels |= 1 << pChannels[i]; + ndspChnWaveBufClear(pChannels[i]); + } + static float mix[12]; + ndspChnSetFormat(pChannels[i], + NDSP_FORMAT_ADPCM | NDSP_3D_SURROUND_PREPROCESSED); + ndspChnSetRate(pChannels[i], pCurrentFile.GetSampleRate()); + + /** Make sure they are 0 */ + for (int j = 0; j < 12; j++) { + mix[j] = 0.f; + } + /** Leave 1 and 2 Channels as they are */ + if (pCurrentFile.GetNumChannels() == 1) { + mix[0] = mix[1] = 0.5f; + } else if (pCurrentFile.GetNumChannels() == 2) { + if (i == 0) { + mix[0] = 0.8f; + mix[2] = 0.2f; + } else { + mix[1] = 0.8f; + mix[3] = 0.2f; + } + /** Dont know if this is the finished code here */ + } else if (pCurrentFile.GetNumChannels() == 4) { + mix[i] = 0.8f; + if (i > 0) mix[i - 1] += 0.2f; + if (i < 3) mix[i + 1] += 0.2f; + } else if (pCurrentFile.GetNumChannels() == 6) { + mix[i] = 0.7f; + if (i > 0) mix[i - 1] += 0.15f; + if (i < 5) mix[i + 1] += 0.15f; + } else if (pCurrentFile.GetNumChannels() == 8) { + mix[i] = 0.6f; + if (i > 0) mix[i - 1] += 0.1f; + if (i < 7) mix[i + 1] += 0.1f; + } + + ndspChnSetMix(pChannels[i], mix); + ndspChnSetAdpcmCoefs(pChannels[i], + pCurrentFile.pDSP_ADPCM_Info[i].Param.Coefficients); + for (int j = 0; j < BufferCount; j++) { + /** still Prefer fill_n over memset */ + std::memset(&pWaveBuf[i][j], 0, sizeof(ndspWaveBuf)); + pWaveBuf[i][j].status = NDSP_WBUF_DONE; + pBufferData[i][j].Resize(pCurrentFile.GetBlockSize()); + } + } + pIsStreaming = true; +} + +void CTRFFDec::Pause() { + if (!pIsStreaming) { + return; + } + pIsPaused = true; + for (PD::u32 i = 0; i < pCurrentFile.GetNumChannels(); i++) { + ndspChnSetPaused(pChannels[i], true); + } +} + +void CTRFFDec::Stop() { + if (!pIsStreaming) { + return; + } + for (PD::u8 i = 0; i < pCurrentFile.GetNumChannels(); i++) { + ndspChnWaveBufClear(pChannels[i]); + pActiveChannels &= ~(1 << pChannels[i]); + } + pIsStreaming = false; + CleanUp(); +} + +void CTRFFDec::Stream() { + pCurrentTime = svcGetSystemTick(); + if (pCurrentTime - pLastTime >= 100000000 && pIsLoaded) { + if (pIsEnding) { + bool all_done = true; + for (PD::u32 buf = 0; buf < BufferCount; buf++) { + for (PD::u8 chn = 0; chn < pCurrentFile.GetNumChannels(); chn++) { + if (pWaveBuf[chn][buf].status != NDSP_WBUF_DONE) { + all_done = false; + break; + } + } + } + if (all_done) { + Stop(); + } + return; + } + if (!pIsStreaming) return; + if (!pIsPaused) pFillBuffers(); + pLastTime = pCurrentTime; + } +} + +void CTRFFDec::pFillBuffers() { + for (PD::u32 buf_idx = 0; buf_idx < BufferCount; buf_idx++) { + bool all_ready = true; + for (PD::u8 ch = 0; ch < pCurrentFile.GetNumChannels(); ch++) { + if (pWaveBuf[ch][buf_idx].status != NDSP_WBUF_DONE) { + all_ready = false; + break; + } + } + if (!all_ready) continue; + + if (pCurrentFile.IsLooping() && + pCurrentBlock == pCurrentFile.GetLoopEnd()) { + pCurrentBlock = pCurrentFile.GetLoopStart(); + pCurrentFile.ReadGotoBeginning(true); + } else if (!pCurrentFile.IsLooping() && + pCurrentBlock == pCurrentFile.GetLoopEnd()) { + pIsEnding = true; + return; + } + + for (PD::u8 chn_idx = 0; chn_idx < pCurrentFile.GetNumChannels(); + chn_idx++) { + ndspWaveBuf *buf = &pWaveBuf[chn_idx][buf_idx]; + + memset(buf, 0, sizeof(ndspWaveBuf)); + buf->data_adpcm = pBufferData.at(chn_idx).at(buf_idx).Data(); + pCurrentFile.ReadBlock(pCurrentBlock, (PD::u8 *)buf->data_adpcm); + DSP_FlushDataCache(buf->data_adpcm, pCurrentFile.GetBlockSize()); + + if (pCurrentBlock == 0) { + buf->adpcm_data = + (ndspAdpcmData *)&pCurrentFile.pDSP_ADPCM_Info[chn_idx].Context; + } else if (pCurrentBlock == pCurrentFile.GetLoopStart()) { + buf->adpcm_data = + (ndspAdpcmData *)&pCurrentFile.pDSP_ADPCM_Info[chn_idx].LoopContext; + } + + if (pCurrentBlock == pCurrentFile.GetNumBlocks() - 1) { + buf->nsamples = pCurrentFile.GetLastBlockSamples(); + } else { + buf->nsamples = pCurrentFile.GetBlockSamples(); + } + + ndspChnWaveBufAdd(pChannels[chn_idx], buf); + } + pCurrentBlock++; + } +} + +void CTRFFDec::CleanUp() { + Stop(); + pCurrentFile.CleanUp(); + pIsLoaded = false; + pIsStreaming = false; + pIsPaused = false; + pIsEnding = false; + pActiveChannels = 0; + pCurrentBlock = 0; + pChannels.Clear(); + pWaveBuf.clear(); + pBufferData.clear(); +} +} // namespace D7 \ No newline at end of file diff --git a/source/bcstm/ctrff_decode.hpp b/source/bcstm/ctrff_decode.hpp new file mode 100644 index 0000000..e9d7ca3 --- /dev/null +++ b/source/bcstm/ctrff_decode.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include <3ds.h> + +#include +#include +#include + +namespace D7 { +class CTRFFDec : public BCSTMPlayerBase { + public: + CTRFFDec() : BCSTMPlayerBase("CTRFF (WIP)", FormatBCSTM | AllChannels) { + CleanUp(); + }; + ~CTRFFDec() { CleanUp(); } + + void LoadFile(const std::string &path); + void Stream(); + + void CleanUp(); + + void Play(); + void Stop(); + void Pause(); + + /** INFO */ + inline bool IsLooping() { return pCurrentFile.IsLooping(); } + inline bool IsLoaded() { return pIsLoaded; } + inline unsigned int GetLoopStart() { return pCurrentFile.GetLoopStart(); } + inline unsigned int GetLoopEnd() { return pCurrentFile.GetLoopEnd(); } + inline unsigned int GetChannelCount() { + return pCurrentFile.GetNumChannels(); + } + inline unsigned int GetTotal() { return pCurrentFile.GetNumBlocks(); } + inline unsigned int GetCurrent() { return pCurrentBlock; } + inline unsigned int GetSampleRate() { return pCurrentFile.GetSampleRate(); } + inline unsigned int GetSamples() const { + return 0; // (block_size * num_blocks) / sample_rate; + } + inline unsigned int GetBlcokSize() const { + return pCurrentFile.GetBlockSize(); + } + inline unsigned int GetBlockSamples() const { + return pCurrentFile.GetBlockSamples(); + } + + void pFillBuffers(); + + /** Probably unused */ + static constexpr PD::u8 MaxChannels = 8; + static constexpr int BufferCount = 20; + ctrff::BCSTM pCurrentFile; + bool pIsLoaded = false; + bool pIsStreaming = false; + bool pIsPaused = false; + bool pIsEnding = false; + + PD::u64 pCurrentTime = 0; + PD::u64 pLastTime = 0; + + PD::u32 pCurrentBlock = 0; + PD::u32 pActiveChannels = 0; + PD::Vec pChannels; + std::vector> pWaveBuf; + std::vector>>> pBufferData; +}; +} // namespace D7 \ No newline at end of file diff --git a/source/bcstm_ctrl.hpp b/source/bcstm_ctrl.hpp new file mode 100644 index 0000000..55d0baa --- /dev/null +++ b/source/bcstm_ctrl.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +struct BCSTM_Ctrl { + enum ReqType { + OpenFile, /** Big Overhead so should be handled in a separate thread */ + CloseFile, /** No Overhead */ + Play, /** No visible overhead */ + Pause, /** Can be direct accessded due to no overhead */ + Stop, /** No Overhead */ + KillThread, /** Should be called on close */ + SwitchDec, /** Stops and switches decoder */ + }; + struct Request { + Request(ReqType t, const std::string& d) { + req = t; + req_dat = d; + } + ReqType req; + std::string req_dat; + }; + + void DoRequest(ReqType t, const std::string& dat = "") { + pRequests.PushBack(Request(t, dat)); + } + + D7::BCSTMPlayerBase* player = nullptr; + PD::List pRequests; + bool pFileLoaded = false; +}; + +extern BCSTM_Ctrl bcstm_ctrl; \ No newline at end of file diff --git a/source/common.cpp b/source/common.cpp deleted file mode 100644 index bb46b13..0000000 --- a/source/common.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include -#include - -namespace BP { -D7::BCSTM player; -bool playing = false; -std::string now_playing = ""; -bool romfs_is_mount = false; -Config config; -Update update_info; -bool checking_for_update = false; -std::string thiz_path; -bool hb_mode; -std::string Clock() { - const time_t ut = time(0); - bool h24 = config.is24h(); - bool ds = config.disp_seconds(); - auto ts = localtime(&ut); - if (!h24) { - int hr = ts->tm_hour % 12; - if (hr == 0) hr = 12; - if (!ds) { - return std::format("{}:{:02} {}", hr, ts->tm_min, - ts->tm_hour >= 12 ? "PM" : "AM"); - } - return std::format("{}:{:02}:{:02} {}", hr, ts->tm_min, ts->tm_sec, - ts->tm_hour >= 12 ? "PM" : "AM"); - } - if (!ds) { - return std::format("{}:{:02}", ts->tm_hour, ts->tm_min); - } - return std::format("{}:{:02}:{:02}", ts->tm_hour, ts->tm_min, ts->tm_sec); -} - -// TODO: Maybe include some safety checks... -void CheckForUpdate() { - // Dont create infinite tasks - if (checking_for_update) return; - PD::Tasks::Create([&]() { - checking_for_update = true; - update_info = Update(); - update_info.nightly = config.GetBool("use_nightly"); - std::string url; - if (update_info.nightly) { - url = "https://api.github.com/repos/NPI-D7/BCSTM-Player/commits"; - } else { - url = "https://api.github.com/repos/NPI-D7/BCSTM-Player/releases/latest"; - } - nlohmann::json js; - auto ret = PD::Net::JsonApiRequest(url, js); - if (ret) { - update_info = Update(); - checking_for_update = false; - return; - } - if (update_info.nightly) { - update_info.version = - js[0]["sha"].get_ref().substr(0, 7); - update_info.text = js[0]["commit"]["message"]; - update_info.valid = true; - if (update_info.version.compare(N_STRING) != 0) { - PD::PushMessage(PD::Lang::Get("UPDATER"), PD::Lang::Get("UPTFND")); - } - } else { - update_info.version = js["tag_name"]; - if (update_info.version[0] != 'v') { - update_info = Update(); - checking_for_update = false; - return; - } - update_info.text = js["body"]; - update_info.valid = true; - if (update_info.version.substr(1).compare(V_STRING) > 0) { - PD::PushMessage(PD::Lang::Get("UPDATER"), PD::Lang::Get("UPTFND")); - } - } - checking_for_update = false; - }); -} -} // namespace BP \ No newline at end of file diff --git a/source/common.hpp b/source/common.hpp deleted file mode 100644 index b781a0a..0000000 --- a/source/common.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#ifndef N_STRING -#define N_STRING "unknown" -#endif - -#ifndef V_STRING -#define V_STRING "unknown" -#endif - -namespace BP { -struct Update { - std::string version = ""; - bool nightly = false; - std::string text = ""; - bool valid = false; -}; -extern Update update_info; -extern D7::BCSTM player; -extern bool playing; -extern std::string now_playing; -extern bool romfs_is_mount; -extern Config config; -extern std::string thiz_path; -extern bool hb_mode; -std::string Clock(); -void CheckForUpdate(); -} // namespace BP \ No newline at end of file diff --git a/source/config.cpp b/source/config.cpp deleted file mode 100644 index 31b033f..0000000 --- a/source/config.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include - -void BP::Config::Load() { - if (!PD::FS::FileExist(PD::GetAppDirectory() + "/config.json")) { - make_new(); - return; - } - std::ifstream iff(PD::GetAppDirectory() + "/config.json", std::ios::in); - if (!iff.is_open()) { - make_new(); - return; - } - iff >> config; - iff.close(); - if (config.find("cfg_ver") == config.end()) { - make_new(); - return; - } - if (config["cfg_ver"].get() != config_version) { - make_new(); - return; - } - reload(); -} - -void BP::Config::Save() { - std::ofstream off(PD::GetAppDirectory() + "/config.json", std::ios::out); - off << config.dump(4); - off.close(); -} - -void BP::Config::make_new() { - config.clear(); - config["cfg_ver"] = config_version; - config["lang"] = PD::Lang::GetSys(); - config["clock"] = true; - config["24h"] = false; - config["disp_seconds"] = true; - config["romfs_browse"] = false; - config["search_updates"] = true; - config["use_nightly"] = false; - Save(); - reload(); -} - -void BP::Config::reload() { - m_cfg_ver = GetInt("cfg_ver"); - m_lang = GetString("lang"); - m_clock = GetBool("clock"); - m_24h = GetBool("24h"); - m_disp_seconds = GetBool("disp_seconds"); - m_romfs_browse = GetBool("romfs_browse"); - m_search_updates = GetBool("search_updates"); - m_use_nightly = GetBool("use_nightly"); -} \ No newline at end of file diff --git a/source/config.hpp b/source/config.hpp deleted file mode 100644 index d89008b..0000000 --- a/source/config.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include - -namespace BP { -class Config { - public: - Config() {} - ~Config() {} - void Load(); - void Save(); - - template - void Set(const std::string& key, const T& value) { - config[key] = value; - reload(); - } - - std::string GetString(const std::string& key) { - if (!config[key].is_string()) { - return ""; - } - return config[key].get(); - } - - bool GetBool(const std::string& key) { - if (!config[key].is_boolean()) { - return false; - } - return config[key].get(); - } - - int GetInt(const std::string& key) { - if (!config[key].is_number_integer()) { - return 0; - } - return config[key].get(); - } - - void Reset() { make_new(); } - - int cfg_ver() { return m_cfg_ver; } - std::string lang() { return m_lang; } - bool clock() { return m_clock; } - bool is24h() { return m_24h; } - bool disp_seconds() { return m_disp_seconds; } - bool romfs_browse() { return m_romfs_browse; } - bool search_updates() { return m_search_updates; } - bool use_nightly() { return m_use_nightly; } - - void reload(); - - private: - const int config_version = 1; - void make_new(); - nlohmann::json config; - int m_cfg_ver; - std::string m_lang; - bool m_fade; - bool m_rd7tf_theme; - bool m_clock; - bool m_24h; - bool m_disp_seconds; - bool m_romfs_browse; - bool m_search_updates; - bool m_use_nightly; -}; -} // namespace BP \ No newline at end of file diff --git a/source/cursor.hpp b/source/cursor.hpp new file mode 100644 index 0000000..6cfbd5f --- /dev/null +++ b/source/cursor.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include + +constexpr int elms_onscreen = 12; +constexpr PD::Color DesignerHeader = PD::Color(0xff111111); + +class Cursor { + public: + Cursor(fvec2 list_start, float entry_height) { + pListStart = list_start; + pEntryHeight = entry_height; + pCursor.From(pListStart).To(pListStart).As(pCursor.Linear).In(0.5f); + } + ~Cursor() = default; + + PD_SMART_CTOR(Cursor); + + int GetIndex() const { return pIndex; } + + void SetIndex(int idx) { + pIndex = idx; + UpdatePos(); + } + + void UpdatePos() { + pCursor.From(pCursor) + .To(fvec2(pListStart.x, pListStart.y + pEntryHeight * pIndex)) + .In(0.1f) + .As(pCursor.EaseInOutSine); + } + + void Update(float delta) { pCursor.Update(delta); } + + Cursor& operator++(int) { + pIndex++; + UpdatePos(); + return *this; + } + + Cursor& operator--(int) { + pIndex--; + UpdatePos(); + return *this; + } + + Cursor& operator+=(int num) { + pIndex += num; + UpdatePos(); + return *this; + } + + Cursor& operator-=(int num) { + pIndex -= num; + UpdatePos(); + return *this; + } + + operator fvec2() const { return pCursor.Get(); } + + PD::Tween pCursor; + fvec2 pListStart; + fvec2 pCursorSize; + float pEntryHeight; + int pIndex = 0; +}; \ No newline at end of file diff --git a/source/filebrowser.cpp b/source/filebrowser.cpp new file mode 100644 index 0000000..d4a679c --- /dev/null +++ b/source/filebrowser.cpp @@ -0,0 +1,180 @@ +#include +#include +#include + +void FileMgr::Update() { + delta.Update(); + cursor.Update(delta.GetSeconds() * 1000.f); + delta.Reset(); + + for (int i = 0; i < 12; i++) { + Top->Rect() + .SetPos(fvec2(0, 18 + 17 * i)) + .SetSize(fvec2(400, 17)) + .SetColor(((i % 2) == 0) ? PD::Color("#222222aa") + : PD::Color("#333333aa")); + } + if (pShowHelp) { + Top->Text( + "Controls:\nUp -> Go 1 Entry Up\nDown -> Go 1 Entry Down\nLeft -> " + "Go 5 " + "Entries Up\nRight -> Go 5 Entries Down\nA -> Go into Directory / " + "Play " + "BCSTM File\nB -> Go Back\nX -> Open .bcstm File in File " + "Inspector\nStart -> Exit App") + .SetPos(fvec2(5, 18)) + .SetColor(PD::Colors::White); + } else { + Top->Rect() + .SetPos(cursor) + .SetSize(fvec2(400, 17)) + .SetColor(PD::Color("#222222cc")); + for (int i = 0; i < int(list.size() > 12 ? 12 : list.size()); i++) { + Top->Text(list[sp + i].Name) + .SetPos(fvec2(5, 18 + 17 * i)) + .SetColor(PD::Colors::White); + } + } + Top->Rect().SetColor(DesignerHeader).SetPos(0).SetSize(fvec2(400, 18)); + Top->Text("BCSTM-Player -> Filebrowser") + .SetPos(fvec2(5, 1)) + .SetColor(PD::Colors::White); + Top->Rect() + .SetPos(fvec2(0, 222)) + .SetSize(fvec2(400, 18)) + .SetColor(DesignerHeader); + Top->Text(cPath).SetPos(fvec2(5, 223)).SetColor(PD::Colors::White); + if (list.size() > 12) { + float rect_h = (12.f / (float)list.size()) * 204.f; + /** Make sure the rect is still visible */ + rect_h = std::clamp(rect_h, 10.f, 204.f); + float rect_pos = + 18.f + ((float)sp / (float)(list.size() - 12)) * (204.f - rect_h); + Top->Rect() + .SetPos(fvec2(396, rect_pos)) + .SetSize(fvec2(4, rect_h)) + .SetColor(PD::Colors::DarkGray); + } + if (!pShowHelp) { + if (Inp->IsUp(Inp->Down) && sp + cursor.pIndex < (int)list.size() - 1) { + if (cursor.pIndex == 11) { + sp++; + } else { + cursor++; + } + } + if (Inp->IsUp(Inp->Right) && sp + cursor.pIndex + 5 < (int)list.size()) { + if (cursor.pIndex == 11) { + sp += 5; + } else { + if (cursor.pIndex + 5 > 11) { + sp += (cursor.pIndex - 11 + 5); + cursor.SetIndex(11); + } else { + cursor += 5; + } + } + } + if (Inp->IsUp(Inp->Up) && cursor.pIndex + sp > 0) { + if (cursor.pIndex == 0) { + sp--; + } else { + cursor--; + } + } + if (Inp->IsUp(Inp->Left) && sp + cursor.pIndex - 5 >= 0) { + if (cursor.pIndex == 0) { + sp -= 5; + } else { + if (cursor.pIndex - 5 < 0) { + sp -= 5 - cursor.pIndex; + cursor.SetIndex(0); + } else { + cursor -= 5; + } + } + } + + if (Inp->IsDown(Inp->A)) { + auto FSE = list[cursor.pIndex + sp]; + if (FSE.Dir) { + pLastPos.Push(PD::Pair(sp, cursor.GetIndex())); + cursor.SetIndex(0); + sp = 0; + ScanDir(FSE.Path); + } else if (FSE.Name.find(".bcstm") != FSE.Name.npos) { + bcstm_ctrl.DoRequest(bcstm_ctrl.Stop); + bcstm_ctrl.DoRequest(bcstm_ctrl.OpenFile, FSE.Path); + bcstm_ctrl.DoRequest(bcstm_ctrl.Play); + } + } + + if (Inp->IsDown(Inp->B)) { + if (cPath != "sdmc:/") { + cPath = std::filesystem::path(cPath).parent_path().string(); + if (cPath == "sdmc:") { + cPath += "/"; + } + if (!pLastPos.IsEmpty()) { + sp = pLastPos.Top().First; + cursor.SetIndex(pLastPos.Top().Second); + pLastPos.Pop(); + } else { + sp = 0; + cursor.SetIndex(0); + } + ScanDir(cPath); + } + } + + if (Inp->IsDown(Inp->X)) { + auto FSE = list[cursor.pIndex + sp]; + if (FSE.Name.find(".bcstm") != FSE.Name.npos) { + FileInspector->ReadFile(FSE.Path); + Goto(FileInspector); + } + } + + if (Inp->IsDown(Inp->L)) { + Goto(Settings); + } + } + + pShowHelp = Inp->IsHeld(Inp->Select); +} + +void FileMgr::SortList(std::vector& l) { + std::sort(l.begin(), l.end(), [](FSEntry& a, FSEntry& b) -> bool { + if (!a.Dir && b.Dir) { + return false; + } + if (a.Dir && !b.Dir) { + return true; + } + std::string la = a.Name; + std::string lb = b.Name; + std::transform(la.begin(), la.end(), la.begin(), + [](char c) { return std::tolower(c); }); + std::transform(lb.begin(), lb.end(), lb.begin(), + [](char c) { return std::tolower(c); }); + return la.compare(lb) < 0; + }); +} + +void FileMgr::BackgroundListUpdate(std::vector& l, + const std::string& path) { + l.clear(); + for (auto& it : std::filesystem::directory_iterator(path)) { + l.push_back( + {it.is_directory(), it.path().string(), it.path().filename().string()}); + std::this_thread::sleep_for(std::chrono::microseconds(5)); + } + SortList(l); +} + +void FileMgr::ScanDir(const std::string& path) { + cPath = path; + BackgroundListUpdate(list, path); + // std::thread tmp([&]() { BackgroundListUpdate(list, path); }); + // tmp.detach(); +} \ No newline at end of file diff --git a/source/filebrowser.hpp b/source/filebrowser.hpp new file mode 100644 index 0000000..19b0cc5 --- /dev/null +++ b/source/filebrowser.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include + +class FileMgr : public Stage { + public: + FileMgr(PD::Hid::Ref inp, PD::LI::Texture::Ref wp, PD::LI::Font::Ref f) + : Stage(inp, wp, f) { + ScanDir("sdmc:/"); + } + ~FileMgr() = default; + PD_SMART_CTOR(FileMgr) + + Cursor cursor = Cursor(fvec2(0.f, 18.f), 17.f); + PD::Stack> pLastPos; + int sp = 0; + PD::Timer delta; + + void Update() override; + + struct FSEntry { + bool Dir; + std::string Path; + std::string Name; + }; + std::vector list; + std::string cPath; + bool pShowHelp = false; + + static void SortList(std::vector& l); + static void BackgroundListUpdate(std::vector& l, + const std::string& path); + void ScanDir(const std::string& path); +}; \ No newline at end of file diff --git a/source/flex/container.hpp b/source/flex/container.hpp new file mode 100644 index 0000000..6bfc0fe --- /dev/null +++ b/source/flex/container.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace PD { +namespace Flex { +class Container { + public: + Container() {} + ~Container() = default; + PD_SMART_CTOR(Container) + + virtual void Draw(PD::LI::DrawList::Ref l) const {} +}; +} // namespace Flex +} // namespace PD \ No newline at end of file diff --git a/gfx/.gitkeep b/source/flex/flex.cpp similarity index 100% rename from gfx/.gitkeep rename to source/flex/flex.cpp diff --git a/source/flex/flex.hpp b/source/flex/flex.hpp new file mode 100644 index 0000000..86c99a9 --- /dev/null +++ b/source/flex/flex.hpp @@ -0,0 +1,77 @@ +#pragma once +#include +#include +#include + +/** Another UI Library (worst one ive created) */ + +namespace PD { +namespace Flex { +class Context { + public: + Context(PD::LI::Texture::Ref white, PD::LI::Font::Ref font, + PD::Hid::Ref inp) { + pDrawList = PD::LI::DrawList::New(white); + pFont = font; + pInp = inp; + pDrawList->SetFont(pFont); + } + ~Context() {} + PD_SMART_CTOR(Context) + + void DirectAccessDraw(std::function f) { + if (f) { + f(pDrawList); + } + } + + Flex::Context& operator<<(Rect::Ref r) { + pObjects.Add(r); + return *this; + } + + Flex::Button& Button(const std::string& name) { + auto btn = Button::New(); + btn->pName = name; + pObjects.Add(btn); + return *(btn.get()); + } + + Flex::Rect& Rect() { + auto o = Rect::New(); + pObjects.Add(o); + return *(o.get()); + } + + Flex::Triangle& Triangle() { + auto o = Triangle::New(); + pObjects.Add(o); + return *(o.get()); + } + + Flex::Image& Image(PD::LI::Texture::Ref tex = nullptr) { + auto o = Image::New(); + pObjects.Add(o); + return o->SetImage(tex); + } + + Flex::Text& Text(const std::string& text = "") { + auto o = Text::New(); + pObjects.Add(o); + return o->SetText(text); + } + + void Update() { + for (auto it : pObjects) { + it->Draw(pDrawList); + } + pObjects.Clear(); + } + + PD::LI::DrawList::Ref pDrawList; + PD::Hid::Ref pInp; + PD::LI::Font::Ref pFont; + PD::Vec pObjects; +}; +} // namespace Flex +} // namespace PD \ No newline at end of file diff --git a/source/flex/objects.cpp b/source/flex/objects.cpp new file mode 100644 index 0000000..329c210 --- /dev/null +++ b/source/flex/objects.cpp @@ -0,0 +1,29 @@ +#include + +namespace PD { +namespace Flex { +void Rect::Draw(PD::LI::DrawList::Ref l) const { + l->DrawSolid(); + l->DrawRectFilled(pPos, pSize, pColor); +} +void Triangle::Draw(PD::LI::DrawList::Ref l) const { + l->DrawSolid(); + l->DrawTriangleFilled(pPosA, pPosB, pPosC, pColor); +} +void Text::Draw(PD::LI::DrawList::Ref l) const { + l->DrawText(pPos, pText, pColor); +} +void Image::Draw(PD::LI::DrawList::Ref l) const { + ivec2 size = pTex->GetSize(); + if (pSize != fvec2(-1.f, -1.f)) { + size = ivec2(pSize.x, pSize.y); + } + l->DrawTexture(pTex); + l->DrawRectFilled(pPos, fvec2(size.x, size.y), pColor); +} +void Button::Draw(PD::LI::DrawList::Ref l) const { + l->DrawRectFilled(pPos, 40, PD::Colors::Gray); + l->DrawText(pPos + 2, pName, PD::Colors::White); +} +} // namespace Flex +} // namespace PD \ No newline at end of file diff --git a/source/flex/objects.hpp b/source/flex/objects.hpp new file mode 100644 index 0000000..6d1d19c --- /dev/null +++ b/source/flex/objects.hpp @@ -0,0 +1,169 @@ +#pragma once + +#include + +/** Header Containing Simple Objects */ + +namespace PD { +namespace Flex { +class Rect : public Container { + public: + Rect() {} + Rect(fvec2 p, fvec2 s, u32 clr) { + pPos = p; + pSize = s; + pColor = clr; + } + ~Rect() = default; + PD_SMART_CTOR(Rect) + + Rect& SetPos(fvec2 p) { + pPos = p; + return *this; + } + Rect& SetSize(fvec2 s) { + pSize = s; + return *this; + } + Rect& SetColor(u32 c) { + pColor = c; + return *this; + } + + fvec2 GetPos() const { return pPos; } + fvec2 GetSize() const { return pSize; } + u32 GetColor() const { return pColor; } + + void Draw(PD::LI::DrawList::Ref l) const override; + + fvec2 pPos; + fvec2 pSize; + u32 pColor; +}; + +class Triangle : public Container { + public: + Triangle() {} + ~Triangle() = default; + PD_SMART_CTOR(Triangle) + + Triangle& SetPosA(fvec2 p) { + pPosA = p; + return *this; + } + Triangle& SetPosB(fvec2 s) { + pPosB = s; + return *this; + } + Triangle& SetPosC(fvec2 s) { + pPosC = s; + return *this; + } + Triangle& SetColor(u32 c) { + pColor = c; + return *this; + } + + fvec2 GetPosA() const { return pPosA; } + fvec2 GetPosB() const { return pPosB; } + fvec2 GetPosC() const { return pPosC; } + u32 GetColor() const { return pColor; } + + void Draw(PD::LI::DrawList::Ref l) const override; + + fvec2 pPosA; + fvec2 pPosB; + fvec2 pPosC; + u32 pColor; +}; + +class Button : public Container { + public: + Button() {} + ~Button() {} + PD_SMART_CTOR(Button) + + void Draw(PD::LI::DrawList::Ref l) const override; + + Button& OnPress(std::function f) { + pOnPress = f; + return *this; + } + + fvec2 pPos; + std::string pName; + std::function pOnPress; +}; + +class Text : public Container { + public: + Text() {} + ~Text() = default; + PD_SMART_CTOR(Text) + + Text& SetPos(fvec2 p) { + pPos = p; + return *this; + } + Text& SetBoxSize(fvec2 s) { + pSize = s; + return *this; + } + Text& SetColor(u32 c) { + pColor = c; + return *this; + } + Text& SetText(const std::string& text) { + pText = text; + return *this; + } + + fvec2 GetPos() const { return pPos; } + fvec2 GetBoxSize() const { return pSize; } + u32 GetColor() const { return pColor; } + const std::string& GetText() const { return pText; } + + void Draw(PD::LI::DrawList::Ref l) const override; + + std::string pText; + fvec2 pPos; + fvec2 pSize; + u32 pColor; +}; + +class Image : public Container { + public: + Image() {} + ~Image() = default; + PD_SMART_CTOR(Image) + + Image& SetPos(fvec2 p) { + pPos = p; + return *this; + } + Image& SetSize(fvec2 s) { + pSize = s; + return *this; + } + Image& SetColor(u32 c) { + pColor = c; + return *this; + } + Image& SetImage(PD::LI::Texture::Ref t) { + pTex = t; + return *this; + } + fvec2 GetPos() const { return pPos; } + fvec2 GetSize() const { return pSize; } + u32 GetColor() const { return pColor; } + PD::LI::Texture::Ref GetImage() const { return pTex; } + + void Draw(PD::LI::DrawList::Ref l) const override; + + PD::LI::Texture::Ref pTex; + fvec2 pPos; + fvec2 pSize = fvec2(-1.f, -1.f); + u32 pColor = 0xffffffff; +}; +} // namespace Flex +} // namespace PD \ No newline at end of file diff --git a/source/inspector_view.cpp b/source/inspector_view.cpp new file mode 100644 index 0000000..b90a07b --- /dev/null +++ b/source/inspector_view.cpp @@ -0,0 +1,389 @@ +#include +#include + +void Inspector::Update() { + delta.Update(); + cursor.Update(delta.GetSeconds() * 1000.f); + delta.Reset(); + + for (int i = 0; i < 12; i++) { + Top->Rect() + .SetPos(fvec2(0, 18 + 17 * i)) + .SetSize(fvec2(200, 17)) + .SetColor(((i % 2) == 0) ? PD::Color("#222222aa") + : PD::Color("#333333aa")); + Top->Rect() + .SetPos(fvec2(200, 18 + 17 * i)) + .SetSize(fvec2(200, 17)) + .SetColor(((i % 2) != 0) ? PD::Color("#222222aa") + : PD::Color("#333333aa")); + } + Top->Rect() + .SetPos(cursor) + .SetSize(fvec2(400, 17)) + .SetColor(PD::Color("#222222cc")); + for (int i = 0; i < int(pDL.size() > 12 ? 12 : pDL.size()); i++) { + Top->Text(pDL.at(sp + i)->First) + .SetPos(fvec2(5, 18 + 17 * i)) + .SetColor(PD::Colors::White); + Top->Text(pDL.at(sp + i)->Second) + .SetPos(fvec2(205, 18 + 17 * i)) + .SetColor(PD::Colors::White); + } + Top->Rect().SetColor(DesignerHeader).SetPos(0).SetSize(fvec2(400, 18)); + Top->Text("BCSTM-Player -> File Inspector") + .SetPos(fvec2(5, 1)) + .SetColor(PD::Colors::White); + Top->Rect() + .SetPos(fvec2(0, 222)) + .SetSize(fvec2(400, 18)) + .SetColor(DesignerHeader); + /** Only use Filename due to no space */ + Top->Text(std::filesystem::path(pPath).filename().string()) + .SetPos(fvec2(5, 223)) + .SetColor(PD::Colors::White); + if (pDL.size() > 12) { + float rect_h = (12.f / (float)pDL.size()) * 204.f; + /** Make sure the rect is still visible */ + rect_h = std::clamp(rect_h, 10.f, 204.f); + float rect_pos = + 18.f + ((float)sp / (float)(pDL.size() - 12)) * (204.f - rect_h); + Top->Rect() + .SetPos(fvec2(396, rect_pos)) + .SetSize(fvec2(4, rect_h)) + .SetColor(PD::Colors::DarkGray); + } + + if (Inp->IsUp(Inp->Down) && sp + cursor.pIndex < (int)pDL.size() - 1) { + if (cursor.pIndex == 11) { + sp++; + } else { + cursor++; + } + } + if (Inp->IsUp(Inp->Right) && sp + cursor.pIndex + 5 < (int)pDL.size()) { + if (cursor.pIndex == 11) { + sp += 5; + } else { + if (cursor.pIndex + 5 > 11) { + sp += (cursor.pIndex - 11 + 5); + cursor.SetIndex(11); + } else { + cursor += 5; + } + } + } + if (Inp->IsUp(Inp->Up) && cursor.pIndex + sp > 0) { + if (cursor.pIndex == 0) { + sp--; + } else { + cursor--; + } + } + if (Inp->IsUp(Inp->Left) && sp + cursor.pIndex - 5 >= 0) { + if (cursor.pIndex == 0) { + sp -= 5; + } else { + if (cursor.pIndex - 5 < 0) { + sp -= 5 - cursor.pIndex; + cursor.SetIndex(0); + } else { + cursor -= 5; + } + } + } + + if (Inp->IsDown(Inp->A)) { + auto FSE = pDL.at(cursor.pIndex + sp); + if (FSE->SubData.size() != 0) { + pLastPos.Push(PD::Pair(sp, cursor.GetIndex())); + cursor.SetIndex(0); + sp = 0; + pStack.Push(pDL); + pDL = FSE->SubData; + } + } + + if (Inp->IsDown(Inp->B)) { + if (!pLastPos.IsEmpty()) { + sp = pLastPos.Top().First; + cursor.SetIndex(pLastPos.Top().Second); + pLastPos.Pop(); + } else { + sp = 0; + cursor.SetIndex(0); + } + if (pStack.IsEmpty()) { + Back(); + } else { + pDL = pStack.Top(); + pStack.Pop(); + } + } +} + +/** MagicStr crates a string of the memory (unsafe) */ +std::string MagicStr(PD::u32 v) { + return std::format("0x{:08X} -> {}", v, std::string((char*)&v, 4)); +} + +/** Simply oneliner to fix stringstream problem with char values */ +std::string _8Str(const PD::u8& v) { return std::format("0x{:02X}", v); } + +/** Universal Type to hex string func */ +template +std::string _Str(const T& v) { + if (sizeof(T) == 1) { + return _8Str(v); + } + std::stringstream s; + s << "0x" << std::uppercase << std::hex << std::setw(sizeof(T)) + << std::setfill('0') << v; + return s.str(); +} + +/** Hex String + Value as integer */ +template +std::string _vStr(const T& v) { + return std::format("{} -> {}", _Str(v), (PD::u32)v); +} + +/** Type Value String like Endianness2String */ +template +std::string _fStr(const T& v, std::function f) { + return std::format("{} -> {}", _Str(v), f(v)); +} + +/** + * Format Bytes value + * does not work with every type but dont care cause its internals + */ +template +std::string _bStr(const T& v) { + return std::format("{} -> {}", _Str(v), PD::Strings::FormatBytes(v)); +} + +/** true false str */ +template +std::string _tStr(const T& v) { + return std::format("{} -> {}", _Str(v), (bool)v); +} + +/** Wrapper for Inspector::MakeEntry */ +Inspector::TabEntry::Ref _me(const std::string& f, const std::string& e) { + return Inspector::MakeEntry(f, e); +} + +Inspector::TabEntry::Ref MakeRef(ctrff::BCSTM::Reference& b, + const std::string& name = "") { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + Lst.push_back(_me("TypeID", _fStr( + (ctrff::BCSTM::ReferenceTypes)b.TypeID, + ctrff::BCSTM::ReferenceType2String))); + Lst.push_back(_me("Padding", _Str(b.Padding))); + Lst.push_back(_me("Offset", _Str(b.Offset))); + e->First = "Referene"; + e->Second = name; + e->SubData = Lst; + return e; +} + +Inspector::TabEntry::Ref MakeSizedRef(ctrff::BCSTM::SizedReference& b, + const std::string& name = "") { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + Lst.push_back(MakeRef(b.Ref)); + Lst.push_back(_me("Size", _vStr(b.Size))); + e->First = "SizedRef"; + e->Second = name; + e->SubData = Lst; + return e; +} + +Inspector::TabEntry::Ref MakeRefListVec(std::vector& b, + const std::string& name = "") { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + for (size_t i = 0; i < b.size(); i++) { + Lst.push_back(MakeRef(b[i], "Entry " + std::to_string(i))); + } + e->First = "Reference List"; + e->Second = name; + e->SubData = Lst; + return e; +} + +Inspector::TabEntry::Ref MakeRefTable(ctrff::BCSTM::ReferenceTable& b, + const std::string& name = "") { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + Lst.push_back(_me("Num References", _vStr(b.Count))); + Lst.push_back(MakeRefListVec(b.Refs)); + e->First = "Reference Table"; + e->Second = name; + e->SubData = Lst; + return e; +} + +Inspector::TabEntry::Ref MakeBlockHdr(ctrff::BCSTM::BlockHeader& b, + const std::string& name = "") { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + Lst.push_back(_me("Magic", MagicStr(b.Magic))); + Lst.push_back(_me("Size", _vStr(b.Size))); + e->First = "Block Header"; + e->Second = name; + e->SubData = Lst; + return e; +} + +Inspector::TabEntry::Ref MakeStreamInfo(ctrff::BCSTM::StreamInfo& b, + const std::string& name = "") { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + Lst.push_back(_me("Encoding", _fStr( + (ctrff::BCSTM::Encoding)b.Encoding, + ctrff::BCSTM::Encoding2String))); + Lst.push_back(_me("Loop", _tStr(b.Loop))); + Lst.push_back(_me("Channels", _vStr(b.ChannelCount))); + Lst.push_back(_me("Padding", _Str(b.Padding))); + Lst.push_back(_me("Sample Rate", _vStr(b.SampleRate))); + Lst.push_back(_me("Loop Start", _vStr(b.LoopStartFrame))); + Lst.push_back(_me("Loop End", _vStr(b.LoopEndFrame))); + Lst.push_back(_me("Sample Blocks", _vStr(b.SampleBlockNum))); + Lst.push_back(_me("Sample Block Size", _vStr(b.SampleBlockSize))); + Lst.push_back(_me("Sample Block Samples", _vStr(b.SampleBlockSampleNum))); + Lst.push_back(_me("Last Sample Block Size", _vStr(b.LastSampleBlockSize))); + Lst.push_back( + _me("Last Sample Block Samples", _vStr(b.LastSampleBlockSampleNum))); + Lst.push_back( + _me("Last Sample Block Padded Size", _vStr(b.LastSampleBlockPaddedSize))); + Lst.push_back(_me("Seek Data Size", _vStr(b.SeekDataSize))); + Lst.push_back(_me("Seek Interval Samples", _vStr(b.SeekIntervalSampleNum))); + Lst.push_back(MakeRef(b.SampleDataRef, "Sample Data")); + e->First = "Stream Info"; + e->Second = name; + e->SubData = Lst; + return e; +} + +Inspector::TabEntry::Ref MakeInfoBlock(ctrff::BCSTM::InfoBlock& b, + const std::string& name = "") { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + Lst.push_back(MakeBlockHdr(b.Header)); + Lst.push_back(MakeRef(b.StreamInfoRef, "Stream Info")); + Lst.push_back(MakeRef(b.TrackInfoTabRef, "Track Info Ref Table")); + Lst.push_back(MakeRef(b.ChannelInfoTabRef, "Channel Info Ref Table")); + Lst.push_back(MakeStreamInfo(b.StreamInfo)); + Lst.push_back(MakeRefTable(b.TrackInfoTab, "Track Info Table")); + Lst.push_back(MakeRefTable(b.ChannelInfoTab, "Channel Info Table")); + Lst.push_back(MakeRefListVec(b.ChannelInfoRefs, "Channel Info References")); + e->First = "Info Block"; + e->Second = name; + e->SubData = Lst; + return e; +} + +Inspector::TabEntry::Ref MakeDspAdpcmParam(ctrff::BCSTM::DSP_ADPCM_Param& b, + const std::string& name = "") { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + /** This code probably generates wrong information + * but to lazy to look on that yet + */ + for (int i = 0; i < 0x10; i += 4) { + std::string res; + for (int j = 0; j < 4; j++) { + res += _Str(b.Coefficients[i + j]) + " "; + } + Lst.push_back(_me(std::to_string(i) + " - " + std::to_string(i + 3), res)); + } + e->First = "DSP ADPCM Param"; + e->Second = name; + e->SubData = Lst; + return e; +} + +Inspector::TabEntry::Ref MakeDspAdpcmContext(ctrff::BCSTM::DSP_ADPCM_Context& b, + const std::string& name = "") { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + Lst.push_back(_me("Predictor / Scale", _Str(b.PredictorScale))); + Lst.push_back(_me("Reserved", _Str(b.Reserved))); + Lst.push_back(_me("Previous Sample", _vStr(b.PreviousSample))); + Lst.push_back(_me("Second Previous Sample", _vStr(b.SecondPreviousSample))); + e->First = "DSP ADPCM Context"; + e->Second = name; + e->SubData = Lst; + return e; +} + +Inspector::TabEntry::Ref MakeDspAdpcmInfo(ctrff::BCSTM::DSP_ADPCM_Info& b, + const std::string& name = "") { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + Lst.push_back(MakeDspAdpcmParam(b.Param, "Param")); + Lst.push_back(MakeDspAdpcmContext(b.Context, "Context")); + Lst.push_back(MakeDspAdpcmContext(b.LoopContext, "Loop Context")); + Lst.push_back(_me("Padding", _Str(b.Padding))); + e->First = "DSP ADPCM Info"; + e->Second = name; + e->SubData = Lst; + return e; +} + +Inspector::TabEntry::Ref MakeDspAdpcmInfoList( + std::vector& b, + const std::string& name = "") { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + for (size_t i = 0; i < b.size(); i++) { + Lst.push_back(MakeDspAdpcmInfo(b[i], "Entry " + std::to_string(i))); + } + e->First = "DSP ADPCM Info List"; + e->Second = name; + e->SubData = Lst; + return e; +} + +Inspector::TabEntry::Ref MakeHeader(ctrff::BCSTM& b) { + auto e = Inspector::TabEntry::New(); + std::vector Lst; + Lst.push_back(_me("Magic", MagicStr(b.pHeader.Magic))); + Lst.push_back( + _me("Endianness", _fStr( + (ctrff::BCSTM::Endianness)b.pHeader.Endianness, + ctrff::BCSTM::Endianness2String))); + Lst.push_back(_me("HeaderSize", _vStr(b.pHeader.HeaderSize))); + Lst.push_back(_me("Version", _vStr(b.pHeader.Version))); + Lst.push_back(_me("FileSize", _bStr(b.pHeader.FileSize))); + Lst.push_back(_me("NumBlocks", _vStr(b.pHeader.NumBlocks))); + Lst.push_back(_me("Reserved", _Str(b.pHeader.Reserved))); + Lst.push_back(MakeSizedRef(b.pInfoBlockRef, "Info Block")); + Lst.push_back(MakeSizedRef(b.pSeekBlockRef, "Seek Block")); + Lst.push_back(MakeSizedRef(b.pDataBlockRef, "Data Block")); + e->First = "Header"; + e->SubData = Lst; + return e; +} + +void Inspector::ReadFile(const std::string& path) { + List.clear(); + pPath = path; + ctrff::BCSTM b; + b.LoadFile(path); + List.push_back(MakeHeader(b)); + List.push_back(MakeInfoBlock(b.pInfoBlock)); + List.push_back(MakeDspAdpcmInfoList(b.pDSP_ADPCM_Info)); + pDL = List; +} + +Inspector::TabEntry::Ref Inspector::MakeEntry(const std::string& id, + const std::string& val) { + auto e = TabEntry::New(); + e->First = id; + e->Second = val; + return e; +} \ No newline at end of file diff --git a/source/inspector_view.hpp b/source/inspector_view.hpp new file mode 100644 index 0000000..854b3be --- /dev/null +++ b/source/inspector_view.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +class Inspector : public Stage { + public: + Inspector(PD::Hid::Ref inp, PD::LI::Texture::Ref wp, PD::LI::Font::Ref f) + : Stage(inp, wp, f) {} + ~Inspector() = default; + PD_SMART_CTOR(Inspector) + + Cursor cursor = Cursor(fvec2(0.f, 18.f), 17.f); + int sp = 0; + PD::Timer delta; + + void Update() override; + + void ReadFile(const std::string& path); + + struct TabEntry { + TabEntry() {} + PD_SMART_CTOR(TabEntry); + + std::string First; + std::string Second; + std::vector SubData; + }; + + static TabEntry::Ref MakeEntry(const std::string& id, const std::string& val); + + /** THIS System of navigation is very memory inefficient btw */ + std::vector pDL; + std::vector List; + PD::Stack> pStack; + PD::Stack> pLastPos; + std::string pPath; +}; \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp index f3838bd..03978ed 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,34 +1,186 @@ -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -void Bcstm_Loop() { +/** + * Code is very unstable currently cause + * 1. PD Ctr Extension is unfinished pre alpha + * 2. the flex ui engine is a small lib planned to be more simple then ui7 but + * the way it works is bad + * 3. The whole code around the stage_mgr is just in alpha state + * 4. The whole code of bcstm_player 2.0.0 is completly hardcoded with extern + * references through all the code and still very unfinished + */ + +float Offset(float x) { + float y = cos(x) * 42; + return y - floor(y); +} + +void Append(PD::LI::DrawList::Ref l, int index, fvec2 position, fvec2 size, + float time) { + float offset = Offset(index) * 62; + float x_position = + position.x + size.x / 8 * ((index % 11) - 1) + cos(offset + time) * 10; + float y_position = position.y + size.y / 8 * (index / 11) + 40 + + sin(offset + time) * 10 + 30; + float color_effect = 1 - exp(-(index / 11) / 3.0f); + + l->DrawTriangleFilled( + fvec2(x_position, y_position), fvec2(x_position + 300, y_position + (90)), + fvec2(x_position - 300, y_position + (90)), + PD::Color(.94f - .17f * color_effect, .61f - .25f * color_effect, + .36f + .38f * color_effect)); +} + +void BCSTM_Handler(BCSTM_Ctrl* ctrl) { + ctrl->player = new D7::BCSTM2; // Stable while (true) { - if (BP::player.IsLoaded()) BP::player.Update(); - PD::Thread::sleep(1 * 10); + if (ctrl->pFileLoaded) { + ctrl->player->Stream(); + } + if (ctrl->pRequests.Size() == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } else { + auto t = ctrl->pRequests.Front(); + if (t.req == BCSTM_Ctrl::OpenFile) { +#if __EXCEPTIONS + try { + ctrl->player->LoadFile(t.req_dat); + /** + * Set to true cause it would get set to false + * if an error got thrown + */ + ctrl->pFileLoaded = true; + } catch (const std::exception& e) { + std::cout << "BCSTM CTRL Error: " << e.what() << std::endl; + ctrl->pFileLoaded = false; + } +#else + ctrl->player->LoadFile(t.req_dat); + ctrl->pFileLoaded = true; +#endif + } else if (t.req == BCSTM_Ctrl::CloseFile) { + ctrl->player->Stop(); + } else if (t.req == BCSTM_Ctrl::Stop) { + ctrl->player->Stop(); + } else if (t.req == BCSTM_Ctrl::Play) { + ctrl->player->Play(); + } else if (t.req == BCSTM_Ctrl::Pause) { + ctrl->player->Pause(); + } else if (t.req == BCSTM_Ctrl::KillThread) { + break; /** Break the loop */ + } else if (t.req == BCSTM_Ctrl::SwitchDec) { + ctrl->player->Stop(); +#ifdef __RTTI + bool v2 = dynamic_cast(ctrl->player); +#else + bool v2 = ctrl->player->GetName() == "BCSTMV2"; +#endif + delete ctrl->player; + if (v2) { + ctrl->player = new D7::CTRFFDec(); + } else { + ctrl->player = new D7::BCSTM2(); + } + } + ctrl->pRequests.PopFront(); + } + } +} + +FileMgr::Ref Filebrowser; +Inspector::Ref FileInspector; +Settings::Ref Settings; +BCSTM_Ctrl bcstm_ctrl; + +void BottomScreenBeta(PD::LI::DrawList::Ref l) { + l->DrawRectFilled(0, fvec2(320, 240), 0xff222222); + l->DrawRectFilled(5, fvec2(310, 20), 0xff111111); + l->DrawRectFilled(7, fvec2(306, 16), 0xff222222); + float scale = 0.f; + + if (bcstm_ctrl.player->GetTotal() != 0) { + scale = (float)bcstm_ctrl.player->GetCurrent() / + (float)bcstm_ctrl.player->GetTotal(); } - BP::player.Stop(); + l->DrawRectFilled(7, fvec2(306 * scale, 16), 0xff00ff00); + l->DrawText( + PD::fvec2(7, 28), + std::format( + "Info:\n Block Pos: {}/{}\n Sample Rate: {}\n Loop: {}\n " + "Loop Start: " + "{}\n Loop End: {}\n Decoder: {}", + bcstm_ctrl.player->GetCurrent(), bcstm_ctrl.player->GetTotal(), + bcstm_ctrl.player->GetSampleRate(), bcstm_ctrl.player->IsLooping(), + bcstm_ctrl.player->GetLoopStart(), bcstm_ctrl.player->GetLoopEnd(), + bcstm_ctrl.player->GetName()), + 0xffffffff); } -int main(int argc, char* argv[]) { - d7mc_load_icons = false; - PD::Init::Main("BCSTM-Player"); - BP::thiz_path = argc == 0 ? "sdmc:/3ds/BCSTM-Player.3dsx" : argv[0]; - BP::hb_mode = argc != 0; - PD::Init::NdspFirm(); - BP::config.Load(); - PD::Lang::Load(BP::config.lang()); - aptSetSleepAllowed(false); - // Enable LRS as the Systemfont Renders Faster with it - PD::LI::GetFeatures() |= PDLithiumFlags_LRS; - - PD::Tasks::Create(Bcstm_Loop); - if (BP::config.search_updates()) BP::CheckForUpdate(); - BP::player.Stop(); // for ui - PD::Scene::Load(std::make_unique()); - - while (PD::MainLoop()) { - PD::FrameEnd(); +int main() { + PD::Ctr::EnableExceptionScreen(); + PD::Ctr::CreateContext(); + aptSetSleepAllowed(true); + auto ret = ndspInit(); + if (R_FAILED(ret)) { + throw std::runtime_error( + "ndspfirm.cdc was not found!\n\nNote: You can dump the file " + "in\n\nRosalina Menu\n Miscellaneous Options\n Dump DSP " + "Firmware\n\nThen " + "Restart this App."); + } + std::thread bcstm_player(BCSTM_Handler, &bcstm_ctrl); + auto font = PD::LI::Font::New(PD::Ctr::GetContext().Gfx); + font->LoadTTF("romfs:/fonts/ComicNeue.ttf"); + auto rl2 = PD::LI::DrawList::New(PD::Ctr::GetWhiteTex()); + auto rl3 = PD::LI::DrawList::New(PD::Ctr::GetWhiteTex()); + rl3->SetFont(font); + Filebrowser = + FileMgr::New(PD::Ctr::GetContext().Inp, PD::Ctr::GetWhiteTex(), font); + FileInspector = + Inspector::New(PD::Ctr::GetContext().Inp, PD::Ctr::GetWhiteTex(), font); + Settings = + Settings::New(PD::Ctr::GetContext().Inp, PD::Ctr::GetWhiteTex(), font); + Stage::Goto(Filebrowser); + + PD::Timer time; + while (PD::Ctr::ContextUpdate()) { + time.Update(); + rl2->SetFont(font); + + if (PD::Ctr::GetContext().Inp->IsUp(PD::Hid::Y)) { + bcstm_ctrl.DoRequest(BCSTM_Ctrl::SwitchDec); + } + + rl2->DrawRectFilled(fvec2(0, 0), fvec2(400, 240), 0xff64c9fd); + for (int i = 0; i < 44; i++) + Append(rl2, i, fvec2(0, 0), fvec2(400, 240), (float)time.GetSeconds()); + BottomScreenBeta(rl3); + + Stage::DoUpdate(); + PD::Ctr::AddDrawList(rl2, false); + PD::Ctr::AddDrawList(rl3, true); + PD::Ctr::AddDrawList(Stage::GetDrawDataTop(), false); + // PD::Ctr::AddDrawList(Stage::GetDrawDataBottom(), true); + if (PD::Ctr::GetContext().Inp->IsDown(PD::Ctr::GetContext().Inp->Start)) { + break; + } } + bcstm_ctrl.DoRequest(bcstm_ctrl.KillThread); + /** Make sure to unload before unloading the rest */ + Filebrowser = nullptr; + FileInspector = nullptr; + PD::Ctr::DestroyContext(); + ndspExit(); return 0; } diff --git a/source/pd_ctr_ext.cpp b/source/pd_ctr_ext.cpp new file mode 100644 index 0000000..9645ce1 --- /dev/null +++ b/source/pd_ctr_ext.cpp @@ -0,0 +1,141 @@ +#include +#include +#include + +constexpr u32 DisplayTransferFlags = + (GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | + GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | + GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | + GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)); + +bool pExceptionCtx = false; +bool pExceptionCtxA = false; +/** Seems to not work if it happens in another thread */ +void HandleException(const std::string& e) { + pExceptionCtx = true; + while (!pExceptionCtxA) { + } + gfxInitDefault(); + consoleInit(GFX_TOP, nullptr); + std::cout << "Palladium Exception Handler\n\n"; + std::cout << "Exeption: \n\n" << e << "\n\n"; + std::cout << "Press Start to Exit!" << std::endl; + while (aptMainLoop()) { + hidScanInput(); + if (hidKeysDown() & KEY_START) { + break; + } + } + gfxExit(); + std::abort(); +} + +void CxxExceptionHandler() { +#ifdef __EXCEPTIONS + std::exception_ptr e_ = std::current_exception(); + if (e_) { + try { + std::rethrow_exception(e_); + } catch (const std::exception& e) { + HandleException(e.what()); + } catch (...) { + HandleException("Unknown Exception"); + } + } +#endif + std::abort(); +} + +namespace PD { +namespace Ctr { +Context* ActiveContext; + +Context* CheckContext() { + if (ActiveContext == nullptr) { + throw std::runtime_error( + "Context was nullptr. Did you forgot to call PD::Ctr::CreateContext?"); + } + return ActiveContext; +} + +void EnableExceptionScreen() { std::set_terminate(CxxExceptionHandler); } + +Context* CreateContext() { + Context* NewCtx = new Context; + osSetSpeedupEnable(true); + romfsInit(); + gfxInitDefault(); + C3D_Init(C3D_DEFAULT_CMDBUF_SIZE * 2); + NewCtx->Top = + C3D_RenderTargetCreate(240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); + NewCtx->Bottom = + C3D_RenderTargetCreate(240, 320, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); + C3D_RenderTargetSetOutput(NewCtx->Top, GFX_TOP, GFX_LEFT, + DisplayTransferFlags); + C3D_RenderTargetSetOutput(NewCtx->Bottom, GFX_BOTTOM, GFX_LEFT, + DisplayTransferFlags); + NewCtx->Gfx = PD::LI::Backend_C3D::New(); + NewCtx->Inp = PD::Hid3DS::New(); + NewCtx->Gfx->Init(); + std::vector wp(16 * 16 * 4, 0xff); + NewCtx->WhitePixel = NewCtx->Gfx->LoadTexture(wp, 16, 16); + if (ActiveContext == nullptr) { + ActiveContext = NewCtx; + } + return NewCtx; +} + +void DestroyContext(Context* ctx) { + if (ctx == nullptr) { + ctx = CheckContext(); + } + ctx->WhitePixel = nullptr; + ctx->Inp = nullptr; + ctx->Gfx->Deinit(); + ctx->Gfx = nullptr; + C3D_Fini(); + gfxExit(); + pExceptionCtxA = true; +} + +bool ContextUpdate() { + auto c = CheckContext(); + c->Inp->Update(); + C3D_FrameBegin(C3D_FRAME_SYNCDRAW); + C3D_RenderTargetClear(c->Top, C3D_CLEAR_ALL, 0x00000000, 0); + C3D_RenderTargetClear(c->Bottom, C3D_CLEAR_ALL, 0x00000000, 0); + C3D_FrameDrawOn(c->Top); + c->Gfx->ViewPort = ivec2(400, 240); + c->Gfx->NewFrame(); + for (auto& it : c->DrawLists[0]) { + c->Gfx->RenderDrawData(it->pDrawList); + it->Clear(); + } + C3D_FrameDrawOn(c->Bottom); + c->Gfx->ViewPort = ivec2(320, 240); + for (auto& it : c->DrawLists[1]) { + c->Gfx->RenderDrawData(it->pDrawList); + it->Clear(); + } + C3D_FrameEnd(0); + c->DrawLists[0].Clear(); + c->DrawLists[1].Clear(); + return aptMainLoop() && !pExceptionCtx; +} + +PD::LI::Texture::Ref GetWhiteTex() { + auto c = CheckContext(); + return c->WhitePixel; +} + +void AddDrawList(PD::LI::DrawList::Ref cmdl, bool bottom) { + auto c = CheckContext(); + c->DrawLists[bottom].Add(cmdl); +} + +Context& GetContext() { + CheckContext(); + return *ActiveContext; +} +} // namespace Ctr +} // namespace PD \ No newline at end of file diff --git a/source/pd_ctr_ext.hpp b/source/pd_ctr_ext.hpp new file mode 100644 index 0000000..f89b33e --- /dev/null +++ b/source/pd_ctr_ext.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace PD { +namespace Ctr { +struct Context { + PD::LI::Backend::Ref Gfx; + PD::Hid::Ref Inp; + PD::LI::Texture::Ref WhitePixel; + C3D_RenderTarget* Top; + C3D_RenderTarget* Bottom; + + PD::Vec DrawLists[2]; +}; + +Context* CreateContext(); +/** Pre alpha feature */ +void EnableExceptionScreen(); +void DestroyContext(Context* ctx = nullptr); +PD::LI::Texture::Ref GetWhiteTex(); +void AddDrawList(PD::LI::DrawList::Ref cmdl, bool bottom); +bool ContextUpdate(); +Context& GetContext(); +} // namespace Ctr +} // namespace PD \ No newline at end of file diff --git a/source/scenes/Filemanager.cpp b/source/scenes/Filemanager.cpp deleted file mode 100644 index d788706..0000000 --- a/source/scenes/Filemanager.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include -#include - -namespace BP { -Filemanager::Filemanager(std::string idevice) { - this->device = idevice; - this->dir = this->device; - this->dircontent = PD::FileSystem::GetDirContentsExt(dir, {"bcstm"}); - this->reload_list(); - this->changeddir = false; -} - -void Filemanager::Update() { - // Render - PD::LI::OnScreen(false); - if (UI7::BeginMenu(PD::Lang::Get("HEAD_FILEMANAGER"))) { - if (config.clock()) { - UI7::SetCursorPos(NVec2(395, 2)); - UI7::Label(Clock(), PDTextFlags_AlignRight); - UI7::RestoreCursor(); - } - UI7::BrowserList(namelist, this->dirsel); - UI7::SetCursorPos(NVec2(5, 222)); - UI7::Label(this->dir); - UI7::RestoreCursor(); - UI7::EndMenu(); - } - - PD::LI::OnScreen(true); - if (UI7::BeginMenu(PD::Lang::Get("INFO"))) { - UI7::Label( - PD::Lang::Get("PLAYING") + - std::string(playing ? now_playing : PD::Lang::Get("NOTHING")), - PDTextFlags_Short); - UI7::Label(PD::Lang::Get("DIRENTRYS") + - std::to_string(this->dircontent.size())); - UI7::Label(PD::Lang::Get("LOADED") + - std::string(player.IsLoaded() ? PD::Lang::Get("YES") - : PD::Lang::Get("NO"))); - UI7::Label(PD::Lang::Get("LOOP") + std::string(player.IsLooping() - ? PD::Lang::Get("YES") - : PD::Lang::Get("NO"))); - UI7::Label(PD::Lang::Get("LOOPSTART") + - std::to_string(player.GetLoopStart())); - UI7::Label(PD::Lang::Get("LOOPEND") + std::to_string(player.GetLoopEnd())); - UI7::Label(PD::Lang::Get("CURRENT") + std::to_string(player.GetCurrent())); - UI7::Label(PD::Lang::Get("TOTAL") + std::to_string(player.GetTotal())); - UI7::Label(PD::Lang::Get("CHANNELS") + - std::to_string(player.GetChannelCount())); - UI7::Label(PD::Lang::Get("ERROR") + player.GetErrorMessage()); - if (UI7::Button(PD::Lang::Get("PLAY"))) { - player.Play(); - } - UI7::SameLine(); - if (UI7::Button(PD::Lang::Get("PAUSE"))) { - player.Pause(); - } - UI7::SameLine(); - if (UI7::Button(PD::Lang::Get("STOP"))) { - playing = false; - player.Stop(); - } - UI7::SetCursorPos(NVec2(5, 215)); - UI7::Progressbar((float)player.GetCurrent() / (float)player.GetTotal()); - UI7::RestoreCursor(); - UI7::EndMenu(); - } - // Logic - if (this->changeddir) { - this->dircontent.clear(); - std::vector temp = - PD::FileSystem::GetDirContentsExt(dir, {"bcstm"}); - - for (int i = 0; i < (int)temp.size(); i++) { - this->dircontent.push_back(temp[i]); - } - this->reload_list(); - this->changeddir = false; - } - if (hidKeysDown() & KEY_A) { - if (this->dircontent.size() > 0) { - if (this->dircontent[dirsel].dir) { - if (dir.substr(dir.length() - 1, 1) != "/") dir += "/"; - dir += this->dircontent[this->dirsel].name; - this->dircontent.clear(); - this->dirsel = 0; - this->changeddir = true; - } else { - if (PD::NameIsEndingWith(this->dircontent[this->dirsel].name, - {"bcstm"})) { - playing = false; - if (player.LoadFile(this->dircontent[this->dirsel].path)) { - player.Play(); - playing = true; - } - now_playing = this->dircontent[this->dirsel].name; - } - } - } - } - if (hidKeysDown() & KEY_B) { - if (strcmp(dir.c_str(), this->device.c_str()) == 0 || - strcmp(dir.c_str(), "/") == 0) { - PD::Scene::Back(); - } else { - dir = PD::FileSystem::GetParentPath(dir, this->device); - dirsel = 0; - changeddir = true; - } - } - if (hidKeysDown() & KEY_X) { - PD::Scene::Back(); - } - if (hidKeysDown() & KEY_UP && dirsel > 0) { - dirsel--; - } - if (hidKeysDown() & KEY_DOWN && dirsel < (int)dircontent.size() - 1) { - dirsel++; - } - if (hidKeysDown() & KEY_LEFT && dirsel - 6 > 0) { - dirsel -= 6; - } - if (hidKeysDown() & KEY_RIGHT && dirsel + 6 < (int)dircontent.size() - 1) { - dirsel += 6; - } -} -} // namespace BP \ No newline at end of file diff --git a/source/scenes/Filemanager.hpp b/source/scenes/Filemanager.hpp deleted file mode 100644 index 61ac10b..0000000 --- a/source/scenes/Filemanager.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -namespace BP { -class Filemanager : public PD::Scene { - public: - void Update() override; - Filemanager(std::string idevice = "sdmc:/"); - - private: - void reload_list() { - namelist.clear(); - for (const auto &it : dircontent) namelist.push_back(it.name); - } - std::string device; - std::string dir; - std::vector dircontent; - std::vector namelist; - mutable int dirsel = 0; - bool changeddir = false; -}; -} // namespace BP \ No newline at end of file diff --git a/source/scenes/MainMenu.cpp b/source/scenes/MainMenu.cpp deleted file mode 100644 index 7080cfe..0000000 --- a/source/scenes/MainMenu.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include -#include - -namespace BP { -MainMenu::MainMenu() { this->load_menu(); } - -void MainMenu::Update() { - // Render - PD::LI::OnScreen(false); - if (UI7::BeginMenu(PD::Lang::Get("HEAD_MAINMENU"))) { - if (config.clock()) { - UI7::SetCursorPos(NVec2(395, 2)); - UI7::Label(Clock(), PDTextFlags_AlignRight); - UI7::RestoreCursor(); - } - UI7::Label( - PD::Lang::Get("PLAYING") + - std::string(playing ? now_playing : PD::Lang::Get("NOTHING")), - PDTextFlags_Short); - UI7::Label(PD::Lang::Get("LOADED") + - std::string(player.IsLoaded() ? PD::Lang::Get("YES") - : PD::Lang::Get("NO"))); - UI7::Label(PD::Lang::Get("LOOP") + std::string(player.IsLooping() - ? PD::Lang::Get("YES") - : PD::Lang::Get("NO"))); - UI7::Label(PD::Lang::Get("LOOPSTART") + - std::to_string(player.GetLoopStart())); - UI7::Label(PD::Lang::Get("LOOPEND") + std::to_string(player.GetLoopEnd())); - UI7::Label(PD::Lang::Get("CURRENT") + std::to_string(player.GetCurrent())); - UI7::Label(PD::Lang::Get("TOTAL") + std::to_string(player.GetTotal())); - UI7::Label(PD::Lang::Get("CHANNELS") + - std::to_string(player.GetChannelCount())); - UI7::Label(PD::Lang::Get("SRATE") + std::to_string(player.GetSampleRate())); - UI7::Label(PD::Lang::Get("ERROR") + player.GetErrorMessage()); - UI7::SetCursorPos(NVec2(5, 215)); - UI7::Progressbar((float)player.GetCurrent() / (float)player.GetTotal()); - UI7::RestoreCursor(); - UI7::EndMenu(); - } - PD::LI::OnScreen(true); - if (UI7::BeginMenu(PD::Lang::Get("CONTROLCENTER"))) { - if (UI7::Button(PD::Lang::Get("PLAY"))) { - player.Play(); - } - UI7::SameLine(); - if (UI7::Button(PD::Lang::Get("PAUSE"))) { - player.Pause(); - } - UI7::SameLine(); - if (UI7::Button(PD::Lang::Get("STOP"))) { - playing = false; - player.Stop(); - } - if (UI7::Button(PD::Lang::Get("BROWSE"))) { - PD::Scene::Load(std::make_unique()); - } - if (UI7::Button(PD::Lang::Get("SETTINGS"))) { - PD::Scene::Load(std::make_unique()); - } - if (rfsopt) { - if (UI7::Button(PD::Lang::Get("TITLES") + " (Citra only)")) { - PD::Scene::Load(std::make_unique()); - } - UI7::SameLine(); - if (UI7::Button("RomFS (Citra only)")) { - PD::Scene::Load(std::make_unique("title:/")); - } - } - if (UI7::Button(PD::Lang::Get("EXIT"))) { - PD::ExitApp(); - } - UI7::EndMenu(); - } - // Logic - // do only one request - if (hidKeysDown() & KEY_START) { - PD::ExitApp(); - } - if (current_lang != PD::Lang::GetShortcut() || - rfsopt != config.romfs_browse()) { - this->load_menu(); - } -} - -void MainMenu::load_menu() { - this->rfsopt = config.romfs_browse(); - this->current_lang = PD::Lang::GetShortcut(); -} -} // namespace BP \ No newline at end of file diff --git a/source/scenes/MainMenu.hpp b/source/scenes/MainMenu.hpp deleted file mode 100644 index 6de431d..0000000 --- a/source/scenes/MainMenu.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include - -namespace BP { -class MainMenu : public PD::Scene { - public: - void Update() override; - MainMenu(); - - private: - void load_menu(); - std::vector menu; - mutable int sel = 0; - std::string current_lang; - bool rfsopt; -}; -} // namespace BP \ No newline at end of file diff --git a/source/scenes/Settings.cpp b/source/scenes/Settings.cpp deleted file mode 100644 index e90d978..0000000 --- a/source/scenes/Settings.cpp +++ /dev/null @@ -1,188 +0,0 @@ -#include -#include - -// #define RELEASE - -namespace BP { -Settings::Settings() { - auto dc = PD::FileSystem::GetDirContent("romfs:/lang"); - int n = 0; - for (auto &it : dc) { - if (it.name == PD::Lang::GetShortcut()) { - lang_sel = n; - } - if (it.dir) { - languages.push_back(it.name); - } - n++; - } - dispc = config.clock(); - h24 = config.is24h(); - dps = config.disp_seconds(); - romfsb = config.romfs_browse(); - search_updates = config.search_updates(); - use_nightly = config.use_nightly(); -} - -void Settings::Update() { - // Render - PD::LI::OnScreen(false); - if (UI7::BeginMenu(PD::Lang::Get("HEAD_SETTINGS"))) { - if (config.clock()) { - UI7::SetCursorPos(NVec2(395, 2)); - UI7::Label(Clock(), PDTextFlags_AlignRight); - UI7::RestoreCursor(); - } - UI7::Label(PD::Lang::Get("CREDITSL")); - UI7::Label(PD::Lang::Get("TPWMR")); - UI7::Label(" - " + PD::Lang::Get("VERSION") + std::string(PDVSTRING)); - UI7::SetCursorPos(NVec2(5, 222)); - UI7::Label(PD::Lang::Get("VERSION") + V_STRING); - UI7::RestoreCursor(); -#ifndef RELEASE - UI7::SetCursorPos(NVec2(395, 222)); - UI7::Label("nightly: " + std::string(N_STRING), PDTextFlags_AlignRight); - UI7::RestoreCursor(); -#endif - UI7::EndMenu(); - } - PD::LI::OnScreen(true); - if (UI7::BeginMenu(PD::Lang::Get("BGB"), NVec2(), UI7MenuFlags_Scrolling)) { - if (languages.size() != 0) { - UI7::Label(PD::Lang::Get("LANGUAGE")); - UI7::Label(" - " + PD::Lang::GetName() + " (" + PD::Lang::GetShortcut() + - ")"); - UI7::Label(" - " + PD::Lang::Get("AUTHOR") + PD::Lang::GetAuthor()); - if (UI7::Button(PD::Lang::Get("NEXT") + - (lang_sel < (int)languages.size() - 1 - ? languages[lang_sel + 1] - : languages[0]))) { - lang_sel++; - if (lang_sel + 1 > (int)languages.size()) { - lang_sel = 0; - } - lang_reload = true; - } - } - UI7::Label(PD::Lang::Get("APPEARANCE")); - UI7::Checkbox(PD::Lang::Get("CLOCK"), dispc); - UI7::Checkbox(PD::Lang::Get("24HRS"), h24); - UI7::Checkbox(PD::Lang::Get("SHOWSECONDS"), dps); - UI7::Label(PD::Lang::Get("UPDATER")); - UI7::Checkbox(PD::Lang::Get("AUTOSEARCHUPDATE"), search_updates); - UI7::Checkbox(PD::Lang::Get("USENIGHTLY"), use_nightly); - if (UI7::Button(PD::Lang::Get("CHECK"))) { - BP::CheckForUpdate(); - } - if (update_info.valid) { - if (!update_info.nightly && - update_info.version.substr(1).compare(V_STRING) < 0) { - UI7::Label("Already Up to date!"); - } else { - UI7::Label(PD::Lang::Get("UPDATE") + update_info.version); - UI7::Label(PD::Lang::Get("INFO") + ":\n" + update_info.text); - if (!downloading && !installing) { - if (UI7::Button(PD::Lang::Get("DOWNLOAD"))) { - PD::Tasks::Create([&]() { - downloading = true; - bool fail = false; - std::string fname = "BCSTM-Player."; - fname += hb_mode ? "3dsx" : "cia"; - if (update_info.nightly) { - auto ret = PD::Net::Download2File( - "https://raw.githubusercontent.com/NPI-D7/nightlys/master/" - "builds/BCSTM-Player/" + - fname, - hb_mode ? thiz_path : "sdmc:/BCSTM-Player.cia"); - if (ret) { - fail = true; - PD::PushMessage(PD::Lang::Get("UPDATER"), - PD::Lang::Get("UPDFAILED") + "\n" + - std::to_string(PD::Net::ErrorCode(ret)) + - "/" + - std::to_string(PD::Net::StatusCode(ret))); - } - } else { - auto ret = PD::Net::GitDownloadRelease( - "https://github.com/NPI-D7/BCSTM-Player", fname, - hb_mode ? thiz_path : "sdmc:/BCSTM-Player.cia"); - if (ret) { - fail = true; - PD::PushMessage(PD::Lang::Get("UPDATER"), - PD::Lang::Get("UPDFAILED") + "\n" + - std::to_string(PD::Net::ErrorCode(ret)) + - "/" + - std::to_string(PD::Net::StatusCode(ret))); - } - } - if (!hb_mode && !fail) { - downloading = false; - installing = true; - Result r = PD::InstallCia("sdmc:/BCSTM-Player.cia", true); - if (R_FAILED(r)) { - PD::PushMessage(PD::Lang::Get("UPDATER"), - PD::Lang::Get("UPDFAILED") + "\n" + - PD::Lang::Get("INSTERR")); - } - std::filesystem::remove("sdmc:/BCSTM-Player.cia"); - installing = false; - } - PD::PushMessage(PD::Lang::Get("UPDATER"), - PD::Lang::Get("UPDDONE")); - downloading = false; - }); - } - } - } - } - if (downloading) { - auto current = PD::Net::GetProgressCurrent(); - auto total = PD::Net::GetProgressTotal(); - UI7::Label(PD::Lang::Get("DOWNLOAD") + ": " + PD::FormatBytes(current) + - "/" + PD::FormatBytes(total)); - UI7::Progressbar((float)current / (float)total); - } else if (installing) { - auto current = PD::InstallGetInfo().current; - auto total = PD::InstallGetInfo().total; - UI7::Label(PD::Lang::Get("INSTALLING") + ": " + PD::FormatBytes(current) + - "/" + PD::FormatBytes(total)); - UI7::Progressbar((float)current / (float)total); - } - UI7::Label(PD::Lang::Get("DEVELOPER")); - UI7::Label(PD::Lang::Get("EXECMODE") + (hb_mode ? "3dsx" : "cia")); - UI7::Checkbox(PD::Lang::Get("SHOWTITLEOPT"), romfsb); - if (UI7::Button("Palladium")) { - PD::LoadSettings(); - } - UI7::EndMenu(); - } - // Logic - if (dispc != config.clock()) { - config.Set("clock", dispc); - } - if (h24 != config.is24h()) { - config.Set("24h", h24); - } - if (dps != config.disp_seconds()) { - config.Set("disp_seconds", dps); - } - if (romfsb != config.romfs_browse()) { - config.Set("romfs_browse", romfsb); - } - if (use_nightly != config.use_nightly()) { - config.Set("use_nightly", use_nightly); - } - if (search_updates != config.search_updates()) { - config.Set("search_updates", search_updates); - } - if (lang_reload) { - PD::Lang::Load(languages[lang_sel]); - config.Set("lang", languages[lang_sel]); - lang_reload = false; - } - if (hidKeysDown() & KEY_B) { - config.Save(); - PD::Scene::Back(); - } -} -} // namespace BP \ No newline at end of file diff --git a/source/scenes/Settings.hpp b/source/scenes/Settings.hpp deleted file mode 100644 index 5d1f892..0000000 --- a/source/scenes/Settings.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -namespace BP { -class Settings : public PD::Scene { - public: - void Update() override; - Settings(); - - private: - std::vector languages; - mutable int lang_sel = 0; - mutable bool dispc = false; - mutable bool h24 = false; - mutable bool dps = false; - mutable bool romfsb = false; - mutable bool downloading = false; - mutable bool use_nightly = false; - mutable bool search_updates = false; - mutable bool lang_reload = false; - mutable bool installing = false; -}; -} // namespace BP \ No newline at end of file diff --git a/source/scenes/Titles.cpp b/source/scenes/Titles.cpp deleted file mode 100644 index 02462dc..0000000 --- a/source/scenes/Titles.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include - -namespace BP { -Titles::Titles() { - std::filesystem::create_directories(PD::GetAppDirectory() + "/cache"); - D7MC::TitleManager::ScanSD(PD::GetAppDirectory() + "/"); - maxtitles = (int)D7MC::TitleManager::sdtitles.size(); - for (const auto &it : D7MC::TitleManager::sdtitles) - namelist.push_back(it->name()); -} - -void Titles::Update() { - // Render - PD::LI::OnScreen(false); - if (UI7::BeginMenu(PD::Lang::Get("HEAD_TITLES"))) { - if (config.clock()) { - UI7::SetCursorPos(NVec2(395, 2)); - UI7::Label(Clock(), PDTextFlags_AlignRight); - UI7::RestoreCursor(); - } - UI7::BrowserList(namelist, selection); - UI7::EndMenu(); - } - PD::LI::OnScreen(true); - if (UI7::BeginMenu(PD::Lang::Get("BGB"))) { - UI7::EndMenu(); - } - // Logic - if (hidKeysDown() & KEY_B) { - PD::Scene::Back(); - } - if (hidKeysDown() & KEY_A) { - if (romfs_is_mount) { - romfsUnmount("title"); - romfs_is_mount = false; - } - Result mntres = romfsMountFromTitle( - D7MC::TitleManager::sdtitles[selection]->id(), - D7MC::TitleManager::sdtitles[selection]->mediatype(), "title"); - if (R_FAILED(mntres)) { - PD::ResultDecoder d; - d.Load(mntres); - d.WriteLog(); - std::stringstream ss; - ss << "Failed to mount romfs: " << d.GetCode() << std::endl; - ss << "Module: " << d.GetModule() << std::endl; - ss << "Level: " << d.GetLevel() << std::endl; - ss << "Summary: " << d.GetSummary() << std::endl; - ss << "Description: " << d.GetDescription() << std::endl; - PD::Error(ss.str()); - } else { - romfs_is_mount = true; - } - } - if (hidKeysDown() & KEY_DOWN && - selection < (int)D7MC::TitleManager::sdtitles.size() - 1) { - selection++; - } - if (hidKeysDown() & KEY_UP && selection > 0) { - selection--; - } -} -} // namespace BP \ No newline at end of file diff --git a/source/scenes/Titles.hpp b/source/scenes/Titles.hpp deleted file mode 100644 index daeb568..0000000 --- a/source/scenes/Titles.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -namespace BP { -class Titles : public PD::Scene { - public: - void Update() override; - Titles(); - - private: - mutable int selection = 0; - int maxtitles = 0; - std::vector namelist; -}; -} // namespace BP \ No newline at end of file diff --git a/source/scenes/scenes.hpp b/source/scenes/scenes.hpp deleted file mode 100644 index 2ada39f..0000000 --- a/source/scenes/scenes.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include -#include -#include -#include \ No newline at end of file diff --git a/source/settings.cpp b/source/settings.cpp new file mode 100644 index 0000000..a273a3a --- /dev/null +++ b/source/settings.cpp @@ -0,0 +1,189 @@ +#include +#include + +void Settings::Update() { + delta.Update(); + cursor.Update(delta.GetSeconds() * 1000.f); + delta.Reset(); + + for (int i = 0; i < 12; i++) { + Top->Rect() + .SetPos(fvec2(0, 18 + 17 * i)) + .SetSize(fvec2(200, 17)) + .SetColor(((i % 2) == 0) ? PD::Color("#222222aa") + : PD::Color("#333333aa")); + Top->Rect() + .SetPos(fvec2(200, 18 + 17 * i)) + .SetSize(fvec2(200, 17)) + .SetColor(((i % 2) != 0) ? PD::Color("#222222aa") + : PD::Color("#333333aa")); + } + Top->Rect() + .SetPos(cursor) + .SetSize(fvec2(400, 17)) + .SetColor(PD::Color("#222222cc")); + for (int i = 0; i < int(pDL.size() > 12 ? 12 : pDL.size()); i++) { + Top->Text(pDL.at(sp + i)->First) + .SetPos(fvec2(5, 18 + 17 * i)) + .SetColor(PD::Colors::White); + Top->Text(pDL.at(sp + i)->Second) + .SetPos(fvec2(205, 18 + 17 * i)) + .SetColor(PD::Colors::White); + } + Top->Rect().SetColor(DesignerHeader).SetPos(0).SetSize(fvec2(400, 18)); + Top->Text("BCSTM-Player -> Settings") + .SetPos(fvec2(5, 1)) + .SetColor(PD::Colors::White); + Top->Rect() + .SetPos(fvec2(0, 222)) + .SetSize(fvec2(400, 18)) + .SetColor(DesignerHeader); + if (pDL.size() > 12) { + float rect_h = (12.f / (float)pDL.size()) * 204.f; + /** Make sure the rect is still visible */ + rect_h = std::clamp(rect_h, 10.f, 204.f); + float rect_pos = + 18.f + ((float)sp / (float)(pDL.size() - 12)) * (204.f - rect_h); + Top->Rect() + .SetPos(fvec2(396, rect_pos)) + .SetSize(fvec2(4, rect_h)) + .SetColor(PD::Colors::DarkGray); + } + + if (Inp->IsUp(Inp->Down) && sp + cursor.pIndex < (int)pDL.size() - 1) { + if (cursor.pIndex == 11) { + sp++; + } else { + cursor++; + } + } + if (Inp->IsUp(Inp->Right) && sp + cursor.pIndex + 5 < (int)pDL.size()) { + if (cursor.pIndex == 11) { + sp += 5; + } else { + if (cursor.pIndex + 5 > 11) { + sp += (cursor.pIndex - 11 + 5); + cursor.SetIndex(11); + } else { + cursor += 5; + } + } + } + if (Inp->IsUp(Inp->Up) && cursor.pIndex + sp > 0) { + if (cursor.pIndex == 0) { + sp--; + } else { + cursor--; + } + } + if (Inp->IsUp(Inp->Left) && sp + cursor.pIndex - 5 >= 0) { + if (cursor.pIndex == 0) { + sp -= 5; + } else { + if (cursor.pIndex - 5 < 0) { + sp -= 5 - cursor.pIndex; + cursor.SetIndex(0); + } else { + cursor -= 5; + } + } + } + + if (Inp->IsDown(Inp->A)) { + auto FSE = pDL.at(cursor.pIndex + sp); + if (FSE->pFunc) { + FSE->pFunc(FSE->Second); + } else if (FSE->SubData.size() != 0) { + pLastPos.Push(PD::Pair(sp, cursor.GetIndex())); + cursor.SetIndex(0); + sp = 0; + pStack.Push(pDL); + pDL = FSE->SubData; + } + } + + if (Inp->IsDown(Inp->B)) { + if (!pLastPos.IsEmpty()) { + sp = pLastPos.Top().First; + cursor.SetIndex(pLastPos.Top().Second); + pLastPos.Pop(); + } else { + sp = 0; + cursor.SetIndex(0); + } + if (pStack.IsEmpty()) { + Back(); + } else { + pDL = pStack.Top(); + pStack.Pop(); + } + } +} + +Settings::TabEntry::Ref Settings::MakeEntry(const std::string& id, + const std::string& val) { + auto e = TabEntry::New(); + e->First = id; + e->Second = val; + return e; +} + +Settings::TabEntry::Ref MakeInfo() { + auto e = Settings::TabEntry::New(); + std::vector pData; + pData.push_back(Settings::MakeEntry("Version", VERSION)); + pData.push_back(Settings::MakeEntry("Commit", GIT_COMMIT)); + pData.push_back(Settings::MakeEntry("Banch", GIT_BRANCH)); + pData.push_back(Settings::MakeEntry( + "C++", std::format("{} ({}-{})", (__cplusplus / 100) % 100, + __cplusplus / 100, __cplusplus % 100))); + pData.push_back( + Settings::MakeEntry("Compiler", PD::Strings::GetCompilerVersion())); + pData.push_back(Settings::MakeEntry("Build", __DATE__ " " __TIME__)); + pData.push_back(Settings::MakeEntry("RTTI", +#ifdef __RTTI + "ON" +#else + "OFF" +#endif + )); + pData.push_back(Settings::MakeEntry("Exceptions", +#ifdef __EXCEPTIONS + "ON" +#else + "OFF" +#endif + )); + e->First = "Show Info"; + e->SubData = pData; + return e; +} + +Settings::TabEntry::Ref MakeCredits() { + auto e = Settings::TabEntry::New(); + std::vector pData; + pData.push_back(Settings::MakeEntry("tobid7", "Lead deceloper, assets")); + pData.push_back(Settings::MakeEntry("", "palladium, ctrff")); + pData.push_back(Settings::MakeEntry("devkitpro", "libctru, citro3d")); + pData.push_back(Settings::MakeEntry("cheuble", "Original BCSTMV1-Decoder")); + pData.push_back(Settings::MakeEntry("3DBrew", "BCSTM Documentation")); + pData.push_back(Settings::MakeEntry("crozynski", "ComicNeue Font")); + + e->First = "Credits"; + e->SubData = pData; + return e; +} + +void Settings::Init() { + List.clear(); + + List.push_back(MakeInfo()); + List.push_back(MakeCredits()); + List.push_back(TabEntry::New( + "Decoder", bcstm_ctrl.player->GetName(), [=, this](std::string& s) { + s = bcstm_ctrl.player->GetName() == "BCSTMV2" ? "CTRFF (WIP)" + : "BCSTMV2"; + bcstm_ctrl.DoRequest(bcstm_ctrl.SwitchDec); + })); + pDL = List; +} \ No newline at end of file diff --git a/source/settings.hpp b/source/settings.hpp new file mode 100644 index 0000000..e09b582 --- /dev/null +++ b/source/settings.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +class Settings : public Stage { + public: + Settings(PD::Hid::Ref inp, PD::LI::Texture::Ref wp, PD::LI::Font::Ref f) + : Stage(inp, wp, f) { + Init(); + } + ~Settings() = default; + PD_SMART_CTOR(Settings); + + void Update() override; + void Init(); + + Cursor cursor = Cursor(fvec2(0.f, 18.f), 17.f); + int sp = 0; + PD::Timer delta; + struct TabEntry { + TabEntry() {} + TabEntry(const std::string& f, const std::string& s) { + First = f; + Second = s; + } + TabEntry(const std::string& f, const std::string& s, + std::function fu) { + First = f; + Second = s; + pFunc = fu; + } + PD_SMART_CTOR(TabEntry); + + std::string First; + std::string Second; + std::function pFunc; + std::vector SubData; + }; + + static TabEntry::Ref MakeEntry(const std::string& id, const std::string& val); + + /** THIS System of navigation is very memory inefficient btw */ + std::vector pDL; + std::vector List; + PD::Stack> pStack; + PD::Stack> pLastPos; +}; \ No newline at end of file diff --git a/source/stagemgr.cpp b/source/stagemgr.cpp new file mode 100644 index 0000000..c771816 --- /dev/null +++ b/source/stagemgr.cpp @@ -0,0 +1,3 @@ +#include + +PD::Stack Stage::pStages; \ No newline at end of file diff --git a/source/stagemgr.hpp b/source/stagemgr.hpp new file mode 100644 index 0000000..ada1d1c --- /dev/null +++ b/source/stagemgr.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +class Stage { + public: + Stage(PD::Hid::Ref inp, PD::LI::Texture::Ref wp, PD::LI::Font::Ref f) { + Top = PD::Flex::Context::New(wp, f, inp); + Bottom = PD::Flex::Context::New(wp, f, inp); + Inp = inp; + } + virtual ~Stage() {} + PD_SMART_CTOR(Stage); + + PD::Flex::Context::Ref Top; + PD::Flex::Context::Ref Bottom; + PD::Hid::Ref Inp; + + virtual void Update() {} + + static void Goto(Stage::Ref s) { pStages.Push(s); } + static void Back() { pStages.Pop(); } + static void DoUpdate() { + pStages.Top()->Top->pDrawList->Clear(); + pStages.Top()->Bottom->pDrawList->Clear(); + pStages.Top()->Update(); + pStages.Top()->Top->Update(); + pStages.Top()->Bottom->Update(); + }; + static PD::LI::DrawList::Ref GetDrawDataTop() { + return pStages.Top()->Top->pDrawList; + } + static PD::LI::DrawList::Ref GetDrawDataBottom() { + return pStages.Top()->Bottom->pDrawList; + } + + static PD::Stack pStages; +}; diff --git a/source/stages.hpp b/source/stages.hpp new file mode 100644 index 0000000..16ccfe1 --- /dev/null +++ b/source/stages.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include + +extern FileMgr::Ref Filebrowser; +extern Inspector::Ref FileInspector; +extern Settings::Ref Settings;