@Override
  public void refreshList(String[] sss) throws Exception {
    String content = NetUtils.doGet("http://optifine.net/downloads");
    if (versions != null) return;
    versionMap = new HashMap<>();
    versions = new ArrayList<>();

    content = content.replace("&nbsp;", " ").replace("&gt;", ">").replace("&lt;", "<");

    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = factory.newDocumentBuilder();
      Document doc = db.parse(new StringBufferInputStream(content));
      Element r = doc.getDocumentElement();
      NodeList tables = r.getElementsByTagName("table");
      for (int i = 0; i < tables.getLength(); i++) {
        Element e = (Element) tables.item(i);
        if ("downloadTable".equals(e.getAttribute("class"))) {
          NodeList tr = e.getElementsByTagName("tr");
          for (int k = 0; k < tr.getLength(); k++) {
            NodeList downloadLine = ((Element) tr.item(k)).getElementsByTagName("td");
            OptiFineVersion v = new OptiFineVersion();
            for (int j = 0; j < downloadLine.getLength(); j++) {
              Element td = (Element) downloadLine.item(j);
              if (StrUtils.startsWith(td.getAttribute("class"), "downloadLineMirror"))
                v.mirror = ((Element) td.getElementsByTagName("a").item(0)).getAttribute("href");
              if (StrUtils.startsWith(td.getAttribute("class"), "downloadLineDownload"))
                v.dl = ((Element) td.getElementsByTagName("a").item(0)).getAttribute("href");
              if (StrUtils.startsWith(td.getAttribute("class"), "downloadLineDate"))
                v.date = td.getTextContent();
              if (StrUtils.startsWith(td.getAttribute("class"), "downloadLineFile"))
                v.ver = td.getTextContent();
            }
            if (StrUtils.isBlank(v.mcver)) {
              Pattern p = Pattern.compile("OptiFine (.*?) ");
              Matcher m = p.matcher(v.ver);
              while (m.find()) v.mcver = StrUtils.formatVersion(m.group(1));
            }
            InstallerVersion iv = new InstallerVersion(v.ver, StrUtils.formatVersion(v.mcver));
            iv.installer = iv.universal = v.mirror;
            root.add(v);
            versions.add(iv);

            List<InstallerVersion> ivl =
                ArrayUtils.tryGetMapWithList(versionMap, StrUtils.formatVersion(v.mcver));
            ivl.add(iv);
          }
        }
      }
    } catch (ParserConfigurationException | SAXException | IOException | DOMException ex) {
      throw new RuntimeException(ex);
    }

    Collections.sort(versions, InstallerVersionComparator.INSTANCE);
  }
  @Override
  public UserProfileProvider login(LoginInfo info) throws AuthenticationException {
    UserProfileProvider result = new UserProfileProvider();
    result.setUserType("mojang");
    if (ua.canPlayOnline()) {
      result.setUserName(info.username);
      result.setUserId(UUIDTypeAdapter.fromUUID(ua.getSelectedProfile().id));
    } else {
      String usr = info.username;
      if (info.username == null || !info.username.contains("@"))
        throw new AuthenticationException(C.i18n("login.not_email"));
      String pwd = info.password;

      if (!ua.isLoggedIn()) ua.setPassword(pwd);
      ua.setUsername(usr);
      ua.logIn();
      if (!ua.isLoggedIn()) throw new AuthenticationException(C.i18n("login.wrong_password"));
      GameProfile selectedProfile = ua.getSelectedProfile();
      GameProfile[] profiles = ua.getAvailableProfiles();
      String username;
      if (selectedProfile == null)
        if (ArrayUtils.isNotEmpty(profiles)) {
          String[] names = new String[profiles.length];
          for (int i = 0; i < profiles.length; i++) names[i] = profiles[i].name;
          Selector s = new Selector(null, names, C.i18n("login.choose_charactor"));
          s.setVisible(true);
          selectedProfile = profiles[s.sel];
          username = names[s.sel];
        } else username = JOptionPane.showInputDialog(C.i18n("login.no_charactor"));
      else username = selectedProfile.name;
      result.setUserName(username);
      result.setUserId(
          selectedProfile == null
              ? OfflineAuthenticator.getUUIDFromUserName(username)
              : UUIDTypeAdapter.fromUUID(selectedProfile.id));
    }
    result.setUserProperties(
        new GsonBuilder()
            .registerTypeAdapter(PropertyMap.class, new PropertyMap.LegacySerializer())
            .create()
            .toJson(ua.getUserProperties()));
    result.setUserPropertyMap(
        new GsonBuilder()
            .registerTypeAdapter(PropertyMap.class, new PropertyMap.Serializer())
            .create()
            .toJson(ua.getUserProperties()));
    result.setAccessToken(ua.getAuthenticatedToken());
    result.setSession(ua.getAuthenticatedToken());
    return result;
  }