First commit

This commit is contained in:
Maschell 2022-03-04 00:59:38 +01:00
commit cd19e89db5
14 changed files with 720 additions and 0 deletions

67
.clang-format Normal file
View File

@ -0,0 +1,67 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: Consecutive
AlignConsecutiveMacros: AcrossEmptyLinesAndComments
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never

65
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,65 @@
name: CI-Release
on:
push:
branches:
- main
jobs:
clang-format:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: clang-format
run: |
docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./source
build-binary:
runs-on: ubuntu-18.04
needs: clang-format
steps:
- uses: actions/checkout@v2
- name: build binary
run: |
docker build . -t builder
docker run --rm -v ${PWD}:/project builder make
- uses: actions/upload-artifact@master
with:
name: binary
path: "*.wms"
deploy-binary:
needs: build-binary
runs-on: ubuntu-18.04
steps:
- name: Get environment variables
id: get_repository_name
run: |
echo REPOSITORY_NAME=$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}' | sed -e "s/:refs//") >> $GITHUB_ENV
echo DATETIME=$(echo $(date '+%Y%m%d-%H%M%S')) >> $GITHUB_ENV
- uses: actions/download-artifact@master
with:
name: binary
- name: zip artifact
run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip *.wms
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }}
release_name: Nightly-${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }}
draft: false
prerelease: true
body: |
Not a stable release:
${{ github.event.head_commit.message }}
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: ./${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip
asset_name: ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip
asset_content_type: application/zip

25
.github/workflows/pr.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: CI-PR
on: [pull_request]
jobs:
clang-format:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: clang-format
run: |
docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./source
build-binary:
runs-on: ubuntu-18.04
needs: clang-format
steps:
- uses: actions/checkout@v2
- name: build binary
run: |
docker build . -t builder
docker run --rm -v ${PWD}:/project builder make
- uses: actions/upload-artifact@master
with:
name: binary
path: "*.wms"

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
*.cbp
*.elf
*.layout
*.rpx
build/
*.save-failed
.idea/
cmake-build-debug/
CMakeLists.txt
*.wms

7
Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM wiiuenv/devkitppc:20220303
COPY --from=wiiuenv/libkernel:20211031 /artifacts $DEVKITPRO
COPY --from=wiiuenv/wiiumodulesystem:20220123 /artifacts $DEVKITPRO
COPY --from=wiiuenv/libsdutils:20220303 /artifacts $DEVKITPRO
WORKDIR project

146
Makefile Normal file
View File

@ -0,0 +1,146 @@
#-------------------------------------------------------------------------------
.SUFFIXES:
#-------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/wums/share/wums_rules
WUMS_ROOT := $(DEVKITPRO)/wums
WUT_ROOT := $(DEVKITPRO)/wut
#-------------------------------------------------------------------------------
# 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
#-------------------------------------------------------------------------------
TARGET := SDHotSwapModule
BUILD := build
SOURCES := source
DATA := data
INCLUDES := source
#-------------------------------------------------------------------------------
# options for code generation
#-------------------------------------------------------------------------------
CFLAGS := -Wall -Wextra -O0 -ffunction-sections\
$(MACHDEP)
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__
CXXFLAGS := $(CFLAGS) -std=c++17
ASFLAGS := -g $(ARCH)
LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) -T$(WUMS_ROOT)/share/libkernel.ld $(WUMSSPECS)
ifeq ($(DEBUG),1)
CXXFLAGS += -DDEBUG -g
CFLAGS += -DDEBUG -g
endif
LIBS := -lwums -lwut -lkernel
#-------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level
# containing include and lib
#-------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(WUT_ROOT) $(WUMS_ROOT)
#-------------------------------------------------------------------------------
# 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)
.PHONY: $(BUILD) clean all
#-------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#-------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).wms $(TARGET).elf
#-------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#-------------------------------------------------------------------------------
# main targets
#-------------------------------------------------------------------------------
all : $(OUTPUT).wms
$(OUTPUT).wms : $(OUTPUT).elf
$(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)
#---------------------------------------------------------------------------------
%.o: %.s
@echo $(notdir $<)
@$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ $(ERROR_FILTER)
-include $(DEPENDS)
#-------------------------------------------------------------------------------
endif
#-------------------------------------------------------------------------------

30
README.md Normal file
View File

@ -0,0 +1,30 @@
[![CI-Release](https://github.com/wiiu-env/SDHotSwapModule/actions/workflows/ci.yml/badge.svg)](https://github.com/wiiu-env/SDHotSwapModule/actions/workflows/ci.yml)
## SDHotSwapModule
Automatically (un)mounts the sd card when the sd card was inserted/ejected. See [libsdutils](https://github.com/wiiu-env/libsdutils) to have an easy way to register a callback for these events.
## Usage
(`[ENVIRONMENT]` is a placeholder for the actual environment name.)
1. Copy the file `SDHotSwapModule.wms` into `sd:/wiiu/environments/[ENVIRONMENT]/modules`.
2. Requires the [WUMSLoader](https://github.com/wiiu-env/WUMSLoader) in `sd:/wiiu/environments/[ENVIRONMENT]/modules/setup`.
## Building using the Dockerfile
It's possible to use a docker image for building. This way you don't need anything installed on your host system.
```
# Build docker image (only needed once)
docker build . -t sdhotswapmodule-builder
# make
docker run -it --rm -v ${PWD}:/project sdhotswapmodule-builder make
# make clean
docker run -it --rm -v ${PWD}:/project sdhotswapmodule-builder make clean
```
## Format the code via docker
`docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./source -i`

52
source/exports.cpp Normal file
View File

@ -0,0 +1,52 @@
#include "exports.h"
#include <cstring>
#include <sdutils/sdutils.h>
#include <wums.h>
#define MAX_HANDLERS 16
static SDAttachHandlerFn sHandlers[MAX_HANDLERS] = {nullptr};
void callAttachCallbacks(SDUtilsAttachStatus status) {
int i;
for (i = 0; i < MAX_HANDLERS; ++i) {
if (sHandlers[i]) {
sHandlers[i](status);
}
}
}
void cleanUpAttachCallbacks() {
memset(sHandlers, 0, sizeof(sHandlers));
}
bool SDUtilsAddAttachHandler(SDAttachHandlerFn fn) {
int i;
for (i = 0; i < MAX_HANDLERS; ++i) {
if (sHandlers[i] == fn) {
return true;
}
if (!sHandlers[i]) {
sHandlers[i] = fn;
return true;
}
}
return false;
}
bool SDUtilsRemoveAttachHandler(SDAttachHandlerFn fn) {
int i;
for (i = 0; i < MAX_HANDLERS; ++i) {
if (sHandlers[i] == fn) {
sHandlers[i] = nullptr;
return true;
}
}
return false;
}
WUMS_EXPORT_FUNCTION(SDUtilsAddAttachHandler);
WUMS_EXPORT_FUNCTION(SDUtilsRemoveAttachHandler);

6
source/exports.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
#include <sdutils/sdutils.h>
void callAttachCallbacks(SDUtilsAttachStatus status);
void cleanUpAttachCallbacks();

36
source/logger.c Normal file
View File

@ -0,0 +1,36 @@
#ifdef DEBUG
#include <stdint.h>
#include <whb/log_cafe.h>
#include <whb/log_module.h>
#include <whb/log_udp.h>
uint32_t moduleLogInit = false;
uint32_t cafeLogInit = false;
uint32_t udpLogInit = false;
#endif // DEBUG
void initLogging() {
#ifdef DEBUG
if (!(moduleLogInit = WHBLogModuleInit())) {
cafeLogInit = WHBLogCafeInit();
udpLogInit = WHBLogUdpInit();
}
#endif // DEBUG
}
void deinitLogging() {
#ifdef DEBUG
if (moduleLogInit) {
WHBLogModuleDeinit();
moduleLogInit = false;
}
if (cafeLogInit) {
WHBLogCafeDeinit();
cafeLogInit = false;
}
if (udpLogInit) {
WHBLogUdpDeinit();
udpLogInit = false;
}
#endif // DEBUG
}

43
source/logger.h Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <string.h>
#include <whb/log.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef DEBUG
#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__)
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0)
#define DEBUG_FUNCTION_LINE(FMT, ARGS...) \
do { \
WHBLogPrintf("[%23s]%30s@L%04d: " FMT "", __FILENAME__, __FUNCTION__, __LINE__, ##ARGS); \
} while (0)
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) \
do { \
WHBLogWritef("[%23s]%30s@L%04d: " FMT "", __FILENAME__, __FUNCTION__, __LINE__, ##ARGS); \
} while (0)
#else
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0)
#define DEBUG_FUNCTION_LINE(FMT, ARGS...) while (0)
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) while (0)
#endif
void initLogging();
void deinitLogging();
#ifdef __cplusplus
}
#endif

105
source/main.cpp Normal file
View File

@ -0,0 +1,105 @@
#include "exports.h"
#include "logger.h"
#include "sdcard.h"
#include <condition_variable>
#include <coreinit/cache.h>
#include <coreinit/filesystem.h>
#include <coreinit/thread.h>
#include <mutex>
#include <sdutils/sdutils.h>
#include <thread>
#include <wums.h>
WUMS_MODULE_EXPORT_NAME("homebrew_sdhotswap");
WUMS_MODULE_SKIP_INIT_FINI();
// This NEEDS to be on the heap. Global DTOR are never called for modules
// but we need a fresh instance of this condition_variable on each app change.
std::condition_variable *cv = nullptr;
std::mutex *cv_m = nullptr;
std::thread *mountThread = nullptr;
bool sStopThread = false;
bool sIsSDInsertedAndMounted = false;
int mount_thread() {
std::unique_lock<std::mutex> lk(*cv_m);
// Wait until the main thread has checked the sd status once.
cv->wait(lk);
while (!sStopThread) {
auto newStatus = IsSDCardInserted();
if (newStatus != sIsSDInsertedAndMounted) {
if (newStatus) {
if (MountSDCard()) {
callAttachCallbacks(SDUTILS_ATTACH_MOUNTED);
sIsSDInsertedAndMounted = true;
}
} else {
if (UnmountSDCard()) {
callAttachCallbacks(SDUTILS_ATTACH_UNMOUNTED);
sIsSDInsertedAndMounted = false;
}
}
OSMemoryBarrier();
}
OSSleepTicks(OSMillisecondsToTicks(100));
}
return 0;
}
WUMS_APPLICATION_STARTS() {
initLogging();
sStopThread = false;
cv = new std::condition_variable;
cv_m = new std::mutex;
OSMemoryBarrier();
mountThread = new std::thread(mount_thread);
auto nativeHandle = (OSThread *) mountThread->native_handle();
OSSetThreadName(nativeHandle, "SDHotSwapModuleThread");
while (!OSSetThreadAffinity(nativeHandle, OS_THREAD_ATTRIB_AFFINITY_CPU2)) {
OSSleepTicks(OSMillisecondsToTicks(16));
}
auto check = InitialSDCheck();
if (check < 0) {
// On error stop thread.
sStopThread = true;
} else {
// Otherwise it retuns the current status.
sIsSDInsertedAndMounted = check == 1;
}
OSMemoryBarrier();
DEBUG_FUNCTION_LINE("Wake up the thread");
// Now we can wake up the thread!
cv->notify_all();
}
WUMS_APPLICATION_ENDS() {
sStopThread = true;
OSMemoryBarrier();
cv->notify_all();
if (mountThread != nullptr) {
mountThread->join();
delete mountThread;
mountThread = nullptr;
}
DeInitSDCheck();
delete cv;
delete cv_m;
cv = nullptr;
cv_m = nullptr;
OSMemoryBarrier();
deinitLogging();
}

101
source/sdcard.cpp Normal file
View File

@ -0,0 +1,101 @@
#include "exports.h"
#include "logger.h"
#include <coreinit/cache.h>
#include <coreinit/filesystem.h>
#include <mutex>
static FSClient sClient;
std::mutex *mutex;
bool sFSClientAdded = false;
int InitialSDCheck() {
mutex = new (std::nothrow) std::mutex;
if (!mutex) {
return -1;
}
FSStatus result = FSAddClient(&sClient, FS_ERROR_FLAG_ALL);
if (result != FS_STATUS_OK) {
return -2;
} else {
sFSClientAdded = true;
OSMemoryBarrier();
FSCmdBlock fsCmd;
FSInitCmdBlock(&fsCmd);
FSMountSource mountSource;
return FSGetMountSource(&sClient, &fsCmd, FS_MOUNT_SOURCE_SD, &mountSource, FS_ERROR_FLAG_ALL) == FS_STATUS_OK;
}
}
void DeInitSDCheck() {
if (mutex) {
delete mutex;
mutex = nullptr;
}
if (sFSClientAdded) {
FSDelClient(&sClient, FS_ERROR_FLAG_ALL);
memset(&sClient, 0, sizeof(sClient));
sFSClientAdded = false;
}
cleanUpAttachCallbacks();
OSMemoryBarrier();
}
int IsSDCardInserted() {
if (!sFSClientAdded || !mutex) {
return -1;
}
std::lock_guard<std::mutex> lk(*mutex);
FSCmdBlock fsCmd;
FSMountSource mountSource;
FSInitCmdBlock(&fsCmd);
memset(&mountSource, 0, sizeof(mountSource));
return FSGetMountSource(&sClient, &fsCmd, FS_MOUNT_SOURCE_SD, &mountSource, FS_ERROR_FLAG_ALL) == FS_STATUS_OK;
}
int MountSDCard() {
if (!sFSClientAdded || !mutex) {
return -1;
}
std::lock_guard<std::mutex> lk(*mutex);
FSCmdBlock fsCmd;
FSInitCmdBlock(&fsCmd);
FSMountSource mountSource;
char mountPath[0x80];
if (FSGetMountSource(&sClient, &fsCmd, FS_MOUNT_SOURCE_SD, &mountSource, FS_ERROR_FLAG_ALL) != FS_STATUS_OK) {
DEBUG_FUNCTION_LINE("No SD Card found");
return 0;
}
FSStatus res;
if ((res = FSMount(&sClient, &fsCmd, &mountSource, mountPath, sizeof(mountPath), FS_ERROR_FLAG_ALL)) != FS_STATUS_OK) {
DEBUG_FUNCTION_LINE("FSMount failed %d", res);
return 0;
}
DEBUG_FUNCTION_LINE("Mounted SD Card");
return 1;
}
int UnmountSDCard() {
if (!sFSClientAdded || !mutex) {
return -1;
}
std::lock_guard<std::mutex> lk(*mutex);
FSCmdBlock fsCmd;
FSInitCmdBlock(&fsCmd);
FSStatus res = FS_STATUS_OK;
while (res == FS_STATUS_OK) {
res = FSUnmount(&sClient, &fsCmd, "/vol/external01", FS_ERROR_FLAG_ALL);
if (res != FS_STATUS_OK && res != FS_STATUS_NOT_FOUND) {
return 0;
}
}
DEBUG_FUNCTION_LINE("Unmounted SD Card");
return 1;
}

27
source/sdcard.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
int InitialSDCheck();
void DeInitSDCheck();
/**
* @return < 0 on error.
* 0 when no FAT32 formatted sd card is inserted
* 1 when a FAT32 formatted sd card is inserted
*/
int IsSDCardInserted();
/**
* @return < 0 on error.
* 0 when mounting failed,
* 1 when mounting was successful.
*/
int MountSDCard();
/**
* @return < 0 on error.
* 0 when unmounting failed,
* 1 when unmounting was successful.
*/
int UnmountSDCard();