diff --git a/src/dmr_csbk.c b/src/dmr_csbk.c index 12effcd..8bfb1fe 100644 --- a/src/dmr_csbk.c +++ b/src/dmr_csbk.c @@ -22,31 +22,33 @@ void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8 int csbk_pf = 0; int csbk_o = 0; int csbk_fid = 0; - uint64_t csbk_data = 0; - int csbk = 0; long int ccfreq = 0; - if(IrrecoverableErrors == 0 && CRCCorrect == 1) + csbk_lb = ( (cs_pdu[0] & 0x80) >> 7 ); + csbk_pf = ( (cs_pdu[0] & 0x40) >> 6 ); + csbk_o = cs_pdu[0] & 0x3F; + csbk_fid = cs_pdu[1]; //feature set id + + //check, regardless of CRC err + if (IrrecoverableErrors == 0) { - - csbk_lb = ( (cs_pdu[0] & 0x80) >> 7 ); - csbk_pf = ( (cs_pdu[0] & 0x40) >> 6 ); - csbk_o = cs_pdu[0] & 0x3F; - csbk_fid = cs_pdu[1]; //feature set id - csbk_pf = ( (cs_pdu[1] & 0x80) >> 7); - csbk = ((cs_pdu[0] & 0x3F) << 8) | cs_pdu[1]; //opcode and fid combo set - - //update time to prevent random 'Control Channel Signal Lost' hopping - //in the middle of voice call on current Control Channel (con+ and t3) - state->last_cc_sync_time = time(NULL); - + //Hytera XPT CSBK Check -- if bits 0 and 1 are used as lcss, gi, ts, then this bit may be set on + if (csbk_fid == 0x68 && csbk_o == 0x0A) csbk_pf = 0; if (csbk_pf == 1) //check the protect flag, don't run if set { fprintf (stderr, "%s", KRED); fprintf (stderr, "\n Protected Control Signalling Block(s)"); fprintf (stderr, "%s", KNRM); } + } + + if(IrrecoverableErrors == 0 && CRCCorrect == 1) + { + + //update time to prevent random 'Control Channel Signal Lost' hopping + //in the middle of voice call on current Control Channel (con+ and t3) + state->last_cc_sync_time = time(NULL); if (csbk_pf == 0) //okay to run { @@ -209,6 +211,7 @@ void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8 SetFreq(opts->rigctl_sockfd, freq); state->p25_vc_freq[0] = state->p25_vc_freq[1] = freq; opts->p25_is_tuned = 1; //set to 1 to set as currently tuned so we don't keep tuning nonstop + dmr_reset_blocks (opts, state); //reset all block gathering since we are tuning away } //rtl_udp @@ -217,6 +220,7 @@ void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8 rtl_udp_tune (opts, state, freq); state->p25_vc_freq[0] = state->p25_vc_freq[1] = freq; opts->p25_is_tuned = 1; + dmr_reset_blocks (opts, state); //reset all block gathering since we are tuning away } } @@ -624,7 +628,56 @@ void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8 //experimental, but seems to be working well if (csbk_fid == 0x10) { - //Cap+ Channel Status PDU + + //Cap+ Something + if (csbk_o == 0x3A) + { + //initial line break + fprintf (stderr, "\n"); + fprintf (stderr, "%s", KYEL); + + fprintf (stderr, " Capacity Plus CSBK 0x3A "); + + // 01:15:08 Sync: +DMR slot1 [slot2] | Color Code=03 | CSBK + // Capacity Plus Channel Status - FL: 3 TS: 1 RS: 0 - Rest Channel 1 - Single Block + // Ch1: Rest Ch2: Idle Ch3: Idle Ch4: Idle + // Ch5: Idle Ch6: Idle Ch7: Idle Ch8: Idle + // DMR PDU Payload [BE][10][E1][00][00][00][00][00][00][00][4E][15] + // 01:15:08 Sync: +DMR [slot1] slot2 | Color Code=03 | CSBK + + //FL, TS, and Rest Channel seem to be in same location as the Channel Status CSBK + //other values are currently unknown + // DMR PDU Payload [BA][10][C1][3B][61][11][51][00][00][00][3D][D6] + // 01:15:08 Sync: +DMR slot1 [slot2] | Color Code=03 | CSBK + // DMR PDU Payload [BA][10][E1][3B][61][11][51][00][00][00][46][BE] + + } + + //Cap+ Neighbors + if (csbk_o == 0x3B) + { + + //initial line break + fprintf (stderr, "\n"); + fprintf (stderr, "%s", KYEL); + + fprintf (stderr, " Capacity Plus Neighbor List "); + + uint8_t nl[6]; //neighbors numerical value + uint8_t nr[6]; //neighbors current rest channel + memset (nl, 0, sizeof (nl)); + memset (nr, 0, sizeof (nr)); + + for (int i = 0; i < 6; i++) + { + nl[i] = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[32+(i*8)], 4); + nr[i] = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[36+(i*8)], 4); + if (nl[i]) fprintf (stderr, "S: %d R(%d) ", nl[i], nr[i]); + } + + } + + //Cap+ Channel Status if (csbk_o == 0x3E) { @@ -634,8 +687,8 @@ void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8 uint8_t fl = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[16], 2); uint8_t ts = cs_pdu_bits[18]; //timeslot this PDU occurs in - uint8_t res = cs_pdu_bits[19]; //unknown, always seems to be 0 - uint8_t rest_channel = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[20], 4); + uint8_t res = cs_pdu_bits[19]; //unknown, always seems to be 0 -- could be used in larger Cap+? like a bank switch? + uint8_t rest_channel = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[20], 4); uint8_t group_tally = 0; //set this to the number of active group channels tallied uint8_t block_num = state->cap_plus_block_num; @@ -809,6 +862,7 @@ void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8 SetFreq(opts->rigctl_sockfd, state->trunk_chan_map[j+1]); state->p25_vc_freq[0] = state->p25_vc_freq[1] = state->trunk_chan_map[j+1]; opts->p25_is_tuned = 1; //set to 1 to set as currently tuned so we don't keep tuning nonstop + dmr_reset_blocks (opts, state); //reset all block gathering since we are tuning away j = 11; //break loop } @@ -818,6 +872,7 @@ void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8 rtl_udp_tune (opts, state, state->trunk_chan_map[j+1]); state->p25_vc_freq[0] = state->p25_vc_freq[1] = state->trunk_chan_map[j+1]; opts->p25_is_tuned = 1; + dmr_reset_blocks (opts, state); //reset all block gathering since we are tuning away j = 11; //break loop } @@ -849,154 +904,20 @@ void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8 } //opcode == 0x3E } //fid == 0x10 - //Capacity+ Section -- fallback if issues arise - // if (csbk_fid == 0x10) - // { - // //not quite sure how these tuning rules will go over - // //if they don't work so well, may just fall back to - // //a 'follow rest channel on no sync' only approach - // if (csbk_o == 0x3E) - // { - - // //initial line break - // fprintf (stderr, "\n"); - // fprintf (stderr, "%s", KYEL); - - // uint8_t fl = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[16], 2); - // uint8_t ts = cs_pdu_bits[18]; //accurate? - // uint8_t res = cs_pdu_bits[19]; //unknown?? - // uint8_t rest_channel = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[20], 4); - - // uint8_t ch[8]; //one bit per channel - // uint8_t tg = 0; - // uint32_t tghex = 0; //combined all tgs for debug - // int i, j, k; - - // //tg and channel info for trunking purposes - // uint8_t t_tg[9]; - // memset (t_tg, 0, sizeof(t_tg)); - - // k = 0; - // if (rest_channel != state->dmr_rest_channel) - // { - // state->dmr_rest_channel = rest_channel; - // } - // for (int i = 0; i < 8; i++) - // { - // ch[i] = cs_pdu_bits[i+24]; - // } - - // //assign to cc freq to follow during no sync - // if (state->trunk_chan_map[rest_channel] != 0) - // { - // state->p25_cc_freq = state->trunk_chan_map[rest_channel]; - // //set to always tuned - // opts->p25_is_tuned = 1; - // } - - // fprintf (stderr, " Capacity Plus Channel Status - FL: %d TS: %d RS: %d - Rest Channel %d", fl, ts, res, rest_channel); - - // fprintf (stderr, "\n "); - // for (i = 0; i < 8; i++) - // { - // fprintf (stderr, "Ch%d: ", i+1); - // if (ch[i] != 0) - // { - // tg = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[k*8+32], 8); - // if (tg != 0) fprintf (stderr, " %03d ", tg); - // else fprintf (stderr, "Priv "); //observed 000s for TG value seem to appear during a Cap+ Private TXI call - // //add values to trunking tg/channel potentials - // t_tg[i] = tg; - // k++; - - // } - // else if (i+1 == rest_channel) fprintf (stderr, "Rest "); - // else fprintf (stderr, "Idle "); - - // if (i == 3) fprintf (stderr, "\n "); - // } - // tghex = (uint32_t)ConvertBitIntoBytes(&cs_pdu_bits[32], 24); - // //fprintf (stderr, "\n TG Hex = 0x%06X", tghex); - // state->dmr_mfid = 0x10; - // sprintf (state->dmr_branding, "%s", "Motorola"); - // sprintf (state->dmr_branding_sub, "%s", "Cap+ "); - - // //nullify any previous TIII data (bugfix for bad assignments or system type switching) - // sprintf(state->dmr_site_parms, "%s", ""); - - // fprintf (stderr, "%s", KNRM); - - // //Skip tuning group calls if group calls are disabled - // if (opts->trunk_tune_group_calls == 0) goto SKIPCAP; - - // //don't tune if currently a vc on the current channel - // if ( (time(NULL) - state->last_vc_sync_time > 2) ) - // { - // for (j = 0; j < 8; j++) //go through the channels stored looking for active ones to tune to - // { - // char mode[8]; //allow, block, digital, enc, etc - - // //if we are using allow/whitelist mode, then write 'B' to mode for block - // //comparison below will look for an 'A' to write to mode if it is allowed - // if (opts->trunk_use_allow_list == 1) sprintf (mode, "%s", "B"); - - // for (int i = 0; i < state->group_tally; i++) - // { - // if (state->group_array[i].groupNumber == t_tg[j]) - // { - // fprintf (stderr, " [%s]", state->group_array[i].groupName); - // strcpy (mode, state->group_array[i].groupMode); - // } - // } - - // //no more 0 reporting, that was some bad code that caused that issue - // //without priority, this will tune the first one it finds (if group isn't blocked) - // if (t_tg[j] != 0 && state->p25_cc_freq != 0 && opts->p25_trunk == 1 && (strcmp(mode, "B") != 0) && (strcmp(mode, "DE") != 0)) - // { - // if (state->trunk_chan_map[j+1] != 0) //if we have a valid frequency - // { - // //RIGCTL - // if (opts->use_rigctl == 1) - // { - // if (opts->setmod_bw != 0 ) SetModulation(opts->rigctl_sockfd, opts->setmod_bw); - // SetFreq(opts->rigctl_sockfd, state->trunk_chan_map[j+1]); - // state->p25_vc_freq[0] = state->p25_vc_freq[1] = state->trunk_chan_map[j+1]; - // opts->p25_is_tuned = 1; //set to 1 to set as currently tuned so we don't keep tuning nonstop - // j = 11; //break loop - // } - - // //rtl_udp - // else if (opts->audio_in_type == 3) - // { - // rtl_udp_tune (opts, state, state->trunk_chan_map[j+1]); - // state->p25_vc_freq[0] = state->p25_vc_freq[1] = state->trunk_chan_map[j+1]; - // opts->p25_is_tuned = 1; - // j = 11; //break loop - // } - - // } - // } - - // } - // } - - // } - // SKIPCAP: ; //do nothing - // } - //Connect+ Section if (csbk_fid == 0x06) { if (csbk_o == 0x01) { - //initial line break - fprintf (stderr, "\n"); uint8_t nb1 = cs_pdu[2] & 0x3F; uint8_t nb2 = cs_pdu[3] & 0x3F; uint8_t nb3 = cs_pdu[4] & 0x3F; uint8_t nb4 = cs_pdu[5] & 0x3F; - uint8_t nb5 = cs_pdu[6] & 0x3F; + uint8_t nb5 = cs_pdu[6] & 0x3F; + + //initial line break + fprintf (stderr, "\n"); fprintf (stderr, "%s", KYEL); fprintf (stderr, " Connect Plus Neighbors\n"); fprintf (stderr, " NB1(%02d), NB2(%02d), NB3(%02d), NB4(%02d), NB5(%02d)", nb1, nb2, nb3, nb4, nb5); @@ -1080,6 +1001,7 @@ void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8 opts->p25_is_tuned = 1; //set to 1 to set as currently tuned so we don't keep tuning nonstop state->is_con_plus = 1; //flag on state->last_vc_sync_time = time(NULL); //bugfix: set sync here so we don't immediately tune back to CC constantly. + dmr_reset_blocks (opts, state); //reset all block gathering since we are tuning away } //rtl_udp @@ -1090,6 +1012,7 @@ void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8 opts->p25_is_tuned = 1; state->is_con_plus = 1; //flag on state->last_vc_sync_time = time(NULL); //bugfix: set sync here so we don't immediately tune back to CC constantly. + dmr_reset_blocks (opts, state); //reset all block gathering since we are tuning away } } @@ -1102,7 +1025,201 @@ void dmr_cspdu (dsd_opts * opts, dsd_state * state, uint8_t cs_pdu_bits[], uint8 fprintf (stderr, "%s", KNRM); - } + } //end Connect+ + + //Hytera XPT + if (csbk_fid == 0x68) + { + //XPT Site Status -- Very Very Experimental, WIP, Don't be surprised by bad decodes or broken trunking + if (csbk_o == 0x0A) + { + //initial line break + fprintf (stderr, "\n"); + fprintf (stderr, "%s", KYEL); + + uint8_t xpt_ch[6]; //one tg/call per timeslot + uint16_t tg = 0; //8-bit TG value or hash + int i, j; + + //tg and channel info for trunking purposes + uint8_t t_tg[24]; + memset (t_tg, 0, sizeof(t_tg)); + + uint8_t xpt_seq = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[0], 2); //this replaces the CSBK lb and pf + uint8_t xpt_free = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[16], 4); //free repeater channel + uint8_t xpt_bank = 0; + + if (xpt_seq) xpt_bank = xpt_seq*6; + + //get 2-bit status values for each 6 channels (timeslots) + for (i = 0; i < 6; i++) xpt_ch[i] = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[20+(i*2)], 2); + + fprintf (stderr, " Hytera XPT Site Status - Free RPT: %d SN: %d\n ", xpt_free+1, xpt_seq); + + //Print List of Channels and Activity + for (i = 0; i < 6; i++) + { + fprintf (stderr, "Ch%02d: ", i+xpt_bank+1); //(xpt_seq*6) + tg = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[i*8+32], 8); + fprintf (stderr, "ST-%X", xpt_ch[i]); //status bits value 0,1,2, or 3 + if (tg != 0) fprintf (stderr, " %03d ", tg); + else + { + if (xpt_ch[i] == 3) fprintf (stderr, " Null "); //offline?, free? but also group call if the TG value is populated? Wut? Null should cover both cases...maybe + else if (xpt_ch[i] == 2) fprintf (stderr, " Priv "); //This seems to be used on both private data and private calls, but one or the other has a 0 TG value? + else if (xpt_ch[i] == 1) fprintf (stderr, " Unk "); //the 01 value has not been observed as of yet, group data? + else if (xpt_ch[i] == 0) fprintf (stderr, " Idle "); //Idle appears to always be a 0, or could indicate Group Data (including status CSBK and VLC/TLC) + } + + if (i == 2) fprintf (stderr, "\n "); + + + //add values to trunking tg/channel potentials + if (tg != 0) t_tg[i+(xpt_seq*6)] = tg; + + } + + //add string for ncurses terminal display + sprintf (state->dmr_site_parms, "Free RPT - %d ", xpt_free+1); + + //assign to cc freq to follow during no sync + //current theory is that user should set channel 0 as the 'home repeater' frequency + //this can change if this is ever tested by anybody properly + if (state->trunk_chan_map[0] != 0) + { + state->p25_cc_freq = state->trunk_chan_map[0]; + //set to always tuned + opts->p25_is_tuned = 1; + } + + //Skip tuning calls if group calls are disabled + if (opts->trunk_tune_group_calls == 0) goto SKIPXPT; + + //don't tune if vc on the current channel + if ( (time(NULL) - state->last_vc_sync_time > 2) ) + { + for (j = 0; j < 6; j++) //go through the channels stored looking for active ones to tune to + { + char mode[8]; //allow, block, digital, enc, etc + + //if we are using allow/whitelist mode, then write 'B' to mode for block + //comparison below will look for an 'A' to write to mode if it is allowed + if (opts->trunk_use_allow_list == 1) sprintf (mode, "%s", "B"); + + //this won't work properly on hashed TGT values + //unless users load a TGT hash in a csv file + //and hope it doesn't clash with other normal TG values + for (int i = 0; i < state->group_tally; i++) + { + if (state->group_array[i].groupNumber == t_tg[j+(xpt_seq*6)]) + { + fprintf (stderr, " [%s]", state->group_array[i].groupName); + strcpy (mode, state->group_array[i].groupMode); + } + } + + //without priority, this will tune the first one it finds (if group isn't blocked) + if (t_tg[j+(xpt_seq*6)] != 0 && state->p25_cc_freq != 0 && opts->p25_trunk == 1 && (strcmp(mode, "B") != 0) && (strcmp(mode, "DE") != 0)) + { + if (state->trunk_chan_map[j+(xpt_seq*6)+1] != 0) //if we have a valid frequency + { + //RIGCTL + if (opts->use_rigctl == 1) + { + if (opts->setmod_bw != 0 ) SetModulation(opts->rigctl_sockfd, opts->setmod_bw); + SetFreq(opts->rigctl_sockfd, state->trunk_chan_map[j+(xpt_seq*6)+1]); + state->p25_vc_freq[0] = state->p25_vc_freq[1] = state->trunk_chan_map[j+1]; + opts->p25_is_tuned = 1; //set to 1 to set as currently tuned so we don't keep tuning nonstop + dmr_reset_blocks (opts, state); //reset all block gathering since we are tuning away + j = 11; //break loop + } + + //rtl_udp + else if (opts->audio_in_type == 3) + { + rtl_udp_tune (opts, state, state->trunk_chan_map[j+(xpt_seq*6)+1]); + state->p25_vc_freq[0] = state->p25_vc_freq[1] = state->trunk_chan_map[j+1]; + opts->p25_is_tuned = 1; + dmr_reset_blocks (opts, state); //reset all block gathering since we are tuning away + j = 11; //break loop + } + + } + } + + } + } //end tuning + + SKIPXPT: ; + + sprintf (state->dmr_branding_sub, "XPT "); + + //Trunking Note: My understanding is that XPT calls always start on the 'home' repeater, and then expand + //outward to the free repeaters, so its probably easiest for a user to load the frequencies in a csv file + //and let FME scan through them as opposed to actively trunking, it will spend most of the time on the home repeater + //and if calls occur on the home repeater, it'll be too busy with those to go to the free repeater + //DSD-FME doesn't have any sort of TG priority, so its first come first served, just block or allow + + } + + //XPT Site Information - Adj Site Info + if (csbk_o == 0x0B) + { + + //initial line break + fprintf (stderr, "\n"); + fprintf (stderr, "%s", KYEL); + + // fprintf (stderr, " Hytera XPT CSBK 0x0B "); + + //wraithe's theory -- seems pretty good to me + //still only a theory though until can be verified + //by having a system with precisely known variables + + // Site ID [5 bits] + // Unknown_3 [3 bits] + // Free Repeater on site [4 bits] + // Unknown_4 [4 bits] + + // 11:44:58 Sync: +DMR slot1 [slot2] | Color Code=01 | CSBK + // Hytera XPT CSBK 0x0B + // DMR PDU Payload [0B][68][10][20][18][10][20][10][28][20][EF][DA] + + // repeat x3 (for total of 4 sites) + int i; + uint8_t xpt_site_id[4]; + uint8_t xpt_site_rp[4]; + uint8_t xpt_site_u1[4]; + uint8_t xpt_site_u2[4]; + + for (i = 0; i < 4; i++) + { + xpt_site_id[i] = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[16+(i*16)], 5); + xpt_site_u1[i] = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[21+(i*16)], 3); + xpt_site_rp[i] = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[24+(i*16)], 4); + xpt_site_u2[i] = (uint8_t)ConvertBitIntoBytes(&cs_pdu_bits[28+(i*16)], 4); + } + + fprintf (stderr, " Hytera XPT Site Id: %d - Free RPT: %d", xpt_site_id[0], xpt_site_rp[0]); + // fprintf (stderr, " RS1: %d RS2: %d", xpt_site_u1[0], xpt_site_u2[1]); //debug + + fprintf (stderr, "\n"); + fprintf (stderr, " XPT Adj Site(s): "); + for (i = 1; i < 4; i++) + { + if (xpt_site_id[i] != 0) + { + fprintf (stderr, "%d (%d) ", xpt_site_id[i], xpt_site_rp[i]); + // fprintf (stderr, "RS1: %d RS2: %d - ", xpt_site_u1[i], xpt_site_u2[i]); //debug + } + } + + sprintf (state->dmr_branding_sub, "XPT "); + + + } + + } //end Hytera XPT section } END: diff --git a/src/dmr_flco.c b/src/dmr_flco.c index 3be4918..47e1b51 100644 --- a/src/dmr_flco.c +++ b/src/dmr_flco.c @@ -30,18 +30,43 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C int is_cap_plus = 0; int is_alias = 0; int is_gps = 0; + + //XPT 'Things' + int is_xpt = 0; + uint8_t xpt_hand = 0; //handshake + uint8_t xpt_free = 0; //free repeater + uint8_t xpt_int = 0; //xpt channel to interrupt (channel/repeater call should occur on?) + uint8_t xpt_res_a = 0; //unknown values of other bits of the XPT LC + uint8_t xpt_res_b = 0; //unknown values of other bits of the XPT LC + uint8_t xpt_res_c = 0; //unknown values of other bits of the XPT LC + uint8_t target_hash[24]; //for XPT (and others if desired, get the hash and compare against SLC or XPT Status CSBKs) + uint8_t tg_hash = 0; //value of the hashed TG + uint8_t slot = state->currentslot; uint8_t unk = 0; //flag for unknown FLCO + FID combo - - pf = (uint8_t)(lc_bits[0]); //Protect Flag - reserved = (uint8_t)(lc_bits[1]); //Reserved + pf = (uint8_t)(lc_bits[0]); //Protect Flag -- Hytera XPT uses this to signify which TS the PDU is on + reserved = (uint8_t)(lc_bits[1]); //Reserved -- Hytera XPT G/I bit; 0 - Individual; 1 - Group; flco = (uint8_t)ConvertBitIntoBytes(&lc_bits[2], 6); //Full Link Control Opcode fid = (uint8_t)ConvertBitIntoBytes(&lc_bits[8], 8); //Feature set ID (FID) so = (uint8_t)ConvertBitIntoBytes(&lc_bits[16], 8); //Service Options target = (uint32_t)ConvertBitIntoBytes(&lc_bits[24], 24); //Target or Talk Group source = (uint32_t)ConvertBitIntoBytes(&lc_bits[48], 24); + //read ahead a little to get this for the xpt flag + if (IrrecoverableErrors == 0 && flco == 0x09 && fid == 0x68) + { + sprintf (state->dmr_branding, "%s", " Hytera"); + sprintf (state->dmr_branding_sub, "XPT "); + } + + //look at the dmr_branding_sub for the XPT string + //branding sub is set at CSBK(68-3A and 3B), SLCO 8, and here on 0x09 + if (strcmp (state->dmr_branding_sub, "XPT ") == 0) is_xpt = 1; + + //hytera XPT -- disable the pf flag, is used for TS value in some Hytera XPT PDUs + if (is_xpt) pf = 0; + //check protect flag if (pf == 1) { @@ -53,18 +78,6 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C goto END_FLCO; } - //look for Cap+ on VLC header, then set source and/or rest channel appropriately - if (IrrecoverableErrors == 0 && type == 1 && fid == 0x10 && (flco == 0x04 || flco == 0x07) ) //0x07 appears to be a cap+ txi private call - { - is_cap_plus = 1; - capsite = (uint8_t)ConvertBitIntoBytes(&lc_bits[48], 4); //can't verify, just speculating - restchannel = (int)ConvertBitIntoBytes(&lc_bits[52], 4); //was 48,8 - source = (uint32_t)ConvertBitIntoBytes(&lc_bits[56], 16); - // sprintf (state->dmr_branding, "%s", "Motorola"); - // sprintf (state->dmr_branding_sub, "%s", "Cap+ "); - } - - if (IrrecoverableErrors == 0) { @@ -72,25 +85,34 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C else state->dmr_flcoR = flco; //Embedded Talker Alias Header Only (format and len storage) - if (fid == 0 && type == 3 && flco == 0x04) + if ( (fid == 0 || fid == 0x68) && type == 3 && flco == 0x04) { dmr_embedded_alias_header(opts, state, lc_bits); } //Embedded Talker Alias Header (continuation) and Blocks - if (fid == 0 && type == 3 && flco > 0x3 && flco < 0x08) + if ( (fid == 0 || fid == 0x68) && type == 3 && flco > 0x03 && flco < 0x08) { is_alias = 1; dmr_embedded_alias_blocks(opts, state, lc_bits); } //Embedded GPS - if (fid == 0 && type == 3 && flco == 0x08) + if ( (fid == 0 || fid == 0x68) && fid == 0 && type == 3 && flco == 0x08) { is_gps = 1; dmr_embedded_gps(opts, state, lc_bits); } + //look for Cap+ on VLC header, then set source and/or rest channel appropriately + if (type == 1 && fid == 0x10 && (flco == 0x04 || flco == 0x07) ) //0x07 appears to be a cap+ txi private call + { + is_cap_plus = 1; + capsite = (uint8_t)ConvertBitIntoBytes(&lc_bits[48], 4); //don't believe so + restchannel = (int)ConvertBitIntoBytes(&lc_bits[52], 4); // + source = (uint32_t)ConvertBitIntoBytes(&lc_bits[56], 16); + } + //Unknown CapMax/Moto Things if (fid == 0x10 && (flco == 0x08 || flco == 0x28)) { @@ -113,8 +135,16 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C { fprintf (stderr, "%s \n", KRED); fprintf (stderr, " SLOT %d", state->currentslot+1); - fprintf (stderr, " Data Terminator (TD_LC)"); - fprintf (stderr, "%s ", KNRM); + fprintf (stderr, " Data Terminator (TD_LC) "); + fprintf (stderr, "%s", KNRM); + + //reset data header format storage + state->data_header_format[slot] = 7; + //flag off data header validity + state->data_header_valid[slot] = 0; + //flag off conf data flag + state->data_conf_data[slot] = 0; + goto END_FLCO; } @@ -132,8 +162,127 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C goto END_FLCO; } + //look for any Hytera XPT system, adjust TG to 16-bit allocation + //Groups use only 8 out of 16, but 16 always seems to be allocated + //private calls use 16-bit target values hased to 8-bit in the site status csbk + //the TLC preceeds the VLC for a 'handshake' call setup in XPT + + //truncate if XPT is set + if (is_xpt == 1) + { + target = (uint32_t)ConvertBitIntoBytes(&lc_bits[32], 16); //16-bit allocation + source = (uint32_t)ConvertBitIntoBytes(&lc_bits[56], 16); //16-bit allocation + + //the crc8 hash is the value represented in the CSBK when dealing with private calls + for (int i = 0; i < 16; i++) target_hash[i] = lc_bits[32+i]; + tg_hash = crc8 (target_hash, 16); + } + + //XPT Call 'Grant' Setup Occurs in TLC (TermLC Handshake) with a flco 0x09 + if (fid == 0x68 && flco == 0x09) + { + //The CSBK always uses an 8-bit TG; The White Papers (user manuals) say 8-bit TG and 16-bit SRC addressing + //It seems that private calls and indiv data calls use a hash of their 8 bit tgt values in the CSBK + xpt_free = (uint8_t)ConvertBitIntoBytes(&lc_bits[24], 4); //24 and 4 on 0x09 + xpt_hand = (uint8_t)ConvertBitIntoBytes(&lc_bits[28], 4); //handshake kind: 0 - ordinary; 1-2 Interrupts; 3-15 reserved; + + target = (uint32_t)ConvertBitIntoBytes(&lc_bits[32], 16); //16-bit allocation + source = (uint32_t)ConvertBitIntoBytes(&lc_bits[56], 16); //16-bit allocation + + //the bits that are left behind + xpt_res_a = (uint8_t)ConvertBitIntoBytes(&lc_bits[16], 8); //Where the SVC bits would usually be + xpt_res_b = (uint8_t)ConvertBitIntoBytes(&lc_bits[48], 8); //where the first 8 bits of the SRC would be + xpt_res_c = (uint8_t)ConvertBitIntoBytes(&lc_bits[72], 8); //some unknown 8 bit value after the SRC + + //speculation: 16,4 may be the current repeater channel this call will occur on? or the channel to interrupt?, could also be 8 bits? + xpt_int = (uint8_t)ConvertBitIntoBytes(&lc_bits[16], 4); + + //the crc8 hash is the value represented in the CSBK when dealing with private calls + for (int i = 0; i < 16; i++) target_hash[i] = lc_bits[32+i]; + tg_hash = crc8 (target_hash, 16); //This is a different poly, but using as placeholder + + fprintf (stderr, "%s \n", KGRN); + fprintf (stderr, " SLOT %d ", state->currentslot+1); + fprintf(stderr, "TGT=%u SRC=%u ", target, source); + + // Here are some of the RIDs with hash address seen in that sample: + // 930 = 88 + // 9317 = 198 + // 10002 = 187 + + if (opts->payload == 1) fprintf(stderr, "HASH=%d ", tg_hash); + // if (opts->payload == 1) fprintf(stderr, "HSK [%X] ", xpt_hand); + + if (opts->payload == 1) fprintf(stderr, "CH=%X ", xpt_int+1); //repeater channel to 'interrupt' with this call? + if (opts->payload == 1) fprintf(stderr, "FLCO=0x%02X FID=0x%02X ", flco, fid); + + // if (opts->payload == 1) fprintf(stderr, "RS [%02X][%02X][%02X] ", xpt_res_a, xpt_res_b, xpt_res_c); + + fprintf (stderr, "Hytera XPT "); + if (reserved == 1) fprintf (stderr, "Group "); //according to observation + else fprintf (stderr, "Private "); //according to observation + fprintf (stderr, "Call Grant "); + + fprintf (stderr, "%s", KYEL); + fprintf (stderr, "F-Rpt %d", xpt_free+1); + fprintf (stderr, "%s ", KNRM); + + //add string for ncurses terminal display + sprintf (state->dmr_site_parms, "Free RPT - %d ", xpt_free+1); + + is_xpt = 1; + goto END_FLCO; + } + + //XPT 'Handshake' -- not observed as of yet on any samples + if ( fid == 0x68 && (flco == 0x2E || flco == 0x2F) ) + { + + //all below is according to the patent, but never observed on any samples I have + target = (uint32_t)ConvertBitIntoBytes(&lc_bits[24], 24); //24-bit values according to patent + source = (uint32_t)ConvertBitIntoBytes(&lc_bits[56], 24); //24-bit values according to patent + + xpt_free = (uint8_t)ConvertBitIntoBytes(&lc_bits[16], 4); //16 and 4 on 2E and 2F + xpt_hand = (uint8_t)ConvertBitIntoBytes(&lc_bits[20], 4); //handshake kind: 0 - ordinary; 1-2 Interrupts; 3-15 reserved; + xpt_int = (uint8_t)ConvertBitIntoBytes(&lc_bits[24], 8); //Interrupt Designated Channel (No idea?) + + fprintf (stderr, "%s \n", KGRN); + fprintf (stderr, " SLOT %d ", state->currentslot+1); + fprintf(stderr, "TGT=%u SRC=%u ", target, source); + + if (opts->payload == 1) fprintf(stderr, "HASH=%d ", tg_hash); + // if (opts->payload == 1) fprintf(stderr, "HSK=[%X] ", xpt_hand); + + if (opts->payload == 1) fprintf(stderr, "FLCO=0x%02X FID=0x%02X ", flco, fid); + fprintf (stderr, "Hytera XPT "); + if (reserved == 1) fprintf (stderr, "Group "); + else fprintf (stderr, "Private "); + + fprintf (stderr, "Call "); + if (flco == 0x2E) fprintf (stderr, "Request "); + if (flco == 0x2F) fprintf (stderr, "Response "); + + fprintf (stderr, "%s ", KNRM); + is_xpt = 1; + goto END_FLCO; + + } + + //Hytera XPT "something" -- seen on Embedded + if (fid == 0x68 && flco == 0x13) + { + if (type == 1) fprintf (stderr, "%s \n", KCYN); + if (type == 2) fprintf (stderr, "%s \n", KCYN); + if (type == 3) fprintf (stderr, "%s", KCYN); + fprintf (stderr, " SLOT %d", state->currentslot+1); + fprintf (stderr, " Hytera "); + unk = 1; + goto END_FLCO; + } + //unknown other manufacturer or OTA ENC, etc. - if (fid != 0 && fid != 0x68 && fid != 0x10) //removed tait from the list + //removed tait from the list, added hytera 0x08 + if (fid != 0 && fid != 0x68 && fid != 0x10 && fid != 0x08) { if (type == 1) fprintf (stderr, "%s \n", KYEL); if (type == 2) fprintf (stderr, "%s \n", KYEL); @@ -147,11 +296,9 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C } //will want to continue to observe for different flco and fid combinations to find out their meaning - //Standard Addressing/Cap+ Addressing (trying to avoid embedded alias and gps, etc) if(IrrecoverableErrors == 0 && is_alias == 0 && is_gps == 0) { //set overarching manufacturer in use when non-standard feature id set is up - //may not want to set moto 0x10 here either, lots of radios use that set as well if (fid != 0) state->dmr_mfid = fid; if (type != 2) //VLC and EMB, set our target, source, so, and fid per channel @@ -222,6 +369,7 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C fprintf (stderr, " SLOT %d ", state->currentslot+1); fprintf(stderr, "TGT=%u SRC=%u ", target, source); + if (opts->payload == 1 && is_xpt == 1) fprintf(stderr, "HASH=%d ", tg_hash); if (opts->payload == 1) fprintf(stderr, "FLCO=0x%02X FID=0x%02X SVC=0x%02X ", flco, fid, so); //0x04 and 0x05 on a TLC seem to indicate a Cap + Private Call Terminator (perhaps one for each MS) @@ -229,7 +377,10 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C //0x23 on the Embedded Voice Burst Sync seems to indicate a Cap+ or Cap+ TXI Private Call in progress //0x20 on the Embedded Voice Burst Sync seems to indicate a Moto (non-specific) Group Call in progress //its possible that both EMB FID 0x10 FLCO 0x20 and 0x23 are just Moto but non-specific (observed 0x20 on Tier 2) - if (flco == 0x4 || flco == 0x5 || flco == 0x7 || flco == 0x23) //Cap+ Things + + if (fid == 0x68) sprintf (state->call_string[slot], " Hytera "); + + else if (flco == 0x4 || flco == 0x5 || flco == 0x7 || flco == 0x23) //Cap+ Things { sprintf (state->call_string[slot], " Cap+"); fprintf (stderr, "Cap+ "); @@ -244,12 +395,12 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C fprintf (stderr, "Private "); } } - else if (flco == 0x3) //UU_V_Ch_Usr + else if (flco == 0x3) //UU_V_Ch_Usr -- still valid on hytera VLC { sprintf (state->call_string[slot], " Private "); fprintf (stderr, "Private "); } - else //Grp_V_Ch_Usr + else //Grp_V_Ch_Usr -- still valid on hytera VLC { sprintf (state->call_string[slot], " Group "); fprintf (stderr, "Group "); @@ -273,64 +424,74 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C //REMUS! Uncomment Line Below if desired // else strcat (state->call_string[slot], " "); - /* Check the "Service Option" bits */ - if(so & 0x30) + //Motorola FID 0x10 Only + if (fid == 0x10) { - /* Experimentally determined with DSD+, - * is equal to 0x2, this is a TXI call */ - if((so & 0x30) == 0x20) + /* Check the "Service Option" bits */ + if(so & 0x30) + { + /* Experimentally determined with DSD+, + * is equal to 0x2, this is a TXI call */ + if((so & 0x30) == 0x20) + { + //REMUS! Uncomment Line Below if desired + // strcat (state->call_string[slot], " TXI"); + fprintf(stderr, "TXI "); + } + else + { + //REMUS! Uncomment Line Below if desired + // strcat (state->call_string[slot], " RES"); + fprintf(stderr, "RS%d ", (so & 0x30) >> 4); + } + } + if(so & 0x08) { //REMUS! Uncomment Line Below if desired - // strcat (state->call_string[slot], " TXI"); - fprintf(stderr, "TXI "); + // strcat (state->call_string[slot], "-BC "); + fprintf(stderr, "Broadcast "); } - else + if(so & 0x04) { //REMUS! Uncomment Line Below if desired - // strcat (state->call_string[slot], " RES"); - fprintf(stderr, "RS%d ", (so & 0x30) >> 4); + // strcat (state->call_string[slot], "-OVCM "); + fprintf(stderr, "OVCM "); } - } - if(so & 0x08) - { - //REMUS! Uncomment Line Below if desired - // strcat (state->call_string[slot], "-BC "); - fprintf(stderr, "Broadcast "); - } - if(so & 0x04) - { - //REMUS! Uncomment Line Below if desired - // strcat (state->call_string[slot], "-OVCM "); - fprintf(stderr, "OVCM "); - } - if(so & 0x03) - { - if((so & 0x03) == 0x01) + if(so & 0x03) { - //REMUS! Uncomment Line Below if desired - // strcat (state->call_string[slot], "-P1"); - fprintf(stderr, "Priority 1 "); - } - else if((so & 0x03) == 0x02) - { - //REMUS! Uncomment Line Below if desired - // strcat (state->call_string[slot], "-P2"); - fprintf(stderr, "Priority 2 "); - } - else if((so & 0x03) == 0x03) - { - //REMUS! Uncomment Line Below if desired - // strcat (state->call_string[slot], "-P3"); - fprintf(stderr, "Priority 3 "); - } - else /* We should never go here */ - { - //REMUS! Uncomment Line Below if desired - // strcat (state->call_string[slot], " "); - fprintf(stderr, "No Priority "); - } + if((so & 0x03) == 0x01) + { + //REMUS! Uncomment Line Below if desired + // strcat (state->call_string[slot], "-P1"); + fprintf(stderr, "Priority 1 "); + } + else if((so & 0x03) == 0x02) + { + //REMUS! Uncomment Line Below if desired + // strcat (state->call_string[slot], "-P2"); + fprintf(stderr, "Priority 2 "); + } + else if((so & 0x03) == 0x03) + { + //REMUS! Uncomment Line Below if desired + // strcat (state->call_string[slot], "-P3"); + fprintf(stderr, "Priority 3 "); + } + else /* We should never go here */ + { + //REMUS! Uncomment Line Below if desired + // strcat (state->call_string[slot], " "); + fprintf(stderr, "No Priority "); + } + } } + //should rework this back into the upper portion + if (fid == 0x68) fprintf (stderr, "Hytera "); + if (is_xpt) fprintf (stderr, "XPT "); + if (fid == 0x68 && flco == 0x00) fprintf (stderr, "Group "); + if (fid == 0x68 && flco == 0x03) fprintf (stderr, "Private "); + fprintf(stderr, "Call "); @@ -380,8 +541,7 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C END_FLCO: if (unk == 1 || pf == 1) { - // fprintf(stderr, " FLCO=0x%02X FID=0x%02X SVC=0x%02X ", flco, fid, so); - fprintf(stderr, " FLCO=0x%02X FID=0x%02X ", flco, fid); //not all LC PDUs contain SVC bits (see TD_LC) + fprintf(stderr, " FLCO=0x%02X FID=0x%02X SVC=0x%02X ", flco, fid, so); fprintf (stderr, "%s", KNRM); } @@ -648,10 +808,17 @@ void dmr_slco (dsd_opts * opts, dsd_state * state, uint8_t slco_bits[]) //Con+ uint8_t con_netid = (uint8_t)ConvertBitIntoBytes(&slco_bits[8], 8); uint8_t con_siteid = (uint8_t)ConvertBitIntoBytes(&slco_bits[16], 8); + //Cap+ - uint8_t capsite = (uint8_t)ConvertBitIntoBytes(&slco_bits[12], 4); //can't verify, just speculating - uint8_t restchannel = (uint8_t)ConvertBitIntoBytes(&slco_bits[16], 4); //was 12,8 - uint8_t cap_reserved = (uint8_t)ConvertBitIntoBytes(&slco_bits[20], 8); //unknown if any significant value? + uint8_t capsite = (uint8_t)ConvertBitIntoBytes(&slco_bits[22], 3); //Seems more consistent + uint8_t restchannel = (uint8_t)ConvertBitIntoBytes(&slco_bits[16], 4); + uint8_t cap_reserved = (uint8_t)ConvertBitIntoBytes(&slco_bits[20], 2); //significant value? + + //Hytera XPT + uint8_t xpt_free = (uint8_t)ConvertBitIntoBytes(&slco_bits[12], 4); //free repeater + //the next two per SDRTrunk, but only 0 values ever observed here + uint8_t xpt_pri = (uint8_t)ConvertBitIntoBytes(&slco_bits[16], 4); //priority repeater + uint8_t xpt_hash = (uint8_t)ConvertBitIntoBytes(&slco_bits[20], 8); //priority TG hash //initial line break fprintf (stderr, "\n"); @@ -700,20 +867,12 @@ void dmr_slco (dsd_opts * opts, dsd_state * state, uint8_t slco_bits[]) fprintf (stderr, " SLCO Activity Update TS1: %X Hash: %02X TS2: %X Hash: %02X", ts1_act, ts1_hash, ts2_act, ts2_hash); //102 361-2 7.1.3.2 else if (slco == 0x9) { - //remove all setting of branding/sub from SLCO, can cause false positives even with seeming good CRC - - // state->dmr_mfid = 0x10; - // sprintf (state->dmr_branding, "%s", "Motorola"); - // sprintf (state->dmr_branding_sub, "%s", "Con+ "); fprintf (stderr, " SLCO Connect Plus Voice Channel - Net ID: %d Site ID: %d", con_netid, con_siteid); sprintf (state->dmr_site_parms, "%d-%d ", con_netid, con_siteid); } else if (slco == 0xA) { - // state->dmr_mfid = 0x10; - // sprintf (state->dmr_branding, "%s", "Motorola"); - // sprintf (state->dmr_branding_sub, "%s", "Con+ "); fprintf (stderr, " SLCO Connect Plus Control Channel - Net ID: %d Site ID: %d", con_netid, con_siteid); sprintf (state->dmr_site_parms, "%d-%d ", con_netid, con_siteid); @@ -727,19 +886,21 @@ void dmr_slco (dsd_opts * opts, dsd_state * state, uint8_t slco_bits[]) else if (slco == 0xF) { - // state->dmr_mfid = 0x10; - // sprintf (state->dmr_branding, "%s", "Motorola"); - // sprintf (state->dmr_branding_sub, "%s", "Cap+ "); - fprintf (stderr, " SLCO Capacity Plus Site: %d - Rest Channel %d - RS: %02X", capsite+1, restchannel, cap_reserved); - // state->dmr_rest_channel = restchannel; //test without, may work better + fprintf (stderr, " SLCO Capacity Plus Site: %d - Rest Channel %d - RS: %02X", capsite, restchannel, cap_reserved); //assign to cc freq if available if (state->trunk_chan_map[restchannel] != 0) { state->p25_cc_freq = state->trunk_chan_map[restchannel]; } - //nullify any previous TIII data (bugfix for bad assignments or system type switching) - // sprintf(state->dmr_site_parms, "%s", ""); + } + else if (slco == 0x08) + { + //The Priority Repeater and Priority Hash values stem from SDRTrunk, but I've never seen these values not be zeroes + // fprintf (stderr, " SLCO Hytera XPT - Free RPT %d - PRI RPT %d - PRI HASH: %02X", xpt_free, xpt_pri, xpt_hash); + //NOTE: on really busy systems, this free repeater assignment can lag due to the 4 TS requirment to get SLC + fprintf (stderr, " SLCO Hytera XPT - Free RPT %d ", xpt_free+1); + sprintf (state->dmr_branding_sub, "XPT "); } else fprintf (stderr, " SLCO Unknown - %d ", slco); diff --git a/src/dsd_main.c b/src/dsd_main.c index 5c43c46..772b7ba 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -52,7 +52,7 @@ char * FM_banner[9] = { " ██║ ██║ ╚═══██╗██║ ██║   ██╔══╝ ██║╚██╔╝██║██╔══╝ ", " ██████╔╝██████╔╝██████╔╝   ██║ ██║ ╚═╝ ██║███████╗", " ╚═════╝ ╚═════╝ ╚═════╝    ╚═╝ ╚═╝ ╚═╝╚══════╝", - " 'Lite' Edition v2.0.0-31-g3167f9d Windows 32-bit RC4 " + " 'Lite' Edition v2.0.0-35-g37c24d0 Windows 32-bit RC4 " }; int comp (const void *a, const void *b) diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index bb6ac8a..cf80b6a 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -2061,7 +2061,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (opts->ncurses_compact == 1) { printw ("------------------------------------------------------------------------------\n"); - printw ("| Digital Speech Decoder: Florida Man Edition - Win32 %s \n", "v2.0.0-31-g3167f9d RC4"); + printw ("| Digital Speech Decoder: Florida Man Edition - Win32 %s \n", "v2.0.0-35-g37c24d0 RC4"); } if (opts->ncurses_compact == 0) { @@ -2073,7 +2073,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (i == 2) printw (" 'q' to Quit "); if (i == 4) printw (" MBElib %s", versionstr); if (i == 5) printw (" %s ", "Win32 RC4"); //printw (" %s \n", GIT_TAG); - if (i == 6) printw (" %s \n", "v2.0.0-31-g3167f9d"); //printw (" %s \n", GIT_TAG); + if (i == 6) printw (" %s \n", "v2.0.0-35-g37c24d0"); //printw (" %s \n", GIT_TAG); else printw ("\n"); } attroff(COLOR_PAIR(6)); //6