mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-12 11:18:11 -07:00
Merge pull request #1914 from henrygab/serial_from_flash_uniqueid
Use unique USB Serial using flash's uniqueID
This commit is contained in:
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
|
||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Enable unique USB serial numbers when built with `FLASH` -- **_UPDATES BOOTROM ALSO_**
|
||||
- Changed the readline package to v8.2 in the CMAKE files for the client (@iceman1001)
|
||||
- Add ICECLASS standalone read/sim mode (@natesales)
|
||||
- Added verbose flag to `hf iclass encode` (@natesales)
|
||||
|
||||
@@ -5,6 +5,8 @@ PLATFORM=PM3RDV4
|
||||
#PLATFORM=PM3GENERIC
|
||||
# If you want more than one PLATFORM_EXTRAS option, separate them by spaces:
|
||||
#PLATFORM_EXTRAS=BTADDON
|
||||
#PLATFORM_EXTRAS=FLASH
|
||||
#PLATFORM_EXTRAS=BTADDON FLASH
|
||||
#STANDALONE=LF_SAMYRUN
|
||||
|
||||
# Uncomment the lines below in order to make a 256KB image
|
||||
|
||||
+15
-1
@@ -2471,8 +2471,11 @@ static void PacketReceived(PacketCommandNG *packet) {
|
||||
LED_B_OFF();
|
||||
break;
|
||||
}
|
||||
if (page < 3)
|
||||
if (page < 3) {
|
||||
isok = Flash_WipeMemoryPage(page);
|
||||
// let spiffs check and update its info post flash erase
|
||||
rdv40_spiffs_check();
|
||||
}
|
||||
|
||||
reply_mix(CMD_ACK, isok, 0, 0, 0, 0);
|
||||
LED_B_OFF();
|
||||
@@ -2677,6 +2680,17 @@ void __attribute__((noreturn)) AppMain(void) {
|
||||
I2C_init(false);
|
||||
#endif
|
||||
|
||||
#ifdef WITH_FLASH
|
||||
if (FlashInit()) {
|
||||
uint64_t flash_uniqueID = 0;
|
||||
if (!Flash_CheckBusy(BUSY_TIMEOUT)) { // OK because firmware was built for devices with flash
|
||||
Flash_UniqueID((uint8_t *)(&flash_uniqueID));
|
||||
}
|
||||
FlashStop();
|
||||
usb_update_serial(flash_uniqueID);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_FPC_USART
|
||||
usart_init(USART_BAUD_RATE, USART_PARITY);
|
||||
#endif
|
||||
|
||||
+7
-1
@@ -34,11 +34,17 @@ VERSIONSRC = version_pm3.c
|
||||
# THUMBSRC :=
|
||||
|
||||
# stdint.h provided locally until GCC 4.5 becomes C99 compliant
|
||||
APP_CFLAGS = -I. -ffunction-sections -fdata-sections
|
||||
APP_CFLAGS = -I. -ffunction-sections -fdata-sections -DAS_BOOTROM
|
||||
|
||||
# stack-protect , no-pie reduces size on Gentoo Hardened 8.2 gcc, no-common makes sure uninitialized vars don't end up in COMMON area
|
||||
APP_CFLAGS += -fno-stack-protector -fno-pie -fno-common
|
||||
|
||||
ifneq (,$(findstring WITH_FLASH,$(PLATFORM_DEFS)))
|
||||
APP_CFLAGS += -DWITH_FLASH
|
||||
APP_CFLAGS += -I../common_arm
|
||||
THUMBSRC += flashmem.c ticks.c
|
||||
endif
|
||||
|
||||
|
||||
# Do not move this inclusion before the definition of {THUMB,ASM,ARM}SRC
|
||||
include ../common_arm/Makefile.common
|
||||
|
||||
+15
-1
@@ -20,6 +20,10 @@
|
||||
#include "clocks.h"
|
||||
#include "usb_cdc.h"
|
||||
|
||||
#ifdef WITH_FLASH
|
||||
#include "flashmem.h"
|
||||
#endif
|
||||
|
||||
#include "proxmark3_arm.h"
|
||||
#define DEBUG 0
|
||||
|
||||
@@ -214,8 +218,18 @@ static void flash_mode(void) {
|
||||
bootrom_unlocked = false;
|
||||
uint8_t rx[sizeof(PacketCommandOLD)];
|
||||
g_common_area.command = COMMON_AREA_COMMAND_NONE;
|
||||
if (!g_common_area.flags.button_pressed && BUTTON_PRESS())
|
||||
if (!g_common_area.flags.button_pressed && BUTTON_PRESS()) {
|
||||
g_common_area.flags.button_pressed = 1;
|
||||
}
|
||||
|
||||
#ifdef WITH_FLASH
|
||||
if (FlashInit()) { // checks for existence of flash also ... OK because bootrom was built for devices with flash
|
||||
uint64_t flash_uniqueID = 0;
|
||||
Flash_UniqueID((uint8_t *)&flash_uniqueID);
|
||||
FlashStop();
|
||||
usb_update_serial(flash_uniqueID);
|
||||
}
|
||||
#endif
|
||||
|
||||
usb_enable();
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ SECTIONS
|
||||
*(.rodata.*)
|
||||
*(.data)
|
||||
*(.data.*)
|
||||
*(.ramfunc)
|
||||
. = ALIGN(4);
|
||||
} >ram AT>bootphase2 :phase2
|
||||
|
||||
|
||||
@@ -1407,7 +1407,7 @@ static int CmdHFiClassDecrypt(const char *Cmd) {
|
||||
case DES:
|
||||
case RFU:
|
||||
case None:
|
||||
// Nothing to do for None anyway...
|
||||
// Nothing to do for None anyway...
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -114,6 +114,10 @@ endif
|
||||
|
||||
# parsing additional PLATFORM_EXTRAS tokens
|
||||
PLATFORM_EXTRAS_TMP:=$(PLATFORM_EXTRAS)
|
||||
ifneq (,$(findstring FLASH,$(PLATFORM_EXTRAS_TMP)))
|
||||
PLATFORM_DEFS += -DWITH_FLASH
|
||||
PLATFORM_EXTRAS_TMP := $(strip $(filter-out FLASH,$(PLATFORM_EXTRAS_TMP)))
|
||||
endif
|
||||
ifneq (,$(findstring BTADDON,$(PLATFORM_EXTRAS_TMP)))
|
||||
PLATFORM_DEFS += -DWITH_FPC_USART_HOST
|
||||
PLATFORM_EXTRAS_TMP := $(strip $(filter-out BTADDON,$(PLATFORM_EXTRAS_TMP)))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -100,17 +100,21 @@
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
|
||||
|
||||
void FlashmemSetSpiBaudrate(uint32_t baudrate);
|
||||
bool FlashInit(void);
|
||||
void FlashSetup(uint32_t baudrate);
|
||||
void Flash_UniqueID(uint8_t *uid);
|
||||
void FlashStop(void);
|
||||
bool Flash_WaitIdle(void);
|
||||
uint8_t Flash_ReadStat1(void);
|
||||
uint8_t Flash_ReadStat2(void);
|
||||
uint16_t FlashSendByte(uint32_t data);
|
||||
void Flash_TransferAdresse(uint32_t address);
|
||||
|
||||
void FlashSetup(uint32_t baudrate);
|
||||
bool Flash_CheckBusy(uint32_t timeout);
|
||||
uint8_t Flash_ReadStat1(void);
|
||||
uint16_t FlashSendByte(uint32_t data);
|
||||
uint16_t FlashSendLastByte(uint32_t data);
|
||||
|
||||
|
||||
#ifndef AS_BOOTROM
|
||||
void FlashmemSetSpiBaudrate(uint32_t baudrate);
|
||||
bool Flash_WaitIdle(void);
|
||||
void Flash_TransferAdresse(uint32_t address);
|
||||
|
||||
void Flash_WriteEnable(void);
|
||||
bool Flash_WipeMemoryPage(uint8_t page);
|
||||
@@ -119,8 +123,12 @@ bool Flash_Erase4k(uint8_t block, uint8_t sector);
|
||||
//bool Flash_Erase32k(uint32_t address);
|
||||
bool Flash_Erase64k(uint8_t block);
|
||||
|
||||
void Flash_UniqueID(uint8_t *uid);
|
||||
uint8_t Flash_ReadID(void);
|
||||
typedef struct {
|
||||
uint8_t manufacturer_id;
|
||||
uint8_t device_id;
|
||||
} flash_device_type_90_t; // to differentiate from JDEC ID via cmd 9F
|
||||
bool Flash_ReadID_90(flash_device_type_90_t *result);
|
||||
|
||||
uint16_t Flash_ReadData(uint32_t address, uint8_t *out, uint16_t len);
|
||||
uint16_t Flash_ReadDataCont(uint32_t address, uint8_t *out, uint16_t len);
|
||||
uint16_t Flash_Write(uint32_t address, uint8_t *in, uint16_t len);
|
||||
@@ -128,6 +136,8 @@ uint16_t Flash_WriteData(uint32_t address, uint8_t *in, uint16_t len);
|
||||
uint16_t Flash_WriteDataCont(uint32_t address, uint8_t *in, uint16_t len);
|
||||
void Flashmem_print_status(void);
|
||||
void Flashmem_print_info(void);
|
||||
uint16_t FlashSendLastByte(uint32_t data);
|
||||
|
||||
#endif // #ifndef AS_BOOTROM
|
||||
|
||||
|
||||
#endif
|
||||
@@ -19,9 +19,13 @@
|
||||
#include "ticks.h"
|
||||
|
||||
#include "proxmark3_arm.h"
|
||||
#ifndef AS_BOOTROM
|
||||
#include "dbprint.h"
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef AS_BOOTROM
|
||||
|
||||
// timer counts in 666ns increments (32/48MHz), rounding applies
|
||||
// WARNING: timer can't measure more than 43ms (666ns * 0xFFFF)
|
||||
void SpinDelayUsPrecision(int us) {
|
||||
@@ -117,40 +121,6 @@ uint32_t RAMFUNC GetTickCountDelta(uint32_t start_ticks) {
|
||||
return (UINT32_MAX - start_ticks) + stop_ticks;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// microseconds timer
|
||||
// -------------------------------------------------------------------------
|
||||
void StartCountUS(void) {
|
||||
AT91C_BASE_PMC->PMC_PCER |= (1 << AT91C_ID_TC0) | (1 << AT91C_ID_TC1);
|
||||
AT91C_BASE_TCB->TCB_BMR = AT91C_TCB_TC0XC0S_NONE | AT91C_TCB_TC1XC1S_TIOA0 | AT91C_TCB_TC2XC2S_NONE;
|
||||
|
||||
// fast clock
|
||||
// tick=1.5mks
|
||||
AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; // timer disable
|
||||
AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK | // MCK(48MHz) / 32
|
||||
AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | AT91C_TC_ACPA_CLEAR |
|
||||
AT91C_TC_ACPC_SET | AT91C_TC_ASWTRG_SET;
|
||||
AT91C_BASE_TC0->TC_RA = 1;
|
||||
AT91C_BASE_TC0->TC_RC = 0xBFFF + 1; // 0xC000
|
||||
|
||||
AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; // timer disable
|
||||
AT91C_BASE_TC1->TC_CMR = AT91C_TC_CLKS_XC1; // from timer 0
|
||||
|
||||
AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
|
||||
AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
|
||||
|
||||
// Assert a sync signal. This sets all timers to 0 on next active clock edge
|
||||
AT91C_BASE_TCB->TCB_BCR = 1;
|
||||
|
||||
while (AT91C_BASE_TC1->TC_CV > 0);
|
||||
}
|
||||
|
||||
uint32_t RAMFUNC GetCountUS(void) {
|
||||
//return (AT91C_BASE_TC1->TC_CV * 0x8000) + ((AT91C_BASE_TC0->TC_CV / 15) * 10);
|
||||
// By suggestion from PwPiwi, http://www.proxmark.org/forum/viewtopic.php?pid=17548#p17548
|
||||
return ((uint32_t)AT91C_BASE_TC1->TC_CV) * 0x8000 + (((uint32_t)AT91C_BASE_TC0->TC_CV) * 2) / 3;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Timer for iso14443 commands. Uses ssp_clk from FPGA
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -246,6 +216,47 @@ uint32_t RAMFUNC GetCountSspClkDelta(uint32_t start) {
|
||||
return (UINT32_MAX - start) + stop;
|
||||
}
|
||||
|
||||
void WaitMS(uint32_t ms) {
|
||||
WaitTicks((ms & 0x1FFFFF) * 1500);
|
||||
}
|
||||
|
||||
#endif // #ifndef AS_BOOTROM
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// microseconds timer
|
||||
// -------------------------------------------------------------------------
|
||||
void StartCountUS(void) {
|
||||
AT91C_BASE_PMC->PMC_PCER |= (1 << AT91C_ID_TC0) | (1 << AT91C_ID_TC1);
|
||||
AT91C_BASE_TCB->TCB_BMR = AT91C_TCB_TC0XC0S_NONE | AT91C_TCB_TC1XC1S_TIOA0 | AT91C_TCB_TC2XC2S_NONE;
|
||||
|
||||
// fast clock
|
||||
// tick=1.5mks
|
||||
AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; // timer disable
|
||||
AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK | // MCK(48MHz) / 32
|
||||
AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | AT91C_TC_ACPA_CLEAR |
|
||||
AT91C_TC_ACPC_SET | AT91C_TC_ASWTRG_SET;
|
||||
AT91C_BASE_TC0->TC_RA = 1;
|
||||
AT91C_BASE_TC0->TC_RC = 0xBFFF + 1; // 0xC000
|
||||
|
||||
AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; // timer disable
|
||||
AT91C_BASE_TC1->TC_CMR = AT91C_TC_CLKS_XC1; // from timer 0
|
||||
|
||||
AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
|
||||
AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
|
||||
|
||||
// Assert a sync signal. This sets all timers to 0 on next active clock edge
|
||||
AT91C_BASE_TCB->TCB_BCR = 1;
|
||||
|
||||
while (AT91C_BASE_TC1->TC_CV > 0);
|
||||
}
|
||||
|
||||
uint32_t RAMFUNC GetCountUS(void) {
|
||||
//return (AT91C_BASE_TC1->TC_CV * 0x8000) + ((AT91C_BASE_TC0->TC_CV / 15) * 10);
|
||||
// By suggestion from PwPiwi, http://www.proxmark.org/forum/viewtopic.php?pid=17548#p17548
|
||||
return ((uint32_t)AT91C_BASE_TC1->TC_CV) * 0x8000 + (((uint32_t)AT91C_BASE_TC0->TC_CV) * 2) / 3;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Timer for bitbanging, or LF stuff when you need a very precis timer
|
||||
// 1us = 1.5ticks
|
||||
@@ -282,7 +293,6 @@ void StartTicks(void) {
|
||||
AT91C_BASE_TC0->TC_CCR = AT91C_TC_SWTRG;
|
||||
while (AT91C_BASE_TC0->TC_CV > 0);
|
||||
}
|
||||
|
||||
uint32_t GetTicks(void) {
|
||||
uint32_t hi, lo;
|
||||
|
||||
@@ -307,9 +317,6 @@ void WaitTicks(uint32_t ticks) {
|
||||
void WaitUS(uint32_t us) {
|
||||
WaitTicks((us & 0x3FFFFFFF) * 3 / 2);
|
||||
}
|
||||
void WaitMS(uint32_t ms) {
|
||||
WaitTicks((ms & 0x1FFFFF) * 1500);
|
||||
}
|
||||
|
||||
// stop clock
|
||||
void StopTicks(void) {
|
||||
@@ -26,6 +26,19 @@
|
||||
#define GET_TICKS GetTicks()
|
||||
#endif
|
||||
|
||||
void StartTicks(void);
|
||||
uint32_t GetTicks(void);
|
||||
void WaitUS(uint32_t us);
|
||||
void WaitTicks(uint32_t ticks);
|
||||
void StartCountUS(void);
|
||||
uint32_t RAMFUNC GetCountUS(void);
|
||||
void StopTicks(void);
|
||||
|
||||
|
||||
#ifndef AS_BOOTROM //////////////////////////////////////////////////////////////
|
||||
// Bootrom does not require these functions.
|
||||
// Wrap in #ifndef to avoid accidental bloat of bootrom
|
||||
|
||||
void SpinDelay(int ms);
|
||||
void SpinDelayUs(int us);
|
||||
void SpinDelayUsPrecision(int us); // precision 0.6us , running for 43ms before
|
||||
@@ -34,8 +47,6 @@ void StartTickCount(void);
|
||||
uint32_t RAMFUNC GetTickCount(void);
|
||||
uint32_t RAMFUNC GetTickCountDelta(uint32_t start_ticks);
|
||||
|
||||
void StartCountUS(void);
|
||||
uint32_t RAMFUNC GetCountUS(void);
|
||||
void ResetUSClock(void);
|
||||
void SpinDelayCountUs(uint32_t us);
|
||||
|
||||
@@ -44,12 +55,10 @@ void ResetSspClk(void);
|
||||
uint32_t RAMFUNC GetCountSspClk(void);
|
||||
uint32_t RAMFUNC GetCountSspClkDelta(uint32_t start);
|
||||
|
||||
void StartTicks(void);
|
||||
uint32_t GetTicks(void);
|
||||
void WaitTicks(uint32_t ticks);
|
||||
void WaitUS(uint32_t us);
|
||||
void WaitMS(uint32_t ms);
|
||||
|
||||
void StopTicks(void);
|
||||
#endif // #ifndef AS_BOOTROM
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
@@ -366,11 +366,60 @@ static const char StrProduct[] = {
|
||||
'p', 0, 'r', 0, 'o', 0, 'x', 0, 'm', 0, 'a', 0, 'r', 0, 'k', 0, '3', 0
|
||||
};
|
||||
|
||||
#ifndef WITH_FLASH
|
||||
static const char StrSerialNumber[] = {
|
||||
14, // Length
|
||||
0x03, // Type is string
|
||||
'i', 0, 'c', 0, 'e', 0, 'm', 0, 'a', 0, 'n', 0
|
||||
};
|
||||
#else // WITH_FLASH is defined
|
||||
|
||||
// Manually calculated size of descriptor with unique ID:
|
||||
// offset 0, lengt h 1: total length field
|
||||
// offset 1, length 1: descriptor type field
|
||||
// offset 2, length 12: 6x unicode chars (original string)
|
||||
// offset 14, length 4: 2x unicode chars (underscores) [[ to avoid descriptor being (size % 8) == 0, OS bug workaround ]]
|
||||
// offset 18, length 32: 16x unicode chars (8-byte serial as hex characters)
|
||||
// ============================
|
||||
// total: 50 bytes
|
||||
#define USB_STRING_DESCRIPTOR_SERIAL_NUMBER_LENGTH 50
|
||||
char StrSerialNumber[] = {
|
||||
14, // Length is initially identical to non-unique version ... The length updated at boot, if unique serial is available
|
||||
0x03, // Type is string
|
||||
'i', 0, 'c', 0, 'e', 0, 'm', 0, 'a', 0, 'n', 0,
|
||||
'_', 0, '_', 0,
|
||||
'x', 0, 'x', 0, 'x', 0, 'x', 0, 'x', 0, 'x', 0, 'x', 0, 'x', 0,
|
||||
'x', 0, 'x', 0, 'x', 0, 'x', 0, 'x', 0, 'x', 0, 'x', 0, 'x', 0,
|
||||
};
|
||||
void usb_update_serial(uint64_t newSerialNumber) {
|
||||
static bool configured = false; // TODO: enable by setting to false here...
|
||||
if (configured) {
|
||||
return;
|
||||
}
|
||||
// run this only once per boot... even if it fails to find serial number
|
||||
configured = true;
|
||||
// reject serial number if all-zero or all-ones
|
||||
if ((newSerialNumber == 0x0000000000000000) || (newSerialNumber == 0xFFFFFFFFFFFFFFFF)) {
|
||||
return;
|
||||
}
|
||||
// Descriptor is, effectively, initially identical to non-unique serial
|
||||
// number because it reports the shorter length in the first byte.
|
||||
// Convert uniqueID's eight bytes to 16 unicode characters in the
|
||||
// descriptor and, finally, update the descriptor's length, which
|
||||
// causes the serial number to become visible.
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
// order of nibbles chosen to match display order from `hw status`
|
||||
uint8_t nibble1 = (newSerialNumber >> ((8 * i) + 4)) & 0xFu; // bitmasks [0xF0, 0xF000, 0xF00000, ... 0xF000000000000000]
|
||||
uint8_t nibble2 = (newSerialNumber >> ((8 * i) + 0)) & 0xFu; // bitmasks [0x0F, 0x0F00, 0x0F0000, ... 0x0F00000000000000]
|
||||
char c1 = nibble1 < 10 ? '0' + nibble1 : 'A' + (nibble1 - 10);
|
||||
char c2 = nibble2 < 10 ? '0' + nibble2 : 'A' + (nibble2 - 10);
|
||||
StrSerialNumber[18 + (4 * i) + 0] = c1; // [ 18, 22, .., 42, 46 ]
|
||||
StrSerialNumber[18 + (4 * i) + 2] = c2; // [ 20, 24, .., 44, 48 ]
|
||||
}
|
||||
StrSerialNumber[0] = USB_STRING_DESCRIPTOR_SERIAL_NUMBER_LENGTH;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// size includes their own field.
|
||||
static const char StrMS_OSDescriptor[] = {
|
||||
|
||||
@@ -31,6 +31,7 @@ bool usb_poll_validate_length(void);
|
||||
uint32_t usb_read(uint8_t *data, size_t len);
|
||||
int usb_write(const uint8_t *data, const size_t len);
|
||||
uint32_t usb_read_ng(uint8_t *data, size_t len);
|
||||
void usb_update_serial(uint64_t newSerialNumber);
|
||||
|
||||
void SetUSBreconnect(int value);
|
||||
int GetUSBreconnect(void);
|
||||
|
||||
+2
-2
@@ -11507,7 +11507,7 @@
|
||||
},
|
||||
"script help": {
|
||||
"command": "script help",
|
||||
"description": "This is a feature to run Lua/Cmd scripts. You can place scripts within the luascripts/cmdscripts folders. --------------------------------------------------------------------------------------- script list available offline: yes",
|
||||
"description": "This is a feature to run Lua/Cmd/Python scripts. You can place scripts within the luascripts/cmdscripts/pyscripts folders. --------------------------------------------------------------------------------------- script list available offline: yes",
|
||||
"notes": [],
|
||||
"offline": true,
|
||||
"options": [],
|
||||
@@ -11904,6 +11904,6 @@
|
||||
"metadata": {
|
||||
"commands_extracted": 749,
|
||||
"extracted_by": "PM3Help2JSON v1.00",
|
||||
"extracted_on": "2023-02-11T10:42:29"
|
||||
"extracted_on": "2023-02-18T20:20:19"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user