commit b1ae55757ad7092f3e6f8d5f5cf0318762e14986 Author: dragonflylee Date: Sun Feb 4 01:45:10 2024 +0800 Init commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1e07f3f --- /dev/null +++ b/.clang-format @@ -0,0 +1,14 @@ +--- +BasedOnStyle: Google +ColumnLimit: 120 +AccessModifierOffset: -4 +AlignAfterOpenBracket: false +IndentWidth: 4 +BreakBeforeBraces: Attach +CommentPragmas: '^[^ ]' +IncludeBlocks: Regroup +PointerAlignment: Left +SortIncludes: Never +IndentCaseLabels : false +Standard: Cpp11 +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..383f90b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +.vscode/ +aDrive.* \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..a41ef82 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "name": "Switch", + "includePath": [ + "${workspaceFolder}/include", + "${env:DEVKITPRO}/libnx/include", + "${env:DEVKITPRO}/devkitA64/aarch64-none-elf/include/**", + "${env:DEVKITPRO}/portlibs/switch/include/**" + ], + "defines": [ + "__SWITCH__" + ], + "compilerPath": "${env:DEVKITPRO}/devkitA64/bin/aarch64-none-elf-g++", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "gcc-arm64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c8fc86d --- /dev/null +++ b/Makefile @@ -0,0 +1,224 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/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): +# - .jpg +# - icon.jpg +# - /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): +# - .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 := aDrive +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include +APP_TITLE := aDrive +APP_AUTHOR := dragonflylee +#ROMFS := romfs + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) `curl-config --cflags` + +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 := `curl-config --libs` -lmbedcrypto -ljson-c -lnx + +#--------------------------------------------------------------------------------- +# 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)/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 +#--------------------------------------------------------------------------------------- diff --git a/icon.jpg b/icon.jpg new file mode 100644 index 0000000..cf97369 Binary files /dev/null and b/icon.jpg differ diff --git a/include/alipan.h b/include/alipan.h new file mode 100644 index 0000000..52b81a5 --- /dev/null +++ b/include/alipan.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +namespace drive { +typedef struct { + std::string name, id, parent; + bool isDir = false; + size_t size; +} gdItem; +} // namespace drive + +class alipan { +public: + virtual ~alipan() = default; + + bool checkLogin(); + void getListWithParent(const std::string& _parent, std::vector& _out); + bool createDir(const std::string& _dirName, const std::string& _parent); + bool deleteFile(const std::string& _fileID); + +private: + std::string deviceId; + std::string accessToken; + std::string rToken; + + std::string driveId; + std::string userId; + std::string signature; + + json_object* request(const std::string& api, const std::string& data); + + bool getSelfuser(); + bool refreshToken(); + bool createSession(); + bool qrLogin(); +}; diff --git a/include/http.h b/include/http.h new file mode 100644 index 0000000..3f55a7b --- /dev/null +++ b/include/http.h @@ -0,0 +1,33 @@ +/* + Copyright 2023 dragonflylee +*/ + +#pragma once + +#include +#include +#include +#include + +class http { +public: + using Form = std::unordered_map; + + http(const std::string& useragent = "JKSV"); + http(const http& other) = delete; + ~http(); + + static std::string encode_form(const Form& form); + void set_headers(const std::vector& headers); + int get(const std::string& url, std::ostream* out); + std::string put(const std::string& url, std::istream* data); + std::string post(const std::string& url, const std::string& data); + +private: + static size_t easy_write_cb(char* ptr, size_t size, size_t nmemb, void* userdata); + static size_t easy_read_cb(char* ptr, size_t size, size_t nmemb, void* userdata); + int perform(std::ostream* body); + + CURL* easy; + struct curl_slist* chunk; +}; diff --git a/include/misc.h b/include/misc.h new file mode 100644 index 0000000..2dc8862 --- /dev/null +++ b/include/misc.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace util { +std::string hexEncode(const unsigned char* data, size_t len); +} // namespace util diff --git a/source/alipan.cpp b/source/alipan.cpp new file mode 100644 index 0000000..7df5c71 --- /dev/null +++ b/source/alipan.cpp @@ -0,0 +1,310 @@ +#include "alipan.h" +#include "http.h" +#include "misc.h" +#include +#include + +#include +#include +#include + +const std::string ALIPAN_PRE_AUTH = "https://auth.aliyundrive.com/v2/oauth/authorize?"; +const std::string ALIPAN_CALLBACK = "https://www.aliyundrive.com/sign/callback"; +const std::string ALIPAN_CANARY = "X-Canary: client=windows,app=adrive,version=v4.12.0"; + +bool alipan::checkLogin() { + if (!this->signature.empty()) return true; + + if (!this->accessToken.empty()) { + if (this->getSelfuser()) return true; + } else if (!this->rToken.empty()) { + if (this->refreshToken()) return true; + } + + // 1. 计算设备ID + if (this->deviceId.empty()) { + uint8_t digest[32]; + randomGet(digest, sizeof(digest)); + this->deviceId = util::hexEncode(digest, sizeof(digest)); + } + + // 2. Open Browser + WebCommonConfig webCfg; + WebCommonReply webReply; + std::string code; + std::string query = http::encode_form({ + {"login_type", "custom"}, + {"response_type", "code"}, + {"redirect_uri", ALIPAN_CALLBACK}, + {"client_id", "25dzX3vbYqktVxyX"}, + {"sid", std::to_string(time(nullptr))}, + {"state", "{\"origin\":\"file://\"}"}, + }); + std::string url = ALIPAN_PRE_AUTH + query; + + if (R_FAILED(webConfigShow(&webCfg, &webReply))) return false; + webConfigSetJsExtension(&webCfg, true); + webConfigSetUserAgentAdditionalString(&webCfg, "aDrive/4.12.0"); + webConfigSetCallbackUrl(&webCfg, ALIPAN_CALLBACK.c_str()); + webConfigShow(&webCfg, &webReply); + + size_t lastUrlLen = 0; + std::string lastUrl(1024, '\0'); + if (R_FAILED(webReplyGetLastUrl(&webReply, lastUrl.data(), lastUrl.size(), &lastUrlLen))) return false; + lastUrl.resize(lastUrlLen); + + int pos = lastUrl.find("?code=") + 6; + int end = lastUrl.find_first_of('&', pos); + if (pos < end) code = lastUrl.substr(pos, end - pos); + printf("code = %s\n", code.c_str()); + + // 3. GetToken + SetSysDeviceNickName nick; + json_object *post = json_object_new_object(); + json_object_object_add(post, "code", json_object_new_string(query.c_str())); + json_object_object_add(post, "loginType", json_object_new_string("normal")); + json_object_object_add(post, "deviceId", json_object_new_string(this->deviceId.c_str())); + if (R_SUCCEEDED(setsysGetDeviceNickname(&nick))) { + json_object_object_add(post, "deviceName", json_object_new_string(nick.nickname)); + } + json_object_object_add(post, "modelName", json_object_new_string("Windows客户端")); + query = json_object_get_string(post); + json_object_put(post); + + json_object *resp = this->request("/token/get", query); + if (!resp) return false; + + json_object *value; + if (json_object_object_get_ex(resp, "access_token", &value)) { + this->accessToken = json_object_get_string(value); + } + if (json_object_object_get_ex(resp, "refresh_token", &value)) { + this->rToken = json_object_get_string(value); + } + if (json_object_object_get_ex(resp, "user_id", &value)) { + this->userId = json_object_get_string(value); + } + if (json_object_object_get_ex(resp, "default_drive_id", &value)) { + this->driveId = json_object_get_string(value); + } + json_object_put(post); + printf("login user_id(%s) drive_id(%s)\n", this->userId.c_str(), this->driveId.c_str()); + + return this->createSession(); +} + +void alipan::getListWithParent(const std::string &_parent, std::vector &_out) { + json_object *post = json_object_new_object(); + json_object_object_add(post, "drive_id", json_object_new_string(this->driveId.c_str())); + json_object_object_add(post, "parent_file_id", json_object_new_string(_parent.c_str())); + json_object_object_add(post, "limit", json_object_new_int(20)); + json_object_object_add(post, "all", json_object_new_boolean(false)); + json_object_object_add(post, "url_expire_sec", json_object_new_int(14400)); + json_object_object_add(post, "image_thumbnail_process", json_object_new_string("image/resize,w_256/format,jpeg")); + json_object_object_add( + post, "image_url_process", json_object_new_string("image/resize,w_1920/format,jpeg/interlace,1")); + json_object_object_add(post, "fields", json_object_new_string("*")); + json_object_object_add(post, "order_by", json_object_new_string("updated_at")); + json_object_object_add(post, "order_direction", json_object_new_string("DESC")); + + json_object *resp = this->request("/adrive/v3/file/list", json_object_get_string(post)); + json_object_put(post); + if (!resp) return; + + json_object *items = nullptr; + if (json_object_object_get_ex(resp, "items", &items)) { + json_object *value = nullptr; + size_t len = json_object_array_length(items); + for (size_t i = 0; i < len; i++) { + drive::gdItem newItem; + json_object *it = json_object_array_get_idx(items, i); + if (json_object_object_get_ex(it, "name", &value)) { + newItem.name = json_object_get_string(value); + } + if (json_object_object_get_ex(it, "file_id", &value)) { + newItem.id = json_object_get_string(value); + } + if (json_object_object_get_ex(it, "parent_file_id", &value)) { + newItem.parent = json_object_get_string(value); + } + if (json_object_object_get_ex(it, "size", &value)) { + newItem.size = json_object_get_uint64(value); + } + if (json_object_object_get_ex(it, "type", &value)) { + newItem.isDir = !strcmp(json_object_get_string(value), "folder"); + } + _out.push_back(newItem); + } + } + + json_object_put(resp); +} + +bool alipan::createDir(const std::string &_dirName, const std::string &_parent) { + json_object *post = json_object_new_object(); + json_object_object_add(post, "drive_id", json_object_new_string(this->driveId.c_str())); + json_object_object_add(post, "parent_file_id", json_object_new_string(_parent.c_str())); + json_object_object_add(post, "name", json_object_new_string(_dirName.c_str())); + json_object_object_add(post, "type", json_object_new_string("folder")); + json_object_object_add(post, "check_name_mode", json_object_new_string("refuse")); + + json_object *resp = this->request("/adrive/v2/file/createWithFolders", json_object_get_string(post)); + json_object_put(post); + + if (!resp) return false; + json_object_put(resp); + return true; +} + +bool alipan::deleteFile(const std::string &_fileID) { + json_object *post = json_object_new_object(); + json_object_object_add(post, "drive_id", json_object_new_string(this->driveId.c_str())); + json_object_object_add(post, "file_id", json_object_new_string(_fileID.c_str())); + + json_object *resp = this->request("/v2/recyclebin/trash", json_object_get_string(post)); + json_object_put(post); + if (!resp) return false; + json_object_put(resp); + return true; +} + +json_object *alipan::request(const std::string &api, const std::string &data) { + http s("Mozilla/5.0 aDrive/4.12.0"); + while (true) { + std::vector headers = { + "Content-Type: application/json", + ALIPAN_CANARY, + }; + if (this->accessToken.size() > 0) { + headers.push_back("Authorization: Bearer " + this->accessToken); + } + if (this->deviceId.size() > 0) { + headers.push_back("X-Device-Id: " + this->deviceId); + } + if (this->signature.size() > 0) { + headers.push_back("X-Signature: " + this->signature); + } + s.set_headers(headers); + std::string resp = s.post("https://api.alipan.com" + api, data); + json_object *data = json_tokener_parse(resp.c_str()); + if (!data) return nullptr; + + json_object *code = json_object_object_get(data, "code"); + if (!code) return data; + + const char *codeStr = json_object_get_string(code); + if (!codeStr) return data; + + if (strcmp(codeStr, "AccessTokenInvalid") == 0) { + this->refreshToken(); + } else if (strcmp(codeStr, "DeviceSessionSignatureInvalid") == 0) { + this->createSession(); + } else { + json_object_put(data); + return nullptr; + } + } +} + +bool alipan::getSelfuser() { + json_object *resp = this->request("/v2/user/get", "{}"); + if (!resp) return false; + + json_object *value = nullptr; + if (json_object_object_get_ex(resp, "user_id", &value)) { + this->userId = json_object_get_string(value); + } + if (json_object_object_get_ex(resp, "default_drive_id", &value)) { + this->driveId = json_object_get_string(value); + } + json_object_put(resp); + + return this->signature.empty() ? this->createSession() : true; +} + +bool alipan::refreshToken() { + http s; + s.set_headers({"Content-Type: application/json", "x-requested-with: XMLHttpRequest"}); + + json_object *post = json_object_new_object(); + json_object_object_add(post, "refresh_token", json_object_new_string(this->rToken.c_str())); + json_object_object_add(post, "grant_type", json_object_new_string("refresh_token")); + std::string body = s.post("https://auth.alipan.com/v2/account/token", json_object_get_string(post)); + json_object_put(post); + + json_object *value = nullptr; + json_object *resp = json_tokener_parse(body.c_str()); + if (json_object_object_get_ex(resp, "refresh_token", &value)) { + this->rToken = json_object_get_string(value); + } + if (json_object_object_get_ex(resp, "access_token", &value)) { + this->accessToken = json_object_get_string(value); + } + json_object_put(resp); + + return this->createSession(); +} + +inline int mbd_rand(void *rng_state, unsigned char *output, size_t len) { + randomGet(output, len); + return 0; +} + +bool alipan::createSession() { + std::string msg = "5dde4e1bdf9e4966b387ba58f4b3fdc3:" + this->deviceId + ":" + this->userId + ":0"; + + mbedtls_ecdsa_context ctx_sign; + mbedtls_ecdsa_init(&ctx_sign); + // gen secp256k1 keypair + mbedtls_ecdsa_genkey(&ctx_sign, MBEDTLS_ECP_DP_SECP256K1, mbd_rand, nullptr); + // mbedtls_ecp_group_load(&ctx_sign.grp, MBEDTLS_ECP_DP_SECP256K1); + // mbedtls_mpi_read_string(&ctx_sign.d, 16, this->device_id.c_str()); + // mbedtls_ecp_mul(&ctx_sign.grp, &ctx_sign.Q, &ctx_sign.d, &ctx_sign.grp.G, mbd_rand, nullptr); + + // dump public key + size_t pub_len = 0; + std::vector pub(MBEDTLS_ECP_MAX_BYTES, 0); + mbedtls_ecp_point_write_binary( + &ctx_sign.grp, &ctx_sign.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &pub_len, pub.data(), pub.size()); + std::string pub_key = util::hexEncode(pub.data(), pub_len); + + // sign message + unsigned char msg_hash[32]; + mbedtls_sha256_ret((uint8_t *)msg.c_str(), msg.size(), msg_hash, 0); + + mbedtls_mpi r, s; + std::vector sigdata(MBEDTLS_ECDSA_MAX_LEN, 0); + mbedtls_mpi_init(&r); + mbedtls_mpi_init(&s); + mbedtls_ecdsa_sign(&ctx_sign.grp, &r, &s, &ctx_sign.d, msg_hash, sizeof(msg_hash), mbd_rand, nullptr); + + size_t plen = mbedtls_mpi_size(&r); + mbedtls_mpi_write_binary(&r, sigdata.data(), plen); + mbedtls_mpi_write_binary(&s, sigdata.data() + plen, plen); + sigdata[plen * 2] = 1; + mbedtls_mpi_free(&r); + mbedtls_mpi_free(&s); + mbedtls_ecdsa_free(&ctx_sign); + this->signature = util::hexEncode(sigdata.data(), plen * 2 + 1); + + SetSysDeviceNickName nick; + json_object *post = json_object_new_object(); + if (R_SUCCEEDED(setsysGetDeviceNickname(&nick))) { + json_object_object_add(post, "deviceName", json_object_new_string(nick.nickname)); + } + json_object_object_add(post, "modelName", json_object_new_string("Windows客户端")); + json_object_object_add(post, "pubKey", json_object_new_string(pub_key.c_str())); + json_object *resp = this->request("/users/v1/users/device/create_session", json_object_get_string(post)); + json_object_put(post); + if (!resp) return false; + + json_object *success; + bool result = false; + if (json_object_object_get_ex(resp, "success", &success)) { + result = json_object_get_boolean(success); + } + json_object_put(resp); + + printf("create session %d pubkey %s\n", result, pub_key.c_str()); + return result; +} diff --git a/source/http.cpp b/source/http.cpp new file mode 100644 index 0000000..c08c075 --- /dev/null +++ b/source/http.cpp @@ -0,0 +1,100 @@ +#include "http.h" +#include + +#ifndef CURL_PROGRESSFUNC_CONTINUE +#define CURL_PROGRESSFUNC_CONTINUE 0x10000001 +#endif + +/// @brief curl context + +http::http(const std::string& useragent) : chunk(nullptr) { + this->easy = curl_easy_init(); + + curl_easy_setopt(this->easy, CURLOPT_USERAGENT, useragent.c_str()); + curl_easy_setopt(this->easy, CURLOPT_FOLLOWLOCATION, 1L); + // enable all supported built-in compressions + curl_easy_setopt(this->easy, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(this->easy, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(this->easy, CURLOPT_SSL_VERIFYPEER, 0L); +} + +http::~http() { + if (this->chunk != nullptr) curl_slist_free_all(this->chunk); + if (this->easy != nullptr) curl_easy_cleanup(this->easy); +} + +void http::set_headers(const std::vector& headers) { + if (this->chunk != nullptr) { + curl_slist_free_all(this->chunk); + this->chunk = nullptr; + } + for (auto& h : headers) { + this->chunk = curl_slist_append(this->chunk, h.c_str()); + } + curl_easy_setopt(this->easy, CURLOPT_HTTPHEADER, this->chunk); +} + +size_t http::easy_write_cb(char* ptr, size_t size, size_t nmemb, void* userdata) { + std::ostream* ctx = reinterpret_cast(userdata); + size_t count = size * nmemb; + ctx->write(ptr, static_cast(count)); + return count; +} + +size_t http::easy_read_cb(char* ptr, size_t size, size_t nmemb, void* userdata) { + std::istream* ctx = reinterpret_cast(userdata); + size_t count = size * nmemb; + ctx->read(ptr, static_cast(count)); + return ctx->gcount(); +} + +int http::perform(std::ostream* body) { + curl_easy_setopt(this->easy, CURLOPT_WRITEFUNCTION, easy_write_cb); + curl_easy_setopt(this->easy, CURLOPT_WRITEDATA, body); + + CURLcode res = curl_easy_perform(this->easy); + if (res != CURLE_OK) { + body->clear(); + return res; + } + int status_code = 0; + curl_easy_getinfo(this->easy, CURLINFO_RESPONSE_CODE, &status_code); + return status_code; +} + +std::string http::encode_form(const Form& form) { + std::ostringstream ss; + char* escaped; + for (auto it = form.begin(); it != form.end(); ++it) { + if (it != form.begin()) ss << '&'; + escaped = curl_escape(it->second.c_str(), it->second.size()); + ss << it->first << '=' << escaped; + curl_free(escaped); + } + return ss.str(); +} + +int http::get(const std::string& url, std::ostream* out) { + curl_easy_setopt(this->easy, CURLOPT_URL, url.c_str()); + curl_easy_setopt(this->easy, CURLOPT_HTTPGET, 1L); + return this->perform(out); +} + +std::string http::put(const std::string& url, std::istream* data) { + std::ostringstream body; + curl_easy_setopt(this->easy, CURLOPT_URL, url.c_str()); + curl_easy_setopt(this->easy, CURLOPT_UPLOAD, 1L); + curl_easy_setopt(this->easy, CURLOPT_READFUNCTION, easy_read_cb); + curl_easy_setopt(this->easy, CURLOPT_READDATA, data); + this->perform(&body); + return body.str(); +} + +std::string http::post(const std::string& url, const std::string& data) { + std::ostringstream body; + curl_easy_setopt(this->easy, CURLOPT_URL, url.c_str()); + curl_easy_setopt(this->easy, CURLOPT_POSTFIELDS, data.c_str()); + curl_easy_setopt(this->easy, CURLOPT_POSTFIELDSIZE, data.size()); + this->perform(&body); + return body.str(); +} \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..2419507 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,58 @@ +// Include the main libnx system header, for Switch development +#include "alipan.h" +#include +#include + +extern "C" { +void userAppInit(void) { + appletInitialize(); + setsysInitialize(); + setInitialize(); + socketInitializeDefault(); + nxlinkStdio(); + accountInitialize(AccountServiceType_Administrator); +} + +void userAppExit(void) { + accountExit(); + socketExit(); + setsysExit(); + setExit(); + appletExit(); +} +} + +int main(int argc, char *argv[]) { + consoleInit(nullptr); + curl_global_init(CURL_GLOBAL_DEFAULT); + + alipan d; + d.checkLogin(); + + // Configure our supported input layout: a single player with standard controller styles + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + PadState pad; + padInitializeDefault(&pad); + + // Main loop + while (appletMainLoop()) { + // Scan the gamepad. This should be done once for each frame + padUpdate(&pad); + + // padGetButtonsDown returns the set of buttons that have been + // newly pressed in this frame compared to the previous one + u64 kDown = padGetButtonsDown(&pad); + + if (kDown & HidNpadButton_Plus) break; // break in order to return to hbmenu + + // Your code goes here + + // Update the console, sending a new frame to the display + consoleUpdate(NULL); + } + + curl_global_cleanup(); + // Deinitialize and clean up resources used by the console (important!) + consoleExit(nullptr); + return 0; +} diff --git a/source/misc.cpp b/source/misc.cpp new file mode 100644 index 0000000..4e76248 --- /dev/null +++ b/source/misc.cpp @@ -0,0 +1,13 @@ +#include "misc.h" +#include +#include + +namespace util { + +std::string hexEncode(const unsigned char* data, size_t len) { + std::stringstream ss; + for (size_t i = 0; i < len; i++) ss << std::hex << std::setw(2) << std::setfill('0') << (int)data[i]; + return ss.str(); +} + +} \ No newline at end of file