var device = null; var devname = ""; var mode = 0; // bitmask: 1: clone, 2: update ds5 firmware, 4: battery low, 8: ds-edge not supported var disable_btn = 0; var last_disable_btn = 0; // 1 if there is any change that can be stored permanently var has_changes_to_write = 0; var lang_orig_text = {}; var lang_cur = {}; var lang_disabled = true; var lang_cur_direction = "ltr"; var gj = 0; var gu = 0; // DS5 finetuning var finetune_original_data = []; var last_written_finetune_data = []; var finetune_visible = false; var on_finetune_updating = false; // Alphabetical order var available_langs = { ar_ar: { name: "العربية", file: "ar_ar.json", direction: "rtl" }, bg_bg: { name: "Български", file: "bg_bg.json", direction: "ltr" }, cz_cz: { name: "Čeština", file: "cz_cz.json", direction: "ltr" }, de_de: { name: "Deutsch", file: "de_de.json", direction: "ltr" }, es_es: { name: "Español", file: "es_es.json", direction: "ltr" }, fr_fr: { name: "Français", file: "fr_fr.json", direction: "ltr" }, hu_hu: { name: "Magyar", file: "hu_hu.json", direction: "ltr" }, it_it: { name: "Italiano", file: "it_it.json", direction: "ltr" }, jp_jp: { name: "日本語", file: "jp_jp.json", direction: "ltr" }, ko_kr: { name: "한국어", file: "ko_kr.json", direction: "ltr" }, nl_nl: { name: "Nederlands", file: "nl_nl.json", direction: "ltr" }, pl_pl: { name: "Polski", file: "pl_pl.json", direction: "ltr" }, pt_br: { name: "Português do Brasil", file: "pt_br.json", direction: "ltr", }, pt_pt: { name: "Português", file: "pt_pt.json", direction: "ltr" }, rs_rs: { name: "Srpski", file: "rs_rs.json", direction: "ltr" }, ru_ru: { name: "Русский", file: "ru_ru.json", direction: "ltr" }, tr_tr: { name: "Türkçe", file: "tr_tr.json", direction: "ltr" }, ua_ua: { name: "Українська", file: "ua_ua.json", direction: "ltr" }, zh_cn: { name: "中文", file: "zh_cn.json", direction: "ltr" }, zh_tw: { name: "中文(繁)", file: "zh_tw.json", direction: "ltr" }, }; function buf2hex(buffer) { return [...new Uint8Array(buffer)] .map((x) => x.toString(16).padStart(2, "0")) .join(""); } function dec2hex(i) { return (i + 0x10000).toString(16).substr(-4).toUpperCase(); } function dec2hex32(i) { return (i + 0x100000000).toString(16).substr(-8).toUpperCase(); } function dec2hex8(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); } function ds5_hw_to_bm(hw_ver) { a = (hw_ver >> 8) & 0xff; if (a == 0x03) { return "BDM-010"; } else if (a == 0x04) { return "BDM-020"; } else if (a == 0x05) { return "BDM-030"; } else if (a == 0x06) { return "BDM-040"; } else if (a == 0x07 || a == 0x08) { return "BDM-050"; } else { return l("Unknown"); } } function ds4_hw_to_bm(hw_ver) { a = hw_ver >> 8; if (a == 0x31) { return "JDM-001"; } else if (a == 0x43) { return "JDM-011"; } else if (a == 0x54) { return "JDM-030"; } else if (a >= 0x64 && a <= 0x74) { return "JDM-040"; } else if ((a > 0x80 && a < 0x84) || a == 0x93) { return "JDM-020"; } else if (a == 0xa4 || a == 0x90) { return "JDM-050"; } else if (a == 0xb0) { return "JDM-055 (Scuf?)"; } else if (a == 0xb4) { return "JDM-055"; } else { if (is_rare(hw_ver)) return "WOW!"; return l("Unknown"); } } function is_rare(hw_ver) { a = hw_ver >> 8; b = a >> 4; return ( (b == 7 && a > 0x74) || (b == 9 && a != 0x93 && a != 0x90) || a == 0xa0 ); } async function ds4_info() { try { var ooc = l("unknown"); var is_clone = false; const view = lf("ds4_info", await device.receiveFeatureReport(0xa3)); var cmd = view.getUint8(0, true); if (cmd != 0xa3 || view.buffer.byteLength < 49) { if (view.buffer.byteLength != 49) { ooc = l("clone"); is_clone = true; } } var k1 = new TextDecoder().decode(view.buffer.slice(1, 0x10)); var k2 = new TextDecoder().decode(view.buffer.slice(0x10, 0x20)); k1 = k1.replace(/\0/g, ""); k2 = k2.replace(/\0/g, ""); var hw_ver_major = view.getUint16(0x21, true); var hw_ver_minor = view.getUint16(0x23, true); var sw_ver_major = view.getUint32(0x25, true); var sw_ver_minor = view.getUint16(0x25 + 4, true); try { if (!is_clone) { const view = await device.receiveFeatureReport(0x81); ooc = l("original"); } } catch (e) { la("clone"); is_clone = true; ooc = "" + l("clone") + ""; disable_btn |= 1; } clear_info(); append_info(l("Build Date"), k1 + " " + k2); append_info( l("HW Version"), "" + dec2hex(hw_ver_major) + ":" + dec2hex(hw_ver_minor), ); append_info( l("SW Version"), dec2hex32(sw_ver_major) + ":" + dec2hex(sw_ver_minor), ); append_info(l("Device Type"), ooc); if (!is_clone) { b_info = ' ' + ''; append_info(l("Board Model"), ds4_hw_to_bm(hw_ver_minor) + b_info); // All ok, safe to lock NVS, query it and get BD Addr nvstatus = await ds4_nvstatus(); if (nvstatus == 0) await ds4_nvlock(); bd_addr = await ds4_getbdaddr(); append_info(l("Bluetooth Address"), bd_addr); if (is_rare(hw_ver_minor)) { show_popup( "Wow, this is a rare/weird controller! Please write me an email at ds4@the.al or contact me on Discord (the_al)", ); } } } catch (e) { ooc = "" + l("clone") + ""; disable_btn |= 1; } return true; } async function ds4_flash() { la("ds4_flash"); try { await ds4_nvunlock(); await ds4_nvlock(); show_popup(l("Changes saved successfully")); } catch (error) { show_popup(l("Error while saving changes:") + " " + str(error)); } } async function ds5_flash() { la("ds5_flash"); try { await ds5_nvunlock(); await ds5_nvlock(); show_popup(l("Changes saved successfully")); } catch (error) { show_popup(l("Error while saving changes: ") + toString(error)); } } async function ds5_edge_flash() { la("ds5_edge_flash"); try { await ds5_edge_flash_modules(); await ds5_reset(); show_popup( "" + l("Changes saved successfully") + ".

" + l( "If the calibration is not stored permanently, please double-check the wirings of the hardware mod.", ) + "
" + l("Please disconnect and reconnect the controller."), true, ); await disconnect(); } catch (error) { show_popup(l("Error while saving changes: ") + toString(error)); } } async function ds4_reset() { la("ds4_reset"); try { await device.sendFeatureReport(0xa0, alloc_req(0xa0, [4, 1, 0])); } catch (error) {} } async function ds5_reset() { la("ds5_reset"); try { await device.sendFeatureReport(0x80, alloc_req(0x80, [1, 1])); } catch (error) {} } async function ds4_calibrate_range_begin() { la("ds4_calibrate_range_begin"); var err = l("Range calibration failed: "); try { // Begin await device.sendFeatureReport(0x90, alloc_req(0x90, [1, 1, 2])); // Assert data = await device.receiveFeatureReport(0x91); data2 = await device.receiveFeatureReport(0x92); d1 = data.getUint32(0, false); d2 = data2.getUint32(0, false); if (d1 != 0x91010201 || d2 != 0x920102ff) { la("ds4_calibrate_range_begin_failed", { d1: d1, d2: d2 }); close_calibrate_window(); return show_popup(err + l("Error 1")); } } catch (e) { la("ds4_calibrate_range_begin_failed", { r: e }); await new Promise((r) => setTimeout(r, 500)); close_calibrate_window(); return show_popup(err + e); } } async function ds4_calibrate_range_end() { la("ds4_calibrate_range_end"); var err = l("Range calibration failed: "); try { // Write await device.sendFeatureReport(0x90, alloc_req(0x90, [2, 1, 2])); data = await device.receiveFeatureReport(0x91); data2 = await device.receiveFeatureReport(0x92); d1 = data.getUint32(0, false); d2 = data2.getUint32(0, false); if (d1 != 0x91010202 || d2 != 0x92010201) { la("ds4_calibrate_range_end_failed", { d1: d1, d2: d2 }); close_calibrate_window(); return show_popup(err + l("Error 3")); } update_nvs_changes_status(1); close_calibrate_window(); show_popup(l("Range calibration completed")); } catch (e) { la("ds4_calibrate_range_end_failed", { r: e }); await new Promise((r) => setTimeout(r, 500)); close_calibrate_window(); return show_popup(err + e); } } async function ds4_calibrate_sticks_begin() { la("ds4_calibrate_sticks_begin"); var err = l("Stick calibration failed: "); try { // Begin await device.sendFeatureReport(0x90, alloc_req(0x90, [1, 1, 1])); // Assert data = await device.receiveFeatureReport(0x91); data2 = await device.receiveFeatureReport(0x92); d1 = data.getUint32(0, false); d2 = data2.getUint32(0, false); if (d1 != 0x91010101 || d2 != 0x920101ff) { la("ds4_calibrate_sticks_begin_failed", { d1: d1, d2: d2 }); show_popup(err + l("Error 1")); return false; } return true; } catch (e) { la("ds4_calibrate_sticks_begin_failed", { r: e }); await new Promise((r) => setTimeout(r, 500)); show_popup(err + e); return false; } } async function ds4_calibrate_sticks_sample() { la("ds4_calibrate_sticks_sample"); var err = l("Stick calibration failed: "); try { // Sample await device.sendFeatureReport(0x90, alloc_req(0x90, [3, 1, 1])); // Assert data = await device.receiveFeatureReport(0x91); data2 = await device.receiveFeatureReport(0x92); if ( data.getUint32(0, false) != 0x91010101 || data2.getUint32(0, false) != 0x920101ff ) { close_calibrate_window(); d1 = dec2hex32(data.getUint32(0, false)); d2 = dec2hex32(data2.getUint32(0, false)); la("ds4_calibrate_sticks_sample_failed", { d1: d1, d2: d2 }); show_popup( err + l("Error 2") + " (" + d1 + ", " + d2 + " at i=" + i + ")", ); return false; } return true; } catch (e) { await new Promise((r) => setTimeout(r, 500)); show_popup(err + e); return false; } } async function ds4_calibrate_sticks_end() { la("ds4_calibrate_sticks_end"); var err = l("Stick calibration failed: "); try { // Write await device.sendFeatureReport(0x90, alloc_req(0x90, [2, 1, 1])); if ( data.getUint32(0, false) != 0x91010101 || data2.getUint32(0, false) != 0x920101ff ) { d1 = dec2hex32(data.getUint32(0, false)); d2 = dec2hex32(data2.getUint32(0, false)); la("ds4_calibrate_sticks_end_failed", { d1: d1, d2: d2 }); show_popup( err + l("Error 3") + " (" + d1 + ", " + d2 + " at i=" + i + ")", ); return false; } update_nvs_changes_status(1); return true; } catch (e) { la("ds4_calibrate_sticks_end_failed", { r: e }); await new Promise((r) => setTimeout(r, 500)); show_popup(err + e); return false; } } async function ds4_calibrate_sticks() { la("ds4_calibrate_sticks"); var err = l("Stick calibration failed: "); try { set_progress(0); // Begin await device.sendFeatureReport(0x90, alloc_req(0x90, [1, 1, 1])); // Assert data = await device.receiveFeatureReport(0x91); data2 = await device.receiveFeatureReport(0x92); d1 = data.getUint32(0, false); d2 = data2.getUint32(0, false); if (d1 != 0x91010101 || d2 != 0x920101ff) { la("ds4_calibrate_sticks_failed", { s: 1, d1: d1, d2: d2 }); close_calibrate_window(); return show_popup(err + l("Error 1")); } set_progress(10); await new Promise((r) => setTimeout(r, 100)); for (var i = 0; i < 3; i++) { // Sample await device.sendFeatureReport(0x90, alloc_req(0x90, [3, 1, 1])); // Assert data = await device.receiveFeatureReport(0x91); data2 = await device.receiveFeatureReport(0x92); if ( data.getUint32(0, false) != 0x91010101 || data2.getUint32(0, false) != 0x920101ff ) { d1 = dec2hex32(data.getUint32(0, false)); d2 = dec2hex32(data2.getUint32(0, false)); la("ds4_calibrate_sticks_failed", { s: 2, i: i, d1: d1, d2: d2, }); close_calibrate_window(); return show_popup( err + l("Error 2") + " (" + d1 + ", " + d2 + " at i=" + i + ")", ); } await new Promise((r) => setTimeout(r, 500)); set_progress(20 + i * 30); } // Write await device.sendFeatureReport(0x90, alloc_req(0x90, [2, 1, 1])); if ( data.getUint32(0, false) != 0x91010101 || data2.getUint32(0, false) != 0x920101ff ) { d1 = dec2hex32(data.getUint32(0, false)); d2 = dec2hex32(data2.getUint32(0, false)); la("ds4_calibrate_sticks_failed", { s: 3, d1: d1, d2: d2 }); close_calibrate_window(); return show_popup( err + l("Error 3") + " (" + d1 + ", " + d2 + " at i=" + i + ")", ); } set_progress(100); await new Promise((r) => setTimeout(r, 500)); close_calibrate_window(); show_popup(l("Stick calibration completed")); } catch (e) { la("ds4_calibrate_sticks_failed", { r: e }); await new Promise((r) => setTimeout(r, 500)); close_calibrate_window(); return show_popup(err + e); } } async function ds4_nvstatus() { try { await device.sendFeatureReport(0x08, alloc_req(0x08, [0xff, 0, 12])); data = lf("ds4_nvstatus", await device.receiveFeatureReport(0x11)); // 1: temporary, 0: permanent ret = data.getUint8(1, false); if (ret == 1) { $("#d-nvstatus").html( "" + l("locked") + "", ); return 1; } else if (ret == 0) { $("#d-nvstatus").html( "" + l("unlocked") + "", ); return 0; } else { $("#d-nvstatus").html( "unk " + ret + "", ); if (ret == 0 || ret == 1) return 2; return ret; } return ret; } catch (e) { $("#d-nvstatus").html("" + l("error") + ""); return 2; // error } } async function ds5_nvstatus() { try { await device.sendFeatureReport(0x80, alloc_req(0x80, [3, 3])); data = lf("ds5_nvstatus", await device.receiveFeatureReport(0x81)); ret = data.getUint32(1, false); if (ret == 0x15010100) { return 4; } if (ret == 0x03030201) { $("#d-nvstatus").html( "" + l("locked") + "", ); return 1; // temporary } else if (ret == 0x03030200) { $("#d-nvstatus").html( "" + l("unlocked") + "", ); return 0; // permanent } else { $("#d-nvstatus").html( "unk " + dec2hex32(ret) + "", ); if (ret == 0 || ret == 1) return 2; return ret; // unknown } } catch (e) { $("#d-nvstatus").html("" + l("error") + ""); return 2; // error } } async function ds4_getbdaddr() { try { data = lf("ds4_getbdaddr", await device.receiveFeatureReport(0x12)); out = ""; for (i = 0; i < 6; i++) { if (i >= 1) out += ":"; out += dec2hex8(data.getUint8(6 - i, false)); } return out; } catch (e) { return "error"; } } async function ds5_getbdaddr() { try { await device.sendFeatureReport(0x80, alloc_req(0x80, [9, 2])); data = lf("ds5_getbdaddr", await device.receiveFeatureReport(0x81)); out = ""; for (i = 0; i < 6; i++) { if (i >= 1) out += ":"; out += dec2hex8(data.getUint8(4 + 5 - i, false)); } return out; } catch (e) { return "error"; } } async function ds4_nvlock() { la("ds4_nvlock"); await device.sendFeatureReport(0xa0, alloc_req(0xa0, [10, 1, 0])); } async function ds4_nvunlock() { la("ds4_nvunlock"); await device.sendFeatureReport( 0xa0, alloc_req(0xa0, [10, 2, 0x3e, 0x71, 0x7f, 0x89]), ); } async function ds5_system_info(base, num, length, decode = true) { await device.sendFeatureReport(128, alloc_req(128, [base, num])); var pcba_id = lf("ds5_pcba_id", await device.receiveFeatureReport(129)); if ( pcba_id.getUint8(1) != base || pcba_id.getUint8(2) != num || pcba_id.getUint8(3) != 2 ) { return l("error"); } else { if (decode) return new TextDecoder().decode( pcba_id.buffer.slice(4, 4 + length), ); else return buf2hex(pcba_id.buffer.slice(4, 4 + length)); } return l("Unknown"); } async function ds5_info() { try { const view = lf("ds5_info", await device.receiveFeatureReport(0x20)); var cmd = view.getUint8(0, true); if (cmd != 0x20 || view.buffer.byteLength != 64) return false; var build_date = new TextDecoder().decode(view.buffer.slice(1, 1 + 11)); var build_time = new TextDecoder().decode(view.buffer.slice(12, 20)); var fwtype = view.getUint16(20, true); var swseries = view.getUint16(22, true); var hwinfo = view.getUint32(24, true); var fwversion = view.getUint32(28, true); var deviceinfo = new TextDecoder().decode( view.buffer.slice(32, 32 + 12), ); var updversion = view.getUint16(44, true); var unk = view.getUint8(46, true); var fwversion1 = view.getUint32(48, true); var fwversion2 = view.getUint32(52, true); var fwversion3 = view.getUint32(56, true); clear_info(); b_info = ' ' + ''; append_info(l("Serial Number"), await ds5_system_info(1, 19, 17), "hw"); append_info_extra( l("MCU Unique ID"), await ds5_system_info(1, 9, 9, false), "hw", ); append_info_extra(l("PCBA ID"), await ds5_system_info(1, 17, 14), "hw"); append_info_extra( l("Battery Barcode"), await ds5_system_info(1, 24, 23), "hw", ); append_info_extra( l("VCM Left Barcode"), await ds5_system_info(1, 26, 16), "hw", ); append_info_extra( l("VCM Right Barcode"), await ds5_system_info(1, 28, 16), "hw", ); append_info(l("Board Model"), ds5_hw_to_bm(hwinfo) + b_info, "hw"); append_info(l("FW Build Date"), build_date + " " + build_time, "fw"); append_info_extra(l("FW Type"), "0x" + dec2hex(fwtype), "fw"); append_info_extra(l("FW Series"), "0x" + dec2hex(swseries), "fw"); append_info_extra(l("HW Model"), "0x" + dec2hex32(hwinfo), "hw"); append_info(l("FW Version"), "0x" + dec2hex32(fwversion), "fw"); append_info(l("FW Update"), "0x" + dec2hex(updversion), "fw"); append_info_extra(l("FW Update Info"), "0x" + dec2hex8(unk), "fw"); append_info_extra( l("SBL FW Version"), "0x" + dec2hex32(fwversion1), "fw", ); append_info_extra( l("Venom FW Version"), "0x" + dec2hex32(fwversion2), "fw", ); append_info_extra( l("Spider FW Version"), "0x" + dec2hex32(fwversion3), "fw", ); append_info_extra( l("Touchpad ID"), await ds5_system_info(5, 2, 8, false), "hw", ); append_info_extra( l("Touchpad FW Version"), await ds5_system_info(5, 4, 8, false), "fw", ); old_controller = build_date.search(/ 2020| 2021/); if (old_controller != -1) { la("ds5_info_error", { r: "old" }); disable_btn |= 2; return true; } nvstatus = await ds5_nvstatus(); if (nvstatus == 0) await ds5_nvlock(); bd_addr = await ds5_getbdaddr(); append_info(l("Bluetooth Address"), bd_addr, "hw"); } catch (e) { la("ds5_info_error", { r: e }); show_popup(l("Cannot read controller information")); return false; } return true; } async function ds5_calibrate_sticks_begin() { la("ds5_calibrate_sticks_begin"); var err = l("Range calibration failed: "); try { // Begin await device.sendFeatureReport(0x82, alloc_req(0x82, [1, 1, 1])); // Assert data = await device.receiveFeatureReport(0x83); if (data.getUint32(0, false) != 0x83010101) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_sticks_begin_failed", { d1: d1 }); show_popup(err + l("Error 1") + " (" + d1 + ")."); return false; } return true; } catch (e) { la("ds5_calibrate_sticks_begin_failed", { r: e }); await new Promise((r) => setTimeout(r, 500)); show_popup(err + e); return false; } } async function ds5_calibrate_sticks_sample() { la("ds5_calibrate_sticks_sample"); var err = l("Stick calibration failed: "); try { // Sample await device.sendFeatureReport(0x82, alloc_req(0x82, [3, 1, 1])); // Assert data = await device.receiveFeatureReport(0x83); if (data.getUint32(0, false) != 0x83010101) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_sticks_sample_failed", { d1: d1 }); show_popup(err + l("Error 2") + " (" + d1 + ")."); return false; } return true; } catch (e) { la("ds5_calibrate_sticks_sample_failed", { r: e }); await new Promise((r) => setTimeout(r, 500)); show_popup(err + e); return false; } } async function ds5_calibrate_sticks_end() { la("ds5_calibrate_sticks_end"); var err = l("Stick calibration failed: "); try { // Write await device.sendFeatureReport(0x82, alloc_req(0x82, [2, 1, 1])); data = await device.receiveFeatureReport(0x83); if (mode == 2) { if (data.getUint32(0, false) != 0x83010102) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_sticks_failed", { s: 3, d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 3") + " (" + d1 + ")."); } } else if (mode == 3) { if (data.getUint32(0, false) != 0x83010101) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_sticks_failed", { s: 3, d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 4") + " (" + d1 + ")."); } await device.sendFeatureReport(0x82, alloc_req(0x82, [2, 1, 1])); data = await device.receiveFeatureReport(0x83); if ( data.getUint32(0, false) != 0x83010103 && data.getUint32(0, false) != 0x83010312 ) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_sticks_failed", { s: 3, d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 5") + " (" + d1 + ")."); } } update_nvs_changes_status(1); return true; } catch (e) { la("ds5_calibrate_sticks_end_failed", { r: e }); await new Promise((r) => setTimeout(r, 500)); show_popup(err + e); return false; } } async function ds5_calibrate_sticks() { la("ds5_fast_calibrate_sticks"); var err = l("Stick calibration failed: "); try { set_progress(0); // Begin await device.sendFeatureReport(0x82, alloc_req(0x82, [1, 1, 1])); // Assert data = await device.receiveFeatureReport(0x83); if (data.getUint32(0, false) != 0x83010101) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_sticks_failed", { s: 1, d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 1") + " (" + d1 + ")."); } set_progress(10); await new Promise((r) => setTimeout(r, 100)); for (var i = 0; i < 3; i++) { // Sample await device.sendFeatureReport(0x82, alloc_req(0x82, [3, 1, 1])); // Assert data = await device.receiveFeatureReport(0x83); if (data.getUint32(0, false) != 0x83010101) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_sticks_failed", { s: 2, i: i, d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 2") + " (" + d1 + ")."); } await new Promise((r) => setTimeout(r, 500)); set_progress(20 + i * 20); } await new Promise((r) => setTimeout(r, 200)); set_progress(80); // Write await device.sendFeatureReport(0x82, alloc_req(0x82, [2, 1, 1])); data = await device.receiveFeatureReport(0x83); if (mode == 2) { if (data.getUint32(0, false) != 0x83010102) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_sticks_failed", { s: 3, d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 3") + " (" + d1 + ")."); } } else if (mode == 3) { if (data.getUint32(0, false) != 0x83010101) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_sticks_failed", { s: 3, d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 4") + " (" + d1 + ")."); } await device.sendFeatureReport(0x82, alloc_req(0x82, [2, 1, 1])); data = await device.receiveFeatureReport(0x83); if (data.getUint32(0, false) != 0x83010103) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_sticks_failed", { s: 3, d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 5") + " (" + d1 + ")."); } } set_progress(100); update_nvs_changes_status(1); await new Promise((r) => setTimeout(r, 500)); close_calibrate_window(); show_popup(l("Stick calibration completed")); } catch (e) { la("ds5_calibrate_sticks_failed", { r: e }); await new Promise((r) => setTimeout(r, 500)); close_calibrate_window(); return show_popup(err + e); } } async function ds5_calibrate_range_begin() { la("ds5_calibrate_range_begin"); var err = l("Range calibration failed: "); try { // Begin await device.sendFeatureReport(0x82, alloc_req(0x82, [1, 1, 2])); // Assert data = await device.receiveFeatureReport(0x83); if (data.getUint32(0, false) != 0x83010201) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_range_begin_failed", { d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 1") + " (" + d1 + ")."); } } catch (e) { la("ds5_calibrate_range_begin_failed", { r: e }); await new Promise((r) => setTimeout(r, 500)); close_calibrate_window(); return show_popup(err + e); } } async function ds5_calibrate_range_end() { la("ds5_calibrate_range_end"); var err = l("Range calibration failed: "); try { // Write await device.sendFeatureReport(0x82, alloc_req(0x82, [2, 1, 2])); // Assert data = await device.receiveFeatureReport(0x83); if (mode == 2) { if (data.getUint32(0, false) != 0x83010202) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_range_end_failed", { d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 3") + " (" + d1 + ")."); } } else { if (data.getUint32(0, false) != 0x83010201) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_range_end_failed", { d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 4") + " (" + d1 + ")."); } await device.sendFeatureReport(0x82, alloc_req(0x82, [2, 1, 2])); data = await device.receiveFeatureReport(0x83); if (data.getUint32(0, false) != 0x83010203) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_range_end_failed", { d1: d1 }); close_calibrate_window(); return show_popup(err + l("Error 5") + " (" + d1 + ")."); } } update_nvs_changes_status(1); close_calibrate_window(); show_popup(l("Range calibration completed")); } catch (e) { la("ds5_calibrate_range_end_failed", { r: e }); await new Promise((r) => setTimeout(r, 500)); close_calibrate_window(); return show_popup(err + e); } } async function ds5_nvlock() { la("ds5_nvlock"); try { await device.sendFeatureReport(0x80, alloc_req(0x80, [3, 1])); data = await device.receiveFeatureReport(0x81); } catch (e) { await new Promise((r) => setTimeout(r, 500)); close_calibrate_window(); return show_popup(l("NVS Lock failed: ") + e); } } async function ds5_edge_flash_modules() { la("ds5_edge_flash_modules"); var modal = null; try { modal = new bootstrap.Modal( document.getElementById("edgeProgressModal"), {}, ); modal.show(); if (device != null) { await device.sendFeatureReport( 0x80, alloc_req(0x80, [21, 6, 0, 11]), ); await new Promise((r) => setTimeout(r, 200)); await device.sendFeatureReport(0x80, alloc_req(0x80, [21, 5, 0])); await new Promise((r) => setTimeout(r, 200)); await device.sendFeatureReport( 0x80, alloc_req(0x80, [21, 6, 1, 11]), ); await new Promise((r) => setTimeout(r, 200)); await device.sendFeatureReport(0x80, alloc_req(0x80, [21, 5, 1])); await new Promise((r) => setTimeout(r, 200)); } await new Promise((r) => setTimeout(r, 500)); if (device != null) { data = await device.receiveFeatureReport(0x81); } await new Promise((r) => setTimeout(r, 1000)); modal.hide(); await ds5_reset(); } catch (e) { modal.hide(); await new Promise((r) => setTimeout(r, 500)); return show_popup(l("NVS Unlock failed: ") + e); } } async function ds5_nvunlock() { la("ds5_nvunlock"); try { await device.sendFeatureReport( 0x80, alloc_req(0x80, [3, 2, 101, 50, 64, 12]), ); data = await device.receiveFeatureReport(0x81); } catch (e) { await new Promise((r) => setTimeout(r, 500)); close_calibrate_window(); return show_popup(l("NVS Unlock failed: ") + e); } } async function disconnect() { la("disconnect"); if (device == null) return; gj = 0; update_nvs_changes_status(0); mode = 0; device.close(); device = null; disable_btn = 0; reset_circularity(); $("#offlinebar").show(); $("#onlinebar").hide(); $("#mainmenu").hide(); $("#d-nvstatus").text = l("Unknown"); $("#d-bdaddr").text = l("Unknown"); close_calibrate_window(); } function handleDisconnectedDevice(e) { la("disconnected"); console.log("Disconnected: " + e.device.productName); disconnect(); } function createCookie(name, value, days) { var expires; if (days) { var date = new Date(); date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); expires = "; expires=" + date.toGMTString(); } else { expires = ""; } document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/"; } function readCookie(name) { var nameEQ = encodeURIComponent(name) + "="; var ca = document.cookie.split(";"); for (var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) === " ") c = c.substring(1, c.length); if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length)); } return null; } function eraseCookie(name) { createCookie(name, "", -1); } function welcome_modal() { var already_accepted = readCookie("welcome_accepted"); if (already_accepted == "1") return; curModal = new bootstrap.Modal(document.getElementById("welcomeModal"), {}); curModal.show(); } function welcome_accepted() { la("welcome_accepted"); createCookie("welcome_accepted", "1"); $("#welcomeModal").modal("hide"); } function gboot() { gu = crypto.randomUUID(); $("#infoshowall").hide(); window.addEventListener("DOMContentLoaded", function () { lang_init(); welcome_modal(); $("#checkCircularity").on("change", on_circ_check_change); on_circ_check_change(); }); if (!("hid" in navigator)) { $("#offlinebar").hide(); $("#onlinebar").hide(); $("#missinghid").show(); return; } $("#offlinebar").show(); navigator.hid.addEventListener("disconnect", handleDisconnectedDevice); } function alloc_req(id, data = []) { len = data.length; try { fr = device.collections[0].featureReports; fr.forEach((e) => { if (e.reportId == id) { len = e.items[0].reportCount; } }); } catch (e) { console.log(e); } out = new Uint8Array(len); for (i = 0; i < data.length && i < len; i++) { out[i] = data[i]; } return out; } async function on_finetune_change(x) { list = [ "LL", "LT", "RL", "RT", "LR", "LB", "RR", "RB", "LX", "LY", "RX", "RY", ]; out = []; for (i = 0; i < 12; i++) { v = $("#finetune" + list[i]).val(); out.push(parseInt(v)); } await write_finetune_data(out); } async function ds5_finetune() { // Lock NVS before nvs = await ds5_nvstatus(); if (nvs == 0) { await ds5_nvlock(); nvs = await ds5_nvstatus(); if (nvs != 1) { show_popup("ERROR: Cannot lock NVS (" + nvs + ")"); return; } } else if (nvs != 1) { show_popup( "ERROR: Cannot read NVS status. Finetuning is not safe on this device.", ); } data = await read_finetune_data(); if (data == null) return; curModal = new bootstrap.Modal( document.getElementById("finetuneModal"), {}, ); curModal.show(); list = [ "LL", "LT", "RL", "RT", "LR", "LB", "RR", "RB", "LX", "LY", "RX", "RY", ]; for (i = 0; i < 12; i++) { $("#finetune" + list[i]).attr("value", data[i]); $("#finetune" + list[i]).on("change", on_finetune_change); } finetune_original_data = data; finetune_visible = true; refresh_finetune(); } async function read_finetune_data() { await device.sendFeatureReport(0x80, alloc_req(0x80, [12, 2])); var data = await device.receiveFeatureReport(0x81); var cmd = data.getUint8(0, true); var p1 = data.getUint8(1, true); var p2 = data.getUint8(2, true); var p3 = data.getUint8(3, true); if (cmd != 129 || p1 != 12 || p2 != 2 || p3 != 2) { finetune_close(); show_popup("ERROR: Cannot read calibration data"); return null; } var out = []; for (i = 0; i < 12; i++) out.push(data.getUint16(4 + i * 2, true)); last_written_finetune_data = out; return out; } async function write_finetune_data(data) { if (data.length != 12) { return; } if (data == last_written_finetune_data) { return; } last_written_finetune_data = data; pkg = [12, 1]; for (i = 0; i < data.length; i++) { x = data[i]; pkg.push(x & 0xff); pkg.push(x >> 8); } await device.sendFeatureReport(0x80, alloc_req(0x80, pkg)); } function refresh_finetune() { if (!finetune_visible) return; if (on_finetune_updating) return; on_finetune_updating = true; setTimeout(ds5_finetune_update_all, 10); } function ds5_finetune_update_all() { ds5_finetune_update("finetuneStickCanvasL", last_lx, last_ly); ds5_finetune_update("finetuneStickCanvasR", last_rx, last_ry); } function ds5_finetune_update(name, plx, ply) { on_finetune_updating = false; var c = document.getElementById(name); var ctx = c.getContext("2d"); var sz = 60; var hb = 20 + sz; var yb = 15 + sz; var w = c.width; ctx.clearRect(0, 0, c.width, c.height); ctx.lineWidth = 1; ctx.fillStyle = "#ffffff"; ctx.strokeStyle = "#000000"; // Left circle ctx.beginPath(); ctx.arc(hb, yb, sz, 0, 2 * Math.PI); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.strokeStyle = "#aaaaaa"; ctx.beginPath(); ctx.moveTo(hb - sz, yb); ctx.lineTo(hb + sz, yb); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(hb, yb - sz); ctx.lineTo(hb, yb + sz); ctx.closePath(); ctx.stroke(); ctx.fillStyle = "#000000"; ctx.strokeStyle = "#000000"; ctx.beginPath(); ctx.arc(hb + plx * sz, yb + ply * sz, 4, 0, 2 * Math.PI); ctx.fill(); ctx.beginPath(); ctx.moveTo(hb, yb); ctx.lineTo(hb + plx * sz, yb + ply * sz); ctx.stroke(); $("#" + name + "x-lbl").text(float_to_str(plx)); $("#" + name + "y-lbl").text(float_to_str(ply)); } function finetune_close() { $("#finetuneModal").modal("hide"); finetune_visible = false; finetune_original_data = []; } function finetune_save() { finetune_close(); // Unlock button update_nvs_changes_status(1); } async function finetune_cancel() { if (finetune_original_data.length == 12) await write_finetune_data(finetune_original_data); finetune_close(); } var last_lx = 0, last_ly = 0, last_rx = 0, last_ry = 0; var ll_updated = false; var ll_data = new Array(48); var rr_data = new Array(48); var enable_circ_test = false; function reset_circularity() { for (i = 0; i < ll_data.length; i++) ll_data[i] = 0; for (i = 0; i < rr_data.length; i++) rr_data[i] = 0; enable_circ_test = false; ll_updated = false; $("#checkCircularity").prop("checked", false); refresh_stick_pos(); } function refresh_stick_pos() { var c = document.getElementById("stickCanvas"); var ctx = c.getContext("2d"); var sz = 60; var hb = 20 + sz; var yb = 15 + sz; var w = c.width; ctx.clearRect(0, 0, c.width, c.height); ctx.lineWidth = 1; ctx.fillStyle = "#ffffff"; ctx.strokeStyle = "#000000"; // Left circle ctx.beginPath(); ctx.arc(hb, yb, sz, 0, 2 * Math.PI); ctx.closePath(); ctx.fill(); ctx.stroke(); // Right circle ctx.beginPath(); ctx.arc(w - hb, yb, sz, 0, 2 * Math.PI); ctx.closePath(); ctx.fill(); ctx.stroke(); function cc_to_color(cc) { var dd = Math.sqrt(Math.pow(1.0 - cc, 2)); if (cc <= 1.0) hh = 220 - 220 * Math.min(1.0, Math.max(0, dd - 0.05) / 0.1); else hh = (245 + (360 - 245) * Math.min(1.0, Math.max(0, dd - 0.05) / 0.15)) % 360; return hh; } if (enable_circ_test) { var MAX_N = ll_data.length; for (i = 0; i < MAX_N; i++) { var kd = ll_data[i]; var kd1 = ll_data[(i + 1) % ll_data.length]; if (kd === undefined || kd1 === undefined) continue; var ka = (i * Math.PI * 2) / MAX_N; var ka1 = (((i + 1) % MAX_N) * 2 * Math.PI) / MAX_N; var kx = Math.cos(ka) * kd; var ky = Math.sin(ka) * kd; var kx1 = Math.cos(ka1) * kd1; var ky1 = Math.sin(ka1) * kd1; ctx.beginPath(); ctx.moveTo(hb, yb); ctx.lineTo(hb + kx * sz, yb + ky * sz); ctx.lineTo(hb + kx1 * sz, yb + ky1 * sz); ctx.lineTo(hb, yb); ctx.closePath(); var cc = (kd + kd1) / 2; var hh = cc_to_color(cc); ctx.fillStyle = "hsla(" + parseInt(hh) + ", 100%, 50%, 0.5)"; ctx.fill(); } for (i = 0; i < MAX_N; i++) { var kd = rr_data[i]; var kd1 = rr_data[(i + 1) % rr_data.length]; if (kd === undefined || kd1 === undefined) continue; var ka = (i * Math.PI * 2) / MAX_N; var ka1 = (((i + 1) % MAX_N) * 2 * Math.PI) / MAX_N; var kx = Math.cos(ka) * kd; var ky = Math.sin(ka) * kd; var kx1 = Math.cos(ka1) * kd1; var ky1 = Math.sin(ka1) * kd1; ctx.beginPath(); ctx.moveTo(w - hb, yb); ctx.lineTo(w - hb + kx * sz, yb + ky * sz); ctx.lineTo(w - hb + kx1 * sz, yb + ky1 * sz); ctx.lineTo(w - hb, yb); ctx.closePath(); var cc = (kd + kd1) / 2; var hh = cc_to_color(cc); ctx.fillStyle = "hsla(" + parseInt(hh) + ", 100%, 50%, 0.5)"; ctx.fill(); } } ctx.strokeStyle = "#aaaaaa"; ctx.beginPath(); ctx.moveTo(hb - sz, yb); ctx.lineTo(hb + sz, yb); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(w - hb - sz, yb); ctx.lineTo(w - hb + sz, yb); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(hb, yb - sz); ctx.lineTo(hb, yb + sz); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(w - hb, yb - sz); ctx.lineTo(w - hb, yb + sz); ctx.closePath(); ctx.stroke(); var plx = last_lx; var ply = last_ly; var prx = last_rx; var pry = last_ry; if (enable_circ_test) { var pld = Math.sqrt(plx * plx + ply * ply); var pla = (parseInt( Math.round((Math.atan2(ply, plx) * MAX_N) / 2.0 / Math.PI), ) + MAX_N) % MAX_N; var old = ll_data[pla]; if (old === undefined) old = 0; ll_data[pla] = Math.max(old, pld); var prd = Math.sqrt(prx * prx + pry * pry); var pra = (parseInt( Math.round((Math.atan2(pry, prx) * MAX_N) / 2.0 / Math.PI), ) + MAX_N) % MAX_N; var old = rr_data[pra]; if (old === undefined) old = 0; rr_data[pra] = Math.max(old, prd); } ctx.fillStyle = "#000000"; ctx.strokeStyle = "#000000"; ctx.beginPath(); ctx.arc(hb + plx * sz, yb + ply * sz, 4, 0, 2 * Math.PI); ctx.fill(); ctx.beginPath(); ctx.moveTo(hb, yb); ctx.lineTo(hb + plx * sz, yb + ply * sz); ctx.stroke(); ctx.beginPath(); ctx.arc(w - hb + prx * sz, yb + pry * sz, 4, 0, 2 * Math.PI); ctx.fill(); ctx.beginPath(); ctx.moveTo(w - hb, yb); ctx.lineTo(w - hb + prx * sz, yb + pry * sz); ctx.stroke(); var lbl = "", lbx = ""; $("#lx-lbl").text(float_to_str(plx)); $("#ly-lbl").text(float_to_str(ply)); $("#rx-lbl").text(float_to_str(prx)); $("#ry-lbl").text(float_to_str(pry)); if (enable_circ_test) { var ofl = 0, ofr = 0, lcounter = 0, rcounter = 0; ofl = 0; ofr = 0; for (i = 0; i < ll_data.length; i++) if (ll_data[i] > 0.2) { lcounter += 1; ofl += Math.pow(ll_data[i] - 1, 2); } for (i = 0; i < rr_data.length; i++) { if (rr_data[i] > 0.2) { rcounter += 1; ofr += Math.pow(rr_data[i] - 1, 2); } } if (lcounter > 0) ofl = Math.sqrt(ofl / lcounter) * 100; if (rcounter > 0) ofr = Math.sqrt(ofr / rcounter) * 100; el = ofl.toFixed(2) + "%"; er = ofr.toFixed(2) + "%"; $("#el-lbl").text(el); $("#er-lbl").text(er); } } function circ_checked() { return $("#checkCircularity").is(":checked"); } function on_circ_check_change() { enable_circ_test = circ_checked(); for (i = 0; i < ll_data.length; i++) ll_data[i] = 0; for (i = 0; i < rr_data.length; i++) rr_data[i] = 0; if (enable_circ_test) { $("#circ-data").show(); } else { $("#circ-data").hide(); } refresh_stick_pos(); } function float_to_str(f) { if (f < 0.004 && f >= -0.004) return "+0.00"; return (f < 0 ? "" : "+") + f.toFixed(2); } var on_delay = false; function timeout_ok() { on_delay = false; if (ll_updated) refresh_stick_pos(); } function refresh_sticks() { if (on_delay) return; refresh_stick_pos(); on_delay = true; setTimeout(timeout_ok, 20); } var last_bat_txt = ""; var last_bat_disable = null; function bat_percent_to_text(bat_charge, is_charging, is_error) { var icon_txt = ""; if (bat_charge < 20) { icon_txt = "fa-battery-empty"; } else if (bat_charge < 40) { icon_txt = "fa-battery-quarter"; } else if (bat_charge < 60) { icon_txt = "fa-battery-half"; } else if (bat_charge < 80) { icon_txt = "fa-battery-three-quarters"; } else { icon_txt = "fa-battery-full"; } var icon_full = ''; var bolt_txt = ""; if (is_charging) bolt_txt = ''; bat_txt = bat_charge + "%" + " " + bolt_txt + " " + icon_full; if (is_error) { bat_txt = '' + l("error") + ""; } return bat_txt; } function update_nvs_changes_status(new_value) { if (new_value == has_changes_to_write) return; if (new_value == 1) { $("#savechanges").prop("disabled", false); $("#savechanges") .addClass("btn-success") .removeClass("btn-outline-secondary"); } else { $("#savechanges").prop("disabled", true); $("#savechanges") .removeClass("btn-success") .addClass("btn-outline-secondary"); } has_changes_to_write = new_value; } function update_battery_status( bat_capacity, cable_connected, is_charging, is_error, ) { var bat_txt = bat_percent_to_text(bat_capacity, is_charging); var can_use_tool = bat_capacity >= 30 && cable_connected && !is_error; // is this even being used? if (bat_txt != last_bat_txt) { $("#d-bat").html(bat_txt); last_bat_txt = bat_txt; } } function process_ds4_input(data) { var lx = data.data.getUint8(0); var ly = data.data.getUint8(1); var rx = data.data.getUint8(2); var ry = data.data.getUint8(3); var new_lx = Math.round(((lx - 127.5) / 128) * 100) / 100; var new_ly = Math.round(((ly - 127.5) / 128) * 100) / 100; var new_rx = Math.round(((rx - 127.5) / 128) * 100) / 100; var new_ry = Math.round(((ry - 127.5) / 128) * 100) / 100; if ( last_lx != new_lx || last_ly != new_ly || last_rx != new_rx || last_ry != new_ry ) { last_lx = new_lx; last_ly = new_ly; last_rx = new_rx; last_ry = new_ry; ll_updated = true; refresh_sticks(); } // Read battery var bat = data.data.getUint8(29); var bat_data = bat & 0x0f; var bat_status = (bat >> 4) & 1; var bat_capacity = 0; var cable_connected = false; var is_charging = false; var is_error = false; if (bat_status == 1) { cable_connected = true; if (bat_data < 10) { bat_capacity = Math.min(bat_data * 10 + 5, 100); is_charging = true; } else if (bat_data == 10) { bat_capacity = 100; is_charging = true; } else if (bat_data == 11) { bat_capacity = 100; // charged } else { // error bat_capacity = 0; is_error = true; } } else { cable_connected = false; if (bat_data < 10) { bat_capacity = bat_data * 10 + 5; } else { bat_capacity = 100; } } update_battery_status(bat_capacity, cable_connected, is_charging, is_error); } function process_ds_input(data) { var lx = data.data.getUint8(0); var ly = data.data.getUint8(1); var rx = data.data.getUint8(2); var ry = data.data.getUint8(3); var new_lx = Math.round(((lx - 127.5) / 128) * 100) / 100; var new_ly = Math.round(((ly - 127.5) / 128) * 100) / 100; var new_rx = Math.round(((rx - 127.5) / 128) * 100) / 100; var new_ry = Math.round(((ry - 127.5) / 128) * 100) / 100; if ( last_lx != new_lx || last_ly != new_ly || last_rx != new_rx || last_ry != new_ry ) { last_lx = new_lx; last_ly = new_ly; last_rx = new_rx; last_ry = new_ry; ll_updated = true; refresh_sticks(); refresh_finetune(); } var bat = data.data.getUint8(52); var bat_charge = bat & 0x0f; var bat_status = bat >> 4; var bat_capacity = 0; var cable_connected = false; var is_charging = false; var is_error = false; if (bat_status == 0) { bat_capacity = Math.min(bat_charge * 10 + 5, 100); } else if (bat_status == 1) { bat_capacity = Math.min(bat_charge * 10 + 5, 100); is_charging = true; cable_connected = true; } else if (bat_status == 2) { bat_capacity = 100; cable_connected = true; } else { is_error = true; } update_battery_status(bat_capacity, cable_connected, is_charging, is_error); } async function continue_connection(report) { try { device.oninputreport = null; var reportLen = report.data.byteLength; var connected = false; // Detect if the controller is connected via USB if (reportLen != 63) { $("#btnconnect").prop("disabled", false); $("#connectspinner").hide(); disconnect(); show_popup(l("Please connect the device using a USB cable.")); return; } if (device.productId == 0x05c4) { $("#infoshowall").hide(); $("#ds5finetune").hide(); if (await ds4_info()) { connected = true; mode = 1; devname = l("Sony DualShock 4 V1"); device.oninputreport = process_ds4_input; } } else if (device.productId == 0x09cc) { $("#infoshowall").hide(); $("#ds5finetune").hide(); if (await ds4_info()) { connected = true; mode = 1; devname = l("Sony DualShock 4 V2"); device.oninputreport = process_ds4_input; } } else if (device.productId == 0x0ce6) { $("#infoshowall").show(); $("#ds5finetune").show(); if (await ds5_info()) { connected = true; mode = 2; devname = l("Sony DualSense"); device.oninputreport = process_ds_input; } } else if (device.productId == 0x0df2) { $("#infoshowall").hide(); $("#ds5finetune").hide(); if (await ds5_info()) { connected = true; mode = 3; devname = l("Sony DualSense Edge"); } n = await ds5_nvstatus(); if (n == 4) { // dualsense edge with pending reboot $("#btnconnect").prop("disabled", false); $("#connectspinner").hide(); disconnect(); show_popup( l( "A reboot is needed to continue using this DualSense Edge. Please disconnect and reconnect your controller.", ), ); return; } device.oninputreport = process_ds_input; } else { $("#btnconnect").prop("disabled", false); $("#connectspinner").hide(); show_popup( l("Connected invalid device: ") + dec2hex(device.vendorId) + ":" + dec2hex(device.productId), ); disconnect(); return; } if (connected) { $("#devname").text( devname + " (" + dec2hex(device.vendorId) + ":" + dec2hex(device.productId) + ")", ); $("#offlinebar").hide(); $("#onlinebar").show(); $("#mainmenu").show(); $("#resetBtn").show(); $("#d-nvstatus").text = l("Unknown"); $("#d-bdaddr").text = l("Unknown"); } else { show_popup(l("Connected invalid device: ") + l("Error 1")); $("#btnconnect").prop("disabled", false); $("#connectspinner").hide(); disconnect(); return; } if (mode == 3) show_edge_modal(); if (disable_btn != 0) update_disable_btn(); $("#btnconnect").prop("disabled", false); $("#connectspinner").hide(); } catch (error) { $("#btnconnect").prop("disabled", false); $("#connectspinner").hide(); show_popup(l("Error: ") + error); return; } } function update_disable_btn() { if (disable_btn == last_disable_btn) return; if (disable_btn == 0) { $(".ds-btn").prop("disabled", false); last_disable_btn = 0; return; } $(".ds-btn").prop("disabled", true); // show only one popup if (disable_btn & 1 && !(last_disable_btn & 1)) { show_popup( l( "The device appears to be a DS4 clone. All functionalities are disabled.", ), ); } else if (disable_btn & 2 && !(last_disable_btn & 2)) { show_popup( l("This DualSense controller has outdated firmware.") + "
" + l("Please update the firmware and try again."), true, ); } else if (disable_btn & 4 && !(last_disable_btn & 4)) { show_popup( l("Please charge controller battery over 30% to use this tool."), ); } last_disable_btn = disable_btn; } async function connect() { gj = crypto.randomUUID(); // This trigger default disable has_changes_to_write = -1; update_nvs_changes_status(0); reset_circularity(); la("begin"); last_bat_txt = ""; try { $("#btnconnect").prop("disabled", true); $("#connectspinner").show(); await new Promise((r) => setTimeout(r, 100)); let ds4v1 = { vendorId: 0x054c, productId: 0x05c4 }; let ds4v2 = { vendorId: 0x054c, productId: 0x09cc }; let ds5 = { vendorId: 0x054c, productId: 0x0ce6 }; let ds5edge = { vendorId: 0x054c, productId: 0x0df2 }; let requestParams = { filters: [ds4v1, ds4v2, ds5, ds5edge] }; var devices = await navigator.hid.getDevices(); if (devices.length == 0) { devices = await navigator.hid.requestDevice(requestParams); } if (devices.length == 0) { $("#btnconnect").prop("disabled", false); $("#connectspinner").hide(); return; } if (devices.length > 1) { $("#btnconnect").prop("disabled", false); $("#connectspinner").hide(); show_popup(l("Please connect only one controller at time.")); return; } await devices[0].open(); device = devices[0]; la("connect", { p: device.productId, v: device.vendorId }); device.oninputreport = continue_connection; } catch (error) { $("#btnconnect").prop("disabled", false); $("#connectspinner").hide(); show_popup(l("Error: ") + error); return; } } var curModal = null; async function multi_flash() { if (mode == 1) ds4_flash(); else if (mode == 2) ds5_flash(); else if (mode == 3) ds5_edge_flash(); update_nvs_changes_status(0); } async function multi_reset() { if (mode == 1) ds4_reset(); else ds5_reset(); } async function multi_nvstatus() { if (mode == 1) ds4_nvstatus(); else ds5_nvstatus(); } async function multi_nvsunlock() { if (mode == 1) { await ds4_nvunlock(); await ds4_nvstatus(); } else { await ds5_nvunlock(); await ds5_nvstatus(); } } async function multi_nvslock() { if (mode == 1) { await ds4_nvlock(); await ds4_nvstatus(); } else { await ds5_nvlock(); await ds5_nvstatus(); } } async function multi_calib_sticks_begin() { if (mode == 1) return ds4_calibrate_sticks_begin(); else return ds5_calibrate_sticks_begin(); } async function multi_calib_sticks_end() { if (mode == 1) await ds4_calibrate_sticks_end(); else await ds5_calibrate_sticks_end(); on_circ_check_change(); } async function multi_calib_sticks_sample() { if (mode == 1) return ds4_calibrate_sticks_sample(); else return ds5_calibrate_sticks_sample(); } async function multi_calibrate_range() { if (mode == 0) return; set_progress(0); curModal = new bootstrap.Modal(document.getElementById("rangeModal"), {}); curModal.show(); await new Promise((r) => setTimeout(r, 1000)); if (mode == 1) ds4_calibrate_range_begin(); else ds5_calibrate_range_begin(); } async function multi_calibrate_range_on_close() { if (mode == 1) await ds4_calibrate_range_end(); else await ds5_calibrate_range_end(); on_circ_check_change(); } async function multi_calibrate_sticks() { if (mode == 0) return; set_progress(0); curModal = new bootstrap.Modal( document.getElementById("calibrateModal"), {}, ); curModal.show(); await new Promise((r) => setTimeout(r, 1000)); if (mode == 1) ds4_calibrate_sticks(); else ds5_calibrate_sticks(); } function close_calibrate_window() { if (curModal != null) { curModal.hide(); curModal = null; } $("#calibCenterModal").modal("hide"); cur_calib = 0; return; } function set_progress(i) { $(".progress-bar").css("width", "" + i + "%"); } function clear_info() { $("#fwinfo").html(""); $("#fwinfoextra-hw").html(""); $("#fwinfoextra-fw").html(""); } function append_info_extra(key, value, cat) { // TODO escape html var s = '
' + key + '
' + value + "
"; $("#fwinfoextra-" + cat).html($("#fwinfoextra-" + cat).html() + s); } function append_info(key, value, cat) { // TODO escape html var s = '
' + key + '
' + value + "
"; $("#fwinfo").html($("#fwinfo").html() + s); append_info_extra(key, value, cat); } function show_popup(text, is_html = false) { if (is_html) { $("#popupBody").html(text); } else { $("#popupBody").text(text); } new bootstrap.Modal(document.getElementById("popupModal"), {}).show(); } function show_faq_modal() { la("faq_modal"); new bootstrap.Modal(document.getElementById("faqModal"), {}).show(); } function show_donate_modal() { la("donate_modal"); new bootstrap.Modal(document.getElementById("donateModal"), {}).show(); } function show_edge_modal() { la("edge_modal"); new bootstrap.Modal(document.getElementById("edgeModal"), {}).show(); } function show_info_modal() { la("info_modal"); new bootstrap.Modal(document.getElementById("infoModal"), {}).show(); } function discord_popup() { la("discord_popup"); show_popup(l("My handle on discord is: the_al")); } function board_model_info() { la("bm_info"); l1 = l("This feature is experimental."); l2 = l( "Please let me know if the board model of your controller is not detected correctly.", ); l3 = l("Board model detection thanks to") + ' Battle Beaver Customs.'; show_popup(l3 + "

" + l1 + " " + l2, true); } function close_new_calib() { $("#calibCenterModal").modal("hide"); cur_calib = 0; } async function calib_step(i) { la("calib_step", { i: i }); if (i < 1 || i > 7) return; var ret = true; if (i >= 2 && i <= 6) { $("#btnSpinner").show(); $("#calibNext").prop("disabled", true); } if (i == 2) { $("#calibNextText").text(l("Initializing...")); await new Promise((r) => setTimeout(r, 100)); ret = await multi_calib_sticks_begin(); } else if (i == 6) { $("#calibNextText").text(l("Sampling...")); await new Promise((r) => setTimeout(r, 100)); ret = await multi_calib_sticks_sample(); await new Promise((r) => setTimeout(r, 100)); $("#calibNextText").text(l("Storing calibration...")); await new Promise((r) => setTimeout(r, 100)); ret = await multi_calib_sticks_end(); } else if (i > 2 && i < 6) { $("#calibNextText").text(l("Sampling...")); await new Promise((r) => setTimeout(r, 100)); ret = await multi_calib_sticks_sample(); } if (i >= 2 && i <= 6) { await new Promise((r) => setTimeout(r, 200)); $("#calibNext").prop("disabled", false); $("#btnSpinner").hide(); } if (ret == false) { close_new_calib(); return; } for (j = 1; j < 7; j++) { $("#list-" + j).hide(); $("#list-" + j + "-calib").removeClass("active"); } $("#list-" + i).show(); $("#list-" + i + "-calib").addClass("active"); if (i == 1) { $("#calibTitle").text(l("Stick center calibration")); $("#calibNextText").text(l("Start")); } else if (i == 6) { $("#calibTitle").text(l("Stick center calibration")); $("#calibNextText").text(l("Done")); } else { $("#calibTitle").html(l("Calibration in progress")); $("#calibNextText").text(l("Continue")); } if (i == 1 || i == 6) $("#calibCross").show(); else $("#calibCross").hide(); } var cur_calib = 0; async function calib_open() { la("calib_open"); cur_calib = 0; await calib_next(); new bootstrap.Modal(document.getElementById("calibCenterModal"), {}).show(); } async function calib_next() { la("calib_next"); if (cur_calib == 6) { close_new_calib(); return; } if (cur_calib < 6) { cur_calib += 1; await calib_step(cur_calib); } } function la(k, v = {}) { $.ajax({ type: "POST", url: "https://the.al/ds4_a/l", data: JSON.stringify({ u: gu, j: gj, k: k, v: v }), contentType: "application/json", dataType: "json", }); } function lf(k, f) { la(k, buf2hex(f.buffer)); return f; } function lang_init() { var id_iter = 0; var items = document.getElementsByClassName("ds-i18n"); for (i = 0; i < items.length; i++) { var item = items[i]; if (item.id.length == 0) { var new_id = "ds-g-id-" + id_iter++; item.id = new_id; } lang_orig_text[item.id] = $(item).html(); } lang_orig_text[".title"] = document.title; var force_lang = readCookie("force_lang"); if (force_lang != null) { lang_set(force_lang, true); } else { var nlang = navigator.language.replace("-", "_").toLowerCase(); var ljson = available_langs[nlang]; if (ljson !== undefined) { la("lang_init", { l: nlang }); lang_translate(ljson["file"], nlang, ljson["direction"]); } } var langs = Object.keys(available_langs); var olangs = ""; olangs += '
  • English
  • '; for (i = 0; i < langs.length; i++) { name = available_langs[langs[i]]["name"]; olangs += '
  • " + name + "
  • "; } olangs += '
  • '; olangs += '
  • Missing your language?
  • '; $("#availLangs").html(olangs); } function lang_set(l, skip_modal = false) { la("lang_set", { l: l }); if (l == "en_us") { lang_reset_page(); } else { var file = available_langs[l]["file"]; var direction = available_langs[l]["direction"]; lang_translate(file, l, direction); } createCookie("force_lang", l); if (!skip_modal) { createCookie("welcome_accepted", "0"); welcome_modal(); } } function lang_reset_page() { lang_set_direction("ltr", "en_us"); var items = document.getElementsByClassName("ds-i18n"); for (i = 0; i < items.length; i++) { var item = items[i]; $(item).html(lang_orig_text[item.id]); } $("#authorMsg").html(""); $("#curLang").html("English"); document.title = lang_orig_text[".title"]; } function lang_set_direction(new_direction, lang_name) { var lang_prefix = lang_name.split("_")[0]; $("html").attr("lang", lang_prefix); if (new_direction == lang_cur_direction) return; if (new_direction == "rtl") { $("#bootstrap-css").attr( "integrity", "sha384-dpuaG1suU0eT09tx5plTaGMLBsfDLzUCCUXOY2j/LSvXYuG6Bqs43ALlhIqAJVRb", ); $("#bootstrap-css").attr( "href", "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.rtl.min.css", ); } else { $("#bootstrap-css").attr( "integrity", "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH", ); $("#bootstrap-css").attr( "href", "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css", ); } $("html").attr("dir", new_direction); lang_cur_direction = new_direction; } function l(text) { if (lang_disabled) return text; var out = lang_cur[text]; if (out !== undefined) { return out; } console.log("Missing translation for: '" + text + "'"); return text; } function lang_translate(target_file, target_lang, target_direction) { lang_cur = {}; $.getJSON("../lang/" + target_file, function (data) { lang_set_direction(target_direction, target_lang); $.each(data, function (key, val) { if (lang_cur[key] !== undefined) { console.log("Warn: already exists " + key); } else { lang_cur[key] = [val]; } }); if (Object.keys(lang_cur).length > 0) { lang_disabled = false; } var items = document.getElementsByClassName("ds-i18n"); for (i = 0; i < items.length; i++) { var item = items[i]; var old = lang_orig_text[item.id]; var tnew = lang_cur[old]; if (tnew !== undefined && tnew.length == 1 && tnew[0].length > 0) { $(item).html(tnew[0]); } else { console.log("Cannot find mapping for " + old); $(item).html(old); } } var old_title = lang_orig_text[".title"]; document.title = lang_cur[old_title]; if (lang_cur[".authorMsg"] !== undefined) { $("#authorMsg").html(lang_cur[".authorMsg"]); } $("#curLang").html(available_langs[target_lang]["name"]); }); }