commit 77d06110aa2ab4305fd03196e4b3285509335bb7 Author: Maschell Date: Fri Jan 20 18:49:24 2023 +0100 first commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..56cc685 --- /dev/null +++ b/.clang-format @@ -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 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..7523a13 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,26 @@ +name: CI-PR + +on: [pull_request] + +jobs: + clang-format: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: clang-format + run: | + docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./source + build-lib: + runs-on: ubuntu-22.04 + needs: clang-format + steps: + - uses: actions/checkout@v3 + - name: build lib + run: | + docker build . -t tmp + docker build . -f Dockerfile.buildlocal -t builder + docker run --rm -v ${PWD}:/project builder make + - uses: actions/upload-artifact@master + with: + name: lib + path: "lib/*.a" \ No newline at end of file diff --git a/.github/workflows/push_image.yml b/.github/workflows/push_image.yml new file mode 100644 index 0000000..e054348 --- /dev/null +++ b/.github/workflows/push_image.yml @@ -0,0 +1,33 @@ +name: Publish Docker Image +on: + push: + branches: + - main +jobs: + clang-format: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: clang-format + run: | + docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./source + build: + runs-on: ubuntu-latest + needs: clang-format + steps: + - uses: actions/checkout@master + - name: Get release version + id: get_release_tag + run: | + echo RELEASE_VERSION=$(echo $(date '+%Y%m%d')) >> $GITHUB_ENV + echo REPOSITORY_NAME=$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}' | sed -e "s/:refs//" | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + echo REPOSITORY_OWNER=$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $1}' | sed 's/[^a-zA-Z0-9]//g' | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + - name: Publish to Registry + uses: elgohr/Publish-Docker-Github-Action@master + with: + name: ${{ env.REPOSITORY_OWNER }}/${{ env.REPOSITORY_NAME }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + snapshot: true + cache: true + tags: "latest, ${{ env.RELEASE_VERSION }}" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..163041f --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/*.a +/build +*.bz2 +release/ +lib/ +CMakeLists.txt +.idea/ +cmake-build-debug/ +share/libfunctionpatcher.ld diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..74cce60 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM wiiuenv/devkitppc:20221228 + +WORKDIR tmp_build +COPY . . +RUN make clean && make && mkdir -p /artifacts/wums && cp -r lib /artifacts/wums +WORKDIR /artifacts + +FROM scratch +COPY --from=0 /artifacts /artifacts \ No newline at end of file diff --git a/Dockerfile.buildlocal b/Dockerfile.buildlocal new file mode 100644 index 0000000..4973d08 --- /dev/null +++ b/Dockerfile.buildlocal @@ -0,0 +1,3 @@ +FROM wiiuenv/devkitppc:20221228 + +WORKDIR project \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..57e7a53 --- /dev/null +++ b/LICENSE @@ -0,0 +1,155 @@ +GNU LESSER GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms +and conditions of version 3 of the GNU General Public License, supplemented +by the additional permissions listed below. + +0. Additional Definitions. + +As used herein, “this License” refers to version 3 of the GNU Lesser General +Public License, and the “GNU GPL” refers to version 3 of the +GNU General Public License. + +“The Library” refers to a covered work governed by this License, other than +an Application or a Combined Work as defined below. + +An “Application” is any work that makes use of an interface provided by the +Library, but which is not otherwise based on the Library. Defining a subclass +of a class defined by the Library is deemed a mode of using an interface +provided by the Library. + +A “Combined Work” is a work produced by combining or linking an Application +with the Library. The particular version of the Library with which the +Combined Work was made is also called the “Linked Version”. + +The “Minimal Corresponding Source” for a Combined Work means the Corresponding +Source for the Combined Work, excluding any source code for portions of the +Combined Work that, considered in isolation, are based on the Application, +and not on the Linked Version. + +The “Corresponding Application Code” for a Combined Work means the object code +and/or source code for the Application, including any data and utility programs +needed for reproducing the Combined Work from the Application, but excluding +the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License without +being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a facility +refers to a function or data to be supplied by an Application that uses the +facility (other than as an argument passed when the facility is invoked), +then you may convey a copy of the modified version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the function or + data, the facility still operates, and performs whatever part of its + purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of this + License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a header +file that is part of the Library. You may convey such object code under terms +of your choice, provided that, if the incorporated material is not limited to +numerical parameters, data structure layouts and accessors, or small macros, +inline functions and templates (ten or fewer lines in length), +you do both of the following: + + a) Give prominent notice with each copy of the object code that the Library + is used in it and that the Library and its use are covered by this License. + + b) Accompany the object code with a copy of the GNU GPL + and this license document. + +4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken together, +effectively do not restrict modification of the portions of the Library +contained in the Combined Work and reverse engineering for debugging such +modifications, if you also do each of the following: + + a) Give prominent notice with each copy of the Combined Work that the + Library is used in it and that the Library and its use are covered + by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and + this license document. + + c) For a Combined Work that displays copyright notices during execution, + include the copyright notice for the Library among these notices, as well + as a reference directing the user to the copies of the GNU GPL + and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form suitable + for, and under terms that permit, the user to recombine or relink + the Application with a modified version of the Linked Version to + produce a modified Combined Work, in the manner specified by section 6 + of the GNU GPL for conveying Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time a + copy of the Library already present on the user's computer system, + and (b) will operate properly with a modified version of the Library + that is interface-compatible with the Linked Version. + + e) Provide Installation Information, but only if you would otherwise be + required to provide such information under section 6 of the GNU GPL, and + only to the extent that such information is necessary to install and + execute a modified version of the Combined Work produced by recombining + or relinking the Application with a modified version of the Linked Version. + (If you use option 4d0, the Installation Information must accompany the + Minimal Corresponding Source and Corresponding Application Code. If you + use option 4d1, you must provide the Installation Information in the + manner specified by section 6 of the GNU GPL for + conveying Corresponding Source.) + +5. Combined Libraries. + +You may place library facilities that are a work based on the Library side by +side in a single library together with other library facilities that are not +Applications and are not covered by this License, and convey such a combined +library under terms of your choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based on + the Library, uncombined with any other library facilities, conveyed under + the terms of this License. + + b) Give prominent notice with the combined library that part of it is a + work based on the Library, and explaining where to find the accompanying + uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions of the +GNU Lesser General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Library as you +received it specifies that a certain numbered version of the GNU Lesser +General Public License “or any later version” applies to it, you have the +option of following the terms and conditions either of that published version +or of any later version published by the Free Software Foundation. If the +Library as you received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser General +Public License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether +future versions of the GNU Lesser General Public License shall apply, that +proxy's public statement of acceptance of any version is permanent +authorization for you to choose that version for the Library. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dedcd73 --- /dev/null +++ b/Makefile @@ -0,0 +1,161 @@ +#------------------------------------------------------------------------------- +.SUFFIXES: +#------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) + +include $(DEVKITPRO)/wut/share/wut_rules + +export VER_MAJOR := 1 +export VER_MINOR := 0 +export VER_PATCH := 0 + +VERSION := $(VER_MAJOR).$(VER_MINOR).$(VER_PATCH) + +#------------------------------------------------------------------------------- +# 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 := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := source + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +CFLAGS := -Wall -Werror -save-temps \ + -ffunction-sections -fdata-sections \ + $(MACHDEP) \ + $(BUILD_CFLAGS) + +CFLAGS += $(INCLUDE) -D__WIIU__ + +CXXFLAGS := $(CFLAGS) -std=gnu++20 + +ASFLAGS := $(MACHDEP) + +LDFLAGS = $(ARCH) -Wl,--gc-sections + + +LIBS := + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(WUT_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 TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +DEFFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.def))) +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 := $(DEFFILES:.def=.o) $(SFILES:.s=.o) $(CFILES:.c=.o) $(CPPFILES:.cpp=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I. + +.PHONY: all dist-bin dist-src dist install clean + +#--------------------------------------------------------------------------------- +all: lib/libcurlwrapper.a + +dist-bin: all + @tar --exclude=*~ -cjf libcurlwrapper-$(VERSION).tar.bz2 lib + +dist-src: + @tar --exclude=*~ -cjf libcurlwrapper-src-$(VERSION).tar.bz2 source Makefile + +dist: dist-src dist-bin + +install: dist-bin + mkdir -p $(DESTDIR)$(DEVKITPRO)/wums + bzip2 -cd libcurlwrapper-$(VERSION).tar.bz2 | tar -xf - -C $(DESTDIR)$(DEVKITPRO)/wums + +lib: + @$(shell [ ! -d $(BUILD) ] && mkdir -p $(BUILD)) + +release: + @$(shell [ ! -d $(BUILD) ] && mkdir -p $(BUILD)) + +share/libcurlwrapper.ld :$(SOURCES) $(INCLUDES) | share release + mv $(CURDIR)/release/*.ld $(CURDIR)/$@ + +lib/libcurlwrapper.a :$(SOURCES) $(INCLUDES) | lib release + @$(shell [ ! -d lib ] && mkdir -p lib) + @$(shell [ ! -d release ] && mkdir -p release) + @$(MAKE) BUILD=release OUTPUT=$(CURDIR)/$@ \ + BUILD_CFLAGS="-DNDEBUG=1 -O2 -s" \ + DEPSDIR=$(CURDIR)/release \ + --no-print-directory -C release \ + -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -rf release lib + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +$(OFILES_SRC) : $(HFILES) + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/README.md b/README.md new file mode 100644 index 0000000..78de4f7 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +[![Publish Docker Image](https://github.com/wiiu-env/libcurlwrapper/actions/workflows/push_image.yml/badge.svg)](https://github.com/wiiu-env/libcurlwrapper/actions/workflows/push_image.yml) + +# libcurlwrapper + +This library loads CURL from an Aroma Module instead of statically linking it to the binary. This results in a much smaller binary size (saves about 750KiB). + +- Requires the [CURLWrapperModule](https://github.com/wiiu-env/CURLWrapperModule) to be running via [WUMSLoader](https://github.com/wiiu-env/WUMSLoader). +- Requires [wut](https://github.com/devkitPro/wut) for building. +- Use with curl 7.84.0 headers, other versions might not be compatible. +- See important changes in the Usage section. + +Install via `make install`. + +## Usage + +Make sure to define this in your Makefile: + +``` +WUMS_ROOT := $(DEVKITPRO)/wums +``` + +Use the normal curl headers, but link against `-lcurlwrapper` instead. For example replace `-lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lz` with `-lcurlwrapper`. + +**If the CURLWrapperModule is not loaded `curl_global_init` will return a negative value.** + +## Changes compared to normal CURL + +libcurlwrapper tries to be a drop-in replacement for libcurl, but some things need to be considered. + +### All function are only available between calling `curl_global_init` and `curl_global_cleanup`. + +Even functions like `curl_version` only work after calling `curl_global_init`. + +### Calling `curl_easy_init` will automatically add CA certificates. + +It `curl_easy_init` returns success, `curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob);` with current CA certificates will be called. +These CA certificates are coming from the modules and can be updated in the future. + +### CURLOPT_SOCKOPTFUNCTION + +- The `CURLOPT_SOCKOPTFUNCTION` callback requires a workaround to work properly. +- This is caused by `setsockopt` not being compatible with the sockets from the CURLWrapperModule. + +Currently, there are two options to get the correct `setsockopt` function: + +#### Option 1: CURLOPT_SOCKOPTDATA + +- To get a pointer to the correct `setsockopt` function, you have to set the `CURLOPT_SOCKOPTDATA` to 0x13371337. +- The `0x13371337` will be replaced with a pointer to the correct `setsockopt` function. + +Example: + +```C +static int initSocket(void *ptr, curl_socket_t socket, curlsocktype type) { + int r; + // If ptr is not our magic value, it got replaced with a pointer to a valid setsockopt function. + if ((uint32_t) ptr != 0x13371337) { + r = reinterpret_cast(ptr)(socket, SOL_SOCKET, SO_WINSCALE, &o, sizeof(o)); + } else { + r = setsockopt(socket, SOL_SOCKET, SO_WINSCALE, &o, sizeof(o)); + } + return r == 0 ? CURL_SOCKOPT_OK : CURL_SOCKOPT_ERROR; +} +[...] +// Set socktopt data to a magic value to get a pointer to the correct setsockopt function. +curl_easy_setopt(curl, CURLOPT_SOCKOPTDATA, 0x13371337); +curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, initSocket); +``` + +Downside of this option: the data pointer of `CURLOPT_SOCKOPTFUNCTION` can not be used. + +#### Option 2: curlwrapper_setsockopt + +This lib exposes the function `curlwrapper_setsockopt` which will call the correct function. Because this lib had no headers, you have to +declare it in your app like this: + +````C +int curlwrapper_setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); +```` + +When using C++, you have to add `extern "C"` at the start. + +Downside of this option: Linking to the original libcurl fails because `curlwrapper_setsockopt` is only defined in this wrapper lib. + +## Use this lib in Dockerfiles. + +A prebuilt version of this lib can found on dockerhub. To use it for your projects, add this to your Dockerfile. + +``` +[...] +COPY --from=wiiuenv/libcurlwrapper:[tag] /artifacts $DEVKITPRO +[...] +``` + +Replace [tag] with a tag you want to use, a list of tags can be found [here](https://hub.docker.com/r/wiiuenv/libcurlwrapper/tags). +It's highly recommended to pin the version to the **latest date** instead of using `latest`. + +## Format the code via docker + +`docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./source -i` diff --git a/source/logger.h b/source/logger.h new file mode 100644 index 0000000..57be87c --- /dev/null +++ b/source/logger.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include + +#define __FILENAME__ ({ \ + const char *__filename = __FILE__; \ + const char *__pos = strrchr(__filename, '/'); \ + if (!__pos) __pos = strrchr(__filename, '\\'); \ + __pos ? __pos + 1 : __filename; \ +}) + +#define LOG_APP_TYPE "L" +#define LOG_APP_NAME "libcurlwrapper" + +#define LOG_EX(FILENAME, FUNCTION, LINE, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) \ + do { \ + LOG_FUNC("[(%s)%18s][%23s]%30s@L%04d: " LOG_LEVEL "" FMT "" LINE_END, LOG_APP_TYPE, LOG_APP_NAME, FILENAME, FUNCTION, LINE, ##ARGS); \ + } while (0) + +#define LOG_EX_DEFAULT(LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) LOG_EX(__FILENAME__, __FUNCTION__, __LINE__, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ##ARGS) + +#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##ERROR## ", "\n", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##WARNING## ", "\n", FMT, ##ARGS) diff --git a/source/macro_magic.h b/source/macro_magic.h new file mode 100644 index 0000000..32ab3bc --- /dev/null +++ b/source/macro_magic.h @@ -0,0 +1,103 @@ +#pragma once + +struct curl_blob { + void *data; + size_t len; + unsigned int flags; /* bit 0 is defined, the rest are reserved and should be + left zeroes */ +}; + +#define MAGIC_CHECK_FUNCTION_PTR(function_name, error_return) \ + if (WUT_PP_CAT(s_, function_name) == nullptr) { \ + if (sModuleHandle == nullptr || OSDynLoad_FindExport(sModuleHandle, FALSE, #function_name, (void **) &WUT_PP_CAT(s_, function_name)) != OS_DYNLOAD_OK) { \ + DEBUG_FUNCTION_LINE_ERR("FindExport " #function_name " failed."); \ + return error_return; \ + } else { \ + functionHandles.push_front((uint32_t *) &WUT_PP_CAT(s_, function_name)); \ + } \ + } + +#define MAGIC_FUNCTION_ARG8(res, function_name, error_return, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) \ + static void *(*WUT_PP_CAT(s_, function_name))(void) = nullptr; \ + extern "C" res function_name(void *param1, void *param2, void *param3, void *param4, void *param5, void *param6, void *param7, void *param8) { \ + MAGIC_CHECK_FUNCTION_PTR(function_name, error_return) \ + return reinterpret_cast(WUT_PP_CAT(s_, function_name))(param1, param2, param3, param4, param5, param6, param7, param8); \ + } + +#define MAGIC_FUNCTION_ARG7(res, function_name, error_return, arg1, arg2, arg3, arg4, arg5, arg6, arg7) \ + static void *(*WUT_PP_CAT(s_, function_name))(void) = nullptr; \ + extern "C" res function_name(void *param1, void *param2, void *param3, void *param4, void *param5, void *param6, void *param7) { \ + MAGIC_CHECK_FUNCTION_PTR(function_name, error_return) \ + return reinterpret_cast(WUT_PP_CAT(s_, function_name))(param1, param2, param3, param4, param5, param6, param7); \ + } + +#define MAGIC_FUNCTION_ARG6(res, function_name, error_return, arg1, arg2, arg3, arg4, arg5, arg6) \ + static void *(*WUT_PP_CAT(s_, function_name))(void) = nullptr; \ + extern "C" res function_name(void *param1, void *param2, void *param3, void *param4, void *param5, void *param6) { \ + MAGIC_CHECK_FUNCTION_PTR(function_name, error_return) \ + return reinterpret_cast(WUT_PP_CAT(s_, function_name))(param1, param2, param3, param4, param5, param6); \ + } + +#define MAGIC_FUNCTION_ARG5(res, function_name, error_return, arg1, arg2, arg3, arg4, arg5) \ + static void *(*WUT_PP_CAT(s_, function_name))(void) = nullptr; \ + extern "C" res function_name(void *param1, void *param2, void *param3, void *param4, void *param5) { \ + MAGIC_CHECK_FUNCTION_PTR(function_name, error_return) \ + return reinterpret_cast(WUT_PP_CAT(s_, function_name))(param1, param2, param3, param4, param5); \ + } + +#define MAGIC_FUNCTION_ARG4(res, function_name, error_return, arg1, arg2, arg3, arg4) \ + static void *(*WUT_PP_CAT(s_, function_name))(void) = nullptr; \ + extern "C" res function_name(void *param1, void *param2, void *param3, void *param4) { \ + MAGIC_CHECK_FUNCTION_PTR(function_name, error_return) \ + return reinterpret_cast(WUT_PP_CAT(s_, function_name))(param1, param2, param3, param4); \ + } + +#define MAGIC_FUNCTION_ARG3(res, function_name, error_return, arg1, arg2, arg3) \ + static void *(*WUT_PP_CAT(s_, function_name))(void) = nullptr; \ + extern "C" res function_name(void *param1, void *param2, void *param3) { \ + MAGIC_CHECK_FUNCTION_PTR(function_name, error_return) \ + return reinterpret_cast(WUT_PP_CAT(s_, function_name))(param1, param2, param3); \ + } + +#define MAGIC_FUNCTION_ARG2(res, function_name, error_return, arg1, arg2) \ + static void *(*WUT_PP_CAT(s_, function_name))(void) = nullptr; \ + extern "C" res function_name(void *param1, void *param2) { \ + MAGIC_CHECK_FUNCTION_PTR(function_name, error_return) \ + return reinterpret_cast(WUT_PP_CAT(s_, function_name))(param1, param2); \ + } + +#define MAGIC_FUNCTION_ARG1(res, function_name, error_return, arg1) \ + static void *(*WUT_PP_CAT(s_, function_name))(void) = nullptr; \ + extern "C" res function_name(void *param1) { \ + MAGIC_CHECK_FUNCTION_PTR(function_name, error_return) \ + return reinterpret_cast(WUT_PP_CAT(s_, function_name))(param1); \ + } + +#define MAGIC_FUNCTION_ARG0(res, function_name, error_return) \ + static void *(*WUT_PP_CAT(s_, function_name))(void) = nullptr; \ + extern "C" res function_name() { \ + MAGIC_CHECK_FUNCTION_PTR(function_name, error_return) \ + return reinterpret_cast(WUT_PP_CAT(s_, function_name))(); \ + } + +#define GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, MACRO_NAME, ...) MACRO_NAME +#define MAGIC_FUNCTION(res, function_name, error_return, ...) \ + GET_MACRO(__VA_ARGS__, \ + MAGIC_FUNCTION_ARG8, \ + MAGIC_FUNCTION_ARG7, \ + MAGIC_FUNCTION_ARG6, \ + MAGIC_FUNCTION_ARG5, \ + MAGIC_FUNCTION_ARG4, \ + MAGIC_FUNCTION_ARG3, \ + MAGIC_FUNCTION_ARG2, \ + MAGIC_FUNCTION_ARG1) \ + (res, function_name, error_return, __VA_ARGS__) + +#define RETURN_VOID + +#define CURLE_FAILED_INIT 2 +#define CURLHE_NOT_BUILT_IN 7 +#define CURLUE_BAD_HANDLE 1 +#define CURLSSLSET_NO_BACKENDS 3 +#define CURLSHE_NOT_BUILT_IN 5 +#define CURLM_INTERNAL_ERROR 4 diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..78ba5ba --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,268 @@ +#include "logger.h" +#include "macro_magic.h" +#include +#include +#include +#include +#include +#include +#include + +static void *(*s_setsockopt)() = nullptr; + +static bool sInitDone = false; +static OSDynLoad_Module sModuleHandle = nullptr; +static uint8_t *s_cacert_pem = nullptr; +static uint32_t *s_cacert_pem_size = nullptr; + +std::forward_list functionHandles; + +extern "C" int curl_global_init() { + if (sInitDone) { + return 0; + } + + if (OSDynLoad_Acquire("homebrew_curlwrapper", &sModuleHandle) != OS_DYNLOAD_OK) { + DEBUG_FUNCTION_LINE_ERR("OSDynLoad_Acquire failed."); + return -1; + } + + if (OSDynLoad_FindExport(sModuleHandle, FALSE, "setsockopt", (void **) &s_setsockopt) != OS_DYNLOAD_OK) { + DEBUG_FUNCTION_LINE_ERR("FindExport setsockopt failed."); + return -2; + } + + if (OSDynLoad_FindExport(sModuleHandle, true, "cacert_pem", (void **) &s_cacert_pem) != OS_DYNLOAD_OK) { + DEBUG_FUNCTION_LINE_WARN("FindExport cacert_pem failed."); + } + + if (OSDynLoad_FindExport(sModuleHandle, true, "cacert_pem_size", (void **) &s_cacert_pem_size) != OS_DYNLOAD_OK) { + DEBUG_FUNCTION_LINE_WARN("FindExport cacert_pem_size failed."); + } + + char *(*s_curl_version_tmp)() = nullptr; + if (OSDynLoad_FindExport(sModuleHandle, false, "curl_version", (void **) &s_curl_version_tmp) == OS_DYNLOAD_OK) { + const char *expectedCURLVersion = "libcurl/7.84.0"; + if (!std::string_view(s_curl_version_tmp()).starts_with(expectedCURLVersion)) { + DEBUG_FUNCTION_LINE_WARN("Unexpected libcurl version: %s (expected %s)", s_curl_version_tmp(), expectedCURLVersion); + } + } else { + DEBUG_FUNCTION_LINE_WARN("Failed to check curl_version"); + } + + sInitDone = true; + return 0; +} + +extern "C" void curl_global_cleanup() { + if (!sInitDone) { return; } + OSDynLoad_Release(sModuleHandle); + sModuleHandle = nullptr; + sInitDone = false; + s_setsockopt = nullptr; + s_cacert_pem = nullptr; + s_cacert_pem_size = nullptr; + for (auto &handle : functionHandles) { + *handle = 0; + } +} + +extern "C" int curlwrapper_setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) { + if (s_setsockopt == nullptr) { + if (curl_global_init() != 0 || s_setsockopt == nullptr) { + return ENOTSUP; + } + } + return reinterpret_cast(s_setsockopt)(sockfd, level, optname, optval, optlen); +} + +// Variadic function in curl header, but always has 3 args +static void *(*s_curl_easy_setopt)() = nullptr; +extern "C" int curl_easy_setopt(void *param1, void *param2, void *param3) { + if (s_curl_easy_setopt == nullptr) { + if (sModuleHandle == nullptr || OSDynLoad_FindExport(sModuleHandle, 0, "curl_easy_setopt", (void **) &s_curl_easy_setopt) != OS_DYNLOAD_OK) { + DEBUG_FUNCTION_LINE_ERR("Failed to find export curl_easy_setopt"); + return CURLE_FAILED_INIT; + } else { + functionHandles.push_front((uint32_t *) &s_curl_easy_setopt); + } + } + if ((uint32_t) param2 == 10000 + 149) { // CURLOPT_SOCKOPTDATA + if (s_setsockopt != nullptr && (uint32_t) param3 == 0x13371337) { + param3 = (void *) s_setsockopt; + } + } + + return reinterpret_cast(s_curl_easy_setopt)(param1, param2, param3); +} + +// Variadic function in curl header, but always has 3 args +static void *(*s_curl_easy_init)() = nullptr; +extern "C" void *curl_easy_init() { + if (s_curl_easy_init == nullptr) { + if (sModuleHandle == nullptr || OSDynLoad_FindExport(sModuleHandle, 0, "curl_easy_init", (void **) &s_curl_easy_init) != OS_DYNLOAD_OK) { + DEBUG_FUNCTION_LINE_ERR("Failed to find export curl_easy_init"); + return nullptr; + } else { + functionHandles.push_front((uint32_t *) &s_curl_easy_init); + } + } + + auto result = reinterpret_cast(s_curl_easy_init)(); + if (result != nullptr && s_cacert_pem != nullptr && s_cacert_pem_size != nullptr) { + struct curl_blob blob {}; + blob.data = (void *) s_cacert_pem; + blob.len = *s_cacert_pem_size; + blob.flags = 1; /*CURL_BLOB_COPY*/ + + // Use the certificate bundle in the data + if (curl_easy_setopt(result, reinterpret_cast(40000 + 309) /*CURLOPT_CAINFO_BLOB*/, &blob) != 0) { + DEBUG_FUNCTION_LINE_WARN("Failed to set ca_certs"); + } + } + return result; +} + +MAGIC_FUNCTION(int, curl_easy_perform, CURLE_FAILED_INIT, arg1); +MAGIC_FUNCTION(void, curl_easy_cleanup, RETURN_VOID, arg1); +MAGIC_FUNCTION(int, curl_easy_getinfo, CURLE_FAILED_INIT, arg1, arg2, arg3); +MAGIC_FUNCTION(void *, curl_easy_duphandle, nullptr, arg1); +MAGIC_FUNCTION(void, curl_easy_reset, RETURN_VOID, arg1); + +MAGIC_FUNCTION(int, curl_easy_recv, CURLE_FAILED_INIT, arg1, arg2, arg3, arg4); +MAGIC_FUNCTION(int, curl_easy_send, CURLE_FAILED_INIT, arg1, arg2, arg3, arg4); +MAGIC_FUNCTION(int, curl_easy_upkeep, CURLE_FAILED_INIT, arg1); + +MAGIC_FUNCTION(int, curl_easy_header, CURLHE_NOT_BUILT_IN, arg1, arg2, arg3, arg4, arg5, arg6); +MAGIC_FUNCTION(void *, curl_easy_nextheader, nullptr, arg1, arg2, arg3, arg4); + +MAGIC_FUNCTION(void *, curl_easy_option_by_name, nullptr, arg1); +MAGIC_FUNCTION(void *, curl_easy_option_by_id, nullptr, arg1); +MAGIC_FUNCTION(void *, curl_easy_option_next, nullptr, arg1); + +MAGIC_FUNCTION_ARG0(void *, curl_url, nullptr); +MAGIC_FUNCTION(void, curl_url_cleanup, RETURN_VOID, arg1); +MAGIC_FUNCTION(void *, curl_url_dup, nullptr, arg1); + +MAGIC_FUNCTION(int, curl_url_get, CURLUE_BAD_HANDLE, arg1, arg2, arg3, arg4); +MAGIC_FUNCTION(int, curl_url_set, CURLUE_BAD_HANDLE, arg1, arg2, arg3, arg4); +MAGIC_FUNCTION(const char *, curl_url_strerror, "[ERROR]", arg1); + +MAGIC_FUNCTION(int, curl_strequal, 0, arg1, arg2); +MAGIC_FUNCTION(int, curl_strnequal, 0, arg1, arg2, arg3); + +MAGIC_FUNCTION(void *, curl_mime_init, nullptr, arg1); +MAGIC_FUNCTION(void, curl_mime_free, RETURN_VOID, arg1); +MAGIC_FUNCTION(void *, curl_mime_addpart, nullptr, arg1); +MAGIC_FUNCTION(int, curl_mime_name, CURLE_FAILED_INIT, arg1, arg2); +MAGIC_FUNCTION(int, curl_mime_filename, CURLE_FAILED_INIT, arg1, arg2); +MAGIC_FUNCTION(int, curl_mime_type, CURLE_FAILED_INIT, arg1, arg2); +MAGIC_FUNCTION(int, curl_mime_encoder, CURLE_FAILED_INIT, arg1, arg2); +MAGIC_FUNCTION(int, curl_mime_data, CURLE_FAILED_INIT, arg1, arg2, arg3); +MAGIC_FUNCTION(int, curl_mime_filedata, CURLE_FAILED_INIT, arg1, arg2); +MAGIC_FUNCTION(int, curl_mime_data_cb, CURLE_FAILED_INIT, arg1, arg2, arg3, arg4, arg5, arg6); +MAGIC_FUNCTION(int, curl_mime_subparts, CURLE_FAILED_INIT, arg1, arg2); +MAGIC_FUNCTION(int, curl_mime_headers, CURLE_FAILED_INIT, arg1, arg2, arg3); + +MAGIC_FUNCTION(int, curl_formadd, CURLE_FAILED_INIT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +MAGIC_FUNCTION(int, curl_formget, CURLE_FAILED_INIT, arg1, arg2, arg3); +MAGIC_FUNCTION(void, curl_formfree, RETURN_VOID, arg1); +MAGIC_FUNCTION(char *, curl_getenv, nullptr, arg1); +MAGIC_FUNCTION_ARG0(const char *, curl_version, "[ERROR]"); +MAGIC_FUNCTION(char *, curl_easy_escape, nullptr, arg1, arg2, arg3); +MAGIC_FUNCTION(char *, curl_escape, nullptr, arg1, arg2); +MAGIC_FUNCTION(char *, curl_easy_unescape, nullptr, arg1, arg2, arg3, arg4); +MAGIC_FUNCTION(char *, curl_unescape, nullptr, arg1, arg2); +MAGIC_FUNCTION(void, curl_free, RETURN_VOID, arg1); +MAGIC_FUNCTION(int, curl_global_init_mem, CURLE_FAILED_INIT, arg1, arg2, arg3, arg4, arg5, arg6); + +MAGIC_FUNCTION(int, curl_global_sslset, CURLSSLSET_NO_BACKENDS, arg1, arg2, arg3); +MAGIC_FUNCTION(void *, curl_slist_append, nullptr, arg1, arg2); +MAGIC_FUNCTION(void, curl_slist_free_all, RETURN_VOID, arg1); +MAGIC_FUNCTION(time_t, curl_getdate, 0, arg1, arg2); + +MAGIC_FUNCTION_ARG0(void *, curl_share_init, nullptr); +// Variadic function in curl header, but always has 3 args +MAGIC_FUNCTION(int, curl_share_setopt, CURLSHE_NOT_BUILT_IN, arg1, arg2, arg3); +MAGIC_FUNCTION(int, curl_share_cleanup, CURLSHE_NOT_BUILT_IN, arg1); + +MAGIC_FUNCTION(void *, curl_version_info, nullptr, arg1); + +MAGIC_FUNCTION(const char *, curl_easy_strerror, "[ERROR]", arg1); +MAGIC_FUNCTION(const char *, curl_share_strerror, "[ERROR]", arg1); +MAGIC_FUNCTION(int, curl_easy_pause, CURLE_FAILED_INIT, arg1, arg2); + +MAGIC_FUNCTION_ARG0(void *, curl_multi_init, nullptr); +MAGIC_FUNCTION(int, curl_multi_add_handle, CURLM_INTERNAL_ERROR, arg1, arg2); +MAGIC_FUNCTION(int, curl_multi_remove_handle, CURLM_INTERNAL_ERROR, arg1, arg2); +MAGIC_FUNCTION(int, curl_multi_fdset, CURLM_INTERNAL_ERROR, arg1, arg2, arg3, arg4, arg5); +MAGIC_FUNCTION(int, curl_multi_wait, CURLM_INTERNAL_ERROR, arg1, arg2, arg3, arg4, arg5); +MAGIC_FUNCTION(int, curl_multi_poll, CURLM_INTERNAL_ERROR, arg1, arg2, arg3, arg4, arg5); +MAGIC_FUNCTION(int, curl_multi_wakeup, CURLM_INTERNAL_ERROR, arg1); +MAGIC_FUNCTION(int, curl_multi_perform, CURLM_INTERNAL_ERROR, arg1, arg2); +MAGIC_FUNCTION(int, curl_multi_cleanup, CURLM_INTERNAL_ERROR, arg1); +MAGIC_FUNCTION(void *, curl_multi_info_read, nullptr, arg1, arg2); +MAGIC_FUNCTION(const char *, curl_multi_strerror, "[ERROR]", arg1); + +MAGIC_FUNCTION(int, curl_multi_socket, CURLM_INTERNAL_ERROR, arg1, arg2, arg3); +MAGIC_FUNCTION(int, curl_multi_socket_action, CURLM_INTERNAL_ERROR, arg1, arg2, arg3, arg4); +MAGIC_FUNCTION(int, curl_multi_socket_all, CURLM_INTERNAL_ERROR, arg1, arg2); +MAGIC_FUNCTION(int, curl_multi_timeout, CURLM_INTERNAL_ERROR, arg1, arg2); +// Variadic function in curl header, but always has 3 args +MAGIC_FUNCTION(int, curl_multi_setopt, CURLM_INTERNAL_ERROR, arg1, arg2, arg3); +MAGIC_FUNCTION(int, curl_multi_assign, CURLM_INTERNAL_ERROR, arg1, arg2, arg3); +MAGIC_FUNCTION(char *, curl_pushheader_bynum, nullptr, arg1, arg2); +MAGIC_FUNCTION(char *, curl_pushheader_byname, nullptr, arg1, arg2); +MAGIC_FUNCTION(int, curl_mvprintf, 0, arg1, arg2); +MAGIC_FUNCTION(int, curl_mvfprintf, 0, arg1, arg2, arg3); +MAGIC_FUNCTION(int, curl_mvsprintf, 0, arg1, arg2, arg3); +MAGIC_FUNCTION(int, curl_mvsnprintf, 0, arg1, arg2, arg3, arg4); +MAGIC_FUNCTION(char *, curl_mvaprintf, nullptr, arg1, arg2); + +extern "C" int curl_mprintf(const char *format, ...) { + va_list va; + + va_start(va, format); + auto res = curl_mvprintf((void *) format, (void *) va); + va_end(va); + + return res; +} +extern "C" int curl_mfprintf(FILE *fd, const char *format, ...) { + va_list va; + + va_start(va, format); + auto res = curl_mvfprintf((void *) fd, (void *) format, (void *) va); + va_end(va); + + return res; +} +extern "C" int curl_msprintf(char *buffer, const char *format, ...) { + va_list va; + + va_start(va, format); + auto res = curl_mvsprintf((void *) buffer, (void *) format, (void *) va); + va_end(va); + + return res; +} + +extern "C" int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) { + va_list va; + + va_start(va, format); + auto res = curl_mvsnprintf((void *) buffer, (void *) maxlength, (void *) format, (void *) va); + va_end(va); + + return res; +} + +extern "C" char *curl_maprintf(const char *format, ...) { + va_list va; + + va_start(va, format); + auto res = curl_mvaprintf((void *) format, (void *) va); + va_end(va); + + return res; +}