Hey @Sizzin,
You may want to have a look at roxfan's cvm tool (
mirror) to convert back and forth between .CVM and .ISO
cvm_tool.exe
ROFS tool v0.02 by roxfan (c) 2010.
Usage: cvm_tool [options] <command> <file1>...
available commands:
info [-p <password>] <file.cvm> Show information about a ROFS volume
split [-p <password>] <file.cvm> <file.iso> [<file.hdr>] Extract ISO file from a ROFS volume
mkcvm [-p <password>] <file.cvm> <file.iso> <file.hdr> Make a ROFS volume from an ISO file and header file
Do not mind the password option, since we are not dealing with encrypted (obfuscated) volumes.
Here is the tool output:
cvm_tool split H:\ST.CVM ST.CVM.iso ST.CVM.hdr
Input file: H:\ST.CVM
00000000: chunk 'CVMH', length 0x000007F4 (0x00000800)
CVMH chunk:
file size: 0x5C375000
date: 2004-12-27 19:45:12, GMT offset: 540 minutes
verinfo1: 1.1.0.0
flags_30: 0x00000000
FS id: ROFS
maker id: 'ROFSBLD Ver.1.52 2003-06-09'
verinfo2: 1.31.0.0
flag_7C: 3
flag_7D: 0
zone table (1 entries): [ 1 ]
zone TOC sector: 1 (index 0)
ISO start sector: 3
00000800: chunk 'ZONE', length 0x5C3747F4 (0x5C374800)
ZONE chunk:
zone0C: 3
zone10: 0
zone11: 0
zone12: 4
zone13: 16
zone14: 0
sector len 1: 2048
sector len 2: 2048
dataloc1: sector 2, len 0x00000800
dataloc ISO: sector 3, len 0x5C373800
Output file: ST.CVM.iso
Header file: ST.CVM.hdr
cvm_tool mkcvm ST-gen.CVM ST.CVM.iso ST.CVM.hdr
Input file: ST.CVM.iso
Header file: ST.CVM.hdr
Writing unencrypted volume
Output file: ST-gen.CVM
Patching ISO zone length to 0x5C373800
Patching file size to 0x5C375000
Setting encryption flag to 0
sha1sum H:\ST.CVM ST-gen.CVM
5624073167a074bbcd596b47f88a5a99eb4e2021 H:\ST.CVM
5624073167a074bbcd596b47f88a5a99eb4e2021 ST-gen.CVM
The CVM (ISO9660) for the USA version has the following structure:
https://snipt.net/raw/0631ca7d35f144c9487a17f5f74d226f/?nice (
listing mirror)

Here are a few observations on the matter:
- There are noticeably fewer .AHX + .ADX files than actually spoken in-game; where are they buried? [in both USA and JPN editions]. I also saw a few structures resembling ADPCM-like sounds
- TXR looks like a bitmap format that has several flavors, 5bpp, 8bpp indexed RGBa, flat 32bpp RGBa, and there are occasionally several images in a single TXR.
- RAX is an archive format containing several of the following filetypes: txr, ban, trs, efp, etc. It employs a clearly marked LZSS compression variant which I have not completely reversed. The window is 0x2000 bytes long, i.e. 13-bit long backreference index. It is kinda weird because the command word is also used as a source, meaning you read 5 bits from the control word and 8 bits from the curret byte to compute the backreference offset.
If you want to have a look, go to sub_14A340 in SLUS-21063, sub_14A310 in SLPM-65773, sub_221FF0 in SLPM-66671
- some files entries in PACK***.RAX match other files in the CVM hierarchy, e.g. AHX/*
- Shining Wind is the other title in the series using the same engine.
AppendicesNON-WORKING TXR converter:
# Extractor for SEGA/Nextech *.TXR (script 0.1.0)
# = used in shining tears & shining wind
# script for QuickBMS http://quickbms.aluigi.org
endian little
get myext EXTENSION
if myext != "TXR"
print "Please run this script on a .TXR file"
cleanexit
endif
get DAT_SIZE asize
# A .txr file typically contains a single bitmap, but there are some edge cases
math i = 0
for OFFSET = 0 < DAT_SIZE
idstring "TXR\x00"
math i += 1
get query->bm_type long
get query->bm_widt short
get query->bm_heig short
get RESERVED_ long # always zero?
callfunction query2tga
cleanexit
savepos OFFSET
next
###############################################
# struct TGAHeader
# {
# uint8 idLength, // Length of optional identification sequence.
# paletteType, // Is a palette present? (1=yes)
# imageType; // Image data type (0=none, 1=indexed, 2=rgb,
# // 3=grey, +8=rle packed).
# uint16 firstPaletteEntry, // First palette index, if present.
# numPaletteEntries; // Number of palette entries, if present.
# uint8 paletteBits; // Number of bits per palette entry.
# uint16 x, // Horiz. pixel coord. of lower left of image.
# y, // Vert. pixel coord. of lower left of image.
# width, // Image width in pixels.
# height; // Image height in pixels.
# uint8 depth, // Image color depth (bits per pixel).
# descriptor; // Image attribute flags.
# };
startfunction query2tga
savepos DATOFFSET
if query->bm_type == 0
# 32bpp RGBa
math query->bm_bpp = 32
math query->bm_pal = 0
set MEMORY_FILE binary "\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\xAA\xAA\xBB\xBB\x20\x28"
elif query->bm_type == 0x13
# indexed 8bpp RGBa with 256-entry RGBa palette (0x400 bytes)
math query->bm_bpp = 8
math query->bm_pal = 0x400
set MEMORY_FILE binary "\x00\x01\x01\x00\x00\x00\x01\x20\x00\x00\x00\x00\xAA\xAA\xBB\xBB\x08\x28"
# elif query->bm_type == 0x14
# 5bpp
else
print "unknown bitmap type in TXR file %query->bm_type% starting @ offset %OFFSET%"
cleanexit
endif
goto 0x0C MEMORY_FILE # x,y
put query->bm_widt short MEMORY_FILE
put query->bm_heig short MEMORY_FILE
get TGAHDSIZE asize MEMORY_FILE
math PXBITMAP = query->bm_widt
math PXBITMAP *= query->bm_heig
math BYT = query->bm_bpp
math BYT u/= 8
math PXBITMAP *= BYT
get NAME basename
string NAME p= "%s_%03d.tga" NAME i
append
log MEMORY_FILE2 0 TGAHDSIZE MEMORY_FILE
if query->bm_pal > 0
math PALOFFSET = DATOFFSET
math PALOFFSET += PXBITMAP # TXR puts image data first, then colormap
log MEMORY_FILE2 PALOFFSET query->bm_pal
endif
log MEMORY_FILE2 DATOFFSET PXBITMAP
append
get TGATOTSIZE asize MEMORY_FILE2
log NAME 0 TGATOTSIZE MEMORY_FILE2
endfunction
Blue-ish Elwyn

Some LZSS sample (extracted out of a .RAX file):


001.BAN.slz = 0x3AE4 bytes
001.BAN = 0xFA90 bytes
EDIT1The initial scene where Elwyn finds a boy washed ashore = event/TOWN02.bss from PACKTOWN02.RAX
A .bss file is a compiled script made of 4 sections:
-
TNVE "EVeNT" seems to be a bunch of offsets
-
CNUF "FUNCtions", a bunch of ASCII symbols
-
DCMV "VM CoDe"
-
TDMV "VM DaTa" = \x00 separated list of strings
- DEMV (end of file marker)
You shall find this file in-memory when snapping a savestate.
The DCMV and TDMV are of particular interest to us:
- DCMV contains opcodes referring to TDMV strings by offset, after removing the 12-byte long header
- TDMV contains game strings (ASCII strings in the case of the USA version)
e.g.:

- offset 678B: Looks like he's hurt pretty bad...
- offset 67AF: Boy (speaker name)
- offset 67B3: U... Ugh...
- offset 67C0: I better get him to (...)
contrast with JPN version:

- offset 59E3: *S4*ひどい傷……
- offset 59F9: 少年 (speaker name)
- offset 59FE: *S5*う…… (...)
- offset 5A28: *S4*一刻も早く^ (...)
Undoubtedly some of those opcodes in the JPN version correspond to voiceovers.
If you compare USA vs. JPN, there are outstanding instructions on the right column:
USA
27: FFFFFFFF (-1) 29: 08000000 (
2A: 8B670000 (ofs 678B) msgbox 29: 13000000 (19) 29: 09000000 (9) 02: 01000000 (1) 02: 02000000 (2) 02: 18000000 (24) 27: FDFFFFFF (-3) 29: 0A000000 (10) 02: 02000000 (2) 03: AF670000 (ofs 67AF) msgspk 27: FEFFFFFF (-2) 29: 0F000000 (15) 02: FFFFFFFF (-1) 27: FFFFFFFF (-1) 29: 08000000 ( 2A: B3670000 (ofs 67B3) msgbox 29: 13000000 (19) 29: 09000000 (9) 02: 01000000 (1) 02: 02000000 (2) 02: 19000000 (31) 27: FDFFFFFF (-3) 29: 0A000000 (10) 02: 01000000 (1) 27: FFFFFFFF (-1) 29: 08000000 ( 2A: C0670000 (ofs 67C0) msgbox
| JPN
27: FFFFFFFF (-1) 29: 08000000 ( 02: 2C000000 (44) 02: 03000000 (3) 02: 00000000 (0) 27: FDFFFFFF (-3) 29: 39000000 (57) 2A: E3590000 (ofs 59E3) msgbox 29: 10000000 (16) 29: 09000000 (9)
02: 02000000 (2) 03: F9590000 (ofs 59F9) msgspk 27: FEFFFFFF (-2) 29: 0C000000 (12) 02: FFFFFFFF (-1) 27: FFFFFFFF (-1) 29: 08000000 ( 02: 2D000000 (45) 02: 03000000 (3) 02: 00000000 (0) 27: FDFFFFFF (-3) 29: 39000000 (57) 2A: FE590000 (ofs 59FE) msgbox 29: 10000000 (16) 29: 09000000 (9) 02: 01000000 (1) 02: 02000000 (2) 02: 19000000 (31) 27: FDFFFFFF (-3) 29: 0A000000 (10) 02: 01000000 (1) 27: FFFFFFFF (-1) 29: 08000000 ( 02: 2E000000 (46) 02: 03000000 (3) 02: 00000000 (0) 27: FDFFFFFF (-3) 29: 39000000 (57) 2A: 285A0000 (ofs 5A28) msgbox
|
EDIT2:OK, I have confirmed by loading a save state, that the incrementing value refers to a voice file.
The 27 opcode seems to indicate how many values need to be "unstacked" by the following function call (-1, -2, -3, etc.)
The 29 opcode, subcode 39 seems to be the code for playing a voice file, taking 3 parameters.
Now what's left:
1/ somehow recompile a valid bss image, fixing pointers
2/ repack a RAX archive. Thoroughly figuring the LZSS compression is a plus, but not necessary, as a dummy compression always reading literals (0xFF control word) should be enough.
2a/ which copy takes precedence in the CVM? The one in the ISO9660 or in the RAX?
3/ recreate ST.CVM
4/ repack complete ISO, test
AppendixRAX archive dumper:
# Dumper for SEGA/Nextech *.RAX (script 0.1.0)
# = used in shining tears & shining wind
# script for QuickBMS http://quickbms.aluigi.org
# This version only dumps LZSS segments as-is
endian little
get myname FILENAME
get myext EXTENSION
if myext != "RAX"
print "Please run this script on a .RAX file"
cleanexit
endif
get DAT_SIZE asize
idstring "RAX\x00"
get rax->RESERVED1_ long
get rax->RESERVED2_ long
get rax->entcount long
math i = 0
for OFFSET = 0x10 < DAT_SIZE
getdstring query->sig 4
get query->blksize long
get query->fnamelen long
get query->usize long
math i += 1
if query->fnamelen > 0x100
print "(%myname% at OFFSET=%OFFSET%) entry filename overly long, maybe bad value [query->fnamelen=%query->fnamelen%]"
cleanexit
endif
if query->fnamelen > 0
getdstring query->fname query->fnamelen
else
string query->fname p= "--%03d--.bin" i
endif
set query->zsize query->blksize
math query->zsize -= 0x10
math query->zsize -= query->fnamelen
savepos tmpOFFSET
get magic long
goto tmpOFFSET
string dumpext = ".slz"
if magic u!= 0x53535A4C # LZSS
string dumpext = ".raw"
endif
set fname query->fname
string fname R= "/" "!"
string fname R= "\\" "!"
get NAME basename
string NAME p= "%s# [%s]%s" NAME fname dumpext
if magic u== 0x53535A4C # LZSS
log NAME tmpOFFSET query->zsize
else
log NAME tmpOFFSET query->usize
endif
goto query->zsize 0 SEEK_CUR
savepos OFFSET
next
if i != rax->entcount
print "(%myname%) Expected %rax->entcount% entries, found %i%"
endif
_ext_rax.cmd helper batch (copy cvm contents into work/ and duplicate its tree structure in extr/) :
@echo off & setlocal ENABLEEXTENSIONS
goto :main
:do_one
SET ZPATH=%~dp1
SET ZPATH=%ZPATH:work=extr%
quickbms -o sega-nextech-shining_wind-rax-010.bms %1 %ZPATH%
goto :eof
:main
for /R %%i in (*.rax) DO call :do_one %%i
:end
pause
EDIT3:I've been leading a few interesting experiments on the NTSC-J version.
(...put in a
separate entry b/c this post exceeds the board limit...)
main conclusions are:
- can mod RAX files
- CVMFS takes over if entry missing from RAX