Merge pull request #1151 from headshot2017/switch

Nintendo Switch port
This commit is contained in:
UnknownShadow200 2024-03-17 08:02:33 +11:00 committed by GitHub
commit 66d328ba2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 1383 additions and 2 deletions

39
.github/workflows/build_switch.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: Build latest (Switch)
on: [push]
concurrency:
group: ${{ github.ref }}-switch
cancel-in-progress: true
jobs:
build-switch:
if: github.ref_name == github.event.repository.default_branch
runs-on: ubuntu-latest
container:
image: devkitpro/devkita64:latest
steps:
- uses: actions/checkout@v4
- name: Compile Switch build
id: compile
run: |
make switch
- uses: ./.github/actions/notify_failure
if: ${{ always() && steps.compile.outcome == 'failure' }}
with:
NOTIFY_MESSAGE: 'Failed to compile Switch build'
WEBHOOK_URL: '${{ secrets.WEBHOOK_URL }}'
- uses: ./.github/actions/upload_build
if: ${{ always() && steps.compile.outcome == 'success' }}
with:
SOURCE_FILE: 'ClassiCube-switch.nro'
DEST_NAME: 'ClassiCube-switch.nro'
- uses: ./.github/actions/upload_build
if: ${{ always() && steps.compile.outcome == 'success' }}
with:
SOURCE_FILE: 'ClassiCube-switch.elf'
DEST_NAME: 'ClassiCube-switch.elf'

1
.gitignore vendored
View File

@ -30,6 +30,7 @@ build-ps2/
build-ps3/
build-vita/
build-wii/
build-switch/
# Build results
[Dd]ebug/

View File

@ -151,6 +151,8 @@ gamecube:
$(MAKE) -f misc/gc/Makefile PLAT=gamecube
wiiu:
$(MAKE) -f misc/wiiu/Makefile PLAT=wiiu
switch:
$(MAKE) -f misc/switch/Makefile PLAT=switch
clean:
$(DEL) $(OBJECTS)

226
misc/switch/Makefile Normal file
View File

@ -0,0 +1,226 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/libnx/switch_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
#
# NO_ICON: if set to anything, do not use icon.
# NO_NACP: if set to anything, no .nacp file is generated.
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
# ICON is the filename of the icon (.jpg), relative to the project folder.
# If not set, it attempts to use one of the following (in this order):
# - <Project name>.jpg
# - icon.jpg
# - <libnx folder>/default_icon.jpg
#
# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder.
# If not set, it attempts to use one of the following (in this order):
# - <Project name>.json
# - config.json
# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
# NACP building is skipped as well.
#---------------------------------------------------------------------------------
TARGET := ClassiCube-switch
BUILD := build-switch
SOURCES := src misc/switch third_party/bearssl/src
DATA := data
INCLUDES := third_party/bearssl/inc
#ROMFS := romfs
APP_TITLE := ClassiCube
APP_AUTHOR := UnknownShadow200
ICON := misc/switch/icon.jpg
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE) -D__SWITCH__
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lGLESv2 -lglad -lEGL -lglapi -ldrm_nouveau -lnx -lm
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(LIBNX)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
ifeq ($(strip $(ICON)),)
icons := $(wildcard *.jpg)
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
else
ifneq (,$(findstring icon.jpg,$(icons)))
export APP_ICON := $(TOPDIR)/icon.jpg
endif
endif
else
export APP_ICON := $(TOPDIR)/$(ICON)
endif
ifeq ($(strip $(NO_ICON)),)
export NROFLAGS += --icon=$(APP_ICON)
endif
ifeq ($(strip $(NO_NACP)),)
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
endif
ifneq ($(APP_TITLEID),)
export NACPFLAGS += --titleid=$(APP_TITLEID)
endif
ifneq ($(ROMFS),)
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/misc/switch/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
ifeq ($(strip $(APP_JSON)),)
@rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf
else
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf
endif
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
ifeq ($(strip $(APP_JSON)),)
all : $(OUTPUT).nro
ifeq ($(strip $(NO_NACP)),)
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
else
$(OUTPUT).nro : $(OUTPUT).elf
endif
else
all : $(OUTPUT).nsp
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
$(OUTPUT).nso : $(OUTPUT).elf
endif
$(OUTPUT).elf : $(OFILES)
$(OFILES_SRC) : $(HFILES_BIN)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

BIN
misc/switch/icon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -928,6 +928,253 @@ void Audio_AllocChunks(cc_uint32 size, void** chunks, int numChunks) {
void Audio_FreeChunks(void** chunks, int numChunks) {
linearFree(chunks[0]);
}
#elif defined CC_BUILD_SWITCH
/*########################################################################################################################*
*-----------------------------------------------------Switch backend------------------------------------------------------*
*#########################################################################################################################*/
#include <switch.h>
#include <stdio.h>
#include <stdlib.h>
struct AudioContext {
int chanID, used;
AudioDriverWaveBuf bufs[AUDIO_MAX_BUFFERS];
int count, channels, sampleRate;
void* _tmpData; int _tmpSize;
};
struct AudioMemPools {
void* chunk;
int mpid;
};
static int channelIDs;
static struct AudioMemPools audioPools[64];
AudioDriver drv;
bool switchAudio = false;
void* audrv_mutex;
static cc_bool AudioBackend_Init(void) {
if (switchAudio) return true;
switchAudio = true;
if (!audrv_mutex) audrv_mutex = Mutex_Create();
Mem_Set(audioPools, 0, sizeof(audioPools));
static const AudioRendererConfig arConfig =
{
.output_rate = AudioRendererOutputRate_48kHz,
.num_voices = 24,
.num_effects = 0,
.num_sinks = 1,
.num_mix_objs = 1,
.num_mix_buffers = 2,
};
audrenInitialize(&arConfig);
audrvCreate(&drv, &arConfig, 2);
static const u8 sink_channels[] = { 0, 1 };
/*int sink =*/ audrvDeviceSinkAdd(&drv, AUDREN_DEFAULT_DEVICE_NAME, 2, sink_channels);
audrvUpdate(&drv);
Result res = audrenStartAudioRenderer();
return R_SUCCEEDED(res);
}
void AudioBackend_Tick(void) {
Mutex_Lock(audrv_mutex);
if (switchAudio) audrvUpdate(&drv);
Mutex_Unlock(audrv_mutex);
}
static void AudioBackend_Free(void) {
for (int i = 0; i < 24; i++) {
audrvVoiceStop(&drv, i);
}
audrvUpdate(&drv);
}
#define AUDIO_HAS_BACKEND
void Audio_Init(struct AudioContext* ctx, int buffers) {
int chanID = -1;
for (int i = 0; i < 24; i++)
{
// channel in use
if (channelIDs & (1 << i)) continue;
chanID = i; break;
}
if (chanID == -1) return;
channelIDs |= (1 << chanID);
ctx->count = buffers;
ctx->chanID = chanID;
ctx->used = true;
}
void Audio_Close(struct AudioContext* ctx) {
if (ctx->used) {
audrvVoiceStop(&drv, ctx->chanID);
channelIDs &= ~(1 << ctx->chanID);
}
ctx->used = false;
AudioBase_Clear(ctx);
}
cc_result Audio_SetFormat(struct AudioContext* ctx, int channels, int sampleRate) {
ctx->channels = channels;
ctx->sampleRate = sampleRate;
audrvVoiceStop(&drv, ctx->chanID);
audrvVoiceInit(&drv, ctx->chanID, ctx->channels, PcmFormat_Int16, ctx->sampleRate);
audrvVoiceSetDestinationMix(&drv, ctx->chanID, AUDREN_FINAL_MIX_ID);
if (channels == 1) {
// mono
audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 0);
audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 1);
}
else {
// stereo
audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 0, 0);
audrvVoiceSetMixFactor(&drv, ctx->chanID, 0.0f, 0, 1);
audrvVoiceSetMixFactor(&drv, ctx->chanID, 0.0f, 1, 0);
audrvVoiceSetMixFactor(&drv, ctx->chanID, 1.0f, 1, 1);
}
return 0;
}
cc_result Audio_QueueChunk(struct AudioContext* ctx, void* chunk, cc_uint32 dataSize) {
AudioDriverWaveBuf* buf;
// Audio buffers must be aligned to a multiple of 0x1000, according to libnx example code
if (((uintptr_t)chunk & 0xFFF) != 0) {
Platform_Log1("Audio_QueueData: tried to queue buffer with non-aligned audio buffer 0x%x\n", &chunk);
}
if ((dataSize & 0xFFF) != 0) {
Platform_Log1("Audio_QueueData: unaligned audio data size 0x%x\n", &dataSize);
}
for (int i = 0; i < ctx->count; i++)
{
buf = &ctx->bufs[i];
int state = buf->state;
cc_uint32 size = dataSize;
cc_uint32 endOffset = dataSize / (sizeof(cc_int16) * ((ctx->channels == 2) ? 2 : 1));
//Platform_Log3("QUEUE_CHUNK %i: %i = %i", &ctx->chanID, &i, &state);
if (state == AudioDriverWaveBufState_Queued || state == AudioDriverWaveBufState_Playing || state == AudioDriverWaveBufState_Waiting)
continue;
buf->data_pcm16 = chunk;
buf->size = size;
buf->start_sample_offset = 0;
buf->end_sample_offset = endOffset;
//Platform_Log3("PLAY %i: %i = %i", &ctx->chanID, &i, &state);
Mutex_Lock(audrv_mutex);
audrvVoiceAddWaveBuf(&drv, ctx->chanID, buf);
Mutex_Unlock(audrv_mutex);
return 0;
}
// tried to queue data without polling for free buffers first
return ERR_INVALID_ARGUMENT;
}
cc_result Audio_Play(struct AudioContext* ctx) {
audrvVoiceStart(&drv, ctx->chanID);
return 0;
}
cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) {
AudioDriverWaveBuf* buf;
int count = 0;
//int states[4];
for (int i = 0; i < ctx->count; i++)
{
buf = &ctx->bufs[i];
//states[i] = buf->state;
//Platform_Log2("CHECK_CHUNK: %i = %i", &ctx->chanID, &buf->status);
if (buf->state == AudioDriverWaveBufState_Queued || buf->state == AudioDriverWaveBufState_Playing || buf->state == AudioDriverWaveBufState_Waiting) {
count++; continue;
}
}
*inUse = count;
/*
char abuf[64];
sprintf(abuf, "%d %d %d %d", states[0], states[1], states[2], states[3]);
Platform_Log2("%i inUse, %c", inUse, abuf);
*/
return 0;
}
cc_bool Audio_FastPlay(struct AudioContext* ctx, struct AudioData* data) {
return true;
}
cc_result Audio_PlayData(struct AudioContext* ctx, struct AudioData* data) {
data->sampleRate = Audio_AdjustSampleRate(data);
cc_result res;
if ((res = Audio_SetFormat(ctx, data->channels, data->sampleRate))) return res;
if ((res = Audio_QueueChunk(ctx, data->data, data->size))) return res;
audrvVoiceSetVolume(&drv, ctx->chanID, data->volume/100.f);
if ((res = Audio_Play(ctx))) return res;
return 0;
}
cc_bool Audio_DescribeError(cc_result res, cc_string* dst) {
return false;
}
void Audio_AllocChunks(cc_uint32 size, void** chunks, int numChunks) {
size = (size + 0xFFF) & ~0xFFF; // round up to nearest multiple of 0x1000
void* dst = aligned_alloc(0x1000, size*numChunks);
for (int i = 0; i < numChunks; i++) {
chunks[i] = dst + size * i;
int mpid = audrvMemPoolAdd(&drv, dst + size * i, size);
audrvMemPoolAttach(&drv, mpid);
for (int j = 0; j < 64; j++) {
if (audioPools[j].chunk != NULL) continue;
audioPools[j].chunk = dst;
audioPools[j].mpid = mpid;
break;
}
}
}
void Audio_FreeChunks(void** chunks, int numChunks) {
// remove memory pool from audren
for (int i=0; i<numChunks; i++) {
for (int j=0; j<64; j++) {
if (audioPools[j].chunk == chunks[0]) {
audrvMemPoolDetach(&drv, audioPools[j].mpid);
audrvMemPoolRemove(&drv, audioPools[j].mpid);
Mem_Set(&audioPools[j], 0, sizeof(struct AudioMemPools));
break;
}
}
}
free(chunks[0]);
}
#elif defined CC_BUILD_DREAMCAST
/*########################################################################################################################*
*----------------------------------------------------Dreamcast backend----------------------------------------------------*

View File

@ -359,6 +359,16 @@ typedef cc_uint8 cc_bool;
#define CC_BUILD_LOWMEM
#define CC_BUILD_BEARSSL
#define CC_BUILD_CONSOLE
#elif defined __SWITCH__
#define CC_BUILD_SWITCH
#define CC_BUILD_HTTPCLIENT
#define CC_BUILD_BEARSSL
#define CC_BUILD_CONSOLE
#define CC_BUILD_TOUCH
#define CC_BUILD_GL
#define CC_BUILD_GLMODERN
#define CC_BUILD_GLES
#define CC_BUILD_EGL
#undef CC_BUILD_FREETYPE
#endif
#endif

549
src/Platform_Switch.c Normal file
View File

@ -0,0 +1,549 @@
#include "Core.h"
#if defined CC_BUILD_SWITCH
#include "_PlatformBase.h"
#include "Stream.h"
#include "ExtMath.h"
#include "Funcs.h"
#include "Window.h"
#include "Utils.h"
#include "Errors.h"
#include "Options.h"
#include <switch.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <poll.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <netdb.h>
#include "_PlatformConsole.h"
const cc_result ReturnCode_FileShareViolation = 1000000000; // not used
const cc_result ReturnCode_FileNotFound = ENOENT;
const cc_result ReturnCode_SocketInProgess = EINPROGRESS;
const cc_result ReturnCode_SocketWouldBlock = EWOULDBLOCK;
const cc_result ReturnCode_DirectoryExists = EEXIST;
const char* Platform_AppNameSuffix = " Switch";
alignas(16) u8 __nx_exception_stack[0x1000];
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
void __libnx_exception_handler(ThreadExceptionDump *ctx)
{
int i;
FILE *f = fopen("sdmc:/exception_dump", "w");
if(f==NULL)return;
fprintf(f, "error_desc: 0x%x\n", ctx->error_desc);//You can also parse this with ThreadExceptionDesc.
//This assumes AArch64, however you can also use threadExceptionIsAArch64().
for(i=0; i<29; i++)fprintf(f, "[X%d]: 0x%lx\n", i, ctx->cpu_gprs[i].x);
fprintf(f, "fp: 0x%lx\n", ctx->fp.x);
fprintf(f, "lr: 0x%lx\n", ctx->lr.x);
fprintf(f, "sp: 0x%lx\n", ctx->sp.x);
fprintf(f, "pc: 0x%lx\n", ctx->pc.x);
//You could print fpu_gprs if you want.
fprintf(f, "pstate: 0x%x\n", ctx->pstate);
fprintf(f, "afsr0: 0x%x\n", ctx->afsr0);
fprintf(f, "afsr1: 0x%x\n", ctx->afsr1);
fprintf(f, "esr: 0x%x\n", ctx->esr);
fprintf(f, "far: 0x%lx\n", ctx->far.x);
fclose(f);
}
/*########################################################################################################################*
*------------------------------------------------------Logging/Time-------------------------------------------------------*
*#########################################################################################################################*/
cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) {
if (end < beg) return 0;
return (end - beg) / 1000;
}
cc_uint64 Stopwatch_Measure(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (cc_uint64)t.tv_sec * 1e9 + t.tv_nsec;
}
void Platform_Log(const char* msg, int len) {
svcOutputDebugString(msg, len);
}
#define UnixTime_TotalMS(time) ((cc_uint64)time.tv_sec * 1000 + UNIX_EPOCH + (time.tv_usec / 1000))
TimeMS DateTime_CurrentUTC_MS(void) {
struct timeval cur;
gettimeofday(&cur, NULL);
return UnixTime_TotalMS(cur);
}
void DateTime_CurrentLocal(struct DateTime* t) {
struct timeval cur;
struct tm loc_time;
gettimeofday(&cur, NULL);
localtime_r(&cur.tv_sec, &loc_time);
t->year = loc_time.tm_year + 1900;
t->month = loc_time.tm_mon + 1;
t->day = loc_time.tm_mday;
t->hour = loc_time.tm_hour;
t->minute = loc_time.tm_min;
t->second = loc_time.tm_sec;
}
/*########################################################################################################################*
*-----------------------------------------------------Directory/File------------------------------------------------------*
*#########################################################################################################################*/
static const cc_string root_path = String_FromConst("sdmc:/switch/ClassiCube/");
static void GetNativePath(char* str, const cc_string* path) {
Mem_Copy(str, root_path.buffer, root_path.length);
str += root_path.length;
String_EncodeUtf8(str, path);
}
cc_result Directory_Create(const cc_string* path) {
char str[NATIVE_STR_LEN];
GetNativePath(str, path);
return mkdir(str, 0) == -1 ? errno : 0;
}
int File_Exists(const cc_string* path) {
char str[NATIVE_STR_LEN];
struct stat sb;
GetNativePath(str, path);
return stat(str, &sb) == 0 && S_ISREG(sb.st_mode);
}
cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCallback callback) {
cc_string path; char pathBuffer[FILENAME_SIZE];
char str[NATIVE_STR_LEN];
struct dirent* entry;
int res;
GetNativePath(str, dirPath);
DIR* dirPtr = opendir(str);
if (!dirPtr) return errno;
// POSIX docs: "When the end of the directory is encountered, a null pointer is returned and errno is not changed."
// errno is sometimes leftover from previous calls, so always reset it before readdir gets called
errno = 0;
String_InitArray(path, pathBuffer);
while ((entry = readdir(dirPtr))) {
path.length = 0;
String_Format1(&path, "%s/", dirPath);
// ignore . and .. entry
char* src = entry->d_name;
if (src[0] == '.' && src[1] == '\0') continue;
if (src[0] == '.' && src[1] == '.' && src[2] == '\0') continue;
int len = String_Length(src);
String_AppendUtf8(&path, src, len);
int is_dir = entry->d_type == DT_DIR;
// TODO: fallback to stat when this fails
if (is_dir) {
res = Directory_Enum(&path, obj, callback);
if (res) { closedir(dirPtr); return res; }
} else {
callback(&path, obj);
}
errno = 0;
}
res = errno; // return code from readdir
closedir(dirPtr);
return res;
}
static cc_result File_Do(cc_file* file, const cc_string* path, int mode) {
char str[NATIVE_STR_LEN];
GetNativePath(str, path);
*file = open(str, mode, 0);
return *file == -1 ? errno : 0;
}
cc_result File_Open(cc_file* file, const cc_string* path) {
return File_Do(file, path, O_RDONLY);
}
cc_result File_Create(cc_file* file, const cc_string* path) {
return File_Do(file, path, O_RDWR | O_CREAT | O_TRUNC);
}
cc_result File_OpenOrCreate(cc_file* file, const cc_string* path) {
return File_Do(file, path, O_RDWR | O_CREAT);
}
cc_result File_Read(cc_file file, void* data, cc_uint32 count, cc_uint32* bytesRead) {
*bytesRead = read(file, data, count);
return *bytesRead == -1 ? errno : 0;
}
cc_result File_Write(cc_file file, const void* data, cc_uint32 count, cc_uint32* bytesWrote) {
*bytesWrote = write(file, data, count);
return *bytesWrote == -1 ? errno : 0;
}
cc_result File_Close(cc_file file) {
return close(file) == -1 ? errno : 0;
}
cc_result File_Seek(cc_file file, int offset, int seekType) {
static cc_uint8 modes[3] = { SEEK_SET, SEEK_CUR, SEEK_END };
return lseek(file, offset, modes[seekType]) == -1 ? errno : 0;
}
cc_result File_Position(cc_file file, cc_uint32* pos) {
*pos = lseek(file, 0, SEEK_CUR);
return *pos == -1 ? errno : 0;
}
cc_result File_Length(cc_file file, cc_uint32* len) {
struct stat st;
if (fstat(file, &st) == -1) { *len = -1; return errno; }
*len = st.st_size; return 0;
}
/*########################################################################################################################*
*--------------------------------------------------------Threading--------------------------------------------------------*
*#########################################################################################################################*/
void Thread_Sleep(cc_uint32 milliseconds) {
cc_uint64 timeout_ns = milliseconds * (1000 * 1000); // to nanoseconds
svcSleepThread(timeout_ns);
}
static void ExecSwitchThread(void* param) {
((Thread_StartFunc)param)();
}
void Thread_Run(void** handle, Thread_StartFunc func, int stackSize, const char* name) {
Thread* thread = (Thread*)Mem_Alloc(1, sizeof(Thread), name);
*handle = thread;
threadCreate(thread, ExecSwitchThread, (void*)func, NULL, stackSize, 0x2C, -2);
threadStart(thread);
}
void Thread_Detach(void* handle) { }
void Thread_Join(void* handle) {
Thread* thread = (Thread*)handle;
threadWaitForExit(thread);
threadClose(thread);
Mem_Free(thread);
}
void* Mutex_Create(void) {
Mutex* mutex = (Mutex*)Mem_Alloc(1, sizeof(Mutex), "mutex");
mutexInit(mutex);
return mutex;
}
void Mutex_Free(void* handle) {
Mem_Free(handle);
}
void Mutex_Lock(void* handle) {
mutexLock((Mutex*)handle);
}
void Mutex_Unlock(void* handle) {
mutexUnlock((Mutex*)handle);
}
struct WaitData {
CondVar cond;
Mutex mutex;
int signalled; // For when Waitable_Signal is called before Waitable_Wait
};
void* Waitable_Create(void) {
struct WaitData* ptr = (struct WaitData*)Mem_Alloc(1, sizeof(struct WaitData), "waitable");
mutexInit(&ptr->mutex);
condvarInit(&ptr->cond);
ptr->signalled = false;
return ptr;
}
void Waitable_Free(void* handle) {
struct WaitData* ptr = (struct WaitData*)handle;
Mem_Free(ptr);
}
void Waitable_Signal(void* handle) {
struct WaitData* ptr = (struct WaitData*)handle;
Mutex_Lock(&ptr->mutex);
condvarWakeOne(&ptr->cond);
Mutex_Unlock(&ptr->mutex);
ptr->signalled = true;
}
void Waitable_Wait(void* handle) {
struct WaitData* ptr = (struct WaitData*)handle;
Mutex_Lock(&ptr->mutex);
if (!ptr->signalled) {
condvarWait(&ptr->cond, &ptr->mutex);
}
ptr->signalled = false;
Mutex_Unlock(&ptr->mutex);
}
void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) {
struct WaitData* ptr = (struct WaitData*)handle;
cc_uint64 timeout_ns = (cc_uint64)milliseconds * (1000 * 1000); // to nanoseconds
Mutex_Lock(&ptr->mutex);
if (!ptr->signalled) {
condvarWaitTimeout(&ptr->cond, &ptr->mutex, timeout_ns);
}
ptr->signalled = false;
Mutex_Unlock(&ptr->mutex);
}
/*
void* Waitable_Create(void) {
LEvent* ptr = (LEvent*)Mem_Alloc(1, sizeof(LEvent), "waitable");
leventInit(ptr, false, true);
return ptr;
}
void Waitable_Free(void* handle) {
LEvent* ptr = (LEvent*)handle;
leventClear(ptr);
Mem_Free(ptr);
}
void Waitable_Signal(void* handle) {
//leventSignal((LEvent*)handle);
}
void Waitable_Wait(void* handle) {
leventWait((LEvent*)handle, UINT64_MAX);
}
void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) {
cc_uint64 timeout_ns = milliseconds * (1000 * 1000); // to nanoseconds
leventWait((LEvent*)handle, timeout_ns);
}
*/
/*########################################################################################################################*
*---------------------------------------------------------Socket----------------------------------------------------------*
*#########################################################################################################################*/
union SocketAddress {
struct sockaddr raw;
struct sockaddr_in v4;
#ifdef AF_INET6
struct sockaddr_in6 v6;
struct sockaddr_storage total;
#endif
};
static cc_result ParseHost(const char* host, int port, cc_sockaddr* addrs, int* numValidAddrs) {
char portRaw[32]; cc_string portStr;
struct addrinfo hints = { 0 };
struct addrinfo* result;
struct addrinfo* cur;
int res, i = 0;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
String_InitArray(portStr, portRaw);
String_AppendInt(&portStr, port);
portRaw[portStr.length] = '\0';
res = getaddrinfo(host, portRaw, &hints, &result);
if (res == EAI_AGAIN) return SOCK_ERR_UNKNOWN_HOST;
if (res) return res;
/* Prefer IPv4 addresses first */
for (cur = result; cur && i < SOCKET_MAX_ADDRS; cur = cur->ai_next)
{
if (cur->ai_family != AF_INET) continue;
Mem_Copy(addrs[i].data, cur->ai_addr, cur->ai_addrlen);
addrs[i].size = cur->ai_addrlen; i++;
}
for (cur = result; cur && i < SOCKET_MAX_ADDRS; cur = cur->ai_next)
{
if (cur->ai_family == AF_INET) continue;
Mem_Copy(addrs[i].data, cur->ai_addr, cur->ai_addrlen);
addrs[i].size = cur->ai_addrlen; i++;
}
freeaddrinfo(result);
*numValidAddrs = i;
return i == 0 ? ERR_INVALID_ARGUMENT : 0;
}
cc_result Socket_ParseAddress(const cc_string* address, int port, cc_sockaddr* addrs, int* numValidAddrs) {
union SocketAddress* addr = (union SocketAddress*)addrs[0].data;
char str[NATIVE_STR_LEN];
String_EncodeUtf8(str, address);
*numValidAddrs = 0;
if (inet_pton(AF_INET, str, &addr->v4.sin_addr) > 0) {
addr->v4.sin_family = AF_INET;
addr->v4.sin_port = htons(port);
addrs[0].size = sizeof(addr->v4);
*numValidAddrs = 1;
return 0;
}
#ifdef AF_INET6
if (inet_pton(AF_INET6, str, &addr->v6.sin6_addr) > 0) {
addr->v6.sin6_family = AF_INET6;
addr->v6.sin6_port = htons(port);
addrs[0].size = sizeof(addr->v6);
*numValidAddrs = 1;
return 0;
}
#endif
return ParseHost(str, port, addrs, numValidAddrs);
}
cc_result Socket_Connect(cc_socket* s, cc_sockaddr* addr, cc_bool nonblocking) {
struct sockaddr* raw = (struct sockaddr*)addr->data;
cc_result res;
*s = socket(raw->sa_family, SOCK_STREAM, IPPROTO_TCP);
if (*s == -1) return errno;
if (nonblocking) {
int blocking_raw = -1; /* non-blocking mode */
ioctl(*s, FIONBIO, &blocking_raw);
}
res = connect(*s, raw, addr->size);
return res == -1 ? errno : 0;
}
cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
int recvCount = recv(s, data, count, 0);
if (recvCount != -1) { *modified = recvCount; return 0; }
*modified = 0; return errno;
}
cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
int sentCount = send(s, data, count, 0);
if (sentCount != -1) { *modified = sentCount; return 0; }
*modified = 0; return errno;
}
void Socket_Close(cc_socket s) {
shutdown(s, SHUT_RDWR);
close(s);
}
static cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
struct pollfd pfd;
int flags;
pfd.fd = s;
pfd.events = mode == SOCKET_POLL_READ ? POLLIN : POLLOUT;
if (poll(&pfd, 1, 0) == -1) { *success = false; return errno; }
/* to match select, closed socket still counts as readable */
flags = mode == SOCKET_POLL_READ ? (POLLIN | POLLHUP) : POLLOUT;
*success = (pfd.revents & flags) != 0;
return 0;
}
cc_result Socket_CheckReadable(cc_socket s, cc_bool* readable) {
return Socket_Poll(s, SOCKET_POLL_READ, readable);
}
cc_result Socket_CheckWritable(cc_socket s, cc_bool* writable) {
socklen_t resultSize = sizeof(socklen_t);
cc_result res = Socket_Poll(s, SOCKET_POLL_WRITE, writable);
if (res || *writable) return res;
/* https://stackoverflow.com/questions/29479953/so-error-value-after-successful-socket-operation */
getsockopt(s, SOL_SOCKET, SO_ERROR, &res, &resultSize);
return res;
}
/*########################################################################################################################*
*--------------------------------------------------------Platform---------------------------------------------------------*
*#########################################################################################################################*/
static void CreateRootDirectory(void) {
mkdir("sdmc:/switch", 0);
int res = mkdir(root_path.buffer, 0);
int err = res == -1 ? errno : 0;
Platform_Log1("Created root directory: %i", &err);
}
void Platform_Init(void) {
// TODO: Redesign Drawer2D to better handle this
//Options_SetBool(OPT_USE_CHAT_FONT, true);
CreateRootDirectory();
socketInitializeDefault();
// Configure our supported input layout: a single player with standard controller styles
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
hidInitializeTouchScreen();
}
void Platform_Free(void) {
socketExit();
}
cc_bool Platform_DescribeError(cc_result res, cc_string* dst) {
char chars[NATIVE_STR_LEN];
int len;
/* For unrecognised error codes, strerror_r might return messages */
/* such as 'No error information', which is not very useful */
/* (could check errno here but quicker just to skip entirely) */
if (res >= 1000) return false;
len = strerror_r(res, chars, NATIVE_STR_LEN);
if (len == -1) return false;
len = String_CalcLen(chars, NATIVE_STR_LEN);
String_AppendUtf8(dst, chars, len);
return true;
}
/*########################################################################################################################*
*-------------------------------------------------------Encryption--------------------------------------------------------*
*#########################################################################################################################*/
static cc_result GetMachineID(cc_uint32* key) {
return ERR_NOT_SUPPORTED;
}
#endif

View File

@ -1055,9 +1055,7 @@ static const struct AssetSet* const asset_sets[] = {
&ccTexsAssetSet,
&mccTexsAssetSet,
&mccMusicAssetSet,
#ifndef CC_BUILD_CONSOLE
&mccSoundAssetSet
#endif /* TODO: Vorbis decoding */
};
void Resources_CheckExistence(void) {

305
src/Window_Switch.c Normal file
View File

@ -0,0 +1,305 @@
#include "Core.h"
#if defined CC_BUILD_SWITCH
#include "_WindowBase.h"
#include "Window.h"
#include "Platform.h"
#include "Input.h"
#include "Event.h"
#include "Graphics.h"
#include "String.h"
#include "Funcs.h"
#include "Bitmap.h"
#include "Errors.h"
#include "ExtMath.h"
#include "Input.h"
#include <switch.h>
static cc_bool launcherMode;
static Framebuffer fb;
static PadState pad;
static AppletHookCookie cookie;
struct _DisplayData DisplayInfo;
struct _WindowData WindowInfo;
static void Set_Resolution(void) {
// check whether the Switch is docked
// use 720p for handheld, 1080p for docked
AppletOperationMode opMode = appletGetOperationMode();
int w = 1280;
int h = 720;
if (opMode == AppletOperationMode_Console) {
w = 1920;
h = 1080;
}
DisplayInfo.Width = w;
DisplayInfo.Height = h;
Window_Main.Width = w;
Window_Main.Height = h;
}
static void Applet_Event(AppletHookType type, void* param) {
if (type == AppletHookType_OnOperationMode) {
Set_Resolution();
if (launcherMode) {
framebufferClose(&fb);
framebufferCreate(&fb, nwindowGetDefault(), DisplayInfo.Width, DisplayInfo.Height, PIXEL_FORMAT_BGRA_8888, 2);
framebufferMakeLinear(&fb);
}
Event_RaiseVoid(&WindowEvents.Resized);
}
}
void Window_Init(void) {
// Initialize the default gamepad (which reads handheld mode inputs as well as the first connected controller)
padInitializeDefault(&pad);
DisplayInfo.Depth = 4; // 32 bit
DisplayInfo.ScaleX = 1;
DisplayInfo.ScaleY = 1;
Window_Main.Focused = true;
Window_Main.Exists = true;
Window_Main.Handle = nwindowGetDefault();
Input_SetTouchMode(true);
Input.Sources = INPUT_SOURCE_GAMEPAD;
nwindowSetDimensions(Window_Main.Handle, 1920, 1080);
appletHook(&cookie, Applet_Event, NULL);
Set_Resolution();
}
void Window_Free(void) {
if (launcherMode)
framebufferClose(&fb);
appletUnhook(&cookie);
}
void Window_Create2D(int width, int height) {
framebufferCreate(&fb, nwindowGetDefault(), DisplayInfo.Width, DisplayInfo.Height, PIXEL_FORMAT_BGRA_8888, 2);
framebufferMakeLinear(&fb);
launcherMode = true;
}
void Window_Create3D(int width, int height) {
framebufferClose(&fb);
launcherMode = false;
}
void Window_SetTitle(const cc_string* title) { }
void Clipboard_GetText(cc_string* value) { }
void Clipboard_SetText(const cc_string* value) { }
int Window_GetWindowState(void) { return WINDOW_STATE_FULLSCREEN; }
cc_result Window_EnterFullscreen(void) { return 0; }
cc_result Window_ExitFullscreen(void) { return 0; }
int Window_IsObscured(void) { return 0; }
void Window_Show(void) { }
void Window_SetSize(int width, int height) { }
void Window_RequestClose(void) {
Event_RaiseVoid(&WindowEvents.Closing);
}
/*########################################################################################################################*
*----------------------------------------------------Input processing-----------------------------------------------------*
*#########################################################################################################################*/
static void HandleButtons(u64 mods) {
Input_SetNonRepeatable(CCPAD_L, mods & HidNpadButton_L);
Input_SetNonRepeatable(CCPAD_R, mods & HidNpadButton_R);
Input_SetNonRepeatable(CCPAD_A, mods & HidNpadButton_A);
Input_SetNonRepeatable(CCPAD_B, mods & HidNpadButton_B);
Input_SetNonRepeatable(CCPAD_X, mods & HidNpadButton_X);
Input_SetNonRepeatable(CCPAD_Y, mods & HidNpadButton_Y);
Input_SetNonRepeatable(CCPAD_START, mods & HidNpadButton_Plus);
Input_SetNonRepeatable(CCPAD_SELECT, mods & HidNpadButton_Minus);
Input_SetNonRepeatable(CCPAD_LEFT, mods & HidNpadButton_Left);
Input_SetNonRepeatable(CCPAD_RIGHT, mods & HidNpadButton_Right);
Input_SetNonRepeatable(CCPAD_UP, mods & HidNpadButton_Up);
Input_SetNonRepeatable(CCPAD_DOWN, mods & HidNpadButton_Down);
}
static void ProcessJoystickInput(HidAnalogStickState* pos) {
// May not be exactly 0 on actual hardware
if (Math_AbsI(pos->x) <= 16) pos->x = 0;
if (Math_AbsI(pos->y) <= 16) pos->y = 0;
Event_RaiseRawMove(&ControllerEvents.RawMoved, pos->x / 512.f, -pos->y / 512.f);
}
static void ProcessTouchInput(void) {
static int currX, currY, prev_touchcount=0;
HidTouchScreenState state={0};
hidGetTouchScreenStates(&state, 1);
if (state.count && !prev_touchcount) { // stylus went down
currX = state.touches[0].x;
currY = state.touches[0].y;
Input_AddTouch(0, currX, currY);
}
else if (state.count) { // stylus is down
currX = state.touches[0].x;
currY = state.touches[0].y;
Input_UpdateTouch(0, currX, currY);
}
else if (!state.count && prev_touchcount) { // stylus was lifted
Input_RemoveTouch(0, currX, currY);
}
prev_touchcount = state.count;
}
void Window_ProcessEvents(double delta) {
// Scan the gamepad. This should be done once for each frame
padUpdate(&pad);
if (!appletMainLoop()) {
Window_Main.Exists = false;
Window_RequestClose();
return;
}
u64 keys = padGetButtons(&pad);
HandleButtons(keys);
// Read the sticks' position
HidAnalogStickState analog_stick_l = padGetStickPos(&pad, 0);
HidAnalogStickState analog_stick_r = padGetStickPos(&pad, 1);
ProcessJoystickInput(&analog_stick_l);
ProcessJoystickInput(&analog_stick_r);
ProcessTouchInput();
}
void Cursor_SetPosition(int x, int y) { } // Makes no sense for PSP
void Window_EnableRawMouse(void) { Input.RawMode = true; }
void Window_DisableRawMouse(void) { Input.RawMode = false; }
void Window_UpdateRawMouse(void) { }
/*########################################################################################################################*
*------------------------------------------------------Framebuffer--------------------------------------------------------*
*#########################################################################################################################*/
void Window_AllocFramebuffer(struct Bitmap* bmp) {
bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels");
}
void Window_DrawFramebuffer(Rect2D r, struct Bitmap* bmp) {
// Retrieve the framebuffer
cc_uint32 stride;
cc_uint32* framebuf = (cc_uint32*) framebufferBegin(&fb, &stride);
// flip upside down
for (cc_uint32 y=r.y; y<r.y + r.Height; y++)
{
for (cc_uint32 x=r.x; x<r.x + r.Width; x++)
{
cc_uint32 pos = y * stride / sizeof(cc_uint32) + x;
framebuf[pos] = bmp->scan0[pos];
}
}
// We're done rendering, so we end the frame here.
framebufferEnd(&fb);
}
void Window_FreeFramebuffer(struct Bitmap* bmp) {
Mem_Free(bmp->scan0);
}
/*########################################################################################################################*
*-----------------------------------------------------OpenGL context------------------------------------------------------*
*#########################################################################################################################*/
static void GLContext_InitSurface(void) {
NWindow* window = (NWindow*)Window_Main.Handle;
if (!window) return; /* window not created or lost */
// terrible, but fixes 720p/1080p resolution change on handheld/docked modes
int real_w = window->width;
int real_h = window->height;
window->width = Window_Main.Width;
window->height = Window_Main.Height;
ctx_surface = eglCreateWindowSurface(ctx_display, ctx_config, window, NULL);
if (!ctx_surface) return;
window->width = real_w;
window->height = real_h;
eglMakeCurrent(ctx_display, ctx_surface, ctx_surface, ctx_context);
}
/*########################################################################################################################*
*------------------------------------------------------Soft keyboard------------------------------------------------------*
*#########################################################################################################################*/
static void OnscreenTextChanged(const char* text) {
char tmpBuffer[NATIVE_STR_LEN];
cc_string tmp = String_FromArray(tmpBuffer);
String_AppendUtf8(&tmp, text, String_Length(text));
Event_RaiseString(&InputEvents.TextChanged, &tmp);
}
void Window_OpenKeyboard(struct OpenKeyboardArgs* args) {
const char* btnText = args->type & KEYBOARD_FLAG_SEND ? "Send" : "Enter";
char input[NATIVE_STR_LEN] = { 0 };
char output[NATIVE_STR_LEN] = { 0 };
String_EncodeUtf8(input, args->text);
int mode = args->type & 0xFF;
SwkbdType type = (mode == KEYBOARD_TYPE_NUMBER || mode == KEYBOARD_TYPE_INTEGER) ? SwkbdType_NumPad : SwkbdType_Normal;
SwkbdConfig kbd;
swkbdCreate(&kbd, 0);
if (mode == KEYBOARD_TYPE_PASSWORD)
swkbdConfigMakePresetPassword(&kbd);
else
{
swkbdConfigMakePresetDefault(&kbd);
swkbdConfigSetType(&kbd, type);
}
swkbdConfigSetInitialText(&kbd, input);
swkbdConfigSetGuideText(&kbd, args->placeholder);
swkbdConfigSetOkButtonText(&kbd, btnText);
Result rc = swkbdShow(&kbd, output, sizeof(output));
if (R_SUCCEEDED(rc))
OnscreenTextChanged(output);
swkbdClose(&kbd);
}
void Window_SetKeyboardText(const cc_string* text) { }
void Window_CloseKeyboard(void) { /* TODO implement */ }
/*########################################################################################################################*
*-------------------------------------------------------Misc/Other--------------------------------------------------------*
*#########################################################################################################################*/
//void Window_ShowDialog(const char* title, const char* msg) {
static void ShowDialogCore(const char* title, const char* msg) {
ErrorApplicationConfig c;
errorApplicationCreate(&c, title, msg);
errorApplicationShow(&c);
}
cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) {
return ERR_NOT_SUPPORTED;
}
cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) {
return ERR_NOT_SUPPORTED;
}
#endif

View File

@ -107,6 +107,9 @@ static EGLSurface ctx_surface;
static EGLConfig ctx_config;
static EGLint ctx_numConfig;
#ifdef CC_BUILD_SWITCH
static void GLContext_InitSurface(void); // replacement in Window_Switch.c for handheld/docked resolution fix
#else
static void GLContext_InitSurface(void) {
void* window = Window_Main.Handle;
if (!window) return; /* window not created or lost */
@ -115,6 +118,7 @@ static void GLContext_InitSurface(void) {
if (!ctx_surface) return;
eglMakeCurrent(ctx_display, ctx_surface, ctx_surface, ctx_context);
}
#endif
static void GLContext_FreeSurface(void) {
if (!ctx_surface) return;