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"}, "rs_rs": { "name": "Srpski", "file": "rs_rs.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"}, "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: ") + str(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,0])) } 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 == 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(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; } 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(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); 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(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 + ")."); } 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(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; 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> 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 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_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); 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.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) { $("#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 = 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_edge_modal(); } 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 ds5_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; 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"]; 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 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"]); }); }