-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added support for mifare classic value block operations (#86)
- Loading branch information
Showing
4 changed files
with
391 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ | |
"name": "chameleon-ultra.js", | ||
"repository": "[email protected]:taichunmin/chameleon-ultra.js.git", | ||
"unpkg": "dist/iife/index.min.js", | ||
"version": "0.2.14", | ||
"version": "0.2.15", | ||
"bugs": { | ||
"url": "https://github.com/taichunmin/chameleon-ultra.js/issues" | ||
}, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
extends /include/bootstrapV4 | ||
|
||
block beforehtml | ||
- const title = 'Mifare Value Block' | ||
|
||
block style | ||
meta(property="og:description", content="MIFARE Classic value block commands") | ||
meta(property="og:locale", content="zh_TW") | ||
meta(property="og:title", content=title) | ||
meta(property="og:type", content="website") | ||
meta(property="og:url", content=`${baseurl}mifare-value.html`) | ||
style | ||
:sass | ||
[v-cloak] | ||
display: none | ||
body, .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 | ||
font-family: 'Noto Sans TC', sans-serif | ||
.input-group-prepend > .input-group-text | ||
width: 80px | ||
.letter-spacing-n1px | ||
&, .btn, textarea, select, input | ||
letter-spacing: -1px | ||
.text-sm | ||
font-size: 0.875rem | ||
block content | ||
#app.my-3.container.text-monospace(v-cloak) | ||
h4.mb-3.text-center.letter-spacing-n1px #[.bgicon.bgicon-chameleon-ultra.mr-1] #{title} | ||
.form-group.letter-spacing-n1px | ||
label Connect method: | ||
.input-group.input-group-sm.mb-3 | ||
select.form-control(v-model="ls.adapter") | ||
option(value="ble") BLE (PC & Android) | ||
option(value="usb") USB Serial (PC only) | ||
.input-group-append: button.btn.btn-outline-secondary(@click="btnAdapterTips") #[i.fa.fa-fw.fa-question] | ||
.form-group.letter-spacing-n1px | ||
label Value Block: Source | ||
.input-group.input-group-sm.mb-2.was-validated | ||
.input-group-prepend: span.input-group-text.justify-content-center Block | ||
input.form-control(type="number", min="0", max="63", required, v-model.number="ss.src.block") | ||
.input-group-append: span.input-group-text Sector {{ blockToSector(ss?.src?.block ?? 0) }} | ||
.input-group.input-group-sm.mb-2 | ||
.input-group-prepend: span.input-group-text.justify-content-center KeyType | ||
select.form-control.form-control-sm(v-model.number="ss.src.keyType") | ||
option(value="0") Key A | ||
option(value="1") Key B | ||
.input-group.input-group-sm.mb-2.was-validated | ||
.input-group-prepend: span.input-group-text.justify-content-center Key | ||
input.form-control(type="text", required, pattern="[\\dA-Fa-f]{12}", v-model="ss.src.key") | ||
.form-group.letter-spacing-n1px | ||
label Value Block: Target | ||
.custom-control.custom-checkbox.mb-2 | ||
input.custom-control-input#ss-srcAsDst(type="checkbox", v-model="ss.srcAsDst") | ||
label.custom-control-label(for="ss-srcAsDst") Use source as target | ||
template(v-if="ss.srcAsDst") | ||
.input-group.input-group-sm.mb-2 | ||
.input-group-prepend: span.input-group-text.justify-content-center Block | ||
input.form-control(type="number", v-model.number="ss.src.block", disabled) | ||
.input-group-append: span.input-group-text Sector {{ blockToSector(ss?.src?.block ?? 0) }} | ||
.input-group.input-group-sm.mb-2 | ||
.input-group-prepend: span.input-group-text.justify-content-center KeyType | ||
select.form-control.form-control-sm(v-model.number="ss.src.keyType", disabled) | ||
option(value="0") Key A | ||
option(value="1") Key B | ||
.input-group.input-group-sm.mb-2 | ||
.input-group-prepend: span.input-group-text.justify-content-center Key | ||
input.form-control(type="text", v-model="ss.src.key", disabled) | ||
template(v-else) | ||
.input-group.input-group-sm.mb-2.was-validated | ||
.input-group-prepend: span.input-group-text.justify-content-center Block | ||
input.form-control(type="number", min="0", max="63", required, v-model.number="ss.dst.block") | ||
.input-group-append: span.input-group-text Sector {{ blockToSector(ss?.dst?.block ?? 0) }} | ||
.input-group.input-group-sm.mb-2 | ||
.input-group-prepend: span.input-group-text.justify-content-center KeyType | ||
select.form-control.form-control-sm(v-model.number="ss.dst.keyType") | ||
option(value="0") Key A | ||
option(value="1") Key B | ||
.input-group.input-group-sm.mb-2.was-validated | ||
.input-group-prepend: span.input-group-text.justify-content-center Key | ||
input.form-control(type="text", required, pattern="[\\dA-Fa-f]{12}", v-model="ss.dst.key") | ||
.form-group.letter-spacing-n1px | ||
label Value Block: Operation | ||
.input-group.input-group-sm.mb-2.was-validated | ||
.input-group-prepend: span.input-group-text.justify-content-center Value | ||
input.form-control(type="number", required, v-model.number="ss.operand") | ||
.row.mx-n1.mb-2 | ||
.col.px-1: button.btn.btn-sm.btn-block.btn-outline-success(@click="btnGetValue") #[i.fa.fa-fw.fa-sign-out] Get Source | ||
.col.px-1: button.btn.btn-sm.btn-block.btn-outline-primary(@click="btnSetValue") #[i.fa.fa-fw.fa-sign-in] Set Source | ||
button.btn.btn-sm.btn-block.btn-outline-dark.mb-2(@click="btnManipulateValue(Mf1VblockOperator.INCREMENT)") #[i.fa.mr-1.fa-plus-square] Increase Value | ||
button.btn.btn-sm.btn-block.btn-outline-dark.mb-2(@click="btnManipulateValue(Mf1VblockOperator.DECREMENT)") #[i.fa.mr-1.fa-minus-square] Decrease Value | ||
button.btn.btn-sm.btn-block.btn-outline-dark.mb-2(@click="btnManipulateValue(Mf1VblockOperator.RESTORE)") #[i.fa.mr-1.fa-clone] Clone Value | ||
|
||
block script | ||
script(crossorigin="anonymous", src="https://cdn.jsdelivr.net/npm/joi@17/dist/joi-browser.min.js") | ||
script. | ||
const { Buffer, ChameleonUltra, DeviceMode, Mf1KeyType, Mf1VblockOperator, WebbleAdapter, WebserialAdapter } = ChameleonUltraJS // eslint-disable-line | ||
const ultraUsb = new ChameleonUltra(true) | ||
ultraUsb.use(new WebserialAdapter()) | ||
const ultraBle = new ChameleonUltra(true) | ||
ultraBle.use(new WebbleAdapter()) | ||
|
||
window.vm = new Vue({ | ||
el: '#app', | ||
data: { | ||
ls: { | ||
adapter: 'ble', | ||
}, | ||
ss: { | ||
src: { block: 4, keyType: 0, key: 'FFFFFFFFFFFF' }, | ||
dst: { block: 4, keyType: 0, key: 'FFFFFFFFFFFF' }, | ||
operand: 0, | ||
srcAsDst: true, | ||
}, | ||
}, | ||
async mounted () { | ||
// 自動儲存功能 | ||
for (const [storage, key] of [[localStorage, 'ls'], [sessionStorage, 'ss']]) { | ||
try { | ||
const saved = JSON5.parse(storage.getItem(location.pathname)) | ||
if (saved) this.$set(this, key, _.merge(this[key], saved)) | ||
} catch (err) {} | ||
this.$watch(key, () => { | ||
storage.setItem(location.pathname, JSON5.stringify(this[key])) | ||
}, { deep: true }) | ||
} | ||
}, | ||
computed: { | ||
ultra () { | ||
return this.ls.adapter === 'usb' ? ultraUsb : ultraBle | ||
}, | ||
}, | ||
methods: { | ||
async btnAdapterTips () { | ||
await Swal.fire({ | ||
title: 'Browser & OS', | ||
html: '<strong class="text-success">BLE</strong> is available in ChromeOS, Chrome for Android 6.0, Mac (Chrome 56) and Windows 10 (Chrome 70), <a class="btn-link" target="_blank" href="https://apps.apple.com/app/bluefy-web-ble-browser/id1492822055">Bluefy</a> for iPhone and iPad.<hr><strong class="text-success">USB</strong> is available on all desktop platforms (ChromeOS, Linux, macOS, and Windows) in Chrome 89.', | ||
}) | ||
}, | ||
async cmdVblockGet (src) { | ||
src = this.mf1ParseBlockKey(src) | ||
const ultra = this.ultra | ||
await ultra.cmdChangeDeviceMode(DeviceMode.READER) | ||
const res = await ultra.mf1VblockGetValue(src) | ||
await Swal.fire({ icon: 'success', title: 'Success', text: `block[${src.block}] = ${JSON.stringify(res)}` }) | ||
}, | ||
async btnGetValue () { | ||
try { | ||
this.showLoading({ text: 'Processing...' }) | ||
await this.cmdVblockGet(this.ss.src) | ||
} catch (err) { | ||
console.error(err) | ||
await Swal.fire({ icon: 'error', title: 'Failed to get value from src', text: err.message }) | ||
} | ||
}, | ||
async btnSetValue () { | ||
try { | ||
this.showLoading({ text: 'Processing...' }) | ||
const src = this.mf1ParseBlockKey(this.ss.src) | ||
const ultra = this.ultra | ||
await ultra.cmdChangeDeviceMode(DeviceMode.READER) | ||
await ultra.mf1VblockSetValue(src, { adr: src.block, value: this.ss.operand }) | ||
await this.cmdVblockGet(this.ss.src) | ||
} catch (err) { | ||
console.error(err) | ||
await Swal.fire({ icon: 'error', title: 'Failed to set value to src', text: err.message }) | ||
} | ||
}, | ||
async btnManipulateValue (operator) { | ||
try { | ||
this.showLoading({ text: 'Processing...' }) | ||
const src = this.mf1ParseBlockKey(this.ss.src) | ||
const dst = this.ss.srcAsDst ? src : this.mf1ParseBlockKey(this.ss.dst) | ||
const operand = this.ss.operand | ||
const ultra = this.ultra | ||
await ultra.cmdChangeDeviceMode(DeviceMode.READER) | ||
await ultra.cmdMf1VblockManipulate(src, operator, operand, dst) | ||
await this.cmdVblockGet(this.ss.src) | ||
} catch (err) { | ||
console.error(err) | ||
await Swal.fire({ icon: 'error', title: `Failed to ${Mf1VblockOperator[operator]} value`, text: err.message }) | ||
} | ||
}, | ||
mf1ParseBlockKey ({ block, keyType, key }) { | ||
return { | ||
block, | ||
keyType: keyType === 1 ? Mf1KeyType.KEY_B : Mf1KeyType.KEY_A, | ||
key: Buffer.from(key, 'hex'), | ||
} | ||
}, | ||
blockToSector (block = 0) { | ||
return block < 128 ? block >>> 2 : 24 + (block >>> 4) | ||
}, | ||
showLoading (opts = {}) { | ||
opts = { | ||
allowOutsideClick: false, | ||
showConfirmButton: false, | ||
...opts, | ||
} | ||
if (Swal.isVisible()) return Swal.update(_.omit(opts, ['progressStepsDistance'])) | ||
Swal.fire({ ...opts, didRender: () => { Swal.showLoading() } }) | ||
}, | ||
}, | ||
}) | ||
|
Oops, something went wrong.