{"id":3766,"date":"2012-04-11T21:25:48","date_gmt":"2012-04-12T04:25:48","guid":{"rendered":"http:\/\/multimedia.cx\/eggs\/?p=3766"},"modified":"2012-04-27T23:19:22","modified_gmt":"2012-04-28T06:19:22","slug":"the-11th-hour-roq-variation","status":"publish","type":"post","link":"https:\/\/multimedia.cx\/eggs\/the-11th-hour-roq-variation\/","title":{"rendered":"The 11th Hour RoQ Variation"},"content":{"rendered":"<p>I have been looking at the <a href=\"http:\/\/wiki.multimedia.cx\/index.php?title=RoQ\">RoQ file format<\/a> almost as long as I have been doing practical multimedia hacking. However, I have never figured out how the RoQ format works on <a href=\"http:\/\/www.mobygames.com\/game\/dos\/11th-hour\">The 11th Hour<\/a>, which was the game for which the RoQ format was initially developed. When I <a href=\"http:\/\/multimedia.cx\/mmentry-2003-03-24.html\">procured the game years ago<\/a>, I remember finding what appeared to be RoQ files and shoving them through the open source decoders but not getting the right images out.<\/p>\n<p>I decided to dust off that old copy of The 11th Hour and have another go at it.<\/p>\n<p><center><br \/>\n<a href=\"http:\/\/multimedia.cx\/eggs\/wp-content\/uploads\/2012\/04\/the-11th-hour-box-and-cdroms.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/multimedia.cx\/eggs\/wp-content\/uploads\/2012\/04\/the-11th-hour-box-and-cdroms.jpg\" alt=\"\" title=\"Photo of 11th hour box + CD-ROMs\" width=\"400\" height=\"263\" class=\"aligncenter size-full wp-image-3769\" srcset=\"https:\/\/multimedia.cx\/eggs\/wp-content\/uploads\/2012\/04\/the-11th-hour-box-and-cdroms.jpg 400w, https:\/\/multimedia.cx\/eggs\/wp-content\/uploads\/2012\/04\/the-11th-hour-box-and-cdroms-300x197.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><br \/>\n<\/center><\/p>\n<p><strong>Baseline<\/strong><br \/>\nThe game consists of 4 CD-ROMs. Each disc has a media\/ directory that has a series of files bearing the extension .gjd, likely the initials of one <a href=\"http:\/\/www.mobygames.com\/developer\/sheet\/view\/developerId,2913\/\">Graeme J. Devine<\/a>. These are resource files which are merely headerless concatenations of other files. Thus, at first glance, one file might appear to be a single RoQ file. So that&#8217;s the source of some of the difficulty: Sending an apparent RoQ .gjd file through a RoQ player will often cause the program to complain when it encounters the header of another RoQ file.<\/p>\n<p><a href=\"http:\/\/samples.libav.org\/game-formats\/idroq\/11th-hour\/\"><strong>I have uploaded some samples to the usual place.<\/strong><\/a><\/p>\n<p>However, even the frames that a player can decode (before encountering a file boundary within the resource file) look wrong.<\/p>\n<p><strong>Investigating Codebooks Using dreamroq<\/strong><br \/>\nI wrote <a href=\"https:\/\/github.com\/multimediamike\/dreamroq\">dreamroq<\/a> last year&#8211; an independent RoQ playback library targeted towards embedded systems. I aimed it at a gjd file and quickly hit a codebook error.<\/p>\n<p>RoQ is a vector quantizer video codec that maintains a codebook of 256 2&#215;2 pixel vectors. In the Quake III and later RoQ files, these are transported using a YUV 4:2:0 colorspace&#8211; 4 Y samples, a U sample, and a V sample to represent 4 pixels. This totals 6 bytes per vector. A RoQ codebook chunk contains a field that indicates the number of 2&#215;2 vectors as well as the number of 4&#215;4 vectors. The latter vectors are each comprised of 4 2&#215;2 vectors.<\/p>\n<p>Thus, the total size of a codebook chunk ought to be (# of 2&#215;2 vectors) * 6 + (# of 4&#215;4 vectors) * 4.<\/p>\n<p><em>However, this is not the case with The 11th Hour RoQ files.<\/em><\/p>\n<p><strong>Longer Codebooks And Mystery Colorspace<\/strong><br \/>\nJuggling the numbers for a few of the codebook chunks, I empirically determined that the 2&#215;2 vectors are represented by <strong>10 bytes instead of 6<\/strong>. Now I need to determine what exactly these 10 bytes represent.<\/p>\n<p>I should note that I suspect that everything else about these files lines up with successive generations of the format. For example if a file has 640&#215;320 resolution, that amounts to 40&#215;20 macroblocks. dreamroq iterates through 40&#215;20 8&#215;8 blocks and precisely exhausts the VQ bitstream. So that all looks valid. I&#8217;m just puzzled on the codebook format.<\/p>\n<p>Here is an example codebook dump:<br \/>\n<!--more--><\/p>\n<pre>\r\nID 0x1002, len = 0x0000014C, args = 0x1C0D\r\n  0: 00 00 00 00 00 00 00 00 80 80\r\n  1: 08 07 00 00 1F 5B 00 00 7E 81\r\n  2: 00 00 15 0F 00 00 40 3B 7F 84\r\n  3: 00 00 00 00 3A 5F 18 13 7E 84\r\n  4: 00 00 00 00 3B 63 1B 17 7E 85\r\n  5: 18 13 00 00 3C 63 00 00 7E 88\r\n  6: 00 00 00 00 00 00 59 3B 7F 81\r\n  7: 00 00 56 23 00 00 61 2B 80 80\r\n  8: 00 00 2F 13 00 00 79 63 81 83\r\n  9: 00 00 00 00 5E 3F AC 9B 7E 81\r\n  10: 1B 17 00 00 B6 EF 77 AB 7E 85\r\n  11: 2E 43 00 00 C1 F7 75 AF 7D 88\r\n  12: 6A AB 28 5F B6 B3 8C B3 80 8A\r\n  13: 86 BF 0A 03 D5 FF 3A 5F 7C 8C\r\n  14: 00 00 9E 6B AB 97 F5 EF 7F 80\r\n  15: 86 73 C8 CB B6 B7 B7 B7 85 8B\r\n  16: 31 17 84 6B E7 EF FF FF 7E 81\r\n  17: 79 AF 3B 5F FC FF E2 FF 7D 87\r\n  18: DC FF AE EF B3 B3 B8 B3 85 8B\r\n  19: EF FF F5 FF BA B7 B6 B7 88 8B\r\n  20: F8 FF F7 FF B3 B7 B7 B7 88 8B\r\n  21: FB FF FB FF B8 B3 B4 B3 85 88\r\n  22: F7 FF F7 FF B7 B7 B9 B7 87 8B\r\n  23: FD FF FE FF B9 B7 BB B7 85 8A\r\n  24: E4 FF B7 EF FF FF FF FF 7F 83\r\n  25: FF FF AC EB FF FF FC FF 7F 83\r\n  26: CC C7 F7 FF FF FF FF FF 7F 81\r\n  27: FF FF FE FF FF FF FF FF 80 80\r\n<\/pre>\n<p>Note that 0x14C (the chunk size) = 332, 0x1C and 0x0D (the chunk arguments &#8212; count of 2&#215;2 and 4&#215;4 vectors, respectively) are 28 and 13. 28 * 10 + 13 * 4 = 332, so the numbers check out.<\/p>\n<p>Do you see any patterns in the codebook? Here are some things I tried:<\/p>\n<ul>\n<li>Treating the last 2 bytes as U &#038; V and treating the first 4 as the 4 Y samples:<br \/>\n<center><br \/>\n<img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/multimedia.cx\/eggs\/wp-content\/uploads\/2012\/04\/roq-y8.png\" alt=\"\" title=\"RoQ, selected 8-bit Y samples\" width=\"70\" height=\"60\" class=\"aligncenter size-full wp-image-3773\" \/><br \/>\n<\/center>\n<\/li>\n<li>Treating the last 2 bytes as U &#038; V and treating the first 8 as 4 16-bit little-endian Y samples:<br \/>\n<center><br \/>\n<img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/multimedia.cx\/eggs\/wp-content\/uploads\/2012\/04\/roq-y16.png\" alt=\"\" title=\"RoQ, 16-bit Y samples\" width=\"70\" height=\"60\" class=\"aligncenter size-full wp-image-3774\" \/><br \/>\n<\/center>\n<\/li>\n<li>Disregarding the final 2 bytes and treating the first 8 bytes as 4 RGB565 pixels (both little- and big-endian, respectively, shown here):<br \/>\n<center><br \/>\n<img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/multimedia.cx\/eggs\/wp-content\/uploads\/2012\/04\/roq-rgb16le.png\" alt=\"\" title=\"RoQ, 16-bit little-endian RGB\" width=\"70\" height=\"60\" class=\"aligncenter size-full wp-image-3775\" \/> <img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/multimedia.cx\/eggs\/wp-content\/uploads\/2012\/04\/roq-rgb16be.png\" alt=\"\" title=\"RoQ, 16-bit big-endian RGB\" width=\"70\" height=\"60\" class=\"aligncenter size-full wp-image-3776\" \/><br \/>\n<\/center>\n<\/li>\n<li>Based on the type of data I&#8217;m seeing in these movies (which appears to be intended as overlays), I figured that some of these bits might indicate transparency; here is 15-bit big-endian RGB which disregards the top bit of each pixel:<br \/>\n<center><br \/>\n<img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/multimedia.cx\/eggs\/wp-content\/uploads\/2012\/04\/roq-rgb15.png\" alt=\"\" title=\"RoQ, 15-bit RGB\" width=\"70\" height=\"60\" class=\"aligncenter size-full wp-image-3777\" \/><br \/>\n<\/center>\n<\/li>\n<\/ul>\n<p>These images are taken from the uploaded sample bdpuz.gjd, apparently a component of <a href=\"http:\/\/www.mobygames.com\/game\/dos\/11th-hour\/screenshots\/gameShotId,50221\/\">the puzzle represented in this screenshot<\/a>.<\/p>\n<p><strong>Unseen Types<\/strong><br \/>\nIt has long been rumored that early RoQ files could contain JPEG images. I finally found one such specimen. One of the files bundled early in the uploaded fhpuz.gjd sample contains a JPEG frame. It&#8217;s a standard JFIF file and can easily be decoded after separating the bytes from the resource using &#8216;dd&#8217;. JPEGs serve as intraframes in the coding scheme, with successive RoQ frames moving objects on top.<\/p>\n<p>However, a new chunk type showed up as well, one identified by 0x1030. I have never encountered this type. Where could I possibly find data about this? Fortunately, <a href=\"https:\/\/github.com\/id-Software\/\">iD Games recently posted all of their open sourced games at Github<\/a>. Reading through <a href=\"https:\/\/github.com\/id-Software\/Quake-III-Arena\/blob\/master\/code\/client\/cl_cin.c\">the code for their official RoQ decoder<\/a>, I see that this is called a RoQ_PACKET. The name and the code behind it are both supremely unhelpful. The code is basically a no-op. The payloads of the various RoQ_PACKETs from one sample are observed to be either 8784, 14752, or 14760 bytes in length. It&#8217;s very likely that this serves the same purpose as the JPEG intraframes.<\/p>\n<p><strong>Other Tidbits<\/strong><br \/>\nI read through the readme.txt on the first game disc and found this nugget:<\/p>\n<pre>\r\n        g)      Animations displayed normally or in SPOOKY MODE\r\n\r\n                SPOOKY MODE is blue-tinted grayscale with color cursors, puzzle\r\n                and game pieces.  It is the preferred display setting of the\r\n                developers at Trilobyte.  Just for fun, try out the SPOOKY\r\n                MODE.\r\n<\/pre>\n<p>The <a href=\"http:\/\/www.mobygames.com\/game\/dos\/11th-hour\/screenshots\">MobyGames screenshot page<\/a> has a number of screenshots labeled as being captured in spooky mode. Color tricks?<\/p>\n<p>Meanwhile, another twist arose as I kept tweaking dreamroq to deal with more RoQ weirdness: After modifying my dreamroq code to handle these 10-byte vectors, it eventually chokes on another codebook. These codebooks happen to have 6-byte vectors again! Fortunately, I was already working on a scheme to automatically detect which codebook is in play (plugging the numbers into a formula and seeing which vector size checks out).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Investigating how to play the RoQ multimedia files found on the 4-CD game The 11th Hour, the original game to use this file format<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[29],"tags":[246,277,245,284],"class_list":["post-3766","post","type-post","status-publish","format-standard","hentry","category-game-hacking","tag-dreamroq","tag-reverse-engineering","tag-roq","tag-vector-quantization"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/posts\/3766","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/comments?post=3766"}],"version-history":[{"count":14,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/posts\/3766\/revisions"}],"predecessor-version":[{"id":3807,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/posts\/3766\/revisions\/3807"}],"wp:attachment":[{"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/media?parent=3766"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/categories?post=3766"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/multimedia.cx\/eggs\/wp-json\/wp\/v2\/tags?post=3766"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}