MVI2: some news

August 8th, 2025

First of all, here’s some information for the context: MVI codecs rely on out-of-band flags to signal what capabilities and subsampling they use (the fact that they decided to store those flags in FOURCC is a different annoyance); and despite the potential variety, only couple of flags are used for each codec. For instance, of all MVI1 files I saw only one flag has been in use (golden frame—and it’s only in one game). MVI2 has two distinct sets of flag combinations, 0x804 and 0x200. The former means bog standard MVI coding (with one chroma sample set for 4×4 block) plus one extension feature, the latter means MVI2 version 2 (if that makes any sense) where they decided to make subsampling and features selectable per frame (as well as adding more of them) and moved them to the frame header while at it.

So far I concentrated my efforts on format 0x804 to see what the feature it is. It turned out to be low-resolution deltas, just like Truemotion 2. In this mode every odd pixel is coded as previous pixel plus half of luma delta for the next pixel. I still have to make the reconstruction run properly, but that’s nothing a lot of debugging can’t fix.

This should allow me to decode even some of MovieCD samples (including the one hidden in samples.mplayerhq.hu/drivers32 for some reason) and I’ve seen quite recognizable frames already.

It’s hard to tell what features the other flavour uses but it’s reasonable to assume that it uses lowres coding as well. Hopefully I’ll get to it soon.

Update from Saturday: after dealing with the annoyance of different deltas coding scheme per each line type, I can now decode the few files I could find (including a couple of movieCDs from archive.org) just fine. The second half seems to use an alternative composing/rendering functions and reads maps differently as well. So while it’ll take more time, at least I’m closer to completion.

MVI1: done

August 3rd, 2025

In last post I wrote about how I’ve managed to reconstruct a recognizable picture for MVI1 codec. After I fixed the prediction code it started to work properly. Surprisingly, Treasure Quest game proved to be a source of MVI1 files in all formats (RGB, YUV422, YUV420, YUV410 and YUV4½0—the last one has one set of chroma samples per 4×4 block and is the most common MVI format in general). Additionally it has MVI1 samples with golden frame feature (I named it after a feature in a family of competing codecs that started with rather similar coding approach): frame 0 is two intra frames with the second frame serving as the background for the other frames; there is an additional map mode which tells that certain rectangles should be copied from the golden frame (instead of previous frame or filled with one colour). MVI2 seems to have an extension of that mode but I’ll see about it when I get to it (and if I obtain samples using that mode).

So, MVI2 next. Considering the number of extensions they added (and how they interfere with frame reconstruction) it’s probably not going to be easy but now I have a base to extend instead of blind guesses to make.

Motion Pixels: breakthrough!

August 1st, 2025

As I mentioned last month, I decided to reverse engineer Motion Pixel codecs and after a lot of failed attempts to make decoder work I’ve finally got something.

First of all, here are two frames from different videos.

MVITEST.AVI:

And a frame from SHOT09.AVI (from Apollo 18 game):

As you can see, the images are not perfect but recognizable already. And just last week it would’ve been a mess of colours with some barely recognizable distorted shapes.

The reason for this is that while MVI1 (and MVI2) is indeed based on stand-alone MVI format (I call it MVI0 for clarity), there are some nuances. On the first glance MVI0 and MVI1 are the same—all steps are the same—and indeed you can use the same code to decode data from either, but the reconstruction steps differ significantly.

Essentially there are four steps there: decode rectangles defining which parts of the frame will be left intact or filled with one colour, decode deltas used to reconstruct the rest of pixels, use those deltas to generate predictors for each line (where needed), use the rest of deltas to reconstruct the rest of pixels. Additionally MVI employs chroma subsampling mode so only one pixel in 1×1 to 4×4 block (depending on mode) has delta differences applied to chroma, all other pixels update only luma component. So if you don’t do it correctly you may end up applying e.g. deltas intended for luma to chroma components and vice versa. That’s what I got for a long time and could not understand why.

It turns out that vertical prediction has its pixel sampling at different position—or maybe it’s scrambled in the same way as line prediction. There for the most common mode (one set of chroma components per 4×4 block) each group of four lines is decoded in reverse order (i.e. 3, 2, 1, 0, 7, 6, 5, 4, …). For 2×2 block only lines in pairs are reversed. You can see artefacts of wrong prediction on Apollo frame.

Anyway, having a recognisable picture means that the hardest part (for MVI1) is done, so all is left now is to fix the remaining bugs, refactor the code and move to MVI2. There are other annoying things there but now I know how to deal with them.

BTW, if you’re curious why it takes so long, the problem is the binary specification being obfuscated to the point that Ghidra refuses to decompile most of MVI1 decoder functions and can’t do much about reconstruction functions since they’re a mess of spaghetti code (probably written in assembly language directly) so it’s more of a state machine than a decoding loop. And they abuse segment registers to access different parts of the context (and this must be the reason why it cannot work under OSes from this millennium). I got some progress when I resorted to debugging this mess by running MVI2 player OllyDbg under Win95 (emulated in DosBox-X) and constantly referring to Ghidra to see where to put breakpoint to trace a certain function. That process is definitely not fun for me but it gave results.

Overall, probably it could’ve gone better but I hope the rest won’t take as long.

Random NihAV news

July 24th, 2025

Since I have not tweaked any weights and have not made any releases, I’ll just write about some stuff I’ve been working on but have not released yet. Meanwhile librempeg got support for a bunch new formats too so its changelog may be a more interesting read. Anyway, this post is about what I have (and haven’t) done.

First of all, I’ve finally fixed an annoying problem with VA-API decoding on one of my laptops. Counterintuitively, it turned out to be faster to request hardware to convert native surface into some other format (NV12 into YUV420) and then use it instead. This made decoder CPU usage drop under 10% at last. Probably it can be optimised further to reduce load on graphics subsystem but I’d rather not mess with OpenGL unless it’s really really really needed.

Then I expended support for two formats in na_game_tool. VDX (used in The 7th Guest) had a different format version for the game demo. It still employs two-colour VQ but data for intra frames is split into separate parts for masks and colours, and inter frames code updates to masks and/or colours for each block instead of independent decoding. Additionally thanks to Discmaster I’ve located DPEG version 2 which employs completely different algorithm from the version 3 (painting 4×4/2×2/1×1 squares for intra and skip/update for inter).

I’ve also discovered some new interesting formats like Lantern MOV (which codes DIB changes using 16-bit RLE and there’s a probably related older version in IFF instead of RIFF). I’m considering making a sister project to na_game_tool to decode various formats like this one, formats coming from Amiga, recording formats and such—for all the formats that I’d like to try decoding but don’t want in main NihAV. I’ll write about it when I actually have something to write about (i.e. when I have a name and enough formats for 0.1.0 release). Another curious find was fractal video codec—not the ClearVideo but something with fourcc FVF1 from Images Incorporated. Who knows, it may be interesting to RE.

And finally here’s what I really wasted too much time on: Motion Pixels decoders. It has rather annoying binary specification (like using segment registers to address decoder context variables) that decompilers refuse to translate and from I heard it’s impossible to run on anything newer than Windows 95 or NT4. Nevertheless the formats pose some interest.

From what I saw long time ago, MVI2 is MVI1 with certain extensions, and MVI1 is surprisingly close in the structure to MVI in its own format files—and Gregory Montoir has reverse engineered it long time ago.

So I started by reimplementing that MVI decoder (since I can debug its behaviour against known working implementation) while trying to understand what it does. I got it more or less working (reconstruction is still not perfect but at least it’s recognizable) and my decoder supports other files (found with Discmaster of course) that trigger demuxer bugs or have different subsampling modes.

Then I moved to implementing MVI1 decoder applying the differences found in the binary specification. While it still does not handle decoding properly (both the pictures are garbled and I don’t use all deltas stored in the frame), at least it proves I’m on the right way. Hopefully it’ll decode properly soon and then I can add MVI2 features. Of course it’s a useless format nobody cares about, but apparently I do.

Strata, or yet another reason for not living in the USA

July 8th, 2025

Disclaimer: this post is not about politics at all, but rather about my personal reason.

Here’s the story that finally made me realise why USA is not for me (beside many other reasons that have something to do with my tastes. And don’t label them as sour grapes—I got some job offers from there back in the day yet I rejected them in favour of Europe).

I like to spend week-ends and holidays travelling around. Before 2020 I liked to travel somewhere far and travel around that distant point, now I can take only local travels (for health considerations). Last Sunday I decided to visit Seligenstadt but thanks to Deutsche Bahn I missed the connection and had to wait for almost an hour at Hanau. Since I had nothing better to do, I decided to take a walk there and was shocked.

The town turned out to be not merely Grimm (being the birthplace of the famous brothers) but also grim and soulless. Essentially all it had to offer in historical buildings was its town hall and a church (maybe I could discover more but probably not in the town centre and definitely not in the time I had). You don’t need to read any documents to guess that Hanau was heavily bombed during WWII and nobody bothered to reconstruct it (it’s much more important to have a bank and an airport after all).

And then I saw Seligenstadt, which is a complete opposite, with a preserved historic centre next to the more than millennium-old monastery. That’s what made me realise that I can’t live in a space without history comfortably. And USA is exactly a country that is rather poor in that aspect (compared even to Mexico). For comparison I consider my home city rather young—and yet it had been founded before New England royal colonies were chartered let alone USA as a country appeared on the maps. That is why I subconsciously liked Europe; probably other things I like about Europe (like food) also have roots in its rich historical soil. Similarly probably a lot of things I dislike about USA also come from its lack of historical soil (again, like food).

Of course other people don’t care about such things, which means less competition for them from my side and vice versa.

P.S. In case it was not obvious, this post name comes from the early Pratchett’s novel Strata, where one of the ideas was that newly terraformed worlds also included specially-crafted fossils in different geological strata—because humanity does not feel right living on a planet without history. It turned out to be true at least for me.

News and foam

July 4th, 2025

…the knowledge of certain principles easily compensates the lack of knowledge of certain facts.

Claude Adrien Helvétius, De l’esprit (1758)

Today I want to rant about a different aspect of the world. There is a constant stream of what is called news every minute, but if you take a closer look at it most of those pieces of news are not worthy of any attention. That is why I distinguish news—pieces of information about events that affected something—and foam—derivative pieces that bring no useful information, taking more volume than the original news and quite often used to obscure the original source. If you have a suspicion that it applies to other produced content (like “X reacts to Y” videos) then you may be right.

Anyway, suppose there is some planned event X. Usually the reports related to it will go like this:

  1. X is going to happen!
  2. random unrelated person comments on X;
  3. famous self-proclaimed expert estimates impact of X;
  4. X is scheduled to happen in next couple of days;
  5. X happens;
  6. the administration comments upon X success/failure/whatever;
  7. random unrelated person comments on X;
  8. random tangentially related person comments on X;
  9. aftermath of X.

It should be obvious that #5 is the real piece of news, with #1 and #4 having some importance (and #9—but only in the case when that event had unforeseen consequences). The rest is just a filler for news feed (those ad views won’t generate themselves, you know). This may be done to keep interest to the topic itself, but then it’s propaganda and not really news.

The statement from the epigraph can be applied to the news as well: if you know how it normally goes you don’t need to follow the news. Here’s a typical example for news I care about: russia commits umpteenth war crime (the fact by itself is no news, it’s circumstances that make it news); Ukrainian president / minister of foreign affairs / other official condemns it (that’s what they always do so it’s no news); some European official expresses condolences (still no news); russia celebrates and boasts how it hit important military target (which almost every time is a civilian infrastructure—tenement house, post office, hospital and such; but russians lying is no news either); USian administration trying their best to ignore the fact that russians did it (if you haven’t spotted the pattern, it’s still no news). There may be some follow-up pieces of actual news eventually (rescuers finishing operation, new bodies discovered, some victims of the attack dying at the hospital, a local mourning day being declared if the victims count is too high) but they do not add much to the picture.

Similarly news from the USA are rather irrelevant if you know a couple of things that have happened recently: USians elected a chaotic president, who decided that it’s time to cash on all the goodwill USA has been building since 1940s; his favourite tool is tariffs; his team consists mostly of people picked for their loyalty and not intellect; after events of 2020 he decided that the system of checks and balances hinders him and should be dismantled. So every time I see something about his administration violating the law with no repercussions, members of it proving themselves incompetent with no consequences, tariffs being declared and/or imposed on some random country and then waived again—those are things to be expected. Even the split of two greatest lovers on Earth was only a question of time—and when it happened was a real piece on news, unlike what they wrote about each other in their own social networks (it may be interesting to the future historians and current stand-up comedians though). And if you remember the phrase “Will no one rid me of this turbulent priest?” then subordinates acting without explicit president’s order is no news for you either.

Similarly global EU news are non-existent if you remember that important decisions require consensus—and there’s Hungary (and Slovakia time from time) using its veto power to extract benefits (from both EU and russia, and occasionally China). And of course member countries not willing to spend money on infrastructure and defence are no news either.

In conclusion I want to say that while thinking hurts, it can still save you time. Sometimes important news happen, but mostly you don’t even need to scan news headlines that thoroughly.

PMM support for na_game_tool

June 27th, 2025

While I suffer from thermal throttling, I don’t have much progress to report. Mostly I did some quality of life fixes for NihAV. For example, BaidUTube decided to switch AAC streams to HE-AACv2 in some cases, so I spent some time debugging and fixing SBR decoder in order to get rid of annoying chirps.

But finally I decided to be more systematic and tried to compile a list of known FMV (and FMV-ish) games and what formats they use. In the end I got a list of about four hundred known games, though of course I did not look at all of them: some games are not easy to find, others are console games which I’d rather not touch. SEGA formats are more conventional (often it’s just Cinepak or TrueMotion in AVI), while 3DO or CD-i is something too exotic and for Playstation you have better luck with librempeg.

Anyway, while compiling this list I discovered that PMM format is used in three games, so I decided to implement it at last. It is supposed to contain rather simple quadtree-based video coding with 15-bit pixels that may be updated per-component, and audio may be coded with one of several simple compression ways. Unfortunately format description in The Wiki was somewhat lacking and did not describe audio type 3, so I had to look at the binary specification.

Audio compression type 3 turned out to be real DPCM (while type 2 is not DPCM but rather 16-to-8 bit mapping scheme a la A-/μ-law). It codes packets of 16 bytes where the first byte is delta shift and the rest are 4-bit signed delta values.

The format itself turned out to be more curious than expected as it may be in either endianness (which affects not merely chunk sizes but header fields and code bits of the video data as well). And to push it further, it apparently also code frames as raw palettised data (using SEGA Saturn palette format).

But that’s not all, Area 51 game actually uses YUV colourspace for some of its videos so sometimes you get BGR555 and sometimes it’s YUV555—with YUV to RGB conversion coefficients sometimes transmitted in a special chunk. I suppose it improves compression ratio a bit.

In conclusion I say the usual thing: looking at those old formats is usually more interesting than looking at the new ones. Back in the day developers had fewer opportunities to borrow ideas from the standard formats (except for FLI of course) and threw different crazy ideas in order to see what sticks. For instance, some French codecs liked to perform motion compensation tied to affine transform (i.e. rotating and/or mirroring that block); some codecs used RLE operating on pairs of bytes or copying backwards; some codecs stopped at coding block with two colours, while others used several or even put those colour combinations into a frame-level codebook. Even those two codecs (Reaper and Xilam DERF) that would look like ordinary JPEG clones used an interesting mix of bytes and nibbles to code coefficients without wasting too much space or resorting to Huffman coding. And nowadays people are lazily waiting for H.267 (and if anybody would use H.266 for real) and AV2 (which should’ve been released about four years ago). No wonder people have little interest in multimedia.

P.S. It’s not likely that I’ll work on supporting another game format soon. Even disregarding the heat wave, I have other things I’d rather work on (and maybe there will be something to report eventually).

The end of FFhistory

June 19th, 2025

For those who infer from the title I’m going to talk about something negative, this is a philosophical term with rather positive definition. Also I guess this post goes against the old Latin principle noli stercus tangere et non olebit but oh well.

I happen to observe FFmpeg (not sure how to call this iteration of it) and it looks like it’s been never greater before, with a further potential to grow:

  • there’s a new developer who not only looks like a perfect fit for the project but also seemed to unite other developers;
  • the development has achieved new levels and got to the modern standards as can be seen from this patchset;
  • even long-standing complaints about inaction against badly behaved developers have been addressed and bans are enacted (since last year even);
  • important patchsets are pushed without unnecessary delays (for example, caused by reviews);
  • participating in various trade shows with constant success;
  • and thanks to STF the core developers can get funding for the crucial (or even critical) work in transparent way; and apparently cryptocurrency is an option too.

The things are going so well that FFmpeg can really afford not to care about every potential contributor (that ship has sailed indeed). More than that, they’ve even got their own soft Jia Tan—a person writing under multiple accounts and who introduced what others considered a glaring security hole (like there’s anything wrong with ffmpeg launching a third-party application on user’s request). All the tell-tale signs of success are there!

I honestly don’t see how the project can get any better (and yes, I feel that I’m not worthy to use it so I don’t).

NihAV: now with TealMovie support

June 11th, 2025

Back in the day I looked at the format and recently, to distract myself from game formats, I decided that it might be a good not the worst idea to implement decoding it.

And in the course of doing that I discovered some things that make it even more peculiar. For starters, it flips every second sample in its ADPCM coding. I don’t know if it improves compression in this particular case or it was done just to be different. Similarly split sub- or sub-sub-blocks are coded in コ-order instead of more traditional zigzag order.

But there are more interesting things about it. For starters, the file is organised into blocks instead of frames. First block always contains metadata (streams parameters, title, creator and such), next blocks contain one or more video frames (which you have to decode one after another; I implemented frame parsing for finding out frame boundaries but that’s inelegant solution), and last blocks are used to store audio. This means demuxer either has to demux audio frames after all video frames are sent or jump places in order to maintain synchronisation. Since this is not na_game_tool, I picked the former. The samples are short, so it’s easier to decode them to AVI+WAV and remux properly (or decode both streams to AVI and make AVI demuxer handle unsynchronised streams better—but that’s a task for another day).

Another surprising thing is that there is 16-bit RGB support, done in a very peculiar way. Frame decoding remains the same, except that now frame data is actually a pseudo-YUV frame with two chroma planes following the luma plane. And of course the conversion is done using one of two tables (depending on file version) using the formula yuv2rgbtab[(u + v) * 128 + y]. I guess it’s coding luma, colour difference and colour difference difference here.

And finally, intra frames in TealMovie are stored raw. But when frame width exceeds 160, it is stored half-size.

That’s why I’m looking at those old formats: there’s significantly more variety there in employed coding methods and storage format nuances. Not all of them make much sense but sometimes they’re entertaining or so original that it makes you wonder why such approaches got no further development.

P.S. Maybe I should take another look at the handheld console video formats.

P.P.S. But I think I’ll have to do some boring things instead. With BaidUTube changing its available formats it seems I finally need my own MP4 muxer. In either case that’s easier than to fix libav.

na_game_tool 0.4.0 release

June 3rd, 2025

At last a new version, now with over a hundred various formats being supported!

About a third of them are console formats of mostly raw variety (you need just to figure out how tile data is represented) but some of them required some reverse engineering as well (e.g. Road Avenger employs LZ77-based compression). Another third comes from known sources (either The Wiki or ScummVM source code. Last third is from my reverse-engineering efforts on DOS games.

What’s next? I don’t make plans, but I like the idea (suggested by Paul of librempeg fame) to make the tool more versatile. So version 0.5.0 should at least support extracting data from game archives: some of the currently supported video formats can be found only inside large game archives, so why not provide a way to obtain the files as well?

Beside that, I’ll probably focus on more complex codecs, namely the ones that are based on other codecs like Cinepak, JPEG or Smacker. I’m not sure I’ll be able to find another dozen formats to RE but at least there should be something new beside merely archive extraction.

Meanwhile grab the current version here (or don’t, it shan’t make a difference).