Merge pull request #2786 from jareckib/master

paxton_clone.lua
This commit is contained in:
Iceman 2025-03-18 07:43:50 +01:00 committed by GitHub
commit 0446509d1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 555 additions and 57 deletions

View file

@ -1,4 +1,3 @@
local getopt = require('getopt') local getopt = require('getopt')
local utils = require('utils') local utils = require('utils')
local ac = require('ansicolors') local ac = require('ansicolors')
@ -8,11 +7,14 @@ local dir = os.getenv('HOME') .. '/.proxmark3/logs/'
local logfile = (io.popen('dir /a-d /o-d /tw /b/s "' .. dir .. '" 2>nul:'):read("*a"):match("%C+")) local logfile = (io.popen('dir /a-d /o-d /tw /b/s "' .. dir .. '" 2>nul:'):read("*a"):match("%C+"))
local log_file_path = dir .. "Paxton_log.txt" local log_file_path = dir .. "Paxton_log.txt"
local nam = "" local nam = ""
local pm3 = require('pm3')
p = pm3.pm3()
local command = core.console local command = core.console
command('clear')
author = ' Author: jareckib - 30.01.2025' author = ' Author: jareckib - 30.01.2025'
tutorial = ' Based on Equipter tutorial - Downgrade Paxton to EM4102' tutorial = ' Based on Equipter tutorial - Downgrade Paxton to EM4102'
version = ' version v1.18' version = ' version v1.19'
desc = [[ desc = [[
The script automates the copying of Paxton fobs read - write. The script automates the copying of Paxton fobs read - write.
It also allows manual input of data for blocks 4-7. It also allows manual input of data for blocks 4-7.
@ -228,13 +230,10 @@ end
local function handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3) local function handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)
while true do while true do
print(" Create Paxton choose " .. ac.cyan .. "1" .. ac.reset .. " or EM4102 choose " .. ac.cyan .. "2" .. ac.reset) io.write(" Create Paxton choose " .. ac.cyan .. "1" .. ac.reset .. " or EM4102 choose " .. ac.cyan .. "2 " .. ac.reset)
print(dash)
io.write(" Your choice "..ac.cyan.."(1/2): "..ac.reset)
local choice = io.read() local choice = io.read()
if choice == "1" then if choice == "1" then
print(dash) io.write(" Place the" .. ac.cyan .. " Paxton " .. ac.reset .. "Fob on the coil to write.." .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
print(" Place the" .. ac.cyan .. " Paxton " .. ac.reset .. "Fob on the coil to write.." .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.read() io.read()
print(dash) print(dash)
command("lf hitag wrbl --ht2 -p 4 -d " .. blocks[4] .. " -k BDF5E846") command("lf hitag wrbl --ht2 -p 4 -d " .. blocks[4] .. " -k BDF5E846")
@ -242,20 +241,18 @@ local function handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)
command("lf hitag wrbl --ht2 -p 6 -d " .. blocks[6] .. " -k BDF5E846") command("lf hitag wrbl --ht2 -p 6 -d " .. blocks[6] .. " -k BDF5E846")
command("lf hitag wrbl --ht2 -p 7 -d " .. blocks[7] .. " -k BDF5E846") command("lf hitag wrbl --ht2 -p 7 -d " .. blocks[7] .. " -k BDF5E846")
elseif choice == "2" then elseif choice == "2" then
print(dash) io.write(" Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
print(" Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.read() io.read()
print(dash) p:console("lf em 410x clone --id " .. padded_hex_id)
command("lf em 410x clone --id " .. padded_hex_id) print(' Cloned EM4102 to T5577 with ID ' ..ac.green.. padded_hex_id ..ac.reset)
else else
print(ac.yellow .. " Invalid choice." .. ac.reset .. " Please enter " .. ac.cyan .. "1" .. ac.reset .. " or " .. ac.cyan .. "2" .. ac.reset) print(ac.yellow .. " Invalid choice." .. ac.reset .. " Please enter " .. ac.cyan .. "1" .. ac.reset .. " or " .. ac.cyan .. "2" .. ac.reset)
goto ask_again goto ask_again
end end
while true do while true do
print(dash) print(dash)
io.write(" Make next RFID Fob"..ac.cyan.." (y/n)"..ac.reset.." > "..ac.yellow) io.write(" Make next RFID Fob"..ac.cyan.." (y/n) "..ac.reset)
local another = io.read() local another = io.read()
io.write(ac.reset..'')
if another:lower() == "n" then if another:lower() == "n" then
if was_option_3 then if was_option_3 then
print(" No writing to Paxton_log.txt - Name: " ..ac.green.. nam .. ac.reset.. " exist") print(" No writing to Paxton_log.txt - Name: " ..ac.green.. nam .. ac.reset.. " exist")
@ -303,7 +300,6 @@ local function main(args)
if o == 'h' then return help() end if o == 'h' then return help() end
end end
command('clear') command('clear')
print()
print(dash) print(dash)
print(ac.green .. ' Select option: ' .. ac.reset) print(ac.green .. ' Select option: ' .. ac.reset)
print(ac.cyan .. ' 1' .. ac.reset .. ' - Read Paxton blocks 4-7 to make a copy') print(ac.cyan .. ' 1' .. ac.reset .. ' - Read Paxton blocks 4-7 to make a copy')
@ -324,12 +320,11 @@ local function main(args)
local show_place_message = true local show_place_message = true
while true do while true do
if show_place_message then if show_place_message then
print(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..') io.write(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..')
print(dash)
end end
io.read() io.read()
command('lf hitag read --ht2 -k BDF5E846') print(dash)
command('clear') p:console('lf hitag read --ht2 -k BDF5E846')
if not logfile then if not logfile then
error(" No files in this directory") error(" No files in this directory")
end end
@ -343,12 +338,11 @@ local function main(args)
end end
end end
if empty_block then if empty_block then
print(dash) io.write(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..')
print(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..')
print(dash)
show_place_message = false show_place_message = false
else else
print(dash) print(' Readed blocks:')
print()
for i = 4, 7 do for i = 4, 7 do
if blocks[i] then if blocks[i] then
print(string.format(" Block %d: %s%s%s", i, ac.yellow, blocks[i], ac.reset)) print(string.format(" Block %d: %s%s%s", i, ac.yellow, blocks[i], ac.reset))
@ -364,7 +358,6 @@ local function main(args)
print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset) print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset)
decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]}) decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]})
end end
print(dash)
print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset)) print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset))
print(dash) print(dash)
handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3) handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)

View file

@ -5,17 +5,20 @@ local os = require('os')
local dash = string.rep('--', 32) local dash = string.rep('--', 32)
local dir = os.getenv('HOME') .. '/.proxmark3/logs/' local dir = os.getenv('HOME') .. '/.proxmark3/logs/'
local logfile = (io.popen('dir /a-d /o-d /tw /b/s "' .. dir .. '" 2>nul:'):read("*a"):match("%C+")) local logfile = (io.popen('dir /a-d /o-d /tw /b/s "' .. dir .. '" 2>nul:'):read("*a"):match("%C+"))
local pm3 = require('pm3')
p = pm3.pm3()
local command = core.console local command = core.console
command('clear')
author = ' Author: jareckib - 15.02.2025' author = ' Author: jareckib - 15.02.2025'
version = ' version v1.00' version = ' version v1.02'
desc = [[ desc = [[
This simple script first checks if a password has been set for the T5577. This simple script first checks if a password has been set for the T5577.
It uses the dictionary t55xx_default_pwds.dic for this purpose. If a password It uses the dictionary t55xx_default_pwds.dic for this purpose. If a password
is found, it uses the wipe command to erase the T5577. Then the reanimation is found, it uses the wipe command to erase the T5577. Then the reanimation
procedure is applied. If the password is not found or doesn't exist the script procedure is applied. If the password is not found or doesn't exist the script
only performs the reanimation procedure. The script revives 99% of blocked tags. only performs the reanimation procedure. The script revives 99% of blocked tags.
]] ]]
usage = [[ usage = [[
script run lf_t55xx_fix script run lf_t55xx_fix
]] ]]
@ -44,6 +47,25 @@ local function read_log_file(logfile)
return content return content
end end
local function sleep(n)
os.execute("sleep " ..tonumber(n))
end
function wait(msec)
local t = os.clock()
repeat
until os.clock() > t + msec * 1e-3
end
local function timer(n)
while n > 0 do
io.write("::::: "..ac.yellow.. tonumber(n) ..ac.yellow.." sec "..ac.reset..":::::\r")
sleep(1)
io.flush()
n = n-1
end
end
local function extract_password(log_content) local function extract_password(log_content)
for line in log_content:gmatch("[^\r\n]+") do for line in log_content:gmatch("[^\r\n]+") do
local password = line:match('%[%+%] found valid password: %[ (%x%x%x%x%x%x%x%x) %]') local password = line:match('%[%+%] found valid password: %[ (%x%x%x%x%x%x%x%x) %]')
@ -54,48 +76,69 @@ local function extract_password(log_content)
return nil return nil
end end
local function reanimate_t5577(password) local function reset_log_file()
if password then
command('clear')
print(dash)
print(" Using found password to wipe: " .. password)
print(dash)
command('lf t55 wipe -p ' .. password)
else
command('clear')
print(dash)
print(ac.yellow.." No valid password found, proceeding with reanimation."..ac.reset)
print(dash)
end
command('lf t55 write -b 0 -d 000880E8 -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --pg1 --r0 -t -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --pg1 --r1 -t -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --pg1 --r2 -t -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -t -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --r0 -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --r1 -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --r2 -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --r3 -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --pg1 --r0 -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --pg1 --r1 -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --pg1 --r2 -p 00000000')
command('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -p 00000000')
command('lf t55 detect')
local file = io.open(logfile, "w+") local file = io.open(logfile, "w+")
file:write("") file:write("")
file:close() file:close()
print(dash) end
print('all done!')
local function reanimate_t5577(password)
if password then
p:console('lf t55 wipe -p ' .. password)
print("T5577 wiped using a password: " ..ac.green.. password ..ac.reset)
else
print(ac.yellow.."No valid password found, proceeding with reanimation."..ac.reset)
end
p:console('lf t55 write -b 0 -d 000880E8 -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r0 -t -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r1 -t -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r2 -t -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -t -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --r0 -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --r1 -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --r2 -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --r3 -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r0 -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r1 -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r2 -p 00000000')
p:console('lf t55 write -b 0 -d 000880E0 --pg1 --r3 -p 00000000')
reset_log_file()
end end
local function main(args) local function main(args)
for o, a in getopt.getopt(args, 'h') do for o, a in getopt.getopt(args, 'h') do
if o == 'h' then return help() end if o == 'h' then return help() end
end end
command('lf t55 chk') p:console('clear')
print(dash)
print('I am initiating the repair process for '..ac.cyan..'T5577'..ac.reset)
io.write("Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.read()
print(dash)
print("::: "..ac.cyan.."Hold on, I'm searching for a password in the dictionary"..ac.reset.." :::")
print(dash)
p:console('lf t55 chk')
local log_content = read_log_file(logfile) local log_content = read_log_file(logfile)
local password = log_content and extract_password(log_content) or nil local password = log_content and extract_password(log_content) or nil
reanimate_t5577(password) reanimate_t5577(password)
p:console('lf t55 detect')
p:console('lf t55 read -b 0')
timer(5)
local success = false
for line in p.grabbed_output:gmatch("[^\r\n]+") do
if line:find("00 | 000880E0 |") then
success = true
break
end
end
if success then
print('Recovery of '..ac.cyan..'T5577'..ac.reset..' was successful !!!')
else
print('Recovery of '..ac.cyan..'T5577'..ac.reset..' was unsuccessful !!!')
end
print(dash)
end end
main(args) main(args)

View file

@ -0,0 +1,462 @@
local getopt = require('getopt')
local utils = require('utils')
local ac = require('ansicolors')
local os = require('os')
local dash = string.rep('--', 32)
local dir = os.getenv('HOME') .. '/.proxmark3/logs/'
local logfile = (io.popen('dir /a-d /o-d /tw /b/s "' .. dir .. '" 2>nul:'):read("*a"):match("%C+"))
local log_file_path = dir .. "Paxton_log.txt"
local nam = ""
local pm3 = require('pm3')
p = pm3.pm3()
local command = core.console
command('clear')
author = ' Author: jareckib - 30.01.2025'
tutorial = ' Based on Equipter tutorial - Downgrade Paxton to EM4102'
version = ' version v1.18'
desc = [[
The script automates the copying of Paxton fobs read - write.
It also allows manual input of data for blocks 4-7.
The third option is reading data stored in the log file and create new fob.
Additionally, the script calculates the ID for downgrading Paxton to EM4102.
]]
usage = [[
script run paxton_clone
]]
arguments = [[
script run paxton_clone -h : this help
]]
local debug = true
local function dbg(args)
if not DEBUG then return end
if type(args) == 'table' then
local i = 1
while args[i] do
dbg(args[i])
i = i+1
end
else
print('###', args)
end
end
local function help()
print()
print(author)
print(tutorial)
print(version)
print(desc)
print(ac.cyan..' Usage'..ac.reset)
print(usage)
print(ac.cyan..' Arguments'..ac.reset)
print(arguments)
end
local function read_log_file(logfile)
local file = io.open(logfile, "r")
if not file then
error(" Could not open the file")
end
local content = file:read("*all")
file:close()
return content
end
local function parse_blocks(result)
local blocks = {}
for line in result:gmatch("[^\r\n]+") do
local block_num, block_data = line:match("%[%=%]%s+%d/0x0([4-7])%s+%|%s+([0-9A-F ]+)")
if block_num and block_data then
block_num = tonumber(block_num)
block_data = block_data:gsub("%s+", "")
blocks[block_num] = block_data
end
end
return blocks
end
local function hex_to_bin(hex_string)
local bin_string = ""
local hex_to_bin_map = {
['0'] = "0000", ['1'] = "0001", ['2'] = "0010", ['3'] = "0011",
['4'] = "0100", ['5'] = "0101", ['6'] = "0110", ['7'] = "0111",
['8'] = "1000", ['9'] = "1001", ['A'] = "1010", ['B'] = "1011",
['C'] = "1100", ['D'] = "1101", ['E'] = "1110", ['F'] = "1111"
}
for i = 1, #hex_string do
bin_string = bin_string .. hex_to_bin_map[hex_string:sub(i, i)]
end
return bin_string
end
local function remove_last_two_bits(binary_str)
return binary_str:sub(1, #binary_str - 2)
end
local function split_into_5bit_chunks(binary_str)
local chunks = {}
for i = 1, #binary_str, 5 do
table.insert(chunks, binary_str:sub(i, i + 4))
end
return chunks
end
local function remove_parity_bit(chunks)
local no_parity_chunks = {}
for _, chunk in ipairs(chunks) do
if #chunk == 5 then
table.insert(no_parity_chunks, chunk:sub(2))
end
end
return no_parity_chunks
end
local function convert_to_hex(chunks)
local hex_values = {}
for _, chunk in ipairs(chunks) do
if #chunk > 0 then
table.insert(hex_values, string.format("%X", tonumber(chunk, 2)))
end
end
return hex_values
end
local function convert_to_decimal(chunks)
local decimal_values = {}
for _, chunk in ipairs(chunks) do
table.insert(decimal_values, tonumber(chunk, 2))
end
return decimal_values
end
local function find_until_before_f(hex_values)
local result = {}
for _, value in ipairs(hex_values) do
if value == 'F' then
break
end
table.insert(result, value)
end
return result
end
local function process_block(block)
local binary_str = hex_to_bin(block)
binary_str = remove_last_two_bits(binary_str)
local chunks = split_into_5bit_chunks(binary_str)
local no_parity_chunks = remove_parity_bit(chunks)
return no_parity_chunks
end
local function calculate_id_net(blocks)
local all_hex_values = {}
for _, block in ipairs(blocks) do
local hex_values = convert_to_hex(process_block(block))
for _, hex in ipairs(hex_values) do
table.insert(all_hex_values, hex)
end
end
local selected_hex_values = find_until_before_f(all_hex_values)
if #selected_hex_values == 0 then
error(ac.red..' Error: '..ac.reset..'No valid data found in blocks 4 and 5')
end
local combined_hex = table.concat(selected_hex_values)
if not combined_hex:match("^%x+$") then
error(ac.red..' Error: '..ac.reset..'Invalid data in blocks 4 and 5')
end
local decimal_id = tonumber(combined_hex)
local stripped_hex_id = string.format("%X", decimal_id)
local padded_hex_id = string.format("%010X", decimal_id)
return decimal_id, padded_hex_id
end
local function calculate_id_switch(blocks)
local all_decimal_values = {}
for _, block in ipairs(blocks) do
local decimal_values = convert_to_decimal(process_block(block))
for _, dec in ipairs(decimal_values) do
table.insert(all_decimal_values, dec)
end
end
if #all_decimal_values < 15 then
error(ac.red..' Error:'..ac.reset..' Not enough data after processing blocks 4, 5, 6, and 7')
end
local id_positions = {9, 11, 13, 15, 2, 4, 6, 8}
local id_numbers = {}
for _, pos in ipairs(id_positions) do
table.insert(id_numbers, all_decimal_values[pos])
end
local decimal_id = tonumber(table.concat(id_numbers))
local padded_hex_id = string.format("%010X", decimal_id)
return decimal_id, padded_hex_id
end
local function name_exists_in_log(name)
local file = io.open(log_file_path, "r")
if not file then
return false
end
local pattern = "^Name:%s*" .. name .. "%s*$"
for line in file:lines() do
if line:match(pattern) then
file:close()
return true
end
end
file:close()
return false
end
local function log_result(blocks, em410_id, name)
local log_file = io.open(log_file_path, "a")
if log_file then
log_file:write("Name: " .. name .. "\n")
log_file:write("Date: ", os.date("%Y-%m-%d %H:%M:%S"), "\n")
for i = 4, 7 do
log_file:write(string.format("Block %d: %s\n", i, blocks[i] or "nil"))
end
log_file:write(string.format('EM4102 ID: %s\n', em410_id or "nil"))
log_file:write('--------------------------\n')
log_file:close()
print(' Log saved as: pm3/.proxmark3/logs/' ..ac.yellow..' Paxton_log.txt'..ac.reset)
else
print(" Failed to open log file for writing.")
end
end
local function handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)
while true do
io.write(" Create Paxton choose " .. ac.cyan .. "1" .. ac.reset .. " or EM4102 choose " .. ac.cyan .. "2 " .. ac.reset)
local choice = io.read()
if choice == "1" then
io.write(" Place the" .. ac.cyan .. " Paxton " .. ac.reset .. "Fob on the coil to write.." .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.read()
print(dash)
command("lf hitag wrbl --ht2 -p 4 -d " .. blocks[4] .. " -k BDF5E846")
command("lf hitag wrbl --ht2 -p 5 -d " .. blocks[5] .. " -k BDF5E846")
command("lf hitag wrbl --ht2 -p 6 -d " .. blocks[6] .. " -k BDF5E846")
command("lf hitag wrbl --ht2 -p 7 -d " .. blocks[7] .. " -k BDF5E846")
elseif choice == "2" then
io.write(" Place the" .. ac.cyan .. " T5577 " .. ac.reset .. "tag on the coil and press" .. ac.green .. " ENTER " .. ac.reset .. "to continue..")
io.read()
p:console("lf em 410x clone --id " .. padded_hex_id)
print(' Cloned EM4102 to T5577 with ID ' ..ac.green.. padded_hex_id ..ac.reset)
else
print(ac.yellow .. " Invalid choice." .. ac.reset .. " Please enter " .. ac.cyan .. "1" .. ac.reset .. " or " .. ac.cyan .. "2" .. ac.reset)
goto ask_again
end
while true do
print(dash)
io.write(" Make next RFID Fob"..ac.cyan.." (y/n) "..ac.reset)
local another = io.read()
if another:lower() == "n" then
if was_option_3 then
print(" No writing to Paxton_log.txt - Name: " ..ac.green.. nam .. ac.reset.. " exist")
return
end
print()
print(ac.green .. " Saving Paxton_log file..." .. ac.reset)
while true do
io.write(" Enter a name for database (cannot be empty/duplicate): "..ac.yellow)
name = io.read()
io.write(ac.reset..'')
if name == nil or name:match("^%s*$") then
print(ac.red .. ' ERROR:'..ac.reset..' Name cannot be empty.')
else
if name_exists_in_log(name) then
print(ac.yellow .. ' Name exists!!! '..ac.reset.. 'Please choose a different name.')
else
break
end
end
end
log_result(blocks, padded_hex_id, name)
print(ac.green .. " Log saved successfully!" .. ac.reset)
local file = io.open(logfile, "w+")
file:write("")
file:close()
return
elseif another:lower() == "y" then
goto ask_again
else
print(ac.yellow.." Invalid response."..ac.reset.." Please enter"..ac.cyan.." y"..ac.reset.." or"..ac.cyan.." n"..ac.reset)
end
end
::ask_again::
end
end
local function is_valid_hex(input)
return #input == 8 and input:match("^[0-9A-Fa-f]+$")
end
local function main(args)
while true do
for o, a in getopt.getopt(args, 'h') do
if o == 'h' then return help() end
end
command('clear')
print(dash)
print(ac.green .. ' Select option: ' .. ac.reset)
print(ac.cyan .. ' 1' .. ac.reset .. ' - Read Paxton blocks 4-7 to make a copy')
print(ac.cyan .. ' 2' .. ac.reset .. ' - Manually input data for Paxton blocks 4-7')
print(ac.cyan .. " 3" .. ac.reset .. " - Search in Paxton_log by name and use the data")
print(dash)
while true do
io.write(' Your choice '..ac.cyan..'(1/2/3): ' .. ac.reset)
input_option = io.read()
if input_option == "1" or input_option == "2" or input_option == "3" then
break
else
print(ac.yellow .. ' Invalid choice.' .. ac.reset .. ' Please enter ' .. ac.cyan .. '1' .. ac.reset .. ' or ' .. ac.cyan .. '2' .. ac.reset..' or'..ac.cyan..' 3'..ac.reset)
end
end
local was_option_3 = false
if input_option == "1" then
local show_place_message = true
while true do
if show_place_message then
io.write(' Place the' .. ac.cyan .. ' Paxton' .. ac.reset .. ' Fob on the coil to read..' .. ac.green .. 'ENTER' .. ac.reset .. ' to continue..')
end
io.read()
print(dash)
p:console('lf hitag read --ht2 -k BDF5E846')
if not logfile then
error(" No files in this directory")
end
local result = read_log_file(logfile)
local blocks = parse_blocks(result)
local empty_block = false
for i = 4, 7 do
if not blocks[i] then
empty_block = true
break
end
end
if empty_block then
io.write(ac.yellow .. ' Adjust the Fob position on the coil.' .. ac.reset .. ' Press' .. ac.green .. ' ENTER' .. ac.reset .. ' to continue..')
show_place_message = false
else
print(' Readed blocks:')
print()
for i = 4, 7 do
if blocks[i] then
print(string.format(" Block %d: %s%s%s", i, ac.yellow, blocks[i], ac.reset))
end
end
local decimal_id, padded_hex_id
if blocks[5] and (blocks[5]:sub(4, 4) == 'F' or blocks[5]:sub(4, 4) == 'f') then
print(dash)
print(' Identified Paxton ' .. ac.cyan .. 'Net2' .. ac.reset)
decimal_id, padded_hex_id = calculate_id_net({blocks[4], blocks[5]})
else
print(dash)
print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset)
decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]})
end
print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset))
print(dash)
handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)
break
end
end
elseif input_option == "2" then
local blocks = {}
for i = 4, 7 do
while true do
io.write(ac.reset..' Enter data for block ' .. i .. ': ' .. ac.yellow)
local input = io.read()
input = input:upper()
if is_valid_hex(input) then
blocks[i] = input
break
else
print(ac.yellow .. ' Invalid input.' .. ac.reset .. ' Each block must be 4 bytes (8 hex characters).')
end
end
end
local decimal_id, padded_hex_id
if blocks[5] and (blocks[5]:sub(4, 4) == 'F' or blocks[5]:sub(4, 4) == 'f') then
print(ac.reset.. dash)
print(' Identified Paxton ' .. ac.cyan .. 'Net2' .. ac.reset)
decimal_id, padded_hex_id = calculate_id_net({blocks[4], blocks[5]})
else
print(ac.reset.. dash)
print(' Identified Paxton ' .. ac.cyan .. 'Switch2' .. ac.reset)
decimal_id, padded_hex_id = calculate_id_switch({blocks[4], blocks[5], blocks[6], blocks[7]})
end
print(dash)
print(string.format(" ID for EM4102 is: %s", ac.green .. padded_hex_id .. ac.reset))
print(dash)
if not padded_hex_id then
print(ac.red..' ERROR: '..ac.reset.. 'Invalid block data provided')
return
end
handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3)
break
elseif input_option == "3" then
was_option_3 = true
local retries = 3
while retries > 0 do
io.write(' Enter the name to search ('..retries..' attempts) : '..ac.yellow)
local user_input = io.read()
io.write(ac.reset..'')
if user_input == nil or user_input:match("^%s*$") then
print(ac.yellow..' Error: '..ac.reset.. 'Empty name !!!')
end
local name_clean = "^Name:%s*" .. user_input:gsub("%s", "%%s") .. "%s*$"
local file = io.open(log_file_path, "r")
if not file then
print(ac.red .. ' Error:'..ac.reset.. 'Could not open log file.')
return
end
local lines = {}
for line in file:lines() do
table.insert(lines, line)
end
file:close()
local found = false
for i = 1, #lines do
if lines[i]:match(name_clean) then
nam = user_input
local blocks = {
[4] = lines[i + 2]:match("Block 4: (.+)"),
[5] = lines[i + 3]:match("Block 5: (.+)"),
[6] = lines[i + 4]:match("Block 6: (.+)"),
[7] = lines[i + 5]:match("Block 7: (.+)")
}
local em4102_id = lines[i + 6]:match("EM4102 ID: (.+)")
print(dash)
print(' I found the data under the name: '..ac.yellow ..nam.. ac.reset)
for j = 4, 7 do
print(string.format(" Block %d: %s%s%s", j, ac.yellow, blocks[j] or "N/A", ac.reset))
end
print(" EM4102 ID: " .. ac.green .. (em4102_id or "N/A") .. ac.reset)
print(dash)
local decimal_id, padded_hex_id = em4102_id, em4102_id
handle_cloning(decimal_id, padded_hex_id, blocks, was_option_3, nam)
found = true
break
end
end
if not found then
retries = retries - 1
else
break
end
end
if retries == 0 then
print(ac.yellow .. " Name not found after 3 attempts." .. ac.reset)
end
end
print(dash)
print(' Exiting script Lua...')
return
end
end
main(args)