#include <cstring>
#include <stdio.h>
#include <stdlib.h>
#include "vgm.h"
CPlayer *CvgmPlayer::factory(Copl *newopl)
{
return new CvgmPlayer(newopl);
}
void fillGD3Tag(binistream *f, wchar_t *data)
{
uint16_t chr, cnt = 0;
do
{
chr = f->readInt(2);
data[cnt < 256 ? cnt : 255] = cnt < 256 ? chr : 0;
cnt++;
} while (chr && !f->eof());
}
bool CvgmPlayer::load(const std::string &filename, const CFileProvider &fp)
{
binistream *f = fp.open(filename);
if (!f) return false;
if (!fp.extension(filename, ".vgm") &&
!fp.extension(filename, ".vgz"))
{
fp.close(f);
return false;
}
if (fp.filesize(f) < VGM_GZIP_MIN)
{
fp.close(f);
return false;
}
char id[4];
f->readString(id, 4);
if ((uint8_t)id[0] == 0x1F &&
(uint8_t)id[1] == 0x8B)
{
fp.close(f);
return false;
}
if (fp.filesize(f) < VGM_HEADER_MIN)
{
fp.close(f);
return false;
}
if (strncmp(id, VGM_HEADER_ID, 4))
{
fp.close(f);
return false;
}
uint32_t eof = f->readInt(4);
if (fp.filesize(f) != eof + OFFSET_EOF)
{
fp.close(f);
return false;
}
version = f->readInt(4);
if (version < 0x151)
{
fp.close(f);
return false;
}
f->seek(12, binio::Add);
samples = f->readInt(4);
loop_ofs = f->readInt(4);
loop_smp = f->readInt(4);
rate = f->readInt(4);
f->seek(12, binio::Add);
int data_ofs = f->readInt(4);
if (data_ofs < OFFSET_YM3812 - OFFSET_DATA + 4)
{
fp.close(f);
return false;
}
clock = 0;
if (data_ofs >= OFFSET_YMF262 - OFFSET_DATA + 4)
{
f->seek(OFFSET_YMF262);
clock = f->readInt(4);
}
vgmOPL3 = clock != 0;
vgmDual = false;
if (!vgmOPL3)
{
f->seek(OFFSET_YM3812);
clock = f->readInt(4);
vgmDual = (clock & VGM_DUAL_BIT) > 0;
}
clock &= (VGM_DUAL_BIT - 1);
if (!clock)
{
fp.close(f);
return false;
}
loop_base = 0;
if (data_ofs >= OFFSET_LOOPBASE - OFFSET_DATA + 1)
{
f->seek(OFFSET_LOOPBASE);
loop_base = f->readInt(1);
}
loop_mod = 0;
if (data_ofs >= OFFSET_LOOPMOD - OFFSET_DATA + 1)
{
f->seek(OFFSET_LOOPMOD);
loop_mod = f->readInt(1);
}
data_sz = 0;
f->seek(OFFSET_GD3);
int gd3_ofs = f->readInt(4);
if (gd3_ofs)
{
f->seek(OFFSET_GD3 + gd3_ofs);
f->readString(id, 4);
if (!strncmp(id, GD3_HEADER_ID, 4))
{
int gd3_ver = f->readInt(4);
int gd3_size = f->readInt(4);
fillGD3Tag(f, GD3.title_en);
fillGD3Tag(f, GD3.title_jp);
fillGD3Tag(f, GD3.game_en);
fillGD3Tag(f, GD3.game_jp);
fillGD3Tag(f, GD3.system_en);
fillGD3Tag(f, GD3.system_jp);
fillGD3Tag(f, GD3.author_en);
fillGD3Tag(f, GD3.author_jp);
fillGD3Tag(f, GD3.date);
fillGD3Tag(f, GD3.ripper);
fillGD3Tag(f, GD3.notes);
}
}
else
{
f->seek(OFFSET_EOF);
gd3_ofs = f->readInt(4);
}
f->seek(OFFSET_DATA + data_ofs);
data_sz = gd3_ofs - data_ofs;
vgmData = new uint8_t[data_sz];
for (int i = 0; i < data_sz; i++)
{
vgmData[i] = f->readInt(1);
}
fp.close(f);
loop_ofs -= data_ofs + (OFFSET_DATA - OFFSET_LOOP);
rewind(0);
return true;
}
bool CvgmPlayer::update()
{
uint8_t reg, val;
wait = 0;
do
{
if (pos >= data_sz)
{
songend = true;
break;
}
uint8_t cmd = vgmData[pos++];
switch (cmd)
{
case CMD_OPL2:
case CMD_OPL3_PORT0:
reg = vgmData[pos++];
val = vgmData[pos++];
if ((!vgmOPL3 && cmd == CMD_OPL2) || (vgmOPL3 && cmd == CMD_OPL3_PORT0))
{
if (opl->getchip() != 0)
opl->setchip(0);
opl->write(reg, val);
}
break;
case CMD_OPL2_2ND:
case CMD_OPL3_PORT1:
reg = vgmData[pos++];
val = vgmData[pos++];
if ((vgmDual && cmd == CMD_OPL2_2ND) || (vgmOPL3 && cmd == CMD_OPL3_PORT1))
{
if (opl->getchip() != 1)
opl->setchip(1);
opl->write(reg, val);
}
break;
case CMD_WAIT:
wait = vgmData[pos++];
wait |= vgmData[pos++] << 8;
break;
case CMD_WAIT_735:
wait = 735;
break;
case CMD_WAIT_882:
wait = 882;
break;
case CMD_DATA_END:
pos = data_sz;
break;
default:
if (cmd >= CMD_WAIT_N && cmd <= CMD_WAIT_N + 0xF)
{
wait = (cmd & 0xF) + 1;
}
}
if (wait && wait < 40)
wait = 0; if (!songend)
songend = pos >= data_sz;
if (pos >= data_sz && loop_ofs >= 0)
pos = loop_ofs;
} while (!wait);
return !songend;
}
void CvgmPlayer::rewind(int subsong)
{
pos = 0; songend = false; wait = 0;
opl->init();
}
float CvgmPlayer::getrefresh()
{
return VGM_FREQUENCY / (wait > 0 ? wait : VGM_FREQUENCY / 1000);
}
std::string CvgmPlayer::gettype()
{
char chip[10];
memset(chip, 0, 10);
if (vgmOPL3)
strcpy(chip, "OPL3");
else if (vgmDual)
strcpy(chip, "Dual OPL2");
else
strcpy(chip, "OPL2");
char tmpstr[40];
uint8_t major = (version >> 8) & 0xFF;
uint8_t minor = version & 0xFF;
sprintf(tmpstr, "Video Game Music %x.%x (%s)", major, minor, chip);
return std::string(tmpstr);
}
std::string CvgmPlayer::gettitle()
{
char str[256];
str[0] = 0;
if (GD3.title_en[0])
{
wcstombs(str, GD3.title_en, 256);
}
else if (GD3.title_jp[0])
{
wcstombs(str, GD3.title_jp, 256);
}
return std::string(str);
}
std::string CvgmPlayer::getauthor()
{
char str[256];
str[0] = 0;
if (GD3.author_en[0])
{
wcstombs(str, GD3.author_en, 256);
}
else if (GD3.author_jp[0])
{
wcstombs(str, GD3.author_jp, 256);
}
return std::string(str);
}
std::string CvgmPlayer::getdesc()
{
char game[256]; game[0] = 0;
char system[256]; system[0] = 0;
char date[256]; date[0] = 0;
char notes[256]; notes[0] = 0;
if (GD3.game_en[0])
{
wcstombs(game, GD3.game_en, 256);
}
else if (GD3.game_jp[0])
{
wcstombs(game, GD3.game_jp, 256);
}
if (GD3.system_en[0])
{
wcstombs(system, GD3.system_en, 256);
}
else if (GD3.system_jp[0])
{
wcstombs(system, GD3.system_jp, 256);
}
if (GD3.date[0])
wcstombs(date, GD3.date, 256);
if (GD3.notes[0])
wcstombs(notes, GD3.notes, 256);
char str_sys[256]; str_sys[0] = 0;
if (system[0] && date[0])
{
sprintf(str_sys, "%s / %s", system, date);
}
else if (system[0])
{
strcpy(str_sys, system);
}
else if (date[0])
{
strcpy(str_sys, date);
}
char str_game[256]; str_game[0] = 0;
char str_desc[256]; str_desc[0] = 0;
if (game[0])
{
if (str_sys[0])
{
sprintf(str_game, "%s (%s)", game, str_sys);
}
else
{
strcpy(str_game, game);
}
}
else if (str_sys[0])
{
strcpy(str_game, str_sys);
}
if (notes[0])
{
sprintf(str_desc, "%s\r\n\r\n%s", str_game, notes);
}
else
{
strcpy(str_desc, str_game);
}
return std::string(str_desc);
}