Welcome, Guest!

Here are some links you may find helpful

Dreamcast Grave robbing Geist Force

Sifting

Well-known member
Original poster
Registered
Aug 23, 2019
63
168
33
Okay, I'm back and this time with Geist Force. This one has been on the todo list for some time now, and while waiting for stuff to compile at work I decided to dig in. No time like the present, right?

I'm using @Sega Dreamcast Info's latest release for this. Right out of the gate it seems all of the game's assets are stored inside these mysterious MIFFs. What is a MIFF? Great question! Upon opening it in 010 Editor, or the hex editor of your choice, it seems that the MIFF is a generic container for various data types. Each data is prefixed with a 4 letter code and a length. From observation I've seen the following types:

  • MAct - Actions​
  • MMdl - Models​
  • MMat - Materials​
  • MTex - Textures​
  • MBmp - Bitmaps​
  • MHch - Hierarchy​

There might be more as well, but so far these are all the types I've found. Bitmaps seem like normal PVR textures, and the MTex block is more mysterious to me. It appears more like a list of textures, and MMat seems similar, but likely contain parameters for diffuse and specular lighting and such. The Hierarchy type is the most interesting to me - it seems like they're generic scene nodes, and, they might also function as bones for skeletal animation. There's also variable length data attached to each one, which at the moment I believe are key frames for animation. There appears to be no compression or anything like we saw in Agartha, so that makes this a much simpler task to figure out.

Beyond that it seems like audio is kept out in the open on the GDI. I suspect it's the usual Yamaha ADPCM, but VLC wouldn't play any of it, nor did ffmpeg recognise it, so there is likely some extra data to frame it. I was under the impression that there might be a lot of interesting things buried in Geist Force, but from my high level perspective it seems like most of the disk's contents are accessible in game? Does anyone have any particular files they'd like to be investigated?

More to come, hopefully soon. The end goal is to get the assets translated into common formats so that they may be viewed in all the different 3D/art packages out there. In the mean time for those wanting to play along, I have included my work so far!

MIFF template for 010 Editor

Code:
//GEIST FORCE
//Miff template
//By Sifting, 2022

BigEndian ();

//Ensure that the file is a miff
char magick[4];
if (magick != "MIFF")
{
    Error ("NOT A MIFF!");
}
uint32 size;        //Size of the entire file
char endian[4];     //Only Litl encoutnered - endian mode for file contents

//Each record seems to be prefixed with this
struct Record_info
{
    float unk;
    float unk;
    uint32 len;
    char name[len];
};

//MAct data
struct Action_entry
{
    uint32 unk;
    uint32 unk;
    uint32 unk;
    uint32 len;
    char name[len];
};
struct Record_action
{
    uint32 unk;
    uint32 count;
    local int i = 0;
    while (i++ < count)
    {
        Action_entry entries;
    }
};

//Record framing
struct Record
{
    char type[4];
    uint32 size;
  
    //Records may be little endian
    if ("Litl" == endian)
    {
        LittleEndian ();
    }

    Record_info info;
    if ("MAct" == type)
    {
        Record_action action;
    }
    else
    {
        byte data[size - sizeof(info)];
    }

    BigEndian ();
};

//Parse the file
local int _record_header_size = 8;
local int _header_size = 12;
local int _read = size - _header_size;
while (0 < _read)
{
    Record record;
    _read -= record.size + _record_header_size;
    Printf ("Read %i (%i)\n", record.size, _read);
}
 

Sifting

Well-known member
Original poster
Registered
Aug 23, 2019
63
168
33
The music has been recovered, and may be heard over at @Sega Dreamcast Info's page:



This was a bit of a fun one to figure out. As far as I could figure out, the audio files are all created using tools from the Saturn SDK. This is unsurprising as Geist Force was going to be a launch title for the Dreamcast, and the AICA sound chip is almost identical to the Saturn's SCSP, save for a more voices and a different controller.

An interesting common feature is both chips only support monaural voices, so to do stereo you have to use two and play them at the same time to mimic the effect. This means the left and right channel in the music are actually two separate mono streams, however on disk they're stored in one file, and what's more, the streams are interleaved in 1-2 second chunks.

So I had to write a script to deinterlace the streams and store them in separate files, from there I ran them through ffmpeg to translate them into normal pcm from the native yamaha adpcm, then I merged them the results in audacity to create a proper stereo mix, saved and gave them to @Sega Dreamcast Info for publishing. Not exactly automated!

Here's the script for the curious:

Python:
import sys

def main (file, chunksize):
    #Read the whole file
    with open (file, 'rb') as f:
        data = f.read ()
    #Open mono streams
    with open ('l.adpcm', 'wb') as l:
        with open ('r.adpcm', 'wb') as r:
            for i in range (0, len (data), 2*chunksize):
                l.write (data[i:i + chunksize])
                r.write (data[i + chunksize:i + 2*chunksize])

main (sys.argv[1], int(sys.argv[2]))

Run it with chunksize = 16384 or Bad Things will happen!

For ffmpeg:

Code:
ffmpeg -f u8 -acodec adpcm_yamaha -ar 22050 -ac 1 -i r.adpcm right.wav
ffmpeg -f u8 -acodec adpcm_yamaha -ar 22050 -ac 1 -i l.adpcm left.wav

You can use the same command above to decode the voice files as well!
 
Last edited:

dark

Well-known member
Registered
Jun 19, 2019
91
72
18
AGName
dark
AG Join Date
September 2, 2011
Thanks for checking it out. Back in the assembler days, there was a guy who was playing around with renaming files in order to play some pre-scripted animations on the disc. These were videos of the animations he was able to trigger, which seemed to show events and bosses that were not triggered in normal gameplay of the demo. Any ideas about how these were triggered and whether there might be more to these than just animations?






Any ideas about how to navigate the debug menu? I recall users at Assembler were able to open up an onscreen debug menu with various options using a Japanese dreamcast keyboard (Japanese keyboards apparently have a particular button that the US ones do not, and this Japan-keyboard only button opened up the debug menu). Even though the menu could be opened I think people weren't able to navigate or select options in the debug menu, none of the keyboard or controller commands in any port seemed to work to move around the menu.

I seem to recall that when the debug menu was opened on the ice stage, it also resulted in enemy buildings/enemies appearing on the ice stage that did not appear if the debug menu was not on. I wonder if there's anything to investigate there.
 

Sifting

Well-known member
Original poster
Registered
Aug 23, 2019
63
168
33
Any ideas about how these were triggered and whether there might be more to these than just animations?

I'm unsure of the specific way they were triggered at the moment, but if the person was just moving around files then it shouldn't be too complex.

I can confirm there is at least four bosses on disk that are not seen in normal gameplay, including one on stage 6 called the Alien Queen. Peeping their MIFFs I can see the action sets are present, and given how big the files are in relative terms, I feel confident in saying there are at least animations for all of them. Whether there is logic there too I'm unsure.

Also, the script for the game appears to be fully voiced in Japanese, from start to finish. So if someone wanted to piece together a drama CD, well, the possibility is there, haha.

Also the debug stuff, I believe Laurent has information on it at his site:


The debug menu (plug in a Japanese keyboard that has the "S3" button in controller port 2):

  • S3: Open debug menus
  • D: Open debug options
  • U: Open user options
  • S: pause the game
  • T: Button for switching the menu screen
    Arrow keys: navigate in the menu (after pressing a letter)
  • F12: Suicide the player's ship
  • F1: Gives the player's ship 9999 shield points
  • Z: rapid-fire laser for the player
 
  • Like
Reactions: Pitito

Sifting

Well-known member
Original poster
Registered
Aug 23, 2019
63
168
33
1656119400860.png

Alright, I have managed to recover the textures from the grave! It's still a bit touchy, since the game uses a custom header for texture data, so I have to do trial and error to figure out the appropriate values to decode them, but it's well underway!

Some player ship textures:

1656119538757.png1656119545634.png1656119557082.png

The world map of planet Susani:

1656119599615.png


And now here's some really interesting bits... did you know Geist Force has characters?

1656119633754.png 1656119644003.png
1656119654264.png 1656119663328.png
1656119673247.png 1656119679946.png

These are just a FEW of them! As I was poking around I kept bumping into new faces all the time! There's actually more than just the portraits too, indeed, there's a rather unique system in place. Each line of dialogue has an accompanying talking head file, and in the file there is a face texture, seen above, as well as textures for body, teeth and optional overlays like helmets and masks. Now the real interesting part is, if you've paid attention to the conversations in game, you may have noticed they're animated. You might have also noticed I said there are textures for teeth. These textures are not just displayed as static images, but they're applied to a few polygons, which are then animated to make the characters talk and give them ambient animation.

You might have noticed too that each time characters talk in the game it hitches and shows Now Loading in the bottom right corner... That's because it's streaming each animation in and out of memory, or so it would seem!

Next up is getting the 3D assets into gltf format, so they may be viewed from your favourite 3D package.
 

Yakumo

Well-known member
Registered
May 31, 2019
400
332
63
AGName
Yakumo
AG Join Date
From the beginning (whenever that was)
I
View attachment 14260

Alright, I have managed to recover the textures from the grave! It's still a bit touchy, since the game uses a custom header for texture data, so I have to do trial and error to figure out the appropriate values to decode them, but it's well underway!

Some player ship textures:

View attachment 14261View attachment 14262View attachment 14263

The world map of planet Susani:

View attachment 14264


And now here's some really interesting bits... did you know Geist Force has characters?

View attachment 14265 View attachment 14266
View attachment 14267 View attachment 14268
View attachment 14269 View attachment 14270

These are just a FEW of them! As I was poking around I kept bumping into new faces all the time! There's actually more than just the portraits too, indeed, there's a rather unique system in place. Each line of dialogue has an accompanying talking head file, and in the file there is a face texture, seen above, as well as textures for body, teeth and optional overlays like helmets and masks. Now the real interesting part is, if you've paid attention to the conversations in game, you may have noticed they're animated. You might have also noticed I said there are textures for teeth. These textures are not just displayed as static images, but they're applied to a few polygons, which are then animated to make the characters talk and give them ambient animation.

You might have noticed too that each time characters talk in the game it hitches and shows Now Loading in the bottom right corner... That's because it's streaming each animation in and out of memory, or so it would seem!

Next up is getting the 3D assets into gltf format, so they may be viewed from your favourite 3D package.
I'm loving the work done on this game. Giest Force is a game I have a deep interest in. By the way, I'm the one who originally published the details about the debug menu features 😊. I used a Japanese keyboard and also a mouse. I was also doing an English translation of the script but never did finish it. I still have the incomplete translation file on my hard drive. Here's a sample of what was done from the L6 Script.

L6Z5E5P3

時間がない!

We have no time! (There’s no time!)



L6Z5E5P2

コードよ、ルック、早く!

Quick, look at the code! (The code, Hurry, look!)



L6Z5E5P1

早くコードを入れて!

Quick, enter the code! (Hurry, enter the code!)



L6Z5E4P3

ああ、もう、違うでしょ!

AA, It’s different! (Oh No, It’s not right!)



L6Z5E4P2

違う、そのコードじゃない!

Not that code! (That’s the wrong code!)



L6Z5E4P1

機体がもたないわ!

There’s no airframe. (this is said by a woman or gay. You can tell by the わ at the end of the sentence. I also don’t really understand what airframe is meant to be)



L6Z5E3

やったぁ!

We did it! (I did it!)



L6Z5E2

このコードを入力して!

The code is entered! (The code has been entered / I’ve entered the code)



L6Z5E1D3

モニターに出すわ!

It’s been sent to the monitor! (It’s on the monitor!) said by a woman.



L6Z5E1D2

解除コードが要る!

The release code is needed! (We need the release code!)



L6Z5E1D1

,ルック、ゼロジー・ブースターを

切り放して!

Look!, The Zero G booster has separated!



CUTSCENE 6



L6C6E4

あぶない!

Watch out! (Danger!)



L6C6E3

了解、さっさとしてくれよ

Choose and do it quickly.
 
Last edited:
  • Like
Reactions: Pitito and Sifting

Sifting

Well-known member
Original poster
Registered
Aug 23, 2019
63
168
33
I was also doing an English translation of the script but never did finish it. I still have the incomplete translation file on my hard drive.
You know, I could write up a script that unpacks/repacks the event scripts. I think it should be easy, at least from my perspective at the moment. This way you could drop in what translations you have, repack the scripts, then we can rebuild the GDI so the first level or so would be in English. I can even rig it up so it copies out the audio files for you, so you can tune your translations to be more appropriate to the character voices, if you'd like
 

T_chan

Well-known member
Registered
Jun 6, 2019
85
65
18
AGName
T_chan
AG Join Date
Apr 13, 2008
機体がもたないわ!

There’s no airframe. (this is said by a woman or gay. You can tell by the わ at the end of the sentence. I also don’t really understand what airframe is meant to be)

I would translate it as:
"The frame won't hold !", or "the ship won't hold", as in: the frame (of the spaceship/...) won't be able to sustain extra damages/stress/....
 
  • Like
Reactions: Sifting

Sifting

Well-known member
Original poster
Registered
Aug 23, 2019
63
168
33
@Yakumo: alright, here it is: the ScriptMiff!


Python:
#!/usr/bin/env python3
from struct import unpack, pack
import argparse
import csv

#There has got to be a better way to do this
def safestring (string):
    s = ''
    for c in string:
        if c >= 32 and c <= 127: s += chr (c)
        else: break
    return s

def miffunpack (file):
    lines = []

    def extract (data):
        unk0, unk1, length = unpack (f'{ls}III', data[:12])
        text = data[12:12 + length].decode ('shift-jis').replace ('\0', '')
        llength = unpack (f'{ls}I', data[12 + length:12 + length + 4])
        link = data[12 + length + 4:].decode ('shift-jis').replace ('\0', '')
        return [link, unk0, unk1, text]

    with open (file, 'rb') as f:
        magick = f.read (4).decode ('latin')
        if 'MIFF' != magick:
            raise Exception (f'{file} is NOT a MIFF!')
   
        size = unpack ('>I', f.read (4))[0]
        endian = f.read (4).decode ('latin')

        if 'Litl' == endian: ls = '<'
        else: ls = '>'

        print (f'MIFF: {file}')
        print (f'SIZE: {size}')
        print (f'ENDIANNESS: {endian}')

        read = f.tell ()
        while read < size:
            #Record info
            rtype = f.read (4).decode ('latin')
            rlength = unpack ('>I', f.read (4))[0]

            #Meta data and file contents
            munk0, munk1, mlen = unpack (f'{ls}ffI', f.read (12))
            mname = safestring (f.read (mlen))
            mmfln = unpack (f'{ls}I', f.read (4))[0]
            mmfl = safestring (f.read (mmfln))

            contents = f.read (rlength - mlen - mmfln - 16)

            lines.append ([mname] + extract (contents))

            read += 8 + rlength
   
    #Now dump the lines into a csv for Yakumo
    with open (f'script.csv', 'w', newline='') as f:
        writer = csv.writer (f, delimiter=';', quotechar='\"', escapechar='\\', quoting=csv.QUOTE_MINIMAL)
        for ln in lines:
            writer.writerow (ln)

def miffpack (file):
    def padstring (string):
        if len (string) == 0:
            return bytes ()
       
        s = bytes (string.encode ('shift-jis'))
        aligned = (len (s)+2)&~1
        count = aligned - len (s)
        for i in range (count):
            s += '\0'.encode ('latin')
        return s

    data = bytearray ()
    with open (file, newline='') as f:
        reader = csv.reader (f, delimiter=';', quotechar='\"', escapechar='\\', quoting=csv.QUOTE_MINIMAL)
        for row in reader:
            print (row)
           
            er0 = padstring (row[0])
            er1 = padstring (row[1])
            er4 = padstring (row[4])

            record = bytearray ()
            record += pack ('<ff', 1.0, 1.0)
            record += pack ('<I', len (er0))
            record += er0
            record += pack ('<I', 0)
            record += pack ('<II', int (row[2]), int (row[3]))
            record += pack ('<I', len (er4))
            record += er4
            record += pack ('<I', len (er1))
            record += er1

            header = bytearray ()
            header += bytes ('GScr'.encode ('latin'))
            header += pack ('>I', len (record))
           
            data += header + record
           
   
    miff = bytearray ()
    miff += bytes ('MIFF'.encode ('latin'))
    miff += pack ('>I', 12 + len (data))
    miff += bytes ('Litl'.encode ('latin'))
    miff += data
    with open ('SCRIPT.MIFF', 'wb') as f:
        f.write (miff)
       
if __name__ == '__main__':
    parser = argparse.ArgumentParser (description='(Un)packs Geist Force script MIFFs')
    parser.add_argument('--pack', help='Builds a MIFF from a CSV file')
    parser.add_argument('--unpack', help='Extracts MIFF into a CSV file')
    args = parser.parse_args ()
   
    if args.unpack:
        miffunpack (args.unpack)

    if args.pack:
        miffpack (args.pack)

It dumps what ever script miff you feed it into a csv, which may be loaded with your favourite spreadsheet editor. To rebuild the miff with your translated lines do --pack <your csv> and it'll compile it into a new SCRIPT.MIFF; from there just replace the old one and rebuild the GDI.

I left out the sound file stuff for now, since I wasn't sure if you had ffmpeg installed. Really, thinking about it, I'm unsure how much of a technical person you are. I can supply all the scripts in csv if you would prefer. Either way a translation is possible now.

I'm unsure of the meaning of the numbers in columns 3 and 4, so I would leave them alone unless you're adventurous. The value in column two references the sound file, I believe. I'm unsure if @Sega Dreamcast Info has published them all yet, but I can supply them in mp3 if desired.

Scripts are located in: data/MIFF/STAGE*/EVENT/SCRIPT.MIFF
 
  • Like
Reactions: Pitito and T_chan

Yakumo

Well-known member
Registered
May 31, 2019
400
332
63
AGName
Yakumo
AG Join Date
From the beginning (whenever that was)
@Yakumo: alright, here it is: the ScriptMiff!


Python:
#!/usr/bin/env python3
from struct import unpack, pack
import argparse
import csv

#There has got to be a better way to do this
def safestring (string):
    s = ''
    for c in string:
        if c >= 32 and c <= 127: s += chr (c)
        else: break
    return s

def miffunpack (file):
    lines = []

    def extract (data):
        unk0, unk1, length = unpack (f'{ls}III', data[:12])
        text = data[12:12 + length].decode ('shift-jis').replace ('\0', '')
        llength = unpack (f'{ls}I', data[12 + length:12 + length + 4])
        link = data[12 + length + 4:].decode ('shift-jis').replace ('\0', '')
        return [link, unk0, unk1, text]

    with open (file, 'rb') as f:
        magick = f.read (4).decode ('latin')
        if 'MIFF' != magick:
            raise Exception (f'{file} is NOT a MIFF!')
  
        size = unpack ('>I', f.read (4))[0]
        endian = f.read (4).decode ('latin')

        if 'Litl' == endian: ls = '<'
        else: ls = '>'

        print (f'MIFF: {file}')
        print (f'SIZE: {size}')
        print (f'ENDIANNESS: {endian}')

        read = f.tell ()
        while read < size:
            #Record info
            rtype = f.read (4).decode ('latin')
            rlength = unpack ('>I', f.read (4))[0]

            #Meta data and file contents
            munk0, munk1, mlen = unpack (f'{ls}ffI', f.read (12))
            mname = safestring (f.read (mlen))
            mmfln = unpack (f'{ls}I', f.read (4))[0]
            mmfl = safestring (f.read (mmfln))

            contents = f.read (rlength - mlen - mmfln - 16)

            lines.append ([mname] + extract (contents))

            read += 8 + rlength
  
    #Now dump the lines into a csv for Yakumo
    with open (f'script.csv', 'w', newline='') as f:
        writer = csv.writer (f, delimiter=';', quotechar='\"', escapechar='\\', quoting=csv.QUOTE_MINIMAL)
        for ln in lines:
            writer.writerow (ln)

def miffpack (file):
    def padstring (string):
        if len (string) == 0:
            return bytes ()
      
        s = bytes (string.encode ('shift-jis'))
        aligned = (len (s)+2)&~1
        count = aligned - len (s)
        for i in range (count):
            s += '\0'.encode ('latin')
        return s

    data = bytearray ()
    with open (file, newline='') as f:
        reader = csv.reader (f, delimiter=';', quotechar='\"', escapechar='\\', quoting=csv.QUOTE_MINIMAL)
        for row in reader:
            print (row)
          
            er0 = padstring (row[0])
            er1 = padstring (row[1])
            er4 = padstring (row[4])

            record = bytearray ()
            record += pack ('<ff', 1.0, 1.0)
            record += pack ('<I', len (er0))
            record += er0
            record += pack ('<I', 0)
            record += pack ('<II', int (row[2]), int (row[3]))
            record += pack ('<I', len (er4))
            record += er4
            record += pack ('<I', len (er1))
            record += er1

            header = bytearray ()
            header += bytes ('GScr'.encode ('latin'))
            header += pack ('>I', len (record))
          
            data += header + record
          
  
    miff = bytearray ()
    miff += bytes ('MIFF'.encode ('latin'))
    miff += pack ('>I', 12 + len (data))
    miff += bytes ('Litl'.encode ('latin'))
    miff += data
    with open ('SCRIPT.MIFF', 'wb') as f:
        f.write (miff)
      
if __name__ == '__main__':
    parser = argparse.ArgumentParser (description='(Un)packs Geist Force script MIFFs')
    parser.add_argument('--pack', help='Builds a MIFF from a CSV file')
    parser.add_argument('--unpack', help='Extracts MIFF into a CSV file')
    args = parser.parse_args ()
  
    if args.unpack:
        miffunpack (args.unpack)

    if args.pack:
        miffpack (args.pack)

It dumps what ever script miff you feed it into a csv, which may be loaded with your favourite spreadsheet editor. To rebuild the miff with your translated lines do --pack <your csv> and it'll compile it into a new SCRIPT.MIFF; from there just replace the old one and rebuild the GDI.

I left out the sound file stuff for now, since I wasn't sure if you had ffmpeg installed. Really, thinking about it, I'm unsure how much of a technical person you are. I can supply all the scripts in csv if you would prefer. Either way a translation is possible now.

I'm unsure of the meaning of the numbers in columns 3 and 4, so I would leave them alone unless you're adventurous. The value in column two references the sound file, I believe. I'm unsure if @Sega Dreamcast Info has published them all yet, but I can supply them in mp3 if desired.

Scripts are located in: data/MIFF/STAGE*/EVENT/SCRIPT.MIFF
Thanks for the script but could you provide the scripts in csv? Every little bit to make life easier is welcome as I don't have much free time.
 
  • Like
Reactions: Sifting

Sifting

Well-known member
Original poster
Registered
Aug 23, 2019
63
168
33
@Yakumo Attached are the scripts in csv format, and the voice samples in mp3. I think only stage1 is used in the builds that we have though. Send them back my way when ever you get the time, and I'll see about getting a rebuilt GDI made. Kind of crazy to think this is probably the first time any development has been done on the game in... what? 23 years? haha

If you want to hear the matching audio file, use the values in column two to find the name of the mp3.

As illustrated:

1656182379375.png

Also, I saved the stuff as a .zip file, but the attachments thing wanted a .rar, which I have no software capable of making those. Kind of funny the site just let me rename the extension, but anyway, if your program complains, rename it to .zip
 

Attachments

  • geist.rar
    5.8 MB · Views: 0
  • Like
Reactions: Yakumo

Pitito

Well-known member
Registered
Jun 19, 2019
161
159
43
AGName
pitito
AG Join Date
03/08/2015
Hello, I just noticed what you do and it's fantastic :)
Have you set out to investigate this unreleased game?
Ring - The Legend of the Nibelungen
It has FMV with a strange format and it would be good to be able to encode and decode them
 
  • Like
Reactions: Sifting

Sifting

Well-known member
Original poster
Registered
Aug 23, 2019
63
168
33
Hello, I just noticed what you do and it's fantastic :)
Have you set out to investigate this unreleased game?
Ring - The Legend of the Nibelungen
It has FMV with a strange format and it would be good to be able to encode and decode them
I have no plans for Ring at present, though I can tell you ffmpeg has a high probability of already supporting what ever file format it uses. ffmpeg more or less supports everything. I wouldn't be surprised if it supports things that haven't been invented yet! I would start there first before anything, if I were you
 
  • Like
Reactions: Pitito

Pitito

Well-known member
Registered
Jun 19, 2019
161
159
43
AGName
pitito
AG Join Date
03/08/2015
Yes, I already tried with ffmpeg, and there was no way.
Try both the pc version fmv and the dreamcast version fmv.
Both carry the same CNM format, but their structure is slightly different.
I was doing some research on it, it seems that they use codec CIN (PC) and CIN 2 (DREAMCAST)
 
  • Like
Reactions: Sifting

Sifting

Well-known member
Original poster
Registered
Aug 23, 2019
63
168
33
Yes, I already tried with ffmpeg, and there was no way.
Try both the pc version fmv and the dreamcast version fmv.
Both carry the same CNM format, but their structure is slightly different.
I was doing some research on it, it seems that they use codec CIN (PC) and CIN 2 (DREAMCAST)

Seems it's already been taken care of
 
  • Like
Reactions: Pitito

Pitito

Well-known member
Registered
Jun 19, 2019
161
159
43
AGName
pitito
AG Join Date
03/08/2015
It's just where I get the information from, but I have no idea how to use those rows to encode and decode.
And it would be nice to have a tool for it
 
  • Like
Reactions: Sifting

dark

Well-known member
Registered
Jun 19, 2019
91
72
18
AGName
dark
AG Join Date
September 2, 2011
There is stuff in this 1998 preview of Geist Force that I haven't seen in the released builds. Namely the following:
-1:03, cruising around in space and coming up on a floating island with open lava cave (seems to be the same cave that's in the animation I posted previously)
-1:46, cruising around inside a lava cave
-2:00, English dialogue in level 1, and it is coming from a character you do not see in level 1 in the demo (sounds like the character pictures you found could have this character in them, or maybe he was a prototype character that was later replaced with the level 1 characters that we do see in the builds we have, or maybe it was intended that the characters would look different in the US release compared to the Japanese release - such a thing happened with Metal Head on the Sega 32X, anime characters for Japan and cruddy low res images of real people a la Mortal Kombat for the US localization)

Any digging around or insight into these would be interesting.

 

Sifting

Well-known member
Original poster
Registered
Aug 23, 2019
63
168
33
There is stuff in this 1998 preview of Geist Force that I haven't seen in the released builds. Namely the following:
-1:03, cruising around in space and coming up on a floating island with open lava cave (seems to be the same cave that's in the animation I posted previously)
-1:46, cruising around inside a lava cave
-2:00, English dialogue in level 1, and it is coming from a character you do not see in level 1 in the demo (sounds like the character pictures you found could have this character in them, or maybe he was a prototype character that was later replaced with the level 1 characters that we do see in the builds we have, or maybe it was intended that the characters would look different in the US release compared to the Japanese release - such a thing happened with Metal Head on the Sega 32X, anime characters for Japan and cruddy low res images of real people a la Mortal Kombat for the US localization)

Any digging around or insight into these would be interesting.



Space, floating island, and lava stuff is on disk, stored in the STAGE6 directory, judging by the texture dumps.


Sky textures:

bk2.MBmp.pngbk1.MBmp.png


Environment textures:

lava.MBmp.pngss.MBmp.pngwall.MBmp.png


The only English line on the whole disk is:

'N/A English Message Meo-Meta will be crush the colony! Only "Geist" stands in there way...'

Maybe the earlier build has more stuff? I can't mount CDI images to check though...

I'm making progress toward getting the assets translated into gltf for viewing, but I have work tomorrow so it will be slow.

EDIT:

Also found some more lava stuff in stage 4, so maybe lava cave is there.

lavaSpiller1.MBmp.pnglavapillar1.MBmp.png
 

Sifting

Well-known member
Original poster
Registered
Aug 23, 2019
63
168
33
Managed to get the 3D geometry mostly figured out. Some weird extra data on each strip though. Not quite sure what it is. Also it looks like they store colour in both float and argb8888 on each vertex? Very strange stuff. The hierarchy nodes are more esoteric. I have some general pieces mapped out but they're going to take a while, I think. I'll probably start on getting the geometry digested into gltf before I figure them out.

Latest template:

Code:
//GEIST FORCE
//Miff template
//By Sifting, 2022

BigEndian ();

//Ensure that the file is a miff
char magick[4];
if (magick != "MIFF")
{
    Error ("NOT A MIFF!");
}
uint32 size;        //Size of the entire file
char endian[4];     //Only Litl encoutnered - endian mode for file contents

//Each record seems to be prefixed with this
struct Record_info
{
    float unk;
    float unk;
    uint32 len;
    char name[len];
    uint32 mfln;
    char mfl[mfln];
};

//MAct data
struct Action_entry
{
    uint32 unk;
    float frame;
    uint32 unk;
    uint32 len;
    char name[len];
};
struct Record_action
{
    uint32 count;
    local int i = 0;
    while (i++ < count)
    {
        Action_entry entries;
    }
};

//MCls data
struct Cls_pair
{
    uint32 vertex; //I think?
    float bias; //How much the vertex is influenced by the node
};
struct Record_cls
{
    uint32 count;
    local int i = 0;
    while (i++ < count)
    {
        Cls_pair pairs;
    }
    uint32 len;
    char node[len];
    uint32 unk;
};

struct Record_bmp
{
    ubyte f0, unk, unk, f1;
    uint32 unk;
    uint32 width;
    uint32 height;
    uint32 unk;
};

struct Record_mch
{
    uint32 count0;
    uint32 count1;
    float funk0[count0];
    float funk1[4*count1];

    uint32 mlen;
    char mdl[mlen];
    uint32 unk;
    uint32 plen;
    char parent[plen];
    float param[4];
};

struct Vertex
{
    uint16 point;       //Which vertex. High bit may be set for some reason - forgot why
    uint16 flags;       //Properties for each face in the strip
    float u, v;        
    uint32 unk;         //argb8888? Mirrors the below...
    float a, r, g, b;   //I think
};
struct Point
{
    float x, y, z;
};
struct Normal
{
    float x, y, z, w;
};
struct Strip
{
    uint32 unk;
    uint32 unk;
    uint32 mlen;
    char material[mlen];
    uint32 unk;

    uint32 count;
    Vertex verts[count];

    uint32 npunk;
    Point punk[npunk];

    uint32 nnormals;
    Point normals[nnormals];

    uint32 unk;
    uint32 unk;
    uint32 unk;
};
struct Record_mdl
{
    uint32 unk;
    uint32 nstrips;
   
    local uint32 i = 0;
    for (i = 0; i < nstrips; i++) Strip strips;

    uint32 npoints;
    Point points[npoints];

    //Bounding box, I guess?
    uint32 unk;
    Point mins;
    Point maxs;
};


//Record framing
struct Record
{
    char type[4];
    uint32 size;
   
    //Records may be little endian
    if ("Litl" == endian)
    {
        LittleEndian ();
    }

    Record_info info;
    if ("MAct" == type) Record_action action;
    else if ("MCls" == type) Record_cls cls;
    else if ("MBmp" == type)
    {
        Record_bmp bmp;
        byte bmp_data[size - sizeof(info) - sizeof (bmp)];
        Printf ("BMP:%s: %ix%i\n", info.name, bmp.width, bmp.height);
        Printf ("rgb565: %i\n", 2*bmp.width*bmp.height);
        Printf ("vq: %i\n", bmp.width*bmp.height/4 + 2048);
        Printf ("size of contents: %i\n", sizeof (bmp_data));
    }
    else if ("MMdl" == type)
    {
        Record_mdl mdl;
    }
    else byte data[size - sizeof(info)];

    BigEndian ();
};

//Parse the file
local int _record_header_size = 8;
local int _header_size = 12;
local int _read = size - _header_size;
while (0 < _read)
{
    Record record;
    _read -= record.size + _record_header_size;
    Printf ("Read %i (%i)\n", record.size, _read);
}

Off to work!
 
  • Like
Reactions: Pitito

Make a donation