Last active
May 22, 2019 14:35
-
-
Save sudotac/53fc0679eae0a775b8f01738f5311369 to your computer and use it in GitHub Desktop.
thbgm.datを読んでwaveファイルを出力するPerlスクリプト。東方風神録(th10)、東方天空璋(th16)でのみ動作確認済み。東方妖々夢以降の他の作品でも動くかもしれません。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
use strict; | |
use warnings; | |
use Getopt::Long; | |
my $num_loop = 2; # default | |
my $artist = '上海アリス幻樂団'; | |
my ($bgmfile, $outdir, $titlefile); | |
GetOptions( | |
'thbgm-dat|d=s' => \$bgmfile, | |
'output-dir|o=s' => \$outdir, | |
'title-file|t=s' => \$titlefile, | |
'loop|l=i' => \$num_loop, | |
'help|h' => sub {print_help()} | |
) or print_help(); | |
if (!defined($bgmfile)) { | |
print STDERR "--thbgm-dat,or -d is mandatory\n"; | |
print_help(); | |
} elsif (!defined($outdir)) { | |
print STDERR "--output-dir,or -o is mandatory\n"; | |
print_help(); | |
} elsif (!defined($titlefile)) { | |
print STDERR "--title-file,or -t is mandatory\n"; | |
print_help(); | |
} | |
open my $bgmfh, '<', $bgmfile or die "'$bgmfile': $!"; | |
my $bgmfilesize = -s $bgmfile or die "$bgmfile is empty."; | |
binmode($bgmfh); | |
my $version; | |
seek($bgmfh, 8, 0); | |
read($bgmfh, $version, 2); | |
$version = get_version($version); | |
if ($version == int($version)) { | |
printf("version may be th%02d\n", $version); | |
} else { | |
printf("version may be th%04.1f\n", $version); | |
} | |
#seek($bgmfh, 6, 1); | |
if (! -e $outdir) { | |
mkdir $outdir or die "'$outdir': $!"; | |
print "created directory '$outdir'\n"; | |
} elsif (! -d $outdir) { | |
die "$outdir is not a directory."; | |
} | |
open my $fhtitle, '<', $titlefile or die "'$titlefile': $!"; | |
binmode($fhtitle); | |
my $titlecnt = 1; | |
my $gametitle; | |
while (my $line = <$fhtitle>) { | |
chomp($line); | |
if (substr($line, 0, 1) eq '#') { | |
next; | |
} elsif (substr($line, 0, 1) eq '@') { | |
my $gamepath; | |
($gamepath, $gametitle) = split(/,/, $line); | |
next; | |
} | |
my @cols = split(/,/, $line); | |
my ($offset, $introlen, $looplen, $title); | |
$offset = hex($cols[0]); | |
$introlen = hex($cols[1]); | |
$looplen = hex($cols[2]); | |
$title = $cols[3]; | |
my ($introwave, $loopwave); | |
seek($bgmfh, $offset, 0); | |
read($bgmfh, $introwave, $introlen); | |
read($bgmfh, $loopwave, $looplen); | |
my $wavesize = $introlen + $looplen*$num_loop; | |
my $wavedata = 'data'.pack('V1', $wavesize).$introwave . $loopwave x $num_loop; | |
my $datasize = 8 + $wavesize; | |
my ($waveformat, $formatsize) = get_waveformat(); | |
my ($wavelist, $listsize) = get_wavelist($gametitle, $title, $artist); | |
my $waveheader = get_waveheader($formatsize + $datasize + $listsize + 4); | |
my $outfilename = sprintf("$outdir/%02d. $title.wav", $titlecnt); | |
open my $ofh, '>', $outfilename or die "'$outfilename': $!"; | |
binmode($ofh); | |
print $ofh $waveheader; | |
print $ofh $waveformat; | |
print $ofh $wavedata; | |
print $ofh $wavelist; | |
close($ofh); | |
$titlecnt++; | |
} | |
close($bgmfh); | |
sub get_waveheader { | |
my ($size) = @_; | |
return 'RIFF'.pack('V1', $size).'WAVE'; | |
} | |
sub get_waveformat { | |
my $waveformat = 'fmt '; | |
$waveformat .= pack('V1', 16); # chunk size | |
$waveformat .= pack('v1', 1); # format id | |
$waveformat .= pack('v1', 2); # # of channels | |
$waveformat .= pack('V1', 44100); # sampling rate | |
$waveformat .= pack('V1', 44100*2*2); # Byte/sec | |
$waveformat .= pack('v1', 2*2); # block size | |
$waveformat .= pack('v1', 16); # bitrate | |
return ($waveformat, 20); | |
} | |
sub get_wavelist { | |
my ($gametitle, $title, $artist) = @_; | |
my $data = ''; | |
my $chunksize = 0; | |
for ((['IPRD',$gametitle], ['INAM',$title], ['IART',$artist])) { | |
my ($t1, $t2) = @{$_}; | |
my $len = length($t2); | |
if ($len % 2 != 0) { | |
$t2 .= pack('c1', 0); # zero padding for alignment | |
$len++; | |
} | |
$chunksize += $len + 8; | |
$data .= $t1 . pack('V1', $len) . $t2; | |
} | |
$chunksize += 4; # INFO | |
my $wavelist = 'LIST'.pack('V1', $chunksize).'INFO'; | |
$wavelist .= $data; | |
return ($wavelist, 8 + $chunksize); | |
} | |
sub get_version { | |
my ($bin) = @_; | |
my @bytes = unpack('C2', $bin); | |
@bytes = map{ $_%16 + int($_/16)*10 } @bytes; | |
my $version = $bytes[1] + $bytes[0]/100; | |
return $version; | |
} | |
sub print_help{ | |
print << 'EOS'; | |
thbgm.pl - Extract wave files from thbgm.dat | |
usage: thbgm.pl <OPTIONS> | |
options: | |
-d, --thbgm-dat=<FILE> set path to thbgm.dat. | |
-o, --output-dir=<DIR> set path to output directory for wave files. If DIR does not exist, create DIR automatically. | |
-t, --title-file=<FILE> set path to title file. | |
-l, --loop=<N> loop N times for each piece of music. If N not set, set N to 2 automatically. | |
-h, --help print this message and exit. | |
examples: | |
./thbgm.pl -d ~/.wine/drive_c/Program\ Files\ \(x86\)/上海アリス幻樂団/東方風神録/thbgm.dat -o th10wave -t titles_th10.txt | |
./thbgm.pl -d ~/.local/share/Steam/steamapps/common/th16/thbgm.dat -o th16wave -t titles_th16.txt | |
EOS | |
exit; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment