mirror of
https://github.com/Proxmark/proxmark3.git
synced 2025-08-20 13:23:25 -07:00
commit
380e0d086c
9 changed files with 63 additions and 96 deletions
|
@ -1907,7 +1907,7 @@ int CmdHF14AMfECFill(const char *Cmd)
|
||||||
default: numSectors = 16;
|
default: numSectors = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("--params: numSectors: %d, keyType:%d", numSectors, keyType);
|
printf("--params: numSectors: %d, keyType:%d\n", numSectors, keyType);
|
||||||
UsbCommand c = {CMD_MIFARE_EML_CARDLOAD, {numSectors, keyType, 0}};
|
UsbCommand c = {CMD_MIFARE_EML_CARDLOAD, {numSectors, keyType, 0}};
|
||||||
SendCommand(&c);
|
SendCommand(&c);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
This is a library to read 14443a tags. It can be used something like this
|
This is a library to read 14443a tags. It can be used something like this
|
||||||
|
|
||||||
local reader = require('read14a')
|
local reader = require('read14a')
|
||||||
result, err = reader.read1443a()
|
result, err = reader.read14443a()
|
||||||
if not result then
|
if not result then
|
||||||
print(err)
|
print(err)
|
||||||
return
|
return
|
||||||
|
@ -26,7 +26,7 @@ local ISO14A_COMMAND = {
|
||||||
ISO14A_NO_RATS = 0x200
|
ISO14A_NO_RATS = 0x200
|
||||||
}
|
}
|
||||||
|
|
||||||
local ISO14443a_TYPES = {}
|
local ISO14443a_TYPES = {}
|
||||||
ISO14443a_TYPES[0x00] = "NXP MIFARE Ultralight | Ultralight C"
|
ISO14443a_TYPES[0x00] = "NXP MIFARE Ultralight | Ultralight C"
|
||||||
ISO14443a_TYPES[0x01] = "NXP MIFARE TNP3xxx Activision Game Appliance"
|
ISO14443a_TYPES[0x01] = "NXP MIFARE TNP3xxx Activision Game Appliance"
|
||||||
ISO14443a_TYPES[0x04] = "NXP MIFARE (various !DESFire !DESFire EV1)"
|
ISO14443a_TYPES[0x04] = "NXP MIFARE (various !DESFire !DESFire EV1)"
|
||||||
|
@ -43,14 +43,14 @@ ISO14443a_TYPES[0x88] = "Infineon MIFARE CLASSIC 1K"
|
||||||
ISO14443a_TYPES[0x98] = "Gemplus MPCOS"
|
ISO14443a_TYPES[0x98] = "Gemplus MPCOS"
|
||||||
|
|
||||||
|
|
||||||
local function tostring_1443a(sak)
|
local function tostring_14443a(sak)
|
||||||
return ISO14443a_TYPES[sak] or ("Unknown (SAK=%x)"):format(sak)
|
return ISO14443a_TYPES[sak] or ("Unknown (SAK=%x)"):format(sak)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function parse1443a(data)
|
local function parse14443a(data)
|
||||||
--[[
|
--[[
|
||||||
|
|
||||||
Based on this struct :
|
Based on this struct :
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
byte_t uid[10];
|
byte_t uid[10];
|
||||||
|
@ -66,14 +66,14 @@ local function parse1443a(data)
|
||||||
local count,uid,uidlen, atqa, sak, ats_len, ats= bin.unpack('H10CH2CC',data)
|
local count,uid,uidlen, atqa, sak, ats_len, ats= bin.unpack('H10CH2CC',data)
|
||||||
uid = uid:sub(1,2*uidlen)
|
uid = uid:sub(1,2*uidlen)
|
||||||
--print("uid, atqa, sak: ",uid, atqa, sak)
|
--print("uid, atqa, sak: ",uid, atqa, sak)
|
||||||
--print("TYPE: ", tostring_1443a(sak))
|
--print("TYPE: ", tostring_14443a(sak))
|
||||||
return { uid = uid, atqa = atqa, sak = sak, name = tostring_1443a(sak)}
|
return { uid = uid, atqa = atqa, sak = sak, name = tostring_14443a(sak)}
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Sends a USBpacket to the device
|
--- Sends a USBpacket to the device
|
||||||
-- @param command - the usb packet to send
|
-- @param command - the usb packet to send
|
||||||
-- @param ignoreresponse - if set to true, we don't read the device answer packet
|
-- @param ignoreresponse - if set to true, we don't read the device answer packet
|
||||||
-- which is usually recipe for fail. If not sent, the host will wait 2s for a
|
-- which is usually recipe for fail. If not sent, the host will wait 2s for a
|
||||||
-- response of type CMD_ACK
|
-- response of type CMD_ACK
|
||||||
-- @return packet,nil if successfull
|
-- @return packet,nil if successfull
|
||||||
-- nil, errormessage if unsuccessfull
|
-- nil, errormessage if unsuccessfull
|
||||||
|
@ -99,7 +99,7 @@ end
|
||||||
local function read14443a(dont_disconnect, no_rats)
|
local function read14443a(dont_disconnect, no_rats)
|
||||||
local command, result, info, err, data
|
local command, result, info, err, data
|
||||||
|
|
||||||
command = Command:new{cmd = cmds.CMD_READER_ISO_14443a,
|
command = Command:new{cmd = cmds.CMD_READER_ISO_14443a,
|
||||||
arg1 = ISO14A_COMMAND.ISO14A_CONNECT}
|
arg1 = ISO14A_COMMAND.ISO14A_CONNECT}
|
||||||
if dont_disconnect then
|
if dont_disconnect then
|
||||||
command.arg1 = command.arg1 + ISO14A_COMMAND.ISO14A_NO_DISCONNECT
|
command.arg1 = command.arg1 + ISO14A_COMMAND.ISO14A_NO_DISCONNECT
|
||||||
|
@ -110,24 +110,24 @@ local function read14443a(dont_disconnect, no_rats)
|
||||||
local result,err = sendToDevice(command)
|
local result,err = sendToDevice(command)
|
||||||
if result then
|
if result then
|
||||||
local count,cmd,arg0,arg1,arg2 = bin.unpack('LLLL',result)
|
local count,cmd,arg0,arg1,arg2 = bin.unpack('LLLL',result)
|
||||||
if arg0 == 0 then
|
if arg0 == 0 then
|
||||||
return nil, "iso14443a card select failed"
|
return nil, "iso14443a card select failed"
|
||||||
end
|
end
|
||||||
data = string.sub(result,count)
|
data = string.sub(result,count)
|
||||||
info, err = parse1443a(data)
|
info, err = parse14443a(data)
|
||||||
else
|
else
|
||||||
err ="No response from card"
|
err ="No response from card"
|
||||||
end
|
end
|
||||||
|
|
||||||
if err then
|
if err then
|
||||||
print(err)
|
print(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
end
|
end
|
||||||
return info
|
return info
|
||||||
end
|
end
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Waits for a mifare card to be placed within the vicinity of the reader.
|
-- Waits for a mifare card to be placed within the vicinity of the reader.
|
||||||
-- @return if successfull: an table containing card info
|
-- @return if successfull: an table containing card info
|
||||||
-- @return if unsuccessfull : nil, error
|
-- @return if unsuccessfull : nil, error
|
||||||
local function waitFor14443a()
|
local function waitFor14443a()
|
||||||
|
@ -139,14 +139,14 @@ local function waitFor14443a()
|
||||||
end
|
end
|
||||||
return nil, "Aborted by user"
|
return nil, "Aborted by user"
|
||||||
end
|
end
|
||||||
|
|
||||||
local library = {
|
local library = {
|
||||||
|
read14443a = read14443a,
|
||||||
read1443a = read14443a,
|
read = read14443a,
|
||||||
read = read14443a,
|
|
||||||
waitFor14443a = waitFor14443a,
|
waitFor14443a = waitFor14443a,
|
||||||
parse1443a = parse1443a,
|
parse14443a = parse14443a,
|
||||||
sendToDevice = sendToDevice,
|
sendToDevice = sendToDevice,
|
||||||
ISO14A_COMMAND = ISO14A_COMMAND,
|
ISO14A_COMMAND = ISO14A_COMMAND,
|
||||||
}
|
}
|
||||||
|
|
||||||
return library
|
return library
|
||||||
|
|
|
@ -106,7 +106,7 @@ function main(args)
|
||||||
dbg("doconnect")
|
dbg("doconnect")
|
||||||
-- We reuse the connect functionality from a
|
-- We reuse the connect functionality from a
|
||||||
-- common library
|
-- common library
|
||||||
info, err = lib14a.read1443a(true, no_rats)
|
info, err = lib14a.read14443a(true, no_rats)
|
||||||
|
|
||||||
if err then return oops(err) end
|
if err then return oops(err) end
|
||||||
print(("Connected to card, uid = %s"):format(info.uid))
|
print(("Connected to card, uid = %s"):format(info.uid))
|
||||||
|
|
|
@ -406,7 +406,7 @@ function main(args)
|
||||||
|
|
||||||
-- GET TAG UID
|
-- GET TAG UID
|
||||||
|
|
||||||
result, err = lib14a.read1443a(false, true)
|
result, err = lib14a.read14443a(false, true)
|
||||||
if not result then
|
if not result then
|
||||||
return oops(err)
|
return oops(err)
|
||||||
end
|
end
|
||||||
|
|
|
@ -71,7 +71,7 @@ end
|
||||||
--
|
--
|
||||||
-- Read information from a card
|
-- Read information from a card
|
||||||
function GetCardInfo()
|
function GetCardInfo()
|
||||||
result, err = lib14a.read1443a(false, true)
|
result, err = lib14a.read14443a(false, true)
|
||||||
if not result then
|
if not result then
|
||||||
print(err)
|
print(err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
--[[
|
--[[
|
||||||
This is an example of Lua-scripting within proxmark3. This is a lua-side
|
This is an example of Lua-scripting within proxmark3. This is a lua-side
|
||||||
implementation of hf mf chk
|
implementation of hf mf chk
|
||||||
|
|
||||||
This code is licensed to you under the terms of the GNU GPL, version 2 or,
|
This code is licensed to you under the terms of the GNU GPL, version 2 or,
|
||||||
at your option, any later version. See the LICENSE.txt file for the text of
|
at your option, any later version. See the LICENSE.txt file for the text of
|
||||||
the license.
|
the license.
|
||||||
|
|
||||||
Copyright (C) 2013 m h swende <martin at swende.se>
|
Copyright (C) 2013 m h swende <martin at swende.se>
|
||||||
--]]
|
--]]
|
||||||
-- Loads the commands-library
|
-- Loads the commands-library
|
||||||
|
@ -14,49 +14,15 @@ local cmds = require('commands')
|
||||||
local keys = require('mf_default_keys')
|
local keys = require('mf_default_keys')
|
||||||
-- Ability to read what card is there
|
-- Ability to read what card is there
|
||||||
local reader = require('read14a')
|
local reader = require('read14a')
|
||||||
|
-- Asks the user for input
|
||||||
|
local utils = require('utils')
|
||||||
|
|
||||||
|
|
||||||
local desc =
|
local desc = ("This script implements check keys. \
|
||||||
("This script implements check keys. It utilises a large list of default keys (currently %d keys).\
|
It utilises a large list of default keys (currently %d keys).\
|
||||||
If you want to add more, just put them inside mf_default_keys.lua. "):format(#keys)
|
If you want to add more, just put them inside mf_default_keys.lua. "):format(#keys)
|
||||||
|
|
||||||
local TIMEOUT = 10000 -- 10 seconds
|
local TIMEOUT = 10000 -- 10 seconds
|
||||||
|
|
||||||
--[[This may be moved to a separate library at some point]]
|
|
||||||
local utils =
|
|
||||||
{
|
|
||||||
---
|
|
||||||
-- Asks the user for Yes or No
|
|
||||||
confirm = function(message, ...)
|
|
||||||
local answer
|
|
||||||
message = message .. " [y]/[n] ?"
|
|
||||||
repeat
|
|
||||||
io.write(message)
|
|
||||||
io.flush()
|
|
||||||
answer=io.read()
|
|
||||||
if answer == 'Y' or answer == "y" then
|
|
||||||
return true
|
|
||||||
elseif answer == 'N' or answer == 'n' then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
until false
|
|
||||||
end,
|
|
||||||
---
|
|
||||||
-- Asks the user for input
|
|
||||||
input = function (message , default)
|
|
||||||
local answer
|
|
||||||
if default ~= nil then
|
|
||||||
message = message .. " (default: ".. default.. " )"
|
|
||||||
end
|
|
||||||
message = message .." \n > "
|
|
||||||
io.write(message)
|
|
||||||
io.flush()
|
|
||||||
answer=io.read()
|
|
||||||
if answer == '' then answer = default end
|
|
||||||
|
|
||||||
return answer
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
local function checkCommand(command)
|
local function checkCommand(command)
|
||||||
|
@ -83,7 +49,7 @@ end
|
||||||
|
|
||||||
|
|
||||||
function checkBlock(blockNo, keys, keyType)
|
function checkBlock(blockNo, keys, keyType)
|
||||||
-- The command data is only 512 bytes, each key is 6 bytes, meaning that we can send max 85 keys in one go.
|
-- The command data is only 512 bytes, each key is 6 bytes, meaning that we can send max 85 keys in one go.
|
||||||
-- If there's more, we need to split it up
|
-- If there's more, we need to split it up
|
||||||
local start, remaining= 1, #keys
|
local start, remaining= 1, #keys
|
||||||
local arg1 = bit32.bor(bit32.lshift(keyType, 8), blockNo)
|
local arg1 = bit32.bor(bit32.lshift(keyType, 8), blockNo)
|
||||||
|
@ -95,10 +61,10 @@ function checkBlock(blockNo, keys, keyType)
|
||||||
--print("data",data)
|
--print("data",data)
|
||||||
--print("data len", #data)
|
--print("data len", #data)
|
||||||
print(("Testing block %d, keytype %d, with %d keys"):format(blockNo, keyType, n))
|
print(("Testing block %d, keytype %d, with %d keys"):format(blockNo, keyType, n))
|
||||||
local command = Command:new{cmd = cmds.CMD_MIFARE_CHKKEYS,
|
local command = Command:new{cmd = cmds.CMD_MIFARE_CHKKEYS,
|
||||||
arg1 = arg1,
|
arg1 = arg1,
|
||||||
arg2 = 1,
|
arg2 = 1,
|
||||||
arg3 = n,
|
arg3 = n,
|
||||||
data = data}
|
data = data}
|
||||||
local status = checkCommand(command)
|
local status = checkCommand(command)
|
||||||
if status then return status, blockNo end
|
if status then return status, blockNo end
|
||||||
|
@ -119,41 +85,43 @@ local function displayresults(results)
|
||||||
for sector,_ in pairs(results) do
|
for sector,_ in pairs(results) do
|
||||||
blockNo, keyA, keyB = unpack(_)
|
blockNo, keyA, keyB = unpack(_)
|
||||||
|
|
||||||
print(("| %3d | %3d |%s|%s|"):format(sector, blockNo, keyA, keyB ))
|
print(("| %3d | %3d |%12s|%12s|"):format(sector, blockNo, keyA, keyB))
|
||||||
end
|
end
|
||||||
print("|--------------------------------------|")
|
print("|--------------------------------------|")
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- A little helper to place an item first in the list
|
-- A little helper to place an item first in the list
|
||||||
local function placeFirst(akey, list)
|
local function placeFirst(akey, list)
|
||||||
akey = akey:lower()
|
akey = akey:lower()
|
||||||
if list[1] == akey then
|
if list[1] == akey then
|
||||||
-- Already at pole position
|
-- Already at pole position
|
||||||
return list
|
return list
|
||||||
end
|
end
|
||||||
local result = {akey}
|
local result = {akey}
|
||||||
--print(("Putting '%s' first"):format(akey))
|
--print(("Putting '%s' first"):format(akey))
|
||||||
for i,v in ipairs(list) do
|
for i,v in ipairs(list) do
|
||||||
if v ~= akey then
|
if v ~= akey then
|
||||||
result[#result+1] = v
|
result[#result+1] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
local function dumptofile(results)
|
local function dumptofile(results)
|
||||||
local sector, blockNo, keyA, keyB,_
|
local sector, blockNo, keyA, keyB,_
|
||||||
|
|
||||||
if utils.confirm("Do you wish to save the keys to dumpfile?") then
|
if utils.confirm("Do you wish to save the keys to dumpfile?") then
|
||||||
local destination = utils.input("Select a filename to store to", "dumpkeys.bin")
|
local destination = utils.input("Select a filename to store to", "dumpkeys.bin")
|
||||||
local file = io.open(destination, "w")
|
local file = io.open(destination, "w")
|
||||||
if file == nil then
|
if file == nil then
|
||||||
print("Could not write to file ", destination)
|
print("Could not write to file ", destination)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local key_a = ""
|
local key_a = ""
|
||||||
local key_b = ""
|
local key_b = ""
|
||||||
|
|
||||||
for sector,_ in pairs(results) do
|
for sector,_ in pairs(results) do
|
||||||
blockNo, keyA, keyB = unpack(_)
|
blockNo, keyA, keyB = unpack(_)
|
||||||
key_a = key_a .. bin.pack("H",keyA);
|
key_a = key_a .. bin.pack("H",keyA);
|
||||||
|
@ -166,11 +134,11 @@ local function dumptofile(results)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function main( args)
|
local function main(args)
|
||||||
|
|
||||||
print(desc);
|
print(desc);
|
||||||
|
|
||||||
result, err = reader.read1443a(false, true)
|
result, err = reader.read14443a(false, true)
|
||||||
if not result then
|
if not result then
|
||||||
print(err)
|
print(err)
|
||||||
return
|
return
|
||||||
|
@ -181,11 +149,11 @@ local function main( args)
|
||||||
core.clearCommandBuffer()
|
core.clearCommandBuffer()
|
||||||
local blockNo
|
local blockNo
|
||||||
local keyType = 0 -- A=0, B=1
|
local keyType = 0 -- A=0, B=1
|
||||||
local numSectors = 16
|
local numSectors = 16
|
||||||
|
|
||||||
if 0x18 == result.sak then --NXP MIFARE Classic 4k | Plus 4k
|
if 0x18 == result.sak then -- NXP MIFARE Classic 4k | Plus 4k
|
||||||
-- IFARE Classic 4K offers 4096 bytes split into forty sectors,
|
-- IFARE Classic 4K offers 4096 bytes split into forty sectors,
|
||||||
-- of which 32 are same size as in the 1K with eight more that are quadruple size sectors.
|
-- of which 32 are same size as in the 1K with eight more that are quadruple size sectors.
|
||||||
numSectors = 40
|
numSectors = 40
|
||||||
elseif 0x08 == result.sak then -- NXP MIFARE CLASSIC 1k | Plus 2k
|
elseif 0x08 == result.sak then -- NXP MIFARE CLASSIC 1k | Plus 2k
|
||||||
-- 1K offers 1024 bytes of data storage, split into 16 sector
|
-- 1K offers 1024 bytes of data storage, split into 16 sector
|
||||||
|
@ -193,7 +161,7 @@ local function main( args)
|
||||||
elseif 0x09 == result.sak then -- NXP MIFARE Mini 0.3k
|
elseif 0x09 == result.sak then -- NXP MIFARE Mini 0.3k
|
||||||
-- MIFARE Classic mini offers 320 bytes split into five sectors.
|
-- MIFARE Classic mini offers 320 bytes split into five sectors.
|
||||||
numSectors = 5
|
numSectors = 5
|
||||||
elseif 0x10 == result.sak then-- "NXP MIFARE Plus 2k"
|
elseif 0x10 == result.sak then -- NXP MIFARE Plus 2k
|
||||||
numSectors = 32
|
numSectors = 32
|
||||||
else
|
else
|
||||||
print("I don't know how many sectors there are on this type of card, defaulting to 16")
|
print("I don't know how many sectors there are on this type of card, defaulting to 16")
|
||||||
|
@ -203,25 +171,25 @@ local function main( args)
|
||||||
for sector=1,numSectors,1 do
|
for sector=1,numSectors,1 do
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
The mifare Classic 1k card has 16 sectors of 4 data blocks each.
|
The mifare Classic 1k card has 16 sectors of 4 data blocks each.
|
||||||
The first 32 sectors of a mifare Classic 4k card consists of 4 data blocks and the remaining
|
The first 32 sectors of a mifare Classic 4k card consists of 4 data blocks and the remaining
|
||||||
8 sectors consist of 16 data blocks.
|
8 sectors consist of 16 data blocks.
|
||||||
--]]
|
--]]
|
||||||
local blockNo = sector * 4 -1
|
local blockNo = sector * 4 - 1
|
||||||
|
|
||||||
if sector > 32 then
|
if sector > 32 then
|
||||||
blockNo = 32*4+ (sector-32)*16 -1
|
blockNo = 32*4 + (sector-32)*16 - 1
|
||||||
end
|
end
|
||||||
|
|
||||||
local keyA = checkBlock(blockNo, keys, 0)
|
local keyA = checkBlock(blockNo, keys, 0)
|
||||||
if keyA then keys = placeFirst(keyA, keys) end
|
if keyA then keys = placeFirst(keyA, keys) end
|
||||||
keyA = keyA or ""
|
keyA = keyA or ""
|
||||||
|
|
||||||
local keyB = checkBlock(blockNo, keys, 1)
|
local keyB = checkBlock(blockNo, keys, 1)
|
||||||
if keyB then keys = placeFirst(keyB, keys) end
|
if keyB then keys = placeFirst(keyB, keys) end
|
||||||
keyB = keyB or ""
|
keyB = keyB or ""
|
||||||
|
|
||||||
result[sector] = {blockNo, keyA, keyB }
|
result[sector] = {blockNo, keyA, keyB}
|
||||||
|
|
||||||
-- Check if user aborted
|
-- Check if user aborted
|
||||||
if core.ukbhit() then
|
if core.ukbhit() then
|
||||||
|
@ -233,5 +201,4 @@ local function main( args)
|
||||||
dumptofile(result)
|
dumptofile(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
main( args)
|
main(args)
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ end
|
||||||
-- @return if unsuccessfull : nil, error
|
-- @return if unsuccessfull : nil, error
|
||||||
function wait_for_mifare()
|
function wait_for_mifare()
|
||||||
while not core.ukbhit() do
|
while not core.ukbhit() do
|
||||||
res, err = reader.read1443a(false, true)
|
res, err = reader.read14443a(false, true)
|
||||||
if res then return res end
|
if res then return res end
|
||||||
-- err means that there was no response from card
|
-- err means that there was no response from card
|
||||||
end
|
end
|
||||||
|
|
|
@ -122,7 +122,7 @@ local function main(args)
|
||||||
|
|
||||||
|
|
||||||
-- find tag
|
-- find tag
|
||||||
result, err = lib14a.read1443a(false, true)
|
result, err = lib14a.read14443a(false, true)
|
||||||
if not result then return oops(err) end
|
if not result then return oops(err) end
|
||||||
|
|
||||||
-- load keys
|
-- load keys
|
||||||
|
|
|
@ -127,7 +127,7 @@ local function main(args)
|
||||||
local cmdSetDbgOff = "hf mf dbg 0"
|
local cmdSetDbgOff = "hf mf dbg 0"
|
||||||
core.console( cmdSetDbgOff)
|
core.console( cmdSetDbgOff)
|
||||||
|
|
||||||
result, err = lib14a.read1443a(false, true)
|
result, err = lib14a.read14443a(false, true)
|
||||||
if not result then
|
if not result then
|
||||||
return oops(err)
|
return oops(err)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue