Compare commits

...

5 Commits
v0.7 ... v0.8

Author SHA1 Message Date
dualshock-tools
11fd1850e8 Add board model detection for DualSense 2024-04-22 20:00:20 +02:00
Yyiyun
4df961d1d4 Update zh_cn.json (#9) 2024-04-22 19:36:14 +02:00
dualshock-tools
f77d0a81c7 Update permission policy 2024-04-22 16:14:48 +02:00
dualshock-tools
f8546a7759 Improve translation 2024-04-22 16:07:51 +02:00
dualshock-tools
0c812c6a4f Add analytics support 2024-04-22 16:07:45 +02:00
10 changed files with 205 additions and 114 deletions

296
core.js
View File

@@ -6,6 +6,8 @@ var disable_btn = false;
var lang_orig_text = {};
var lang_cur = {};
var lang_disabled = true;
var gj = 0;
var gu = 0;
var available_langs = {
"zh_cn": { "name": "中文", "file": "zh_cn.json"},
@@ -18,16 +20,37 @@ var available_langs = {
"tr_tr": { "name": "Türkçe", "file": "tr_tr.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 {
return l("Unknown");
}
}
function ds4_hw_to_bm(hw_ver) {
a = hw_ver >> 8;
if(a == 0x31) {
@@ -38,6 +61,8 @@ function ds4_hw_to_bm(hw_ver) {
return "JDM-030";
} else if(a == 0x64) {
return "JDM-040";
} else if(a == 0x83) {
return "JDM-020";
} else if(a == 0xa4) {
return "JDM-050";
} else if(a == 0xb4) {
@@ -49,11 +74,12 @@ function ds4_hw_to_bm(hw_ver) {
async function ds4_info() {
try {
const view = await device.receiveFeatureReport(0xa3);
const view = lf("ds4_info", await device.receiveFeatureReport(0xa3));
var cmd = view.getUint8(0, true);
if(cmd != 0xa3 || view.buffer.byteLength != 49)
if(cmd != 0xa3 || view.buffer.byteLength != 49) {
return false;
}
var k1 = new TextDecoder().decode(view.buffer.slice(1, 0x10));
var k2 = new TextDecoder().decode(view.buffer.slice(0x10, 0x20));
@@ -73,6 +99,7 @@ async function ds4_info() {
const view = await device.receiveFeatureReport(0x81);
ooc = l("original");
} catch(e) {
la("clone");
is_clone = true;
ooc = "<font color='red'><b>" + l("clone") + "</b></font>";
disable_btn = true;
@@ -100,6 +127,7 @@ async function ds4_info() {
}
async function ds4_reset() {
la("ds4_reset");
try {
await device.sendFeatureReport(0xa0, alloc_req(0x80, [4,1,0]))
} catch(error) {
@@ -107,6 +135,7 @@ async function ds4_reset() {
}
async function ds5_reset() {
la("ds5_reset");
try {
await device.sendFeatureReport(0x80, alloc_req(0x80, [1,1,0]))
} catch(error) {
@@ -114,6 +143,7 @@ async function ds5_reset() {
}
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) {
@@ -142,6 +172,7 @@ async function ds4_calibrate_range_begin(perm_ch) {
}
async function ds4_calibrate_range_end(perm_ch) {
la("ds4_calibrate_range_end", {"p": perm_ch});
var err = l("Range calibration failed: ");
try {
// Write
@@ -172,6 +203,7 @@ async function ds4_calibrate_range_end(perm_ch) {
}
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) {
@@ -202,6 +234,7 @@ async function ds4_calibrate_sticks_begin(has_perm_changes) {
}
async function ds4_calibrate_sticks_sample() {
la("ds4_calibrate_sticks_sample");
var err = l("Stick calibration failed: ");
try {
// Sample
@@ -226,6 +259,7 @@ async function ds4_calibrate_sticks_sample() {
}
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
@@ -254,6 +288,7 @@ async function ds4_calibrate_sticks_end(has_perm_changes) {
}
async function ds4_calibrate_sticks() {
la("ds4_calibrate_sticks");
var err = l("Stick calibration failed: ");
try {
set_progress(0);
@@ -312,7 +347,7 @@ async function ds4_calibrate_sticks() {
async function ds4_nvstatus() {
await device.sendFeatureReport(0x08, alloc_req(0x08, [0xff,0, 12]))
data = await device.receiveFeatureReport(0x11)
data = lf("ds4_nvstatus", await device.receiveFeatureReport(0x11))
// 1: temporary, 0: permanent
ret = data.getUint8(1, false);
if(ret == 1) {
@@ -328,7 +363,7 @@ async function ds4_nvstatus() {
async function ds5_nvstatus() {
try {
await device.sendFeatureReport(0x80, alloc_req(0x80, [3,3]))
data = await device.receiveFeatureReport(0x81)
data = lf("ds5_nvstatus", await device.receiveFeatureReport(0x81))
ret = data.getUint32(1, false);
if(ret == 0x03030201) {
$("#d-nvstatus").html("<font color='green'>" + l("locked") + "</font>");
@@ -348,7 +383,7 @@ async function ds5_nvstatus() {
async function ds4_getbdaddr() {
try {
data = await device.receiveFeatureReport(0x12)
data = lf("ds4_getbdaddr", await device.receiveFeatureReport(0x12));
out = ""
for(i=0;i<6;i++) {
if(i >= 1) out += ":";
@@ -364,8 +399,8 @@ async function ds4_getbdaddr() {
async function ds5_getbdaddr() {
try {
await device.sendFeatureReport(0x80, alloc_req(0x80, [9,2]))
data = await device.receiveFeatureReport(0x81)
await lf("ds5_getbdaddr", device.sendFeatureReport(0x80, alloc_req(0x80, [9,2])))
data = await device.receiveFeatureReport(0x81);
out = ""
for(i=0;i<6;i++) {
if(i >= 1) out += ":";
@@ -380,15 +415,17 @@ async function ds5_getbdaddr() {
}
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() {
const view = await device.receiveFeatureReport(0x20);
const view = lf("ds5_info", await device.receiveFeatureReport(0x20));
var cmd = view.getUint8(0, true);
if(cmd != 0x20 || view.buffer.byteLength != 64)
@@ -421,12 +458,19 @@ async function ds5_info() {
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 = '&nbsp;<a class="link-body-emphasis" href="#" onclick="board_model_info()">' +
'<svg class="bi" width="1.3em" height="1.3em"><use xlink:href="#info"/></svg></a>';
append_info(l("Board Model:"), ds5_hw_to_bm(hwinfo) + b_info);
await ds5_nvstatus();
await ds5_getbdaddr();
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: ");
console.log("::ds5_calibrate_sticks_begin(" + has_perm_changes + ")");
try {
if(has_perm_changes) {
await ds5_nvunlock();
@@ -454,8 +498,8 @@ async function ds5_calibrate_sticks_begin(has_perm_changes) {
}
async function ds5_calibrate_sticks_sample() {
la("ds5_calibrate_sticks_sample");
var err = l("Stick calibration failed: ");
console.log("::ds5_calibrate_sticks_sample()");
try {
// Sample
await device.sendFeatureReport(0x82, alloc_req(0x82, [3,1,1]))
@@ -476,8 +520,8 @@ async function ds5_calibrate_sticks_sample() {
}
async function ds5_calibrate_sticks_end(has_perm_changes) {
la("ds5_calibrate_sticks_end", {"p": has_perm_changes});
var err = l("Stick calibration failed: ");
console.log("::ds5_calibrate_sticks_end(" + has_perm_changes + ")");
try {
// Write
await device.sendFeatureReport(0x82, alloc_req(0x82, [2,1,1]))
@@ -505,6 +549,7 @@ async function ds5_calibrate_sticks_end(has_perm_changes) {
}
async function ds5_calibrate_sticks() {
la("ds5_fast_calibrate_sticks");
var err = l("Stick calibration failed: ");
try {
set_progress(0);
@@ -567,6 +612,7 @@ async function ds5_calibrate_sticks() {
}
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) {
@@ -595,6 +641,7 @@ async function ds5_calibrate_range_begin(perm_ch) {
}
async function ds5_calibrate_range_end(perm_ch) {
la("ds5_calibrate_range_end", {"p": perm_ch});
var err = l("Range calibration failed: ");
try {
// Write
@@ -626,6 +673,7 @@ async function ds5_calibrate_range_end(perm_ch) {
}
async function ds5_nvlock() {
la("ds5_nvlock");
try {
await device.sendFeatureReport(0x80, alloc_req(0x80, [3,1]))
data = await device.receiveFeatureReport(0x83)
@@ -637,19 +685,22 @@ async function ds5_nvlock() {
}
async function 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);
}
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;
@@ -662,6 +713,7 @@ async function disconnect() {
}
function handleDisconnectedDevice(e) {
la("disconnected");
console.log("Disconnected: " + e.device.productName)
disconnect();
}
@@ -706,11 +758,13 @@ function welcome_modal() {
}
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();
@@ -743,101 +797,104 @@ function alloc_req(id, data=[]) {
}
async function connect() {
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);
}
gj = crypto.randomUUID();
la("begin");
try {
$("#btnconnect").prop("disabled", true);
$("#connectspinner").show();
await new Promise(r => setTimeout(r, 100));
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]
var connected = false
if(device.productId == 0x05c4) {
if(await ds4_info()) {
connected = true
mode = 1;
devname = l("Sony DualShock 4 V1");
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);
}
} else if(device.productId == 0x09cc) {
if(await ds4_info()) {
connected = true
mode = 1;
devname = l("Sony DualShock 4 V2");
if (devices.length == 0) {
$("#btnconnect").prop("disabled", false);
$("#connectspinner").hide();
return;
}
} else if(device.productId == 0x0ce6) {
if(await ds5_info()) {
connected = true
mode = 2;
devname = l("Sony DualSense");
if (devices.length > 1) {
$("#btnconnect").prop("disabled", false);
$("#connectspinner").hide();
show_popup(l("Please connect only one controller at time."));
return;
}
} else if(device.productId == 0x0df2) {
if(await ds5_info()) {
connected = true
mode = 0;
devname = l("Sony DualSense Edge");
disable_btn = true;
}
} 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");
}
if(disable_btn) {
if(device.productId == 0x0df2) {
show_popup(l("Calibration of the DualSense Edge is not currently supported."));
await devices[0].open();
device = devices[0]
la("connect", {"p": device.productId, "v": device.vendorId});
var connected = false
if(device.productId == 0x05c4) {
if(await ds4_info()) {
connected = true
mode = 1;
devname = l("Sony DualShock 4 V1");
}
} else if(device.productId == 0x09cc) {
if(await ds4_info()) {
connected = true
mode = 1;
devname = l("Sony DualShock 4 V2");
}
} else if(device.productId == 0x0ce6) {
if(await ds5_info()) {
connected = true
mode = 2;
devname = l("Sony DualSense");
}
} else if(device.productId == 0x0df2) {
if(await ds5_info()) {
connected = true
mode = 0;
devname = l("Sony DualSense Edge");
disable_btn = true;
}
} else {
show_popup(l("The device appears to be a DS4 clone. All functionalities are disabled."));
$("#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");
}
if(disable_btn) {
if(device.productId == 0x0df2) {
show_popup(l("Calibration of the DualSense Edge is not currently supported."));
} else {
show_popup(l("The device appears to be a DS4 clone. All functionalities are disabled."));
}
}
$(".ds-btn").prop("disabled", disable_btn);
$("#btnconnect").prop("disabled", false);
$("#connectspinner").hide();
} catch(error) {
$("#btnconnect").prop("disabled", false);
$("#connectspinner").hide();
show_popup(l("Error: ") + error);
return;
}
$(".ds-btn").prop("disabled", disable_btn);
$("#btnconnect").prop("disabled", false);
$("#connectspinner").hide();
} catch(error) {
$("#btnconnect").prop("disabled", false);
$("#connectspinner").hide();
show_popup(l("Error: ") + error);
return;
}
}
var curModal = null
@@ -982,12 +1039,22 @@ function show_popup(text, is_html = false) {
}
function show_faq_modal() {
la("faq_modal");
new bootstrap.Modal(document.getElementById('faqModal'), {}).show()
}
function discord_popup() { show_popup(l("My handle on discord is: the_al")); }
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") + ' <a href="https://battlebeavercustoms.com/">Battle Beaver Customs</a>.';
@@ -1006,6 +1073,7 @@ function close_new_calib() {
}
async function calib_step(i) {
la("calib_step", {"i": i})
if(i < 1 || i > 7) return;
var pc = calib_perm_changes();
@@ -1072,6 +1140,7 @@ async function calib_step(i) {
var cur_calib = 0;
async function calib_open() {
la("calib_open");
cur_calib = 0;
reset_calib_perm_changes();
await calib_next();
@@ -1079,6 +1148,7 @@ async function calib_open() {
}
async function calib_next() {
la("calib_next");
if(cur_calib == 6) {
close_new_calib()
return;
@@ -1089,6 +1159,14 @@ async function calib_next() {
}
}
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');
@@ -1110,6 +1188,7 @@ function lang_init() {
var nlang = navigator.language.replace('-', '_').toLowerCase();
var ljson = available_langs[nlang];
if(ljson !== undefined) {
la("lang_init", {"l": nlang});
lang_translate(ljson["file"], nlang);
}
}
@@ -1128,6 +1207,7 @@ function lang_init() {
}
function lang_set(l, skip_modal=false) {
la("lang_set", {"l": l})
if(l == "en_us") {
lang_reset_page();
} else {

View File

@@ -19,7 +19,7 @@
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
crossorigin="anonymous"></script>
<meta http-equiv="Permissions-Policy" content="interest-cohort=()">
<script src="core.js"></script>
</head>
<body>
@@ -162,6 +162,7 @@
<ul>
<li class="ds-i18n">This website is not affiliated with Sony, PlayStation &amp; co.</li>
<li class="ds-i18n">This service is provided without warranty. Use at your own risk.</li>
<li class="ds-i18n">This website uses analytics to improve the service.</li>
<li class="ds-i18n">Keep the internal battery of the controller connected and ensure it is well charged. If the battery dies during operations, the controller will be damaged and rendered unusable.</li>
<li class="ds-i18n">Before doing the permanent calibration, try the temporary one to ensure that everything is working well.</li>
</ul>
@@ -437,7 +438,7 @@
<div class="container">
<footer>
<div class="d-flex flex-column flex-sm-row justify-content-between py-4 my-4 border-top" id="footbody">
<p><span class="ds-i18n">Version</span> 0.7 (2024-04-21) - <a href="#" class="ds-i18n" data-bs-toggle="modal" data-bs-target="#donateModal">Support this project</a>&nbsp;<span id="authorMsg"></span></p>
<p><span class="ds-i18n">Version</span> 0.8 (2024-04-22) - <a href="#" class="ds-i18n" onclick="show_donate_modal();">Support this project</a>&nbsp;<span id="authorMsg"></span></p>
<ul class="list-unstyled d-flex">
<li class="ms-3"><a class="link-body-emphasis" href="mailto:ds4@the.al" target="_blank"><svg class="bi" width="24" height="24"><use xlink:href="#mail"/></svg></a></li>

View File

@@ -160,5 +160,7 @@
"This feature is experimental.": "",
"Please let me know if the board model of your controller is not detected correctly.": "",
"Board model detection thanks to": "",
"This website uses analytics to improve the service.": "Diese Website verwendet Analytics, um den Service zu verbessern.",
"": ""
}

View File

@@ -156,5 +156,6 @@
"Translate this website in your language": "Traduz esta web en tu idioma",
", to help more people like you!": ", para que ayude mas personas como tu!",
"This website uses analytics to improve the service.": "Este sitio web utiliza análisis para mejorar el servicio.",
"": ""
}

View File

@@ -161,5 +161,6 @@
"Please let me know if the board model of your controller is not detected correctly.": "",
"Board model detection thanks to": "",
"This website uses analytics to improve the service.": "Ce site utilise des analyses pour améliorer le service.",
"": ""
}

View File

@@ -155,5 +155,7 @@
"Ship me a controller you would love to add (send me an email for organization).": "",
"Translate this website in your language": "",
", to help more people like you!": "",
"This website uses analytics to improve the service.": "Ez a weboldal analitikát használ a szolgáltatás javításához.",
"": ""
}

View File

@@ -161,5 +161,6 @@
"Please let me know if the board model of your controller is not detected correctly.": "Scrivimi se il modello della scheda del tuo controller non viene riconosciuto correttamente.",
"Board model detection thanks to": "Rilevamento del modello della scheda grazie a",
"This website uses analytics to improve the service.": "Questo sito web utilizza analytics per migliorare il servizio.",
"": ""
}

View File

@@ -161,5 +161,6 @@
"Please let me know if the board model of your controller is not detected correctly.": "",
"Board model detection thanks to": "",
"This website uses analytics to improve the service.": "このウェブサイトはサービスを向上させるためにアナリティクスを使用しています。",
"": ""
}

View File

@@ -160,5 +160,6 @@
"Please let me know if the board model of your controller is not detected correctly.": "",
"Board model detection thanks to": "",
"This website uses analytics to improve the service.": "Bu web sitesi hizmeti iyileştirmek için analiz kullanır.",
"": ""
}

View File

@@ -156,10 +156,11 @@
"Translate this website in your language": "将这个网站翻译成您的语言",
", to help more people like you!": "以帮助更多像您一样的人!",
"Board Model:": "",
"This feature is experimental.": "",
"Please let me know if the board model of your controller is not detected correctly.": "",
"Board model detection thanks to": "",
"Board Model:": "主板型号",
"This feature is experimental.": "此功能为实验性质。",
"Please let me know if the board model of your controller is not detected correctly.": "如果您的手柄的主板型号没有被正确检测,请告诉我。",
"Board model detection thanks to": "主板型号检测功能需要感谢",
"This website uses analytics to improve the service.": "该网站使用分析工具来改善服务。",
"": ""
}