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; var lang_orig_text = {}; var lang_cur = {}; var lang_disabled = true; var gj = 0; var gu = 0; // Alphabetical order var available_langs = { "bg_bg": { "name": "Български", "file": "bg_bg.json"}, "cz_cz": { "name": "Čeština", "file": "cz_cz.json"}, "de_de": { "name": "Deutsch", "file": "de_de.json"}, "es_es": { "name": "Español", "file": "es_es.json"}, "fr_fr": { "name": "Français", "file": "fr_fr.json"}, "hu_hu": { "name": "Magyar", "file": "hu_hu.json"}, "it_it": { "name": "Italiano", "file": "it_it.json"}, "ko_kr": { "name": "한국어", "file": "ko_kr.json"}, "jp_jp": { "name": "日本語", "file": "jp_jp.json"}, "pl_pl": { "name": "Polski", "file": "pl_pl.json"}, "pt_br": { "name": "Português do Brasil", "file": "pt_br.json"}, "ru_ru": { "name": "Русский", "file": "ru_ru.json"}, "tr_tr": { "name": "Türkçe", "file": "tr_tr.json"}, "zh_cn": { "name": "中文", "file": "zh_cn.json"}, "zh_tw": { "name": "中文(繁)", "file": "zh_tw.json"}, }; 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) { 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) { 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 == 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 query NVS Status and BD Addr await ds4_nvstatus(); await ds4_getbdaddr(); 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_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,0])) } catch(error) { } } async function ds4_calibrate_range_begin(perm_ch) { la("ds4_calibrate_range_begin", {"p": perm_ch}); var err = l("Range calibration failed: "); try { if(perm_ch) { await ds4_nvunlock(); if(await ds4_nvstatus() != 0) { la("ds4_calibrate_range_begin_failed", {"r": "nvunlock"}); close_calibrate_window(); return show_popup(err + l("Cannot unlock NVS")); } } // 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(perm_ch) { la("ds4_calibrate_range_end", {"p": perm_ch}); 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")); } if(perm_ch) { await ds4_nvlock(); if(await ds4_nvstatus() != 1) { la("ds4_calibrate_range_end_failed", {"r": "nvlock"}); close_calibrate_window(); return show_popup(err + l("Cannot relock NVS")); } } 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(has_perm_changes) { la("ds4_calibrate_sticks_begin", {"p": has_perm_changes}); var err = l("Stick calibration failed: "); try { if(has_perm_changes) { await ds4_nvunlock(); if(await ds4_nvstatus() != 0) { la("ds4_calibrate_sticks_begin_failed", {"r": "nvunlock"}); show_popup(err + l("Cannot unlock NVS")); return false; } } // 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(has_perm_changes) { la("ds4_calibrate_sticks_end", {"p": has_perm_changes}); 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; } if(has_perm_changes) { await ds4_nvlock(); if(await ds4_nvstatus() != 1) { la("ds4_calibrate_sticks_end_failed", {"r": "nvlock"}); show_popup(err + l("Cannot relock NVS")); return false; } } 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() { 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") + ""); } else if(ret == 0) { $("#d-nvstatus").html("" + l("unlocked") + ""); } else { $("#d-nvstatus").html("unk " + ret + ""); } return ret; } 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 == 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) + ""); 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(i, false)); } $("#d-bdaddr").text(out); return out; } catch(e) { $("#d-bdaddr").html("" + l("error") + ""); 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)); } $("#d-bdaddr").text(out); return out; } catch(e) { $("#d-bdaddr").html("" + l("error") + ""); 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_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.getUint16(46, true); var fwversion1 = view.getUint32(50, true); var fwversion2 = view.getUint32(54, true); var fwversion3 = view.getUint32(58, true); clear_info(); append_info(l("Build Date:"), build_date + " " + build_time); append_info(l("Firmware Type:"), "0x" + dec2hex(fwtype)); append_info(l("SW Series:"), "0x" + dec2hex(swseries)); append_info(l("HW Info:"), "0x" + dec2hex32(hwinfo)); append_info(l("SW Version:"), "0x" + dec2hex32(fwversion)); append_info(l("UPD Version:"), "0x" + dec2hex(updversion)); append_info(l("FW Version1:"), "0x" + dec2hex32(fwversion1)); append_info(l("FW Version2:"), "0x" + dec2hex32(fwversion2)); append_info(l("FW Version3:"), "0x" + dec2hex32(fwversion3)); b_info = ' ' + ''; append_info(l("Board Model:"), ds5_hw_to_bm(hwinfo) + b_info); old_controller = build_date.search(/ 2020| 2021/); if(old_controller != -1) { la("ds5_info_error", {"r": "old"}) disable_btn |= 2; return true; } await ds5_nvstatus(); await ds5_getbdaddr(); } 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(has_perm_changes) { la("ds5_calibrate_sticks_begin", {"p": has_perm_changes}); var err = l("Range calibration failed: "); try { if(has_perm_changes) { await ds5_nvunlock(); if(await ds5_nvstatus() != 0) { la("ds5_calibrate_sticks_begin_failed", {"r": "nvunlock"}); show_popup(err + l("Cannot unlock NVS")); return false; } } // 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(has_perm_changes) { la("ds5_calibrate_sticks_end", {"p": has_perm_changes}); var err = l("Stick calibration failed: "); try { // Write await device.sendFeatureReport(0x82, alloc_req(0x82, [2,1,1])) data = await device.receiveFeatureReport(0x83) if(data.getUint32(0, false) != 0x83010102) { d1 = dec2hex32(data.getUint32(0, false)); la("ds5_calibrate_sticks_end_failed", {"d1": d1}); show_popup(err + l("Error 3") + " (" + d1 + ")."); return false; } if(has_perm_changes) { await ds5_nvlock(); if(await ds5_nvstatus() != 1) { la("ds5_calibrate_sticks_end_failed", {"r": "nvlock"}); show_popup(err + l("Cannot relock NVS")); return false; } } 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(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 + ")."); } set_progress(100); 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(perm_ch) { la("ds5_calibrate_range_begin", {"p": perm_ch}); var err = l("Range calibration failed: "); try { if(perm_ch) { await ds5_nvunlock(); if(await ds5_nvstatus() != 0) { la("ds5_calibrate_range_begin_failed", {"r": "nvunlock"}); close_calibrate_window(); return show_popup(err + l("Cannot unlock NVS")); } } // 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(perm_ch) { la("ds5_calibrate_range_end", {"p": perm_ch}); 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(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 1") + " (" + d1 + ")."); } if(perm_ch) { await ds5_nvlock(); if(await ds5_nvstatus() != 1) { la("ds5_calibrate_range_end_failed", {"r": "nvlock"}); close_calibrate_window(); return show_popup(err + l("Cannot relock NVS")); } } 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(0x83) } catch(e) { await new Promise(r => setTimeout(r, 500)); close_calibrate_window(); return show_popup(l("NVS Lock 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(0x83) } 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; 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(); 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 0.2) { lcounter += 1; ofl += Math.pow(ll_data[i] - 1, 2); } for (i=0;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= -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_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); 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(); } 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.max(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) { if(await ds4_info()) { connected = true; mode = 1; devname = l("Sony DualShock 4 V1"); device.oninputreport = process_ds4_input; } } else if(device.productId == 0x09cc) { if(await ds4_info()) { connected = true; mode = 1; devname = l("Sony DualShock 4 V2"); device.oninputreport = process_ds4_input; } } else if(device.productId == 0x0ce6) { if(await ds5_info()) { connected = true; mode = 2; devname = l("Sony DualSense"); device.oninputreport = process_ds_input; } } else if(device.productId == 0x0df2) { if(await ds5_info()) { connected = true; mode = 0; devname = l("Sony DualSense Edge"); disable_btn |= 8; } } 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(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 & 8 && !(last_disable_btn & 8)) { show_popup(l("Calibration of the DualSense Edge is not currently supported.")); } 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(); 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_reset() { if(mode == 1) ds4_reset(); else ds5_reset(); } async function multi_getbdaddr() { if(mode == 1) ds4_getbdaddr(); else ds5_getbdaddr(); } 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(pc) { if(mode == 1) return ds4_calibrate_sticks_begin(pc); else return ds5_calibrate_sticks_begin(pc); } async function multi_calib_sticks_end(pc) { if(mode == 1) await ds4_calibrate_sticks_end(pc); else await ds5_calibrate_sticks_end(pc); on_circ_check_change(); } async function multi_calib_sticks_sample() { if(mode == 1) return ds4_calibrate_sticks_sample(); else return ds5_calibrate_sticks_sample(); } var last_perm_ch = 0 async function multi_calibrate_range(perm_ch) { if(mode == 0) return; set_progress(0); curModal = new bootstrap.Modal(document.getElementById('rangeModal'), {}) curModal.show(); last_perm_ch = perm_ch await new Promise(r => setTimeout(r, 1000)); if(mode == 1) ds4_calibrate_range_begin(perm_ch); else ds5_calibrate_range_begin(perm_ch); } async function multi_calibrate_range_on_close() { if(mode == 1) await ds4_calibrate_range_end(last_perm_ch); else await ds5_calibrate_range_end(last_perm_ch); 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(""); } function append_info(key, value) { // TODO escape html var s = '

' + key + '

' + value + '

'; $("#fwinfo").html($("#fwinfo").html() + s); } 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 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 calib_perm_changes() { return $("#calibPermanentChanges").is(':checked') } function reset_calib_perm_changes() { $("#calibPermanentChanges").prop("checked", false).parent().removeClass('active'); } 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 pc = calib_perm_changes(); 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(pc); } 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(pc); } 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; reset_calib_perm_changes(); 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; iEnglish'; for(i=0;i' + 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"]; lang_translate(file, l); } createCookie("force_lang", l); if(!skip_modal) { createCookie("welcome_accepted", "0"); welcome_modal(); } } function lang_reset_page() { var items = document.getElementsByClassName('ds-i18n'); for(i=0; i 0) { lang_disabled = false; } var items = document.getElementsByClassName('ds-i18n'); for(i=0; i 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"]); }); }