dualsense-ts
Version:
The natural interface for your DualSense and DualSense Access controllers, with Typescript
166 lines • 6.69 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultFactoryInfo = exports.DualsenseColorMap = exports.DualsenseColor = void 0;
exports.readFactoryInfo = readFactoryInfo;
/** Known DualSense body colors */
var DualsenseColor;
(function (DualsenseColor) {
DualsenseColor["Unknown"] = "Unknown";
DualsenseColor["White"] = "White";
DualsenseColor["MidnightBlack"] = "Midnight Black";
DualsenseColor["CosmicRed"] = "Cosmic Red";
DualsenseColor["NovaPink"] = "Nova Pink";
DualsenseColor["GalacticPurple"] = "Galactic Purple";
DualsenseColor["StarlightBlue"] = "Starlight Blue";
DualsenseColor["GreyCamouflage"] = "Grey Camouflage";
DualsenseColor["VolcanicRed"] = "Volcanic Red";
DualsenseColor["SterlingSilver"] = "Sterling Silver";
DualsenseColor["CobaltBlue"] = "Cobalt Blue";
DualsenseColor["ChromaTeal"] = "Chroma Teal";
DualsenseColor["ChromaIndigo"] = "Chroma Indigo";
DualsenseColor["ChromaPearl"] = "Chroma Pearl";
DualsenseColor["Anniversary30th"] = "30th Anniversary";
DualsenseColor["GodOfWarRagnarok"] = "God of War Ragnarok";
DualsenseColor["SpiderMan2"] = "Spider-Man 2";
DualsenseColor["AstroBot"] = "Astro Bot";
DualsenseColor["Fortnite"] = "Fortnite";
DualsenseColor["TheLastOfUs"] = "The Last of Us";
DualsenseColor["IconBlueLimitedEdition"] = "Icon Blue Limited Edition";
DualsenseColor["GenshinImpact"] = "Genshin Impact";
})(DualsenseColor || (exports.DualsenseColor = DualsenseColor = {}));
/** Known DualSense body colors, keyed by the 2-char code from the serial number */
exports.DualsenseColorMap = {
"00": DualsenseColor.White,
"01": DualsenseColor.MidnightBlack,
"02": DualsenseColor.CosmicRed,
"03": DualsenseColor.NovaPink,
"04": DualsenseColor.GalacticPurple,
"05": DualsenseColor.StarlightBlue,
"06": DualsenseColor.GreyCamouflage,
"07": DualsenseColor.VolcanicRed,
"08": DualsenseColor.SterlingSilver,
"09": DualsenseColor.CobaltBlue,
"10": DualsenseColor.ChromaTeal,
"11": DualsenseColor.ChromaIndigo,
"12": DualsenseColor.ChromaPearl,
"30": DualsenseColor.Anniversary30th,
Z1: DualsenseColor.GodOfWarRagnarok,
Z2: DualsenseColor.SpiderMan2,
Z3: DualsenseColor.AstroBot,
Z4: DualsenseColor.Fortnite,
Z6: DualsenseColor.TheLastOfUs,
ZB: DualsenseColor.IconBlueLimitedEdition,
ZE: DualsenseColor.GenshinImpact,
};
/** Board revision names, keyed by the character at serial position 1 */
const BoardRevisionMap = {
"1": "BDM-010",
"2": "BDM-020",
"3": "BDM-030",
"4": "BDM-040",
"5": "BDM-050",
};
/** Default FactoryInfo used when the test command protocol is unavailable */
exports.DefaultFactoryInfo = {
serialNumber: "unknown",
colorName: "unknown",
colorCode: "??",
boardRevision: "unknown",
};
/** Feature report IDs for the test command protocol */
const SEND_REPORT_ID = 0x80;
const RECV_REPORT_ID = 0x81;
/** Report size for test command feature reports (report ID + 63 bytes payload) */
const REPORT_SIZE = 64;
/** Test command device/action IDs */
const DEVICE_SYSTEM = 0x01;
const ACTION_READ_SERIAL = 0x13;
/** Test command response status values */
const STATUS_COMPLETE = 0x02;
/**
* Send a test command via Feature Report 0x80 and poll 0x81 for the response.
* Returns the result data bytes (after the header), or undefined on failure.
*/
async function sendTestCommand(provider, deviceId, actionId, maxAttempts = 20) {
// Build the send report: report ID at byte 0, then payload.
// The provider handles platform differences (WebHID strips byte 0, adds CRC for BT).
const sendBuf = new Uint8Array(REPORT_SIZE).fill(0);
sendBuf[0] = SEND_REPORT_ID;
sendBuf[1] = deviceId;
sendBuf[2] = actionId;
await provider.sendFeatureReport(SEND_REPORT_ID, sendBuf);
// Poll for response
for (let i = 0; i < maxAttempts; i++) {
await sleep(50);
const response = await provider.readFeatureReport(RECV_REPORT_ID, REPORT_SIZE);
if (response.length === 0)
continue;
// Response layout: [reportId, deviceId, actionId, status, ...data]
// Note: node-hid includes the report ID at byte 0; WebHID may not.
// Find the actual start based on whether byte 0 is the report ID.
const offset = response[0] === RECV_REPORT_ID ? 1 : 0;
const respDevice = response[offset];
const respAction = response[offset + 1];
const respStatus = response[offset + 2];
if (respDevice !== deviceId || respAction !== actionId)
continue;
if (respStatus === STATUS_COMPLETE) {
return response.slice(offset + 3);
}
}
return undefined;
}
/** Decode a byte array as ASCII, stopping at null terminator */
function decodeAscii(data, offset, length) {
let str = "";
for (let i = 0; i < length; i++) {
const byte = data[offset + i];
if (byte === 0)
break;
str += String.fromCharCode(byte);
}
return str;
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Read factory info (serial number, body color, board revision) from a connected controller.
*
* Requires firmware support: hardwareInfo >= 777 and mainFirmwareVersion >= 65655.
* Use the values from FirmwareInfo (Feature Report 0x20) to check this gate.
*
* @param provider The HID provider for the connected controller
* @param hardwareInfo Hardware info word from FirmwareInfo
* @param mainFwVersionRaw Raw uint32 main firmware version from FirmwareInfo
*/
async function readFactoryInfo(provider, hardwareInfo, mainFwVersionRaw) {
// Firmware gate check
if ((hardwareInfo & 0xffff) < 777 || mainFwVersionRaw < 65655) {
return undefined;
}
try {
const result = await sendTestCommand(provider, DEVICE_SYSTEM, ACTION_READ_SERIAL);
if (!result)
return undefined;
const serialNumber = decodeAscii(result, 0, 32);
if (serialNumber.length < 6)
return undefined;
const colorCode = serialNumber.slice(4, 6);
const revisionChar = serialNumber.slice(1, 2);
const colorName = colorCode in exports.DualsenseColorMap ? exports.DualsenseColorMap[colorCode] : colorCode;
const boardRevision = revisionChar in BoardRevisionMap
? BoardRevisionMap[revisionChar]
: "unknown";
return {
serialNumber,
colorName,
colorCode,
boardRevision,
};
}
catch {
return undefined;
}
}
//# sourceMappingURL=factory_info.js.map