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

taichunmin / chameleon-ultra.js / 20416919018

21 Dec 2025 10:44PM UTC coverage: 68.909% (+0.7%) from 68.228%
20416919018

push

github

web-flow
v0.4.1: support new APIs & npm Trusted Publisher (#209)

293 of 476 branches covered (61.55%)

Branch coverage included in aggregate %.

10 of 61 new or added lines in 3 files covered. (16.39%)

3 existing lines in 1 file now uncovered.

1633 of 2319 relevant lines covered (70.42%)

5261752.24 hits per line

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

83.33
/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 HidProxFormat,
11
  type Mf1EmuWriteMode,
12
  type MfuEmuWriteMode,
13
  type TagType,
14
} from './enums'
15
import { type HidProxTag, type Mf1AcquireStaticEncryptedNestedRes } from './types'
16

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

21
export function bufIsLenOrFail (buf: Buffer, len: number, name: string): void {
22
  if (Buffer.isBuffer(buf) && buf.length === len) return
63✔
23
  throw new TypeError(`${name} must be a ${len} ${['byte', 'bytes'][+(len > 1)]} Buffer.`)
15✔
24
}
25

26
export class SlotInfo {
27
  hfTagType: TagType
28
  lfTagType: TagType
29

30
  constructor (hf: TagType, lf: TagType) {
31
    this.hfTagType = hf
8✔
32
    this.lfTagType = lf
8✔
33
  }
34

35
  static fromCmd1019 (buf: Buffer): SlotInfo[] {
36
    bufIsLenOrFail(buf, 32, 'buf')
3✔
37
    return _.map(buf.chunk(4), chunk => bufUnpackToClass(chunk, '!HH', SlotInfo))
8✔
38
  }
39
}
40

41
export class SlotFreqIsEnable {
42
  hf: boolean
43
  lf: boolean
44

45
  constructor (hf: boolean, lf: boolean) {
46
    ;[this.hf, this.lf] = _.map([hf, lf], Boolean)
8✔
47
  }
48

49
  static fromCmd1023 (buf: Buffer): SlotFreqIsEnable[] {
50
    bufIsLenOrFail(buf, 16, 'buf')
3✔
51
    return _.map(buf.chunk(2), chunk => bufUnpackToClass(chunk, '!??', SlotFreqIsEnable))
8✔
52
  }
53
}
54

55
export class BatteryInfo {
56
  voltage: number
57
  level: number
58

59
  constructor (voltage: number, level: number) {
60
    ;[this.voltage, this.level] = [voltage, level]
1✔
61
  }
62

63
  static fromCmd1025 (buf: Buffer): BatteryInfo {
64
    bufIsLenOrFail(buf, 3, 'buf')
3✔
65
    return bufUnpackToClass(buf, '!HB', BatteryInfo)
3✔
66
  }
67
}
68

69
export class DeviceSettings {
70
  version: number // version of setting
71
  animation: AnimationMode
72
  buttonPressAction: ButtonAction[]
73
  buttonLongPressAction: ButtonAction[]
74
  blePairingMode: boolean
75
  blePairingKey: string
76

77
  constructor (
78
    version: number,
79
    animation: AnimationMode,
80
    btnPressA: ButtonAction,
81
    btnPressB: ButtonAction,
82
    btnLongPressA: ButtonAction,
83
    btnLongPressB: ButtonAction,
84
    blePairingMode: boolean,
85
    blePairingKey: string
86
  ) {
87
    this.version = version
1✔
88
    this.animation = animation
1✔
89
    this.buttonPressAction = [btnPressA, btnPressB]
1✔
90
    this.buttonLongPressAction = [btnLongPressA, btnLongPressB]
1✔
91
    this.blePairingMode = Boolean(blePairingMode)
1✔
92
    this.blePairingKey = blePairingKey
1✔
93
  }
94

95
  static fromCmd1034 (buf: Buffer): DeviceSettings {
96
    bufIsLenOrFail(buf, 13, 'buf')
3✔
97
    return bufUnpackToClass(buf, '!6B?6s', DeviceSettings)
3✔
98
  }
99
}
100

101
/**
102
 * Class for Hf14aAntiColl Decoding.
103
 */
104
export class Hf14aAntiColl {
105
  uid: Buffer
106
  atqa: Buffer
107
  sak: Buffer
108
  ats: Buffer
109

110
  constructor (uid: Buffer, atqa: Buffer, sak: Buffer, ats: Buffer) {
111
    ;[this.uid, this.atqa, this.sak, this.ats] = [uid, atqa, sak, ats]
3✔
112
  }
113

114
  static fromBuffer (buf: Buffer): Hf14aAntiColl {
115
    // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen]
116
    const uidLen = buf[0]
5✔
117
    if (buf.length < uidLen + 4) throw new Error('invalid length of uid')
5✔
118
    const atsLen = buf[uidLen + 4]
4✔
119
    if (buf.length < uidLen + atsLen + 5) throw new Error('invalid length of ats')
4✔
120
    return bufUnpackToClass(buf, `!${uidLen + 1}p2ss${atsLen + 1}p`, Hf14aAntiColl)
3✔
121
  }
122

123
  static fromCmd2000 (buf: Buffer): Hf14aAntiColl[] {
124
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer')
3✔
125
    const tags: Hf14aAntiColl[] = []
2✔
126
    while (buf.length > 0) {
2✔
127
      const tag = Hf14aAntiColl.fromBuffer(buf)
2✔
128
      buf = buf.subarray(tag.uid.length + tag.ats.length + 5)
2✔
129
      tags.push(tag)
2✔
130
    }
131
    return tags
2✔
132
  }
133
}
134

135
export class Mf1AcquireStaticNestedRes {
136
  uid: Buffer
137
  atks: Array<{ nt1: Buffer, nt2: Buffer }>
138

139
  constructor (uid: Buffer, atks: Array<{ nt1: Buffer, nt2: Buffer }>) {
140
    ;[this.uid, this.atks] = [uid, atks]
1✔
141
  }
142

143
  static fromCmd2003 (buf: Buffer): Mf1AcquireStaticNestedRes {
144
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer')
2✔
145
    return new Mf1AcquireStaticNestedRes(
1✔
146
      buf.subarray(0, 4), // uid
147
      _.map(buf.subarray(4).chunk(8), chunk => ({
2✔
148
        nt1: chunk.subarray(0, 4),
149
        nt2: chunk.subarray(4, 8),
150
      })), // atks
151
    )
152
  }
153
}
154

155
export class Mf1DarksideRes {
156
  status: DarksideStatus
157
  uid?: Buffer
158
  nt?: Buffer
159
  par?: Buffer
160
  ks?: Buffer
161
  nr?: Buffer
162
  ar?: Buffer
163

164
  constructor (
165
    status: DarksideStatus,
166
    uid?: Buffer,
167
    nt?: Buffer,
168
    par?: Buffer,
169
    ks?: Buffer,
170
    nr?: Buffer,
171
    ar?: Buffer
172
  ) {
173
    this.status = status
2✔
174
    this.uid = uid
2✔
175
    this.nt = nt
2✔
176
    this.par = par
2✔
177
    this.ks = ks
2✔
178
    this.nr = nr
2✔
179
    this.ar = ar
2✔
180
  }
181

182
  static fromCmd2004 (buf: Buffer): Mf1DarksideRes {
183
    if (!Buffer.isBuffer(buf) || !_.includes([1, 33], buf.length)) throw new TypeError('buf must be a 1 or 33 bytes Buffer.')
4✔
184
    return bufUnpackToClass(buf, buf.length === 1 ? '!B' : '!B4s4s8s8s4s4s', Mf1DarksideRes)
2✔
185
  }
186
}
187

188
export class Mf1NtDistanceRes {
189
  uid: Buffer
190
  dist: Buffer
191

192
  constructor (uid: Buffer, dist: Buffer) {
193
    ;[this.uid, this.dist] = [uid, dist]
1✔
194
  }
195

196
  static fromCmd2005 (buf: Buffer): Mf1NtDistanceRes {
197
    bufIsLenOrFail(buf, 8, 'buf')
3✔
198
    return bufUnpackToClass(buf, '!4s4s', Mf1NtDistanceRes)
3✔
199
  }
200
}
201

202
/** Answer the random number parameters required for Nested attack */
203
export class Mf1NestedRes {
204
  nt1: number // Unblocked explicitly random number
205
  nt2: number // Random number of nested verification encryption
206
  par: number // The 3 parity bit of nested verification encryption
207

208
  constructor (nt1: number, nt2: number, par: number) {
209
    ;[this.nt1, this.nt2, this.par] = [nt1, nt2, par]
2✔
210
  }
211

212
  static fromCmd2006 (buf: Buffer): Mf1NestedRes[] {
213
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer.')
2✔
214
    return _.map(buf.chunk(9), chunk => bufUnpackToClass(chunk, '!IIB', Mf1NestedRes))
2✔
215
  }
216
}
217

218
export class Mf1CheckKeysOfSectorsRes {
219
  found: Buffer
220
  sectorKeys: Array<Buffer | null>
221

222
  constructor (found: Buffer, sectorKeys: Buffer[]) {
223
    this.found = found
3✔
224
    this.sectorKeys = _.times(80, i => found.readBitMSB(i) === 1 ? sectorKeys[i] : null)
240✔
225
  }
226

227
  static fromCmd2012 (buf: Buffer): Mf1CheckKeysOfSectorsRes {
228
    bufIsLenOrFail(buf, 490, 'buf')
5✔
229
    return new Mf1CheckKeysOfSectorsRes(
5✔
230
      buf.subarray(0, 10), // found
231
      buf.subarray(10).chunk(6), // sectorKeys
232
    )
233
  }
234
}
235

236
export class Mf1DetectionLog {
237
  block: number
238
  isKeyB: boolean
239
  isNested: boolean
240
  uid: Buffer
241
  nt: Buffer
242
  nr: Buffer
243
  ar: Buffer
244

245
  constructor (
246
    block: number,
247
    flags: Buffer,
248
    uid: Buffer,
249
    nt: Buffer,
250
    nr: Buffer,
251
    ar: Buffer
252
  ) {
253
    this.block = block
3✔
254
    this.isKeyB = flags.readBitLSB(0) === 1
3✔
255
    this.isNested = flags.readBitLSB(1) === 1
3✔
256
    this.uid = uid
3✔
257
    this.nt = nt
3✔
258
    this.nr = nr
3✔
259
    this.ar = ar
3✔
260
  }
261

262
  static fromBuffer (buf: Buffer): Mf1DetectionLog {
263
    bufIsLenOrFail(buf, 18, 'buf')
5✔
264
    return bufUnpackToClass(buf, '!Bs4s4s4s4s', Mf1DetectionLog)
5✔
265
  }
266

267
  static fromCmd4006 (buf: Buffer): Mf1DetectionLog[] {
268
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer.')
2✔
269
    return _.map(buf.chunk(18), Mf1DetectionLog.fromBuffer)
1✔
270
  }
271
}
272

273
export class Mf1EmuSettings {
274
  detection: boolean
275
  gen1a: boolean
276
  gen2: boolean
277
  antiColl: boolean
278
  write: Mf1EmuWriteMode
279

280
  constructor (detection: boolean, gen1a: boolean, gen2: boolean, antiColl: boolean, write: Mf1EmuWriteMode) {
281
    this.detection = detection
1✔
282
    this.gen1a = gen1a
1✔
283
    this.gen2 = gen2
1✔
284
    this.antiColl = antiColl
1✔
285
    this.write = write
1✔
286
  }
287

288
  static fromCmd4009 (buf: Buffer): Mf1EmuSettings {
289
    bufIsLenOrFail(buf, 5, 'buf')
2✔
290
    return bufUnpackToClass(buf, '!4?B', Mf1EmuSettings)
2✔
291
  }
292
}
293

294
/** Answer the random number parameters required for Hard Nested attack */
295
export class Mf1AcquireHardNestedRes {
296
  nt: number // tag nonce of nested authentication
297
  ntEnc: number // encrypted tag nonce of nested authentication
298
  par: number // The 8 parity bit of nested authentication
299

300
  constructor (nt: number, ntEnc: number, par: number) {
301
    ;[this.nt, this.ntEnc, this.par] = [nt, ntEnc, par]
×
302
  }
303

304
  static fromCmd2013 (buf: Buffer): Mf1AcquireHardNestedRes[] {
305
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer.')
×
306
    return _.map(buf.chunk(9), chunk => bufUnpackToClass(chunk, '!IIB', Mf1AcquireHardNestedRes))
×
307
  }
308
}
309

310
export class HidProxScanRes implements HidProxTag {
311
  format: HidProxFormat
312
  fc: number
313
  cn: number
314
  il: number
315
  oem: number
316

317
  constructor (format: HidProxFormat, fc: number, cn1: number, cn2: number, il: number, oem: number) {
318
    ;[this.format, this.fc, this.cn, this.il, this.oem] = [format, fc, cn1 * 0x100000000 + cn2, il, oem]
×
319
  }
320

321
  static fromCmd3002 (buf: Buffer): HidProxScanRes {
322
    bufIsLenOrFail(buf, 13, 'buf')
×
323
    return bufUnpackToClass(buf, '!BIBIBH', HidProxScanRes)
×
324
  }
325
}
326

327
export class Mf1AcquireStaticEncryptedNestedDecoder implements Mf1AcquireStaticEncryptedNestedRes {
328
  uid: number
329
  atks: Mf1AcquireStaticEncryptedNestedRes['atks']
330

331
  constructor (uid: number, atks: Mf1AcquireStaticEncryptedNestedRes['atks']) {
332
    ;[this.uid, this.atks] = [uid, atks]
×
333
  }
334

335
  static fromCmd2014 (startSector: number, buf: Buffer): Mf1AcquireStaticEncryptedNestedDecoder {
336
    if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a Buffer')
×
337
    return new Mf1AcquireStaticEncryptedNestedDecoder(
×
338
      buf.readUint32BE(0), // uid
339
      _.flatMap(buf.subarray(4).chunk(18), (chunk, i) => [
×
340
        { // key A
341
          sector: startSector + i,
342
          keyType: Mf1KeyType.KEY_A,
343
          nt: chunk.readUint32BE(0),
344
          ntEnc: chunk.readUint32BE(4),
345
          par: chunk[8],
346
        },
347
        { // key B
348
          sector: startSector + i,
349
          keyType: Mf1KeyType.KEY_B,
350
          nt: chunk.readUint32BE(9),
351
          ntEnc: chunk.readUint32BE(13),
352
          par: chunk[17],
353
        },
354
      ])
355
    )
356
  }
357
}
358

359
export class MfuEmuSettings {
360
  detection: boolean
361
  uid: boolean
362
  write: MfuEmuWriteMode
363

364
  constructor (detection: boolean, uid: boolean, write: MfuEmuWriteMode) {
NEW
365
    this.detection = detection
×
NEW
366
    this.uid = uid
×
NEW
367
    this.write = write
×
368
  }
369

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

© 2025 Coveralls, Inc