commit f6a5c33742a6dacfe209a095eb68b4da534771c6 Author: hummypkg Date: Sat Apr 30 00:57:30 2011 +0000 initial hmt tool commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..de664e4 --- /dev/null +++ b/Makefile @@ -0,0 +1,70 @@ + +MAKE=make + +# 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 \ + hmt.c \ + main.c \ + util.c + +OBJS= $(SRCS:.c=.o) +#CC=mipsel-linux-gcc +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} + @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 $@ + +install: hmt + strip hmt + cp hmt /mod/bin/hmt + +${OBJS}: lint.h + diff --git a/cmd.c b/cmd.c new file mode 100644 index 0000000..afbbb2d --- /dev/null +++ b/cmd.c @@ -0,0 +1,396 @@ +#include +#include +#include +#include +#include "lint.h" + +#define CHECK_OFFSET(xxx) \ + if (hmt->binsize < (xxx)) \ + { \ + printf("File too small to patch.\n"); \ + return; \ + } + +void +cmd_new(struct hmt *hmt, int flag) +{ + CHECK_OFFSET(HMT_FLAGS1) + + if (flag) + hmt->bin[HMT_FLAGS1] &= ~HMT_FLAGS1_NEW; + else + hmt->bin[HMT_FLAGS1] |= HMT_FLAGS1_NEW; + hmt->modified++; +} + +void +cmd_lock(struct hmt *hmt, int flag) +{ + CHECK_OFFSET(HMT_FLAGS1) + + if (flag) + hmt->bin[HMT_FLAGS1] |= HMT_FLAGS1_LOCKED; + else + hmt->bin[HMT_FLAGS1] &= ~HMT_FLAGS1_LOCKED; + hmt->modified++; +} + +void +cmd_guidance(struct hmt *hmt, int flag) +{ + /* 0x03E0 2 byte 'Guide' flag for Media List. + 0xFF00 = Guide off. 0x0101 = Guide on. */ + if (flag) + { + patch_byte(hmt, HMT_GUIDEFLAG, 0x01); + patch_byte(hmt, HMT_GUIDEFLAG + 1, 0x01); + } + else + { + patch_byte(hmt, HMT_GUIDEFLAG, 0xff); + patch_byte(hmt, HMT_GUIDEFLAG + 1, 0x00); + } + hmt->modified++; +} + +void +cmd_protect(struct hmt *hmt, int flag) +{ + CHECK_OFFSET(HMT_FLAGS2) + + if (flag) + hmt->bin[HMT_FLAGS2] = 0; + else + hmt->bin[HMT_FLAGS2] = 4; + hmt->modified++; +} + +void +cmd_encrypted(struct hmt *hmt, int flag) +{ + CHECK_OFFSET(HMT_ENCRYPTED); + + if (flag) + hmt->bin[HMT_ENCRYPTED] |= HMT_IS_ENCRYPTED; + else + hmt->bin[HMT_ENCRYPTED] &= ~HMT_IS_ENCRYPTED; + hmt->modified++; +} + +void +cmd_shrunk(struct hmt *hmt, int flag) +{ + CHECK_OFFSET(HMT_SHRUNK); + + if (flag) + hmt->bin[HMT_SHRUNK] = 'E'; + else + hmt->bin[HMT_SHRUNK] = 'e'; + hmt->modified++; +} + +void +cmd_dedup(struct hmt *hmt, int flag) +{ + CHECK_OFFSET(HMT_DEDUPED); + + if (flag) + hmt->bin[HMT_DEDUPED] = 'N'; + else + hmt->bin[HMT_DEDUPED] = 'n'; + hmt->modified++; +} + +void +cmd_detectads(struct hmt *hmt, int flag) +{ + CHECK_OFFSET(HMT_DETECTADS); + + if (flag) + hmt->bin[HMT_DETECTADS] = 'G'; + else + hmt->bin[HMT_DETECTADS] = 'g'; + hmt->modified++; +} + +void +cmd_setgenre(struct hmt *hmt, char *g) +{ + CHECK_OFFSET(HMT_GENRE) + + printf("Current genre: %s\n", genredescr(hmt->bin[HMT_GENRE])); + if (*g == '-') + hmt->bin[HMT_GENRE] = atoi(g + 1); + else + hmt->bin[HMT_GENRE] = genrecode(g); + printf("Set genre to: %s\n", genredescr(hmt->bin[HMT_GENRE])); + hmt->modified++; +} + +void +cmd_setresume(struct hmt *hmt, char *resume) +{ + int32_t r = strtoll(resume, (char **)NULL, 10); + + CHECK_OFFSET(HMT_PLAYED_TIME) + + if (r < 0) + { + uint32_t d; + d = hmt->end - hmt->start; + printf("Recording Duration:%lu\n", d); + r += d; + } + + printf("Setting resume point to: %lu second(s) in.\n", r); + write_uint32(hmt->bin + HMT_PLAYED_TIME, r); + hmt->modified++; +} + +static void +patch_epg_offset(struct hmt *hmt, uint32_t offset, uint32_t len, char *str) +{ + if (hmt->binsize > 0x1001) + { + uint32_t j, k; + uint32_t n; + + /* Read the number of EPG blocks. */ + j = read_uint16(hmt->bin + 0x1000, 1); + + /* Loop through them. */ + for (n = 1; n <= j; n++) + { + /* Offset of block header. */ + uint32_t off = 0x1004 + n * 32; + + if (hmt->binsize < off) + break; + + /* Read offset of EPG data. */ + k = read_uint32(hmt->bin + off + 8, 1); + + if (k < off) + continue; + + if (hmt->binsize >= k + 704) + { + if (debug) + printf("Patching EPG block %d.\n", n); + patch_string(hmt, k + offset, len, str); + } + } + } +} + +void +cmd_settitle(struct hmt *hmt, char *str) +{ + patch_string(hmt, HMT_TITLE, HMT_TITLE_LEN, str); + patch_string(hmt, HMT_ITITLE, HMT_ITITLE_LEN, str); + patch_epg_offset(hmt, 0x3e, HMT_TITLE_LEN, str); +} + +void +cmd_setsynopsis(struct hmt *hmt, char *str) +{ + CHECK_OFFSET(HMT_EPG) + + if (strlen(str) >= HMT_EPG_LEN) + { + fprintf(stderr, "New synopsis too long.\n"); + return; + } + + patch_string(hmt, HMT_EPG, HMT_EPG_LEN, str); + patch_epg_offset(hmt, 0x141, HMT_EPG_LEN, str); +} + +void +cmd_setseries(struct hmt *hmt, char *g) +{ + unsigned int series, episode, episodes; + + if (sscanf(g, "%u,%u,%u", &series, &episode, &episodes) >= 2) + { + hmt->bin[HMT_SERIES] = series; + hmt->bin[HMT_EPISODE] = episode; + hmt->bin[HMT_EPISODETOT] = episodes; + hmt->modified++; + } + else + printf("Invalid episode '%s'\n", g); +} + +static int +intcomp(const void *a, const void *b) +{ + return (*(uint32_t *)a - *(uint32_t *)b); +} + +void +cmd_bookmarks(struct hmt *hmt, char *str, int add) +{ + uint32_t *b = (uint32_t *)(hmt->bin + HMT_BOOKMARKS); + uint16_t n = read_uint16(hmt->bin + HMT_BOOKMARKS_CNT, 1); + char *p; + + if (!add) + n = 0; + if (!str || !(p = strtok(str, ":"))) + { + /* No bookmarks provided. */ + if (add) + return; + *(uint16_t *)(hmt->bin + HMT_BOOKMARKS_CNT) = 0; + hmt->modified++; + return; + } + + do + { + if (n >= 32) + { + fprintf(stderr, "Too many bookmarks, maximum 32.\n"); + return; + } + b[n++] = (uint32_t)strtoul(p, (char **)NULL, 10); + hmt->modified++; + } while ((p = strtok((char *)NULL, ":"))); + *(uint16_t *)(hmt->bin + HMT_BOOKMARKS_CNT) = n; + + qsort(b, n, sizeof(uint32_t), intcomp); +} + +void +patch_byte(struct hmt *hmt, uint32_t offset, uint8_t val) +{ + CHECK_OFFSET(offset) + + if (hmt->bin[offset] != val) + { + hmt->bin[offset] = val; + hmt->modified++; + } +} + +void +patch_string(struct hmt *hmt, uint32_t offset, uint32_t len, char *str) +{ + CHECK_OFFSET(offset + strlen(str)) + + if (debug > 3) + fprintf(stderr, "patch_string(%#x, %d, %s)\n", + offset, len, str); + + memset(hmt->bin + offset, '\0', len - 1); + strcpy((char *)(hmt->bin + offset), (const char *)str); + hmt->modified++; +} + +void +patch_uint16(struct hmt *hmt, uint32_t offset, uint32_t val) +{ + CHECK_OFFSET(offset) + + write_uint16(hmt->bin + offset, val); + hmt->modified++; +} + +void +patch_uint32(struct hmt *hmt, uint32_t offset, uint32_t val) +{ + CHECK_OFFSET(offset) + + write_uint32(hmt->bin + offset, val); + hmt->modified++; +} + +void +cmd_patch(struct hmt *hmt, char *str) +{ + int width; + uint32_t offset; + uint32_t val; + char offsets[0x20], vals[0x20]; + + if (sscanf(str, "%d=%[^:]:%s", &width, offsets, vals) != 3) + { + printf("Syntax error.\n"); + return; + } + + offset = strtoul(offsets, (char **)NULL, 0); + val = strtoul(vals, (char **)NULL, 0); + + printf("Patching width %d - %#x(%lu) = %#x(%lu)\n", width, + offset, offset, val, val); + + switch(width) + { + case 8: + patch_byte(hmt, offset, val); + break; + case 16: + patch_uint16(hmt, offset, val); + break; + case 32: + patch_uint32(hmt, offset, val); + break; + default: + printf("Unknown patch width.\n"); + break; + } +} + +void +cmd_unpatch(struct hmt *hmt, char *str) +{ + int width; + uint32_t offset; + char offsets[0x20]; + + if (sscanf(str, "%d=%[^:]", &width, offsets) != 2) + { + printf("Syntax error.\n"); + return; + } + + offset = strtoul(offsets, (char **)NULL, 0); + + if (hmt->binsize < offset) + { + printf("Offset too large for file.\n"); + return; + } + + if (debug) + printf("Reading width %d - %#x(%lu)\n", width, + offset, offset); + + switch(width) + { + case 8: + { + uint8_t val = hmt->bin[offset] & 0xff; + printf("%d %#x\n", val, val); + break; + } + case 16: + { + uint16_t val = read_uint16(hmt->bin + offset, 1); + printf("%d %#x\n", val, val); + break; + } + case 32: + { + uint32_t val = read_uint32(hmt->bin + offset, 1); + printf("%ld %#x\n", val, val); + break; + } + default: + printf("Unknown patch width.\n"); + break; + } +} + diff --git a/display.c b/display.c new file mode 100644 index 0000000..ee3b3ac --- /dev/null +++ b/display.c @@ -0,0 +1,155 @@ +#include +#include +#include +#include "lint.h" + +char * +ctimenl(uint32_t *tm) +{ + static char buf[25]; + char *p; + + strcpy(buf, ctime((time_t *)tm)); + + if ((p = strpbrk(buf, "\r\n"))) + *p = '\0'; + + return buf; +} + +void +display_bookmarks(struct hmt *hmt) +{ + int i; + + for (i = 0; i < hmt->num_bookmarks && i < 32; i++) + printf("%d ", hmt->bookmarks[i]); + printf("\n"); +} + +void +display_hmt(struct hmt *hmt) +{ + int g; + + g = guidance(hmt); + + if (sysopts & SYSOPT_PARSABLE) + { + printf("%s\t", hmt->mediatitle); + printf("%s\t", hmt->synopsis); + printf("%s\t", hmt->definition == DEF_HD ? "HD" : "SD"); + printf("%lu\t%s\t", hmt->lcn, hmt->channel); + printf("%lu\t", hmt->start); + printf("%lu\t", hmt->end); + printf("%s\t", hmt_flags(hmt)); + if (g) + printf("%s\t", hmt->guidance); + else + printf("\t"); + + printf("%d\t", hmt->num_bookmarks); + printf("%lu\t", hmt->schedstart); + printf("%lu\t", hmt->scheddur); + printf("%d\t", hmt->genre); + printf("%lu\t", hmt->resume); + printf("%s/%s\t", recordingstatus(hmt), failurereason(hmt)); + printf("%u\t%u\t%u\t", hmt->series, hmt->episode, + hmt->episodetot); + + printf("\n"); + + return; + } + + printf("Format:%s\n", hmt->definition == DEF_HD ? "HD" : "SD"); + printf("Title:%s\n", hmt->mediatitle); + printf("ITitle:%s\n", hmt->title); + printf("Channel:%lu (%s)\n", hmt->lcn, hmt->channel); + printf("Episode:%u,%u/%u\n", + hmt->series, hmt->episode, hmt->episodetot); + + printf("Folder:%s\n", hmt->directory); + if (*hmt->filename == '\0' && hmt->filename[1] != '\0') + printf("Filename:%s\n", hmt->filename + 1); + else + printf("Filename:%s\n", hmt->filename); + + printf("Genre:%s (%d)\n", genredescr(hmt->genre), hmt->genre); + printf("EPG:%s\n", hmt->synopsis); + if (g) + printf("Guidance:%s\n", hmt->guidance); + + if (hmt_is(hmt, HMTA_ENCRYPTED)) + printf("Raw file is encrypted on disk.\n"); + + printf("\n"); + + printf("Recording Status: %s (%s)\n", + recordingstatus(hmt), failurereason(hmt)); + printf("Flags: %s\n", hmt_flags(hmt)); + printf("Copy count:%d\n", hmt->copycount); + + printf("\n"); + + printf("Scheduled start:%lu (%s)\n", hmt->schedstart, + ctimenl(&hmt->schedstart)); + printf("Scheduled duration:%lu\n", hmt->scheddur); + printf("Recording start:%lu (%s)\n", hmt->start, + ctimenl(&hmt->start)); + printf("Recording end:%lu (%s)\n", hmt->end, ctimenl(&hmt->end)); + printf("Duration:%lu\n", hmt->end - hmt->start); + printf("Stored duration:%lu\n", hmt->duration); + + printf("Play resumes at:%lu second%s in.\n", hmt->resume, + hmt->resume == 1 ? "" : "s"); + printf("Timezone offset:%d\n", hmt->tzoffset); + + printf("\n"); + + printf("Service ID (SID):%u\n", hmt->service_id); + printf("Event ID:%u\n", hmt->event_id); + printf("Transport Stream ID (TSID):%u\n", hmt->tsid); + printf("Originating Network ID (ONID):%u\n", hmt->onid); + printf("Programme Map Table PID (PMTPID):%u\n", hmt->pmt_pid); + printf("Video PID:%u\n", hmt->video_pid); + printf("Audio PID:%u\n", hmt->audio_pid); + printf("Bookmarks:%d = ", hmt->num_bookmarks); + display_bookmarks(hmt); + + /* Display basic information EPG blocks... TBC */ + if (hmt->binsize > 0x1001) + { + uint16_t j, k; + int n; + + printf("\n"); + j = read_uint16(hmt->bin + 0x1000, 1); + printf("EPG Blocks:%u\n", j); + for (n = 1; n <= j; n++) + { + uint32_t off = 0x1004 + n * 32; + if (hmt->binsize < off) + break; + printf(" Block:%d", n); + k = read_uint32(hmt->bin + off + 4, 1); + printf(" Time:%lu", k); + k = read_uint32(hmt->bin + off + 8, 1); + printf(" Offset:%#x\n", k); + + if (k < off) + continue; + + if (hmt->binsize >= k + 704) + { + /* Offset to EPG block. */ + off = k; + printf(" Block%d_Title:%s\n", n, + strip_string(hmt->bin + off + 0x3e)); + } + + /* Synopsis is off + 0x141 */ + } + } +} + diff --git a/file.c b/file.c new file mode 100644 index 0000000..af287ab --- /dev/null +++ b/file.c @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lint.h" + +struct hmt * +open_file(char *filename, int readonly) +{ + struct hmt *hmt; + struct stat st; + char *ext; + uint16_t magic; + + if (!(hmt = malloc(sizeof(struct hmt)))) + { + perror("malloc"); + return NULL; + } + + if (debug) + printf("Opening file '%s' (readonly=%d)\n", filename, readonly); + + 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); + + hmt->readonly = readonly; + + if ((hmt->fd = open(hmt->fname, readonly ? O_RDONLY : O_RDWR, 0)) == -1) + { + perror(hmt->fname); + free(hmt); + return NULL; + } + + hmt->bin = (uint8_t *)mmap(NULL, hmt->binsize, + readonly ? PROT_READ : PROT_READ|PROT_WRITE, + MAP_SHARED, hmt->fd, 0); + if (hmt->bin == MAP_FAILED) + { + if (debug) + printf("mmap() failed, falling back to read.\n"); + /* This occurs if the underlying filesystem does not support + * mmap, e.g. NTFS. Fallback to holding the file in memory. + */ + if (!(hmt->bin = (uint8_t *)malloc(hmt->binsize))) + { + perror("malloc"); + free(hmt); + close(hmt->fd); + return NULL; + } + read(hmt->fd, hmt->bin, hmt->binsize); + hmt->mmapped = 0; + } + else + { + if (debug) + printf("File mapped into memory.\n"); + hmt->mmapped = 1; + } + + magic = read_uint16(hmt->bin, 0); + + if (magic != 0x1701) + { + printf("Invalid HMT file, %s\n", hmt->fname); + close_file(hmt); + return NULL; + } + + hmt->modified = 0; + parse_hmt(hmt); + + return hmt; +} + +void +close_file(struct hmt *hmt) +{ + if (debug) + printf("Closing file.\n"); + + if (hmt->mmapped) + munmap((void *)hmt->bin, hmt->binsize); + else + { + if (hmt->modified && !hmt->readonly) + { + lseek(hmt->fd, SEEK_SET, 0); + write(hmt->fd, hmt->bin, hmt->binsize); + } + free(hmt->bin); + } + if (hmt->fd > 0) + close(hmt->fd); + hmt->fd = -1; +} + diff --git a/hmt.c b/hmt.c new file mode 100644 index 0000000..1e9b406 --- /dev/null +++ b/hmt.c @@ -0,0 +1,263 @@ +#include +#include +#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 */ +inline 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_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_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"; +} + +int +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..087c24c --- /dev/null +++ b/hmt.h @@ -0,0 +1,151 @@ + +#include + +enum hmt_attribute { + HMTA_ENCRYPTED = 0, + HMTA_LOCKED, + HMTA_NEW, + HMTA_ENCFLAGGED, + HMTA_SHRUNK, + HMTA_DEDUPED, + HMTA_DETECTADS, + HMTA_UNLIMITEDCOPY, + HMTA_RADIO, + HMTA_GHOST, +}; + +struct hmt { + char fname[MAXPATHLEN + 1]; + int fd; + uint8_t *bin; + uint32_t binsize; + int mmapped; + int modified; + int readonly; + + /* Recording information */ + char *directory; + char *filename; + uint8_t type; + uint32_t start; + uint32_t end; + uint16_t duration; + uint8_t recstatus; + uint8_t flags1; + uint8_t flags2; + uint8_t failreason; + uint32_t resume; + uint16_t num_bookmarks; + char *mediatitle; + uint32_t *bookmarks; + uint8_t flags3; + uint8_t guidance_type; + uint8_t guidance_mode; + char *guidance; + uint8_t copycount; + uint16_t tzoffset; + uint32_t lcn; + char *channel; + uint16_t service_id; + uint16_t tsid; + uint16_t onid; + uint16_t pmt_pid; + uint16_t video_pid; + enum { DEF_SD = 1, DEF_HD } definition; + uint16_t audio_pid; + uint32_t schedstart; + uint32_t scheddur; + uint8_t genre; + char *title; + char *synopsis; + uint16_t event_id; + uint8_t xflags1; + uint8_t xflags2; + uint8_t xflags3; + + int series; + int episode; + int episodetot; +}; + +void parse_hmt(struct hmt *); +uint8_t *strip_string(uint8_t *); +int guidance(struct hmt *); +const char *genredescr(unsigned char b); +int genrecode(char *); +const char *recordingstatus(struct hmt *); +const char *failurereason(struct hmt *); +char *hmt_flags(struct hmt *); +int hmt_is(struct hmt *, enum hmt_attribute); + +#define HMT_FOLDER 0x80 +#define HMT_FOLDER_LEN 254 + +#define HMT_FILENAME 0x17f +#define HMT_FILENAME_LEN 256 + +#define HMT_TITLE 0x29a +#define HMT_TITLE_LEN 48 +#define HMT_ITITLE 0x516 +#define HMT_ITITLE_LEN 48 + +#define HMT_CHANNEL_NUM 0x458 /* 4 bytes */ +#define HMT_CHANNEL_NAME 0x45c +#define HMT_EPG 0x616 +#define HMT_EPG_LEN 252 + +#define HMT_RECSTATUS 0x28c +#define HMT_FAILREASON 0x290 + +#define HMT_RECTYPE 0x4b8 + +#define HMT_FLAGS1 0x28d +#define HMT_FLAGS1_LOCKED 0x4 +#define HMT_FLAGS1_NEW 0x8 + +#define HMT_ENCRYPTED 0x28e +#define HMT_IS_ENCRYPTED 0x1 + +#define HMT_BOOKMARKS_CNT 0x298 +#define HMT_BOOKMARKS 0x31c + +#define HMT_FLAGS2 0x3dc +#define HMT_FLAGS2_ENCRYPTED 0x2 /* As in "keep encrypted" */ +#define HMT_FLAGS2_UNLIMITCOPY 0x4 + +#define HMT_RECORDING_START 0x280 /* 4 bytes */ +#define HMT_RECORDING_END 0x284 /* 4 bytes */ +#define HMT_STORED_DURATION 0x288 /* 2 bytes */ +#define HMT_SCHEDULED_START 0x4f8 /* 4 bytes */ +#define HMT_SCHEDULED_DURATION 0x4fc /* 4 bytes */ +#define HMT_PLAYED_TIME 0x294 /* 4 bytes */ +#define HMT_GENRE 0x514 + +#define HMT_EVENTID 0x716 + +#define HMT_GUIDEFLAG 0x3e0 +#define HMT_GUIDEFLAG_ON 0x01 +#define HMT_GUIDANCE 0x3e2 +#define HMT_GUIDANCE_LEN 74 + +#define HMT_SID 0x488 /* 2 bytes */ +#define HMT_TSID 0x48a /* 2 bytes */ +#define HMT_ONID 0x48c /* 2 bytes */ +#define HMT_PMTPID 0x48e /* 2 bytes */ +#define HMT_VIDEOPID 0x490 /* 2 bytes */ +#define HMT_AUDIOPID 0x49c /* 2 bytes */ + +#define HMT_HDFLAG 0x498 /* 1 == SD, 2 == HD */ +#define HMT_HDFLAG2 0x4bc /* 1 == SD, 0 == HD */ + +#define HMT_COPYCOUNT 0x431 + +/* Section of HMT used for 'eng' string from guidance text. */ +#define HMT_SHRUNK 0x73d /* Set to E for shrunk.. */ +#define HMT_DEDUPED 0x73e /* Set to N for deduped.. */ +#define HMT_DETECTADS 0x73f /* Set to G for detectads.. */ + +#define HMT_SERIES 0x10 +#define HMT_EPISODE 0x11 +#define HMT_EPISODETOT 0x12 + diff --git a/lint.h b/lint.h new file mode 100644 index 0000000..0f68f53 --- /dev/null +++ b/lint.h @@ -0,0 +1,44 @@ + +#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); +inline void write_uint32(uint8_t *, uint32_t); +inline void write_uint16(uint8_t *, uint16_t); + +void hexdump(uint8_t *, uint32_t, uint32_t); + +struct hmt *open_file(char *, int); +void close_file(struct hmt *); + +void display_bookmarks(struct hmt *hmt); +void display_hmt(struct hmt *hmt); + +void cmd_protect(struct hmt *, int); +void cmd_encrypted(struct hmt *, int); +void cmd_shrunk(struct hmt *, int); +void cmd_dedup(struct hmt *, int); +void cmd_detectads(struct hmt *, int); +void cmd_new(struct hmt *, int); +void cmd_lock(struct hmt *, int); +void cmd_guidance(struct hmt *, int); +void cmd_setresume(struct hmt *, char *); +void cmd_setsynopsis(struct hmt *, char *); +void cmd_settitle(struct hmt *, char *); +void cmd_setgenre(struct hmt *, char *); +void cmd_bookmarks(struct hmt *, char *, int); +void patch_byte(struct hmt *, uint32_t, uint8_t); +void patch_string(struct hmt *, uint32_t, uint32_t, char *); +void cmd_patch(struct hmt *, char *); +void cmd_unpatch(struct hmt *, char *); +void cmd_setseries(struct hmt *, char *); + +const char *genre(unsigned char); +int genrecode(char *); + diff --git a/main.c b/main.c new file mode 100644 index 0000000..fb47e81 --- /dev/null +++ b/main.c @@ -0,0 +1,387 @@ +/* + * Humax HMT Tool + * (c) af123, 2011-2016 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lint.h" + +int debug = 0; +const char *version = "2.0.10"; +unsigned long sysopts = 0; + +int +syntax() +{ + fprintf(stderr, "Humax HMT Tool v%s, by af123, 2011-2016.\n\n", + version); + fprintf(stderr, + "Syntax: hmt [command] [filename] ...\n"); + fprintf(stderr, + " Commands:\n" + " +/-new Mark/unmark recording as new.\n" + " +/-lock Mark/unmark recording as locked.\n" + " +/-guidance Mark/unmark recording as having guidance.\n" + ); + fprintf(stderr, + " +/-protect Enable/disable protection" + " (prevents decryption on copy).\n" + " +/-encrypted Mark/unmark recording as encrypted.\n" + " +/-shrunk Mark/unmark recording as shrunk.\n" + " +/-dedup Mark/unmark recording as deduped.\n" + " +/-detectads Mark/unmark recording as ad-detection-done.\n" + ); + fprintf(stderr, + " -p Display parseable file information (see *).\n" + " -list Display file information (default).\n" + " -bookmarks Display bookmarks.\n" + " +addbookmark=[:]...\n" + " +setbookmarks=[:]...\n" + " +clearbookmarks\n" + " +settitle=\n" + " +setsynopsis=\n" + " +setguidance=\n" + ); + fprintf(stderr, + " +setseries=\n" + " +setfolder= (patch hmt only)\n" + " +setfilename= (patch hmt only)\n" + " +setgenre= (can just specifiy initial part)\n" + " +setresume= " + "(-seconds to set from end)\n" + ); + + fprintf(stderr, + "\n" + "Generic patch commands:\n" + " +patch8=offset:value patch 8-bit value\n" + " +patch16=offset:value patch 16-bit value\n" + " +patch32=offset:value patch 32-bit value\n" + " Offset and value can be preceeded with 0x to indicate hex.\n" + ); + + fprintf(stderr, + "\n" + "Generic read commands:\n" + " +read8=offset read 8-bit value\n" + " +read16=offset read 16-bit value\n" + " +read32=offset read 32-bit value\n" + " Offset can be preceeded with 0x to indicate hex.\n" + ); + + fprintf(stderr, + "\n" + "* Parseable output is tab delimited and contains the following" + " fields:\n" + " Title, Synopsis, HD/SD, LCN, Channel Name,\n" + " Start time, End time, Flags, Guidance, Bookmark count,\n" + " Scheduled start, Scheduled duration, Genre code,\n" + " Resume point, Status/Reason.\n" + ); + fprintf(stderr, "\n"); + return 0; +} + +int +main(int argc, char **argv) +{ + enum { + CMD_LIST = 0, + CMD_NEW, + CMD_LOCK, + CMD_PROTECT, + CMD_GUIDANCE, + CMD_ENCRYPTED, + CMD_SHRUNK, + CMD_DEDUP, + CMD_DETECTADS, + CMD_SETTITLE, + CMD_SETSYNOPSIS, + CMD_SETGUIDANCE, + CMD_SETFOLDER, + CMD_SETSERIES, + CMD_SETFILENAME, + CMD_SETGENRE, + CMD_SETRESUME, + CMD_BOOKMARKS, + CMD_ADDBOOKMARK, + CMD_SETBOOKMARKS, + CMD_CLEARBOOKMARKS, + CMD_PATCH, + CMD_UNPATCH + } 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); + if (debug > 0) + { + printf("Set debug level to %d\n", debug); + argc--, argv++; + } + } + + if (argc > 1 && !strcmp(argv[1], "-p")) + { + sysopts |= SYSOPT_PARSABLE; + argc--, argv++; + } + + if (argc < 2) + return syntax(); + + toggle = 0; + switch (*argv[1]) + { + case '+': + toggle = 1; + /* Fall-through */ + case '-': + if (!strcmp(argv[1] + 1, "list")) + cmd = CMD_LIST; + else if (!strcmp(argv[1] + 1, "new")) + cmd = CMD_NEW; + else if (!strcmp(argv[1] + 1, "lock")) + cmd = CMD_LOCK; + else if (!strcmp(argv[1] + 1, "protect")) + cmd = CMD_PROTECT; + else if (!strcmp(argv[1] + 1, "guidance")) + cmd = CMD_GUIDANCE; + else if (!strcmp(argv[1] + 1, "encrypted")) + cmd = CMD_ENCRYPTED; + else if (!strcmp(argv[1] + 1, "shrunk")) + cmd = CMD_SHRUNK; + else if (!strcmp(argv[1] + 1, "dedup")) + cmd = CMD_DEDUP; + else if (!strcmp(argv[1] + 1, "detectads")) + cmd = CMD_DETECTADS; + else if (!strcmp(argv[1] + 1, "bookmarks")) + cmd = CMD_BOOKMARKS; + else if (!strncmp(argv[1], "+patch", 6)) + { + newstr = argv[1] + 6; + cmd = CMD_PATCH; + } + else if (!strncmp(argv[1], "+read", 5)) + { + newstr = argv[1] + 5; + cmd = CMD_UNPATCH; + } + 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; + } + else if (!strncmp(argv[1], "+setsynopsis=", 13)) + { + newstr = argv[1] + 13; + cmd = CMD_SETSYNOPSIS; + } + else if (!strncmp(argv[1], "+setguidance=", 13)) + { + newstr = argv[1] + 13; + if (strlen(newstr) >= HMT_GUIDANCE_LEN) + { + fprintf(stderr, "New guidance too long.\n"); + return 0; + } + cmd = CMD_SETGUIDANCE; + } + else if (!strncmp(argv[1], "+setseries=", 11)) + { + newstr = argv[1] + 11; + cmd = CMD_SETSERIES; + } + else if (!strncmp(argv[1], "+setfolder=", 11)) + { + newstr = argv[1] + 11; + if (strlen(newstr) >= HMT_FOLDER_LEN) + { + fprintf(stderr, "New folder too long.\n"); + return 0; + } + cmd = CMD_SETFOLDER; + } + 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; + } + else if (!strncmp(argv[1], "+setgenre=", 10)) + { + newstr = argv[1] + 10; + cmd = CMD_SETGENRE; + } + else if (!strncmp(argv[1], "+setresume=", 11)) + { + newstr = argv[1] + 11; + cmd = CMD_SETRESUME; + } + else if (!strncmp(argv[1], "+addbookmark=", 13)) + { + newstr = argv[1] + 13; + cmd = CMD_ADDBOOKMARK; + } + else if (!strncmp(argv[1], "+setbookmarks=", 14)) + { + newstr = argv[1] + 14; + cmd = CMD_SETBOOKMARKS; + } + else if (!strncmp(argv[1], "+clearbookmarks", 15)) + { + cmd = CMD_CLEARBOOKMARKS; + } + else + { + printf("Unknown command, %s\n", argv[1] + 1); + return syntax(); + } + argc--, argv++; + break; + } + + if (debug) + printf("Command %d\n", cmd); + + for (i = 1; i < argc; i++) + { + struct hmt *hmt; + + if (!(hmt = open_file(argv[i], + cmd == CMD_LIST || cmd == CMD_BOOKMARKS))) + continue; + + switch (cmd) + { + case CMD_NEW: + cmd_new(hmt, toggle); + break; + + case CMD_LOCK: + cmd_lock(hmt, toggle); + break; + + case CMD_PROTECT: + cmd_protect(hmt, toggle); + break; + + case CMD_ENCRYPTED: + cmd_encrypted(hmt, toggle); + break; + + case CMD_SHRUNK: + cmd_shrunk(hmt, toggle); + break; + + case CMD_DEDUP: + cmd_dedup(hmt, toggle); + break; + + case CMD_DETECTADS: + cmd_detectads(hmt, toggle); + break; + + case CMD_SETTITLE: + cmd_settitle(hmt, newstr); + break; + + case CMD_SETSYNOPSIS: + cmd_setsynopsis(hmt, newstr); + break; + + case CMD_SETGUIDANCE: + /* 0x03E0 2 byte 'Guide' flag for Media List. + * 0xFF00 = Guide off. 0x0101 = Guide on. */ + patch_byte(hmt, HMT_GUIDEFLAG, HMT_GUIDEFLAG_ON); + patch_byte(hmt, HMT_GUIDEFLAG + 1, HMT_GUIDEFLAG_ON); + patch_string(hmt, HMT_GUIDANCE, HMT_GUIDANCE_LEN, + newstr); + break; + + case CMD_SETSERIES: + cmd_setseries(hmt, newstr); + break; + + case CMD_SETFOLDER: + patch_string(hmt, HMT_FOLDER, HMT_FOLDER_LEN, newstr); + break; + + case CMD_SETFILENAME: + patch_string(hmt, HMT_FILENAME, HMT_FILENAME_LEN, + newstr); + break; + + case CMD_BOOKMARKS: + display_bookmarks(hmt); + break; + + case CMD_GUIDANCE: + cmd_guidance(hmt, toggle); + break; + + case CMD_SETGENRE: + cmd_setgenre(hmt, newstr); + break; + + case CMD_SETRESUME: + cmd_setresume(hmt, newstr); + break; + + case CMD_ADDBOOKMARK: + cmd_bookmarks(hmt, newstr, 1); + display_bookmarks(hmt); + break; + + case CMD_SETBOOKMARKS: + cmd_bookmarks(hmt, newstr, 0); + display_bookmarks(hmt); + break; + + case CMD_CLEARBOOKMARKS: + cmd_bookmarks(hmt, NULL, 0); + display_bookmarks(hmt); + break; + + case CMD_PATCH: + cmd_patch(hmt, newstr); + break; + + case CMD_UNPATCH: + cmd_unpatch(hmt, newstr); + break; + + default: + display_hmt(hmt); + } + + close_file(hmt); + } + + return 0; +} + diff --git a/util.c b/util.c new file mode 100644 index 0000000..36c280c --- /dev/null +++ b/util.c @@ -0,0 +1,174 @@ +/* + * HMT Tool + * (c) 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 & 0xff; + *p++ = val >> 8 & 0xff; + *p++ = val >> 16 & 0xff; + *p++ = val >> 24 & 0xff; +} + +inline void +write_uint16(uint8_t *p, uint16_t val) +{ + *p++ = val & 0xff; + *p++ = val >> 8 & 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 +