فهرست منبع

Add support for exporting complete Quantumult configuration file

Fix incorrect generation of Quantumult X configuratios.
Fix broken group name option.
Add external configuration support for customize Quantumult(X) base.
Update Travis CI build script.
Tweak Emoji match rules.
Clean up codes.
Tindy X 6 سال پیش
والد
کامیت
b085c0d0c1
8فایلهای تغییر یافته به همراه226 افزوده شده و 51 حذف شده
  1. 7 12
      .travis.yml
  2. 2 0
      base/config/example_external_config.ini
  3. 5 2
      base/pref.ini
  4. 27 0
      base/quan.conf
  5. 36 15
      src/main.cpp
  6. 1 3
      src/nodemanip.cpp
  7. 146 18
      src/subexport.cpp
  8. 2 1
      src/subexport.h

+ 7 - 12
.travis.yml

@@ -1,11 +1,11 @@
 language: cpp
-sudo: required
+os: linux
 stages:
     - name: deploy
       if: branch = master
     - name: before_script
       if: branch = master
-matrix:
+jobs:
     include:
     - name: "macOS Build"
       os: osx
@@ -15,9 +15,8 @@ matrix:
       - bash scripts/build.macos.release.sh
       deploy:
         provider: releases
-        api_key: "$GITHUB_OAUTH_TOKEN"
+        token: "$GITHUB_OAUTH_TOKEN"
         file: "subconverter_darwin64.tar.gz"
-        skip_cleanup: true
         draft: true
         on:
           tags: true
@@ -29,9 +28,8 @@ matrix:
         - mv subconverter_linux64.tar.gz subconverter_linux32.tar.gz
       deploy:
         provider: releases
-        api_key: "$GITHUB_OAUTH_TOKEN"
+        token: "$GITHUB_OAUTH_TOKEN"
         file: "subconverter_linux32.tar.gz"
-        skip_cleanup: true
         draft: true
         on:
           tags: true
@@ -41,9 +39,8 @@ matrix:
       - docker run -v $TRAVIS_BUILD_DIR:/root/workdir alpine:latest /bin/sh -c "apk add bash git && cd /root/workdir && chmod +x scripts/build.alpine.release.sh && bash scripts/build.alpine.release.sh"
       deploy:
         provider: releases
-        api_key: "$GITHUB_OAUTH_TOKEN"
+        token: "$GITHUB_OAUTH_TOKEN"
         file: "subconverter_linux64.tar.gz"
-        skip_cleanup: true
         draft: true
         on:
           tags: true
@@ -56,9 +53,8 @@ matrix:
         - mv subconverter_linux64.tar.gz subconverter_armhf.tar.gz
       deploy:
         provider: releases
-        api_key: "$GITHUB_OAUTH_TOKEN"
+        token: "$GITHUB_OAUTH_TOKEN"
         file: "subconverter_armhf.tar.gz"
-        skip_cleanup: true
         draft: true
         on:
           tags: true
@@ -71,9 +67,8 @@ matrix:
         - mv subconverter_linux64.tar.gz subconverter_aarch64.tar.gz
       deploy:
         provider: releases
-        api_key: "$GITHUB_OAUTH_TOKEN"
+        token: "$GITHUB_OAUTH_TOKEN"
         file: "subconverter_aarch64.tar.gz"
-        skip_cleanup: true
         draft: true
         on:
           tags: true

+ 2 - 0
base/config/example_external_config.ini

@@ -40,6 +40,8 @@ clash_rule_base=config/forcerule.yml
 ;surge_rule_base=surge.conf
 ;surfboard_rule_base=surfboard.conf
 ;mellow_rule_base=mellow.conf
+;quan_rule_base=quan.conf
+;quanx_rule_base=quanx.conf
 
 ;Options for renaming nodes
 ;rename=Test-(.*?)-(.*?)-(.*?)\((.*?)\)@\1\4x测试线路_自\2到\3

+ 5 - 2
base/pref.ini

@@ -27,6 +27,9 @@ surfboard_rule_base=surfboard.conf
 ;Mellow config base used by the generator, supports local files/URL
 mellow_rule_base=mellow.conf
 
+;Quantumult X config base used by the generator, supports local files/URL
+quan_rule_base=quan.conf
+
 ;Quantumult X config base used by the generator, supports local files/URL
 quanx_rule_base=quanx.conf
 
@@ -146,8 +149,8 @@ rule=ES,🇪🇸
 rule=EU,🇪🇺
 rule=(Finland|芬兰|赫尔辛基),🇫🇮
 rule=(FR|France|法国|巴黎),🇫🇷
-rule=(UK|England|UnitedKingdom|英国|英|伦敦),🇬🇧
-rule=(HK|HongKong|香港|深港|沪港|呼港|HKT|HKBN|HGC|WTT|CMI|穗港|京港|港),🇭🇰
+rule=(UK|England|United.*?Kingdom|英国|英|伦敦),🇬🇧
+rule=(?i)(HK|Hong.*?Kong|香港|深港|沪港|呼港|HKT|HKBN|HGC|WTT|CMI|穗港|京港|港),🇭🇰
 rule=(Indonesia|印尼|印度尼西亚|雅加达),🇮🇩
 rule=(Ireland|爱尔兰|都柏林),🇮🇪
 rule=(India|印度|孟买),🇮🇳

+ 27 - 0
base/quan.conf

@@ -0,0 +1,27 @@
+[SERVER]
+
+[SOURCE]
+
+[BACKUP-SERVER]
+
+[SUSPEND-SSID]
+
+[POLICY]
+
+[DNS]
+1.1.1.1
+
+[REWRITE]
+
+[URL-REJECTION]
+
+[TCP]
+
+[GLOBAL]
+
+[HOST]
+
+[STATE]
+STATE,AUTO
+
+[MITM]

+ 36 - 15
src/main.cpp

@@ -40,7 +40,7 @@ std::string proxy_ruleset, proxy_subscription;
 
 std::string clash_rule_base;
 string_array clash_extra_group;
-std::string surge_rule_base, surfboard_rule_base, mellow_rule_base, quanx_rule_base;
+std::string surge_rule_base, surfboard_rule_base, mellow_rule_base, quan_rule_base, quanx_rule_base;
 std::string surge_ssr_path;
 
 //pre-compiled rule bases
@@ -172,6 +172,8 @@ void readConf()
         surfboard_rule_base = ini.Get("surfboard_rule_base");
     if(ini.ItemExist("mellow_rule_base"))
         mellow_rule_base = ini.Get("mellow_rule_base");
+    if(ini.ItemExist("quan_rule_base"))
+        quan_rule_base = ini.Get("quan_rule_base");
     if(ini.ItemExist("quanx_rule_base"))
         quanx_rule_base = ini.Get("quanx_rule_base");
     if(ini.ItemExist("append_proxy_type"))
@@ -289,6 +291,8 @@ struct ExternalConfig
     std::string surge_rule_base;
     std::string surfboard_rule_base;
     std::string mellow_rule_base;
+    std::string quan_rule_base;
+    std::string quanx_rule_base;
     string_array rename;
     string_array emoji;
     bool overwrite_original_rules = false;
@@ -305,7 +309,6 @@ int loadExternalConfig(std::string &path, ExternalConfig &ext, std::string proxy
 
     INIReader ini;
     ini.store_isolated_line = true;
-    ini.keep_empty_section = false;
     ini.SetIsolatedItemsSection("custom");
     if(ini.Parse(base_content) != INIREADER_EXCEPTION_NONE)
     {
@@ -327,6 +330,10 @@ int loadExternalConfig(std::string &path, ExternalConfig &ext, std::string proxy
         ext.surfboard_rule_base = ini.Get("surfboard_rule_base");
     if(ini.ItemExist("mellow_rule_base"))
         ext.mellow_rule_base = ini.Get("mellow_rule_base");
+    if(ini.ItemExist("quan_rule_base"))
+        ext.quan_rule_base = ini.Get("quan_rule_base");
+    if(ini.ItemExist("quanx_rule_base"))
+        ext.quanx_rule_base = ini.Get("quanx_rule_base");
 
     if(ini.ItemExist("overwrite_original_rules"))
         ext.overwrite_original_rules = ini.GetBool("overwrite_original_rules");
@@ -390,6 +397,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
 
     //for external configuration
     std::string ext_clash_base = clash_rule_base, ext_surge_base = surge_rule_base, ext_mellow_base = mellow_rule_base, ext_surfboard_base = surfboard_rule_base;
+    std::string ext_quan_base = quan_rule_base, ext_quanx_base = quanx_rule_base;
 
     //validate urls
     if(!url.size())
@@ -434,6 +442,10 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
             ext_surfboard_base = extconf.surfboard_rule_base;
         if(extconf.mellow_rule_base.size())
             ext_mellow_base = extconf.mellow_rule_base;
+        if(extconf.quan_rule_base.size())
+            ext_quan_base = extconf.quan_rule_base;
+        if(extconf.quanx_rule_base.size())
+            ext_quanx_base = extconf.quanx_rule_base;
         if(extconf.rename.size())
             ext.rename_array = extconf.rename;
         if(extconf.emoji.size())
@@ -530,10 +542,6 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
     else
         exclude_remarks = def_exclude_remarks;
 
-    //check custom group name
-    if(group.size())
-        custom_group = group;
-
     //start parsing urls
     string_array stream_temp = safe_get_streams(), time_temp = safe_get_times();
     for(std::string &x : urls)
@@ -554,6 +562,11 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
         return "No nodes were found!";
     }
 
+    //check custom group name
+    if(group.size())
+        for(nodeInfo &x : nodes)
+            x.group = group;
+
     if(subInfo.size() && groupID == 1)
         extra_headers.emplace("Subscription-UserInfo", subInfo);
 
@@ -667,26 +680,34 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS)
     else if(target == "quan")
     {
         std::cerr<<"Quantumult"<<std::endl;
-        output_content = netchToQuan(nodes, ext);
+        if(!ext.nodelist)
+        {
+            if(fileExist(ext_quan_base))
+                base_content = fileGet(ext_quan_base, false);
+            else
+                base_content = webGet(ext_quan_base, getSystemProxy());
+        }
+
+        output_content = netchToQuan(nodes, base_content, rca, extra_group, ext);
+
         if(upload == "true")
             uploadGist("quan", upload_path, output_content, false);
     }
     else if(target == "quanx")
     {
         std::cerr<<"Quantumult X"<<std::endl;
-
-        if(fileExist(quanx_rule_base))
-            base_content = fileGet(quanx_rule_base, false);
-        else
-            base_content = webGet(quanx_rule_base, getSystemProxy());
+        if(!ext.nodelist)
+        {
+            if(fileExist(ext_quanx_base))
+                base_content = fileGet(ext_quanx_base, false);
+            else
+                base_content = webGet(ext_quanx_base, getSystemProxy());
+        }
 
         output_content = netchToQuanX(nodes, base_content, rca, extra_group, ext);
 
         if(upload == "true")
             uploadGist("quanx", upload_path, output_content, false);
-
-        if(write_managed_config && managed_config_prefix.size())
-            output_content = "#!MANAGED-CONFIG " + managed_config_prefix + "/sub?" + argument + "\n\n" + output_content;
     }
     else if(target == "ssd")
     {

+ 1 - 3
src/nodemanip.cpp

@@ -9,7 +9,7 @@
 #include "webget.h"
 #include "speedtestutil.h"
 
-std::string override_conf_port, custom_group;
+std::string override_conf_port;
 int socksport;
 bool ss_libev, ssr_libev;
 extern bool api_mode;
@@ -107,8 +107,6 @@ int addNodes(std::string link, std::vector<nodeInfo> &allNodes, int groupID, std
         if(linkType > 0)
         {
             explode(link, ss_libev, ssr_libev, override_conf_port, socksport, node);
-            if(custom_group.size() != 0)
-                node.group = custom_group;
             if(node.linkType == -1)
             {
                 writeLog(LOG_TYPE_ERROR, "No valid link found.");

+ 146 - 18
src/subexport.cpp

@@ -363,6 +363,9 @@ void rulesetToSurge(INIReader &base_rule, std::vector<ruleset_content> &ruleset_
     case -1:
         base_rule.SetCurrentSection("filter_local"); //Quantumult X
         break;
+    case -2:
+        base_rule.SetCurrentSection("TCP"); //Quantumult
+        break;
     default:
         base_rule.SetCurrentSection("Rule");
     }
@@ -382,7 +385,7 @@ void rulesetToSurge(INIReader &base_rule, std::vector<ruleset_content> &ruleset_
             if(strLine == "MATCH")
                 strLine = "FINAL";
             strLine += "," + rule_group;
-            if(surge_ver == -1)
+            if(surge_ver == -1 || surge_ver == -2)
             {
                 if(std::count(strLine.begin(), strLine.end(), ',') > 2 && regReplace(strLine, rule_match_regex, "$2") == ",no-resolve")
                     strLine = regReplace(strLine, rule_match_regex, "$1$3$2");
@@ -416,13 +419,13 @@ void rulesetToSurge(INIReader &base_rule, std::vector<ruleset_content> &ruleset_
             strStrm<<retrived_rules;
             while(getline(strStrm, strLine, delimiter))
             {
-                if(surge_ver == -1 && (strLine.find("IP-CIDR6") == 0 || strLine.find("URL-REGEX") == 0 || strLine.find("PROCESS-NAME") == 0 || strLine.find("AND") == 0 || strLine.find("OR") == 0)) //remove unsupported types
+                if((surge_ver == -1 || surge_ver == -2) && (strLine.find("IP-CIDR6") == 0 || strLine.find("URL-REGEX") == 0 || strLine.find("PROCESS-NAME") == 0 || strLine.find("AND") == 0 || strLine.find("OR") == 0)) //remove unsupported types
                     continue;
                 strLine = replace_all_distinct(strLine, "\r", ""); //remove line break
                 if(!strLine.size() || strLine.find("#") == 0 || strLine.find(";") == 0) //remove comments
                     continue;
                 strLine += "," + rule_group;
-                if(surge_ver == -1)
+                if(surge_ver == -1 || surge_ver == -2)
                 {
                     if(std::count(strLine.begin(), strLine.end(), ',') > 2 && regReplace(strLine, rule_match_regex, "$2") == ",no-resolve")
                         strLine = regReplace(strLine, rule_match_regex, "$1$3$2");
@@ -1246,7 +1249,29 @@ std::string netchToVMess(std::vector<nodeInfo> &nodes, extra_settings &ext)
     return base64_encode(allLinks);
 }
 
-std::string netchToQuan(std::vector<nodeInfo> &nodes, extra_settings &ext)
+std::string netchToQuan(std::vector<nodeInfo> &nodes, std::string &base_conf, std::vector<ruleset_content> &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext)
+{
+    INIReader ini;
+    ini.store_any_line = true;
+    if(!ext.nodelist && ini.Parse(base_conf) != 0)
+        return std::string();
+
+    netchToQuan(nodes, ini, ruleset_content_array, extra_proxy_group, ext);
+
+    if(ext.nodelist)
+    {
+        string_array allnodes;
+        ini.GetAll("SERVER", "{NONAME}", allnodes);
+        std::string allLinks = std::accumulate(allnodes.begin(), allnodes.end(), allnodes[0], [](std::string a, std::string b)
+        {
+            return std::move(a) + "\n" + std::move(b);
+        });
+        return base64_encode(allLinks);
+    }
+    return ini.ToString();
+}
+
+void netchToQuan(std::vector<nodeInfo> &nodes, INIReader &ini, std::vector<ruleset_content> &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext)
 {
     rapidjson::Document json;
     std::string type;
@@ -1256,6 +1281,7 @@ std::string netchToQuan(std::vector<nodeInfo> &nodes, extra_settings &ext)
     std::string id, aid, transproto, faketype, host, path, quicsecure, quicsecret;
     std::string proxyStr, allLinks;
     bool tlssecure;
+    std::vector<nodeInfo> nodelist;
 
     std::for_each(nodes.begin(), nodes.end(), [ext](nodeInfo &x)
     {
@@ -1275,6 +1301,8 @@ std::string netchToQuan(std::vector<nodeInfo> &nodes, extra_settings &ext)
         });
     }
 
+    ini.SetCurrentSection("SERVER");
+    ini.EraseSection();
     for(nodeInfo &x : nodes)
     {
         json.Parse(x.proxyStr.data());
@@ -1309,7 +1337,9 @@ std::string netchToQuan(std::vector<nodeInfo> &nodes, extra_settings &ext)
                 proxyStr += ", certificate=0";
             if(transproto == "ws")
                 proxyStr += ", obfs=ws, obfs-path=\"" + path + "\", obfs-header=\"Host: " + host + "\"";
-            proxyStr = "vmess://" + urlsafe_base64_encode(proxyStr);
+
+            if(ext.nodelist)
+                proxyStr = "vmess://" + urlsafe_base64_encode(proxyStr);
             break;
         case SPEEDTEST_MESSAGE_FOUNDSSR:
             protocol = GetMember(json, "Protocol");
@@ -1317,27 +1347,121 @@ std::string netchToQuan(std::vector<nodeInfo> &nodes, extra_settings &ext)
             obfs = GetMember(json, "OBFS");
             obfsparam = GetMember(json, "OBFSParam");
 
-            proxyStr = "ssr://" + urlsafe_base64_encode(hostname + ":" + port + ":" + protocol + ":" + method + ":" + obfs + ":" + urlsafe_base64_encode(password) \
-                       + "/?group=" + urlsafe_base64_encode(x.group) + "&remarks=" + urlsafe_base64_encode(remark) \
-                       + "&obfsparam=" + urlsafe_base64_encode(obfsparam) + "&protoparam=" + urlsafe_base64_encode(protoparam));
+            if(ext.nodelist)
+            {
+                proxyStr = "ssr://" + urlsafe_base64_encode(hostname + ":" + port + ":" + protocol + ":" + method + ":" + obfs + ":" + urlsafe_base64_encode(password) \
+                           + "/?group=" + urlsafe_base64_encode(x.group) + "&remarks=" + urlsafe_base64_encode(remark) \
+                           + "&obfsparam=" + urlsafe_base64_encode(obfsparam) + "&protoparam=" + urlsafe_base64_encode(protoparam));
+            }
+            else
+            {
+                proxyStr = remark + " = shadowsocksr, " + hostname + ", " + port + ", " + method + ", \"" + password + "\", group=" + x.group + ", protocol=" + protocol + ", obfs=" + obfs;
+                if(protoparam.size())
+                    proxyStr += ", protocol_param=" + protoparam;
+                if(obfsparam.size())
+                    proxyStr += ", obfs_param=" + obfsparam;
+            }
             break;
         case SPEEDTEST_MESSAGE_FOUNDSS:
             plugin = GetMember(json, "Plugin");
             pluginopts = GetMember(json, "PluginOption");
-            proxyStr = "ss://" + urlsafe_base64_encode(method + ":" + password) + "@" + hostname + ":" + port;
-            if(plugin.size() & pluginopts.size())
+
+            if(ext.nodelist)
             {
-                proxyStr += "/?plugin=" + UrlEncode(plugin + ";" +pluginopts);
+                proxyStr = "ss://" + urlsafe_base64_encode(method + ":" + password) + "@" + hostname + ":" + port;
+                if(plugin.size() && pluginopts.size())
+                {
+                    proxyStr += "/?plugin=" + UrlEncode(plugin + ";" + pluginopts);
+                }
+                proxyStr += "&group=" + urlsafe_base64_encode(x.group) + "#" + UrlEncode(remark);
+            }
+            else
+            {
+                proxyStr = remark + " = shadowsocks, " + hostname + ", " + port + ", " + method + ", \"" + password + "\", group=" + x.group;
+                if(plugin == "simple-obfs" && pluginopts.size())
+                {
+                    proxyStr += ", " + replace_all_distinct(pluginopts, ";", ", ");
+                }
             }
-            proxyStr += "&group=" + urlsafe_base64_encode(x.group) + "#" + UrlEncode(remark);
             break;
         default:
             continue;
         }
-        allLinks += proxyStr + "\n";
+
+        ini.Set("{NONAME}", proxyStr);
+        nodelist.emplace_back(x);
     }
 
-    return base64_encode(allLinks);
+    if(ext.nodelist)
+        return;
+
+    string_array filtered_nodelist;
+    ini.SetCurrentSection("POLICY");
+    ini.EraseSection();
+
+    std::string singlegroup;
+    std::string name, proxies;
+    string_array vArray;
+    for(std::string &x : extra_proxy_group)
+    {
+        eraseElements(filtered_nodelist);
+        unsigned int rules_upper_bound = 0;
+
+        vArray = split(x, "`");
+        if(vArray.size() < 3)
+            continue;
+
+        if(vArray[1] == "select")
+        {
+            type = "static";
+            rules_upper_bound = vArray.size();
+        }
+        else if(vArray[1] == "url-test")
+        {
+            if(vArray.size() < 5)
+                continue;
+            type = "auto";
+            rules_upper_bound = vArray.size() - 2;
+        }
+        else if(vArray[1] == "fallback")
+        {
+            if(vArray.size() < 5)
+                continue;
+            type = "static";
+            rules_upper_bound = vArray.size() - 2;
+        }
+        else if(vArray[1] == "load-balance")
+        {
+            if(vArray.size() < 5)
+                continue;
+            type = "balance, round-robin";
+            rules_upper_bound = vArray.size() - 2;
+        }
+        else
+            continue;
+
+        name = vArray[0];
+
+        for(unsigned int i = 2; i < rules_upper_bound; i++)
+            groupGenerate(vArray[i], nodelist, filtered_nodelist, true);
+
+        if(!filtered_nodelist.size())
+            filtered_nodelist.emplace_back("direct");
+
+        proxies = std::accumulate(std::next(filtered_nodelist.begin()), filtered_nodelist.end(), filtered_nodelist[0], [](std::string a, std::string b)
+        {
+            return std::move(a) + "\n" + std::move(b);
+        });
+
+        singlegroup = name + " : " + type;
+        if(type == "static")
+            singlegroup += ", " + filtered_nodelist[0];
+        singlegroup += "\n" + proxies + "\n";
+        ini.Set("{NONAME}", base64_encode(singlegroup));
+    }
+
+    if(ext.enable_rule_generator)
+        rulesetToSurge(ini, ruleset_content_array, -2, ext.overwrite_original_rules);
 }
 
 std::string netchToQuanX(std::vector<nodeInfo> &nodes, std::string &base_conf, std::vector<ruleset_content> &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext)
@@ -1349,7 +1473,7 @@ std::string netchToQuanX(std::vector<nodeInfo> &nodes, std::string &base_conf, s
 
     netchToQuanX(nodes, ini, ruleset_content_array, extra_proxy_group, ext);
 
-    if(!ext.nodelist)
+    if(ext.nodelist)
     {
         string_array allnodes;
         ini.GetAll("server_local", "{NONAME}", allnodes);
@@ -1419,11 +1543,15 @@ void netchToQuanX(std::vector<nodeInfo> &nodes, INIReader &ini, std::vector<rule
                 method = "chacha20-ietf-poly1305";
             proxyStr = "vmess = " + hostname + ":" + port + ", method=" + method + ", password=" + id;
             if(transproto == "ws")
-                proxyStr += ", obfs=ws, obfs-host=" + host + ", obfs-uri=" + path;
+            {
+                if(tlssecure)
+                    proxyStr += ", obfs=wss";
+                else
+                    proxyStr += ", obfs=ws";
+                proxyStr += ", obfs-host=" + host + ", obfs-uri=" + path;
+            }
             else if(tlssecure)
                 proxyStr += ", obfs=over-tls, obfs-host=" + host;
-            if(ext.skip_cert_verify)
-                proxyStr += ", certificate=0";
             break;
         case SPEEDTEST_MESSAGE_FOUNDSS:
             password = GetMember(json, "Password");

+ 2 - 1
src/subexport.h

@@ -41,7 +41,8 @@ std::string netchToSSR(std::vector<nodeInfo> &nodes, extra_settings &ext);
 std::string netchToVMess(std::vector<nodeInfo> &nodes, extra_settings &ext);
 std::string netchToQuanX(std::vector<nodeInfo> &nodes, std::string &base_conf, std::vector<ruleset_content> &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext);
 void netchToQuanX(std::vector<nodeInfo> &nodes, INIReader &ini, std::vector<ruleset_content> &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext);
-std::string netchToQuan(std::vector<nodeInfo> &nodes, extra_settings &ext);
+std::string netchToQuan(std::vector<nodeInfo> &nodes, std::string &base_conf, std::vector<ruleset_content> &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext);
+void netchToQuan(std::vector<nodeInfo> &nodes, INIReader &ini, std::vector<ruleset_content> &ruleset_content_array, string_array &extra_proxy_group, extra_settings &ext);
 std::string netchToSSD(std::vector<nodeInfo> &nodes, std::string &group, extra_settings &ext);
 std::string buildGistData(std::string name, std::string content);
 int uploadGist(std::string name, std::string path, std::string content, bool writeManageURL);