mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-16 19:15:14 -04:00
commit
66d328ba2e
39
.github/workflows/build_switch.yml
vendored
Normal file
39
.github/workflows/build_switch.yml
vendored
Normal 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
1
.gitignore
vendored
@ -30,6 +30,7 @@ build-ps2/
|
||||
build-ps3/
|
||||
build-vita/
|
||||
build-wii/
|
||||
build-switch/
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
|
2
Makefile
2
Makefile
@ -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
226
misc/switch/Makefile
Normal 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
BIN
misc/switch/icon.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
247
src/Audio.c
247
src/Audio.c
@ -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----------------------------------------------------*
|
||||
|
10
src/Core.h
10
src/Core.h
@ -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
549
src/Platform_Switch.c
Normal 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
|
@ -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
305
src/Window_Switch.c
Normal 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
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user