From 7080bd92b01a49240ff7f21ad54382fc08981691 Mon Sep 17 00:00:00 2001 From: hummypkg Date: Sat, 30 Apr 2011 00:57:30 +0000 Subject: [PATCH] initial hmt tool commit --- Makefile | 72 +++++++++++++++ cmd.c | 150 +++++++++++++++++++++++++++++++ display.c | 203 +++++++++++++++++++++++++++++++++++++++++ file.c | 253 +++++++++++++++++++++++++++++++++++++++++++++++++++ hmt.c | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ hmt.h | 66 ++++++++++++++ lint.h | 27 ++++++ main.c | 232 +++++++++++++++++++++++++++++++++++++++++++++++ util.c | 174 +++++++++++++++++++++++++++++++++++ 9 files changed, 1442 insertions(+) create mode 100644 Makefile create mode 100644 cmd.c create mode 100644 display.c create mode 100644 file.c create mode 100644 hmt.c create mode 100644 hmt.h create mode 100644 lint.h create mode 100644 main.c create mode 100644 util.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..936e0f7 --- /dev/null +++ b/Makefile @@ -0,0 +1,72 @@ + +MAKE=gmake + +# Setting these makes sure that the subsequent include files know that +# we are going to be threaded so they should do whatever they need to +# support this. In particular, this makes the global variable 'errno' +# become thread safe by replacing it with a call to the internal function +# ___errno() on Solaris - and should do something similar on other operating +# systems. Solaris also supports _TS_ERRNO to enable thread-safe errno +# _REENTRANT is also required for the tempnam() function to be properly +# thread-safe. +DEFS=-D_REENTRANT -D_TS_ERRNO -DHMT_PROTECT +# -DWITH_MPATROL + +SRCS= cmd.c \ + display.c \ + file.c \ + main.c \ + util.c + +OBJS= $(SRCS:.c=.o) +#CC=mips-linux-gcc +#CC=gcc +#STRIP=mips-linux-strip +#STRIP=strip +CC=mips-linux-gcc +STRIP=mips-linux-strip + +PLATFORM=$(shell uname -s | cut -d- -f1) +PROCESSOR=$(shell uname -p) +CFLAGS=-g +INCS= +LIBS= +ifeq ($(PLATFORM),CYGWIN_NT) +CFLAGS=-g -mno-cygwin +endif +#ifeq ($(PLATFORM),Linux) +#CFLAGS=-static -g +#endif +#CFLAGS=-g -O3 -fno-strict-aliasing +#WARN=-pedantic -Wall -Wnested-externs -Wpointer-arith -Werror -Wno-unused +WARN=-pedantic -Wall -W -Wnested-externs -Wpointer-arith -Wno-long-long + +all: tags hmt + +hmt: ${OBJS} + @echo "Linking..." + @-[ -f $@ ] && mv $@ $@~ || exit 0 + ${CC} -static-libgcc \ + ${WARN} \ + ${DEFS} \ + ${CFLAGS} -o $@ \ + ${OBJS} \ + ${LIBS} + ${STRIP} hmt + @echo "Done..." + +clean: + @-touch core + rm -f hmt hmt~ *.exe core tags asm ${OBJS} ${AROBJS} + rm -f *.raw *.raw.sum *.lh5 + rm -rf hmt-* + +tags: + @-ctags *.[ch] 2>> /dev/null + +.c.o: + @echo " $<" + @$(CC) $(CFLAGS) ${WARN} ${DEFS} ${INCS} -c $< -o $@ + +${OBJS}: lint.h + diff --git a/cmd.c b/cmd.c new file mode 100644 index 0000000..01ecd10 --- /dev/null +++ b/cmd.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include "lint.h" + +void +cmd_new(struct hmt *hmt, int flag) +{ + if (hmt->binsize < HMT_FLAGS1) + { + printf("File too small to patch.\n"); + return; + } + + if (flag) + hmt->bin[HMT_FLAGS1] &= ~HMT_FLAGS1_NEW; + else + hmt->bin[HMT_FLAGS1] |= HMT_FLAGS1_NEW; +} + +void +cmd_lock(struct hmt *hmt, int flag) +{ + if (hmt->binsize < HMT_FLAGS1) + { + printf("File too small to patch.\n"); + return; + } + + if (flag) + hmt->bin[HMT_FLAGS1] |= HMT_FLAGS1_LOCKED; + else + hmt->bin[HMT_FLAGS1] &= ~HMT_FLAGS1_LOCKED; +} + +void +cmd_protect(struct hmt *hmt, int flag) +{ + if (hmt->binsize < HMT_FLAGS1) + { + printf("File too small to patch.\n"); + return; + } + + if (flag) + hmt->bin[HMT_FLAGS1] |= HMT_FLAGS1_PROTECTED; + else + hmt->bin[HMT_FLAGS1] &= ~HMT_FLAGS1_PROTECTED; +} + +void +patch_string(struct hmt *hmt, uint32_t offset, char *str, int ReportErrs) +{ + int i; + + if (hmt->binsize < offset + strlen(str)) + { + if (ReportErrs==1) { + printf("File too small to patch.\n"); + } + return; + } + + if (offset != HMT_FILENAME) { + for (i=0; i<255; i++) {hmt->bin[offset+i] = 0x00;} + hmt->bin[offset] = 0x15; + strcpy((char *)(hmt->bin + offset+1), (const char *)str); + } else { + for (i=0; i<512; i++) {hmt->bin[offset+i] = 0x00;} + strcpy((char *)(hmt->bin + offset), (const char *)str); + } +} + +void +cmd_getstring(struct hmt *hmt, uint32_t offset) +{ + printf("%s\n", strip_string(hmt->bin + offset)); +} + +void +expand_hmt(struct hmt *hmt, uint32_t new_size) +{ + /*void *new_bin = malloc(new_size);*/ + + if (debug) + printf("expand_hmt(0x%08X, %d) {\n", (void *)hmt, (int)new_size); + /*memset(new_bin, 0, new_size);*/ + /*memcpy(new_bin, hmt->bin, hmt->binsize);*/ + munmap((void *)hmt->bin, hmt->binsize); + hmt->binsize = new_size; + /* Will this work when size is larger than file? */ + hmt->bin = (uint8_t *)mmap(NULL, hmt->binsize, PROT_READ|PROT_WRITE, + MAP_SHARED, hmt->fd, 0); + + /*free(hmt->bin);*/ + /*hmt->bin = new_bin;*/ + hmt->epgstart = hmt->bin + 0x1004; + if (debug) + printf("expand_hmt(0x%08X, %d) }\n", (void *)hmt, (int)new_size); +} + +void +init_hmt_epg_block(struct hmt *hmt) +{ + uint32_t duration = 0; + + if (debug) + printf("init_hmt_epg_block(0x%08X) {\n", (void *)hmt); + hmt->bin[HMT_BLOCK_LENGTH] = 0x1; + if (debug) + printf( + "Zeroing 0x%04X bytes from 0x%04X\n", + HMT_EPG_BLOCK_LENGTH + HMT_GUIDE_BLOCK_MIN_SIZE, + hmt->epgstart - hmt->bin + ); + memset(hmt->epgstart, 0, HMT_EPG_BLOCK_LENGTH + HMT_GUIDE_BLOCK_MIN_SIZE); + if (debug) + printf( + "Copying 0x%04X bytes from 0x%04X to 0x%04X\n", + 4, HMT_RECORDING_START, hmt->epgstart + HMT_SCHEDULED_START - hmt->bin + ); + memcpy(hmt->epgstart + HMT_SCHEDULED_START, hmt->bin + HMT_RECORDING_START, 4); + if (debug) + printf("1\n"); + duration = + *(uint32_t*)(hmt->bin + HMT_RECORDING_END) - + *(uint32_t*)(hmt->bin + HMT_RECORDING_START); + if (debug) + printf( + "Setting duration %d in 0x%04X\n", + duration, hmt->epgstart + HMT_SCHEDULED_DURATION - hmt->bin + ); + memcpy(hmt->epgstart + HMT_SCHEDULED_DURATION, &duration, 4); + *(hmt->epgstart + 0x0c) = 0x04; + *(hmt->epgstart + 0x0e) = 0x01; + /**(hmt->epgstart + 0x14) = 0x15;*/ + if (debug) + printf( + "Copying 0x%04X bytes from 0x%04X to 0x%04X\n", + 0xff, HMT_TITLE, hmt->epgstart + 0x14 - hmt->bin + ); + memcpy(hmt->epgstart + 0x14, hmt->bin + HMT_TITLE, 0xff /*53*/); + /* *(hmt->epgstart + 0x114) = 0x15; */ + /* Synopsis, up to 255 bytes. */ + /* No guide block. */ + if (debug) + printf("init_hmt_epg_block(0x%08X) }\n", (void *)hmt); +} diff --git a/display.c b/display.c new file mode 100644 index 0000000..0a40767 --- /dev/null +++ b/display.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include "lint.h" + +const char * +genre(unsigned char b) +{ + switch (b) + { + case 0: + return "Unclassified"; + case 0x10: + return "Film"; + case 0x20: + return "News & Factual"; + case 0x30: + return "Entertainment"; + case 0x40: + return "Sport"; + case 0x50: + return "Children"; + case 0x60: + return "Education"; + case 0xa0: + return "Lifestyle"; + case 0xf0: + return "Drama"; + default: + return "Unknown"; + } +} + +char * +ctimenl(time_t *tm) +{ + static char buf[25]; + char *p; + + strcpy(buf, ctime(tm)); + + if ((p = strpbrk(buf, "\r\n"))) + *p = '\0'; + + return buf; +} + +uint8_t * +strip_string(uint8_t *str) +{ + if (str[0] == 0x10 && str[1] == 0x69 && str[2] == 0x37) + return str + 3; + if (*str == 0x15) + return str + 1; + /* non freesat recordings seems to have a 0x05 as the header string */ + if (*str == 0x05) + return str + 1; + return str; +} + +void +display_hmt(struct hmt *hmt) +{ + uint16_t i; + uint16_t j; + time_t tm; + char time_buf[256]; + + char fullpath[512]={0}; + char filename[512]={0}; + int posslash = -1; + + /* title, epg, def, channel num, channel name, start, end, flags, + * guidance + */ + if (sysopts & SYSOPT_PARSABLE) + { + printf("%s\t", strip_string(hmt->bin + (HMT_TITLE))); + if (hmt->epgstart != NULL) + printf("%s\t", strip_string(hmt->epgstart + HMT_EPG)); + else + printf("\t"); + printf("%s\t", hmt->bin[HMT_FLAGS2] & HMT_FLAGS2_HD ? "HD" : "SD"); + + i = read_uint16(hmt->bin + HMT_CHANNEL_NUM, 0); + printf("%i\t%s\t", i, + strip_string(hmt->bin + HMT_CHANNEL_NAME)); + + tm = read_uint32(hmt->bin + HMT_RECORDING_START, 0); + (void) strftime(time_buf, sizeof(time_buf), "%a %b %d %H:%M:%S %Y", gmtime(&tm)); + printf("%s\t", time_buf); + printf("%lu\t", tm); + + tm = read_uint32(hmt->bin + HMT_RECORDING_END, 0); + (void) strftime(time_buf, sizeof(time_buf), "%a %b %d %H:%M:%S %Y", gmtime(&tm)); + printf("%s\t", time_buf); + printf("%lu\t", tm); + + printf("%s%s%s%s%s", + hmt->bin[HMT_FLAGS1] & HMT_FLAGS1_LOCKED ? "Locked," : "", + hmt->bin[HMT_FLAGS1] & HMT_FLAGS1_NEW ? "" : "New,", + hmt->bin[HMT_FLAGS1] & HMT_FLAGS1_PROTECTED? "Protected," : "", + hmt->bin[HMT_FLAGS2] & HMT_FLAGS2_ENCRYPTED ? "Encrypted," : "", + hmt->epgguidance != NULL ? "Guidance," : "" + ); + if (hmt->bin[HMT_CONVERT_FLAG] == 'P') + printf("Converting,\t"); + else + if (hmt->bin[HMT_CONVERT_FLAG] == 'F') + printf("Converted,\t"); + else + printf("Unconverted,\t"); + + if (hmt->epgguidance != NULL) + printf("%s\t", strip_string(hmt->epgguidance)); + + printf("\n"); + + return; + } + + i = read_uint16(hmt->bin + HMT_CHANNEL_NUM, 0); + + memcpy(fullpath, hmt->bin + HMT_FOLDER, sizeof(fullpath)); + posslash = (strrchr(fullpath, '/') - fullpath); + memcpy(filename, hmt->bin + HMT_FOLDER + posslash + 1, (sizeof(fullpath) - posslash)); + fullpath[posslash+1]=0; + + printf("Format: %s\n", hmt->bin[HMT_FLAGS2] & HMT_FLAGS2_HD ? "HD" : "SD"); + printf("Title: %s\n", strip_string(hmt->bin + (HMT_TITLE))); + + printf("Channel: %i (%s)\n", i, hmt->bin + HMT_CHANNEL_NAME); + + printf("Folder: %s\n", fullpath); + printf("Filename: %s.ts\n", filename); + if (hmt->epgstart != NULL) { + printf("EPG: %s\n", strip_string(hmt->epgstart + HMT_EPG)); + } else { + printf("EPG:\n"); + } + + if (hmt->epgguidance != NULL) + printf("Guidance: %s\n", strip_string(hmt->epgguidance)); + + printf("\n"); + + printf("Flags: %s%s%s%s%s%s", + hmt->bin[HMT_FLAGS2] & HMT_FLAGS2_HD ? "HD," : "SD,", + hmt->bin[HMT_FLAGS1] & HMT_FLAGS1_LOCKED ? "Locked," : "", + hmt->bin[HMT_FLAGS1] & HMT_FLAGS1_NEW ? "" : "New,", + hmt->bin[HMT_FLAGS1] & HMT_FLAGS1_PROTECTED? "Protected," : "", + hmt->bin[HMT_FLAGS2] & HMT_FLAGS2_ENCRYPTED ? "Encrypted," : "", + hmt->epgguidance != NULL ? "Guidance," : "" + ); + if (hmt->bin[HMT_CONVERT_FLAG] == 'P') + printf("Converting,\n"); + else + if (hmt->bin[HMT_CONVERT_FLAG] == 'F') + printf("Converted,\n"); + else + printf("Unconverted,\n"); + + printf("Copy count: %d\n", hmt->bin[HMT_COPYCOUNT]); + + printf("\n"); + + tm = read_uint32(hmt->epgstart + HMT_SCHEDULED_START, 0); + (void) strftime(time_buf, sizeof(time_buf), "%a %b %d %H:%M:%S %Y", gmtime(&tm)); + printf("Scheduled start: %s\n", time_buf); +/* printf("Scheduled start:%lu (%s)\n", tm, ctimenl(&tm)); */ + + tm = read_uint32(hmt->epgstart + HMT_SCHEDULED_DURATION, 0); + printf("Scheduled duration: %lu\n", tm); + + tm = read_uint32(hmt->bin + HMT_RECORDING_START, 0); + (void) strftime(time_buf, sizeof(time_buf), "%a %b %d %H:%M:%S %Y", gmtime(&tm)); + printf("Recording start: %s\n", time_buf); +/* printf("Recording start:%lu (%s)\n", tm, ctimenl(&tm)); */ + + tm = read_uint32(hmt->bin + HMT_RECORDING_END, 0); + (void) strftime(time_buf, sizeof(time_buf), "%a %b %d %H:%M:%S %Y", gmtime(&tm)); + printf("Recording end: %s\n", time_buf); +/* printf("Recording end:%lu (%s)\n", tm, ctimenl(&tm)); */ + +/* printf("Play resumes at: %i seconds in.\n", read_uint16(hmt->bin + HMT_PLAYED_TIME, 0)); */ + + printf("\n"); + + j = read_uint16(hmt->bin + HMT_SID, 0); + printf("Service ID (SID): %u\n", j); + j = read_uint16(hmt->bin + HMT_TSID, 0); + printf("Transport Stream ID (TSID): %u\n", j); + j = read_uint16(hmt->bin + HMT_ONID, 0); + printf("Originating Network ID (ONID): %u\n", j); + j = read_uint16(hmt->bin + HMT_PMTPID, 0); + printf("Programme Map Table PID (PMTPID): %u\n", j); + j = read_uint16(hmt->bin + HMT_VIDEOPID, 0); + printf("Video PID: %u\n", j); + j = read_uint16(hmt->bin + HMT_AUDIOPID, 0); + printf("Audio PID: %u\n", j); +} diff --git a/file.c b/file.c new file mode 100644 index 0000000..7f1b83a --- /dev/null +++ b/file.c @@ -0,0 +1,253 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lint.h" + +struct hmt * +open_file(char *filename, int iOpenMode) +{ + struct hmt *hmt; + struct stat st; + char *ext; + uint16_t magic; + uint16_t numepgblocks = 0; + uint16_t thisepgblock = 0; + uint8_t numtransportblocks = 0; + uint8_t numguidanceblocks = 0; + int i; + uint16_t thisblocklength; + uint16_t eventid; + int foundit; + + if (!(hmt = malloc(sizeof(struct hmt)))) + { + perror("malloc"); + return NULL; + } + + hmt->epgguidance = NULL; + hmt->epgstart = NULL; + + if (debug) + printf("Opening file '%s'\n", filename); + + strcpy(hmt->fname, filename); + + if ((ext = strrchr(hmt->fname, '.'))) + { + ext++; + if (debug) + printf(" Extension: '%s'", ext); + + if (strcmp(ext, "hmt")) + { + *ext = '\0'; + strcat(hmt->fname, "hmt"); + } + } + else + strcat(hmt->fname, ".hmt"); + + if (debug) + printf(" Actual: '%s'\n", hmt->fname); + + if (stat(hmt->fname, &st) == -1) + { + perror(hmt->fname); + free(hmt); + return NULL; + } + + hmt->binsize = st.st_size; + + if (debug) + printf("Opening %s, %lu bytes.\n", hmt->fname, + (unsigned long)hmt->binsize); + + if ((hmt->fd = open(hmt->fname, iOpenMode, 0)) == -1) + { + perror(hmt->fname); + free(hmt); + return NULL; + } + + if (iOpenMode == O_RDONLY) + { + hmt->bin = (uint8_t *)mmap(NULL, hmt->binsize, PROT_READ, + MAP_SHARED, hmt->fd, 0); + } + else + { + hmt->bin = (uint8_t *)mmap(NULL, hmt->binsize, PROT_READ|PROT_WRITE, + MAP_SHARED, hmt->fd, 0); + } + if (hmt->bin == MAP_FAILED) + { + perror("mmap"); + close(hmt->fd); + free(hmt); + return NULL; + } + + magic = read_uint16(hmt->bin, 0); + +/* if (magic != 0x1701) */ + if (magic != 0x0000) + { + printf("Invalid HMT file, %s", hmt->fname); + close_file(hmt); + return NULL; + } + +/* Check for non-standard/non-freesat HMTs + Don't try to find the EPG data if this is one of them! +*/ + if (hmt->binsize < 0x1004) + { + hmt->epgstart=NULL; + } else { + + /* + Need to find the correct EPG entry after the main HMT Block at the start of the file + If the recording spans multiple EPG entries there will be the same number of EPG blocks + each of which may have one or more Guidance Blocks and/or one or more Transport Stream + blocks. + + Two possible ways of doing this: + 1. If the recording was an EPG based recording then the target event ID will be located in the header block + at offset 0x36b (2 bytes) and also in the EPG block at offset 0x00. This can be used for a definite match. + 2. For manual record on demand and manual timed recordings the event ID is not present in the header so have to + find the correct EPG block using a text based comparison of the programme titles. + + */ + + /* First EPG Block starts at the end of the HMT Block */ + hmt->epgstart = (hmt->bin + 0x1004); + numepgblocks = read_uint16(hmt->bin + (HMT_BLOCK_LENGTH - 1), 0); + thisepgblock=1; + foundit = 0; + + eventid = read_uint16(hmt->bin + HMT_HEADER_PROG_EV_ID, 0); + if (eventid == 0) { + /* This is a manual recording where the eventid is not present so use string matching to find the EPG block */ + while ((strcmp((char *)hmt->epgstart+HMT_EPG_TITLE, (char *)hmt->bin+HMT_TITLE)!=0) && (thisepgblock <= numepgblocks)) { + /* This is not the correct epg entry so skip to next one */ + numtransportblocks = hmt->epgstart[(HMT_EPG_BLOCK_LENGTH - 11)]; + numguidanceblocks = hmt->epgstart[(HMT_EPG_BLOCK_LENGTH - 10)]; + + hmt->epgstart = hmt->epgstart + HMT_EPG_BLOCK_LENGTH; + for (i=0; i< (numtransportblocks+numguidanceblocks); i++) { + /* Skip this guidance or transport block as it's not the correct EPG entry */ + thisblocklength = read_uint16(hmt->epgstart+2, 0); + hmt->epgstart = hmt->epgstart + thisblocklength + 4; + } + thisepgblock++; + } + if (strcmp((char *)hmt->epgstart+HMT_EPG_TITLE, (char *)hmt->bin+HMT_TITLE) == 0) { + foundit = 1; + } + } else { + /* This is an Event Based (EPG Based) recording so find the EPG Block using the Event ID */ + while ((read_uint16(hmt->epgstart, 0) != eventid) && (thisepgblock <= numepgblocks)) { + /* This is not the correct epg entry so skip to next one */ + numtransportblocks = hmt->epgstart[(HMT_EPG_BLOCK_LENGTH - 11)]; + numguidanceblocks = hmt->epgstart[(HMT_EPG_BLOCK_LENGTH - 10)]; + + hmt->epgstart = hmt->epgstart + HMT_EPG_BLOCK_LENGTH; + for (i=0; i< (numtransportblocks+numguidanceblocks); i++) { + /* Skip this guidance or transport block as it's not the correct EPG entry */ + thisblocklength = read_uint16(hmt->epgstart+2, 0); + hmt->epgstart = hmt->epgstart + thisblocklength + 4; + } + thisepgblock++; + } + if (read_uint16(hmt->epgstart, 0) == eventid) { + foundit = 1; + } + } + + + /* Check that we've found the EPG block ..... if not, revert to the first EPG block in the file. + This can happen if, for example, the recording was a manual recording which has been renamed so + there's no event ID and the title strings between the header and the EPG block don't match. + */ + if (foundit == 0) { + hmt->epgstart = (hmt->bin + 0x1004); + } + + /* So now we have the start of the correct EPG record. + See if there's any guidance blocks and if so, set the relevant pointer + for the start of the Guidance test. + Note - Foxsat HDR HMT files can contain multiple Guidance Blocks - return the first one */ + + numguidanceblocks = hmt->epgstart[(HMT_EPG_BLOCK_LENGTH - 10)]; + + if (numguidanceblocks != 0) { + hmt->epgguidance = hmt->epgstart + HMT_EPG_BLOCK_LENGTH; + + /* Skip any transport blocks first */ + numtransportblocks = hmt->epgstart[(HMT_EPG_BLOCK_LENGTH - 11)]; + if (numtransportblocks != 0) { + for(i=0; iepgguidance+2, 0); + hmt->epgguidance = hmt->epgguidance + thisblocklength + 4; + } + } + + /* Pointer now at the start of the guidance block + so now set it to the Guidance Text itself */ + hmt->epgguidance = hmt->epgguidance + 21; + } + } + return hmt; +} + +void +close_file(struct hmt *hmt) +{ + if (debug) + printf("Closing file.\n"); + + munmap((void *)hmt->bin, hmt->binsize); + if (hmt->fd > 0) + close(hmt->fd); + hmt->fd = -1; + +} + +struct hmt * +extend_file(struct hmt *hmt, uint32_t extra_size) +{ + FILE *file = NULL; + void *nul = NULL; + + /* + * No portable way of extending a memory mapped file, just close it, append + * extra bytes and reopen. + */ + close_file(hmt); + + if (debug) + printf("Extending file by %d byte(s)\n", extra_size); + if (file = fopen(hmt->fname, "ab")) { + nul = malloc(extra_size); + memset(nul, 0, extra_size); + fwrite(nul, 1, extra_size, file); + fclose(file); + free(nul); + nul = NULL; + } + + return open_file(hmt->fname, O_RDWR); +} diff --git a/hmt.c b/hmt.c new file mode 100644 index 0000000..1e8c67f --- /dev/null +++ b/hmt.c @@ -0,0 +1,265 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lint.h" + +#define STRING(addr, x, len) hmt->x = (char *)strip_string(hmt->bin + addr) +#define LE32(addr, x) hmt->x = read_uint32(hmt->bin + addr, 1) +#define LE16(addr, x) hmt->x = read_uint16(hmt->bin + addr, 1) +#define LE8(addr, x) hmt->x = hmt->bin[addr] & 0xff +#define PTR32(addr, x) hmt->x = (uint32_t *)(hmt->bin + addr); + +/* ETSI EN 300 468 Annex A.2 */ +uint8_t * +strip_string(uint8_t *str) +{ + if (*str >= 0x20) + return str; + if (*str == 0x10) + return str + 3; + if (*str == 0x1f) + return str + 2; + return str + 1; +} + +void +parse_hmt(struct hmt *hmt) +{ + STRING( 0x80, directory, 254); + STRING( 0x17f, filename, 256); + LE32( 0x280, start); + LE32( 0x284, end); + LE16( 0x288, duration); + LE8( 0x28c, recstatus); + LE8( 0x28d, flags1); + LE8( 0x28e, flags2); + LE8( 0x290, failreason); + LE32( 0x294, resume); + LE16( 0x298, num_bookmarks); + STRING( 0x29a, mediatitle, 48); + PTR32( 0x31c, bookmarks); + LE8( 0x3dc, flags3); + LE8( 0x3e0, guidance_type); + LE8( 0x3e1, guidance_mode); + STRING( 0x3e2, guidance, 75); + LE8( 0x431, copycount); + LE16( 0x434, tzoffset); + LE32( 0x458, lcn); + LE8( 0x4b8, type); + STRING( 0x45c, channel, 43); + LE16( 0x488, service_id); + LE16( 0x48a, tsid); + LE16( 0x48c, onid); + LE16( 0x48e, pmt_pid); + LE16( 0x490, video_pid); +// LE8( 0x498, definition); + LE8( 0x4bc, definition); + LE16( 0x49c, audio_pid); + LE32( 0x4f8, schedstart); + LE32( 0x4fc, scheddur); + LE8( 0x514, genre); + STRING( 0x516, title, 51); + STRING( 0x616, synopsis, 255); + LE16( 0x716, event_id); + LE8( 0x73d, xflags1); + LE8( 0x73e, xflags2); + LE8( 0x73f, xflags3); + + LE8( 0x10, series); + LE8( 0x11, episode); + LE8( 0x12, episodetot); +} + +int +hmt_is(struct hmt *hmt, enum hmt_attribute attr) +{ + switch (attr) + { + case HMTA_ENCRYPTED: + return hmt->flags2 & 0x1; + case HMTA_THUMBNAIL: + return hmt->flags2 & 0x2; + case HMTA_GHOST: + return hmt->flags2 & 0x8; + case HMTA_LOCKED: + return hmt->flags1 & 0x4; + case HMTA_NEW: + return !(hmt->flags1 & 0x8); + case HMTA_ENCFLAGGED: + return hmt->flags3 != 4; + case HMTA_UNLIMITEDCOPY: + return hmt->flags3 & 0x4; + case HMTA_SHRUNK: + return hmt->xflags1 == 'E'; + case HMTA_DEDUPED: + return hmt->xflags2 == 'N'; + case HMTA_DETECTADS: + return hmt->xflags3 == 'G'; + case HMTA_RADIO: + return hmt->type == 0x2; + default: + fprintf(stderr, "Unhandled hmt_is, %d\n", attr); + break; + } + return 0; +} + +char * +hmt_flags(struct hmt *hmt) +{ + static char buf[0x400]; + int g; + + *buf = '\0'; + if (hmt->definition == DEF_HD) + strcat(buf, "HD,"); + else + strcat(buf, "SD,"); + if (hmt_is(hmt, HMTA_LOCKED)) + strcat(buf, "Locked,"); + if (hmt_is(hmt, HMTA_NEW)) + strcat(buf, "New,"); + if (hmt_is(hmt, HMTA_UNLIMITEDCOPY)) + strcat(buf, "Unlimited Copies,"); + if (hmt_is(hmt, HMTA_ENCFLAGGED)) + strcat(buf, "Encrypted,"); + if ((g = guidance(hmt)) == 2) + strcat(buf, "GGuidance,"); + else if (g == 1) + strcat(buf, "Guidance,"); + if (hmt_is(hmt, HMTA_ENCRYPTED)) + strcat(buf, "ODEncrypted,"); + if (hmt_is(hmt, HMTA_THUMBNAIL)) + strcat(buf, "Thumbnail,"); + if (hmt_is(hmt, HMTA_SHRUNK)) + strcat(buf, "Shrunk,"); + if (hmt_is(hmt, HMTA_DEDUPED)) + strcat(buf, "Deduped,"); + if (hmt_is(hmt, HMTA_DETECTADS)) + strcat(buf, "Addetection,"); + if (hmt_is(hmt, HMTA_RADIO)) + strcat(buf, "Radio,"); + if (hmt_is(hmt, HMTA_GHOST)) + strcat(buf, "Ghost,"); + return buf; +} + +struct { + unsigned char genre; + char *text; +} genretab[] = { + { 0x00, "Unclassified" }, + { 0x10, "Film" }, + { 0x20, "News & Factual" }, + { 0x30, "Entertainment" }, + { 0x40, "Sport" }, + { 0x50, "Children" }, + { 0x60, "Entertainment" }, + { 0x70, "News & Factual" }, + { 0x80, "News & Factual" }, + { 0x90, "Education" }, + { 0xa0, "Lifestyle" }, + { 0xf0, "Drama" }, + { 0, NULL }, +}; + +const char * +genredescr(unsigned char b) +{ + int i; + + for (i = 0; genretab[i].text; i++) + if (b == genretab[i].genre) + return genretab[i].text; + return "Unknown"; +} + +unsigned char +genrecode(char *s) +{ + int i; + + for (i = 0; genretab[i].text; i++) + if (!strncasecmp(genretab[i].text, s, strlen(s))) + return genretab[i].genre; + return 0; +} + +const char * +recordingstatus(struct hmt *hmt) +{ + switch (hmt->recstatus) + { + case 0: return "Zero length"; + case 1: return "Loss of power"; + case 2: return "Valid"; + case 3: return "Scrambled"; + case 4: return "Failed"; + default: break; + } + return "Unknown"; +} + +const char * +failurereason(struct hmt *hmt) +{ + switch (hmt->failreason) + { + case 0: return "OK"; + case 1: return "Failed: Disk was full"; + case 8: + case 2: return "Failed: Conflicted with another recording"; + case 3: return "Failed: Maximum number of files per folder"; + case 4: return "Recording less than 30 seconds"; + case 5: return "Failed: Lack of signal"; + case 13: return "Incomplete: Disk was full"; + case 15: return "Failed: No storage device detected"; + case 16: return "Incomplete: USB storage device was removed"; + case 17: return "Failed: Appears not to have been broadcast"; + case 19: return "Failed: Conflicted with a higher priority recording"; + case 20: return "Failed: Unable to track"; + default: break; + } + return "Unknown"; +} + +int +guidance(struct hmt *hmt) +{ +#if 0 + printf("GUIDANCE TYPE/MODE: %d/%d\n", + hmt->guidance_type, hmt->guidance_mode); +#endif + switch (hmt->guidance_type) + { + case 1: + switch (hmt->guidance_mode) + { + case 0: + /* General guidance */ + return 2; + default: + /* Adult content */ + return 1; + } + break; + default: + switch (hmt->guidance_mode) + { + case 0: + /* No guidance */ + return 0; + default: + /* Adult content */ + return 1; + } + break; + } +} + diff --git a/hmt.h b/hmt.h new file mode 100644 index 0000000..7fc3693 --- /dev/null +++ b/hmt.h @@ -0,0 +1,66 @@ + +#include + +struct hmt { + char fname[MAXPATHLEN + 1]; + int fd; + uint8_t *bin; + uint8_t *epgstart; /* Start of the correct EPG entry in the HMT file */ + uint8_t *epgguidance; /* Start of the guidance text for the relevant EPG entry */ + uint32_t binsize; +}; +#define HMT_EPG_BLOCK_LENGTH 0x220 +#define HMT_EPG_TITLE 0x14 +#define HMT_BLOCK_LENGTH 0x1003 + +#define HMT_HEADER_PROG_EV_ID 0x036b + +#define HMT_FOLDER 0x21 +#define HMT_FOLDER_LEN 512 + +#define HMT_FILENAME 0x21 +#define HMT_FILENAME_LEN 512 + +#define HMT_TITLE 0x221 +#define HMT_TITLE_LEN 255 +#define HMT_SYNOPSIS_LEN 255 + +#define HMT_CHANNEL_NUM 0x11 /* 2 bytes */ +#define HMT_CHANNEL_NAME 0x321 +#define HMT_EPG 0x114 + +#define HMT_FLAGS1 0x367 +#define HMT_FLAGS1_LOCKED 0x80 +#define HMT_FLAGS1_PROTECTED 0x40 +#define HMT_FLAGS1_NEW 0x20 +#define HMT_CONVERT_FLAG 0x500 + +#define HMT_FLAGS2 0x368 +#define HMT_FLAGS2_ENCRYPTED 0x10 /* As in "keep encrypted" */ +#define HMT_FLAGS2_HD 0x80 + +#define HMT_FLAGS3 0x369 +#define HMT_FLAGS3_LIMITCOPY 0x20 + +#define HMT_RECORDING_START 0x19 /* 4 bytes */ +#define HMT_RECORDING_END 0x1D /* 4 bytes */ +#define HMT_SCHEDULED_START 0x04 /* 4 bytes */ +#define HMT_SCHEDULED_DURATION 0x08 /* 4 bytes */ +#define HMT_PLAYED_TIME 0x05 /* 4 bytes */ + +#define HMT_GUIDEFLAG 0x3e0 +#define HMT_GUIDEFLAG_ON 0x01 +#define HMT_GUIDANCE 0x3e2 + +#define HMT_SID 0x417 /* 2 bytes */ +#define HMT_TSID 0x425 /* 2 bytes */ +#define HMT_ONID 0x427 /* 2 bytes */ +#define HMT_PMTPID 0x419 /* 2 bytes */ +#define HMT_VIDEOPID 0x41b /* 2 bytes */ +#define HMT_AUDIOPID 0x41d /* 2 bytes */ + +#define HMT_COPYCOUNT 0x431 + +#define HMT_EPG_SYNOPSIS 0x114 /* 0x115 */ +#define HMT_GUIDE_BLOCK_MIN_SIZE 0x15 +#define HMT_WITH_EPG_BLOCK_MIN_SIZE (HMT_BLOCK_LENGTH + HMT_EPG_BLOCK_LENGTH + HMT_GUIDE_BLOCK_MIN_SIZE) diff --git a/lint.h b/lint.h new file mode 100644 index 0000000..91f5a73 --- /dev/null +++ b/lint.h @@ -0,0 +1,27 @@ + +#define SYSOPT_PARSABLE 0x1 + +#include "hmt.h" + +extern int debug; +extern const char *version; +extern unsigned long sysopts; + +inline uint16_t read_uint16(uint8_t *, int); +inline uint32_t read_uint32(uint8_t *, int); +void hexdump(uint8_t *, uint32_t, uint32_t); + +struct hmt *open_file(char *, int); +void close_file(struct hmt *); +struct hmt *extend_file(struct hmt *hmt, uint32_t extra_size); + +uint8_t * strip_string(uint8_t *); +void display_hmt(struct hmt *hmt); + +void cmd_protect(struct hmt *, int); +void cmd_new(struct hmt *, int); +void cmd_lock(struct hmt *, int); +void patch_string(struct hmt *, uint32_t, char *, int); +void cmd_getstring(struct hmt *, uint32_t); +void expand_hmt(struct hmt *hmt, uint32_t new_size); +void init_hmt_epg_block(struct hmt *hmt); diff --git a/main.c b/main.c new file mode 100644 index 0000000..545e9a8 --- /dev/null +++ b/main.c @@ -0,0 +1,232 @@ +/* + * Humax HMT Tool + * af123, 2011 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lint.h" + +int debug = 0; +const char *version = "2.5"; +unsigned long sysopts = 0; + +int +syntax() +{ + fprintf(stderr, "Humax HMT Tool v%s, by af123, 2012.\n", version); + fprintf(stderr, "Modified for Foxsat HDR by adrianf36 2012 and MofTot 2018-20.\n\n"); + fprintf(stderr, + "Syntax: hmt [command] [filename] ...\n"); + fprintf(stderr, + " Commands:\n" + " -p Read HMT and produce parseable output for use by the WebIf\n" + " -f Read HMT and return path and filename (without extension) to TS file\n" + " +/-new Mark/unmark recording as new.\n" + " +/-lock Mark/unmark recording as locked.\n" + ); +#ifdef HMT_PROTECT + fprintf(stderr, + " +/-protect Mark/unmark recording as protected.\n" + ); +#endif + fprintf(stderr, + " +settitle=\n" + " +setsynopsis=\n" + " +setfilename=\n" + " where = Full Path and filename of related TS file\n" + " WITHOUT the .TS extension. e.g. /mnt/hd3/Video/File123\n" + " Note. File paths MUST be /mnt/hd3/... and NOT /media/...\n" + ); + fprintf(stderr, "\n"); + return 0; +} + +int +main(int argc, char **argv) +{ + int iFileOpenMode = O_RDONLY; + enum { + CMD_LIST = 0, + CMD_NEW, + CMD_LOCK, +#ifdef HMT_PROTECT + CMD_PROTECT, +#endif + CMD_SETTITLE, + CMD_SETSYNOPSIS, + CMD_SETFILENAME, + CMD_GETPATHANDFILENAME + } cmd = CMD_LIST; + char *newstr; + int i, toggle; + + if (argc > 1 && !strncmp(argv[1], "-d", 2)) + { + if (argv[1][2] == '\0') + debug = 1; + else + debug = atoi(argv[1] + 2); + printf("Set debug level to %d\n", debug); + argc--, argv++; + } + + if (argc > 1 && !strcmp(argv[1], "-p")) + { + sysopts |= SYSOPT_PARSABLE; + argc--, argv++; + } + + if (argc > 1 && !strcmp(argv[1], "-f")) + { + cmd = CMD_GETPATHANDFILENAME; + argc--, argv++; + } + + if (argc < 2) + return syntax(); + + toggle = 0; + switch (*argv[1]) + { + case '+': + toggle = 1; + /* Fall-through */ + case '-': + if (debug) + printf("argv[1] = \"%s\"\n", argv[1]); + + if (!strcmp(argv[1] + 1, "new")) + { + cmd = CMD_NEW; + iFileOpenMode = O_RDWR; + } + else if (!strcmp(argv[1] + 1, "lock")) + { + cmd = CMD_LOCK; + iFileOpenMode = O_RDWR; + } +#ifdef HMT_PROTECT + else if (!strcmp(argv[1] + 1, "protect")) + { + cmd = CMD_PROTECT; + iFileOpenMode = O_RDWR; + } +#endif + else if (!strncmp(argv[1], "+settitle=", 10)) + { + newstr = argv[1] + 10; + if (strlen(newstr) >= HMT_TITLE_LEN) + { + fprintf(stderr, "New title too long.\n"); + return 0; + } + cmd = CMD_SETTITLE; + iFileOpenMode = O_RDWR; + } + else if (!strncmp(argv[1], "+setsynopsis=", 13)) + { + newstr = argv[1] + 13; + if (strlen(newstr) >= HMT_SYNOPSIS_LEN) + { + fprintf(stderr, "New synopsis too long.\n"); + return 0; + } + cmd = CMD_SETSYNOPSIS; + iFileOpenMode = O_RDWR; + } + else if (!strncmp(argv[1], "+setfilename=", 13)) + { + newstr = argv[1] + 13; + if (strlen(newstr) >= HMT_FILENAME_LEN) + { + fprintf(stderr, "New filename too long.\n"); + return 0; + } + cmd = CMD_SETFILENAME; + iFileOpenMode = O_RDWR; + } + else + { + printf("Unknown command, %s\n", argv[1] + 1); + return syntax(); + } + argc--, argv++; + break; + } + + if (debug) { + printf("Command %d\n", cmd); + printf("File open mode %d\n", iFileOpenMode); + } + + for (i = 1; i < argc; i++) + { + struct hmt *hmt; + + if (strlen(argv[i]) > 4 && + ( + !strcmp(argv[i] + strlen(argv[i]) - 4, ".hmt") || + !strcmp(argv[i] + strlen(argv[i]) - 3, ".ts") + )) { + if (!(hmt = open_file(argv[i], iFileOpenMode))) + continue; + } + else continue; + + switch (cmd) + { + case CMD_NEW: + cmd_new(hmt, toggle); + break; + + case CMD_LOCK: + cmd_lock(hmt, toggle); + break; + +#ifdef HMT_PROTECT + case CMD_PROTECT: + cmd_protect(hmt, toggle); + break; +#endif + + case CMD_SETTITLE: + patch_string(hmt, HMT_TITLE, newstr,1); + patch_string(hmt, (hmt->epgstart - hmt->bin) + HMT_EPG_TITLE, newstr,0); + break; + + case CMD_SETSYNOPSIS: + if (!hmt->epgstart || hmt->binsize < HMT_WITH_EPG_BLOCK_MIN_SIZE) { + hmt = extend_file(hmt, HMT_WITH_EPG_BLOCK_MIN_SIZE - hmt->binsize); + init_hmt_epg_block(hmt); + } + patch_string(hmt, (hmt->epgstart - hmt->bin) + HMT_EPG_SYNOPSIS, newstr, 1 /*0*/); + break; + + case CMD_SETFILENAME: + patch_string(hmt, HMT_FILENAME, newstr,1); + break; + + case CMD_GETPATHANDFILENAME: + cmd_getstring(hmt, HMT_FILENAME); + break; + + default: + display_hmt(hmt); + } + + close_file(hmt); + } + + return 0; +} diff --git a/util.c b/util.c new file mode 100644 index 0000000..1e2a2eb --- /dev/null +++ b/util.c @@ -0,0 +1,174 @@ +/* + * HMT Tool + * af123, 2011 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lint.h" + +inline uint32_t +read_uint32(uint8_t *p, int le) +{ + if (le) + return p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0]; + else + return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; +} + +inline uint16_t +read_uint16(uint8_t *p, int le) +{ + if (le) + return p[1] << 8 | p[0]; + else + return p[0] << 8 | p[1]; +} + +inline void +write_uint32(uint8_t *p, uint32_t val) +{ + *p++ = val >> 24 & 0xff; + *p++ = val >> 16 & 0xff; + *p++ = val >> 8 & 0xff; + *p++ = val & 0xff; +} + +inline void +write_uint16(uint8_t *p, uint16_t val) +{ + *p++ = val >> 8 & 0xff; + *p++ = val & 0xff; +} + +uint8_t * +memmem(register uint8_t *mem, uint32_t mlen, + register uint8_t *pat, uint32_t plen) +{ + while (mlen >= plen) + { + if (!memcmp(mem, pat, plen)) + return mem; + mem++, mlen--; + } + return (uint8_t *)NULL; +} + +void +hexdump(uint8_t *s, uint32_t len, uint32_t o) +{ + uint16_t off; + + if (!s) + return; + + if (!len) + len = strlen((char *)s); + + for (off = 0; off < len; off += 16) + { + uint32_t i; + + printf("%08lx: ", (unsigned long)off + o); + + for (i = off; i - off < 16; i++) + { + if (i < len) + printf("%02x ", s[i] & 0xff); + else + printf(" "); + } + + printf(" "); + + for (i = off; i < len && i - off < 16; i++) + printf("%c", isprint((int)s[i]) ? s[i] : '.'); + + printf("\n"); + } +} + +void +hexstr(uint8_t *s, uint32_t len) +{ + uint16_t off; + + if (!s) + return; + + if (!len) + len = strlen((char *)s); + + for (off = 0; off < len; off += 16) + { + uint32_t i; + + for (i = off; i - off < 16; i++) + { + if (i < len) + printf("%02x", s[i] & 0xff); + } + } +} + +void +hexdump_fd(int fd, uint32_t len) +{ + off_t pos = lseek(fd, 0, SEEK_CUR); + + uint8_t *buffer; + + buffer = (uint8_t *)malloc(len); + + read(fd, buffer, len); + hexdump(buffer, len, 0); + + lseek(fd, pos, SEEK_SET); +} + +void +error(char *fmt, ...) +{ + char buf[0x400]; + va_list argp; + + va_start(argp, fmt); + vsprintf(buf, fmt, argp); + + fprintf(stderr, "%s\n", buf); + va_end(argp); + exit(1); +} + +#if defined(__MINGW32__) || defined(__linux__) + +size_t +strlcpy(char *s, const char *t, size_t n) +{ + const char *o = t; + + if (n) + do + { + if (!--n) + { + *s = 0; + break; + } + } while ((*s++ = *t++)); + if (!n) + while (*t++); + return t - o - 1; +} + +#endif +