mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-07-05 20:41:34 -07:00
...added the standalone folder..
This commit is contained in:
parent
fda4a25f51
commit
8581fec47c
9 changed files with 860 additions and 0 deletions
357
armsrc/Standalone/hf_colin.c
Normal file
357
armsrc/Standalone/hf_colin.c
Normal file
|
@ -0,0 +1,357 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Colin Brigato, 2016,2017
|
||||
// Christian Herrmann, 2017
|
||||
//
|
||||
// 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
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// main code for HF Mifare aka ColinRun by Colin Brigato
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "hf_colin.h"
|
||||
|
||||
// Colin's sniff and repeat routine for HF Mifare
|
||||
void RunMod() {
|
||||
|
||||
/* Messy messy */
|
||||
#define MF1KSZ 1024
|
||||
#define MF1KSZSIZE 64
|
||||
|
||||
/* some mess forgotten to get rid of */
|
||||
uint8_t sectorsCnt = (mifare_size / sectorSize);
|
||||
uint64_t key64;
|
||||
uint8_t *keyBlock = NULL;
|
||||
|
||||
/* know number of known keys for standalone mode */
|
||||
#define STKEYS 35
|
||||
uint64_t mfKeys[STKEYS] = {
|
||||
0xffffffffffff, // TRANSPORTS
|
||||
0x414c41524f4e, // ALARON NORALSY
|
||||
0x424c41524f4e, // BLARON NORALSY
|
||||
0x8829da9daf76, // URMET CAPTIV IF A => ALL A/B
|
||||
0xa0a1a2a3a4a5, // PUBLIC BLOC0 BTICINO HARDENED & MAD ACCESS
|
||||
0x021209197591, // BTCINO HARDENED UNDETERMINED SPREAKD 0x01->0x13 key
|
||||
0x484558414354, // INFINEON ON A / 0F SEC B
|
||||
/* . . * /
|
||||
}
|
||||
|
||||
/* Can remember something like that in case of Bigbuf */
|
||||
keyBlock = BigBuf_malloc(STKEYS * 6);
|
||||
int mfKeysCnt = sizeof(mfKeys) / sizeof(uint64_t);
|
||||
|
||||
for (int mfKeyCounter = 0; mfKeyCounter < mfKeysCnt; mfKeyCounter++) {
|
||||
num_to_bytes(mfKeys[mfKeyCounter], 6, (uint8_t *)(keyBlock + mfKeyCounter * 6));
|
||||
}
|
||||
|
||||
uint8_t sectorsCnt = (MF1KSZ / MF1KSZSIZE);
|
||||
uint8_t foundKey[2][40][6]= {0xff}; /* C99 abusal 6.7.8.21 */
|
||||
/* TODO : remember why we actually had need to initialize this array in such specific case */
|
||||
/* and why not a simple memset abuse to 0xffize the whole space in one go ? */
|
||||
|
||||
int key = -1;
|
||||
int block = 0;
|
||||
bool err = 0;
|
||||
bool trapped = 0;
|
||||
uint32_t size = mfKeysCnt; /* what’s the point for copy ? int should be uint32_t in this case, same deal */
|
||||
|
||||
Dbprintf("...Waiting For Tag...");
|
||||
iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN);
|
||||
while (!iso14443a_select_card(cjuid, NULL, &cjcuid)) {
|
||||
WDT_HIT();
|
||||
}
|
||||
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
|
||||
SpinDelay(100);
|
||||
|
||||
Dbprintf("Got tag : %02x%02x%02x%02x", at91stdio_explode(cjuid, &cjcuid));
|
||||
|
||||
uint32_t end_time;
|
||||
uint32_t start_time = end_time = GetTickCount();
|
||||
|
||||
/* then let’s expose this “optimal case” of “well known vigik schemes” : */
|
||||
for (uint8_t type = 0; type < 2 && !err && !trapped; type++) {
|
||||
for (int sec = 0; sec < sectorsCnt && !err && !trapped; ++sec) {
|
||||
/* see after for the chk, nothing fancy */
|
||||
key = cjat91_saMifareChkKeys(sec * 4, type, NULL, size, &keyBlock[0], &key64);
|
||||
if (key == -1) {
|
||||
err = 1;
|
||||
/* used in “portable” imlementation on microcontroller: it reports back the fail and open the standalone lock */
|
||||
cmd_send(CMD_CJB_FSMSTATE_MENU, 0, 0, 0, 0, 0);
|
||||
break;
|
||||
} else {
|
||||
|
||||
/* BRACE YOURSELF */
|
||||
/* AS LONG AS WE TRAP A KNOWN KEY, WE STOP CHECKING AND ENFORCE KNOWN SCHEMES */
|
||||
else {
|
||||
num_to_bytes(key64, 6, foundKey[type][sec]);
|
||||
uint8_t tosendkey[12];
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[type][sec][0], foundKey[type][sec][1], foundKey[type][sec][2],
|
||||
foundKey[type][sec][3], foundKey[type][sec][4], foundKey[type][sec][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sec, type, tosendkey, 12);
|
||||
|
||||
switch (key64) {
|
||||
case 0x484558414354:
|
||||
Dbprintf("%c>>>>>>>>>>>>!*STOP*!<<<<<<<<<<<<<<%c", _RED_, _WHITE_);
|
||||
Dbprintf(" .TAG SEEMS %cDETERMINISTIC%c. ", _GREEN_, _WHITE_);
|
||||
Dbprintf("%cDetected: %c INFI_HEXACT_VIGIK_TAG%c", _ORANGE_, _CYAN_, _WHITE_);
|
||||
Dbprintf("...%c[%cKey_derivation_schemeTest%c]%c...", _YELLOW_,_GREEN_, _YELLOW_, _GREEN_);
|
||||
Dbprintf("%c>>>>>>>>>>>>!*DONE*!<<<<<<<<<<<<<<%c",_GREEN_, _WHITE_);
|
||||
;
|
||||
uint16_t t = 0;
|
||||
for (uint16_t sectorNo = 0; sectorNo < sectorsCnt; sectorNo++) {
|
||||
num_to_bytes(0x484558414354, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
}
|
||||
t = 1;
|
||||
uint16_t sectorNo = 0;
|
||||
num_to_bytes(0xa22ae129c013, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 1;
|
||||
num_to_bytes(0x49fae4e3849f, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 2;
|
||||
num_to_bytes(0x38fcf33072e0, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 3;
|
||||
num_to_bytes(0x8ad5517b4b18, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 4;
|
||||
num_to_bytes(0x509359f131b1, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 5;
|
||||
num_to_bytes(0x6c78928e1317, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 6;
|
||||
num_to_bytes(0xaa0720018738, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 7;
|
||||
num_to_bytes(0xa6cac2886412, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 8;
|
||||
num_to_bytes(0x62d0c424ed8e, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 9;
|
||||
num_to_bytes(0xe64a986a5d94, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 10;
|
||||
num_to_bytes(0x8fa1d601d0a2, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 11;
|
||||
num_to_bytes(0x89347350bd36, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 12;
|
||||
num_to_bytes(0x66d2b7dc39ef, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 13;
|
||||
num_to_bytes(0x6bc1e1ae547d, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 14;
|
||||
num_to_bytes(0x22729a9bd40f, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
sectorNo = 15;
|
||||
num_to_bytes(0x484558414354, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
trapped = 1;
|
||||
break;
|
||||
case 0x8829da9daf76:
|
||||
Dbprintf("%c>>>>>>>>>>>>!*STOP*!<<<<<<<<<<<<<<%c", _RED_, _WHITE_);
|
||||
Dbprintf(" .TAG SEEMS %cDETERMINISTIC%c. ", _GREEN_, _WHITE_);
|
||||
Dbprintf("%cDetected :%cURMET_CAPTIVE_VIGIK_TAG%c", _ORANGE_, _CYAN_, _WHITE_);
|
||||
Dbprintf("...%c[%cKey_derivation_schemeTest%c]%c...", _YELLOW_, _GREEN_, _YELLOW_, _GREEN_);
|
||||
Dbprintf("%c>>>>>>>>>>>>!*DONE*!<<<<<<<<<<<<<<%c",_GREEN_, _WHITE_);
|
||||
// emlClearMem();
|
||||
for (uint16_t t = 0; t < 2; t++) {
|
||||
for (uint16_t sectorNo = 0; sectorNo < sectorsCnt; sectorNo++) {
|
||||
// validKey[t][sectorNo] = true;
|
||||
num_to_bytes(key64, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
}
|
||||
}
|
||||
trapped = 1;
|
||||
break;
|
||||
case 0x414c41524f4e:
|
||||
case 0x424c41524f4e:
|
||||
Dbprintf("%c>>>>>>>>>>>>!*STOP*!<<<<<<<<<<<<<<%c", _RED_, _WHITE_);
|
||||
Dbprintf(" .TAG SEEMS %cDETERMINISTIC%c. ", _GREEN_, _WHITE_);
|
||||
Dbprintf("%c Detected :%cNORALSY_VIGIK_TAG %c", _ORANGE_, _CYAN_, _WHITE_);
|
||||
Dbprintf("...%c[%cKey_derivation_schemeTest%c]%c...", _YELLOW_, _GREEN_, _YELLOW_, _GREEN_);
|
||||
Dbprintf("%c>>>>>>>>>>>>!*DONE*!<<<<<<<<<<<<<<%c", _GREEN_, _WHITE_);
|
||||
;
|
||||
t = 0;
|
||||
for (uint16_t sectorNo = 0; sectorNo < sectorsCnt; sectorNo++) {
|
||||
num_to_bytes(0x414c41524f4e, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
}
|
||||
t = 1;
|
||||
for (uint16_t sectorNo = 0; sectorNo < sectorsCnt; sectorNo++) {
|
||||
num_to_bytes(0x424c41524f4e, 6, foundKey[t][sectorNo]);
|
||||
sprintf(tosendkey, "%02x%02x%02x%02x%02x%02x",
|
||||
foundKey[t][sectorNo][0], foundKey[t][sectorNo][1], foundKey[t][sectorNo][2],
|
||||
foundKey[t][sectorNo][3], foundKey[t][sectorNo][4], foundKey[t][sectorNo][5]
|
||||
);
|
||||
cmd_send(CMD_CJB_INFORM_CLIENT_KEY, 12, sectorNo, t, tosendkey, 12);
|
||||
}
|
||||
trapped = 1;
|
||||
break;
|
||||
}
|
||||
/* etc etc for testing schemes quick schemes */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allKeysFound) {
|
||||
cmd_send(CMD_CJB_FSMSTATE_MENU, 0, 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Settings keys to emulator */
|
||||
emlClearMem();
|
||||
uint8_t mblock[16];
|
||||
for (uint8_t sectorNo = 0; sectorNo < sectorsCnt; sectorNo++) {
|
||||
emlGetMem(mblock, FirstBlockOfSector(sectorNo) + NumBlocksPerSector(sectorNo) - 1, 1);
|
||||
for (uint8_t t = 0; t < 2; t++) {
|
||||
memcpy(mblock + t * 10, foundKey[t][sectorNo], 6);
|
||||
}
|
||||
emlSetMem(mblock, FirstBlockOfSector(sectorNo) + NumBlocksPerSector(sectorNo) - 1, 1);
|
||||
}
|
||||
Dbprintf("%c>>%c Setting Keys->Emulator MEM...[%cOK%c]", _YELLOW_, _WHITE_, _GREEN_, _WHITE_);
|
||||
|
||||
/* filling TAG to emulator */
|
||||
uint8_t filled = 0
|
||||
Dbprintf("%c>>%c Filling Emulator <- from A keys...", _YELLOW_, _WHITE_);
|
||||
/* no trace, no dbg */
|
||||
MifareECardLoad(sectorsCnt, 0, 0, &filled);
|
||||
if (filled != 1) {
|
||||
Dbprintf("%c>>%c W_FAILURE ! %cTrying fallback B keys....", _RED_, _ORANGE_, _WHITE_);
|
||||
|
||||
/* no trace, no dbg */
|
||||
MifareECardLoad(sectorsCnt, 1, 0, &filled);
|
||||
if (filled != 1) {
|
||||
Dbprintf("FATAL:EML_FALLBACKFILL_B");
|
||||
cmd_send(CMD_CJB_FSMSTATE_MENU, 0, 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
end_time = GetTickCount();
|
||||
Dbprintf("%c>>%c Time for VIGIK break :%c%dms%c", _GREEN_, _WHITE_, _YELLOW_, end_time - start_time, _WHITE_);
|
||||
cmd_send(CMD_CJB_FSMSTATE_MENU, 0, 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* . . . */
|
||||
|
||||
/* the chk function is a piwi’ed(tm) check that will try all keys for
|
||||
a particular sector. also no tracing no dbg */
|
||||
|
||||
int cjat91_saMifareChkKeys(uint8_t blockNo, uint8_t keyType, bool clearTrace, uint8_t keyCount, uint8_t * datain, uint64_t *key) {
|
||||
|
||||
MF_DBGLEVEL = MF_DBG_NONE;
|
||||
iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN);
|
||||
set_tracing(FALSE);
|
||||
|
||||
for (i = 0; i < keyCount; ++i) {
|
||||
/* no need for anticollision. just verify tag is still here */
|
||||
if (!iso14443a_select_card(uid, NULL, &cuid)) {
|
||||
cjat91_printf("FATAL : E_MF_LOSTTAG");
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t ui64Key = bytes_to_num(datain + i * 6, 6);
|
||||
if (mifare_classic_auth(pcs, cuid, blockNo, keyType, ui64Key, AUTH_FIRST)) {
|
||||
|
||||
uint8_t dummy_answer = 0;
|
||||
ReaderTransmit(&dummy_answer, 1, NULL);
|
||||
timeout = GetCountSspClk() + AUTHENTICATION_TIMEOUT;
|
||||
|
||||
// wait for the card to become ready again
|
||||
while(GetCountSspClk() < timeout) {};
|
||||
|
||||
continue;
|
||||
}
|
||||
isOK = 1;
|
||||
crypto1_destroy(pcs);
|
||||
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
|
||||
*key = ui64Key;
|
||||
return i;
|
||||
}
|
||||
crypto1_destroy(pcs);
|
||||
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
|
||||
return -1;
|
||||
}
|
21
armsrc/Standalone/hf_colin.h
Normal file
21
armsrc/Standalone/hf_colin.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Colin Brigato 2016, 2017
|
||||
// Christian Herrmann, 2017
|
||||
//
|
||||
// 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
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// StandAlone Mod
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __HF_COLIN_H
|
||||
#define __HF_COLIN_H
|
||||
|
||||
#include <stdbool.h> // for bool
|
||||
#include "standalone.h" // standalone definitions
|
||||
#include "iso14443a.h"
|
||||
#include "protocols.h"
|
||||
|
||||
|
||||
#endif /* __HF_COLIN_H */
|
263
armsrc/Standalone/hf_young.c
Normal file
263
armsrc/Standalone/hf_young.c
Normal file
|
@ -0,0 +1,263 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Craig Young, 2014
|
||||
// Christian Herrmann, 2017
|
||||
//
|
||||
// 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
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// main code for HF standalone mode Mifare /sniff/emulation by Craig Young
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "hf_young.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t uid[10];
|
||||
uint8_t uidlen;
|
||||
uint8_t atqa[2];
|
||||
uint8_t sak;
|
||||
} __attribute__((__packed__)) card_clone_t;
|
||||
|
||||
|
||||
void RunMod() {
|
||||
StandAloneMode();
|
||||
FpgaDownloadAndGo(FPGA_BITSTREAM_HF);
|
||||
|
||||
int selected = 0, playing = 0, iGotoRecord = 0, iGotoClone = 0;
|
||||
int cardRead[OPTS] = {0};
|
||||
|
||||
card_clone_t uids[OPTS];
|
||||
iso14a_card_select_t card[OPTS];
|
||||
uint8_t params = (MAGIC_SINGLE | MAGIC_DATAIN);
|
||||
|
||||
LED(selected + 1, 0);
|
||||
|
||||
for (;;) {
|
||||
WDT_HIT();
|
||||
// exit from Standalone Mode, send a usbcommand.
|
||||
if (usb_poll_validate_length()) goto out;
|
||||
|
||||
SpinDelay(300);
|
||||
|
||||
if (iGotoRecord == 1 || cardRead[selected] == 0) {
|
||||
iGotoRecord = 0;
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
LED(LED_RED2, 0);
|
||||
|
||||
// record
|
||||
Dbprintf("Enabling iso14443a reader mode for [Bank: %d]...", selected);
|
||||
/* need this delay to prevent catching some weird data */
|
||||
SpinDelay(500);
|
||||
iso14443a_setup(FPGA_HF_ISO14443A_READER_MOD);
|
||||
|
||||
for (;;) {
|
||||
// exit from Standalone Mode, send a usbcommand.
|
||||
if (usb_poll_validate_length()) goto out;
|
||||
|
||||
if (BUTTON_PRESS()) {
|
||||
if (cardRead[selected]) {
|
||||
Dbprintf("Button press detected -- replaying card in bank[%d]", selected);
|
||||
break;
|
||||
} else if (cardRead[(selected+1) % OPTS]) {
|
||||
Dbprintf("Button press detected but no card in bank[%d] so playing from bank[%d]", selected, (selected+1)%OPTS);
|
||||
selected = (selected+1) % OPTS;
|
||||
break; // playing = 1;
|
||||
} else {
|
||||
Dbprintf("Button press detected but no stored tag to play. (Ignoring button)");
|
||||
SpinDelay(300);
|
||||
}
|
||||
}
|
||||
|
||||
if (!iso14443a_select_card(NULL, &card[selected], NULL, true, 0)) {
|
||||
continue;
|
||||
} else {
|
||||
Dbprintf("Read UID:");
|
||||
Dbhexdump(card[selected].uidlen, card[selected].uid, 0);
|
||||
|
||||
if (memcmp(uids[(selected+1)%OPTS].uid, card[selected].uid, card[selected].uidlen ) == 0 ) {
|
||||
Dbprintf("Card selected has same UID as what is stored in the other bank. Skipping.");
|
||||
} else {
|
||||
uids[selected].sak = card[selected].sak;
|
||||
uids[selected].uidlen = card[selected].uidlen;
|
||||
memcpy(uids[selected].uid , card[selected].uid, uids[selected].uidlen);
|
||||
memcpy(uids[selected].atqa, card[selected].atqa, 2);
|
||||
|
||||
if (uids[selected].uidlen > 4)
|
||||
Dbprintf("Bank[%d] received a 7-byte UID", selected);
|
||||
else
|
||||
Dbprintf("Bank[%d] received a 4-byte UID", selected);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dbprintf("ATQA = %02X%02X", uids[selected].atqa[0], uids[selected].atqa[1]);
|
||||
Dbprintf("SAK = %02X", uids[selected].sak);
|
||||
LEDsoff();
|
||||
LED(LED_GREEN, 200);
|
||||
LED(LED_ORANGE, 200);
|
||||
LED(LED_GREEN, 200);
|
||||
LED(LED_ORANGE, 200);
|
||||
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
|
||||
// Next state is replay:
|
||||
playing = 1;
|
||||
|
||||
cardRead[selected] = 1;
|
||||
}
|
||||
|
||||
/* MF Classic UID clone */
|
||||
else if (iGotoClone==1) {
|
||||
iGotoClone=0;
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
LED(LED_ORANGE, 250);
|
||||
|
||||
// magiccards holds 4bytes uid. *usually*
|
||||
uint32_t tmpuid = bytes_to_num(uids[selected].uid, 4);
|
||||
|
||||
// record
|
||||
Dbprintf("Preparing to Clone card [Bank: %d]; uid: %08x", selected, tmpuid);
|
||||
|
||||
// wait for button to be released
|
||||
// Delay cloning until card is in place
|
||||
while(BUTTON_PRESS())
|
||||
WDT_HIT();
|
||||
|
||||
Dbprintf("Starting clone. [Bank: %d]", selected);
|
||||
// need this delay to prevent catching some weird data
|
||||
SpinDelay(500);
|
||||
// Begin clone function here:
|
||||
/* Example from client/mifarehost.c for commanding a block write for "magic Chinese" cards:
|
||||
UsbCommand c = {CMD_MIFARE_CSETBLOCK, {params & (0xFE | (uid == NULL ? 0:1)), blockNo, 0}};
|
||||
memcpy(c.d.asBytes, data, 16);
|
||||
SendCommand(&c);
|
||||
|
||||
Block read is similar:
|
||||
UsbCommand c = {CMD_MIFARE_CGETBLOCK, {params, blockNo, 0}};
|
||||
We need to imitate that call with blockNo 0 to set a uid.
|
||||
|
||||
The get and set commands are handled in this file:
|
||||
// Work with "magic Chinese" card
|
||||
case CMD_MIFARE_CSETBLOCK:
|
||||
MifareCSetBlock(c->arg[0], c->arg[1], c->d.asBytes);
|
||||
break;
|
||||
case CMD_MIFARE_CGETBLOCK:
|
||||
MifareCGetBlock(c->arg[0], c->arg[1], c->d.asBytes);
|
||||
break;
|
||||
|
||||
mfCSetUID provides example logic for UID set workflow:
|
||||
-Read block0 from card in field with MifareCGetBlock()
|
||||
-Configure new values without replacing reserved bytes
|
||||
memcpy(block0, uid, 4); // Copy UID bytes from byte array
|
||||
// Mifare UID BCC
|
||||
block0[4] = block0[0]^block0[1]^block0[2]^block0[3]; // BCC on byte 5
|
||||
Bytes 5-7 are reserved SAK and ATQA for mifare classic
|
||||
-Use mfCSetBlock(0, block0, oldUID, wantWipe, MAGIC_SINGLE) to write it
|
||||
*/
|
||||
uint8_t oldBlock0[16] = {0}, newBlock0[16] = {0}, testBlock0[16] = {0};
|
||||
// arg0 = Flags, arg1=blockNo
|
||||
MifareCGetBlock(params, 0, oldBlock0);
|
||||
if (oldBlock0[0] == 0 && oldBlock0[0] == oldBlock0[1] && oldBlock0[1] == oldBlock0[2] && oldBlock0[2] == oldBlock0[3]) {
|
||||
Dbprintf("No changeable tag detected. Returning to replay mode for bank[%d]", selected);
|
||||
playing = 1;
|
||||
} else {
|
||||
Dbprintf("UID from target tag: %02X%02X%02X%02X", oldBlock0[0], oldBlock0[1], oldBlock0[2], oldBlock0[3]);
|
||||
memcpy(newBlock0, oldBlock0, 16);
|
||||
|
||||
// Copy uid for bank (2nd is for longer UIDs not supported if classic)
|
||||
memcpy(newBlock0, uids[selected].uid, 4);
|
||||
newBlock0[4] = newBlock0[0] ^ newBlock0[1] ^ newBlock0[2] ^ newBlock0[3];
|
||||
|
||||
// arg0 = workFlags, arg1 = blockNo, datain
|
||||
MifareCSetBlock(params, 0, newBlock0);
|
||||
MifareCGetBlock(params, 0, testBlock0);
|
||||
|
||||
if (memcmp(testBlock0, newBlock0, 16)==0) {
|
||||
DbpString("Cloned successfull!");
|
||||
cardRead[selected] = 0; // Only if the card was cloned successfully should we clear it
|
||||
playing = 0;
|
||||
iGotoRecord = 1;
|
||||
selected = (selected + 1) % OPTS;
|
||||
} else {
|
||||
Dbprintf("Clone failed. Back to replay mode on bank[%d]", selected);
|
||||
playing = 1;
|
||||
}
|
||||
}
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
}
|
||||
|
||||
// Change where to record (or begin playing)
|
||||
// button_pressed == BUTTON_SINGLE_CLICK && cardRead[selected])
|
||||
else if (playing==1) {
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
|
||||
// Begin transmitting
|
||||
LED(LED_GREEN, 0);
|
||||
DbpString("Playing");
|
||||
for ( ; ; ) {
|
||||
// exit from Standalone Mode, send a usbcommand.
|
||||
if (usb_poll_validate_length()) goto out;
|
||||
|
||||
int button_action = BUTTON_HELD(1000);
|
||||
if ( button_action == 0) { // No button action, proceed with sim
|
||||
|
||||
uint8_t flags = FLAG_4B_UID_IN_DATA;
|
||||
uint8_t data[USB_CMD_DATA_SIZE] = {0}; // in case there is a read command received we shouldn't break
|
||||
|
||||
memcpy(data, uids[selected].uid, uids[selected].uidlen);
|
||||
|
||||
uint64_t tmpuid = bytes_to_num(uids[selected].uid, uids[selected].uidlen);
|
||||
|
||||
if ( uids[selected].uidlen == 7 ) {
|
||||
flags = FLAG_7B_UID_IN_DATA;
|
||||
Dbprintf("Simulating ISO14443a tag with uid: %014" PRIx64 " [Bank: %d]", tmpuid, selected);
|
||||
} else {
|
||||
Dbprintf("Simulating ISO14443a tag with uid: %08" PRIx64 " [Bank: %d]", tmpuid, selected);
|
||||
}
|
||||
|
||||
if (uids[selected].sak == 0x08 && uids[selected].atqa[0] == 0x04 && uids[selected].atqa[1] == 0) {
|
||||
DbpString("Mifare Classic 1k");
|
||||
SimulateIso14443aTag(1, flags, data);
|
||||
} else if (uids[selected].sak == 0x18 && uids[selected].atqa[0] == 0x02 && uids[selected].atqa[1] == 0) {
|
||||
DbpString("Mifare Classic 4k (4b uid)");
|
||||
SimulateIso14443aTag(8, flags, data);
|
||||
} else if (uids[selected].sak == 0x08 && uids[selected].atqa[0] == 0x44 && uids[selected].atqa[1] == 0) {
|
||||
DbpString("Mifare Classic 4k (7b uid)");
|
||||
SimulateIso14443aTag(8, flags, data);
|
||||
} else if (uids[selected].sak == 0x00 && uids[selected].atqa[0] == 0x44 && uids[selected].atqa[1] == 0) {
|
||||
DbpString("Mifare Ultralight");
|
||||
SimulateIso14443aTag(2, flags, data);
|
||||
} else if (uids[selected].sak == 0x20 && uids[selected].atqa[0] == 0x04 && uids[selected].atqa[1] == 0x03) {
|
||||
DbpString("Mifare DESFire");
|
||||
SimulateIso14443aTag(3, flags, data);
|
||||
} else {
|
||||
Dbprintf("Unrecognized tag type -- defaulting to Mifare Classic emulation");
|
||||
SimulateIso14443aTag(1, flags, data);
|
||||
}
|
||||
|
||||
} else if (button_action == BUTTON_SINGLE_CLICK) {
|
||||
selected = (selected + 1) % OPTS;
|
||||
Dbprintf("Done playing. Switching to record mode on bank %d", selected);
|
||||
iGotoRecord = 1;
|
||||
break;
|
||||
} else if (button_action == BUTTON_HOLD) {
|
||||
Dbprintf("Playtime over. Begin cloning...");
|
||||
iGotoClone = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* We pressed a button so ignore it here with a delay */
|
||||
SpinDelay(300);
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
}
|
||||
}
|
||||
// time to exit standalone mode
|
||||
out:
|
||||
}
|
22
armsrc/Standalone/hf_young.h
Normal file
22
armsrc/Standalone/hf_young.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Craig Young 2014
|
||||
// Christian Herrmann, 2017
|
||||
//
|
||||
// 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
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// StandAlone Mod
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __HF_YOUNG_H
|
||||
#define __HF_YOUNG_H
|
||||
|
||||
#include <stdbool.h> // for bool
|
||||
#include "standalone.h" // standalone definitions
|
||||
#include "iso14443a.h"
|
||||
#include "protocols.h"
|
||||
|
||||
#define OPTS 2
|
||||
|
||||
#endif /* __HF_YOUNG_H */
|
134
armsrc/Standalone/lf_samyrun.c
Normal file
134
armsrc/Standalone/lf_samyrun.c
Normal file
|
@ -0,0 +1,134 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Samy Kamkar, 2012
|
||||
// Christian Herrmann, 2017
|
||||
//
|
||||
// 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
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// main code for LF aka SamyRun by Samy Kamkar
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "lf_samyrun.h"
|
||||
|
||||
// samy's sniff and repeat routine for LF
|
||||
void RunMod() {
|
||||
StandAloneMode();
|
||||
FpgaDownloadAndGo(FPGA_BITSTREAM_LF);
|
||||
|
||||
int high[OPTS], low[OPTS];
|
||||
int selected = 0;
|
||||
int playing = 0;
|
||||
int cardRead = 0;
|
||||
|
||||
// Turn on selected LED
|
||||
LED(selected + 1, 0);
|
||||
|
||||
for (;;) {
|
||||
WDT_HIT();
|
||||
|
||||
// exit from SamyRun, send a usbcommand.
|
||||
if (usb_poll_validate_length()) break;
|
||||
|
||||
// Was our button held down or pressed?
|
||||
int button_pressed = BUTTON_HELD(1000);
|
||||
SpinDelay(300);
|
||||
|
||||
// Button was held for a second, begin recording
|
||||
if (button_pressed > 0 && cardRead == 0) {
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
LED(LED_RED2, 0);
|
||||
|
||||
// record
|
||||
DbpString("Starting recording");
|
||||
|
||||
// wait for button to be released
|
||||
while(BUTTON_PRESS())
|
||||
WDT_HIT();
|
||||
|
||||
/* need this delay to prevent catching some weird data */
|
||||
SpinDelay(500);
|
||||
|
||||
CmdHIDdemodFSK(1, &high[selected], &low[selected], 0);
|
||||
Dbprintf("Recorded %x %x %08x", selected, high[selected], low[selected]);
|
||||
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
// Finished recording
|
||||
// If we were previously playing, set playing off
|
||||
// so next button push begins playing what we recorded
|
||||
playing = 0;
|
||||
cardRead = 1;
|
||||
}
|
||||
else if (button_pressed > 0 && cardRead == 1) {
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
LED(LED_ORANGE, 0);
|
||||
|
||||
// record
|
||||
Dbprintf("Cloning %x %x %08x", selected, high[selected], low[selected]);
|
||||
|
||||
// wait for button to be released
|
||||
while(BUTTON_PRESS())
|
||||
WDT_HIT();
|
||||
|
||||
/* need this delay to prevent catching some weird data */
|
||||
SpinDelay(500);
|
||||
|
||||
CopyHIDtoT55x7(0, high[selected], low[selected], 0);
|
||||
Dbprintf("Cloned %x %x %08x", selected, high[selected], low[selected]);
|
||||
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
// Finished recording
|
||||
|
||||
// If we were previously playing, set playing off
|
||||
// so next button push begins playing what we recorded
|
||||
playing = 0;
|
||||
cardRead = 0;
|
||||
}
|
||||
|
||||
// Change where to record (or begin playing)
|
||||
else if (button_pressed) {
|
||||
// Next option if we were previously playing
|
||||
if (playing)
|
||||
selected = (selected + 1) % OPTS;
|
||||
playing = !playing;
|
||||
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
|
||||
// Begin transmitting
|
||||
if (playing) {
|
||||
LED(LED_GREEN, 0);
|
||||
DbpString("Playing");
|
||||
// wait for button to be released
|
||||
while(BUTTON_PRESS())
|
||||
WDT_HIT();
|
||||
|
||||
Dbprintf("%x %x %08x", selected, high[selected], low[selected]);
|
||||
CmdHIDsimTAG(high[selected], low[selected], 0);
|
||||
DbpString("Done playing");
|
||||
|
||||
if (BUTTON_HELD(1000) > 0) {
|
||||
DbpString("Exiting");
|
||||
LEDsoff();
|
||||
return;
|
||||
}
|
||||
|
||||
/* We pressed a button so ignore it here with a delay */
|
||||
SpinDelay(300);
|
||||
|
||||
// when done, we're done playing, move to next option
|
||||
selected = (selected + 1) % OPTS;
|
||||
playing = !playing;
|
||||
LEDsoff();
|
||||
LED(selected + 1, 0);
|
||||
}
|
||||
else {
|
||||
while(BUTTON_PRESS())
|
||||
WDT_HIT();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
armsrc/Standalone/lf_samyrun.h
Normal file
21
armsrc/Standalone/lf_samyrun.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Samy Kamkar 2012
|
||||
// Christian Herrmann, 2017
|
||||
//
|
||||
// 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
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// StandAlone Mod
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __LF_SAMYRUN_H
|
||||
#define __LF_SAMYRUN_H
|
||||
|
||||
#include <stdbool.h> // for bool
|
||||
#include "standalone.h" // standalone definitions
|
||||
#include "apps.h" // debugstatements, lfops?
|
||||
|
||||
#define OPTS 2
|
||||
|
||||
#endif /* __LF_SAMYRUN_H */
|
24
armsrc/Standalone/readme.md
Normal file
24
armsrc/Standalone/readme.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# StandAlone Mods
|
||||
|
||||
This contains functionality for different StandAlone mods. The fullimage will be built given the correct compiler flags used. Build targets for these files are contained in `armsrc/Makefile`.
|
||||
|
||||
If you want to implement a new standalone mod, you need to implement the methods provided in `standalone.h`.
|
||||
|
||||
## Implementing a standalone mod
|
||||
|
||||
Each standalone mod needs to have its own compiler flag to be added in `armsrc\makefile` and inside the function `AppMain` inside AppMain.c
|
||||
|
||||
Use HF/LF to denote which frequence your mod is targeting.
|
||||
Use you own github name/similar for perpetual honour to denote your mod
|
||||
|
||||
samples:
|
||||
# -DWITH_LF_ICERUN
|
||||
# -DWITH_LF_SAMYRUN
|
||||
# -DWITH_LF_PROXBRUTE
|
||||
# -DWITH_LF_HIDCORP
|
||||
# -DWITH_HF_YOUNG
|
||||
# -DWITH_HF_MATTYRUN
|
||||
|
||||
## Adding identification of your mod
|
||||
Do please add a identification string in the function `printStandAloneModes` inside `armsrc\appmain.c`
|
||||
This will enable an easy way to detect on client side which standalone mods has been installed on the device.
|
18
armsrc/Standalone/standalone.h
Normal file
18
armsrc/Standalone/standalone.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Christian Herrmann, 2017
|
||||
//
|
||||
// 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
|
||||
// the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// StandAlone Mod header file
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __STANDALONE_H
|
||||
#define __STANDALONE_H
|
||||
|
||||
#include <stdbool.h> // for bool
|
||||
|
||||
extern void RunMod();
|
||||
|
||||
#endif /* __STANDALONE_H */
|
0
armsrc/obj/Standalone/.dummy
Normal file
0
armsrc/obj/Standalone/.dummy
Normal file
Loading…
Add table
Add a link
Reference in a new issue