UNPKG

dualsense-ts

Version:

The natural interface for your DualSense and DualSense Access controllers, with Typescript

166 lines 6.69 kB
"use strict"; 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