[libbdplus-devel] [Git][videolan/libbdplus][master] segment: Initial support for in-stream tables.
Petri Hintukainen (@hpi)
gitlab at videolan.org
Sun Oct 10 13:11:54 UTC 2021
Petri Hintukainen pushed to branch master at VideoLAN / libbdplus
Commits:
e851459c by anonymous at 2021-10-10T16:10:52+03:00
segment: Initial support for in-stream tables.
- Support stream processing without full conversion table.
- Querying masks from the VM is not yet implemented.
=> Testing requires injecting masks with BDPLUS_CONVTAB environment variable.
- Getting correct masks from VM is easy to implement, but requires mode 1 VM config.
- Mode 2 seems to use different masks.
- Mode 2 masks may also change between playback devices / sessions (?).
- Masks should be queried from VM in sync with playback (BD-J ?).
Caching the masks would make starting next playback sessions faster, and
allow playback without menus.
Caching masks requires < 10 KB space (full tables use > 1 MB).
- - - - -
1 changed file:
- src/libbdplus/bdsvm/segment.c
Changes:
=====================================
src/libbdplus/bdsvm/segment.c
=====================================
@@ -124,6 +124,8 @@ struct conv_table_s {
uint32_t current_table; // When iterating
uint32_t current_segment; // - "" -
+ /* Table type. Redundant, but simplifies things ... */
+ enum { ct_full = 0, ct_segment_masks = 1, ct_derivation_data = 2 } type;
};
/*
@@ -141,6 +143,9 @@ struct bdplus_st_s {
uint64_t stream_offset;
uint64_t next_patch_offset; /* optimize patching */
+
+ /* Process fixups from stream ? */
+ uint8_t process_stream;
};
/*
@@ -211,6 +216,18 @@ static int _is_invalid_entry(const entry_t *e, const entry_t *prev)
*
*/
+static int _findTableIndex(conv_table_t *ct, unsigned m2ts)
+{
+ unsigned ii;
+ for (ii = 0; ii < ct->numTables; ii++) {
+ if (ct->Tables[ii].tableID == m2ts) {
+ return ii;
+ }
+ }
+ return -1;
+}
+
+
uint32_t segment_numTables(conv_table_t *ct)
{
return ct ? ct->numTables : 0;
@@ -249,6 +266,17 @@ static int32_t segment_validateTable(conv_table_t *ct)
return errors;
}
+static void *_arrayGrow(void *mem, size_t elem_size, size_t n_old, size_t n_add)
+{
+ void *new_mem = realloc(mem, elem_size * (n_old + n_add));
+ if (!new_mem) {
+ free(mem);
+ return NULL;
+ }
+ memset((uint8_t*)new_mem + elem_size * n_old, 0, elem_size * n_add);
+ return new_mem;
+}
+
int32_t segment_setTable(conv_table_t **conv_tab, uint8_t *Table, uint32_t len)
{
@@ -408,7 +436,81 @@ int32_t segment_setTable(conv_table_t **conv_tab, uint8_t *Table, uint32_t len)
return -1;
}
+static int32_t segment_setMasks(conv_table_t **conv_tab, uint8_t *Table, size_t len)
+{
+ conv_table_t *ct;
+ uint32_t ptr = 0;
+
+ if (!Table || !len) return -1;
+
+ BD_DEBUG(DBG_BDPLUS,"[segment] Starting decode of segment_masks.bin: %p (%zu)\n", Table, len);
+
+ // If we do not already have a conv_tab, allocate it.
+ if (!*conv_tab) {
+ *conv_tab = (conv_table_t *) calloc(1, sizeof(*ct));
+ if (!*conv_tab) return -2;
+ (*conv_tab)->type = ct_segment_masks;
+ } else if ((*conv_tab)->type != ct_segment_masks) {
+ return -1;
+ }
+
+ ct = *conv_tab;
+
+ if (!memcmp(Table, "SEGK", 4)) {
+ if (memcmp(Table, "SEGK0100", 8)) {
+ BD_DEBUG(DBG_BDPLUS | DBG_CRIT,"[segment] unsupported segment mask file version %8.8s\n", Table);
+ return -1;
+ }
+ Table += 8;
+ } else {
+ BD_DEBUG(DBG_BDPLUS | DBG_CRIT,"[segment] no header found from segment mask file\n");
+ //return -1;
+ }
+
+ while (ptr + 4 + 2 + 16 <= len) {
+ subtable_t *subtable;
+ segment_t *segment;
+ uint32_t tableID, subtableID;
+ int table;
+ tableID = FETCH4(&Table[ptr]);
+ ptr += 4;
+ subtableID = (Table[ptr] << 8) | Table[ptr + 1];
+ ptr += 2;
+
+ /* find sutable or allocate new */
+ table = _findTableIndex(ct, tableID);
+ if (table < 0) {
+ ct->Tables = _arrayGrow(ct->Tables, sizeof(subtable_t), ct->numTables, 1);
+ if (!ct->Tables)
+ return segment_freeTable(conv_tab);
+ table = ct->numTables;
+ ct->numTables++;
+ }
+ subtable = &ct->Tables[ table ];
+ subtable->tableID = tableID;
+
+ /* find segment or allocate new */
+ if (subtable->numSegments <= subtableID) {
+ int diff = subtableID + 1 - subtable->numSegments;
+ subtable->Segments = _arrayGrow(subtable->Segments, sizeof(segment_t), subtable->numSegments, diff);
+ if (!subtable->Segments)
+ return segment_freeTable(conv_tab);
+ subtable->numSegments = subtableID + 1;
+ }
+ segment = &subtable->Segments[ subtableID ];
+
+ BD_DEBUG(DBG_BDPLUS,"[segment] Table %d ID %08X, %u segments\n",
+ table, subtable->tableID, subtable->numSegments);
+
+ segment->encrypted = 1;
+
+ memcpy(segment->key, &Table[ptr], 16);
+ ptr += 16;
+ }
+
+ return ct->numTables;
+}
int32_t segment_freeTable(conv_table_t **Table)
{
@@ -879,7 +981,10 @@ int32_t segment_load(conv_table_t **conv_tab, FILE *fd)
// Save the table to VM
if (len) {
// Decode the table into C structures.
- segment_setTable(conv_tab, buffer, fileLen);
+ if (fileLen > 8 && !memcmp(buffer, "SEGK0", 5))
+ segment_setMasks(conv_tab, buffer, fileLen);
+ else
+ segment_setTable(conv_tab, buffer, fileLen);
}
X_FREE(buffer);
@@ -1179,6 +1284,21 @@ bdplus_st_t *segment_set_m2ts(conv_table_t *ct, uint32_t m2ts)
BD_DEBUG(DBG_BDPLUS, "using table index %d for %05u.m2ts\n", table, m2ts);
+ if (ct->type == ct_segment_masks) {
+ unsigned missing_masks = 0;
+ const uint8_t empty_mask[16] = {0};
+ for (ii = 0; ii < ct->Tables[table].numSegments; ii++) {
+ ct->Tables[table].Segments[ii].numEntries = 0;
+ X_FREE(ct->Tables[table].Segments[ii].Entries);
+ ct->Tables[table].Segments[ii].encrypted = 1;
+ missing_masks += !memcmp(ct->Tables[table].Segments[ii].key, empty_mask, 16);
+ }
+ if (missing_masks) {
+ /* it is quite typical to have empty first segment. => not fatal if one or two masks are missing. */
+ BD_DEBUG(DBG_BDPLUS | DBG_CRIT, "conversion table for %05d.m2ts does not have all masks (%d/%d are missing)\n",
+ m2ts, missing_masks, ct->Tables[table].numSegments);
+ }
+ } else {
/* empty table -> no patching needed */
int segments = 0;
for (ii = 0; ii < ct->Tables[table].numSegments; ii++) {
@@ -1194,6 +1314,7 @@ bdplus_st_t *segment_set_m2ts(conv_table_t *ct, uint32_t m2ts)
BD_DEBUG(DBG_BDPLUS | DBG_CRIT, "conversion table for %05d.m2ts is still encrypted\n", m2ts);
return NULL;
}
+ }
// Changing table, or seeking elsewhere, means zero the segment and
// entry so we have to find them again.
@@ -1203,6 +1324,7 @@ bdplus_st_t *segment_set_m2ts(conv_table_t *ct, uint32_t m2ts)
BD_DEBUG(DBG_CRIT, "out of memory\n");
return NULL;
}
+ st->process_stream = (ct->type != ct_full);
st->stream_table = table;
st->table = ct;
BD_DEBUG(DBG_BDPLUS,"[segment] settable(%05u.m2ts): %p\n", m2ts, st);
@@ -1212,6 +1334,15 @@ bdplus_st_t *segment_set_m2ts(conv_table_t *ct, uint32_t m2ts)
int32_t segment_patchseek(bdplus_st_t *ct, uint64_t offset)
{
+ if (ct->process_stream) {
+ /* flush cache */
+ unsigned currseg;
+ subtable_t *subtable = &ct->table->Tables[ ct->stream_table ];
+ for (currseg = 0; currseg < subtable->numSegments; currseg++) {
+ X_FREE(subtable->Segments[ currseg ].Entries);
+ subtable->Segments[ currseg ].numEntries = 0;
+ }
+ }
// Changing table, or seeking elsewhere, means zero the segment and
// entry so we have to find them again.
@@ -1223,9 +1354,127 @@ int32_t segment_patchseek(bdplus_st_t *ct, uint64_t offset)
BD_DEBUG(DBG_BDPLUS,"[segment] seek: %016"PRIx64"\n", offset);
+ if (ct->process_stream) {
+ /* data (offset) must be aligned */
+ if ((offset % 192) != 0) {
+ BD_DEBUG(DBG_BDPLUS | DBG_CRIT, "[segment] segment_patchseek() error: unaligned seek in mode1\n");
+ return -1;
+ }
+ }
+
return 0;
}
+//#define DUMP_0x89
+#ifdef DUMP_0x89
+static void _dump_0x89(unsigned spn, const uint8_t *d, const uint8_t *mask)
+{
+ fprintf(stderr, "@%-12u | ", spn);
+ for (int i = 0; i < d[1]; i++)
+ fprintf(stderr, "%02x ", d[2+i]);
+ fprintf(stderr, "| next @%d (+%d)\n", spn + (d[d[1]]<<8) + d[d[1]+1], (d[d[1]]<<8) + d[d[1]+1]);
+ fprintf(stderr, " MASK | %4u |%02x %02x %02x %02x ...\n", (d[2] << 8) | d[3], mask[0], mask[1], mask[2], mask[3]);
+
+ /* no mask -> no point to continue */
+ const uint8_t empty_mask[16] = {0};
+ if (!memcmp(mask, empty_mask, 16))
+ return;
+
+ if (((d[4] ^ mask[0]) >> 6) == 0 || ((d[4] ^ mask[0]) >> 6) == 3)
+ fprintf(stderr, " F%d | ", (d[4] ^ mask[0]) >> 6);
+ else
+ fprintf(stderr, " %s | ", ((d[4] ^ mask[0]) >> 6) == 2 ? "FORENSIC" : " PATCH");
+
+ if (((d[4] ^ mask[0]) >> 6) != 0) {
+ /* dump content */
+ const uint8_t *p = d + 4;
+ for (int i = 0; i < d[1] - 4; i++)
+ fprintf(stderr, "%02x ", p[i] ^ mask[i]);
+ uint32_t t = (FETCH4(&p[1]) ^ FETCH4(&mask[1]));
+ fprintf(stderr, " | BP: %d %d O: %d %d", p[4] ^ mask[4], p[5] ^ mask[5], t>>20, (t>>8)&0xfff);
+ }
+ fprintf(stderr, "\n");
+}
+#endif
+
+static int ts_parse_desc_0x89(bdplus_st_t *ct, const unsigned spn, const unsigned spn0, const uint8_t *d)
+{
+ subtable_t *st = &ct->table->Tables[ ct->stream_table ];
+ segment_t *segment;
+ const uint8_t *mask;
+ unsigned sp_id = (d[2] << 8) | d[3];
+ const uint8_t empty_mask[16] = {0};
+
+ if (sp_id >= st->numSegments) {
+ /* broken table or stream */
+ return 0;
+ }
+
+ mask = st->Segments[sp_id].key;
+#ifdef DUMP_0x89
+ _dump_0x89(spn, d, mask);
+#endif
+
+ /* TODO: if mask missing, call VM for computeSP */
+ if (!memcmp(mask, empty_mask, 16)) {
+ return 0;
+ }
+
+ /* skip fuzzing entries (random data) */
+ if (((d[4] ^ mask[0]) >> 6) == 0)
+ return 0;
+ /* sanity check flags */
+ if (((d[4] ^ mask[0]) >> 6) == 3) {
+ BD_DEBUG(DBG_BDPLUS | DBG_CRIT, "[segment] found invalid flags value. incorrect mask ?\n");
+ return 0;
+ }
+
+ /* avoid polluting tables with incomplete lists */
+ if (sp_id > 0) {
+ X_FREE(st->Segments[sp_id - 1].Entries);
+ st->Segments[sp_id - 1].numEntries = 0;
+ }
+
+ segment = &st->Segments[ sp_id ];
+
+#define E1_CACHE_SIZE 50 /* there must be some upper bound in the specs (?) */
+ /* keep ordered list of next patches */
+ if (segment->Entries == NULL) {
+ segment->Entries = calloc(E1_CACHE_SIZE, sizeof(st->Segments[sp_id].Entries[0]));
+ segment->numEntries = 0;
+ }
+ /* drop past entries */
+ while (segment->numEntries > 0 &&
+ segment->Entries[0].index + segment->Entries[0].patch0_address_adjust + segment->Entries[0].patch1_address_adjust < spn0 - 1) {
+ segment->numEntries--;
+ if (segment->numEntries > 0)
+ memmove(&segment->Entries[0], &segment->Entries[1], segment->numEntries * sizeof(segment->Entries[0]));
+ }
+ if (segment->numEntries >= E1_CACHE_SIZE - 1) {
+ BD_DEBUG(DBG_BDPLUS | DBG_CRIT, "[segment] stream entry cache overflow\n");
+ return 0;
+ }
+
+ /* inject entry to the table for segment_patch() */
+ entry_t entry = {
+ spn, d[4] ^ mask[0], ((d[5] ^ mask[1]) << 4) | ((d[6] ^ mask[2]) >> 4),
+ (((d[6] ^ mask[2]) & 0x0f) << 8) | ( d[7] ^ mask[3]), d[8] ^ mask[4], d[9] ^ mask[5],
+ {d[10] ^ mask[6], d[11] ^ mask[7], d[12] ^ mask[8], d[13] ^ mask[9], d[14] ^ mask[10], },
+ {d[15] ^ mask[11], d[16] ^ mask[12], d[17] ^ mask[13], d[18] ^ mask[14], d[19] ^ mask[15], }, 1,
+ };
+ memcpy(&segment->Entries[segment->numEntries], &entry, sizeof(entry));
+ segment->numEntries++;
+
+ ct->stream_segment = sp_id;
+ ct->stream_entry = 0;
+ ct->next_patch_offset = 0;
+
+ BD_DEBUG(DBG_BDPLUS, "[segment] injected patch for segment %u, SPN %u (queue %d patches).\n",
+ sp_id, entry.index + entry.patch0_address_adjust, segment->numEntries);
+
+ return 1;
+}
+
//
// Given a buffer and its size in bytes. Using the "offset" the buffer
// starts at, work out if any patch Entries lie inside that offset, and
@@ -1253,6 +1502,20 @@ int32_t segment_patch(bdplus_st_t *ct, int len, uint8_t *buffer)
end_offset = ct->stream_offset + (uint64_t) len;
ct->stream_offset += (uint64_t) len;
+ if (ct->process_stream) {
+ const uint8_t *p;
+ if (buffer[4] != 0x47) {
+ BD_DEBUG(DBG_BDPLUS | DBG_CRIT, "[segment] segment_patch() input error: unaligned data in stream fixup mode\n");
+ return -1;
+ }
+
+ for (p = buffer; p < buffer + len; p += 192) {
+ if (p[5] == 0x41 && p[6] == 0x00 && !(p[7] & 0x20) && p[4] == 0x47 && p[21] == 0x89 && p[22] == 0x14) {
+ ts_parse_desc_0x89(ct, ((start_offset + p - buffer) / 192), start_offset / 192, p + 21);
+ }
+ }
+ }
+
if (ct->next_patch_offset > end_offset) {
return 0;
}
View it on GitLab: https://code.videolan.org/videolan/libbdplus/-/commit/e851459c26c7f7fb476c14c1bee6c563a2c6f8d0
--
View it on GitLab: https://code.videolan.org/videolan/libbdplus/-/commit/e851459c26c7f7fb476c14c1bee6c563a2c6f8d0
You're receiving this email because of your account on code.videolan.org.
More information about the libbdplus-devel
mailing list