Init commit
This commit is contained in:
14
.clang-format
Normal file
14
.clang-format
Normal file
@@ -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
|
||||
...
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build/
|
||||
.vscode/
|
||||
aDrive.*
|
||||
21
.vscode/c_cpp_properties.json
vendored
Normal file
21
.vscode/c_cpp_properties.json
vendored
Normal file
@@ -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
|
||||
}
|
||||
224
Makefile
Normal file
224
Makefile
Normal file
@@ -0,0 +1,224 @@
|
||||
#---------------------------------------------------------------------------------
|
||||
.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 := 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
|
||||
#---------------------------------------------------------------------------------------
|
||||
39
include/alipan.h
Normal file
39
include/alipan.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <json-c/json.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<drive::gdItem>& _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();
|
||||
};
|
||||
33
include/http.h
Normal file
33
include/http.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2023 dragonflylee
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <curl/curl.h>
|
||||
|
||||
class http {
|
||||
public:
|
||||
using Form = std::unordered_map<std::string, std::string>;
|
||||
|
||||
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<std::string>& 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;
|
||||
};
|
||||
7
include/misc.h
Normal file
7
include/misc.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace util {
|
||||
std::string hexEncode(const unsigned char* data, size_t len);
|
||||
} // namespace util
|
||||
310
source/alipan.cpp
Normal file
310
source/alipan.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
#include "alipan.h"
|
||||
#include "http.h"
|
||||
#include "misc.h"
|
||||
#include <string.h>
|
||||
#include <switch.h>
|
||||
|
||||
#include <mbedtls/ecdsa.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <mbedtls/base64.h>
|
||||
|
||||
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<drive::gdItem> &_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<std::string> 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<uint8_t> 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<uint8_t> 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;
|
||||
}
|
||||
100
source/http.cpp
Normal file
100
source/http.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include "http.h"
|
||||
#include <sstream>
|
||||
|
||||
#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<std::string>& 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<std::ostream*>(userdata);
|
||||
size_t count = size * nmemb;
|
||||
ctx->write(ptr, static_cast<std::streamsize>(count));
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t http::easy_read_cb(char* ptr, size_t size, size_t nmemb, void* userdata) {
|
||||
std::istream* ctx = reinterpret_cast<std::istream*>(userdata);
|
||||
size_t count = size * nmemb;
|
||||
ctx->read(ptr, static_cast<std::streamsize>(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();
|
||||
}
|
||||
58
source/main.cpp
Normal file
58
source/main.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
// Include the main libnx system header, for Switch development
|
||||
#include "alipan.h"
|
||||
#include <switch.h>
|
||||
#include <curl/curl.h>
|
||||
|
||||
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;
|
||||
}
|
||||
13
source/misc.cpp
Normal file
13
source/misc.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "misc.h"
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user