From f6a5c33742a6dacfe209a095eb68b4da534771c6 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 | 70 ++++++++++ cmd.c | 396 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ display.c | 155 +++++++++++++++++++++ file.c | 139 +++++++++++++++++++ hmt.c | 263 ++++++++++++++++++++++++++++++++++++ hmt.h | 151 +++++++++++++++++++++ lint.h | 44 ++++++ main.c | 387 ++++++++++++++++++++++++++++++++++++++++++++++++++++ util.c | 174 ++++++++++++++++++++++++ 9 files changed, 1779 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..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 +