Skip to content

Instantly share code, notes, and snippets.

@sudotac
Last active May 22, 2019 14:35
Show Gist options
  • Save sudotac/53fc0679eae0a775b8f01738f5311369 to your computer and use it in GitHub Desktop.
Save sudotac/53fc0679eae0a775b8f01738f5311369 to your computer and use it in GitHub Desktop.
thbgm.datを読んでwaveファイルを出力するPerlスクリプト。東方風神録(th10)、東方天空璋(th16)でのみ動作確認済み。東方妖々夢以降の他の作品でも動くかもしれません。
#!/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