svnno****@sourc*****
svnno****@sourc*****
2012年 1月 29日 (日) 22:04:13 JST
Revision: 4810 http://sourceforge.jp/projects/ttssh2/svn/view?view=rev&revision=4810 Author: yutakapon Date: 2012-01-29 22:04:12 +0900 (Sun, 29 Jan 2012) Log Message: ----------- KERMIT: - CAPAS(Long Packet, File Attribute)の機能追加。ただし、穴あけが完了していないため、 条件コンパイル文(#ifdef KERMIT_CAPAS)で無効化してある。 - teraterm.ini に KmtLongPacket, KmtFileAttr エントリを追加。ただし、現状 on にしても 有効化できない。 - 受信バッファが MAXL バイトを超えたら、BOF回避のため、受信処理を終了させるようにした。 Modified Paths: -------------- trunk/installer/release/TERATERM.INI trunk/teraterm/common/ttftypes.h trunk/teraterm/common/tttypes.h trunk/teraterm/ttpfile/kermit.c trunk/teraterm/ttpset/ttset.c -------------- next part -------------- Modified: trunk/installer/release/TERATERM.INI =================================================================== --- trunk/installer/release/TERATERM.INI 2012-01-26 12:28:44 UTC (rev 4809) +++ trunk/installer/release/TERATERM.INI 2012-01-29 13:04:12 UTC (rev 4810) @@ -442,6 +442,10 @@ ; Kermit log KmtLog=off +; Kermit CAPAS: Ability to transmit and receive extended-length packets +KmtLongPacket=off +; Kermit CAPAS: Ability to accept "A" packets (file attributes) +KmtFileAttr=off ; Language selection LanguageSelection=on Modified: trunk/teraterm/common/ttftypes.h =================================================================== --- trunk/teraterm/common/ttftypes.h 2012-01-26 12:28:44 UTC (rev 4809) +++ trunk/teraterm/common/ttftypes.h 2012-01-29 13:04:12 UTC (rev 4810) @@ -103,11 +103,16 @@ typedef TFileVar far *PFileVar; typedef struct { - BYTE MAXL,TIME,NPAD,PADC,EOL,QCTL,QBIN,CHKT,REPT; + int MAXL; + BYTE TIME,NPAD,PADC,EOL,QCTL,QBIN,CHKT,REPT,CAPAS,WINDO,MAXLX1,MAXLX2; } KermitParam; +#define KMT_DATAMAX 4000 +#define KMT_PKTMAX (KMT_DATAMAX + 32) +#define KMT_PKTQUE 4 + typedef struct { - BYTE PktIn[96], PktOut[96]; + BYTE PktIn[KMT_PKTMAX], PktOut[KMT_PKTMAX]; int PktInPtr; int PktInLen, PktInCount; int PktNum, PktNumOffset; @@ -120,6 +125,12 @@ BYTE NextSeq; BYTE NextByte; KermitParam KmtMy, KmtYour; + int PktOutCount, PktInLongPacketLen; + int FileAttrFlag; + BOOL FileType; + time_t FileTime; + int FileMode; + LONGLONG FileSize; } TKmtVar; typedef TKmtVar far *PKmtVar; @@ -134,6 +145,7 @@ #define SendData 3 #define SendEOF 4 #define SendEOT 5 +#define SendFileAttr 6 #define ReceiveInit 6 #define ReceiveFile 7 Modified: trunk/teraterm/common/tttypes.h =================================================================== --- trunk/teraterm/common/tttypes.h 2012-01-26 12:28:44 UTC (rev 4809) +++ trunk/teraterm/common/tttypes.h 2012-01-29 13:04:12 UTC (rev 4810) @@ -169,6 +169,11 @@ #define YoptG 2 #define YoptSingle 3 + /* KERMIT option */ +#define KmtOptLongPacket 1 +#define KmtOptFileAttr 2 +#define KmtOptSlideWin 4 + /* Language */ #define IdEnglish 1 #define IdJapanese 2 @@ -547,6 +552,7 @@ char ClickableUrlBrowser[MAX_PATH]; char ClickableUrlBrowserArg[MAX_PATH]; WORD LogLockExclusive; + WORD KermitOpt; }; typedef struct tttset TTTSet, *PTTSet; @@ -994,6 +1000,7 @@ * * - At version 4.73, ttset_memfilemap was replaced with ttset_memfilemap_18. * added tttset.LogLockExclusive + * added tttset.KermitOpt * * - At version 4.72, ttset_memfilemap was replaced with ttset_memfilemap_17. * added tttset.TabStopFlag. Modified: trunk/teraterm/ttpfile/kermit.c =================================================================== --- trunk/teraterm/ttpfile/kermit.c 2012-01-26 12:28:44 UTC (rev 4809) +++ trunk/teraterm/ttpfile/kermit.c 2012-01-29 13:04:12 UTC (rev 4810) @@ -14,6 +14,7 @@ #include <stdio.h> #include <time.h> #include <string.h> +#include <sys/stat.h> #include "tt_res.h" #include "ttcommon.h" @@ -37,7 +38,15 @@ #define DefCHKT 1 #define MyREPT '~' +#define KMT_CAP_LONGPKT 2 +#define KMT_CAP_SLIDWIN 4 +#define KMT_CAP_FILATTR 8 +#define KMT_ATTR_TIME 001 +#define KMT_ATTR_MODE 002 +#define KMT_ATTR_SIZE 003 +#define KMT_ATTR_TYPE 004 + BYTE KmtNum(BYTE b); @@ -177,6 +186,21 @@ } } +// a single-character type 1 checksum \x82\xF0\x8Cv\x8EZ\x82\xB7\x82\xE9 +static int KmtCheckSumType1(BYTE *buf, int len) +{ + WORD Sum; + BYTE check; + int i; + + Sum = 0; + for (i = 0 ; i < len ; i++) { + Sum = Sum + buf[i]; + } + KmtCalcCheck(Sum, 1, &check); + return (check); +} + void KmtSendPacket(PFileVar fv, PKmtVar kv, PComVar cv) { int C; @@ -186,7 +210,11 @@ CommBinaryOut(cv,&(kv->KmtYour.PADC), 1); /* packet */ +#ifdef KERMIT_CAPAS + C = kv->PktOutCount; +#else C = KmtNum(kv->PktOut[1]) + 2; +#endif CommBinaryOut(cv,&kv->PktOut[0], C); if (fv->LogFlag) @@ -209,19 +237,44 @@ void KmtMakePacket(PFileVar fv, PKmtVar kv, BYTE SeqNum, BYTE PktType, int DataLen) { - int i; + int i, nlen, headnum; WORD Sum; + // SEQ\x82\xA9\x82\xE7CHECK\x82܂ł̒\xB7\x82\xB3\x81BMARK\x82\xC6LEN\x82͊܂܂Ȃ\xA2\x81B + nlen = DataLen + kv->KmtMy.CHKT + 2; + kv->PktOut[0] = 1; /* MARK */ - kv->PktOut[1] = KmtChar((BYTE)(DataLen + kv->KmtMy.CHKT + 2)); /* LEN */ + kv->PktOut[1] = KmtChar((BYTE)(nlen)); /* LEN */ kv->PktOut[2] = KmtChar(SeqNum); /* SEQ */ kv->PktOut[3] = PktType; /* TYPE */ + /* Long Packet\x82̏ꍇ */ + if (nlen > MaxNum) { + int k; + memmove_s(&kv->PktOut[7], KMT_PKTMAX-7, &kv->PktOut[4], DataLen); + kv->PktOut[1] = KmtChar(0); /* LEN=0 */ + k = DataLen + kv->KmtMy.CHKT; + kv->PktOut[4] = KmtChar(k / 95); + kv->PktOut[5] = KmtChar(k % 95); + Sum = KmtCheckSumType1(&kv->PktOut[1], 5); + kv->PktOut[6] = KmtChar((BYTE)Sum); /* HCHECK */ + + /* LEN+SEQ+TYPE+LENX1+LENX2+HCHECK */ + headnum = 6; + + } else { + /* LEN+SEQ+TYPE */ + headnum = 3; + } + /* check sum */ Sum = 0; - for (i = 1 ; i <= DataLen+3 ; i++) + for (i = 1 ; i <= DataLen + headnum ; i++) Sum = Sum + kv->PktOut[i]; - KmtCalcCheck(Sum, kv->KmtMy.CHKT, &(kv->PktOut[DataLen+4])); + KmtCalcCheck(Sum, kv->KmtMy.CHKT, &(kv->PktOut[DataLen + headnum + 1])); + + /* \x83o\x83b\x83t\x83@\x82̑S\x91̃T\x83C\x83Y */ + kv->PktOutCount = 1 + headnum + DataLen + kv->KmtMy.CHKT; } @@ -246,6 +299,19 @@ kv->PktOut[11] = kv->KmtMy.CHKT + 0x30; kv->PktOut[12] = kv->KmtMy.REPT; +#ifdef KERMIT_CAPAS + if (kv->KmtMy.CAPAS > 0) { + kv->PktOut[13] = KmtChar(kv->KmtMy.CAPAS); + NParam++; + if (kv->KmtMy.CAPAS & KMT_CAP_LONGPKT) { + kv->PktOut[14] = KmtChar(1); + kv->PktOut[15] = KmtChar(KMT_DATAMAX / 95); + kv->PktOut[16] = KmtChar(KMT_DATAMAX % 95); + NParam += 3; + } + } +#endif + KmtMakePacket(fv,kv,(BYTE)(kv->PktNum - kv->PktNumOffset),PktType,NParam); KmtSendPacket(fv,kv,cv); @@ -273,13 +339,25 @@ BOOL KmtCheckPacket(PKmtVar kv) { - int i; + int i, len; WORD Sum; BYTE Check[3]; + /* Long Packet \x82̏ꍇ\x81A\x82܂\xB8 HCHECK \x82\xF0\x8C\x9F\x8F\xB7\x82\xE9\x81B */ + if (kv->PktInLen == 0) { + Sum = KmtCheckSumType1(&kv->PktIn[1], 5); + if ((BYTE)Sum != kv->PktIn[6]) + return FALSE; + len = kv->PktInCount - 1 - kv->KmtMy.CHKT; + + } else { + len = kv->PktInLen+1-kv->KmtMy.CHKT; + + } + /* Calc sum */ Sum = 0; - for (i = 1 ; i <= kv->PktInLen+1-kv->KmtMy.CHKT ; i++) + for (i = 1 ; i <= len ; i++) Sum = Sum + kv->PktIn[i]; /* Calc CHECK */ @@ -287,7 +365,7 @@ for (i = 1 ; i <= kv->KmtMy.CHKT ; i++) if (Check[i-1] != - kv->PktIn[ kv->PktInLen +1- kv->KmtMy.CHKT +i ]) + kv->PktIn[ len + i ]) return FALSE; return TRUE; } @@ -300,14 +378,21 @@ void KmtParseInit(PKmtVar kv, BOOL AckFlag) { - int i, NParam; + int i, NParam, off, cap; BYTE b, n; - NParam = kv->PktInLen - 2 - kv->KmtMy.CHKT; + if (kv->PktInLen == 0) { /* Long Packet */ + NParam = kv->PktInLongPacketLen - kv->KmtMy.CHKT; + off = 7; + } else { + NParam = kv->PktInLen - 2 - kv->KmtMy.CHKT; + off = 3; + } + for (i=1 ; i <= NParam ; i++) { - b = kv->PktIn[i+3]; + b = kv->PktIn[i + off]; n = KmtNum(b); switch (i) { case 1: @@ -392,9 +477,40 @@ */ kv->RepeatFlag = kv->KmtMy.REPT == kv->KmtYour.REPT; break; + + case 10: /* CAPAS */ + kv->KmtYour.CAPAS = n; + + // Tera Term\x82ƃT\x81[\x83o\x82\xCC capabilities \x82\xCCAND\x82\xF0\x8E\xE6\x82\xE9\x81B + cap = 0; + if (n & kv->KmtMy.CAPAS & KMT_CAP_LONGPKT) { + cap |= KMT_CAP_LONGPKT; + } + if (n & kv->KmtMy.CAPAS & KMT_CAP_SLIDWIN) { + cap |= KMT_CAP_SLIDWIN; + } + if (n & kv->KmtMy.CAPAS & KMT_CAP_FILATTR) { + cap |= KMT_CAP_FILATTR; + } + kv->KmtMy.CAPAS = cap; + break; } } + /* Long Packet \x82̏ꍇ\x81AMAXL \x82\xF0\x8DX\x90V\x82\xB7\x82\xE9\x81B*/ + if (kv->KmtMy.CAPAS & KMT_CAP_LONGPKT) { + kv->KmtMy.MAXL = kv->PktInLongPacketLen; + if (kv->KmtMy.MAXL < 10) + kv->KmtMy.MAXL = 80; + else if (kv->KmtMy.MAXL > KMT_DATAMAX) + kv->KmtMy.MAXL = KMT_DATAMAX; + + } else { + /* Capabilities \x82\xAA\x97\x8E\x82\xBF\x82Ă\xA2\x82\xE9\x82̂ɁALEN=0 \x82̏ꍇ\x82́AMAXL \x82\xCD DefMAXL \x82̂܂܂Ƃ\xB7\x82\xE9\x81B + * TODO: \x96{\x97\x88\x82̓G\x83\x89\x81[\x82Ƃ\xB7\x82ׂ\xAB\x81H + */ + + } } void KmtSendAck(PFileVar fv, PKmtVar kv, PComVar cv) @@ -412,13 +528,20 @@ void KmtDecode(PFileVar fv, PKmtVar kv, PCHAR Buff, int *BuffLen) { - int i, j, DataLen, BuffPtr; + int i, j, DataLen, BuffPtr, off; BYTE b, b2; BOOL CTLflag,BINflag,REPTflag,OutFlag; BuffPtr = 0; - DataLen = kv->PktInLen - kv->KmtMy.CHKT - 2; + if (kv->PktInLen == 0) { /* Long Packet */ + DataLen = kv->PktInLongPacketLen - kv->KmtMy.CHKT; + off = 7; + } else { + DataLen = kv->PktInLen - kv->KmtMy.CHKT - 2; + off = 3; + } + OutFlag = FALSE; kv->RepeatCount = 1; CTLflag = FALSE; @@ -426,7 +549,7 @@ REPTflag = FALSE; for (i = 1 ; i <= DataLen ; i++) { - b = kv->PktIn[3+i]; + b = kv->PktIn[off + i]; b2 = b & 0x7f; if (CTLflag) { @@ -478,6 +601,24 @@ SetDlgTime(fv->HWin, IDC_PROTOELAPSEDTIME, fv->StartTime, fv->ByteCount); } +static void KmtRecvFileAttr(PFileVar fv, PKmtVar kv, PCHAR Buff, int *BuffLen) +{ + int DataLen, BuffPtr, off; + BYTE *p; + + BuffPtr = 0; + + if (kv->PktInLen == 0) { /* Long Packet */ + DataLen = kv->PktInLongPacketLen - kv->KmtMy.CHKT; + off = 7; + } else { + DataLen = kv->PktInLen - kv->KmtMy.CHKT - 2; + off = 3; + } + + p = &kv->PktIn[off]; +} + BOOL KmtEncode(PFileVar fv, PKmtVar kv) { BYTE b, b2, b7; @@ -638,6 +779,7 @@ BOOL KmtSendNextFile(PFileVar fv, PKmtVar kv, PComVar cv) { char uimsg[MAX_UIMSG], uimsg2[MAX_UIMSG]; + struct _stati64 st; if (! GetNextFname(fv)) { @@ -645,6 +787,16 @@ return TRUE; } + if (_stati64(fv->FullName, &st) == 0) { + kv->FileAttrFlag = KMT_ATTR_TIME | KMT_ATTR_MODE | KMT_ATTR_SIZE | KMT_ATTR_TYPE; + kv->FileType = TRUE; + kv->FileTime = st.st_ctime; + kv->FileMode = st.st_mode; + kv->FileSize = st.st_size; + } else { + kv->FileAttrFlag = 0; + } + /* file open */ fv->FileHandle = _lopen(fv->FullName,OF_READ); fv->FileOpen = fv->FileHandle>0; @@ -684,6 +836,46 @@ return TRUE; } +BOOL KmtSendNextFileAttr(PFileVar fv, PKmtVar kv, PComVar cv) +{ + char buf[512], s[128]; + char t[64]; + + buf[0] = '\0'; + if ( (kv->FileAttrFlag & KMT_ATTR_TYPE) != 0 ) { + _snprintf_s(s, sizeof(s), _TRUNCATE, "\"%c%s8", KmtChar(2), kv->FileType ? "A" : "B"); + strncat_s(buf, sizeof(buf), s, _TRUNCATE); + } + if ( (kv->FileAttrFlag & KMT_ATTR_TIME) != 0 ) { + struct tm *date = localtime(&kv->FileTime); + int len; + len = strftime(t, sizeof(t), "%Y%m%d %H:%M:%S", date); + _snprintf_s(s, sizeof(s), _TRUNCATE, "#%d%s", len, t); + strncat_s(buf, sizeof(buf), s, _TRUNCATE); + } + if ( (kv->FileAttrFlag & KMT_ATTR_MODE) != 0 ) { + _snprintf_s(t, sizeof(t), _TRUNCATE, "%03o", kv->FileMode & 0777); + _snprintf_s(s, sizeof(s), _TRUNCATE, ",%d%s", strlen(t), t); + strncat_s(buf, sizeof(buf), s, _TRUNCATE); + } + if ( (kv->FileAttrFlag & KMT_ATTR_SIZE) != 0 ) { + _snprintf_s(t, sizeof(t), _TRUNCATE, "%I64d", kv->FileSize); + _snprintf_s(s, sizeof(s), _TRUNCATE, "1%d%s", strlen(t), t); + strncat_s(buf, sizeof(buf), s, _TRUNCATE); + } + + KmtIncPacketNum(kv); + strncpy_s(&(kv->PktOut[4]),sizeof(kv->PktOut)-4, buf, _TRUNCATE); + KmtMakePacket(fv,kv,(BYTE)(kv->PktNum-kv->PktNumOffset),(BYTE)'A', + strlen(buf)); + KmtSendPacket(fv,kv,cv); + + kv->RepeatCount = 0; + kv->NextByteFlag = FALSE; + kv->KmtState = SendFileAttr; + return TRUE; +} + void KmtSendReceiveInit(PFileVar fv, PKmtVar kv, PComVar cv) { kv->PktNum = 0; @@ -769,6 +961,17 @@ kv->KmtMy.CHKT = DefCHKT; kv->KmtMy.REPT = MyREPT; + /* CAPAS: a capability of Kermit + * (2012/1/22 yutaka) + */ + kv->KmtMy.CAPAS = 0x00; +#ifdef KERMIT_CAPAS + if (ts->KermitOpt & KmtOptLongPacket) + kv->KmtMy.CAPAS |= KMT_CAP_LONGPKT; + if (ts->KermitOpt & KmtOptFileAttr) + kv->KmtMy.CAPAS |= KMT_CAP_FILATTR; +#endif + /* default your parameters */ kv->KmtYour = kv->KmtMy; @@ -876,15 +1079,48 @@ case WaitLen: kv->PktIn[1] = b; kv->PktInLen = KmtNum(b); +#ifdef KERMIT_CAPAS + if (kv->PktInLen == 0) { /* Long Packet */ + kv->PktInCount = 0; + } else if (kv->PktInLen >= 3) { /* Normal Packet */ + kv->PktInCount = kv->PktInLen + 2; + } else { + /* If unchar(LEN) = 1 or 2, the packet is invalid and should cause an Error. */ + GetPkt = FALSE; + goto read_end; + } +#else kv->PktInCount = kv->PktInLen; +#endif kv->PktInPtr = 2; kv->PktReadMode = WaitCheck; break; case WaitCheck: + // \x83o\x83b\x83t\x83@\x82\xAA\x88\xEC\x82ꂽ\x82\xE7\x81A\x88ُ\xED\x8FI\x97\xB9\x82\xB7\x82\xE9\x81B + // Tera Term\x91\xA4\x82\xAALong Packet\x82\xF0\x83T\x83|\x81[\x83g\x82\xB5\x82Ă\xA2\x82Ȃ\xA2\x8Fꍇ\x82ɁA\x83T\x81[\x83o\x91\xA4\x82\xA9\x82\xE7\x95s\x90\xB3\x82\xC9 + // Long Packet\x82\xAA\x91\x97\x82\xE7\x82\xEA\x82Ă\xAB\x82\xBD\x8Fꍇ\x82\xE0\x8B~\x8Dςł\xAB\x82\xE9\x81B + if (kv->PktInPtr > kv->KmtMy.MAXL) { + GetPkt = FALSE; + goto read_end; + } kv->PktIn[kv->PktInPtr] = b; kv->PktInPtr++; +#ifdef KERMIT_CAPAS + // Long Packet + if (kv->PktInCount == 0 && kv->PktInPtr == 6) { + kv->PktInLongPacketLen = KmtNum(kv->PktIn[4])*95 + KmtNum(kv->PktIn[5]); + kv->PktInCount = kv->PktInLongPacketLen + 7; + } + + // \x8A\xFA\x91҂\xB5\x82\xBD\x83o\x83b\x83t\x83@\x83T\x83C\x83Y\x82ɂȂ\xC1\x82\xBD\x82\xE7\x8FI\x82\xED\x82\xE9\x81B + if (kv->PktInCount != 0 && kv->PktInPtr >= kv->PktInCount) { + GetPkt = TRUE; + break; + } +#else kv->PktInCount--; GetPkt = (kv->PktInCount==0); +#endif if (GetPkt) kv->PktReadMode = WaitMark; break; } @@ -892,14 +1128,13 @@ if (! GetPkt) c = CommRead1Byte(cv,&b); } +read_end: if (! GetPkt) return TRUE; if (fv->LogFlag) { -#if 0 - _lwrite(fv->LogFile,"< ",2); - _lwrite(fv->LogFile,&(kv->PktIn[1]),kv->PktInLen+1); - _lwrite(fv->LogFile,"\015\012",2); +#ifdef KERMIT_CAPAS + KmtReadLog(fv, kv, &(kv->PktIn[0]), kv->PktInCount); #else KmtReadLog(fv, kv, &(kv->PktIn[0]), kv->PktInLen+2); #endif @@ -949,6 +1184,13 @@ } break; + case 'A': /* File Attribute(Optional) */ + if ((kv->KmtState == ReceiveData) && + (PktNumNew > kv->PktNum)) { + KmtRecvFileAttr(fv,kv,NULL,&Len); + } + break; + case 'S': if ((kv->KmtState == ReceiveInit) || (kv->KmtState == GetInit)) @@ -967,8 +1209,12 @@ case SendFile: if (PktNumNew==kv->PktNum) KmtSendPacket(fv,kv,cv); - else if (PktNumNew==kv->PktNum+1) - KmtSendNextData(fv,kv,cv); + else if (PktNumNew==kv->PktNum+1) { + if (kv->KmtMy.CAPAS & KMT_CAP_FILATTR) + KmtSendNextData(fv,kv,cv); + else + KmtSendNextData(fv,kv,cv); + } break; case SendData: if (PktNumNew==kv->PktNum) @@ -1015,8 +1261,17 @@ } break; case SendFile: - if (PktNumNew==kv->PktNum) + if (PktNumNew==kv->PktNum) { + if (kv->KmtMy.CAPAS & KMT_CAP_FILATTR) + KmtSendNextFileAttr(fv,kv,cv); + else + KmtSendNextData(fv,kv,cv); + } + break; + case SendFileAttr: + if (PktNumNew==kv->PktNum) { KmtSendNextData(fv,kv,cv); + } break; case SendData: if (PktNumNew==kv->PktNum) Modified: trunk/teraterm/ttpset/ttset.c =================================================================== --- trunk/teraterm/ttpset/ttset.c 2012-01-26 12:28:44 UTC (rev 4809) +++ trunk/teraterm/ttpset/ttset.c 2012-01-29 13:04:12 UTC (rev 4810) @@ -900,6 +900,10 @@ /* Kermit log -- special option */ if (GetOnOff(Section, "KmtLog", FName, FALSE)) ts->LogFlag |= LOG_KMT; + if (GetOnOff(Section, "KmtLongPacket", FName, FALSE)) + ts->KermitOpt |= KmtOptLongPacket; + if (GetOnOff(Section, "KmtFileAttr", FName, FALSE)) + ts->KermitOpt |= KmtOptFileAttr; // Enable language selection -- special option if (!GetOnOff(Section, "LanguageSelection", FName, TRUE)) @@ -2159,6 +2163,8 @@ /* Kermit log -- special option */ WriteOnOff(Section, "KmtLog", FName, (WORD) (ts->LogFlag & LOG_KMT)); + WriteOnOff(Section, "KmtLongPacket", FName, (WORD) (ts->KermitOpt & KmtOptLongPacket)); + WriteOnOff(Section, "KmtFileAttr", FName, (WORD) (ts->KermitOpt & KmtOptFileAttr)); // Enable language selection -- special option if ((ts->MenuFlag & MF_NOLANGUAGE) == 0)