WindThunder Script Format

While this is a script format, and while it contains dialogue,
"script" in this example refers more to scripted actions.
The extensions for this format are .SC0 or .SC1.

The scripts are generated by WindThunder's in-house event editor,
a remaining dialogue of which can be found in Dialog 152
of wt_script_dll.dll. Unfortunately, the code for interacting with
this form doesn't appear to be present in the packaged DLL.

File Format

The script structure is very complicated; currently, only enough
to extract the text and rebuild the file is known.

Some script files have an 0×1004 byte header. In all scripts with the
header, it is exactly identical. I cannot remember anymore whether
or not you actually need the header. To check if one exist, make sure
0x1007 is not 0xcdcdcd, and 0x1006 is not 0x30000000. If both are not,
then there is no header. Otherwise, the file begins at 0x1004.

From that offset, the string loop begins; the actual string data
immediately follows. All offsets are in little endian, and all strings
are padded by null bytes:

(Optional 0x1004 byte header)
String count [2 bytes, short]
(String loop)
  Tag [4 bytes, long (Unknown)]
  String offset [4 bytes, long]
  String length [4 bytes, long]
  String description [16 bytes, string (Usually 字串X)]
(End loop)
0x0000 [2 bytes, short]
(Data)
(Footer)

The data of the footer is currently known, but based on what the
script editor in wt_script_dll.dll suggests, it's event code for what
order things should happen and what events should loop. It likely
calls the strings from the above block like an array.

All script files can safely be rebuilt by recalculating the string
table and joining the original footer to the file.

Proof of Concept

A sample routine to extract all strings to separate lines is shown
below. It's advised you use a termination marker, since dialogue lines
use \n and \t:

$fd = fopen('3012104.sc0', 'rb');
fseek($fd, 0x1006, SEEK_SET);
$test2 = fread($fd, 4);
$test1 = subst($test2, 1);
if($test1==ord(0xcd).ord(0xcd).ord(0xcd) && $test2!=pack('V*', 0x30))
  fseek($fd, 0, SEEK_SET);
else
  fseek($fd, 0x1004, SEEK_SET);
$count = unpack('v*', fread($fd, 2));
$tag = array();
$offset = array();
$length = array();
$descrip = array();
for($i=0; $i< $count; $i++) {
  $tag[$i] = unpack('N*', fread($fd, 4));
  $offset[$i] = unpack('V*', fread($fd, 4));
  $length[$i] = unpack('V*', fread($fd, 4));
  $descrip[$i] = rtrim(fread($fd, 16));
}
$null = fread($fd, 2);
for($i=0; $i<$count; $i++) {
  echo $descrip[$1].' ['.$tag[$i].']n';
  // Since strings run end-to-end we do not need to
  // fseek() to the next offset
  echo rtrim(fread($fd, $length[$i])).'nn';
}
fclose($fd);


Copyright (c) 2005 Derrick Sobodash. Some Rights Reserved.

This work is licensed under a CC Attribution-ShareAlike 4.0 International
License (http://creativecommons.org/licenses/by-sa/4.0/).