+struct asf_object {
+ char *ptr;
+ uint64_t size;
+};
+
+struct tag_object_nums {
+ int content_descr_obj_num;
+ int extended_content_descr_obj_num;
+};
+
+struct afs_top_level_header_object {
+ uint64_t size;
+ uint32_t num_objects;
+ uint8_t reserved1, reserved2;
+ struct asf_object *objects;
+};
+
+#define CHECK_HEADER(_p, _h) (memcmp((_p), (_h), sizeof((_h))) == 0)
+
+static int read_asf_objects(const char *src, size_t size, uint32_t num_objects,
+ struct asf_object *objs, struct tag_object_nums *ton)
+{
+ int i;
+ const char *p;
+
+ for (i = 0, p = src; i < num_objects; p += objs[i++].size) {
+ if (p + 24 > src + size)
+ return -E_NO_WMA;
+ objs[i].ptr = (char *)p;
+ objs[i].size = read_u64(p + 16);
+ if (p + objs[i].size > src + size)
+ return -E_NO_WMA;
+
+ if (CHECK_HEADER(p, content_description_header))
+ ton->content_descr_obj_num = i;
+ else if (CHECK_HEADER(p, extended_content_header))
+ ton->extended_content_descr_obj_num = i;
+ }
+ return 1;
+}
+
+static const char top_level_header_object_guid[] = {
+ 0x30, 0x26, 0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11,
+ 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c
+};
+
+static int convert_utf8_to_utf16(char *src, char **dst)
+{
+ /*
+ * Without specifying LE (little endian), iconv includes a byte order
+ * mark (e.g. 0xFFFE) at the beginning.
+ */
+ iconv_t cd = iconv_open("UTF-16LE", "UTF-8");
+ size_t sz, inbytes, outbytes, inbytesleft, outbytesleft;
+ char *inbuf, *outbuf;
+ int ret;
+
+ if (!src || !*src) {
+ *dst = para_calloc(2);
+ ret = 0;
+ goto out;
+ }
+ if (cd == (iconv_t) -1)
+ return -ERRNO_TO_PARA_ERROR(errno);
+ inbuf = src;
+ /* even though src is in UTF-8, strlen() should DTRT */
+ inbytes = inbytesleft = strlen(src);
+ outbytes = outbytesleft = 4 * inbytes + 2; /* hope that's enough */
+ *dst = outbuf = para_malloc(outbytes);
+ sz = iconv(cd, ICONV_CAST &inbuf, &inbytesleft, &outbuf, &outbytesleft);
+ if (sz == (size_t)-1) {
+ ret = -ERRNO_TO_PARA_ERROR(errno);
+ goto out;
+ }
+ assert(outbytes >= outbytesleft);
+ assert(outbytes - outbytesleft < INT_MAX - 2);
+ ret = outbytes - outbytesleft;
+ outbuf = para_realloc(*dst, ret + 2);
+ outbuf[ret] = outbuf[ret + 1] = '\0';
+ ret += 2;
+ *dst = outbuf;
+ PARA_INFO_LOG("converted %s to %d UTF-16 bytes\n", src, ret);
+out:
+ if (ret < 0)
+ free(*dst);
+ if (iconv_close(cd) < 0)
+ PARA_WARNING_LOG("iconv_close: %s\n", strerror(errno));
+ return ret;
+}
+
+/* The content description object contains artist, title, comment. */
+static int make_cdo(struct taginfo *tags, const struct asf_object *cdo,
+ struct asf_object *result)
+{
+ const char *cr, *rating; /* orig data */
+ uint16_t orig_title_bytes, orig_artist_bytes, orig_cr_bytes,
+ orig_comment_bytes, orig_rating_bytes;
+ /* pointers to new UTF-16 tags */
+ char *artist = NULL, *title = NULL, *comment = NULL;
+ /* number of bytes in UTF-16 for the new tags */
+ int artist_bytes, title_bytes, comment_bytes, ret;
+ char *p, null[2] = "\0\0";
+
+ result->ptr = NULL;
+ result->size = 0;
+ ret = convert_utf8_to_utf16(tags->artist, &artist);
+ if (ret < 0)
+ return ret;
+ artist_bytes = ret;
+ ret = convert_utf8_to_utf16(tags->title, &title);
+ if (ret < 0)
+ goto out;
+ title_bytes = ret;
+ ret = convert_utf8_to_utf16(tags->comment, &comment);
+ if (ret < 0)
+ goto out;
+ comment_bytes = ret;
+
+ if (cdo) {
+ /*
+ * Sizes of the five fields (stored as 16-bit numbers) are
+ * located after the header (16 bytes) and the cdo size (8
+ * bytes).
+ */
+ orig_title_bytes = read_u16(cdo->ptr + 24);
+ orig_artist_bytes = read_u16(cdo->ptr + 26);
+ orig_cr_bytes = read_u16(cdo->ptr + 28);
+ orig_comment_bytes = read_u16(cdo->ptr + 30);
+ orig_rating_bytes = read_u16(cdo->ptr + 32);
+ cr = cdo->ptr + 34 + orig_title_bytes + orig_artist_bytes;
+ rating = cr + orig_cr_bytes + orig_comment_bytes;
+ } else {
+ orig_title_bytes = 2;
+ orig_artist_bytes = 2;
+ orig_cr_bytes = 2;
+ orig_comment_bytes = 2;
+ orig_rating_bytes = 2;
+ cr = null;
+ rating = null;
+ }
+
+ /* compute size of result cdo */
+ result->size = 16 + 8 + 5 * 2 + title_bytes + artist_bytes
+ + orig_cr_bytes + comment_bytes + orig_rating_bytes;
+ PARA_DEBUG_LOG("cdo is %zu bytes\n", (size_t)result->size);
+ p = result->ptr = para_malloc(result->size);
+ memcpy(p, content_description_header, 16);
+ p += 16;
+ write_u64(p, result->size);
+ p += 8;
+ write_u16(p, title_bytes);
+ p += 2;
+ write_u16(p, artist_bytes);
+ p += 2;
+ write_u16(p, orig_cr_bytes);
+ p += 2;
+ write_u16(p, comment_bytes);
+ p += 2;
+ write_u16(p, orig_rating_bytes);
+ p += 2;
+ memcpy(p, title, title_bytes);
+ p += title_bytes;
+ memcpy(p, artist, artist_bytes);
+ p += artist_bytes;
+ memcpy(p, cr, orig_cr_bytes);
+ p += orig_cr_bytes;
+ memcpy(p, comment, comment_bytes);
+ p += comment_bytes;
+ memcpy(p, rating, orig_rating_bytes);
+ p += orig_rating_bytes;
+ assert(p - result->ptr == result->size);
+ ret = 1;
+out:
+ free(artist);
+ free(title);
+ free(comment);
+ return ret;
+}
+
+/* The extended content description object contains album and year. */
+static int make_ecdo(struct taginfo *tags, struct asf_object *result)
+{
+ int ret;
+ char *p, *album = NULL, *year = NULL, null[2] = "\0\0";
+ int album_bytes, year_bytes;
+
+ result->ptr = NULL;
+ result->size = 0;
+ ret = convert_utf8_to_utf16(tags->album, &album);
+ if (ret < 0)
+ return ret;
+ album_bytes = ret;
+ ret = convert_utf8_to_utf16(tags->year, &year);
+ if (ret < 0)
+ goto out;
+ year_bytes = ret;
+ result->size = 16 + 8 + 2; /* GUID, size, count */
+ /* name_length + name + null + data type + val length + val */
+ result->size += 2 + sizeof(album_tag_header) + 2 + 2 + 2 + album_bytes;
+ result->size += 2 + sizeof(year_tag_header) + 2 + 2 + 2 + year_bytes;
+
+ p = result->ptr = para_malloc(result->size);
+ memcpy(p, extended_content_header, 16);
+ p += 16;
+ write_u64(p, result->size);
+ p += 8;
+ write_u16(p, 2); /* count */
+ p += 2;
+
+ /* album */
+ write_u16(p, sizeof(album_tag_header) + 2);
+ p += 2;
+ memcpy(p, album_tag_header, sizeof(album_tag_header));
+ p += sizeof(album_tag_header);
+ memcpy(p, null, 2);
+ p += 2;
+ write_u16(p, 0); /* data type (UTF-16) */
+ p += 2;
+ write_u16(p, album_bytes);
+ p += 2;
+ memcpy(p, album, album_bytes);
+ p += album_bytes;
+
+ /* year */
+ write_u16(p, sizeof(year_tag_header));
+ p += 2;
+ memcpy(p, year_tag_header, sizeof(year_tag_header));
+ p += sizeof(year_tag_header);
+ memcpy(p, null, 2);
+ p += 2;
+ write_u16(p, 0); /* data type (UTF-16) */
+ p += 2;
+ write_u16(p, year_bytes);
+ p += 2;
+ memcpy(p, year, year_bytes);
+ p += year_bytes;
+ assert(p - result->ptr == result->size);
+ ret = 1;
+out:
+ free(album);
+ free(year);
+ return ret;
+}
+
+static int write_output_file(int fd, const char *map, size_t mapsize,
+ struct afs_top_level_header_object *top, struct tag_object_nums *ton,
+ struct asf_object *cdo, struct asf_object *ecdo)
+{
+ int i, ret;
+ uint64_t sz; /* of the new header object */
+ uint32_t num_objects;
+ char tmp[8];
+
+ sz = 16 + 8 + 4 + 1 + 1; /* top-level header object */
+ for (i = 0; i < top->num_objects; i++) {
+ if (i == ton->content_descr_obj_num)
+ continue;
+ if (i == ton->extended_content_descr_obj_num)
+ continue;
+ sz += top->objects[i].size;
+ }
+ sz += cdo->size;
+ sz += ecdo->size;
+ num_objects = top->num_objects;
+ if (ton->content_descr_obj_num < 0)
+ num_objects++;
+ if (ton->extended_content_descr_obj_num < 0)
+ num_objects++;
+ ret = xwrite(fd, top_level_header_object_guid, 16);
+ if (ret < 0)
+ goto out;
+ write_u64(tmp, sz);
+ ret = xwrite(fd, tmp, 8);
+ if (ret < 0)
+ goto out;
+ write_u32(tmp, num_objects);
+ ret = xwrite(fd, tmp, 4);
+ if (ret < 0)
+ goto out;
+ write_u8(tmp, top->reserved1);
+ ret = xwrite(fd, tmp, 1);
+ if (ret < 0)
+ goto out;
+ write_u8(tmp, top->reserved2);
+ ret = xwrite(fd, tmp, 1);
+ if (ret < 0)
+ goto out;
+ /*
+ * Write cto and ecto as objects 0 and 1 if they did not exist in the
+ * original file.
+ */
+ if (ton->content_descr_obj_num < 0) {
+ ret = xwrite(fd, cdo->ptr, cdo->size);
+ if (ret < 0)
+ goto out;
+ }
+ if (ton->extended_content_descr_obj_num < 0) {
+ ret = xwrite(fd, ecdo->ptr, ecdo->size);
+ if (ret < 0)
+ goto out;
+ }
+
+ for (i = 0; i < top->num_objects; i++) {
+ char *buf = top->objects[i].ptr;
+ sz = top->objects[i].size;
+ if (i == ton->content_descr_obj_num) {
+ buf = cdo->ptr;
+ sz = cdo->size;
+ } else if (i == ton->extended_content_descr_obj_num) {
+ buf = ecdo->ptr;
+ sz = ecdo->size;
+ }
+ ret = xwrite(fd, buf, sz);
+ if (ret < 0)
+ goto out;
+ }
+ ret = xwrite(fd, map + top->size, mapsize - top->size);
+out:
+ return ret;
+}
+
+static int wma_rewrite_tags(const char *map, size_t mapsize,
+ struct taginfo *tags, int fd,
+ __a_unused const char *filename)
+{
+ struct afs_top_level_header_object top;
+ struct tag_object_nums ton = {-1, -1};
+ const char *p = map;
+ /* (extended) content description object */
+ struct asf_object cdo = {.ptr = NULL}, ecdo = {.ptr = NULL};
+ int ret;
+
+ /* guid + size + num_objects + 2 * reserved */
+ if (mapsize < 16 + 8 + 4 + 1 + 1)
+ return -E_NO_WMA;
+ if (memcmp(map, top_level_header_object_guid, 16))
+ return -E_NO_WMA;
+ p += 16;
+ top.size = read_u64(p);
+ PARA_INFO_LOG("header_size: %lu\n", (long unsigned)top.size);
+ if (top.size >= mapsize)
+ return -E_NO_WMA;
+ p += 8;
+ top.num_objects = read_u32(p);
+ PARA_NOTICE_LOG("%u header objects\n", top.num_objects);
+ if (top.num_objects > top.size / 24)
+ return -E_NO_WMA;
+ p += 4;
+ top.reserved1 = read_u8(p);
+ p++;
+ top.reserved2 = read_u8(p);
+ if (top.reserved2 != 2)
+ return -E_NO_WMA;
+ p++; /* objects start at p */
+ top.objects = para_malloc(top.num_objects * sizeof(struct asf_object));
+ ret = read_asf_objects(p, top.size - (p - map), top.num_objects,
+ top.objects, &ton);
+ if (ret < 0)
+ goto out;
+ ret = make_cdo(tags, ton.content_descr_obj_num >= 0?
+ top.objects + ton.content_descr_obj_num : NULL, &cdo);
+ if (ret < 0)
+ goto out;
+ ret = make_ecdo(tags, &ecdo);
+ if (ret < 0)
+ goto out;
+ ret = write_output_file(fd, map, mapsize, &top, &ton, &cdo,
+ &ecdo);
+out:
+ free(cdo.ptr);
+ free(ecdo.ptr);
+ free(top.objects);
+ return ret;
+}
+
+static const char * const wma_suffixes[] = {"wma", NULL};