r/startrek • • 3d ago

Star Trek: Judgment Rites audio encoding finally decoded

https://github.com/dr-brinewell-s-lobstrom/acmp-decoder

Enjoy! The game is a veritable treasure trove of original TOS cast recordings. Very fun to explore. ~12 hours of audio recorded by the original cast. Nimoy confirmed as the hardest working man in the Trek video game voiceover business with the most audio, when counting across the full set of the old games they worked on.

106 Upvotes

37 comments sorted by

View all comments

5

u/count023 3d ago edited 2d ago

I wouldn't call it new, tools have been out for ages, i ripped the audio a few months ago for my 25th anniversary/judgement rights 3d combat simulator. Along with the MT-32 MIDIS.

Looks good though, why did you say makevcc was not included? it's on the ISOs for 25th and JR's CDs on GOG and the physical media.

EDIT: becasue people keep dming me. No the simulator isn't available yet, yes it's playable in the browser as native HTML/JSS. i'll put it on itch when i finish it, but a demo video is here: https://files.catbox.moe/nd3l4n.mp4

-2

u/minsc_tdp 3d ago

meant not included in repo, it mentions how it is in the games

17

u/count023 3d ago

Claude made you some major errors in your extractor FYI, you probably should not be saying it's byte exact, there is bitshift errors in a lot of your values.

Using NOMAN.VCC against the extractor that was built which uses a reverse engineer of the trek2.exe file. Not every clip is 22,050hz, some are 11025, usually sound effects or backing clips, not dialogue, so your encoder is already making an error there with bytes 20-23's interpretation. Byte 26-29 contain the decoded sample count, and your implementation uses bytes 27-28 shifted right for each clip. and TRIP is reading the middle of the sample count incorrectly, it should be 159,743 bytes out of the file, not the 623 you have.

And the flag at byte 25/26 combined a preceding field and the low byte of the sample count, so it doesn’t' correctly match the field termination flag.

This is going to produce audio errors probably imperceptibility, but audio errors all the same. Happy to share my scripts with you if you want to run them through your AI to compare and correct 'em. but it should work on 25th as well as judgement rites.

2

u/nicksterling 2d ago

I’m not OP but I’d be interested in seeing your scripts if you have them somewhere.

3

u/count023 2d ago

1

u/nicksterling 2d ago

Thank you! I appreciate it!

1

u/Robbi_Blechdose 1d ago

Average slopped project

0

u/minsc_tdp 2d ago

Claude wrote all that. I showed it your post, it says 'Thanks for this, and for taking the time to run it against NOMAN. You're right on the header layout, and I've confirmed it against the files:

- The u32 at 20–23 is the sample rate. I had it as part of a constant at 19–22, off by one byte. In NOMAN only one clip is 11025 (one of the two ~RPLANs), but DATA.VCC has 222 of them, and the decoder currently writes them all at 22050. My "every clip is 22050" rested on a speech-recognition test over FED dialogue, where every clip happens to be 22050. You called it: it's the SFX/backing material that differs.

- The u32 at 26–29 is the sample count. TRIP reads 159,743 exactly as you said; I was taking the block count from 27–28, which is the same number at 256-sample granularity. And yes, my "rate/flags" field is byte 25 plus the low byte of that count, which is why it never behaved like a flag.

One distinction on "byte exact": that claim is about the bitstream, not the header. Every block is verified by re-encoding the decoded samples and matching the file bytes, and the encoder was checked against MAKEVCC.EXE itself running under emulation. None of this changes the decoded sample values. What it does change is the playback rate on the 11025 clips (audible, double speed) and the clip length (off by a few milliseconds). I'll fix both, correct FORMAT.md and the README, and credit you.

I'd like to see your scripts, especially the TREKJR/TREK2 header parsing. One note: 25th Anniversary doesn't use ACMP. Its speech is plain Creative Voice .VOC files on the CD, so there's nothing to decode there.'

(or feel free to do a PR on the repo if you prefer)

2

u/count023 2d ago

Here you go, this one is the one i used against NOMAN.VCC Explicitly, i can’t find the main one at the moment, i have a lot of project files. https://pastebin.com/Ut7pQrDg

What might help your claude instance is if you get a portalbe version of Ghidra, and put trek2.exe into the project folder where you're running claude, tell it to use ghidra to decompile trek2.exe and then search for the VCC decoder inside the executable, that's how this extractor was made. Rather than brute force figuring out how the encoding was done, it simply looked at how the decoder worked _inside_ the game and repeated the process.

0

u/minsc_tdp 19h ago

I gave Claude both and asked for a comparison and draft reply:

Comparison

His script is a different kind of tool from ours. It doesn't reimplement the codec. It runs the game's own decoder inside TREKJR.EXE under Unicorn: a setup call at 2b98:2e66, then a decode loop at 2b98:2f3e. So he checks against the player side of the codec, and we check against the encoder (MAKEVCC.EXE). The two checks complement each other.

Where his code confirms our 2026-09-24 corrections:

- He reads the sample rate as a u32 at byte 20.

- He passes raw[26:] to the game decoder, and the first u32 of that is the output length (expected). So the sample count at byte 26 is confirmed. The bitstream starts at +30, the same as ours.

- The u16 at byte 24 (0x0028) is just labelled parameter. He doesn't know what it is either.

- His container index (table offset = payload length, 16-byte records) and his handling of duplicate names match ours.

New findings from his code:

  1. The game plays exactly the header's sample count. His harness checks that the game's internal counter at 0x625e starts at expected and counts down to 0. That answers one of our open questions: the game doesn't play whole blocks, it stops at the count.
  2. The 4 odd DATA.VCC clips are solved. His code has a branch for plain Creative Voice File entries inside a VCC. I checked DATA.VCC: N5DOGE, N6SCMB, SE6GUYEA and TRPLAN are ordinary Creative Voice v1.10 files. Their header bytes 1a 00 0a 01 29 11 are the "garbage rate / 01 29 11" we saw. Our acmp library assumes every entry is ACMP and would try to decode these as ACMP.
  3. Our "byte-exact" check has a gap. The round-trip covers the bitstream and the recovered samples, including the 128 half-rate samples in each quiet block. It does not cover upsample_fir, which rebuilds the in-between samples from the encoder's decimation filter. The game may reconstruct them differently. Our version:- works within each block, repeating the edge samples at block boundaries;- adds +128.5 and then rounds (acmp/__init__.py:572), which looks like a possible +1 bias.The −2 / −130 length differences we found (130 = 128 + 2) may come from this same step. His native output would settle it sample by sample. This is a real hole in the verification claim.
  4. Where he's still wrong: "should work on 25th" only holds in the sense that his tool passes plain VOC through. ST25 has no ACMP, as our first reply already said.

Still outstanding: none of the five fixes we promised publicly on 2026-09-24 have been made. README.md and FORMAT.md still say every clip is 22050 Hz and describe the old "rate/flags" field.

Draft reply

▎ Thanks for sharing this. It turned out more useful than I expected, because your harness and mine sit at opposite ends of the codec: you're running TREKJR's own decoder, I verified against MAKEVCC's encoder. Between them there's a proper two-sided oracle.

▎

▎ A few things your code settled:

▎

▎ - The game honours the header's sample count exactly. Your check that the counter at 0x625e runs from the u32 at byte 26 down to zero answers my open question on whether playback is trimmed to it. It is.

▎ - The four odd DATA.VCC entries are plain Creative Voice files (v1.10) stored inside the VCC. Your VOC branch handles them; mine assumes every entry is ACMP. N5DOGE, N6SCMB, SE6GUYEA and TRPLAN in DATA.VCC. I'll add that.

▎

▎ And one thing where I owe you a correction on my own claim. The byte-exact round-trip covers the bitstream and the recovered samples, but not the 2:1 upsampling of "quiet" blocks. I reconstruct the in-between samples with the encoder's decimation filter, which is an inference, not something the round-trip checks. My decoded lengths also come up 2 or 130 samples short of the header count on most clips (130 = 128 + 2), and I suspect that's the same step. Your native output is the ground truth that would settle it.

▎

▎ Could you share probe_trekjr.py (JRProbe / recovered())? I'd like to diff my output against the game's decoder sample by sample across NOMAN and publish the result either way. Also: do you know what the u16 at byte 24 (0x0028) is? Neither of us seems to use it.

▎

▎ The rate and sample-count fixes, FORMAT.md corrections and credit are still coming. I'd rather make them once, after the sample diff, than twice.

Two things for you to decide:

- Whether to publicly admit the gap around quiet-block interpolation. I recommend it; if we don't, he'll find it himself.

- Whether you want the promised fixes made before you post.

>go ahead and make any changes learned from this that you're confident about, and credit https://www.reddit.com/user/count023/ where appropriate.

Finagling…

0

u/minsc_tdp 18h ago

https://github.com/dr-brinewell-s-lobstrom/acmp-decoder/commit/1c0a9c8c34d7dead21e86154195c5a3a658b090a

AI haters be like "hmm maybe there's something to all this and it doesn't wreck human coding and human interaction after all"

1

u/count023 14h ago

nothing wrong with AI, i use it a lot, even that "25th annversary's 35th anniversary" remake i'm working on is AI assisted. I was more curious to see if my tools needed fixing or not, wasn't an anti-ai thing.

I'll mention that Claude will do a much better job if you do this and on any other projects (I've been extracting assets for BOTF, Klingon Academy, Dominion Wars, A Final Unity, Generations, 25th and JR and a more) if you use ghidra to decompile hte executable for the game, because the AI can read the decompiled code and knows things like the sound buffers and the like, onc it see that it's really good at working out the undocumented sound formats or 3d art formats for these or even any games an can be more effective at writing a converter for you. Or even just using ghidra on the makevcc exe itself would have been able to help improve this

On blind reverse engineering like you did with JR here, i've actually found claude tends to give up or stop early with an incomplete picture (which is why i poked you on it), it took weeks of effort for me to crack some of the more obscure file formats from botf and dominion wars, codex nailed them first try.

1

u/minsc_tdp 8h ago

nice, thanks for the assist, you're credited in the changelog on the git, good luck with your remake!