• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

taichunmin / chameleon-ultra.js / 23279164471

19 Mar 2026 03:59AM UTC coverage: 67.508% (-1.4%) from 68.909%
23279164471

push

github

web-flow
v0.4.2: support new APIs (#213)

265 of 457 branches covered (57.99%)

Branch coverage included in aggregate %.

75 of 113 new or added lines in 4 files covered. (66.37%)

1634 of 2356 relevant lines covered (69.35%)

5179116.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.91
/src/decoder.ts
1
import { Buffer } from '@taichunmin/buffer'
2
import * as _ from 'lodash-es'
3
import { type Class } from 'type-fest'
4

5
import {
6
  Mf1KeyType,
7
  type AnimationMode,
8
  type ButtonAction,
9
  type DarksideStatus,
10
  type Hf14aBccMode,
11
  type Hf14aCascadeLevelMode,
12
  type Hf14aRatsMode,
13
  type HidProxFormat,
14
  type Mf1EmuWriteMode,
15
  type MfuEmuWriteMode,
16
  type TagType,
17
} from './enums'
18
import { type HidProxTag, type Mf1AcquireStaticEncryptedNestedRes } from './types'
19

20
function bufUnpackToClass <T> (buf: Buffer, format: string, Type: Class<T>): T {
21
  return new Type(...buf.unpack<ConstructorParameters<typeof Type>>(format))
30✔
22
}
23

24
export function bufIsLenOrFail (buf: Buffer, len: number | number[], name: string): void {
25
  if (!_.isArray(len)) {
70✔
26
    if (Buffer.isBuffer(buf) && buf.length === len) return
62✔
27
    throw new TypeError(`${name} must be a Buffer with length of ${len}.`)
15✔
28
  } else {
29
    if (Buffer.isBuffer(buf) && _.includes(len, buf.length)) return
8✔
NEW
30
    throw new TypeError(`${name} must be a Buffer with length of ${_.join(len, ' or ')}.`)
×
31
  }
32
}
33

34
export class SlotInfo {
35
  hfTagType: TagType
36
  lfTagType: TagType
37

38
  constructor (hf: TagType, lf: TagType) {
39
    this.hfTagType = hf
8✔
40
    this.lfTagType = lf
8✔
41
  }
42

43
  static fromCmd1019 (buf: Buffer): SlotInfo[] {
44
    bufIsLenOrFail(buf, 32, 'buf')
3✔
45
    return _.map(buf.chunk(4), chunk => bufUnpackToClass(chunk, '!HH', SlotInfo))
8✔
46
  }
47
}
48

49
export class SlotFreqIsEnable {
50
  hf: boolean
51
  lf: boolean
52

53
  constructor (hf: boolean, lf: boolean) {
54
    ;[this.hf, this.lf] = _.map([hf, lf], Boolean)
8✔
55
  }
56

57
  static fromCmd1023 (buf: Buffer): SlotFreqIsEnable[] {
58
    bufIsLenOrFail(buf, 16, 'buf')
3✔
59
    return _.map(buf.chunk(2), chunk => bufUnpackToClass(chunk, '!??', SlotFreqIsEnable))
8✔
60
  }
61
}
62

63
export class BatteryInfo {
64
  voltage: number
65
  level: number
66

67
  constructor (voltage: number, level: number) {
68
    ;[this.voltage, this.level] = [voltage, level]
1✔
69
  }
70

71
  static fromCmd1025 (buf: Buffer): BatteryInfo {
72
    bufIsLenOrFail(buf, 3, 'buf')
3✔
73
    return bufUnpackToClass(buf, '!HB', BatteryInfo)
3✔
74
  }
75
}
76

77
export class DeviceSettings {
78
  version: number // version of setting
79
  animation: AnimationMode
80
  buttonPressAction: ButtonAction[]
81
  buttonLongPressAction: ButtonAction[]
82
  blePairingMode: boolean
83
  blePairingKey: string
84

85
  constructor (
86
    version: number,
87
    animation: AnimationMode,
88
    btnPressA: ButtonAction,
89
    btnPressB: ButtonAction,
90
    btnLongPressA: ButtonAction,
91
    btnLongPressB: ButtonAction,
92
    blePairingMode: boolean,
93
    blePairingKey: string
94
  ) {
95
    this.version = version
1✔
96
    this.animation = animation
1✔
97
    this.buttonPressAction = [btnPressA, btnPressB]
1✔
98
    this.buttonLongPressAction = [btnLongPressA, btnLongPressB]
1✔
99
    this.blePairingMode = Boolean(blePairingMode)
1✔
100
    this.blePairingKey = blePairingKey
1✔
101
  }
102

103
  static fromCmd1034 (buf: Buffer): DeviceSettings {
104
    bufIsLenOrFail(buf, 13, 'buf')
3✔
105
    return bufUnpackToClass(buf, '!6B?6s', DeviceSettings)
3✔
106
  }
107
}
108

109
/**
110
 * Class for Hf14aAntiColl Decoding.
111
 */
112
export class Hf14aAntiColl {
113
  uid: Buffer
114
  atqa: Buffer
115
  sak: Buffer
116
  ats: Buffer
117

118
  constructor (uid: Buffer, atqa: Buffer, sak: Buffer, ats: Buffer) {
119
    ;[this.uid, this.atqa, this.sak, this.ats] = [uid, atqa, sak, ats]
3✔
120
  }
121

122
  static fromBuffer (buf: Buffer): Hf14aAntiColl {
123
    // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen]
124
    const uidLen = buf[0]
5✔
125
    if (buf.length < uidLen + 4) throw new Error('invalid length of uid')
5✔
126
    const atsLen = buf[uidLen + 4]
4✔
127
    if (buf.length < uidLen + atsLen + 5) throw new Error('invalid length of ats')
4✔
128
    return bufUnpackToClass(buf, `!${uidLen + 1}p2ss${atsLen + 1}p`, Hf14aAntiColl)
3✔
129
  }
130

131
  static fromCmd2000 (buf: Buffer): Hf14aAntiColl[] {
132
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer')
3✔
133
    const tags: Hf14aAntiColl[] = []
2✔
134
    while (buf.length > 0) {
2✔
135
      const tag = Hf14aAntiColl.fromBuffer(buf)
2✔
136
      buf = buf.subarray(tag.uid.length + tag.ats.length + 5)
2✔
137
      tags.push(tag)
2✔
138
    }
139
    return tags
2✔
140
  }
141
}
142

143
export class Hf14aSettings {
144
  bcc: Hf14aBccMode
145
  cl2: Hf14aCascadeLevelMode
146
  cl3: Hf14aCascadeLevelMode
147
  rats: Hf14aRatsMode
148

149
  constructor (bcc: Hf14aBccMode, cl2: Hf14aCascadeLevelMode, cl3: Hf14aCascadeLevelMode, rats: Hf14aRatsMode) {
NEW
150
    ;[this.bcc, this.cl2, this.cl3, this.rats] = [bcc, cl2, cl3, rats]
×
151
  }
152

153
  static fromCmd2200 (buf: Buffer): Hf14aSettings {
NEW
154
    bufIsLenOrFail(buf, 4, 'buf')
×
NEW
155
    return bufUnpackToClass(buf, '!BBBB', Hf14aSettings)
×
156
  }
157
}
158

159
export class Mf1AcquireStaticNestedRes {
160
  uid: Buffer
161
  atks: Array<{ nt1: Buffer, nt2: Buffer }>
162

163
  constructor (uid: Buffer, atks: Array<{ nt1: Buffer, nt2: Buffer }>) {
164
    ;[this.uid, this.atks] = [uid, atks]
1✔
165
  }
166

167
  static fromCmd2003 (buf: Buffer): Mf1AcquireStaticNestedRes {
168
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer')
2✔
169
    return new Mf1AcquireStaticNestedRes(
1✔
170
      buf.subarray(0, 4), // uid
171
      _.map(buf.subarray(4).chunk(8), chunk => ({
2✔
172
        nt1: chunk.subarray(0, 4),
173
        nt2: chunk.subarray(4, 8),
174
      })), // atks
175
    )
176
  }
177
}
178

179
export class Mf1DarksideRes {
180
  status: DarksideStatus
181
  uid?: Buffer
182
  nt?: Buffer
183
  par?: Buffer
184
  ks?: Buffer
185
  nr?: Buffer
186
  ar?: Buffer
187

188
  constructor (
189
    status: DarksideStatus,
190
    uid?: Buffer,
191
    nt?: Buffer,
192
    par?: Buffer,
193
    ks?: Buffer,
194
    nr?: Buffer,
195
    ar?: Buffer
196
  ) {
197
    this.status = status
2✔
198
    this.uid = uid
2✔
199
    this.nt = nt
2✔
200
    this.par = par
2✔
201
    this.ks = ks
2✔
202
    this.nr = nr
2✔
203
    this.ar = ar
2✔
204
  }
205

206
  static fromCmd2004 (buf: Buffer): Mf1DarksideRes {
207
    if (!Buffer.isBuffer(buf) || !_.includes([1, 33], buf.length)) throw new TypeError('buf must be a 1 or 33 bytes Buffer.')
4✔
208
    return bufUnpackToClass(buf, buf.length === 1 ? '!B' : '!B4s4s8s8s4s4s', Mf1DarksideRes)
2✔
209
  }
210
}
211

212
export class Mf1NtDistanceRes {
213
  uid: Buffer
214
  dist: Buffer
215

216
  constructor (uid: Buffer, dist: Buffer) {
217
    ;[this.uid, this.dist] = [uid, dist]
1✔
218
  }
219

220
  static fromCmd2005 (buf: Buffer): Mf1NtDistanceRes {
221
    bufIsLenOrFail(buf, 8, 'buf')
3✔
222
    return bufUnpackToClass(buf, '!4s4s', Mf1NtDistanceRes)
3✔
223
  }
224
}
225

226
/** Answer the random number parameters required for Nested attack */
227
export class Mf1NestedRes {
228
  nt1: number // Unblocked explicitly random number
229
  nt2: number // Random number of nested verification encryption
230
  par: number // The 3 parity bit of nested verification encryption
231

232
  constructor (nt1: number, nt2: number, par: number) {
233
    ;[this.nt1, this.nt2, this.par] = [nt1, nt2, par]
2✔
234
  }
235

236
  static fromCmd2006 (buf: Buffer): Mf1NestedRes[] {
237
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer.')
2✔
238
    return _.map(buf.chunk(9), chunk => bufUnpackToClass(chunk, '!IIB', Mf1NestedRes))
2✔
239
  }
240
}
241

242
export class Mf1CheckKeysOfSectorsRes {
243
  found: Buffer
244
  sectorKeys: Array<Buffer | null>
245

246
  constructor (found: Buffer, sectorKeys: Buffer[]) {
247
    this.found = found
3✔
248
    this.sectorKeys = _.times(80, i => found.readBitMSB(i) === 1 ? sectorKeys[i] : null)
240✔
249
  }
250

251
  static fromCmd2012 (buf: Buffer): Mf1CheckKeysOfSectorsRes {
252
    bufIsLenOrFail(buf, 490, 'buf')
5✔
253
    return new Mf1CheckKeysOfSectorsRes(
5✔
254
      buf.subarray(0, 10), // found
255
      buf.subarray(10).chunk(6), // sectorKeys
256
    )
257
  }
258
}
259

260
export class Mf1DetectionLog {
261
  block: number
262
  isKeyB: boolean
263
  isNested: boolean
264
  uid: Buffer
265
  nt: Buffer
266
  nr: Buffer
267
  ar: Buffer
268

269
  constructor (
270
    block: number,
271
    flags: Buffer,
272
    uid: Buffer,
273
    nt: Buffer,
274
    nr: Buffer,
275
    ar: Buffer
276
  ) {
277
    this.block = block
3✔
278
    this.isKeyB = flags.readBitLSB(0) === 1
3✔
279
    this.isNested = flags.readBitLSB(1) === 1
3✔
280
    this.uid = uid
3✔
281
    this.nt = nt
3✔
282
    this.nr = nr
3✔
283
    this.ar = ar
3✔
284
  }
285

286
  static fromBuffer (buf: Buffer): Mf1DetectionLog {
287
    bufIsLenOrFail(buf, 18, 'buf')
5✔
288
    return bufUnpackToClass(buf, '!Bs4s4s4s4s', Mf1DetectionLog)
5✔
289
  }
290

291
  static fromCmd4006 (buf: Buffer): Mf1DetectionLog[] {
292
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer.')
2✔
293
    return _.map(buf.chunk(18), Mf1DetectionLog.fromBuffer)
1✔
294
  }
295
}
296

297
export class Mf1EmuSettings {
298
  detection: boolean
299
  gen1a: boolean
300
  gen2: boolean
301
  antiColl: boolean
302
  write: Mf1EmuWriteMode
303

304
  constructor (detection: boolean, gen1a: boolean, gen2: boolean, antiColl: boolean, write: Mf1EmuWriteMode) {
305
    this.detection = detection
1✔
306
    this.gen1a = gen1a
1✔
307
    this.gen2 = gen2
1✔
308
    this.antiColl = antiColl
1✔
309
    this.write = write
1✔
310
  }
311

312
  static fromCmd4009 (buf: Buffer): Mf1EmuSettings {
313
    bufIsLenOrFail(buf, 5, 'buf')
2✔
314
    return bufUnpackToClass(buf, '!4?B', Mf1EmuSettings)
2✔
315
  }
316
}
317

318
/** Answer the random number parameters required for Hard Nested attack */
319
export class Mf1AcquireHardNestedRes {
320
  nt: number // tag nonce of nested authentication
321
  ntEnc: number // encrypted tag nonce of nested authentication
322
  par: number // The 8 parity bit of nested authentication
323

324
  constructor (nt: number, ntEnc: number, par: number) {
325
    ;[this.nt, this.ntEnc, this.par] = [nt, ntEnc, par]
×
326
  }
327

328
  static fromCmd2013 (buf: Buffer): Mf1AcquireHardNestedRes[] {
329
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer.')
×
330
    return _.map(buf.chunk(9), chunk => bufUnpackToClass(chunk, '!IIB', Mf1AcquireHardNestedRes))
×
331
  }
332
}
333

334
export class HidProxScanRes implements HidProxTag {
335
  format: HidProxFormat
336
  fc: number
337
  cn: number
338
  il: number
339
  oem: number
340

341
  constructor (format: HidProxFormat, fc: number, cn1: number, cn2: number, il: number, oem: number) {
342
    ;[this.format, this.fc, this.cn, this.il, this.oem] = [format, fc, cn1 * 0x100000000 + cn2, il, oem]
×
343
  }
344

345
  static fromCmd3002 (buf: Buffer): HidProxScanRes {
346
    bufIsLenOrFail(buf, 13, 'buf')
×
347
    return bufUnpackToClass(buf, '!BIBIBH', HidProxScanRes)
×
348
  }
349
}
350

351
export class Mf1AcquireStaticEncryptedNestedDecoder implements Mf1AcquireStaticEncryptedNestedRes {
352
  uid: number
353
  atks: Mf1AcquireStaticEncryptedNestedRes['atks']
354

355
  constructor (uid: number, atks: Mf1AcquireStaticEncryptedNestedRes['atks']) {
356
    ;[this.uid, this.atks] = [uid, atks]
×
357
  }
358

359
  static fromCmd2014 (startSector: number, buf: Buffer): Mf1AcquireStaticEncryptedNestedDecoder {
360
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer')
×
361
    return new Mf1AcquireStaticEncryptedNestedDecoder(
×
362
      buf.readUint32BE(0), // uid
363
      _.flatMap(buf.subarray(4).chunk(18), (chunk, i) => [
×
364
        { // key A
365
          sector: startSector + i,
366
          keyType: Mf1KeyType.KEY_A,
367
          nt: chunk.readUint32BE(0),
368
          ntEnc: chunk.readUint32BE(4),
369
          par: chunk[8],
370
        },
371
        { // key B
372
          sector: startSector + i,
373
          keyType: Mf1KeyType.KEY_B,
374
          nt: chunk.readUint32BE(9),
375
          ntEnc: chunk.readUint32BE(13),
376
          par: chunk[17],
377
        },
378
      ])
379
    )
380
  }
381
}
382

383
export class MfuEmuSettings {
384
  detection: boolean
385
  uid: boolean
386
  write: MfuEmuWriteMode
387

388
  constructor (detection: boolean, uid: boolean, write: MfuEmuWriteMode) {
389
    this.detection = detection
×
390
    this.uid = uid
×
391
    this.write = write
×
392
  }
393

394
  static fromCmd4037 (buf: Buffer): MfuEmuSettings {
395
    bufIsLenOrFail(buf, 3, 'buf')
×
396
    return bufUnpackToClass(buf, '!2?B', MfuEmuSettings)
×
397
  }
398
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc