diff --git a/.build.sh b/.build.sh index 2ccebf7..7ba379a 100644 --- a/.build.sh +++ b/.build.sh @@ -1,6 +1,7 @@ #!/bin/sh set -ex +# Copy all files needed in cpp3ds archive mkdir -p $CPP3DS/bin/ cp $DEVKITARM/bin/makerom $CPP3DS/bin/ cp $DEVKITARM/bin/3dsxtool $CPP3DS/bin/ @@ -8,4 +9,5 @@ cp $DEVKITARM/bin/bannertool $CPP3DS/bin/ cp $DEVKITARM/bin/nihstro-assemble $CPP3DS/bin/ cp -r $PORTLIBS/lib/ $CPP3DS cp -r $PORTLIBS/include/ $CPP3DS + tar -cJvf $1 cpp3ds diff --git a/.travis.yml b/.travis.yml index 9ca4dc8..0279743 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,13 @@ script: before_deploy: - export RELEASE_FILENAME=cpp3ds-$TRAVIS_OS_NAME-$TRAVIS_TAG.tar.xz - - docker run --rm -v "$PWD":/usr/build -w /opt cpp3ds sh /usr/build/.build.sh /usr/build/$RELEASE_FILENAME + - docker run --rm -v "$PWD":/usr/build -w /usr/src cpp3ds sh /usr/build/.build.sh /usr/build/$RELEASE_FILENAME - sudo chmod 777 $RELEASE_FILENAME +after_success: + - docker run --rm -v "$PWD":/usr/build -w /usr/src/cpp3ds cpp3ds cp -r . /usr/build + - bash <(curl -s https://codecov.io/bash) + deploy: provider: releases api_key: diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dc090c..08fb1d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,13 +9,18 @@ include(cpp3ds) option(BUILD_EMULATOR "Build cpp3ds emulator (Qt5 required)" ON) option(BUILD_EXAMPLES "Build all cpp3ds example projects" ON) option(BUILD_DOCS "Build doxygen documentation" OFF) -option(ENABLE_OGG "Include OGG encoder/decoder classes" ON) +option(BUILD_TESTS "Build unit tests" OFF) +option(ENABLE_OGG "Include OGG decoder classes" ON) +option(ENABLE_AAC "Include AAC decoder classes" OFF) option(ENABLE_FLAC "Include FLAC encoder/decoder classes" OFF) option(ENABLE_MP3 "Include MP3 decoder class" OFF) if(ENABLE_OGG) add_definitions(-DCPP3DS_ENABLE_OGG) endif() +if(ENABLE_AAC) + add_definitions(-DCPP3DS_ENABLE_AAC) +endif() if(ENABLE_FLAC) add_definitions(-DCPP3DS_ENABLE_FLAC) endif() @@ -71,9 +76,11 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin") # compile flags set(ARCH "-march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft") -set(CPP3DS_ARM_FLAGS "-g -O2 ${ARCH} -ffunction-sections -fdata-sections") -set(CPP3DS_TEST_FLAGS "-g -O2") -set(CPP3DS_EMU_FLAGS "-g -O2") +set(CPP3DS_ARM_FLAGS "-g -O3 ${ARCH} -ffunction-sections -fdata-sections") +set(CPP3DS_TEST_FLAGS "-g -O3 -coverage") +set(CPP3DS_EMU_FLAGS "-g -O3") + +add_subdirectory(src) if(BUILD_EXAMPLES) add_subdirectory(examples) @@ -81,5 +88,6 @@ endif() if(BUILD_DOCS) add_subdirectory(doc) endif() - -add_subdirectory(src) +if(BUILD_TESTS) + add_subdirectory(test) +endif() diff --git a/Dockerfile b/Dockerfile index 92b460c..fc1b147 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,13 @@ FROM thecruel/devkitarm-3ds:latest MAINTAINER Thomas Edvalson "machin3@gmail.com" -ENV CPP3DS /opt/cpp3ds +ENV CPP3DS /usr/src/cpp3ds COPY . /usr/src/cpp3ds WORKDIR /usr/src RUN apt-get update && apt-get -y install \ + libgtest-dev \ libsfml-dev \ libglew-dev \ qt5-default \ @@ -14,31 +15,34 @@ RUN apt-get update && apt-get -y install \ libjpeg-dev \ libpng-dev \ libfreetype6-dev \ - libvorbis-dev + libvorbis-dev \ + libfaad-dev \ + libfmt3-dev RUN apt-get -y clean -RUN wget -q https://github.com/cpp3ds/3ds_portlibs/releases/download/r3/portlibs-3ds-r3.tar.xz -O portlibs.tar.xz && \ +RUN wget -q https://github.com/cpp3ds/3ds_portlibs/releases/download/r5/portlibs-3ds-r5.tar.xz -O portlibs.tar.xz && \ tar -xaf portlibs.tar.xz && \ rm portlibs.tar.xz && \ ln -s $(pwd)/portlibs $DEVKITPRO/portlibs && \ ln -s $DEVKITPRO/portlibs/3ds $DEVKITPRO/portlibs/armv6k -RUN wget -q https://github.com/cpp3ds/3ds-tools/releases/download/r4/3ds-tools-linux-r4.tar.gz -O tools.tar.gz && \ +RUN wget -q https://github.com/cpp3ds/3ds-tools/releases/download/r6/3ds-tools-linux-r6.tar.gz -O tools.tar.gz && \ tar -xaf tools.tar.gz && \ cp 3ds-tools/* $DEVKITARM/bin && \ rm tools.tar.gz +WORKDIR /usr/src/gtest +RUN cmake . && \ + make -j4 && \ + cp *.a /usr/lib && \ + make clean + WORKDIR /usr/src/cpp3ds RUN mkdir build && \ cd build && \ - cmake -DBUILD_EMULATOR=ON -DENABLE_OGG=ON -DBUILD_EXAMPLES=OFF .. && \ + cmake -DBUILD_EMULATOR=ON -DENABLE_OGG=ON -DENABLE_AAC=ON -DBUILD_EXAMPLES=OFF -DBUILD_TESTS=ON .. && \ make -j4 && \ + mv lib .. && \ cd .. && \ - mkdir $CPP3DS && \ - cp -r build/lib $CPP3DS && \ - cp -r include $CPP3DS && \ - cp -r cmake $CPP3DS && \ - cp -r scripts $CPP3DS && \ - cd .. && \ - rm -r cpp3ds + ./bin/tests diff --git a/README.md b/README.md index ee3383e..9f3ba5c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -cpp3ds [![Build Status](https://travis-ci.org/cpp3ds/cpp3ds.png?branch=master)](https://travis-ci.org/cpp3ds/cpp3ds) +cpp3ds ====== +[![Build Status](https://travis-ci.org/cpp3ds/cpp3ds.png?branch=master)](https://travis-ci.org/cpp3ds/cpp3ds) [![Codecov branch](https://img.shields.io/codecov/c/github/cpp3ds/cpp3ds/master.svg?maxAge=86400)](https://codecov.io/gh/cpp3ds/cpp3ds) [![Docker pulls](https://img.shields.io/docker/pulls/thecruel/cpp3ds.svg?maxAge=86400)](https://hub.docker.com/r/thecruel/cpp3ds/) [![AUR package](https://img.shields.io/aur/version/cpp3ds-git.svg?maxAge=86400)](https://aur.archlinux.org/packages/cpp3ds-git/) + Basic C++ gaming framework and library for Nintendo 3DS. cpp3ds is essentially a barebones port of SFML with a parallel native 3ds emulator built on top of it. The goal is to completely abstract the developer from the hardware SDK and provide a nice object-oriented C++ framework for clean and easy coding. And the emulator is designed to provide a means of surface-level realtime debugging (with GDB or whatever you prefer). @@ -20,12 +22,14 @@ Requirements - DevkitARM - ctrulib -- [gl3ds](https://github.com/cpp3ds/gl3ds) +- citro3d For emulator: -- [SFML 2.1](http://www.sfml-dev.org/index.php) +- [SFML 2.3](http://www.sfml-dev.org/index.php) - [Qt 5](https://qt-project.org/) +- OpenAL +- libvorbis For unit tests: diff --git a/cmake/FindFaad.cmake b/cmake/FindFaad.cmake new file mode 100644 index 0000000..42b88b8 --- /dev/null +++ b/cmake/FindFaad.cmake @@ -0,0 +1,43 @@ +# Try to find FAAD library and include path. +# Once done this will define +# +# FAAD_INCLUDE_DIRS - where to find faad.h, etc. +# FAAD_LIBRARIES - List of libraries when using libfaad. +# FAAD_FOUND - True if libfaad found. + +if(WIN32) + find_path(FAAD_INCLUDE_DIR faad.h $ENV{PROGRAMFILES}/FAAD/include DOC "The directory where faad.h resides") + find_library(FAAD_LIBRARY NAMES faad PATHS $ENV{PROGRAMFILES}/FAAD/lib DOC "The libfaad library") + +else(WIN32) + find_path(FAAD_INCLUDE_DIR NAMES faad.h faad2/faad.h DOC "The directory where faad.h resides") + find_library(FAAD_LIBRARY NAMES faad DOC "The libfaad library") + +endif(WIN32) + +if(FAAD_INCLUDE_DIR AND FAAD_LIBRARY) + set(FAAD_FOUND 1) + set(FAAD_LIBRARIES ${FAAD_LIBRARY}) + set(FAAD_INCLUDE_DIRS ${FAAD_INCLUDE_DIR}) +else(FAAD_INCLUDE_DIR AND FAAD_LIBRARY) + set(FAAD_FOUND 0) + set(FAAD_LIBRARIES) + set(FAAD_INCLUDE_DIRS) +endif(FAAD_INCLUDE_DIR AND FAAD_LIBRARY) + +mark_as_advanced(FAAD_INCLUDE_DIR) +mark_as_advanced(FAAD_LIBRARY) +mark_as_advanced(FAAD_FOUND) + +if(NOT FAAD_FOUND) + set(FAAD_DIR_MESSAGE "libfaad was not found. Make sure FAAD_LIBRARY and FAAD_INCLUDE_DIR are set.") + if(NOT FAAD_FIND_QUIETLY) + message(STATUS "${FAAD_DIR_MESSAGE}") + else(NOT FAAD_FIND_QUIETLY) + if(FAAD_FIND_REQUIRED) + message(FATAL_ERROR "${FAAD_DIR_MESSAGE}") + endif(FAAD_FIND_REQUIRED) + endif(NOT FAAD_FIND_QUIETLY) +else(NOT FAAD_FOUND) + message(STATUS "Found libfaad: ${FAAD_LIBRARY}") +endif(NOT FAAD_FOUND) diff --git a/cmake/FindTremor.cmake b/cmake/FindTremor.cmake new file mode 100644 index 0000000..5234dfd --- /dev/null +++ b/cmake/FindTremor.cmake @@ -0,0 +1,21 @@ +# - Find Tremor +# +# TREMOR_INCLUDE_DIR - where to find Tremor headers. +# TREMOR_LIBRAY - List of libraries when using libTremor. +# TREMOR_FOUND - True if Tremor found. + +if(TREMOR_INCLUDE_DIR) + # Already in cache, be silent + set(TREMOR_FIND_QUIETLY TRUE) +endif(TREMOR_INCLUDE_DIR) + +find_path(TREMOR_INCLUDE_DIR tremor/ivorbisfile.h) +find_library(TREMOR_LIBRARY NAMES vorbisidec) + +# Handle the QUIETLY and REQUIRED arguments and set TREMOR_FOUND to TRUE if +# all listed variables are TRUE. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(TREMOR DEFAULT_MSG + TREMOR_INCLUDE_DIR TREMOR_LIBRARY) + +mark_as_advanced(TREMOR_INCLUDE_DIR TREMOR_LIBRARY) diff --git a/cmake/arm-toolchain.cmake b/cmake/arm-toolchain.cmake index 0ed4d3d..edb35f5 100644 --- a/cmake/arm-toolchain.cmake +++ b/cmake/arm-toolchain.cmake @@ -9,7 +9,7 @@ SET(CMAKE_OBJCOPY ${DEVKITARM}/bin/arm-none-eabi-objcopy) SET(CMAKE_AR ${DEVKITARM}/bin/arm-none-eabi-ar) SET(CMAKE_RANLIB ${DEVKITARM}/bin/arm-none-eabi-ranlib) -set(CMAKE_FIND_ROOT_PATH ${DEVKITARM} ${DEVKITPRO} ${DEVKITPRO}/portlibs/3ds ${DEVKITPRO}/portlibs/armv6k) +set(CMAKE_FIND_ROOT_PATH ${DEVKITARM} ${DEVKITPRO} ${DEVKITPRO}/portlibs/3ds ${DEVKITPRO}/portlibs/armv6k ${DEVKITPRO}/portlibs/3ds/lib ${DEVKITPRO}/portlibs/armv6k/lib) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment diff --git a/cmake/cpp3ds.cmake b/cmake/cpp3ds.cmake index cea2c38..6d037d8 100644 --- a/cmake/cpp3ds.cmake +++ b/cmake/cpp3ds.cmake @@ -218,7 +218,7 @@ endmacro() function(__add_smdh target APP_TITLE APP_DESCRIPTION APP_AUTHOR APP_ICON) if(BANNERTOOL AND NOT FORCE_SMDHTOOL) - set(__SMDH_COMMAND ${BANNERTOOL} makesmdh -s ${APP_TITLE} -l ${APP_DESCRIPTION} -p ${APP_AUTHOR} -i ${APP_ICON} -o ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}) + set(__SMDH_COMMAND ${BANNERTOOL} makesmdh -s ${APP_TITLE} -l ${APP_DESCRIPTION} -p ${APP_AUTHOR} -i ${APP_ICON} -o ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target} ${ICON_FLAGS}) else() set(__SMDH_COMMAND ${SMDHTOOL} --create ${APP_TITLE} ${APP_DESCRIPTION} ${APP_AUTHOR} ${APP_ICON} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}) endif() @@ -302,16 +302,8 @@ function(__add_ncch_banner target IMAGE SOUND) endfunction() -function(add_cia_target target RSF IMAGE SOUND ) +function(add_cia_target target RSF IMAGE SOUND) get_filename_component(target_we ${target} NAME_WE) - if(${ARGC} GREATER 6) - set(APP_TITLE ${ARGV4}) - set(APP_DESCRIPTION ${ARGV5}) - set(APP_AUTHOR ${ARGV6}) - endif() - if(${ARGC} EQUAL 8) - set(APP_ICON ${ARGV7}) - endif() if(NOT APP_TITLE) set(APP_TITLE ${target}) endif() @@ -321,6 +313,9 @@ function(add_cia_target target RSF IMAGE SOUND ) if(NOT APP_AUTHOR) set(APP_AUTHOR "Unspecified Author") endif() + if(NOT APP_VERSION) + set(APP_VERSION 0) + endif() if(NOT APP_ICON) if(EXISTS ${target}.png) set(APP_ICON ${target}.png) @@ -335,7 +330,10 @@ function(add_cia_target target RSF IMAGE SOUND ) if( NOT ${target_we}.smdh) __add_smdh(${target_we}.smdh ${APP_TITLE} ${APP_DESCRIPTION} ${APP_AUTHOR} ${APP_ICON}) endif() - __add_ncch_banner(${target_we}.bnr ${IMAGE} ${SOUND}) + if(NOT BANNER) + set(BANNER ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target_we}.bnr) + __add_ncch_banner(${target_we}.bnr ${IMAGE} ${SOUND}) + endif() add_custom_command(OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target_we}.cia COMMAND ${MAKEROM} -f cia -target t @@ -343,12 +341,13 @@ function(add_cia_target target RSF IMAGE SOUND ) -o ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target_we}.cia -elf $ -rsf ${RSF} - -banner ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target_we}.bnr + -ver ${APP_VERSION} + -banner ${BANNER} -icon ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target_we}.smdh -DAPP_TITLE=${APP_TITLE} -DAPP_PRODUCT_CODE=${APP_PRODUCT_CODE} -DAPP_UNIQUE_ID=${APP_UNIQUE_ID} - DEPENDS ${target} ${RSF} ${ROMFS_FILES} ${SHADER_OUTPUT} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target_we}.bnr ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target_we}.smdh + DEPENDS ${target} ${RSF} ${ROMFS_FILES} ${SHADER_OUTPUT} ${BANNER} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target_we}.smdh WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} VERBATIM ) diff --git a/cmake/template_arm/CMakeLists.txt b/cmake/template_arm/CMakeLists.txt index ea2eaa4..6bb51d7 100644 --- a/cmake/template_arm/CMakeLists.txt +++ b/cmake/template_arm/CMakeLists.txt @@ -27,6 +27,7 @@ file(GLOB_RECURSE ROMFS_FILES ${PROJECT_SOURCE_DIR}/res/romfs/*) add_executable(${PROJECT_NAME}.elf ${SOURCE_FILES} ${ARM_SOURCE_FILES}) target_link_libraries(${PROJECT_NAME}.elf ${CPP3DS_ARM_LIBS}) +set_target_properties(${PROJECT_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "_3DS") set_target_properties(${PROJECT_NAME}.elf PROPERTIES COMPILE_FLAGS "${CPP3DS_ARM_FLAGS}") set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_FLAGS "-specs=3dsx.specs -march=armv6k -mtune=mpcore -mfloat-abi=hard -gc-sections") SET(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") diff --git a/include/cpp3ds/Audio/AlResource.hpp b/include/cpp3ds/Audio/AlResource.hpp index 2370f62..0aec05f 100644 --- a/include/cpp3ds/Audio/AlResource.hpp +++ b/include/cpp3ds/Audio/AlResource.hpp @@ -28,10 +28,17 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include +#include namespace cpp3ds { +#ifdef _3DS +extern Mutex g_activeNdspChannelsMutex; +extern Uint32 g_activeNdspChannels; +#endif + //////////////////////////////////////////////////////////// /// \brief Base class for classes that require an OpenAL context /// diff --git a/include/cpp3ds/Audio/SoundFileReaderAAC.hpp b/include/cpp3ds/Audio/SoundFileReaderAAC.hpp new file mode 100644 index 0000000..647bb68 --- /dev/null +++ b/include/cpp3ds/Audio/SoundFileReaderAAC.hpp @@ -0,0 +1,138 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#ifndef CPP3DS_SOUNDFILEREADERAAC_HPP +#define CPP3DS_SOUNDFILEREADERAAC_HPP + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include + +namespace cpp3ds +{ +namespace priv +{ +//////////////////////////////////////////////////////////// +/// \brief Implementation of sound file reader that handles AAC encoded files +/// +//////////////////////////////////////////////////////////// +class SoundFileReaderAAC : public SoundFileReader +{ +public: + + //////////////////////////////////////////////////////////// + /// \brief Check if this reader can handle a file given by an input stream + /// + /// \param stream Source stream to check + /// + /// \return True if the file is supported by this reader + /// + //////////////////////////////////////////////////////////// + static bool check(InputStream& stream); + +public: + + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + //////////////////////////////////////////////////////////// + SoundFileReaderAAC(); + + ~SoundFileReaderAAC(); + + //////////////////////////////////////////////////////////// + /// \brief Open a sound file for reading + /// + /// \param stream Stream to open + /// \param info Structure to fill with the attributes of the loaded sound + /// + //////////////////////////////////////////////////////////// + virtual bool open(cpp3ds::InputStream& stream, Info& info); + + //////////////////////////////////////////////////////////// + /// \brief Change the current read position to the given sample offset + /// + /// If the given offset exceeds to total number of samples, + /// this function must jump to the end of the file. + /// + /// \param sampleOffset Index of the sample to jump to, relative to the beginning + /// + //////////////////////////////////////////////////////////// + virtual void seek(Uint64 sampleOffset); + + //////////////////////////////////////////////////////////// + /// \brief Read audio samples from the open file + /// + /// \param samples Pointer to the sample array to fill + /// \param maxCount Maximum number of samples to read + /// + /// \return Number of samples actually read (may be less than \a maxCount) + /// + //////////////////////////////////////////////////////////// + virtual Uint64 read(Int16* samples, Uint64 maxCount); + +private: + + void close(); + + bool readChunk(); + + //////////////////////////////////////////////////////////// + /// \brief Read the header of the open file + /// + /// \param info Attributes of the sound file + /// + /// \return True on success, false on error + /// + //////////////////////////////////////////////////////////// + bool parseHeader(Info& info); + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + InputStream* m_stream; ///< Source stream to read from + unsigned int m_bytesPerSample; ///< Size of a sample, in bytes + Uint64 m_dataStart; ///< Starting position of the audio data in the open file + + unsigned long m_sampleRate; + unsigned char m_channelCount; + + std::vector m_inputBuffer; + std::vector m_sampleBuffer; + int m_inputBufferPosition; + int m_sampleBufferPosition; + + NeAACDecHandle m_handle; +}; + +} // namespace priv + +} // namespace cpp3ds + + +#endif // CPP3DS_SOUNDFILEREADERAAC_HPP diff --git a/include/cpp3ds/Audio/SoundFileReaderOgg.hpp b/include/cpp3ds/Audio/SoundFileReaderOgg.hpp index 9b30e67..7ba5420 100644 --- a/include/cpp3ds/Audio/SoundFileReaderOgg.hpp +++ b/include/cpp3ds/Audio/SoundFileReaderOgg.hpp @@ -29,7 +29,11 @@ // Headers //////////////////////////////////////////////////////////// #include +#ifdef _3DS +#include +#else #include +#endif namespace cpp3ds diff --git a/include/cpp3ds/Audio/SoundStream.hpp b/include/cpp3ds/Audio/SoundStream.hpp index ac9b317..6d9646c 100644 --- a/include/cpp3ds/Audio/SoundStream.hpp +++ b/include/cpp3ds/Audio/SoundStream.hpp @@ -284,7 +284,7 @@ class SoundStream : public SoundSource enum { - BufferCount = 3 ///< Number of audio buffers used by the streaming loop + BufferCount = 30 ///< Number of audio buffers used by the streaming loop }; //////////////////////////////////////////////////////////// diff --git a/include/cpp3ds/Emulator.hpp b/include/cpp3ds/Emulator.hpp index ff77e73..3e5955c 100644 --- a/include/cpp3ds/Emulator.hpp +++ b/include/cpp3ds/Emulator.hpp @@ -1,9 +1,16 @@ #ifndef CPP3DS_EMULATOR_HPP #define CPP3DS_EMULATOR_HPP -#include +#ifdef TEST -#include + #include + +#else + + #include + #include + +#endif #endif diff --git a/include/cpp3ds/Graphics/Rect.hpp b/include/cpp3ds/Graphics/Rect.hpp index 793da71..d04724f 100644 --- a/include/cpp3ds/Graphics/Rect.hpp +++ b/include/cpp3ds/Graphics/Rect.hpp @@ -187,13 +187,14 @@ bool operator !=(const Rect& left, const Rect& right); #include // Create typedefs for the most common types -typedef Rect IntRect; -typedef Rect FloatRect; +typedef Rect IntRect; +typedef Rect UintRect; +typedef Rect FloatRect; -} // namespace sf +} // namespace cpp3ds -#endif // SFML_RECT_HPP +#endif // CPP3DS_RECT_HPP //////////////////////////////////////////////////////////// diff --git a/include/cpp3ds/Graphics/RenderStates.hpp b/include/cpp3ds/Graphics/RenderStates.hpp index 6dab176..5841a72 100644 --- a/include/cpp3ds/Graphics/RenderStates.hpp +++ b/include/cpp3ds/Graphics/RenderStates.hpp @@ -91,6 +91,14 @@ public : //////////////////////////////////////////////////////////// RenderStates(const Shader* theShader); + //////////////////////////////////////////////////////////// + /// \brief Construct a default set of render states with a custom scissor + /// + /// \param theScissor Scissor rect to use + /// + //////////////////////////////////////////////////////////// + RenderStates(const UintRect& theScissor); + //////////////////////////////////////////////////////////// /// \brief Construct a set of render states with all its attributes /// @@ -101,7 +109,7 @@ public : /// //////////////////////////////////////////////////////////// RenderStates(const BlendMode& theBlendMode, const Transform& theTransform, - const Texture* theTexture, const Shader* theShader); + const Texture* theTexture, const Shader* theShader, const IntRect& theScissor); //////////////////////////////////////////////////////////// // Static member data @@ -115,6 +123,7 @@ public : Transform transform; ///< Transform const Texture* texture; ///< Texture const Shader* shader; ///< Shader + UintRect scissor; ///< Scissor }; } diff --git a/include/cpp3ds/Graphics/RenderTarget.hpp b/include/cpp3ds/Graphics/RenderTarget.hpp index f82ee5c..07449b7 100644 --- a/include/cpp3ds/Graphics/RenderTarget.hpp +++ b/include/cpp3ds/Graphics/RenderTarget.hpp @@ -49,6 +49,8 @@ class Drawable; //////////////////////////////////////////////////////////// class RenderTarget : NonCopyable { +friend class Text; + public : //////////////////////////////////////////////////////////// @@ -359,6 +361,14 @@ protected : //////////////////////////////////////////////////////////// void applyBlendMode(const BlendMode& mode); + //////////////////////////////////////////////////////////// + /// \brief Apply a new scissor rect + /// + /// \param rect Scissor rect to use (Empty IntRect() to disable) + /// + //////////////////////////////////////////////////////////// + void applyScissor(const UintRect& rect); + //////////////////////////////////////////////////////////// /// \brief Apply a new transform /// @@ -411,6 +421,7 @@ protected : Uint64 lastTextureId; ///< Cached texture bool useVertexCache; ///< Did we previously use the vertex cache? Vertex* vertexCache; ///< Pre-transformed vertices cache + UintRect lastScissor; }; //////////////////////////////////////////////////////////// diff --git a/include/cpp3ds/Graphics/Text.hpp b/include/cpp3ds/Graphics/Text.hpp index a3d8d89..65f06e4 100644 --- a/include/cpp3ds/Graphics/Text.hpp +++ b/include/cpp3ds/Graphics/Text.hpp @@ -40,6 +40,7 @@ namespace cpp3ds { + //////////////////////////////////////////////////////////// /// \brief Graphical text that can be drawn to a render target /// @@ -124,6 +125,8 @@ namespace cpp3ds //////////////////////////////////////////////////////////// void setFont(const Font& font); + void useSystemFont(); + //////////////////////////////////////////////////////////// /// \brief Set the character size /// @@ -337,6 +340,8 @@ namespace cpp3ds //////////////////////////////////////////////////////////// virtual void draw(RenderTarget& target, RenderStates states) const; + void drawSystemFont(RenderTarget& target, RenderStates states) const; + //////////////////////////////////////////////////////////// /// \brief Make sure the text's geometry is updated /// @@ -346,6 +351,9 @@ namespace cpp3ds //////////////////////////////////////////////////////////// void ensureGeometryUpdate() const; + void ensureGeometryUpdateSystemFont() const; + Vector2f findCharacterPosSystemFont(std::size_t index) const; + //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// @@ -360,6 +368,10 @@ namespace cpp3ds mutable VertexArray m_outlineVertices; ///< Vertex array containing the outline geometry mutable FloatRect m_bounds; ///< Bounding rectangle of the text (in local coordinates) mutable bool m_geometryNeedUpdate; ///< Does the geometry need to be recomputed? + bool m_useSystemFont; ///< Flag to use 3DS system font +#ifndef EMULATION + mutable std::vector m_systemGlyphTextures; +#endif }; } // namespace cpp3ds diff --git a/include/cpp3ds/Graphics/Texture.hpp b/include/cpp3ds/Graphics/Texture.hpp index 4a1062e..9ce6a22 100644 --- a/include/cpp3ds/Graphics/Texture.hpp +++ b/include/cpp3ds/Graphics/Texture.hpp @@ -217,6 +217,7 @@ public : bool loadFromImage(const Image& image, const IntRect& area = IntRect()); #ifndef EMULATION + bool loadFromPreprocessedFile(const std::string& filename); bool loadFromPreprocessedFile(const std::string& filename, size_t width, size_t height, GPU_TEXCOLOR format); bool loadFromPreprocessedMemory(void *data, size_t size, size_t width, size_t height, GPU_TEXCOLOR format, bool copyData = true); diff --git a/include/cpp3ds/Network/Http.hpp b/include/cpp3ds/Network/Http.hpp index d64e1bf..dd2d075 100644 --- a/include/cpp3ds/Network/Http.hpp +++ b/include/cpp3ds/Network/Http.hpp @@ -231,7 +231,8 @@ class Http : NonCopyable // 10xx: cpp3ds custom codes InvalidResponse = 1000, ///< Response is not a valid HTTP one - ConnectionFailed = 1001 ///< Connection with server failed + ConnectionFailed = 1001, ///< Connection with server failed + TimedOut = 1002, ///< Connection timed out }; //////////////////////////////////////////////////////////// @@ -319,7 +320,7 @@ class Http : NonCopyable #ifdef EMULATION void parse(const std::string& data); #else - void parse(httpcContext *context); + void parse(httpcContext *context, Time timeout); #endif @@ -362,6 +363,10 @@ class Http : NonCopyable //////////////////////////////////////////////////////////// Http(); + ~Http(); + + void close(); + //////////////////////////////////////////////////////////// /// \brief Construct the HTTP client with the target host /// @@ -413,7 +418,7 @@ class Http : NonCopyable /// \return Server's response /// //////////////////////////////////////////////////////////// - Response sendRequest(const Request& request, Time timeout = Time::Zero, RequestCallback callback = nullptr); + Response sendRequest(const Request& request, Time timeout = Time::Zero, RequestCallback callback = nullptr, size_t bufferSize = 4096); private: diff --git a/include/cpp3ds/System/I18n.hpp b/include/cpp3ds/System/I18n.hpp index 4fb1e4d..e4356bb 100644 --- a/include/cpp3ds/System/I18n.hpp +++ b/include/cpp3ds/System/I18n.hpp @@ -10,26 +10,11 @@ #include #include #include +#include #define _(key, ...) (cpp3ds::I18n::getInstance().translate(key, ##__VA_ARGS__)) -namespace { - - template - cpp3ds::String string_format( const std::string& format, Args ... args ) - { - size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0' - std::unique_ptr buf( new char[ size ] ); - snprintf( buf.get(), size, format.c_str(), args ... ); - std::string stringUtf8( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside - std::wstring stringUtf32; - cpp3ds::Utf8::toUtf32(stringUtf8.begin(), stringUtf8.end(), std::back_inserter(stringUtf32)); - return cpp3ds::String(stringUtf32); - } -} - - namespace cpp3ds { enum Language { @@ -45,6 +30,8 @@ enum Language { Portuguese, Russian, ChineseTraditional, + + COUNT, }; class I18n { @@ -53,9 +40,8 @@ class I18n { static I18n& getInstance(); static void loadLanguage(Language language); - - static inline void loadLanguageFile(const std::string& filename); - + static void loadLanguageFile(const std::string& filename); + static void clearLoadedLanguage(); static Language getLanguage(); template @@ -66,7 +52,7 @@ class I18n { trans = std::string(key); else trans = it->second; - return string_format(trans, args ...); + return fmt::sprintf(trans, args ...); } template diff --git a/include/cpp3ds/System/Thread.hpp b/include/cpp3ds/System/Thread.hpp index 91caa1a..8b8bf58 100644 --- a/include/cpp3ds/System/Thread.hpp +++ b/include/cpp3ds/System/Thread.hpp @@ -177,6 +177,7 @@ public : void setStackSize(size_t stacksize); void setPriority(int priority); + void setRelativePriority(int priority); void setAffinity(int affinity); private : diff --git a/res/emu/emulator.ui b/res/emu/emulator.ui index b74cdd6..e74958b 100644 --- a/res/emu/emulator.ui +++ b/res/emu/emulator.ui @@ -10,14 +10,8 @@ 385 - - - 0 - 0 - - - MainWindow + cpp3ds Emulator @@ -34,16 +28,7 @@ - - 0 - - - 0 - - - 0 - - + 0 @@ -64,7 +49,7 @@ 0 0 318 - 21 + 25 diff --git a/scripts/sendfile.py b/scripts/sendfile.py index f4adc59..23f5e10 100644 --- a/scripts/sendfile.py +++ b/scripts/sendfile.py @@ -3,16 +3,19 @@ def sendfile(filename, ip): statinfo = os.stat(filename) - fbiinfo = struct.pack('!q', statinfo.st_size) with open(filename, 'rb') as f: sock = socket.socket() sock.connect((ip, 5000)) - sock.send(fbiinfo) - while True: - chunk = f.read(16384) - if not chunk: - break # EOF - sock.sendall(chunk) + sock.send(struct.pack('!i', 1)) + if struct.unpack("!b", sock.recv(1))[0] == 1: + sock.send(struct.pack('!q', statinfo.st_size)) + while True: + chunk = f.read(1024 * 256) + if not chunk: + break # EOF + sock.sendall(chunk) + else: + print("Canceled by FBI") sock.close() def show_usage_exit(): diff --git a/src/cpp3ds/Audio/AlResource.cpp b/src/cpp3ds/Audio/AlResource.cpp index 91dbfcb..c69a687 100644 --- a/src/cpp3ds/Audio/AlResource.cpp +++ b/src/cpp3ds/Audio/AlResource.cpp @@ -30,6 +30,10 @@ namespace cpp3ds { + +Mutex g_activeNdspChannelsMutex; +Uint32 g_activeNdspChannels = 0; + //////////////////////////////////////////////////////////// AlResource::AlResource() { diff --git a/src/cpp3ds/Audio/CMakeLists.txt b/src/cpp3ds/Audio/CMakeLists.txt index d0ead8f..db03b53 100644 --- a/src/cpp3ds/Audio/CMakeLists.txt +++ b/src/cpp3ds/Audio/CMakeLists.txt @@ -17,9 +17,16 @@ set(SRC ) if(ENABLE_OGG) + find_package(Tremor REQUIRED) + include_directories(${TREMOR_INCLUDE_DIRS}) list(APPEND SRC - ${SRCROOT}/SoundFileReaderOgg.cpp - ${SRCROOT}/SoundFileWriterOgg.cpp) + ${SRCROOT}/SoundFileReaderOgg.cpp) +endif() +if(ENABLE_AAC) + find_package(Faad REQUIRED) + include_directories(${FAAD_INCLUDE_DIRS}) + list(APPEND SRC + ${SRCROOT}/SoundFileReaderAAC.cpp) endif() if(ENABLE_MP3) find_package(mpg123 REQUIRED) diff --git a/src/cpp3ds/Audio/Music.cpp b/src/cpp3ds/Audio/Music.cpp index b497615..35e6322 100644 --- a/src/cpp3ds/Audio/Music.cpp +++ b/src/cpp3ds/Audio/Music.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -53,6 +54,9 @@ Music::~Music() //////////////////////////////////////////////////////////// bool Music::openFromFile(const std::string& filename) { + if (!Service::isEnabled(Audio)) + return false; + // First stop the music if it was already running stop(); @@ -70,6 +74,9 @@ bool Music::openFromFile(const std::string& filename) //////////////////////////////////////////////////////////// bool Music::openFromMemory(const void* data, std::size_t sizeInBytes) { + if (!Service::isEnabled(Audio)) + return false; + // First stop the music if it was already running stop(); @@ -87,6 +94,9 @@ bool Music::openFromMemory(const void* data, std::size_t sizeInBytes) //////////////////////////////////////////////////////////// bool Music::openFromStream(InputStream& stream) { + if (!Service::isEnabled(Audio)) + return false; + // First stop the music if it was already running stop(); @@ -138,7 +148,7 @@ void Music::initialize() m_duration = m_file.getDuration(); // Resize the internal buffer so that it can contain 1 second of audio samples - m_samples.resize(m_file.getSampleRate() * m_file.getChannelCount()); + m_samples.resize(m_file.getSampleRate() * m_file.getChannelCount() / 16); // Initialize the stream SoundStream::initialize(m_file.getChannelCount(), m_file.getSampleRate()); diff --git a/src/cpp3ds/Audio/Sound.cpp b/src/cpp3ds/Audio/Sound.cpp index b639920..9bf87eb 100644 --- a/src/cpp3ds/Audio/Sound.cpp +++ b/src/cpp3ds/Audio/Sound.cpp @@ -30,6 +30,8 @@ #include <3ds.h> #include #include +#include +#include namespace cpp3ds { @@ -78,6 +80,8 @@ Sound::~Sound() //////////////////////////////////////////////////////////// void Sound::play() { + if (!Service::isEnabled(Audio)) + return; if (!m_buffer || m_buffer->getSampleCount() == 0) return; if (getStatus() == Playing) @@ -87,14 +91,20 @@ void Sound::play() return; } - m_channel = 0; - while (m_channel < 24 && ndspChnIsPlaying(m_channel)) - m_channel++; + if (m_channel == -1) + { + Lock lock(g_activeNdspChannelsMutex); + m_channel = 0; + while (m_channel < 24 && ((g_activeNdspChannels >> m_channel) & 1)) + m_channel++; - if (m_channel == 24) { - err() << "Sound::play() failed because all channels are in use." << std::endl; - m_channel = -1; - return; + if (m_channel == 24) { + err() << "Failed to play audio stream: all channels are in use." << std::endl; + m_channel = -1; + return; + } + + g_activeNdspChannels |= 1 << m_channel; } setPlayingOffset(Time::Zero); @@ -106,7 +116,7 @@ void Sound::play() ndspChnSetRate(m_channel, float(m_buffer->getSampleRate())); ndspChnSetFormat(m_channel, (m_buffer->getChannelCount() == 1) ? NDSP_FORMAT_MONO_PCM16 : NDSP_FORMAT_STEREO_PCM16); - DSP_FlushDataCache((u8*)m_buffer->getSamples(), size); + DSP_FlushDataCache(m_buffer->getSamples(), size); ndspChnWaveBufAdd(m_channel, &m_ndspWaveBuf); } @@ -129,6 +139,13 @@ void Sound::stop() return; ndspChnWaveBufClear(m_channel); + + // TODO: set ndsp callback to make channel inactive after playback finishes + { + Lock lock(g_activeNdspChannelsMutex); + g_activeNdspChannels &= ~(1 << m_channel); + m_channel = -1; + } } @@ -144,7 +161,7 @@ void Sound::setBuffer(const SoundBuffer& buffer) memset(&m_ndspWaveBuf, 0, sizeof(ndspWaveBuf)); m_ndspWaveBuf.data_vaddr = buffer.getSamples(); - m_ndspWaveBuf.nsamples = buffer.getSampleCount(); + m_ndspWaveBuf.nsamples = buffer.getSampleCount() / buffer.getChannelCount(); m_ndspWaveBuf.looping = m_loop; // Loop enabled m_ndspWaveBuf.status = NDSP_WBUF_FREE; @@ -184,7 +201,7 @@ void Sound::setPlayingOffset(Time timeOffset) m_playOffset = timeOffset; int offset = m_buffer->getSampleRate() * m_buffer->getChannelCount() * timeOffset.asSeconds(); m_ndspWaveBuf.data_vaddr = m_buffer->getSamples() + offset; - m_ndspWaveBuf.nsamples = m_buffer->getSampleCount() - offset; + m_ndspWaveBuf.nsamples = m_buffer->getSampleCount() / m_buffer->getChannelCount() - offset; if (status == Playing) ndspChnWaveBufAdd(m_channel, &m_ndspWaveBuf); } diff --git a/src/cpp3ds/Audio/SoundFileFactory.cpp b/src/cpp3ds/Audio/SoundFileFactory.cpp index 849b4ea..e63fc4b 100644 --- a/src/cpp3ds/Audio/SoundFileFactory.cpp +++ b/src/cpp3ds/Audio/SoundFileFactory.cpp @@ -32,7 +32,10 @@ #endif #ifdef CPP3DS_ENABLE_OGG #include -#include +//#include +#endif +#ifdef CPP3DS_ENABLE_AAC +#include #endif #ifdef CPP3DS_ENABLE_MP3 #include @@ -57,7 +60,10 @@ namespace #endif #ifdef CPP3DS_ENABLE_OGG cpp3ds::SoundFileFactory::registerReader(); - cpp3ds::SoundFileFactory::registerWriter(); +// cpp3ds::SoundFileFactory::registerWriter(); +#endif +#ifdef CPP3DS_ENABLE_AAC + cpp3ds::SoundFileFactory::registerReader(); #endif #ifdef CPP3DS_ENABLE_MP3 cpp3ds::SoundFileFactory::registerReader(); diff --git a/src/cpp3ds/Audio/SoundFileReaderAAC.cpp b/src/cpp3ds/Audio/SoundFileReaderAAC.cpp new file mode 100644 index 0000000..82b77e3 --- /dev/null +++ b/src/cpp3ds/Audio/SoundFileReaderAAC.cpp @@ -0,0 +1,271 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2015 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace +{ + const cpp3ds::Uint64 mainChunkSize = 64; +} + +namespace cpp3ds +{ +namespace priv +{ +//////////////////////////////////////////////////////////// +bool SoundFileReaderAAC::check(InputStream& stream) +{ + unsigned char mainChunk[mainChunkSize]; + if (stream.read(mainChunk, sizeof(mainChunk)) != sizeof(mainChunk)) + return false; + + NeAACDecHandle handle = NeAACDecOpen(); + unsigned long samplerate; + unsigned char channels; + long ret = NeAACDecInit(handle, mainChunk, sizeof(mainChunk), &samplerate, &channels); + NeAACDecClose(handle); + if (ret != 0) + return false; + + std::cout << "AAC Samplerate: " << samplerate << " Channels: " << (int)channels << " BytesRead: " << (int)ret << std::endl; + + return true; +} + + +//////////////////////////////////////////////////////////// +SoundFileReaderAAC::SoundFileReaderAAC() : +m_stream (NULL), +m_bytesPerSample(0), +m_dataStart (0) +{ + m_handle = NeAACDecOpen(); + + // Configure the library to our needs + NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(m_handle); + conf->outputFormat = FAAD_FMT_16BIT; // We only support 16bit audio + conf->downMatrix = 1; // Convert from 5.1 to stereo if required + NeAACDecSetConfiguration(m_handle, conf); +} + + +//////////////////////////////////////////////////////////// +SoundFileReaderAAC::~SoundFileReaderAAC() +{ + close(); +} + + +//////////////////////////////////////////////////////////// +bool SoundFileReaderAAC::open(InputStream& stream, Info& info) +{ + m_stream = &stream; + + if (!parseHeader(info)) + { + err() << "Failed to open AAC sound file (invalid or unsupported file)" << std::endl; + return false; + } + + return true; +} + + +//////////////////////////////////////////////////////////// +void SoundFileReaderAAC::seek(Uint64 sampleOffset) +{ + assert(m_stream); + + m_stream->seek(m_dataStart + sampleOffset * m_bytesPerSample); + + m_inputBufferPosition = m_inputBuffer.size(); + readChunk(); +} + + +//////////////////////////////////////////////////////////// +bool SoundFileReaderAAC::readChunk() +{ + Int64 bytesRead = 0; + + if (m_inputBufferPosition == 0) + return true; + + // If there still exists some data in the buffer, + // move it to beginning and fill buffer from there. + if (m_inputBufferPosition < m_inputBuffer.size()) + { + int bytesRemaining = m_inputBuffer.size() - m_inputBufferPosition; + memmove(m_inputBuffer.data(), &m_inputBuffer[m_inputBufferPosition], bytesRemaining); + bytesRead = m_stream->read(&m_inputBuffer[bytesRemaining], m_inputBufferPosition); + bytesRead += bytesRemaining; + } + else // Buffer has been completed consumed + { + bytesRead = m_stream->read(m_inputBuffer.data(), m_inputBuffer.size()); + } + + if (!bytesRead) + return false; + if (bytesRead < m_inputBuffer.size()) + { + // Last bytes in file don't fill whole buffer, align accordingly + m_inputBufferPosition = m_inputBuffer.size() - bytesRead; + memmove(&m_inputBuffer[m_inputBufferPosition], m_inputBuffer.data(), bytesRead); + } + else + m_inputBufferPosition = 0; + + return true; +} + + +//////////////////////////////////////////////////////////// +Uint64 SoundFileReaderAAC::read(Int16* samples, Uint64 maxCount) +{ + assert(m_stream); + + NeAACDecFrameInfo frameInfo; + Uint32 count = 0; + Uint8 *buf = m_sampleBuffer.data(); + + while (count < maxCount) + { + size_t bytesLeft = static_cast(maxCount - count) * sizeof(Int16); + + // Keep input buffer full + if (m_inputBufferPosition > m_inputBuffer.size() - 1024) + { + if (!readChunk()) + break; + } + + // Output from sample buffer until it needs to be refilled + size_t bytesToCopy = std::min(bytesLeft, m_sampleBuffer.size() - m_sampleBufferPosition); + if (bytesToCopy > 0) + { + memcpy(samples + count, buf + m_sampleBufferPosition, bytesToCopy); + count += (bytesToCopy/sizeof(Int16)); + m_sampleBufferPosition += bytesToCopy; + } + + // Keep decoded sample buffer filled + if (m_sampleBufferPosition == m_sampleBuffer.size()) + { + NeAACDecDecode2(m_handle, &frameInfo, + m_inputBuffer.data() + m_inputBufferPosition, + m_inputBuffer.size() - m_inputBufferPosition, + reinterpret_cast(&buf), + m_sampleBuffer.size()); + + if (frameInfo.error > 0) + { +// std::cout << "Error (" << (int)frameInfo.error << "): " << NeAACDecGetErrorMessage(frameInfo.error) << std::endl; + if (m_inputBufferPosition == 0) + { + std::cout << "Error (" << (int)frameInfo.error << "): " << NeAACDecGetErrorMessage(frameInfo.error) << std::endl; + break; + } + if (frameInfo.error != 13 && frameInfo.error != 15) + std::cout << "Need to fill input buffer! error: " << (int)frameInfo.error << std::endl; + if (!readChunk()) + break; + } + else // Success + { + m_inputBufferPosition += frameInfo.bytesconsumed; + if (frameInfo.samples) + { + int sampleCapacity = m_sampleBuffer.size() / sizeof(Int16); + if (frameInfo.samples < sampleCapacity) + { + m_sampleBufferPosition = (sampleCapacity - frameInfo.samples) * sizeof(Int16); + memmove(&m_sampleBuffer[m_sampleBufferPosition], m_sampleBuffer.data(), frameInfo.samples * sizeof(Int16)); + } + else + m_sampleBufferPosition = 0; + } + } + } + } + + return count; +} + + +//////////////////////////////////////////////////////////// +bool SoundFileReaderAAC::parseHeader(Info& info) +{ + assert(m_stream); + + unsigned char mainChunk[mainChunkSize]; + if (m_stream->read(mainChunk, sizeof(mainChunk)) != sizeof(mainChunk)) + return false; + + long ret = NeAACDecInit(m_handle, mainChunk, sizeof(mainChunk), &m_sampleRate, &m_channelCount); + if (ret != 0) + return false; + + info.sampleCount = 0; + info.channelCount = m_channelCount; + info.sampleRate = m_sampleRate; + + // TODO: Figure these out + m_dataStart = ret; + m_bytesPerSample = 1; + + m_inputBuffer.resize(m_sampleRate); + m_inputBufferPosition = m_inputBuffer.size(); + m_sampleBuffer.resize(1024*2 * sizeof(uint16_t) * m_channelCount); + m_sampleBufferPosition = m_sampleBuffer.size(); + + m_stream->seek(ret); + + return true; +} + + +//////////////////////////////////////////////////////////// +void SoundFileReaderAAC::close() +{ + NeAACDecClose(m_handle); +} + + +} // namespace priv + +} // namespace cpp3ds diff --git a/src/cpp3ds/Audio/SoundFileReaderOgg.cpp b/src/cpp3ds/Audio/SoundFileReaderOgg.cpp index 9aba217..c8313ab 100644 --- a/src/cpp3ds/Audio/SoundFileReaderOgg.cpp +++ b/src/cpp3ds/Audio/SoundFileReaderOgg.cpp @@ -144,7 +144,11 @@ Uint64 SoundFileReaderOgg::read(Int16* samples, Uint64 maxCount) while (count < maxCount) { int bytesToRead = static_cast(maxCount - count) * sizeof(Int16); +#ifdef _3DS + long bytesRead = ov_read(&m_vorbis, reinterpret_cast(samples), bytesToRead, NULL); +#else long bytesRead = ov_read(&m_vorbis, reinterpret_cast(samples), bytesToRead, 0, 2, 1, NULL); +#endif if (bytesRead > 0) { long samplesRead = bytesRead / sizeof(Int16); diff --git a/src/cpp3ds/Audio/SoundStream.cpp b/src/cpp3ds/Audio/SoundStream.cpp index 9642008..49d35c3 100644 --- a/src/cpp3ds/Audio/SoundStream.cpp +++ b/src/cpp3ds/Audio/SoundStream.cpp @@ -46,7 +46,8 @@ SoundStream::SoundStream() , m_loop (false) , m_samplesProcessed(0) { - + m_thread.setPriority(0x1A); + m_thread.setStackSize(1024*64); } @@ -54,15 +55,8 @@ SoundStream::SoundStream() SoundStream::~SoundStream() { // Stop the sound if it was playing - - // Request the thread to terminate - { - Lock lock(m_threadMutex); - m_isStreaming = false; - } - - // Wait for the thread to terminate - m_thread.wait(); + if (m_threadStartState != Stopped) + stop(); } @@ -120,15 +114,20 @@ void SoundStream::play() return; } - m_channel = 0; - while (m_channel < 24 && ndspChnIsPlaying(m_channel)) - m_channel++; + { + Lock lock(g_activeNdspChannelsMutex); + m_channel = 0; + while (m_channel < 24 && ((g_activeNdspChannels >> m_channel) & 1)) + m_channel++; + + if (m_channel == 24) { + err() << "Failed to play audio stream: all channels are in use." << std::endl; + m_channel = -1; + return; + } - if (m_channel == 24) { - err() << "Failed to play audio stream: all channels are in use." << std::endl; - m_channel = -1; - return; - } + g_activeNdspChannels |= 1 << m_channel; + } ndspChnReset(m_channel); ndspChnSetInterp(m_channel, NDSP_INTERP_LINEAR); @@ -180,6 +179,11 @@ void SoundStream::stop() // Reset the playing position m_samplesProcessed = 0; + + { + Lock lock(g_activeNdspChannelsMutex); + g_activeNdspChannels &= ~(1 << m_channel); + } } @@ -410,13 +414,11 @@ bool SoundStream::fillAndPushBuffer(unsigned int bufferNum) // Fill the buffer buffer.assign(data.samples, data.samples + data.sampleCount); + DSP_FlushDataCache(buffer.data(), buffer.size() * sizeof(Int16)); + memset(&ndspBuffer, 0, sizeof(ndspWaveBuf)); - ndspBuffer.data_vaddr = &buffer[0]; + ndspBuffer.data_vaddr = buffer.data(); ndspBuffer.nsamples = data.sampleCount / m_channelCount; - ndspBuffer.looping = false; - ndspBuffer.status = NDSP_WBUF_FREE; - - DSP_FlushDataCache((u8*)&buffer[0], data.sampleCount); // Push it into the sound queue ndspChnWaveBufAdd(m_channel, &ndspBuffer); @@ -437,7 +439,7 @@ bool SoundStream::fillQueue() { if (fillAndPushBuffer(i)) requestStop = true; - sleep(milliseconds(10)); + sleep(milliseconds(20)); } return requestStop; diff --git a/src/cpp3ds/CMakeLists.txt b/src/cpp3ds/CMakeLists.txt index d6c1a47..546cd3c 100644 --- a/src/cpp3ds/CMakeLists.txt +++ b/src/cpp3ds/CMakeLists.txt @@ -7,6 +7,11 @@ include_directories( ${LIBCTRU_INCLUDE_DIRS} ) +set(CMAKE_C_FLAGS "") +set(CMAKE_CXX_FLAGS "-std=c++11") + +add_definitions(-D_3DS) + add_subdirectory(Audio) add_subdirectory(Graphics) add_subdirectory(Network) diff --git a/src/cpp3ds/Graphics/Console.cpp b/src/cpp3ds/Graphics/Console.cpp index e70a373..44e5574 100644 --- a/src/cpp3ds/Graphics/Console.cpp +++ b/src/cpp3ds/Graphics/Console.cpp @@ -22,7 +22,7 @@ namespace cpp3ds { extern "C" { #ifndef EMULATION -ssize_t console_write(struct _reent *r, int fd, const char *ptr, size_t len) { +ssize_t console_write(struct _reent *r, void *fd, const char *ptr, size_t len) { cpp3ds::String s; int i = 0; while (i < len) { @@ -87,6 +87,7 @@ void Console::enable(Screen screen, Color color) console.m_memoryText.setFont(console.m_font); console.m_memoryText.setCharacterSize(12); + console.m_memoryText.useSystemFont(); console.m_screen = screen; console.m_limit = 1000; @@ -159,6 +160,7 @@ void Console::write(String text) { Text line(text, m_font, 10); line.setFillColor(m_color); + line.useSystemFont(); m_lines.push_back(line); } diff --git a/src/cpp3ds/Graphics/RenderStates.cpp b/src/cpp3ds/Graphics/RenderStates.cpp index 981e9c8..4db9b4d 100644 --- a/src/cpp3ds/Graphics/RenderStates.cpp +++ b/src/cpp3ds/Graphics/RenderStates.cpp @@ -44,7 +44,8 @@ RenderStates::RenderStates() : blendMode(BlendAlpha), transform(), texture (NULL), -shader (NULL) +shader (NULL), +scissor () { } @@ -54,7 +55,8 @@ RenderStates::RenderStates(const Transform& theTransform) : blendMode(BlendAlpha), transform(theTransform), texture (NULL), -shader (NULL) +shader (NULL), +scissor () { } @@ -64,7 +66,8 @@ RenderStates::RenderStates(const BlendMode& theBlendMode) : blendMode(theBlendMode), transform(), texture (NULL), -shader (NULL) +shader (NULL), +scissor () { } @@ -74,7 +77,8 @@ RenderStates::RenderStates(const Texture* theTexture) : blendMode(BlendAlpha), transform(), texture (theTexture), -shader (NULL) +shader (NULL), +scissor () { } @@ -84,18 +88,31 @@ RenderStates::RenderStates(const Shader* theShader) : blendMode(BlendAlpha), transform(), texture (NULL), -shader (theShader) +shader (theShader), +scissor () +{ +} + + +//////////////////////////////////////////////////////////// +RenderStates::RenderStates(const UintRect& theScissor) : +blendMode(BlendAlpha), +transform(), +texture (NULL), +shader (NULL), +scissor (theScissor) { } //////////////////////////////////////////////////////////// RenderStates::RenderStates(const BlendMode& theBlendMode, const Transform& theTransform, - const Texture* theTexture, const Shader* theShader) : + const Texture* theTexture, const Shader* theShader, const IntRect& theScissor) : blendMode(theBlendMode), transform(theTransform), texture (theTexture), -shader (theShader) +shader (theShader), +scissor (theScissor) { } diff --git a/src/cpp3ds/Graphics/RenderTarget.cpp b/src/cpp3ds/Graphics/RenderTarget.cpp index 4d492b8..d189e45 100644 --- a/src/cpp3ds/Graphics/RenderTarget.cpp +++ b/src/cpp3ds/Graphics/RenderTarget.cpp @@ -243,6 +243,10 @@ void RenderTarget::draw(const Vertex* vertices, unsigned int vertexCount, if (states.blendMode != m_cache.lastBlendMode) applyBlendMode(states.blendMode); + // Apply the scissor mode + if (states.scissor != m_cache.lastScissor) + applyScissor(states.scissor); + // Apply the texture Uint64 textureId = states.texture ? states.texture->m_cacheId : 0; if (textureId != m_cache.lastTextureId) @@ -324,6 +328,7 @@ void RenderTarget::resetGLStates() applyBlendMode(BlendAlpha); applyTransform(Transform::Identity); applyTexture(NULL); + applyScissor(UintRect()); if (shaderAvailable) applyShader(NULL); @@ -384,6 +389,27 @@ void RenderTarget::applyBlendMode(const BlendMode& mode) } +//////////////////////////////////////////////////////////// +void RenderTarget::applyScissor(const UintRect& rect) +{ + if (rect == UintRect()) + C3D_SetScissor(GPU_SCISSOR_DISABLE, 0, 0, 0, 0); + else { + // Keep in mind the sideway 3ds screen, so it seems screwy + int bottom = getSize().x - rect.left; + int top = getSize().y - rect.top; + int left = top - rect.height; + int right = bottom - rect.width; + if (bottom < 0) bottom = 0; + if (top < 0) top = 0; + if (left < 0) left = 0; + if (right < 0) right = 0; + C3D_SetScissor(GPU_SCISSOR_NORMAL, left, right, top, bottom); + } + m_cache.lastScissor = rect; +} + + //////////////////////////////////////////////////////////// void RenderTarget::applyTransform(const Transform& transform) { diff --git a/src/cpp3ds/Graphics/Text.cpp b/src/cpp3ds/Graphics/Text.cpp index ef4597a..8417979 100644 --- a/src/cpp3ds/Graphics/Text.cpp +++ b/src/cpp3ds/Graphics/Text.cpp @@ -30,6 +30,11 @@ #include #include #include +#include +#ifndef EMULATION +#include "CitroHelpers.hpp" +#include +#endif namespace @@ -73,6 +78,11 @@ void addGlyphQuad(cpp3ds::VertexArray& vertices, cpp3ds::Vector2f position, cons namespace cpp3ds { + +#ifndef EMULATION +extern cpp3ds::Texture *system_font_textures; +#endif + namespace priv { // Default font for Text objects for user convenience @@ -92,7 +102,8 @@ Text::Text() : m_vertices (Triangles), m_outlineVertices (Triangles), m_bounds (), - m_geometryNeedUpdate(false) + m_geometryNeedUpdate(false), + m_useSystemFont (false) { } @@ -110,7 +121,8 @@ Text::Text(const String& string, const Font& font, unsigned int characterSize) : m_vertices (Triangles), m_outlineVertices (Triangles), m_bounds (), - m_geometryNeedUpdate(true) + m_geometryNeedUpdate(true), + m_useSystemFont (false) { } @@ -134,10 +146,21 @@ void Text::setFont(const Font& font) { m_font = &font; m_geometryNeedUpdate = true; + m_useSystemFont = false; } } +//////////////////////////////////////////////////////////// +void Text::useSystemFont() +{ +#ifndef EMULATION + m_geometryNeedUpdate = true; + m_useSystemFont = true; +#endif +} + + //////////////////////////////////////////////////////////// void Text::setCharacterSize(unsigned int size) { @@ -256,9 +279,19 @@ float Text::getOutlineThickness() const } +//////////////////////////////////////////////////////////// +Vector2f Text::findCharacterPosSystemFont(std::size_t index) const +{ + return Vector2f(); +} + + //////////////////////////////////////////////////////////// Vector2f Text::findCharacterPos(std::size_t index) const { + if (m_useSystemFont) + return findCharacterPosSystemFont(index); + // Make sure that we have a valid font if (!m_font) return Vector2f(); @@ -318,10 +351,63 @@ FloatRect Text::getGlobalBounds() const } +//////////////////////////////////////////////////////////// +void Text::drawSystemFont(RenderTarget& target, RenderStates states) const +{ + ensureGeometryUpdate(); + states.transform *= getTransform(); +#ifdef _3DS + if (target.m_cache.viewChanged) + target.applyCurrentView(); + if (states.blendMode != target.m_cache.lastBlendMode) + target.applyBlendMode(states.blendMode); + if (states.shader) + target.applyShader(states.shader); + if (states.scissor != target.m_cache.lastScissor) + target.applyScissor(states.scissor); + + target.applyTransform(states.transform); + Mtx_Identity(MtxStack_Cur(CitroGetTextureMatrix())); + CitroUpdateMatrixStacks(); + + C3D_BufInfo* bufInfo = C3D_GetBufInfo(); + BufInfo_Init(bufInfo); + BufInfo_Add(bufInfo, &m_vertices[0], sizeof(Vertex), 3, 0x210); + + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvSrc(env, C3D_RGB, GPU_PRIMARY_COLOR, 0, 0); + C3D_TexEnvSrc(env, C3D_Alpha, GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0); + C3D_TexEnvOp(env, C3D_Both, 0, 0, 0); + C3D_TexEnvFunc(env, C3D_RGB, GPU_REPLACE); + C3D_TexEnvFunc(env, C3D_Alpha, GPU_MODULATE); + + int vertexIndex = 0; + int lastTextureIndex = -1; + for (Uint16 textureIndex : m_systemGlyphTextures) + { + if (lastTextureIndex != textureIndex) { + lastTextureIndex = textureIndex; + C3D_TexBind(0, system_font_textures[textureIndex].getNativeTexture()); + } + C3D_DrawArrays(GPU_TRIANGLE_STRIP, vertexIndex, 4); + vertexIndex += 4; + } +#endif + target.applyTexture(NULL); +} + + //////////////////////////////////////////////////////////// void Text::draw(RenderTarget& target, RenderStates states) const { - if (m_font) + if (m_string.isEmpty()) + return; + + if (m_useSystemFont) + { + drawSystemFont(target, states); + } + else if (m_font) { ensureGeometryUpdate(); @@ -337,11 +423,71 @@ void Text::draw(RenderTarget& target, RenderStates states) const } +//////////////////////////////////////////////////////////// +void Text::ensureGeometryUpdateSystemFont() const +{ +#ifndef EMULATION + m_systemGlyphTextures.clear(); + + float maxX = 0.f; + float x = 0.f; + float y = 0.f; + float scaleX = static_cast(m_characterSize) / 25.f; + float scaleY = scaleX; + bool baseline = false; + + ssize_t units; + uint32_t code; + + auto str = m_string.toUtf8(); + const uint8_t* p = str.c_str(); + float firstX = x; + int lastSheet = -1; + int vertexIndex = 0; + do + { + if (!*p) break; + units = decode_utf8(&code, p); + if (units == -1) + break; + p += units; + if (code == '\n') + { + x = firstX; + y += scaleY*fontGetInfo()->lineFeed; + } + else if (code > 0) + { + int glyphIdx = fontGlyphIndexFromCodePoint(code); + fontGlyphPos_s data; + fontCalcGlyphPos(&data, glyphIdx, GLYPH_POS_CALC_VTXCOORD, scaleX, scaleY); + + m_systemGlyphTextures.push_back(data.sheetIndex); + + m_vertices.append(Vertex(Vector2f(x+data.vtxcoord.left, y+data.vtxcoord.bottom), m_fillColor, Vector2f(data.texcoord.left, data.texcoord.bottom))); + m_vertices.append(Vertex(Vector2f(x+data.vtxcoord.right, y+data.vtxcoord.bottom), m_fillColor, Vector2f(data.texcoord.right, data.texcoord.bottom))); + m_vertices.append(Vertex(Vector2f(x+data.vtxcoord.left, y+data.vtxcoord.top), m_fillColor, Vector2f(data.texcoord.left, data.texcoord.top))); + m_vertices.append(Vertex(Vector2f(x+data.vtxcoord.right, y+data.vtxcoord.top), m_fillColor, Vector2f(data.texcoord.right, data.texcoord.top))); + + x += data.xAdvance; + if (x > maxX) + maxX = x; + } + } while (code > 0); + + m_bounds.left = 0; + m_bounds.top = 0; + m_bounds.width = maxX; + m_bounds.height = y + scaleY * fontGetInfo()->lineFeed; +#endif +} + + //////////////////////////////////////////////////////////// void Text::ensureGeometryUpdate() const { // Load system's opensans.tff if user attempts drawing without font - if (m_font == &priv::system_font && !priv::system_font_loaded) + if (!m_useSystemFont && m_font == &priv::system_font && !priv::system_font_loaded) { priv::system_font_loaded = true; priv::ResourceInfo font = priv::core_resources["opensans.ttf"]; @@ -364,6 +510,12 @@ void Text::ensureGeometryUpdate() const if (!m_font || m_string.isEmpty()) return; + if (m_useSystemFont) + { + ensureGeometryUpdateSystemFont(); + return; + } + // Compute values related to the text style bool bold = (m_style & Bold) != 0; bool underlined = (m_style & Underlined) != 0; diff --git a/src/cpp3ds/Graphics/Texture.cpp b/src/cpp3ds/Graphics/Texture.cpp index 6265daa..5108ef4 100644 --- a/src/cpp3ds/Graphics/Texture.cpp +++ b/src/cpp3ds/Graphics/Texture.cpp @@ -47,6 +47,17 @@ GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGBA8) | \ GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)) +#ifdef _3DS +typedef struct +{ + u16 format; //< Format matching ctrulib enum GPU_TEXCOLOR + u16 width; //< Width (original width to next power of 2) + u16 height; //< Height (original height to next power of 2) + u16 widthOriginal; //< Width of original input + u16 heightOriginal; //< Height of original input +} Header; +#endif + namespace { cpp3ds::Mutex mutex; @@ -113,6 +124,34 @@ namespace } } } + + inline size_t fmtSize(GPU_TEXCOLOR fmt) + { + switch (fmt) + { + case GPU_RGBA8: + return 32; + case GPU_RGB8: + return 24; + case GPU_RGBA5551: + case GPU_RGB565: + case GPU_RGBA4: + case GPU_LA8: + case GPU_HILO8: + return 16; + case GPU_L8: + case GPU_A8: + case GPU_LA4: + case GPU_ETC1A4: + return 8; + case GPU_L4: + case GPU_A4: + case GPU_ETC1: + return 4; + default: + return 0; + } + } } @@ -152,11 +191,12 @@ m_cacheId (getUniqueId()) //////////////////////////////////////////////////////////// Texture::~Texture() { - if (m_ownsData) - C3D_TexDelete(m_texture); - if (m_texture) + { + if (m_ownsData) + C3D_TexDelete(m_texture); delete m_texture; + } } @@ -298,6 +338,40 @@ bool Texture::loadFromImage(const Image& image, const IntRect& area) } +//////////////////////////////////////////////////////////// +bool Texture::loadFromPreprocessedFile(const std::string& filename) +{ + if (filename.empty()) + return false; + + Header header; + FileInputStream file; + if (!file.open(filename)) + return false; + + file.read(&header, sizeof(Header)); + size_t size = file.getSize() - sizeof(Header); + + // Verify header + GPU_TEXCOLOR format = static_cast(header.format); + if (size != header.width * header.height * fmtSize(format) / 8) + { + err() << "Improper file header: " << filename << std::endl; + return false; + } + + void *data = malloc(size); + file.read(data, size); + + bool ret = loadFromPreprocessedMemory(data, size, header.width, header.height, format, true); + m_size.x = header.widthOriginal; + m_size.y = header.heightOriginal; + + free(data); + return ret; +} + + //////////////////////////////////////////////////////////// bool Texture::loadFromPreprocessedFile(const std::string& filename, size_t width, size_t height, GPU_TEXCOLOR format) { @@ -306,6 +380,8 @@ bool Texture::loadFromPreprocessedFile(const std::string& filename, size_t width FileInputStream file; file.open(filename); + if (!file.open(filename)) + return false; size_t size = file.getSize(); void *data = malloc(size); @@ -353,6 +429,8 @@ bool Texture::loadFromPreprocessedMemory(void *data, size_t size, size_t width, m_texture->height = height; m_texture->width = width; + C3D_TexFlush(m_texture); + C3D_TexSetWrap(m_texture, m_isRepeated ? GPU_REPEAT : GPU_CLAMP_TO_EDGE, m_isRepeated ? GPU_REPEAT : GPU_CLAMP_TO_EDGE); @@ -385,17 +463,8 @@ Image Texture::copyToImage() const u32 *data = (u32*)linearAlloc(m_texture->size); - if (m_texture->width < 64 || m_texture->height < 64) imageUntile32((u8*)data, (u8*)m_texture->data, 0, 0, m_texture->width, m_texture->height, m_texture->width, m_texture->height); - else - { - u32 dim = GX_BUFFER_DIM(m_texture->width, m_texture->height); - GX_DisplayTransfer((u32*)m_texture->data, dim, data, dim, TEXTURE_TRANSFER_FLAGS); - gspWaitForPPF(); GSPGPU_FlushDataCache(data, m_texture->size); - for (int i = 0; i < m_texture->width * m_texture->height; ++i) - data[i] = __builtin_bswap32(data[i]); - } if ((m_size == m_actualSize) && !m_pixelsFlipped) { @@ -457,37 +526,11 @@ void Texture::update(const Uint8* pixels, unsigned int width, unsigned int heigh if (pixels && m_texture) { - if (m_texture->width < 64 || m_texture->height < 64) - { u8* dest = (u8*)m_texture->data; imageTile32(dest, pixels, x, y, width, height, m_texture->width, m_texture->height); C3D_TexFlush(m_texture); - } - else - { - const u32 *pixels32 = reinterpret_cast(pixels); - u32 *data = (u32*)linearAlloc(m_texture->size); - u32 dim = GX_BUFFER_DIM(m_texture->width, m_texture->height); - - GX_DisplayTransfer((u32*)m_texture->data, dim, data, dim, TEXTURE_TRANSFER_FLAGS); - gspWaitForPPF(); - - for (int h = 0; h < height; ++h) - { - for (int w = 0; w < width; ++w) - { - data[(y+h)*m_texture->width+x+w] = __builtin_bswap32(pixels32[(h*width) + w]); - } - } - GSPGPU_FlushDataCache(data, m_texture->size); - - GX_DisplayTransfer(data, dim, (u32*)m_texture->data, dim, TEXTURE_TRANSFER_FLAGS | GX_TRANSFER_OUT_TILED(1)); - gspWaitForPPF(); - - linearFree(data); - } m_pixelsFlipped = false; m_cacheId = getUniqueId(); diff --git a/src/cpp3ds/Network/Http.cpp b/src/cpp3ds/Network/Http.cpp index f2c06de..066f1cd 100644 --- a/src/cpp3ds/Network/Http.cpp +++ b/src/cpp3ds/Network/Http.cpp @@ -32,6 +32,8 @@ #include #include #include +#include <3ds/services/httpc.h> +#include namespace @@ -130,9 +132,9 @@ const std::string& Http::Response::getField(const std::string& field) const { return it->second; } - else + else if (m_context && m_context->httphandle) { - char val[255]; + char val[1024]; httpcGetResponseHeader(m_context, field.c_str(), val, sizeof(val)); m_fields[field] = val; return m_fields[field]; @@ -169,13 +171,20 @@ const std::string& Http::Response::getBody() const //////////////////////////////////////////////////////////// -void Http::Response::parse(httpcContext *context) +void Http::Response::parse(httpcContext *context, Time timeout) { Result ret; - u32 statusCode; + u32 statusCode = ConnectionFailed; m_context = context; - ret = httpcGetResponseStatusCode(context, &statusCode, 0); + if (R_FAILED(ret = httpcGetResponseStatusCodeTimeout(context, &statusCode, (u64)timeout.asMicroseconds() * 1000))) + { + if (ret == HTTPC_RESULTCODE_TIMEDOUT) + m_status = TimedOut; + else + err() << _("Failed to get HTTP status: 0x%08lX", ret).toAnsiString() << std::endl; + return; + } m_status = (Status)statusCode; } @@ -192,7 +201,7 @@ Http::Http() : m_host(), m_port(0) { - + m_context.httphandle = 0; } @@ -200,6 +209,26 @@ m_port(0) Http::Http(const std::string& host, unsigned short port) { setHost(host, port); + m_context.httphandle = 0; +} + + +//////////////////////////////////////////////////////////// +Http::~Http() +{ + close(); +} + + +//////////////////////////////////////////////////////////// +void Http::close() +{ + if (m_context.httphandle) + { + httpcCancelConnection(&m_context); + httpcCloseContext(&m_context); + m_context.httphandle = 0; + } } @@ -239,10 +268,24 @@ void Http::setHost(const std::string& host, unsigned short port) //////////////////////////////////////////////////////////// -Http::Response Http::sendRequest(const Http::Request& request, Time timeout, RequestCallback callback) +Http::Response Http::sendRequest(const Http::Request& request, Time timeout, RequestCallback callback, size_t bufferSize) { + close(); + + // Use 90 second default timeout for httpc + if (timeout == Time::Zero) + timeout = seconds(90); + // First make sure that the request is valid -- add missing mandatory fields Request toSend(request); + if (!toSend.hasField("User-Agent")) + { + toSend.setField("User-Agent", "Mozilla/5.0 (Nintendo 3DS; Mobile; rv:10.0) Gecko/20100101 libcpp3ds-network"); + } + if (!toSend.hasField("Connection")) + { + toSend.setField("Connection", "Keep-Alive"); + } if (!toSend.hasField("Content-Length")) { std::ostringstream out; @@ -258,6 +301,8 @@ Http::Response Http::sendRequest(const Http::Request& request, Time timeout, Req Response received; std::string url = m_hostUrl + toSend.m_uri; + if (url.empty()) + return received; HTTPC_RequestMethod method; switch (toSend.m_method) { case Request::Get: method = HTTPC_METHOD_GET; break; @@ -267,13 +312,19 @@ Http::Response Http::sendRequest(const Http::Request& request, Time timeout, Req case Request::Delete: method = HTTPC_METHOD_DELETE; break; } - // TODO: Handle return values / errors Result ret; - u32 contentSize; - ret = httpcOpenContext(&m_context, method, url.c_str(), 0); - ret = httpcSetClientCertDefault(&m_context, SSLC_DefaultClientCert_ClCertA); - ret = httpcSetSSLOpt(&m_context, SSLCOPT_DisableVerify); + u32 contentSize = 0; + if (R_FAILED(ret = httpcOpenContext(&m_context, method, url.c_str(), 1)) || + R_FAILED(ret = httpcSetClientCertDefault(&m_context, SSLC_DefaultClientCert_ClCertA)) || + R_FAILED(ret = httpcSetSSLOpt(&m_context, SSLCOPT_DisableVerify))) + { + if (m_context.httphandle) + err() << "lolwut" << std::endl; + err() << _("Failed to open HTTPC context: 0x%08lX", ret).toAnsiString() << std::endl; + return received; + } + // TODO: Handle return values / errors // Add fields for (auto i = toSend.m_fields.begin(); i != toSend.m_fields.end(); ++i) { @@ -283,9 +334,15 @@ Http::Response Http::sendRequest(const Http::Request& request, Time timeout, Req if (!toSend.m_body.empty()) httpcAddPostDataRaw(&m_context, (u32*)toSend.m_body.c_str(), toSend.m_body.size()); - ret = httpcBeginRequest(&m_context); + if (R_FAILED(ret = httpcBeginRequest(&m_context))) + { + err() << _("Failed to make HTTPC request: 0x%08lX", ret).toAnsiString() << std::endl; + return received; + } - received.parse(&m_context); + received.parse(&m_context, timeout); + if (received.getStatus() == Response::ConnectionFailed) + return received; ret = httpcGetDownloadSizeState(&m_context, NULL, &contentSize); @@ -293,16 +350,25 @@ Http::Response Http::sendRequest(const Http::Request& request, Time timeout, Req u32 size = 0; u32 lastProcessed = 0; u32 processed = 0; - u8 buffer[4*1024]; + u8 *buffer = new u8[bufferSize]; char *charBuf = reinterpret_cast(buffer); - - Result dlret = HTTPC_RESULTCODE_DOWNLOADPENDING; while (dlret == HTTPC_RESULTCODE_DOWNLOADPENDING) { - - dlret = httpcReceiveData(&m_context, buffer, sizeof(buffer)); + dlret = httpcReceiveDataTimeout(&m_context, buffer, bufferSize, (u64)timeout.asMicroseconds() * 1000); + if (dlret != HTTPC_RESULTCODE_DOWNLOADPENDING && dlret != 0) + { + if (dlret == HTTPC_RESULTCODE_TIMEDOUT) + received.m_status = Response::TimedOut; + else + { + err() << _("Failed to receieve HTTP data: 0x%08lX", dlret).toAnsiString() << std::endl; + if (callback) + callback(buffer, 0, processed, received); // Send 0 length callback + } + break; + } if (R_FAILED(ret = httpcGetDownloadSizeState(&m_context, &processed, NULL))) break; @@ -322,8 +388,7 @@ Http::Response Http::sendRequest(const Http::Request& request, Time timeout, Req } } - httpcCloseContext(&m_context); - + delete[] buffer; received.m_body = receivedStr; return received; } diff --git a/src/cpp3ds/Network/TcpSocket.cpp b/src/cpp3ds/Network/TcpSocket.cpp index a9c9d19..d7e3294 100644 --- a/src/cpp3ds/Network/TcpSocket.cpp +++ b/src/cpp3ds/Network/TcpSocket.cpp @@ -215,6 +215,13 @@ Socket::Status TcpSocket::connect(const IpAddress& remoteAddress, unsigned short if (getRemoteAddress() != cpp3ds::IpAddress::None) { // Connection accepted +#ifdef _3DS + if (isSecure()) + sslc_init(getSecureData(), getHandle(), remoteAddress.toString().c_str()); +#else + if (isSecure()) + SSL_connect(getSecureData().ssl); +#endif status = Done; } else diff --git a/src/cpp3ds/System/FileSystem.cpp b/src/cpp3ds/System/FileSystem.cpp index b12c00d..99f3809 100644 --- a/src/cpp3ds/System/FileSystem.cpp +++ b/src/cpp3ds/System/FileSystem.cpp @@ -6,17 +6,24 @@ namespace cpp3ds { const std::string FileSystem::getFilePath(const std::string& filename) { +#ifdef TEST + std::string pathPrefix = "../res/test/"; +#elif defined(EMULATION) + std::string pathPrefix = "../res/"; +#endif + #ifdef EMULATION std::string newpath; - if (filename.find("../res/romfs/") == 0) { + if (filename.find(pathPrefix + "romfs/") == 0) + return filename; + if (filename.find(pathPrefix + "sdmc/") == 0) return filename; - } if (filename.find("sdmc:/") == 0) { newpath = filename; newpath.erase(0, 5); - return "../res/sdmc" + newpath; + return pathPrefix + "sdmc" + newpath; } - newpath = "../res/romfs/" + filename; + newpath = pathPrefix + "romfs/" + filename; return newpath; #else return filename; diff --git a/src/cpp3ds/System/I18n.cpp b/src/cpp3ds/System/I18n.cpp index cf77a37..fb33aa1 100644 --- a/src/cpp3ds/System/I18n.cpp +++ b/src/cpp3ds/System/I18n.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #define TOKEN_COMMENT '#' @@ -50,14 +51,12 @@ I18n::I18n() #ifdef EMULATION langcode = 1; // TODO: get actual locale of PC #else - ret = CFGU_GetSystemLanguage(&langcode); - if (!ret) { + if (R_FAILED(ret = CFGU_GetSystemLanguage(&langcode))) // If the syscall fails, as it does with Citra-emu, default to English langcode = 1; - } + std::cout << (int)langcode << std::endl; #endif - m_language = static_cast(langcode); - loadFromLanguage(m_language); + loadFromLanguage(static_cast(langcode)); } @@ -71,7 +70,7 @@ const std::string I18n::getLangString(const Language langcode) const case Italian: return "it"; case Spanish: return "es"; case ChineseSimplified: return "zh"; - case Korean: return "ko"; + case Korean: return "kr"; case Dutch: return "nl"; case Portuguese: return "pt"; case Russian: return "ru"; @@ -89,12 +88,13 @@ void I18n::loadLanguage(Language language) Language I18n::getLanguage() { - return static_cast(getInstance().m_language); + return getInstance().m_language; } bool I18n::loadFromLanguage(const Language language) { + m_language = language; std::string filename = "lang/" + getLangString(language) + ".lang"; return loadFromFile(filename); } @@ -106,12 +106,18 @@ void I18n::loadLanguageFile(const std::string& filename) } +void I18n::clearLoadedLanguage() +{ + getInstance().m_content.clear(); +} + + bool I18n::loadFromFile(const std::string filename) { - std::ifstream file(filename); + m_content.clear(); + std::ifstream file(FileSystem::getFilePath(filename)); if (file) { - m_content.clear(); std::string line; std::string content; const std::map replaceList = { diff --git a/src/cpp3ds/System/String.cpp b/src/cpp3ds/System/String.cpp index d66529c..27e3dd9 100644 --- a/src/cpp3ds/System/String.cpp +++ b/src/cpp3ds/System/String.cpp @@ -73,7 +73,7 @@ String::String(const char* ansiString, const std::locale& locale) if (length > 0) { m_string.reserve(length + 1); - Utf32::fromAnsi(ansiString, ansiString + length, std::back_inserter(m_string), locale); + Utf8::toUtf32(ansiString, ansiString + length, std::back_inserter(m_string)); } } } @@ -83,7 +83,7 @@ String::String(const char* ansiString, const std::locale& locale) String::String(const std::string& ansiString, const std::locale& locale) { m_string.reserve(ansiString.length() + 1); - Utf32::fromAnsi(ansiString.begin(), ansiString.end(), std::back_inserter(m_string), locale); + Utf8::toUtf32(ansiString.begin(), ansiString.end(), std::back_inserter(m_string)); } @@ -154,7 +154,7 @@ std::string String::toAnsiString(const std::locale& locale) const output.reserve(m_string.length() + 1); // Convert - Utf32::toAnsi(m_string.begin(), m_string.end(), std::back_inserter(output), 0, locale); + Utf32::toUtf8(m_string.begin(), m_string.end(), std::back_inserter(output)); return output; } @@ -168,7 +168,7 @@ std::wstring String::toWideString() const output.reserve(m_string.length() + 1); // Convert - Utf32::toWide(m_string.begin(), m_string.end(), std::back_inserter(output), 0); + Utf32::toUtf8(m_string.begin(), m_string.end(), std::back_inserter(output)); return output; } diff --git a/src/cpp3ds/System/Thread.cpp b/src/cpp3ds/System/Thread.cpp index 5a53f7a..5ddc987 100644 --- a/src/cpp3ds/System/Thread.cpp +++ b/src/cpp3ds/System/Thread.cpp @@ -27,19 +27,16 @@ //////////////////////////////////////////////////////////// #include #include - +#include namespace cpp3ds { //////////////////////////////////////////////////////////// void Thread::initialize() { - s32 prio = 0; - svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); - m_stackSize = 32 * 1024; - m_priority = prio - 1; m_affinity = -2; + setRelativePriority(1); } @@ -109,6 +106,16 @@ void Thread::setPriority(int priority) m_priority = priority; } +void Thread::setRelativePriority(int relPriority) +{ + s32 priority; + svcGetThreadPriority(&priority, CUR_THREAD_HANDLE); + priority += relPriority; + if (priority < 0x18) priority = 0x18; + if (priority > 0x3F) priority = 0x3F; + m_priority = priority; +} + void Thread::setAffinity(int affinity) { m_affinity = affinity; diff --git a/src/cpp3ds/Window/EventManager.cpp b/src/cpp3ds/Window/EventManager.cpp index 318ba52..22138c2 100644 --- a/src/cpp3ds/Window/EventManager.cpp +++ b/src/cpp3ds/Window/EventManager.cpp @@ -6,7 +6,7 @@ namespace cpp3ds { EventManager::EventManager() { - m_joystickThreshold = 10.f; + m_joystickThreshold = 15.f; } bool EventManager::pollEvent(Event& event) { diff --git a/src/emu3ds/CMakeLists.txt b/src/emu3ds/CMakeLists.txt index cc51cc8..cb56330 100644 --- a/src/emu3ds/CMakeLists.txt +++ b/src/emu3ds/CMakeLists.txt @@ -115,10 +115,18 @@ set(SRC ) if(ENABLE_OGG) + find_package(Vorbis REQUIRED) + include_directories(${VORBIS_INCLUDE_DIRS}) list(APPEND SRC ${SRCROOT}/Audio/SoundFileReaderOgg.cpp ${SRCROOT}/Audio/SoundFileWriterOgg.cpp) endif() +if(ENABLE_AAC) + find_package(Faad REQUIRED) + include_directories(${FAAD_INCLUDE_DIRS}) + list(APPEND SRC + ${SRCROOT}/Audio/SoundFileReaderAAC.cpp) +endif() if(ENABLE_MP3) find_package(mpg123 REQUIRED) include_directories(${MPG123_INCLUDE_DIRS}) diff --git a/src/emu3ds/Graphics/RenderTarget.cpp b/src/emu3ds/Graphics/RenderTarget.cpp index 45a3b37..0fe4bd9 100644 --- a/src/emu3ds/Graphics/RenderTarget.cpp +++ b/src/emu3ds/Graphics/RenderTarget.cpp @@ -250,6 +250,10 @@ void RenderTarget::draw(const Vertex* vertices, unsigned int vertexCount, if (states.blendMode != m_cache.lastBlendMode) applyBlendMode(states.blendMode); + // Apply the scissor mode + if (states.scissor != m_cache.lastScissor) + applyScissor(states.scissor); + // Apply the texture Uint64 textureId = states.texture ? states.texture->m_cacheId : 0; if (textureId != m_cache.lastTextureId) @@ -272,28 +276,10 @@ void RenderTarget::draw(const Vertex* vertices, unsigned int vertexCount, // Setup the pointers to the vertices' components if (vertices) { - #ifdef EMULATION - const char* data = reinterpret_cast(vertices); - glCheck(glVertexPointer(2, GL_FLOAT, sizeof(Vertex), data + 0)); - glCheck(glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), data + 8)); // 8 = sizeof(Vector2f) - glCheck(glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), data + 12)); // 12 = 8 + sizeof(Color) - #else - // Temorary workaround until gl3ds can get VAO gl*Pointer functions working - u32 bufferOffsets[] = {0}; - u64 bufferPermutations[] = {0x210}; - u8 bufferAttribCounts[] = {3}; - GPU_SetAttributeBuffers( - 3, // number of attributes - (u32*)osConvertVirtToPhys(vertices), - GPU_ATTRIBFMT(0, 2, GPU_FLOAT) | GPU_ATTRIBFMT(1, 4, GPU_UNSIGNED_BYTE) | GPU_ATTRIBFMT(2, 2, GPU_FLOAT), - 0xFF8, //0b1100 - 0x210, - 1, //number of buffers - bufferOffsets, - bufferPermutations, - bufferAttribCounts // number of attributes for each buffer - ); - #endif + const char* data = reinterpret_cast(vertices); + glCheck(glVertexPointer(2, GL_FLOAT, sizeof(Vertex), data + 0)); + glCheck(glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), data + 8)); // 8 = sizeof(Vector2f) + glCheck(glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), data + 12)); // 12 = 8 + sizeof(Color) } // Find the OpenGL primitive type @@ -395,6 +381,7 @@ void RenderTarget::resetGLStates() applyBlendMode(BlendAlpha); applyTransform(Transform::Identity); applyTexture(NULL); + applyScissor(UintRect()); if (shaderAvailable) applyShader(NULL); @@ -453,6 +440,21 @@ void RenderTarget::applyBlendMode(const BlendMode& mode) } +//////////////////////////////////////////////////////////// +void RenderTarget::applyScissor(const UintRect& rect) +{ + if (rect == UintRect()) { + glCheck(glDisable(GL_SCISSOR_TEST)); + } else { + int y = getSize().y - (rect.top + rect.height); + if (y < 0) y = 0; + glCheck(glEnable(GL_SCISSOR_TEST)); + glScissor(rect.left, y, rect.width, rect.height); + } + m_cache.lastScissor = rect; +} + + //////////////////////////////////////////////////////////// void RenderTarget::applyTransform(const Transform& transform) { diff --git a/src/emu3ds/Network/Http.cpp b/src/emu3ds/Network/Http.cpp index 5734921..3f89d5d 100644 --- a/src/emu3ds/Network/Http.cpp +++ b/src/emu3ds/Network/Http.cpp @@ -312,6 +312,20 @@ Http::Http(const std::string& host, unsigned short port) } +//////////////////////////////////////////////////////////// +Http::~Http() +{ + // +} + + +//////////////////////////////////////////////////////////// +void Http::close() +{ + // +} + + //////////////////////////////////////////////////////////// void Http::setHost(const std::string& host, unsigned short port) { @@ -347,7 +361,7 @@ void Http::setHost(const std::string& host, unsigned short port) //////////////////////////////////////////////////////////// -Http::Response Http::sendRequest(const Http::Request& request, Time timeout, RequestCallback callback) +Http::Response Http::sendRequest(const Http::Request& request, Time timeout, RequestCallback callback, size_t bufferSize) { // First make sure that the request is valid -- add missing mandatory fields Request toSend(request); @@ -375,7 +389,7 @@ Http::Response Http::sendRequest(const Http::Request& request, Time timeout, Req } if ((toSend.m_majorVersion * 10 + toSend.m_minorVersion >= 11) && !toSend.hasField("Connection")) { - toSend.setField("Connection", "close"); + toSend.setField("Connection", "Keep-Alive"); } // Prepare the response @@ -396,8 +410,8 @@ Http::Response Http::sendRequest(const Http::Request& request, Time timeout, Req std::string receivedStr; std::size_t size = 0; std::size_t processed = 0; - char buffer[4*1024]; - while (m_connection.receive(buffer, sizeof(buffer), size) == Socket::Done) + char *buffer = new char[bufferSize]; + while (m_connection.receive(buffer, bufferSize, size) == Socket::Done) { if (callback) { @@ -426,6 +440,8 @@ Http::Response Http::sendRequest(const Http::Request& request, Time timeout, Req receivedStr.append(buffer, buffer + size); } + delete[] buffer; + // Build the Response object from the received data if (!callback) received.parse(receivedStr); diff --git a/src/emu3ds/Network/Socket.cpp b/src/emu3ds/Network/Socket.cpp index 36a1606..b9fa587 100644 --- a/src/emu3ds/Network/Socket.cpp +++ b/src/emu3ds/Network/Socket.cpp @@ -53,7 +53,8 @@ namespace cpp3ds Socket::Socket(Type type, bool secure) : m_type (type), m_socket (priv::SocketImpl::invalidSocket()), -m_isBlocking(true) +m_isBlocking(true), +m_isSecure (secure) { m_secureData.ssl = nullptr; m_secureData.sslMethod = nullptr; @@ -142,7 +143,8 @@ void Socket::create() m_secureData.sslContext = SSL_CTX_new(m_secureData.sslMethod); m_secureData.ssl = SSL_new(m_secureData.sslContext); SSL_set_verify(m_secureData.ssl, SSL_VERIFY_NONE, nullptr); - SSL_set_fd(m_secureData.ssl, m_socket); + if (!SSL_set_fd(m_secureData.ssl, m_socket)) + err() << "SSL_set_fd() failed." << std::endl; } } diff --git a/src/emu3ds/System/Thread.cpp b/src/emu3ds/System/Thread.cpp index 3da9e44..6587b62 100644 --- a/src/emu3ds/System/Thread.cpp +++ b/src/emu3ds/System/Thread.cpp @@ -87,6 +87,11 @@ void Thread::setPriority(int priority) m_priority = priority; } +void Thread::setRelativePriority(int priority) +{ + m_priority = priority; +} + void Thread::setAffinity(int affinity) { m_affinity = affinity; diff --git a/src/emu3ds/Window/EventManager.cpp b/src/emu3ds/Window/EventManager.cpp index 8422e59..e4cf5ab 100644 --- a/src/emu3ds/Window/EventManager.cpp +++ b/src/emu3ds/Window/EventManager.cpp @@ -8,6 +8,10 @@ namespace cpp3ds { // TODO: configurable key-mapping std::map keyMap = { + {sf::Keyboard::Up, cpp3ds::Keyboard::Up}, + {sf::Keyboard::Left, cpp3ds::Keyboard::Left}, + {sf::Keyboard::Right, cpp3ds::Keyboard::Right}, + {sf::Keyboard::Down, cpp3ds::Keyboard::Down}, {sf::Keyboard::A, cpp3ds::Keyboard::A}, {sf::Keyboard::B, cpp3ds::Keyboard::B}, {sf::Keyboard::X, cpp3ds::Keyboard::X}, @@ -52,6 +56,7 @@ bool EventManager::filterEvent(const Event& event) { //////////////////////////////////////////////////////////// void EventManager::processEvents() { +#ifndef TEST int BOTTOM_X = 40, BOTTOM_Y = 240, BOTTOM_WIDTH = 320; @@ -111,6 +116,7 @@ void EventManager::processEvents() { break; } } +#endif } //////////////////////////////////////////////////////////// diff --git a/src/emu3ds/Window/Game.cpp b/src/emu3ds/Window/Game.cpp index 744ab8d..eb164e1 100644 --- a/src/emu3ds/Window/Game.cpp +++ b/src/emu3ds/Window/Game.cpp @@ -5,7 +5,7 @@ #include #include #include -#include