├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── buildsupport └── link_map.ld ├── cmake ├── AddPlaydateApplication.cmake └── CompileTargetForPlaydate.cmake ├── examples ├── CMakeLists.txt ├── hello_world │ ├── CMakeLists.txt │ └── main.cpp └── particles │ ├── CMakeLists.txt │ ├── Source │ ├── font │ │ ├── namco-1x-table-9-9.png │ │ └── namco-1x.fnt │ ├── images │ │ ├── snowflake1.png │ │ ├── snowflake2.png │ │ ├── snowflake3.png │ │ └── snowflake4.png │ ├── main.lua │ └── pdxinfo │ └── main.cpp ├── inc └── pdcpp │ └── pdnewlib.h ├── readme.md └── src ├── pdnewdelete.cpp ├── pdnewlib.c └── setup.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | .vscode 3 | build/ 4 | *.pdx -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project("PlaydateCPP") 2 | 3 | option(PDCPP_STAGE_IN_BINARY_DIR "Use CMake binary dir (instead of source dir) to stage files" OFF) 4 | 5 | set(ENVSDK $ENV{PLAYDATE_SDK_PATH}) 6 | file(TO_CMAKE_PATH ${ENVSDK} SDK) 7 | set(SDK ${SDK} PARENT_SCOPE) 8 | message(STATUS "Playdate SDK Path: " ${SDK}) 9 | set(PDC "${SDK}/bin/pdc" -sdkpath "${SDK}" CACHE FILEPATH "path to the Playdate Compiler") 10 | 11 | set(CMAKE_C_STANDARD 11) 12 | set(CMAKE_CXX_STANDARD 20) 13 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 14 | 15 | include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CompileTargetForPlaydate.cmake) 16 | include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AddPlaydateApplication.cmake) 17 | 18 | # Build the C API as a static library first 19 | add_library(playdate_sdk STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/setup.c) 20 | 21 | target_compile_definitions(playdate_sdk PUBLIC TARGET_EXTENSION=1) 22 | 23 | if (TOOLCHAIN STREQUAL "armgcc") 24 | message(STATUS "Building for Playdate hardware") 25 | 26 | # Device-only 27 | set(HEAP_SIZE 8388208) 28 | set(STACK_SIZE 61800) 29 | set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -x assembler-with-cpp -D__HEAP_SIZE=${HEAP_SIZE} -D__STACK_SIZE=${STACK_SIZE}") 30 | set(MCFLAGS -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-sp-d16 -D__FPU_USED=1) 31 | 32 | target_compile_definitions(playdate_sdk PUBLIC TARGET_PLAYDATE=1) 33 | target_compile_options(playdate_sdk PUBLIC -Wall -Wno-unknown-pragmas -Wdouble-promotion) 34 | target_compile_options(playdate_sdk PRIVATE $<$:-O2>) 35 | target_compile_options(playdate_sdk INTERFACE $<$:-O0>) 36 | target_compile_options(playdate_sdk PUBLIC $<$:-O3>) 37 | 38 | target_compile_options(playdate_sdk PUBLIC ${MCFLAGS}) 39 | target_compile_options(playdate_sdk PUBLIC -falign-functions=16 -fomit-frame-pointer) 40 | target_compile_options(playdate_sdk PUBLIC -gdwarf-2) 41 | target_compile_options(playdate_sdk PUBLIC -fverbose-asm) 42 | target_compile_options(playdate_sdk PUBLIC -ffunction-sections -fdata-sections) 43 | target_compile_options(playdate_sdk PUBLIC -mword-relocations -fno-common) 44 | target_compile_options(playdate_sdk PUBLIC $<$:-fno-exceptions>) 45 | 46 | target_link_options(playdate_sdk PUBLIC ${MCFLAGS}) 47 | target_link_options(playdate_sdk PUBLIC -T${CMAKE_CURRENT_SOURCE_DIR}/buildsupport/link_map.ld) 48 | target_link_options(playdate_sdk PUBLIC "-Wl,-Map=game.map,--cref,--gc-sections,--no-warn-mismatch,--emit-relocs") 49 | target_link_options(playdate_sdk PUBLIC --entry eventHandlerShim) 50 | else () 51 | # Simulator build defs 52 | target_compile_definitions(playdate_sdk PUBLIC TARGET_SIMULATOR=1) 53 | if (MSVC) 54 | target_compile_definitions(playdate_sdk PUBLIC _WINDLL=1) 55 | target_compile_options(playdate_sdk PUBLIC /W3) 56 | target_compile_options(playdate_sdk PUBLIC $<$:/Od>) 57 | else() 58 | target_compile_options(playdate_sdk PUBLIC -Wall -Wstrict-prototypes -Wno-unknown-pragmas -Wdouble-promotion) 59 | target_compile_options(playdate_sdk PUBLIC $<$:-ggdb -O0>) 60 | endif() 61 | endif() 62 | target_include_directories(playdate_sdk PUBLIC ${SDK}/C_API) 63 | 64 | # now we can build the core PDCPP Core library 65 | add_library(pdcpp_core STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/pdnewlib.c ${CMAKE_CURRENT_SOURCE_DIR}/src/pdnewdelete.cpp) 66 | target_include_directories(pdcpp_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc) 67 | target_link_libraries(pdcpp_core playdate_sdk) 68 | 69 | if (PDCPP_BUILD_EXAMPLES) 70 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/examples) 71 | endif () 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Metaphase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /buildsupport/link_map.ld: -------------------------------------------------------------------------------- 1 | ENTRY(eventHandlerShim) 2 | GROUP(libgcc.a libc.a libm.a) 3 | 4 | SECTIONS 5 | { 6 | .text : 7 | { 8 | *(.text) 9 | *(.text.*) 10 | 11 | KEEP(*(.init)) 12 | KEEP(*(.fini)) 13 | 14 | /* .ctors */ 15 | *crtbegin.o(.ctors) 16 | *crtbegin?.o(.ctors) 17 | *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) 18 | *(SORT(.ctors.*)) 19 | *(.ctors) 20 | 21 | /* .dtors */ 22 | *crtbegin.o(.dtors) 23 | *crtbegin?.o(.dtors) 24 | *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) 25 | *(SORT(.dtors.*)) 26 | *(.dtors) 27 | 28 | *(.rodata*) 29 | 30 | KEEP(*(.eh_frame*)) 31 | 32 | } 33 | 34 | .data : 35 | { 36 | __etext = .; 37 | 38 | __data_start__ = .; 39 | *(vtable) 40 | *(.data*) 41 | 42 | . = ALIGN(4); 43 | PROVIDE (__exidx_start = .); 44 | *(.ARM.exidx* *exidx.*) 45 | PROVIDE (__exidx_end = .); 46 | 47 | . = ALIGN(4); 48 | /* preinit data */ 49 | PROVIDE_HIDDEN (__preinit_array_start = .); 50 | KEEP(*(.preinit_array)) 51 | PROVIDE_HIDDEN (__preinit_array_end = .); 52 | 53 | . = ALIGN(4); 54 | /* init data */ 55 | PROVIDE_HIDDEN (__init_array_start = .); 56 | KEEP(*(SORT(.init_array.*))) 57 | KEEP(*(.init_array)) 58 | PROVIDE_HIDDEN (__init_array_end = .); 59 | 60 | . = ALIGN(4); 61 | /* finit data */ 62 | PROVIDE_HIDDEN (__fini_array_start = .); 63 | KEEP(*(SORT(.fini_array.*))) 64 | KEEP(*(.fini_array)) 65 | PROVIDE_HIDDEN (__fini_array_end = .); 66 | 67 | . = ALIGN(4); 68 | /* All data end */ 69 | __data_end__ = .; 70 | 71 | } 72 | 73 | .bss : 74 | { 75 | . = ALIGN(4); 76 | __bss_start__ = .; 77 | *(.bss*) 78 | *(COMMON) 79 | *(COM) 80 | . = ALIGN(4); 81 | __bss_end__ = .; 82 | 83 | } 84 | 85 | /DISCARD/ : 86 | { 87 | *(.ARM.exidx) 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /cmake/AddPlaydateApplication.cmake: -------------------------------------------------------------------------------- 1 | function(add_playdate_application PLAYDATE_GAME_NAME) 2 | message(STATUS "Adding playdate application ${PLAYDATE_GAME_NAME}") 3 | 4 | if (PDCPP_STAGE_IN_BINARY_DIR) 5 | set(PDCPP_STAGING_DIR ${CMAKE_CURRENT_BINARY_DIR}) 6 | else () 7 | set(PDCPP_STAGING_DIR ${CMAKE_CURRENT_SOURCE_DIR}) 8 | endif () 9 | 10 | if (TOOLCHAIN STREQUAL "armgcc") 11 | add_executable(${PLAYDATE_GAME_NAME}) 12 | 13 | set_property(TARGET ${PLAYDATE_GAME_NAME} PROPERTY OUTPUT_NAME "${PLAYDATE_GAME_NAME}.elf") 14 | 15 | add_custom_command( 16 | TARGET ${PLAYDATE_GAME_NAME} POST_BUILD 17 | COMMAND ${CMAKE_COMMAND} -E copy 18 | ${CMAKE_CURRENT_BINARY_DIR}/${BUILD_SUB_DIR}${PLAYDATE_GAME_NAME}.elf 19 | ${PDCPP_STAGING_DIR}/Source/pdex.elf 20 | ) 21 | 22 | add_custom_command( 23 | TARGET ${PLAYDATE_GAME_NAME} POST_BUILD 24 | COMMAND ${CMAKE_STRIP} --strip-unneeded -R .comment -g 25 | ${PLAYDATE_GAME_NAME}.elf 26 | -o ${PDCPP_STAGING_DIR}/Source/pdex.elf 27 | ) 28 | 29 | add_custom_command( 30 | TARGET ${PLAYDATE_GAME_NAME} POST_BUILD 31 | COMMAND ${PDC} Source ${PLAYDATE_GAME_NAME}.pdx 32 | WORKING_DIRECTORY ${PDCPP_STAGING_DIR} 33 | ) 34 | 35 | set_property( 36 | TARGET ${PLAYDATE_GAME_NAME} PROPERTY ADDITIONAL_CLEAN_FILES 37 | ${PDCPP_STAGING_DIR}/${PLAYDATE_GAME_NAME}.pdx 38 | ) 39 | 40 | else () 41 | add_library(${PLAYDATE_GAME_NAME} SHARED) 42 | 43 | if (MSVC) 44 | if(${CMAKE_GENERATOR} MATCHES "Visual Studio*" ) 45 | set(BUILD_SUB_DIR $/) 46 | file(TO_NATIVE_PATH ${SDK}/bin/PlaydateSimulator.exe SIMPATH) 47 | file(TO_NATIVE_PATH ${PDCPP_STAGING_DIR}/${PLAYDATE_GAME_NAME}.pdx SIMGAMEPATH) 48 | set_property(TARGET ${PLAYDATE_GAME_NAME} PROPERTY VS_DEBUGGER_COMMAND ${SIMPATH}) 49 | set_property(TARGET ${PLAYDATE_GAME_NAME} PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "\"${SIMGAMEPATH}\"") 50 | endif() 51 | 52 | add_custom_command( 53 | TARGET ${PLAYDATE_GAME_NAME} POST_BUILD 54 | COMMAND ${CMAKE_COMMAND} -E copy 55 | ${CMAKE_CURRENT_BINARY_DIR}/${BUILD_SUB_DIR}${PLAYDATE_GAME_NAME}.dll 56 | ${PDCPP_STAGING_DIR}/Source/pdex.dll) 57 | elseif (MINGW) 58 | add_custom_command( 59 | TARGET ${PLAYDATE_GAME_NAME} POST_BUILD 60 | COMMAND ${CMAKE_COMMAND} -E copy 61 | ${CMAKE_CURRENT_BINARY_DIR}/lib${PLAYDATE_GAME_NAME}.dll 62 | ${PDCPP_STAGING_DIR}/Source/pdex.dll) 63 | elseif(APPLE) 64 | if(${CMAKE_GENERATOR} MATCHES "Xcode" ) 65 | set(BUILD_SUB_DIR $/) 66 | set_property(TARGET ${PLAYDATE_GAME_NAME} PROPERTY XCODE_SCHEME_ARGUMENTS \"${PDCPP_STAGING_DIR}/${PLAYDATE_GAME_NAME}.pdx\") 67 | set_property(TARGET ${PLAYDATE_GAME_NAME} PROPERTY XCODE_SCHEME_EXECUTABLE ${SDK}/bin/Playdate\ Simulator.app) 68 | endif() 69 | 70 | add_custom_command( 71 | TARGET ${PLAYDATE_GAME_NAME} POST_BUILD 72 | COMMAND ${CMAKE_COMMAND} -E copy 73 | ${CMAKE_CURRENT_BINARY_DIR}/${BUILD_SUB_DIR}lib${PLAYDATE_GAME_NAME}.dylib 74 | ${PDCPP_STAGING_DIR}/Source/pdex.dylib) 75 | 76 | elseif(UNIX) 77 | add_custom_command( 78 | TARGET ${PLAYDATE_GAME_NAME} POST_BUILD 79 | COMMAND ${CMAKE_COMMAND} -E copy 80 | ${CMAKE_CURRENT_BINARY_DIR}/lib${PLAYDATE_GAME_NAME}.so 81 | ${PDCPP_STAGING_DIR}/Source/pdex.so) 82 | else() 83 | message(FATAL_ERROR "Platform not supported!") 84 | endif() 85 | 86 | set_property( 87 | TARGET ${PLAYDATE_GAME_NAME} PROPERTY ADDITIONAL_CLEAN_FILES 88 | ${PDCPP_STAGING_DIR}/${PLAYDATE_GAME_NAME}.pdx 89 | ) 90 | 91 | add_custom_command( 92 | TARGET ${PLAYDATE_GAME_NAME} POST_BUILD 93 | COMMAND ${PDC} ${PDCPP_STAGING_DIR}/Source 94 | ${PDCPP_STAGING_DIR}/${PLAYDATE_GAME_NAME}.pdx) 95 | endif() 96 | 97 | target_link_libraries(${PLAYDATE_GAME_NAME} PUBLIC pdcpp_core) 98 | endfunction() 99 | -------------------------------------------------------------------------------- /cmake/CompileTargetForPlaydate.cmake: -------------------------------------------------------------------------------- 1 | function(modify_target_for_playdate TARGET) 2 | if (TOOLCHAIN STREQUAL "armgcc") 3 | set(MCFLAGS -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-sp-d16 -D__FPU_USED=1) 4 | target_compile_options(${TARGET} PUBLIC $<$:-O0>) 5 | target_compile_options(${TARGET} PUBLIC $<$:-O2>) 6 | target_compile_options(${TARGET} PUBLIC ${MCFLAGS}) 7 | target_compile_options(${TARGET} PUBLIC -falign-functions=16 -fomit-frame-pointer) 8 | target_compile_options(${TARGET} PUBLIC -gdwarf-2) 9 | target_compile_options(${TARGET} PUBLIC -fverbose-asm) 10 | target_compile_options(${TARGET} PUBLIC -ffunction-sections -fdata-sections) 11 | target_compile_options(${TARGET} PUBLIC -mword-relocations -fno-common) 12 | endif() 13 | endfunction() -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | message(STATUS "Building PDCPP Base Examples") 2 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/hello_world) 3 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/particles) 4 | -------------------------------------------------------------------------------- /examples/hello_world/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | project("HelloWorldCPP") 3 | 4 | # C++ 20 and up is required in order to include the playdate headers 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_CONFIGURATION_TYPES "Debug;Release") 8 | set(CMAKE_XCODE_GENERATE_SCHEME TRUE) 9 | 10 | # As this is part of the repo, we can't demonstrate this aptly, but for a full 11 | # project, you will want to include this project in order to use the build 12 | # system, which might looks something like this: 13 | # 14 | # add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/submodules/playdate-cpp) 15 | 16 | # Now we can declare our application 17 | add_playdate_application(HelloWorld) 18 | 19 | # Add its sources, and you're good to go! 20 | target_sources(HelloWorld PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) 21 | -------------------------------------------------------------------------------- /examples/hello_world/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Hello World CPP 3 | * 4 | * Reproduces the Hello World Example from the Playdate C SDK, but with C++ 5 | * instead. A full game can be built from similar principles. 6 | * 7 | * MrBZapp 9/2023 8 | */ 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | constexpr int TEXT_WIDTH = 86; 15 | constexpr int TEXT_HEIGHT = 16; 16 | 17 | /** 18 | * This Class contains the entire logic of the "game" 19 | */ 20 | class HelloWorld 21 | { 22 | public: 23 | explicit HelloWorld(PlaydateAPI* api) 24 | : pd(api) 25 | , fontpath("/System/Fonts/Asheville-Sans-14-Bold.pft") 26 | , x((400 - TEXT_WIDTH) / 2) 27 | , y((240 - TEXT_HEIGHT) / 2) 28 | , dx(1) , dy(2) 29 | { 30 | const char* err; 31 | font = pd->graphics->loadFont(fontpath.c_str(), &err); 32 | 33 | if (font == nullptr) 34 | pd->system->error("%s:%i Couldn't load font %s: %s", __FILE__, __LINE__, fontpath.c_str(), err); 35 | } 36 | 37 | void update() 38 | { 39 | pd->graphics->clear(kColorWhite); 40 | pd->graphics->setFont(font); 41 | pd->graphics->drawText("Hello C++!", strlen("Hello World!"), kASCIIEncoding, x, y); 42 | 43 | x += dx; 44 | y += dy; 45 | 46 | if ( x < 0 || x > LCD_COLUMNS - TEXT_WIDTH ) 47 | { 48 | dx = -dx; 49 | } 50 | 51 | if ( y < 0 || y > LCD_ROWS - TEXT_HEIGHT ) 52 | { 53 | dy = -dy; 54 | } 55 | 56 | pd->system->drawFPS(0,0); 57 | } 58 | 59 | private: 60 | PlaydateAPI* pd; 61 | std::string fontpath; 62 | LCDFont* font; 63 | int x, y, dx, dy; 64 | }; 65 | 66 | /** 67 | * You can use STL containers! Be careful with some stdlib objects which rely 68 | * on an OS though, those will cause your app to crash on launch. 69 | */ 70 | std::unique_ptr helloWorld; 71 | 72 | 73 | /** 74 | * The Playdate API requires a C-style, or static function to be called as the 75 | * main update function. Here we use such a function to delegate execution to 76 | * our class. 77 | */ 78 | static int gameTick(void* userdata) 79 | { 80 | helloWorld->update(); 81 | return 1; 82 | }; 83 | 84 | 85 | /** 86 | * the `eventHandler` function is the entry point for all Playdate applications 87 | * and Lua extensions. It requires C-style linkage (no name mangling) so we 88 | * must wrap the entire thing in this `extern "C" {` block 89 | */ 90 | #ifdef __cplusplus 91 | extern "C" { 92 | #endif 93 | 94 | /** 95 | * This is the main event handler. Using the `Init` and `Terminate` events, we 96 | * allocate and free the `HelloWorld` handler accordingly. 97 | * 98 | * You only need this `_WINDLL` block if you want to run this on a simulator on 99 | * a windows machine, but for the sake of broad compatibility, we'll leave it 100 | * here. 101 | */ 102 | #ifdef _WINDLL 103 | __declspec(dllexport) 104 | #endif 105 | int eventHandler(PlaydateAPI* pd, PDSystemEvent event, uint32_t arg) 106 | { 107 | /* 108 | * This is required, otherwise linker errors abound 109 | */ 110 | eventHandler_pdnewlib(pd, event, arg); 111 | 112 | // Initialization just creates our "game" object 113 | if (event == kEventInit) 114 | { 115 | pd->display->setRefreshRate(20); 116 | helloWorld = std::make_unique(pd); 117 | 118 | // and sets the tick function to be called on update to turn off the 119 | // typical 'Lua'-ness. 120 | pd->system->setUpdateCallback(gameTick, pd); 121 | } 122 | 123 | // Destroy the global state to prevent memory leaks 124 | if (event == kEventTerminate) 125 | { 126 | pd->system->logToConsole("Tearing down..."); 127 | helloWorld = nullptr; 128 | } 129 | return 0; 130 | } 131 | #ifdef __cplusplus 132 | } 133 | #endif 134 | -------------------------------------------------------------------------------- /examples/particles/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | #set(CMAKE_C_STANDARD 11) 3 | # 4 | #set(ENVSDK $ENV{PLAYDATE_SDK_PATH}) 5 | # 6 | #if (NOT ${ENVSDK} STREQUAL "") 7 | # # Convert path from Windows 8 | # file(TO_CMAKE_PATH ${ENVSDK} SDK) 9 | #else() 10 | # execute_process( 11 | # COMMAND bash -c "egrep '^\\s*SDKRoot' $HOME/.Playdate/config" 12 | # COMMAND head -n 1 13 | # COMMAND cut -c9- 14 | # OUTPUT_VARIABLE SDK 15 | # OUTPUT_STRIP_TRAILING_WHITESPACE 16 | # ) 17 | #endif() 18 | # 19 | #if (NOT EXISTS ${SDK}) 20 | # message(FATAL_ERROR "SDK Path not found; set ENV value PLAYDATE_SDK_PATH") 21 | # return() 22 | #endif() 23 | # 24 | #set(CMAKE_CONFIGURATION_TYPES "Debug;Release") 25 | #set(CMAKE_XCODE_GENERATE_SCHEME TRUE) 26 | # 27 | ## Game Name Customization 28 | #set(PLAYDATE_GAME_NAME Particles) 29 | #set(PLAYDATE_GAME_DEVICE Particles_DEVICE) 30 | # 31 | #project(${PLAYDATE_GAME_NAME} C ASM) 32 | # 33 | #file(GLOB IMAGES 34 | # "Source/images/*" 35 | #) 36 | # 37 | #if (TOOLCHAIN STREQUAL "armgcc") 38 | # add_executable(${PLAYDATE_GAME_DEVICE} main.c) 39 | #else() 40 | # add_library(${PLAYDATE_GAME_NAME} SHARED main.c ${IMAGES}) 41 | #endif() 42 | # 43 | #include(${SDK}/C_API/buildsupport/playdate_game.cmake) 44 | 45 | cmake_minimum_required(VERSION 3.19) 46 | project("ParticlesCPP") 47 | 48 | # C++ 20 and up is required in order to include the playdate headers 49 | set(CMAKE_CXX_STANDARD 20) 50 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 51 | set(CMAKE_CONFIGURATION_TYPES "Debug;Release") 52 | set(CMAKE_XCODE_GENERATE_SCHEME TRUE) 53 | 54 | # As this is part of the repo, we can't demonstrate this aptly, but for a full 55 | # project, you will want to include this project in order to use the build 56 | # system, which might looks something like this: 57 | # 58 | # add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/submodules/playdate-cpp) 59 | 60 | # Now we can declare our application 61 | add_playdate_application(Particles) 62 | 63 | # Add its sources, and you're good to go! 64 | target_sources(Particles PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) 65 | -------------------------------------------------------------------------------- /examples/particles/Source/font/namco-1x-table-9-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nstbayless/playdate-cpp/8aa27171fb1a007d3e3e94be58d8445473ecd73c/examples/particles/Source/font/namco-1x-table-9-9.png -------------------------------------------------------------------------------- /examples/particles/Source/font/namco-1x.fnt: -------------------------------------------------------------------------------- 1 | space 9 2 | ! 9 3 | " 9 4 | # 9 5 | $ 9 6 | % 9 7 | & 9 8 | ' 9 9 | ( 9 10 | ) 9 11 | * 9 12 | + 9 13 | , 9 14 | - 9 15 | . 9 16 | / 9 17 | 0 9 18 | 1 9 19 | 2 9 20 | 3 9 21 | 4 9 22 | 5 9 23 | 6 9 24 | 7 9 25 | 8 9 26 | 9 9 27 | : 9 28 | ; 9 29 | < 9 30 | = 9 31 | > 9 32 | ? 9 33 | @ 9 34 | A 9 35 | B 9 36 | C 9 37 | D 9 38 | E 9 39 | F 9 40 | G 9 41 | H 9 42 | I 9 43 | J 9 44 | K 9 45 | L 9 46 | M 9 47 | N 9 48 | O 9 49 | P 9 50 | Q 9 51 | R 9 52 | S 9 53 | T 9 54 | U 9 55 | V 9 56 | W 9 57 | X 9 58 | Y 9 59 | Z 9 60 | [ 9 61 | \ 9 62 | ] 9 63 | _ 9 64 | ` 9 65 | a 9 66 | b 9 67 | c 9 68 | d 9 69 | e 9 70 | f 9 71 | g 9 72 | h 9 73 | i 9 74 | j 9 75 | k 9 76 | l 9 77 | m 9 78 | n 9 79 | o 9 80 | p 9 81 | q 9 82 | r 9 83 | s 9 84 | t 9 85 | u 9 86 | v 9 87 | w 9 88 | x 9 89 | y 9 90 | z 9 91 | | 9 92 | ~ 9 93 | -------------------------------------------------------------------------------- /examples/particles/Source/images/snowflake1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nstbayless/playdate-cpp/8aa27171fb1a007d3e3e94be58d8445473ecd73c/examples/particles/Source/images/snowflake1.png -------------------------------------------------------------------------------- /examples/particles/Source/images/snowflake2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nstbayless/playdate-cpp/8aa27171fb1a007d3e3e94be58d8445473ecd73c/examples/particles/Source/images/snowflake2.png -------------------------------------------------------------------------------- /examples/particles/Source/images/snowflake3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nstbayless/playdate-cpp/8aa27171fb1a007d3e3e94be58d8445473ecd73c/examples/particles/Source/images/snowflake3.png -------------------------------------------------------------------------------- /examples/particles/Source/images/snowflake4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nstbayless/playdate-cpp/8aa27171fb1a007d3e3e94be58d8445473ecd73c/examples/particles/Source/images/snowflake4.png -------------------------------------------------------------------------------- /examples/particles/Source/main.lua: -------------------------------------------------------------------------------- 1 | 2 | local font = playdate.graphics.font.new('font/namco-1x') 3 | 4 | local gfx = playdate.graphics 5 | 6 | 7 | local particleCount = 20 8 | local particles = particlelib.particles.new(particleCount) 9 | 10 | local testRect = playdate.geometry.rect.new(170, 90, 60, 60) 11 | 12 | local showInstructions = true 13 | 14 | function playdate.update() 15 | 16 | particles:update() 17 | particles:draw() 18 | 19 | gfx.setColor(gfx.kColorWhite) 20 | gfx.drawRect(testRect) 21 | 22 | gfx.setFont(font) 23 | gfx.setFontTracking(-1) 24 | gfx.drawText("count: "..particleCount, 4, 2) 25 | 26 | local boxcount = particles:particleCountInRect(testRect.x, testRect.y, testRect.width, testRect.height) 27 | gfx.drawText(" box: "..boxcount, 4, 12) 28 | 29 | if showInstructions then 30 | gfx.drawText("Press UP to add more snowflakes, DOWN to remove", 10, 60) 31 | end 32 | 33 | gfx.setImageDrawMode(playdate.graphics.kDrawModeCopy) 34 | playdate.drawFPS(2, 224) 35 | 36 | end 37 | 38 | 39 | function playdate.upButtonDown() 40 | 41 | particleCount += 40 42 | particles:setNumberOfParticles(particleCount) 43 | showInstructions = false 44 | 45 | end 46 | 47 | 48 | function playdate.downButtonDown() 49 | 50 | particleCount -= 40 51 | if particleCount < 20 then particleCount = 20 end 52 | particles:setNumberOfParticles(particleCount) 53 | showInstructions = false 54 | 55 | end 56 | -------------------------------------------------------------------------------- /examples/particles/Source/pdxinfo: -------------------------------------------------------------------------------- 1 | name=Particles 2 | author= 3 | description= 4 | bundleID=com.panic.test 5 | imagePath= 6 | -------------------------------------------------------------------------------- /examples/particles/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.c 3 | // Extension 4 | // 5 | // Original Created by Dan Messing on 5/01/18. 6 | // Refactored into a C++ example by MrBZapp on 9/16/23 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | // First, give the library that will be included a name 17 | constexpr char* PARTICLE_CLASS_NAME = "particlelib.particles"; 18 | 19 | // These static elements will be initialized in the eventHandler's `InitLua` event 20 | static PlaydateAPI* pd = nullptr; 21 | static LCDBitmap* flakes[4]; 22 | 23 | /* 24 | * Each individual particle will draw a snowflake randomly selected from the 25 | * array of flakes above 26 | */ 27 | class Particle 28 | { 29 | public: 30 | Particle() 31 | : x((rand() % 441) - 20.0f) 32 | , y((rand() % 241) - 240.0f) 33 | , w(19) 34 | , h(21) 35 | , speed(rand() % 4 + 1) 36 | , type(rand() % 4) 37 | , drift((float)((rand() % 5) - 2.0) / 10.0f) 38 | {} 39 | 40 | // Move the snowflake according to its movement properties 41 | void update() 42 | { 43 | drift += (float)(((rand() % 5) - 2.0f) / 10.0f); 44 | y += speed; 45 | x += drift; 46 | 47 | if (y > 240) // if the particle is off the screen, move it back to the top 48 | { 49 | y = -22; 50 | x = (rand() % 441) - 20; 51 | drift = (float)((rand() % 5) - 2.0f) / 10.0f; 52 | } 53 | } 54 | 55 | // Paint the specified snowflake at its location 56 | void draw() 57 | { 58 | pd->graphics->setDrawMode(kDrawModeInverted); 59 | pd->graphics->drawBitmap(flakes[type], round(x), y, kBitmapUnflipped); 60 | } 61 | 62 | // Determine if the snowflake is inside a given box 63 | bool isInBounds(int boxX, int boxY, int boxW, int boxH) 64 | { 65 | return !(y >= boxY + boxH || y + h <= boxY || x >= boxX + boxW || x + w <= boxX); 66 | } 67 | 68 | private: 69 | float x, drift; 70 | int y, w, h, speed, type; 71 | }; 72 | 73 | /** 74 | * This class is essentially a helper wrapper around a std::vector. Each could 75 | * theoretically be done in the C functions passed to Lua as well, but this 76 | * class also helps demonstrate generally how one might consolidate logic, 77 | * isolate lua and the playdate API, and/or use std::unique_ptr for memory 78 | * management. 79 | */ 80 | class ParticlesLuaManager 81 | { 82 | public: 83 | explicit ParticlesLuaManager(int count) 84 | { setNumParticles(count); } 85 | 86 | void setNumParticles(int count) {m_Particles.resize(count); } 87 | void update() { for(auto& p : m_Particles) { p.update(); } } 88 | void draw() { for(auto& p : m_Particles) { p.draw(); } } 89 | 90 | int countInBounds(int x, int y, int w, int h) 91 | { 92 | int rv = 0; 93 | for(auto& p : m_Particles) 94 | { 95 | if (p.isInBounds(x, y, w, h)) 96 | { rv++; } 97 | } 98 | return rv; 99 | } 100 | 101 | private: 102 | std::vector m_Particles; 103 | }; 104 | 105 | // It's likely impossible, but using a std::unique_ptr here this ensures that 106 | // if `newobject` is called unevenly with dealloc that we can't leak anything 107 | std::unique_ptr particles; 108 | 109 | // The remaining functions here are all bridge functions between things we're 110 | // allowed to pass to lua and the C++ objects actually responsible for the 111 | // game's operation. 112 | 113 | static int particlelib_dealloc(lua_State* L) 114 | { 115 | (void)L; // Ignore me! 116 | particles.reset(nullptr); 117 | return 0; 118 | } 119 | 120 | 121 | static int particlelib_newobject(lua_State* L) 122 | { 123 | (void)L; // Ignore me! 124 | int count = pd->lua->getArgInt(1); 125 | particles = std::make_unique(count); 126 | pd->lua->pushObject(particles.get(), PARTICLE_CLASS_NAME, 0); 127 | return 1; 128 | } 129 | 130 | 131 | static int particlelib_setNumberOfParticles(lua_State* L) 132 | { 133 | (void)L; // Ignore me! 134 | int count = pd->lua->getArgInt(2); 135 | particles->setNumParticles(count); 136 | return 0; 137 | } 138 | 139 | 140 | static int particlelib_particleCountInRect(lua_State* L) 141 | { 142 | (void)L; // Ignore me! 143 | 144 | int x = pd->lua->getArgInt(2); 145 | int y = pd->lua->getArgInt(3); 146 | int w = pd->lua->getArgInt(4); 147 | int h = pd->lua->getArgInt(5); 148 | // int type = pd->lua->getArgInt(6); // could also filter for type here if it was piped down 149 | 150 | auto result = particles->countInBounds(x, y, w, h); 151 | pd->lua->pushInt(result); 152 | return 1; 153 | } 154 | 155 | 156 | static int particlelib_update(lua_State* L) 157 | { 158 | (void)L; 159 | particles->update(); 160 | return 0; 161 | } 162 | 163 | 164 | static int particlelib_draw(lua_State* L) 165 | { 166 | (void)L; 167 | pd->graphics->clear(kColorBlack); 168 | particles->draw(); 169 | return 0; 170 | } 171 | 172 | static const lua_reg particlesLib[] = 173 | { 174 | { "__gc", particlelib_dealloc }, 175 | { "new", particlelib_newobject }, 176 | { "setNumberOfParticles", particlelib_setNumberOfParticles }, 177 | { "particleCountInRect", particlelib_particleCountInRect }, 178 | { "update", particlelib_update }, 179 | { "draw", particlelib_draw }, 180 | { nullptr, nullptr } 181 | }; 182 | 183 | /** 184 | * the `eventHandler` function is the entry point for all Playdate applications 185 | * and Lua extensions. It requires C-style linkage (no name mangling) so we 186 | * must wrap the entire thing in this `extern "C" {` block 187 | */ 188 | #ifdef __cplusplus 189 | extern "C" { 190 | #endif 191 | 192 | #ifdef _WINDLL 193 | __declspec(dllexport) 194 | #endif 195 | int eventHandler(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg) 196 | { 197 | (void)arg; 198 | eventHandler_pdnewlib(pd, event, arg); 199 | if (event == kEventInit) 200 | { 201 | 202 | } 203 | 204 | if (event == kEventInitLua) 205 | { 206 | pd = playdate; 207 | 208 | const char* err; 209 | 210 | if ( !pd->lua->registerClass(PARTICLE_CLASS_NAME, particlesLib, nullptr, 0, &err) ) 211 | pd->system->logToConsole("%s:%i: registerClass failed, %s", __FILE__, __LINE__, err); 212 | 213 | srand(pd->system->getSecondsSinceEpoch(nullptr)); 214 | 215 | // load particle images 216 | const char *outErr = nullptr; 217 | const char *path1 = "images/snowflake1"; 218 | const char *path2 = "images/snowflake2"; 219 | const char *path3 = "images/snowflake3"; 220 | const char *path4 = "images/snowflake4"; 221 | flakes[0] = pd->graphics->loadBitmap(path1, &outErr); 222 | flakes[1] = pd->graphics->loadBitmap(path2, &outErr); 223 | flakes[2] = pd->graphics->loadBitmap(path3, &outErr); 224 | flakes[3] = pd->graphics->loadBitmap(path4, &outErr); 225 | } 226 | 227 | return 0; 228 | } 229 | 230 | #ifdef __cplusplus 231 | } 232 | #endif 233 | -------------------------------------------------------------------------------- /inc/pdcpp/pdnewlib.h: -------------------------------------------------------------------------------- 1 | #ifndef __STDPD_H 2 | #define __STDPD_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "pd_api.h" 9 | int eventHandler_pdnewlib(PlaydateAPI*, PDSystemEvent event, uint32_t arg); 10 | 11 | #ifdef __cplusplus 12 | } 13 | #endif 14 | 15 | #endif -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Playdate-C++ 2 | 3 | *for SDK version 2.0.3, but attempts will be made to keep this up to date* 4 | 5 | You want to use C++ on the playdate! Great, except you keep getting linker 6 | errors about exception handling, and your global variables aren't getting 7 | initialized properly, and `__attribute__((constructor))` isn't helping, and... 8 | 9 | ## Prerequisites 10 | 1. [CMake](https://cmake.org/download/) 11 | 2. A compiler capable of handling C++20 (Only a concern for the simulator, the 12 | `arm-none-eabi` compiler referenced by prerequisites section from 13 | ["Inside Playdate With C"](https://sdk.play.date/2.0.3/Inside%20Playdate%20with%20C.html) 14 | supports this.) 15 | 3. For the Playdate hardware, you need to have the ARM toolchain of version 16 | 12.3.rel1 or later. If you don't you may encounter linker errors. 17 | 4. The Playdate SDK installed (and preferably added to the `PATH`) 18 | 5. The forward declaration of `eventHandler` in `pd_api.h` needs to be deleted 19 | or commented out. At time of writing, this is lines 58-61, there is also a 20 | comment indicating that is the main entry point function. This will not 21 | impact pure C builds, including the builds of all the Playdate examples. Not 22 | doing this will lead to linker errors, if you're seeing errors like "linkage 23 | specification contradicts earlier specification for 'eventHandler'", you did 24 | not remove this forward declaration. 25 | 26 | ## How to use 27 | ### Project Setup: 28 | Follow along with the `CMakeLists.txt` file in the "Hello World" example: 29 | 1. Add this repo as a subdirectory of your project. 30 | 2. Use the `add_subdirectory` directive to include this package in your project 31 | 3. Use the `add_playdate_application` directive with your source to add your 32 | application to the build 33 | 34 | Follow along with the `main.cpp` file in the "Hello World" example: 35 | 1. Surround the `eventHandler` function with and `extern C` statement to ensure 36 | correct linkage. 37 | 2. call `eventHandler_pdnewlib` first thing in your `eventHandler` 38 | 39 | ### Building 40 | This uses the CMake build system, which is fully cross-platform. Many IDEs like 41 | CLion and VSCode either integrate directly with CMake, or provide extensions 42 | which do so. Otherwise, refer to the documentation from [CMake](https://cmake.org/runningcmake/) 43 | for the recommended process for building on your platform from the command line 44 | or CMake GUI. 45 | 46 | #### To build for the playdate hardware 47 | A toolchain file needs to be specified. This can be done in the `CMake options` 48 | field in a build configuration in CLion, or from the command line, with the 49 | following option: 50 | ``` 51 | -DCMAKE_TOOLCHAIN_FILE=/C_API/buildsupport/arm.cmake 52 | ``` 53 | 54 | #### To build the examples 55 | Same as the toolchain file, this is achieved with an option passed to CMake: 56 | ``` 57 | -DPDCPP_BUILD_EXAMPLES=ON 58 | ``` 59 | 60 | ## Building other libraries for the playdate 61 | Ok, so now you can write your project in C++, so you want to link in that one 62 | library that makes your life super easy. Everything works in the simulator, you 63 | build for the hardware, boot it up and... Everything crashes; no error logs. 64 | 65 | You can try this: 66 | ``` 67 | ... 68 | modify_target_for_playdate(the_library_that_does_the_thing_you_need) 69 | ... 70 | target_link_libraries(my_project pdcpp the_library_that_does_the_thing_you_need) 71 | ``` 72 | 73 | The CMake function `modify_target_for_playdate` sets a handful of attributes on 74 | any CMake target in the project that are required for operation on the Playdate. 75 | 76 | This function works for libraries which: 77 | * are compiled from source by your project 78 | * don't link in other libraries that haven't been modified for playdate. 79 | * are reasonably platform-agnostic 80 | * either don't throw exceptions, or provide options for disabling exceptions. 81 | (we're working on the exceptions thing, and would really rather support them.) 82 | 83 | ### Caveat Emptor 84 | The `modify_target_for_playdate` function alone may not be enough. 85 | 86 | If the library expects a certain type of hardware, or does anything that expects 87 | there to be a traditional operating system backing it up (threads, file systems, 88 | networking, etc.) nothing about this build-system trickery will save it. 89 | 90 | The best advice here is to test with the Playdate hardware early and often. 91 | The simulator can't sandbox the OS, so you will not see certain classes of 92 | errors until trying to run on the hardware. You will be far happier if you catch 93 | those failures without writing a bunch of code that won't work on the target. 94 | 95 | ## This doesn't work for me 96 | The base principles of this are explained on this [forum thread](https://devforum.play.date/t/cpp-guide-c-on-playdate/5085). 97 | Good luck. 98 | 99 | ## This doesn't work at all 100 | Yikes!?‽!? Please submit a bug report to NaOH#1432 or MrBZapp on discord, or if 101 | you're able, a please submit pull request with a fix :) :) 102 | -------------------------------------------------------------------------------- /src/pdnewdelete.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by MrBZapp on 9/9/2023. 3 | // 4 | #ifdef TARGET_PLAYDATE 5 | #include 6 | void operator delete(void* obj, unsigned int size) { free(obj); } 7 | void* operator new(unsigned int size) { return malloc(size); } 8 | #endif 9 | -------------------------------------------------------------------------------- /src/pdnewlib.c: -------------------------------------------------------------------------------- 1 | #include "pd_api.h" 2 | 3 | #if TARGET_PLAYDATE 4 | #include 5 | #include 6 | #include 7 | #include 8 | #ifdef errno 9 | #undef errno 10 | #endif 11 | extern int errno; 12 | 13 | static PlaydateAPI* pd; 14 | 15 | static int _is_init = 0; 16 | 17 | __attribute__((constructor)) 18 | static void _init(void) { _is_init = 1; } 19 | 20 | int eventHandler_pdnewlib(PlaydateAPI* _pd, PDSystemEvent event, uint32_t arg) 21 | { 22 | if (event == kEventInit) 23 | { 24 | pd = _pd; 25 | if (!_is_init) 26 | { 27 | pd->system->error("Error: .init is not implemented.\nIf you are a developer, please ensure that the event handler in setup.c is up-to-date and implements .init_array execution."); 28 | } 29 | } 30 | return 0; 31 | } 32 | 33 | void _exit(int code) { while (1) { pd->system->error("exited with code %d.", code); } } 34 | void __exit(int code) { _exit(code); } 35 | int _kill(int pid, int sig) { return 0; } 36 | int _getpid(void) { return 1; } 37 | 38 | #define HANDLE_STDIN 0 39 | #define HANDLE_STDOUT 1 40 | #define HANDLE_STDERR 2 41 | 42 | #define MAXFILES 32 43 | #define FILEHANDLEOFF 3 44 | SDFile* openfiles[MAXFILES]; 45 | 46 | typedef struct { 47 | char* v; 48 | size_t c; 49 | const char* fmt; 50 | } buf_t; 51 | 52 | // stdout, stderr 53 | buf_t iobuffs[2] = {{NULL, 0, NULL}, {NULL, 0, "\e[0;31m%s\e[0m"}}; 54 | 55 | static void write_buf(buf_t* wbuf, char* data, int size) 56 | { 57 | wbuf->v = realloc(wbuf->v, wbuf->c + size + 1); 58 | memcpy(wbuf->v + wbuf->c, data, size); 59 | wbuf->c += size; 60 | wbuf->v[wbuf->c] = 0; 61 | } 62 | 63 | // logs as far as the last newline. 64 | static void log_buf_to_console(buf_t* buf) 65 | { 66 | char* start = buf->v; 67 | while (1) 68 | { 69 | char* end = strchr(start, '\n'); 70 | if (end == NULL) 71 | { 72 | break; 73 | } 74 | *end = 0; 75 | pd->system->logToConsole(buf->fmt ? buf->fmt : "%s", start); 76 | start = end + 1; 77 | } 78 | if (start != buf->v) 79 | { 80 | size_t newsize = buf->c + buf->v - start; 81 | memcpy(buf->v, start, newsize); 82 | buf->c = newsize; 83 | buf->v = realloc(buf->v, newsize ? newsize + 1 : 0); 84 | } 85 | } 86 | 87 | int _wait(int *status) { 88 | errno = ECHILD; 89 | return -1; 90 | } 91 | 92 | int _fork(void) { 93 | errno = ENOMEM; 94 | return -1; 95 | } 96 | 97 | int _execve(char *name, char **argv, char **env) 98 | { 99 | errno = ENOMEM; 100 | return -1; 101 | } 102 | 103 | int _write(int handle, char* data, int size) 104 | { 105 | if (size == 0 || data == NULL) return 0; 106 | 107 | if (handle == HANDLE_STDOUT || handle == HANDLE_STDERR) 108 | { 109 | buf_t* wbuf = &iobuffs[handle == HANDLE_STDERR]; 110 | write_buf(wbuf, data, size); 111 | log_buf_to_console(wbuf); 112 | return size; 113 | } 114 | else if (handle >= FILEHANDLEOFF && handle < FILEHANDLEOFF + MAXFILES) 115 | { 116 | SDFile* file = openfiles[handle - FILEHANDLEOFF]; 117 | 118 | if (file) 119 | { 120 | int s = pd->file->write(file, data, size); 121 | if (s < 0) 122 | { 123 | // TODO: errno 124 | return -1; 125 | } 126 | return s; 127 | } 128 | } 129 | 130 | // TODO: errno (no such open file) 131 | return -1; 132 | } 133 | 134 | int _read(int file, char* ptr, int len) 135 | { 136 | return 0; 137 | } 138 | 139 | int _open(const char *name, int flags, int mode) 140 | { 141 | for (size_t i = 0; i < MAXFILES; ++i) 142 | { 143 | if (!openfiles[i]) 144 | { 145 | // TODO: convert mode 146 | openfiles[i] = pd->file->open(name, mode); 147 | if (openfiles[i] == NULL) 148 | { 149 | errno = ENOENT; 150 | return -1; 151 | } 152 | 153 | return FILEHANDLEOFF + i; 154 | } 155 | } 156 | 157 | errno = ENFILE; 158 | return -1; 159 | } 160 | 161 | int _close(int file) 162 | { 163 | if (file >= FILEHANDLEOFF && file < FILEHANDLEOFF + MAXFILES) 164 | { 165 | file -= FILEHANDLEOFF; 166 | SDFile* f = openfiles[file]; 167 | if (f != NULL) 168 | { 169 | if (pd->file->close(f)) 170 | { 171 | // TODO: errno 172 | return -1; 173 | } 174 | else 175 | { 176 | openfiles[file] = NULL; 177 | return 0; 178 | } 179 | } 180 | } 181 | 182 | // TODO: errno (no such file handle) 183 | return -1; 184 | } 185 | 186 | int _mkdir(char* dir) 187 | { 188 | return pd->file->mkdir(dir); 189 | } 190 | 191 | int _rename(char* src, char* dst) 192 | { 193 | if (pd->file->rename(src, dst)) 194 | { 195 | errno = ENOENT; 196 | return -1; 197 | } 198 | return 0; 199 | } 200 | 201 | int _unlink(char* p) 202 | { 203 | if (pd->file->unlink(p, 1)) 204 | { 205 | errno = ENOENT; 206 | return -1; 207 | } 208 | return 0; 209 | } 210 | 211 | 212 | int _isatty(int file) 213 | { 214 | return file >= 0 && file <= HANDLE_STDERR; 215 | } 216 | 217 | int _lseek(int file, int pos, int whence) { 218 | if (file < FILEHANDLEOFF || file >= MAXFILES + FILEHANDLEOFF) 219 | { 220 | SDFile* f = openfiles[file - FILEHANDLEOFF]; 221 | if (f) 222 | { 223 | if (pd->file->seek(f, pos, whence)) 224 | { 225 | // TODO: errno 226 | return -1; 227 | } 228 | return 0; 229 | } 230 | } 231 | 232 | // TODO: errno (no such file) 233 | return -1; 234 | } 235 | 236 | int _fstat(int file, struct stat *st) { 237 | memset(stat, 0, sizeof(stat)); 238 | if (_isatty(file)) 239 | { 240 | st->st_mode = S_IFCHR; 241 | } 242 | else 243 | { 244 | // TODO: get filepath and then call _stat(...) 245 | errno = ENFILE; 246 | return -1; 247 | } 248 | return 0; 249 | } 250 | 251 | int _stat(char *file, struct stat *st) { 252 | memset(stat, 0, sizeof(stat)); 253 | FileStat pdstat; 254 | 255 | if (pd->file->stat(file, &pdstat)) 256 | { 257 | errno = ENOENT; 258 | return -1; 259 | } 260 | 261 | st->st_mode = pdstat.isdir ? S_IFDIR : S_IFREG; 262 | st->st_size = pdstat.size; 263 | st->st_blksize = 512; 264 | st->st_blocks = (pdstat.size + st->st_blksize - 1) / st->st_blksize; 265 | st->st_atime = (0); // TODO 266 | st->st_mtime = st->st_atime; 267 | st->st_ctime = st->st_ctime; 268 | return 0; 269 | } 270 | 271 | static char *__env[1] = { 0 }; 272 | char **_environ = __env; 273 | 274 | #else 275 | 276 | int eventHandler_pdnewlib(PlaydateAPI* p, PDSystemEvent e, uint32_t a) 277 | { 278 | return 0; 279 | } 280 | 281 | #endif -------------------------------------------------------------------------------- /src/setup.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Matt on 8/27/2023. 3 | // 4 | 5 | #include "pd_api.h" 6 | 7 | typedef int (PDEventHandler)(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg); 8 | 9 | extern PDEventHandler eventHandler; 10 | 11 | static void* (*pdrealloc)(void* ptr, size_t size); 12 | 13 | #if TARGET_PLAYDATE 14 | 15 | typedef const void(*init_routine_t)(void); 16 | extern init_routine_t __preinit_array_start, __preinit_array_end, __init_array_start, __init_array_end, __fini_array_start, __fini_array_end; 17 | static PlaydateAPI* pd; 18 | 19 | static void exec_array(init_routine_t* start, init_routine_t* end) 20 | { 21 | while (start < end) 22 | { 23 | if (*start) (*start)(); 24 | ++start; 25 | } 26 | } 27 | 28 | int eventHandlerShim(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg) 29 | { 30 | if ( event == kEventInit ) 31 | { 32 | pd = playdate; 33 | pdrealloc = playdate->system->realloc; 34 | exec_array(&__preinit_array_start, &__preinit_array_end); 35 | exec_array(&__init_array_start, &__init_array_end); 36 | } 37 | 38 | if (event == kEventTerminate) 39 | { 40 | int result = eventHandler(playdate, event, arg); 41 | exec_array(&__fini_array_start, &__fini_array_end); 42 | return result; 43 | } 44 | 45 | return eventHandler(playdate, event, arg); 46 | } 47 | 48 | // standard library functions 49 | void* _malloc_r(struct _reent* _REENT, size_t nbytes) { return pdrealloc(NULL,nbytes); } 50 | void* _realloc_r(struct _reent* _REENT, void* ptr, size_t nbytes) { return pdrealloc(ptr,nbytes); } 51 | void _free_r(struct _reent* _REENT, void* ptr ) { pdrealloc(ptr,0); } 52 | 53 | PDEventHandler* PD_eventHandler __attribute__((section(".capi_handler"))) = &eventHandlerShim; 54 | 55 | extern uint32_t bssStart asm("__bss_start__"); 56 | uint32_t* _bss_start __attribute__((section(".bss_start"))) = &bssStart; 57 | 58 | extern uint32_t bssEnd asm("__bss_end__"); 59 | uint32_t* _bss_end __attribute__((section(".bss_end"))) = &bssEnd; 60 | 61 | #else 62 | 63 | int eventHandlerShim(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg) 64 | { 65 | if ( event == kEventInit ) 66 | pdrealloc = playdate->system->realloc; 67 | 68 | return eventHandler(playdate, event, arg); 69 | } 70 | 71 | #ifdef _WINDLL 72 | __declspec(dllexport) 73 | #endif 74 | void* malloc(size_t nbytes) { return pdrealloc(NULL,nbytes); } 75 | 76 | #ifdef _WINDLL 77 | __declspec(dllexport) 78 | #endif 79 | void* realloc(void* ptr, size_t nbytes) { return pdrealloc(ptr,nbytes); } 80 | 81 | #ifdef _WINDLL 82 | __declspec(dllexport) 83 | #endif 84 | void free(void* ptr ) 85 | { 86 | pdrealloc(ptr,0); 87 | } 88 | 89 | #endif 90 | --------------------------------------------------------------------------------