Compare commits

...

4 Commits
arm64 ... main

Author SHA1 Message Date
ab287a2b69
fix: Dockerfile make buildable 2025-03-29 21:40:32 +09:00
zhaarey
4459548fb3 update 2025-01-17 10:12:44 +08:00
zhaarey
6ffd3585b6 update 2024-12-20 17:46:15 +08:00
ZHAAREY
3b3a73bb60
add arm 2024-12-01 19:13:30 +08:00
12 changed files with 250 additions and 56 deletions

View File

@ -27,6 +27,9 @@ jobs:
- name: Build - name: Build
run: | run: |
mkdir build
cd build
cmake ..
make make
- name: Set outputs - name: Set outputs
@ -41,3 +44,6 @@ jobs:
path: | path: |
rootfs rootfs
wrapper wrapper
Dockerfile

1
.gitignore vendored
View File

@ -89,3 +89,4 @@ rootfs/
.vscode/ .vscode/
wrapper wrapper
rootfs/system/bin/linker64 rootfs/system/bin/linker64
build/

58
CMakeLists.txt Normal file
View File

@ -0,0 +1,58 @@
cmake_minimum_required(VERSION 3.25)
project(wrapper)
include(ExternalProject)
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11)
set(ANDROID_NDK_PATH "$ENV{HOME}/android-ndk-r23b")
set(TOOLCHAIN "${ANDROID_NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64")
set(CMAKE_C_COMPILER "${TOOLCHAIN}/bin/x86_64-linux-android22-clang")
set(CMAKE_CXX_COMPILER "${TOOLCHAIN}/bin/x86_64-linux-android22-clang++")
set(C_COMPILER "${TOOLCHAIN}/bin/clang")
set(CMAKE_C_FLAGS "-Wall -Werror -O3")
set(CMAKE_CXX_FLAGS "-Wall -Werror -O3")
set(CMDLINE_SOURCE cmdline.c)
set(HANDLE_SOURCE main.cpp)
set(MAIN_SOURCE main.c)
set(WRAPPER_SOURCE wrapper.c)
add_library(cmdline_object OBJECT ${CMDLINE_SOURCE})
add_library(handle_object OBJECT ${HANDLE_SOURCE})
add_executable(main ${MAIN_SOURCE} $<TARGET_OBJECTS:cmdline_object> $<TARGET_OBJECTS:handle_object>)
set_target_properties(main PROPERTIES
COMPILE_DEFINITIONS "MyRelease"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/rootfs/system/bin"
)
find_library(ANDROIDAPPMUSIC_LIB androidappmusic PATHS ${CMAKE_SOURCE_DIR}/rootfs/system/lib64)
find_library(STORESERVICESCORE_LIB storeservicescore PATHS ${CMAKE_SOURCE_DIR}/rootfs/system/lib64)
find_library(MEDIAPLATFORM_LIB mediaplatform PATHS ${CMAKE_SOURCE_DIR}/rootfs/system/lib64)
find_library(CXX_SHARED_LIB c++_shared PATHS ${CMAKE_SOURCE_DIR}/rootfs/system/lib64)
# Link libraries
target_link_libraries(main
${CXX_SHARED_LIB}
${ANDROIDAPPMUSIC_LIB}
${STORESERVICESCORE_LIB}
${MEDIAPLATFORM_LIB}
)
link_directories(${CMAKE_SOURCE_DIR}/rootfs/system/lib64)
ExternalProject_Add(
wrapper
PREFIX ${CMAKE_BINARY_DIR}
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
CONFIGURE_COMMAND ""
BUILD_COMMAND ${C_COMPILER} -O3 -Wall -o wrapper ${WRAPPER_SOURCE}
BUILD_IN_SOURCE 1
INSTALL_COMMAND ""
DEPENDS main
)

26
Dockerfile Normal file
View File

@ -0,0 +1,26 @@
FROM ubuntu:latest AS builder
WORKDIR /app
RUN apt update && \
apt install aria2 lsb-release wget software-properties-common gnupg unzip build-essential cmake -y
RUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
RUN aria2c -o android-ndk-r23b-linux.zip https://dl.google.com/android/repository/android-ndk-r23b-linux.zip
RUN unzip -q -d ~ android-ndk-r23b-linux.zip
COPY . /app
RUN mkdir /app/build && cd /app/build && cmake .. && make
FROM ubuntu:latest
WORKDIR /app
COPY --from=builder /app/rootfs/ /app/rootfs/
COPY --from=builder /app/wrapper /app/
ENV args ""
CMD ["bash", "-c", "./wrapper ${args}"]
EXPOSE 10020 10020

View File

@ -1,16 +0,0 @@
all: cmdline.o handle.o main wrapper
cmdline.o:
~/android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android22-clang -Wall -Werror -nostdlib -c -O3 -o cmdline.o cmdline.c
handle.o: main.cpp
~/android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android22-clang++ -Wall -Werror -nostdlib -c -O3 -o handle.o main.cpp
main: handle.o test.c cmdline.o
~/android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android22-clang -DMyRelease -Wall -Werror -L ./rootfs/system/lib64 -landroidappmusic -lstoreservicescore -lmediaplatform -lc++_shared -O3 -Wall -o rootfs/system/bin/main cmdline.o handle.o test.c
wrapper: wrapper.c
~/android-ndk-r23b/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -O3 -Wall -o wrapper wrapper.c
clean:
rm handle.o wrapper rootfs/system/bin/main cmdline.o

View File

@ -3,10 +3,24 @@
No need for an Android emulator to decrypt ALAC files. All files from anonymous. No need for an Android emulator to decrypt ALAC files. All files from anonymous.
### Recommended Environment ### Recommended Environment
#### x86_64 only #### Only support Linux x86_64 and arm64.
For best results, it's recommended to use **Windows Subsystem for Linux (WSL)**. For best results, it's recommended to use **Windows Subsystem for Linux (WSL)**.
# Special thanks
- Anonymous, for providing the original version of this project and the legacy Frida decryption method.
- chocomint, for providing support for arm64 arch.
--- ---
### Version 2 Docker
Available for x86_64 and arm64. Need to download prebuilt version from releases or actions.
Build image: `docker build --tag wrapper .`
Login: `docker run -v ./rootfs/data:/app/rootfs/data -p 10020:10020 -e args="-L username:password -F -H 0.0.0.0" wrapper`
Run: `docker run -v ./rootfs/data:/app/rootfs/data -p 10020:10020 -e args="-H 0.0.0.0" wrapper`
### Version 2 ### Version 2
@ -21,22 +35,32 @@ For best results, it's recommended to use **Windows Subsystem for Linux (WSL)**.
-P, --proxy=STRING (default: `''`) -P, --proxy=STRING (default: `''`)
-L, --login=STRING ([username]:[password]) -L, --login=STRING ([username]:[password])
``` ```
#### Installation #### Installation x86_64
```shell ```shell
sudo -i sudo -i
wget "https://github.com/zhaarey/wrapper/releases/download/linux.V2/wrapper.linux.x86_64.V2.tar.gz" wget "https://github.com/zhaarey/wrapper/releases/download/linux.V2/wrapper.x86_64.tar.gz"
mkdir wrapper mkdir wrapper
tar -xzf wrapper.linux.x86_64.V2.tar.gz -C wrapper tar -xzf wrapper.x86_64.tar.gz -C wrapper
cd wrapper cd wrapper
./wrapper ./wrapper
``` ```
#### Installation arm64
```shell
sudo -i
wget "https://github.com/zhaarey/wrapper/releases/download/arm64/wrapper.arm64.tar.gz"
mkdir wrapper
tar -xzf wrapper.arm64.tar.gz -C wrapper
cd wrapper
./wrapper
```
--- ---
### Version 1 ### Version 1
#### Usage: #### Usage:
`./wrapper [port] ([username] [password])` `./wrapper [port] ([username] [password])`
#### Installation #### Installation only x86_64
```shell ```shell
sudo -i sudo -i
wget "https://github.com/zhaarey/wrapper/releases/download/linux/wrapper.linux.x86_64.tar.gz" wget "https://github.com/zhaarey/wrapper/releases/download/linux/wrapper.linux.x86_64.tar.gz"

View File

@ -1,7 +1,7 @@
/* /*
File autogenerated by gengetopt version 2.23 File autogenerated by gengetopt version 2.23
generated with the following command: generated with the following command:
gengetopt -i wrapper.ggo gengetopt
The developers of gengetopt consider the fixed text that goes in all The developers of gengetopt consider the fixed text that goes in all
gengetopt output files to be in the public domain: gengetopt output files to be in the public domain:
@ -40,11 +40,13 @@ const char *gengetopt_args_info_help[] = {
" -D, --decrypt-port=INT (default=`10020')", " -D, --decrypt-port=INT (default=`10020')",
" -M, --m3u8-port=INT (default=`20020')", " -M, --m3u8-port=INT (default=`20020')",
" -P, --proxy=STRING (default=`')", " -P, --proxy=STRING (default=`')",
" -L, --login=STRING ", " -L, --login=STRING username:password",
" -F, --code-from-file (default=off)",
0 0
}; };
typedef enum {ARG_NO typedef enum {ARG_NO
, ARG_FLAG
, ARG_STRING , ARG_STRING
, ARG_INT , ARG_INT
} cmdline_parser_arg_type; } cmdline_parser_arg_type;
@ -72,6 +74,7 @@ void clear_given (struct gengetopt_args_info *args_info)
args_info->m3u8_port_given = 0 ; args_info->m3u8_port_given = 0 ;
args_info->proxy_given = 0 ; args_info->proxy_given = 0 ;
args_info->login_given = 0 ; args_info->login_given = 0 ;
args_info->code_from_file_given = 0 ;
} }
static static
@ -88,6 +91,7 @@ void clear_args (struct gengetopt_args_info *args_info)
args_info->proxy_orig = NULL; args_info->proxy_orig = NULL;
args_info->login_arg = NULL; args_info->login_arg = NULL;
args_info->login_orig = NULL; args_info->login_orig = NULL;
args_info->code_from_file_flag = 0;
} }
@ -103,6 +107,7 @@ void init_args_info(struct gengetopt_args_info *args_info)
args_info->m3u8_port_help = gengetopt_args_info_help[4] ; args_info->m3u8_port_help = gengetopt_args_info_help[4] ;
args_info->proxy_help = gengetopt_args_info_help[5] ; args_info->proxy_help = gengetopt_args_info_help[5] ;
args_info->login_help = gengetopt_args_info_help[6] ; args_info->login_help = gengetopt_args_info_help[6] ;
args_info->code_from_file_help = gengetopt_args_info_help[7] ;
} }
@ -244,6 +249,8 @@ cmdline_parser_dump(FILE *outfile, struct gengetopt_args_info *args_info)
write_into_file(outfile, "proxy", args_info->proxy_orig, 0); write_into_file(outfile, "proxy", args_info->proxy_orig, 0);
if (args_info->login_given) if (args_info->login_given)
write_into_file(outfile, "login", args_info->login_orig, 0); write_into_file(outfile, "login", args_info->login_orig, 0);
if (args_info->code_from_file_given)
write_into_file(outfile, "code-from-file", 0, 0 );
i = EXIT_SUCCESS; i = EXIT_SUCCESS;
@ -410,6 +417,9 @@ int update_arg(void *field, char **orig_field,
val = possible_values[found]; val = possible_values[found];
switch(arg_type) { switch(arg_type) {
case ARG_FLAG:
*((int *)field) = !*((int *)field);
break;
case ARG_INT: case ARG_INT:
if (val) *((int *)field) = strtol (val, &stop_char, 0); if (val) *((int *)field) = strtol (val, &stop_char, 0);
break; break;
@ -440,6 +450,7 @@ int update_arg(void *field, char **orig_field,
/* store the original value */ /* store the original value */
switch(arg_type) { switch(arg_type) {
case ARG_NO: case ARG_NO:
case ARG_FLAG:
break; break;
default: default:
if (value && orig_field) { if (value && orig_field) {
@ -507,10 +518,11 @@ cmdline_parser_internal (
{ "m3u8-port", 1, NULL, 'M' }, { "m3u8-port", 1, NULL, 'M' },
{ "proxy", 1, NULL, 'P' }, { "proxy", 1, NULL, 'P' },
{ "login", 1, NULL, 'L' }, { "login", 1, NULL, 'L' },
{ "code-from-file", 0, NULL, 'F' },
{ 0, 0, 0, 0 } { 0, 0, 0, 0 }
}; };
c = getopt_long (argc, argv, "hVH:D:M:P:L:", long_options, &option_index); c = getopt_long (argc, argv, "hVH:D:M:P:L:F", long_options, &option_index);
if (c == -1) break; /* Exit from `while (1)' loop. */ if (c == -1) break; /* Exit from `while (1)' loop. */
@ -574,7 +586,7 @@ cmdline_parser_internal (
goto failure; goto failure;
break; break;
case 'L': /* . */ case 'L': /* username:password. */
if (update_arg( (void *)&(args_info->login_arg), if (update_arg( (void *)&(args_info->login_arg),
@ -586,6 +598,16 @@ cmdline_parser_internal (
goto failure; goto failure;
break; break;
case 'F': /* . */
if (update_arg((void *)&(args_info->code_from_file_flag), 0, &(args_info->code_from_file_given),
&(local_args_info.code_from_file_given), optarg, 0, 0, ARG_FLAG,
check_ambiguity, override, 1, 0, "code-from-file", 'F',
additional_error))
goto failure;
break;
case 0: /* Long option with no short option */ case 0: /* Long option with no short option */
case '?': /* Invalid option. */ case '?': /* Invalid option. */

View File

@ -51,9 +51,11 @@ struct gengetopt_args_info
char * proxy_arg; /**< @brief (default=''). */ char * proxy_arg; /**< @brief (default=''). */
char * proxy_orig; /**< @brief original value given at command line. */ char * proxy_orig; /**< @brief original value given at command line. */
const char *proxy_help; /**< @brief help description. */ const char *proxy_help; /**< @brief help description. */
char * login_arg; /**< @brief . */ char * login_arg; /**< @brief username:password. */
char * login_orig; /**< @brief original value given at command line. */ char * login_orig; /**< @brief username:password original value given at command line. */
const char *login_help; /**< @brief help description. */ const char *login_help; /**< @brief username:password help description. */
int code_from_file_flag; /**< @brief (default=off). */
const char *code_from_file_help; /**< @brief help description. */
unsigned int help_given ; /**< @brief Whether help was given. */ unsigned int help_given ; /**< @brief Whether help was given. */
unsigned int version_given ; /**< @brief Whether version was given. */ unsigned int version_given ; /**< @brief Whether version was given. */
@ -62,6 +64,7 @@ struct gengetopt_args_info
unsigned int m3u8_port_given ; /**< @brief Whether m3u8-port was given. */ unsigned int m3u8_port_given ; /**< @brief Whether m3u8-port was given. */
unsigned int proxy_given ; /**< @brief Whether proxy was given. */ unsigned int proxy_given ; /**< @brief Whether proxy was given. */
unsigned int login_given ; /**< @brief Whether login was given. */ unsigned int login_given ; /**< @brief Whether login was given. */
unsigned int code_from_file_given ; /**< @brief Whether code-from-file was given. */
} ; } ;

10
compose.yaml Normal file
View File

@ -0,0 +1,10 @@
services:
wrapper:
image: sim1222/wrapper:latest
build: .
volumes:
- ./rootfs/data:/app/rootfs/data
ports:
- 10020:10020
environment:
- args="-H 0.0.0.0"

View File

@ -10,6 +10,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h>
#include "import.h" #include "import.h"
#include "cmdline.h" #include "cmdline.h"
@ -22,6 +23,11 @@ static uint8_t leaseMgr[16];
struct gengetopt_args_info args_info; struct gengetopt_args_info args_info;
char *amUsername, *amPassword; char *amUsername, *amPassword;
int file_exists(char *filename) {
struct stat buffer;
return (stat (filename, &buffer) == 0);
}
static void dialogHandler(long j, struct shared_ptr *protoDialogPtr, static void dialogHandler(long j, struct shared_ptr *protoDialogPtr,
struct shared_ptr *respHandler) { struct shared_ptr *respHandler) {
const char *const title = std_string_data( const char *const title = std_string_data(
@ -79,9 +85,33 @@ static void credentialHandler(struct shared_ptr *credReqHandler,
int passLen = strlen(amPassword); int passLen = strlen(amPassword);
if (need2FA) { if (need2FA) {
if (args_info.code_from_file_flag) {
fprintf(stderr, "[!] Enter your 2FA code into rootfs/data/code.txt\n");
fprintf(stderr, "[!] Example command: echo -n 114514 > rootfs/data/2fa.txt\n");
fprintf(stderr, "[!] Waiting for input...\n");
int count = 0;
while (1)
{
if (count >= 20) {
fprintf(stderr, "[!] Failed to get 2FA Code in 60s. Exiting...\n");
exit(0);
}
if (file_exists("/data/2fa.txt")) {
FILE *fp = fopen("/data/2fa.txt", "r");
fscanf(fp, "%6s", amPassword + passLen);
remove("/data/2fa.txt");
fprintf(stderr, "[!] Code file detected! Logging in...\n");
break;
} else {
sleep(3);
count++;
}
}
} else {
printf("2FA code: "); printf("2FA code: ");
scanf("%6s", amPassword + passLen); scanf("%6s", amPassword + passLen);
} }
}
uint8_t *const ptr = malloc(80); uint8_t *const ptr = malloc(80);
memset(ptr + 8, 0, 16); memset(ptr + 8, 0, 16);
@ -435,16 +465,43 @@ inline static int new_socket() {
const char* get_m3u8_method_play(uint8_t leaseMgr[16], unsigned long adam) { const char* get_m3u8_method_play(uint8_t leaseMgr[16], unsigned long adam) {
union std_string HLS = new_std_string_short_mode("HLS"); union std_string HLS = new_std_string_short_mode("HLS");
struct std_vector HLSParam = new_std_vector(&HLS); struct std_vector HLSParam = new_std_vector(&HLS);
static uint8_t z0 = 1; static uint8_t z0 = 0;
struct shared_ptr *ptr_result = (struct shared_ptr *) malloc(32); struct shared_ptr ptr_result;
_ZN22SVPlaybackLeaseManager12requestAssetERKmRKNSt6__ndk16vectorINS2_12basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEENS7_IS9_EEEERKb( _ZN22SVPlaybackLeaseManager12requestAssetERKmRKNSt6__ndk16vectorINS2_12basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEENS7_IS9_EEEERKb(
ptr_result, leaseMgr, &adam, &HLSParam, &z0 &ptr_result, leaseMgr, &adam, &HLSParam, &z0
); );
if (_ZNK23SVPlaybackAssetResponse13hasValidAssetEv(ptr_result->obj)) {
struct shared_ptr *playbackAsset = _ZNK23SVPlaybackAssetResponse13playbackAssetEv(ptr_result->obj); if (ptr_result.obj == NULL) {
union std_string *m3u8 = (union std_string *) malloc(24); return NULL;
_ZNK17storeservicescore13PlaybackAsset9URLStringEv(m3u8, playbackAsset->obj); }
return std_string_data(m3u8);
if (_ZNK23SVPlaybackAssetResponse13hasValidAssetEv(ptr_result.obj)) {
struct shared_ptr *playbackAsset = _ZNK23SVPlaybackAssetResponse13playbackAssetEv(ptr_result.obj);
if (playbackAsset == NULL || playbackAsset->obj == NULL) {
return NULL;
}
union std_string *m3u8 = malloc(sizeof(union std_string));
if (m3u8 == NULL) {
return NULL;
}
void *playbackObj = playbackAsset->obj;
_ZNK17storeservicescore13PlaybackAsset9URLStringEv(m3u8, playbackObj);
if (m3u8 == NULL || std_string_data(m3u8) == NULL) {
free(m3u8);
return NULL;
}
const char *m3u8_str = std_string_data(m3u8);
if (m3u8_str) {
char *result = strdup(m3u8_str); // Make a copy
free(m3u8);
return result;
} else {
return NULL;
}
} else { } else {
return NULL; return NULL;
} }
@ -469,11 +526,17 @@ void handle_m3u8(const int connfd) {
const char *m3u8 = get_m3u8_method_play(leaseMgr, adamID); const char *m3u8 = get_m3u8_method_play(leaseMgr, adamID);
if (m3u8 == NULL) { if (m3u8 == NULL) {
fprintf(stderr, "[.] failed to get m3u8 of adamId: %ld\n", adamID); fprintf(stderr, "[.] failed to get m3u8 of adamId: %ld\n", adamID);
writefull(connfd, NULL, sizeof(NULL)); writefull(connfd, "\n", sizeof("\n"));
} else { } else {
fprintf(stderr, "[.] m3u8 adamId: %ld, url: %s\n", adamID, m3u8); fprintf(stderr, "[.] m3u8 adamId: %ld, url: %s\n", adamID, m3u8);
strcat((char *)m3u8, "\n"); char *with_newline = malloc(strlen(m3u8) + 2);
writefull(connfd, (void *)m3u8, strlen(m3u8)); if (with_newline) {
strcpy(with_newline, m3u8);
strcat(with_newline, "\n");
writefull(connfd, with_newline, strlen(with_newline));
free(with_newline);
}
free((void *)m3u8);
} }
} }
} }
@ -544,11 +607,9 @@ int main(int argc, char *argv[]) {
_ZN22SVPlaybackLeaseManager12requestLeaseERKb(leaseMgr, &autom); _ZN22SVPlaybackLeaseManager12requestLeaseERKb(leaseMgr, &autom);
FHinstance = _ZN21SVFootHillSessionCtrl8instanceEv(); FHinstance = _ZN21SVFootHillSessionCtrl8instanceEv();
if (args_info.m3u8_port_given) {
pthread_t m3u8_thread; pthread_t m3u8_thread;
pthread_create(&m3u8_thread, NULL, &new_socket_m3u8, NULL); pthread_create(&m3u8_thread, NULL, &new_socket_m3u8, NULL);
} else { pthread_detach(m3u8_thread);
fprintf(stderr, "[!] The feature of getting m3u8 is defaultly disabled because it's unstable now. To enable it, please manually specify m3u8-port param.\n");
}
return new_socket(); return new_socket();
} }

View File

@ -1,6 +1,6 @@
#define _GNU_SOURCE #define _GNU_SOURCE
#include <errno.h> #include <errno.h>
#include <sched.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -35,21 +35,19 @@ int main(int argc, char *argv[], char *envp[]) {
chmod("/system/bin/linker64", 0755); chmod("/system/bin/linker64", 0755);
chmod("/system/bin/main", 0755); chmod("/system/bin/main", 0755);
if (unshare(CLONE_NEWPID)) {
perror("unshare");
return 1;
}
child_proc = fork(); child_proc = fork();
if (child_proc == -1) { if (child_proc == -1) {
perror("fork"); perror("fork");
return 1; return 1;
} }
if (child_proc > 0) { if (child_proc > 0) {
close(STDOUT_FILENO); close(STDOUT_FILENO);
wait(NULL); wait(NULL); // Parent waits for the child process to terminate
return 0; return 0;
} }
// Child process logic
mkdir("/data/data/com.apple.android.music/files", 0777); mkdir("/data/data/com.apple.android.music/files", 0777);
mkdir("/data/data/com.apple.android.music/files/mpl_db", 0777); mkdir("/data/data/com.apple.android.music/files/mpl_db", 0777);
execve("/system/bin/main", argv, envp); execve("/system/bin/main", argv, envp);

View File

@ -5,4 +5,5 @@ option "host" H "" string optional default="127.0.0.1"
option "decrypt-port" D "" int optional default="10020" option "decrypt-port" D "" int optional default="10020"
option "m3u8-port" M "" int optional default="20020" option "m3u8-port" M "" int optional default="20020"
option "proxy" P "" string optional default="" option "proxy" P "" string optional default=""
option "login" L "" string optional option "login" L "username:password" string optional
option "code-from-file" F "" flag off