468 lines
9.9 KiB
C
468 lines
9.9 KiB
C
/*
|
|
* Humax EPG Tool
|
|
* by af123, 2011
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <strings.h>
|
|
|
|
#include "lint.h"
|
|
|
|
struct descriptor *
|
|
read_descriptor_header(struct epg *epg)
|
|
{
|
|
struct descriptor *d;
|
|
|
|
d = (struct descriptor *)calloc(sizeof(struct descriptor), 1);
|
|
d->loaded = 0;
|
|
|
|
if (epg->binsize - epg->offset < 2)
|
|
return NULL;
|
|
|
|
memcpy(d, epg->bin + epg->offset, 2);
|
|
|
|
epg->offset += 2;
|
|
|
|
return d;
|
|
}
|
|
|
|
static char *
|
|
string_to_end(struct epg *epg, struct descriptor *d, int sofar,
|
|
unsigned int *len)
|
|
{
|
|
int rest = d->len - sofar;
|
|
char *str;
|
|
|
|
*len = rest;
|
|
|
|
if (rest <= 0)
|
|
return NULL;;
|
|
|
|
str = (char *)malloc(rest + 1);
|
|
memcpy(str, epg->bin + epg->offset, rest);
|
|
str[rest] = '\0';
|
|
epg->offset += rest;
|
|
|
|
return str;
|
|
}
|
|
|
|
static char *
|
|
read_lstring(struct epg *epg, unsigned int *len)
|
|
{
|
|
unsigned int l = epg->bin[epg->offset++];
|
|
char *c;
|
|
|
|
/* Check that there is enough file left. */
|
|
if (epg->binsize - epg->offset < l)
|
|
l = epg->binsize - epg->offset;
|
|
|
|
c = malloc(l + 1);
|
|
memcpy(c, epg->bin + epg->offset, l);
|
|
c[l] = '\0';
|
|
epg->offset += l;
|
|
|
|
if (len)
|
|
*len = l;
|
|
|
|
return c;
|
|
}
|
|
|
|
void
|
|
read_descriptor(struct epg *epg, struct descriptor *d)
|
|
{
|
|
d->loaded = 1;
|
|
switch (d->tag)
|
|
{
|
|
case DS_SHORT_EVENT:
|
|
memcpy(d->content.d77.lang, epg->bin + epg->offset, 3);
|
|
epg->offset += 3;
|
|
|
|
d->content.d77.name =
|
|
read_lstring(epg, &d->content.d77.namelen);
|
|
d->content.d77.text =
|
|
read_lstring(epg, &d->content.d77.textlen);
|
|
|
|
break;
|
|
|
|
case DS_EXTENDED_EVENT:
|
|
{
|
|
unsigned int i;
|
|
|
|
epg->offset++; /* Skip descriptor number and last descriptor */
|
|
memcpy(d->content.d78.lang, epg->bin + epg->offset, 3);
|
|
epg->offset += 3;
|
|
|
|
d->content.d78.items = epg->bin[epg->offset++];
|
|
if (debug > 1 && d->content.d78.items > 0)
|
|
printf("Extended event items: %d\n",
|
|
d->content.d78.items);
|
|
/* Skip items. */
|
|
for (i = 0; i < d->content.d78.items; i++)
|
|
{
|
|
int l = epg->bin[epg->offset++];
|
|
epg->offset += l;
|
|
}
|
|
|
|
d->content.d78.text =
|
|
read_lstring(epg, &d->content.d78.textlen);
|
|
|
|
break;
|
|
}
|
|
|
|
case DS_COMPONENT:
|
|
memcpy(&d->content.d80, epg->bin + epg->offset, 6);
|
|
epg->offset += 6;
|
|
|
|
d->content.d80.text = string_to_end(epg, d, 6,
|
|
&d->content.d80.textlen);
|
|
break;
|
|
|
|
case DS_USER_DEFINED:
|
|
{
|
|
int sofar = 0;
|
|
|
|
/* reserved:6 guidance_type:2 */
|
|
d->content.d137.guidance_type = epg->bin[epg->offset++] & 0x3;
|
|
sofar++;
|
|
switch (d->content.d137.guidance_type)
|
|
{
|
|
case 0:
|
|
/* guidance_type 0 means unsuitable for broadcast
|
|
* prior to the watershed so set mode to 1. */
|
|
d->content.d137.guidance_mode = 1;
|
|
break;
|
|
case 1:
|
|
/* reserved:7 guidance_mode:1 */
|
|
d->content.d137.guidance_mode =
|
|
epg->bin[epg->offset++] & 0x1;
|
|
sofar++;
|
|
break;
|
|
default:
|
|
/* Unknown guidance type.. */
|
|
break;
|
|
}
|
|
|
|
memcpy(d->content.d137.lang, epg->bin + epg->offset, 3);
|
|
epg->offset += 3;
|
|
sofar += 3;
|
|
d->content.d137.warning = string_to_end(epg, d, sofar,
|
|
&d->content.d137.warninglen);
|
|
break;
|
|
}
|
|
|
|
case DS_LINKAGE:
|
|
memcpy(&d->content.d74, epg->bin + epg->offset,
|
|
sizeof(d->content.d74));
|
|
epg->offset += d->len;
|
|
|
|
d->content.d74.tsid = _swap16(d->content.d74.tsid);
|
|
d->content.d74.orig_netid = _swap16(d->content.d74.orig_netid);
|
|
d->content.d74.service_id = _swap16(d->content.d74.service_id);
|
|
|
|
if (d->content.d74.linkage_type == 8)
|
|
d->content.d74.l.l8.id =
|
|
_swap16(d->content.d74.l.l8.id);
|
|
else
|
|
d->content.d74.l.ld.event_id =
|
|
_swap16(d->content.d74.l.ld.event_id);
|
|
|
|
break;
|
|
|
|
case DS_CONTENT:
|
|
{
|
|
unsigned int end = epg->offset + d->len;
|
|
int cnt = 0;
|
|
/* A content descriptor can contain multiple types. Only
|
|
* save the first one at the moment. */
|
|
while (epg->offset < end)
|
|
{
|
|
if (!cnt++)
|
|
memcpy(&d->content.d84, epg->bin + epg->offset,
|
|
sizeof(d->content.d84));
|
|
epg->offset += sizeof(d->content.d84);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case DS_FTA_CONTENT_MGMT:
|
|
memcpy(&d->content.d126, epg->bin + epg->offset,
|
|
sizeof(d->content.d126));
|
|
epg->offset += d->len;
|
|
break;
|
|
|
|
case DS_CONTENT_IDENTIFIER:
|
|
{
|
|
unsigned int end = epg->offset + d->len;
|
|
|
|
d->content.d118.i = 0;
|
|
|
|
while (epg->offset < end)
|
|
{
|
|
struct crid *crid =
|
|
&d->content.d118.crids[d->content.d118.i];
|
|
|
|
memcpy(crid, epg->bin + epg->offset, 1);
|
|
epg->offset++;
|
|
|
|
crid->cridlen = crid->ref = 0;
|
|
switch (crid->location)
|
|
{
|
|
case 0:
|
|
crid->crid = read_lstring(epg, &crid->cridlen);
|
|
break;
|
|
|
|
case 1:
|
|
crid->ref =
|
|
_swap16((uint16_t)epg->bin[epg->offset]);
|
|
/* Not safe to use post increment in macro */
|
|
epg->offset++;
|
|
break;
|
|
|
|
default:
|
|
printf("Unknown CRID location - %d\n",
|
|
crid->location);
|
|
}
|
|
if (d->content.d118.i++ >= 3)
|
|
{
|
|
printf("Too many content IDs.\n");
|
|
exit(0);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
printf("WARNING: Default descriptor: %d\n", d->tag);
|
|
case DS_PRIVATE_DATA_SPECIFIER:
|
|
d->content.unknown.text = string_to_end(epg, d, 0,
|
|
&d->content.unknown.textlen);
|
|
break;
|
|
}
|
|
}
|
|
|
|
inline void
|
|
skip_descriptor(struct epg *epg, struct descriptor *d)
|
|
{
|
|
epg->offset += d->len;
|
|
}
|
|
|
|
void
|
|
free_descriptor(struct descriptor *d)
|
|
{
|
|
if (d->loaded)
|
|
{
|
|
switch (d->tag)
|
|
{
|
|
case DS_SHORT_EVENT:
|
|
if (d->content.d77.name)
|
|
free(d->content.d77.name);
|
|
if (d->content.d77.text)
|
|
free(d->content.d77.text);
|
|
break;
|
|
|
|
case DS_EXTENDED_EVENT:
|
|
if (d->content.d78.text)
|
|
free(d->content.d78.text);
|
|
break;
|
|
|
|
case DS_COMPONENT:
|
|
if (d->content.d80.text)
|
|
free(d->content.d80.text);
|
|
break;
|
|
|
|
case DS_USER_DEFINED:
|
|
if (d->content.d137.warning)
|
|
free(d->content.d137.warning);
|
|
break;
|
|
|
|
case DS_CONTENT_IDENTIFIER:
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < d->content.d118.i; i++)
|
|
{
|
|
struct crid *crid = &d->content.d118.crids[i];
|
|
if (crid->cridlen)
|
|
free(crid->crid);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case DS_CONTENT:
|
|
case DS_FTA_CONTENT_MGMT:
|
|
case DS_LINKAGE:
|
|
break;
|
|
|
|
default:
|
|
if (d->content.unknown.text)
|
|
free(d->content.unknown.text);
|
|
break;
|
|
}
|
|
}
|
|
free(d);
|
|
}
|
|
|
|
const char *
|
|
descriptor_name(struct descriptor *d)
|
|
{
|
|
switch (d->tag)
|
|
{
|
|
case DS_LINKAGE: return "linkage";
|
|
case DS_SHORT_EVENT: return "short event";
|
|
case DS_COMPONENT: return "component";
|
|
case DS_CONTENT: return "content";
|
|
case DS_PRIVATE_DATA_SPECIFIER: return "private data spec.";
|
|
case DS_CONTENT_IDENTIFIER: return "content id";
|
|
case DS_FTA_CONTENT_MGMT: return "content mgmt";
|
|
case DS_USER_DEFINED: return "user defined";
|
|
default: return "Unknown";
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
const char *
|
|
content_type(struct descriptor *d)
|
|
{
|
|
switch (d->content.d84.level1)
|
|
{
|
|
case 0x1:
|
|
return "Film/Drama";
|
|
case 0x2:
|
|
return "News/Current affairs";
|
|
case 0x3:
|
|
return "Show/Game show";
|
|
case 0x4:
|
|
return "Sport";
|
|
case 0x5:
|
|
return "Children";
|
|
case 0x6:
|
|
return "Music/Ballet/Dance";
|
|
case 0x7:
|
|
return "Arts/Culture";
|
|
case 0x8:
|
|
return "Social/Political/Economic";
|
|
case 0x9:
|
|
return "Education/Science/Factual";
|
|
case 0xa:
|
|
return "Leisure";
|
|
default:
|
|
return "Undefined";
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
void
|
|
dump_descriptor(struct descriptor *d, int content)
|
|
{
|
|
printf("Descriptor header:\n");
|
|
printf(" %30s: %#x [%d] (%s)\n", "descriptor", d->tag, d->tag,
|
|
descriptor_name(d));
|
|
DUMPINT(d, len);
|
|
|
|
if (!content || !d->loaded)
|
|
return;
|
|
|
|
switch (d->tag)
|
|
{
|
|
case DS_SHORT_EVENT:
|
|
DUMPNSTR(d, content.d77.lang, 3);
|
|
DUMPINT(d, content.d77.namelen);
|
|
DUMPHEX(d, content.d77.name, d->content.d77.namelen);
|
|
DUMPINT(d, content.d77.textlen);
|
|
DUMPHEX(d, content.d77.text, d->content.d77.textlen);
|
|
break;
|
|
|
|
case DS_EXTENDED_EVENT:
|
|
DUMPNSTR(d, content.d78.lang, 3);
|
|
DUMPINT(d, content.d78.items);
|
|
DUMPINT(d, content.d78.textlen);
|
|
DUMPHEX(d, content.d78.text, d->content.d78.textlen);
|
|
break;
|
|
|
|
case DS_COMPONENT:
|
|
DUMPINT(d, content.d80.stream_content);
|
|
DUMPINT(d, content.d80.reserved);
|
|
DUMPINT(d, content.d80.type);
|
|
DUMPINT(d, content.d80.tag);
|
|
DUMPNSTR(d, content.d80.lang, 3);
|
|
if (d->content.d80.textlen)
|
|
DUMPHEX(d, content.d80.text, d->content.d80.textlen);
|
|
break;
|
|
|
|
case DS_USER_DEFINED:
|
|
DUMPINT(d, content.d137.guidance_type);
|
|
DUMPINT(d, content.d137.guidance_mode);
|
|
DUMPNSTR(d, content.d137.lang, 3);
|
|
DUMPHEX(d, content.d137.warning, d->content.d137.warninglen);
|
|
break;
|
|
|
|
case DS_CONTENT_IDENTIFIER:
|
|
{
|
|
int i;
|
|
|
|
DUMPINT(d, content.d118.i);
|
|
|
|
for (i = 0; i < d->content.d118.i; i++)
|
|
{
|
|
struct crid *crid = &d->content.d118.crids[i];
|
|
DUMPINT(crid, location);
|
|
DUMPINT(crid, type);
|
|
DUMPINT(crid, cridlen);
|
|
if (crid->cridlen)
|
|
DUMPHEX(crid, crid, crid->cridlen);
|
|
DUMPINT(crid, ref);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case DS_CONTENT:
|
|
DUMPINT(d, content.d84.level1);
|
|
DUMPINT(d, content.d84.level2);
|
|
DUMPINT(d, content.d84.user);
|
|
printf("%30s: %s\n", "Type", content_type(d));
|
|
break;
|
|
|
|
case DS_FTA_CONTENT_MGMT:
|
|
DUMPINT(d, content.d126.reserved);
|
|
DUMPINT(d, content.d126.no_scramble);
|
|
DUMPINT(d, content.d126.control_remote_access);
|
|
DUMPINT(d, content.d126.no_revocation);
|
|
break;
|
|
|
|
case DS_LINKAGE:
|
|
DUMPINT(d, content.d74.tsid);
|
|
DUMPINT(d, content.d74.orig_netid);
|
|
DUMPINT(d, content.d74.service_id);
|
|
DUMPINT(d, content.d74.linkage_type);
|
|
if (d->content.d74.linkage_type == 8)
|
|
{
|
|
DUMPINT(d, content.d74.l.l8.handover_type);
|
|
DUMPINT(d, content.d74.l.l8.reserved);
|
|
DUMPINT(d, content.d74.l.l8.origin_type);
|
|
DUMPINT(d, content.d74.l.l8.id);
|
|
}
|
|
else if (d->content.d74.linkage_type == 13)
|
|
{
|
|
DUMPINT(d, content.d74.l.ld.event_id);
|
|
DUMPINT(d, content.d74.l.ld.listed);
|
|
DUMPINT(d, content.d74.l.ld.simulcast);
|
|
DUMPINT(d, content.d74.l.ld.reserved);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (d->content.unknown.textlen)
|
|
DUMPHEX(d, content.unknown.text,
|
|
d->content.unknown.textlen);
|
|
break;
|
|
}
|
|
}
|
|
|