Last active
January 1, 2022 12:08
-
-
Save h1romas4/6e4ccaa738e50e73f394209ed863e1e7 to your computer and use it in GitHub Desktop.
MAME vgmwrite patch (YM2151 only) | to start mame with the "-vgmwrite 1" option. (ex. mame -vgmwrite 1)
This file contains hidden or 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
| From a6d60e1b9620975707728d80490d0ca9e4215cf1 Mon Sep 17 00:00:00 2001 | |
| From: h1romas4 <h1romas4@gmail.com> | |
| Date: Sat, 1 Jan 2022 21:06:16 +0900 | |
| Subject: [PATCH] vgmwrite patched | |
| --- | |
| makefile | 4 +- | |
| scripts/src/lib.lua | 2 + | |
| src/devices/sound/ymfm_mame.h | 12 + | |
| src/devices/sound/ymopm.cpp | 20 + | |
| src/emu/emuopts.cpp | 1 + | |
| src/emu/emuopts.h | 2 + | |
| src/emu/machine.cpp | 7 + | |
| src/lib/util/vgmwrite.cpp | 2605 +++++++++++++++++++++++++++++++++ | |
| src/lib/util/vgmwrite.h | 66 + | |
| 9 files changed, 2717 insertions(+), 2 deletions(-) | |
| create mode 100644 src/lib/util/vgmwrite.cpp | |
| create mode 100644 src/lib/util/vgmwrite.h | |
| diff --git a/makefile b/makefile | |
| index 5f091b9d10c..311594583e9 100644 | |
| --- a/makefile | |
| +++ b/makefile | |
| @@ -1553,7 +1553,7 @@ $(GENDIR)/version.cpp: makefile $(GENDIR)/git_desc | $(GEN_FOLDERS) | |
| @echo 'extern const char build_version[];' >> $@ | |
| @echo 'const char bare_build_version[] = BARE_BUILD_VERSION;' >> $@ | |
| @echo 'const char bare_vcs_revision[] = BARE_VCS_REVISION;' >> $@ | |
| - @echo 'const char build_version[] = BARE_BUILD_VERSION " (" BARE_VCS_REVISION ")";' >> $@ | |
| + @echo 'const char build_version[] = BARE_BUILD_VERSION "VGM mod (" BARE_VCS_REVISION ")";' >> $@ | |
| else | |
| $(GENDIR)/version.cpp: makefile $(GENDIR)/git_desc | $(GEN_FOLDERS) | |
| @echo #define BARE_BUILD_VERSION "0.239" > $@ | |
| @@ -1563,7 +1563,7 @@ $(GENDIR)/version.cpp: makefile $(GENDIR)/git_desc | $(GEN_FOLDERS) | |
| @echo extern const char build_version[]; >> $@ | |
| @echo const char bare_build_version[] = BARE_BUILD_VERSION; >> $@ | |
| @echo const char bare_vcs_revision[] = BARE_VCS_REVISION; >> $@ | |
| - @echo const char build_version[] = BARE_BUILD_VERSION " (" BARE_VCS_REVISION ")"; >> $@ | |
| + @echo const char build_version[] = BARE_BUILD_VERSION "VGM mod (" BARE_VCS_REVISION ")"; >> $@ | |
| endif | |
| diff --git a/scripts/src/lib.lua b/scripts/src/lib.lua | |
| index 1cb1ce50f60..aed9eff604f 100644 | |
| --- a/scripts/src/lib.lua | |
| +++ b/scripts/src/lib.lua | |
| @@ -130,6 +130,8 @@ end | |
| MAME_DIR .. "src/lib/util/vbiparse.h", | |
| MAME_DIR .. "src/lib/util/vecstream.cpp", | |
| MAME_DIR .. "src/lib/util/vecstream.h", | |
| + MAME_DIR .. "src/lib/util/vgmwrite.cpp", | |
| + MAME_DIR .. "src/lib/util/vgmwrite.h", | |
| MAME_DIR .. "src/lib/util/wavwrite.cpp", | |
| MAME_DIR .. "src/lib/util/wavwrite.h", | |
| MAME_DIR .. "src/lib/util/xmlfile.cpp", | |
| diff --git a/src/devices/sound/ymfm_mame.h b/src/devices/sound/ymfm_mame.h | |
| index d55be4f35f2..f17572743e8 100644 | |
| --- a/src/devices/sound/ymfm_mame.h | |
| +++ b/src/devices/sound/ymfm_mame.h | |
| @@ -8,7 +8,12 @@ | |
| #include "ymfm/src/ymfm.h" | |
| #include "ymfm/src/ymfm_ssg.h" | |
| +#include "ymfm/src/ymfm_misc.h" | |
| +#include "ymfm/src/ymfm_opl.h" | |
| +#include "ymfm/src/ymfm_opm.h" | |
| +#include "ymfm/src/ymfm_opn.h" | |
| +#include "vgmwrite.h" | |
| // set this to control the output sample rate for SSG-based chips | |
| #define SSG_FIDELITY (ymfm::OPN_FIDELITY_MED) | |
| @@ -162,6 +167,8 @@ protected: | |
| devcb_write_line m_update_irq; // IRQ update callback | |
| devcb_read8 m_io_read[2]; // up to 2 input port handlers | |
| devcb_write8 m_io_write[2]; // up to 2 output port handlers | |
| + uint16_t vgm_idx; // vgmwrite hack | |
| + uint16_t vgm_offset; // vgmwrite hack | |
| }; | |
| @@ -207,6 +214,11 @@ protected: | |
| ymfm::ymfm_saved_state state(m_save_blob, true); | |
| m_chip.save_restore(state); | |
| + // vgmwrite hack | |
| + if (std::is_same<ChipClass, ymfm::ym2151>::value) { | |
| + vgm_idx = vgm_open(VGMC_YM2151, device_t::clock()); | |
| + } | |
| + | |
| // now register the blob for save, on the assumption the size won't change | |
| save_item(NAME(m_save_blob)); | |
| } | |
| diff --git a/src/devices/sound/ymopm.cpp b/src/devices/sound/ymopm.cpp | |
| index a75da72b449..df3fff41e30 100644 | |
| --- a/src/devices/sound/ymopm.cpp | |
| +++ b/src/devices/sound/ymopm.cpp | |
| @@ -30,6 +30,19 @@ void ym2151_device::write(offs_t offset, u8 data) | |
| { | |
| if (m_reset_state == 0) | |
| return; | |
| + | |
| + // vgmwrite hack | |
| + switch (offset & 1) | |
| + { | |
| + case 0: // address port | |
| + vgm_offset = data; | |
| + break; | |
| + | |
| + case 1: // data port | |
| + vgm_write(vgm_idx, 0x00, vgm_offset, data); | |
| + break; | |
| + } | |
| + | |
| parent::write(offset, data); | |
| } | |
| @@ -37,6 +50,10 @@ void ym2151_device::address_w(u8 data) | |
| { | |
| if (m_reset_state == 0) | |
| return; | |
| + | |
| + // vgmwrite hack | |
| + vgm_offset = data; | |
| + | |
| parent::address_w(data); | |
| } | |
| @@ -44,6 +61,9 @@ void ym2151_device::data_w(u8 data) | |
| { | |
| if (m_reset_state == 0) | |
| return; | |
| + // vgmwrite hack | |
| + vgm_write(vgm_idx, 0x00, vgm_offset, data); | |
| + | |
| parent::data_w(data); | |
| } | |
| diff --git a/src/emu/emuopts.cpp b/src/emu/emuopts.cpp | |
| index 8959c00da03..a515bb2db65 100644 | |
| --- a/src/emu/emuopts.cpp | |
| +++ b/src/emu/emuopts.cpp | |
| @@ -74,6 +74,7 @@ const options_entry emu_options::s_option_entries[] = | |
| { OPTION_MNGWRITE, nullptr, OPTION_STRING, "optional filename to write a MNG movie of the current session" }, | |
| { OPTION_AVIWRITE, nullptr, OPTION_STRING, "optional filename to write an AVI movie of the current session" }, | |
| { OPTION_WAVWRITE, nullptr, OPTION_STRING, "optional filename to write a WAV file of the current session" }, | |
| + { OPTION_VGMWRITE, "0", OPTION_INTEGER, "enable to write a VGM of the current session (name is based on romname)" }, | |
| { OPTION_SNAPNAME, "%g/%i", OPTION_STRING, "override of the default snapshot/movie naming; %g == gamename, %i == index" }, | |
| { OPTION_SNAPSIZE, "auto", OPTION_STRING, "specify snapshot/movie resolution (<width>x<height>) or 'auto' to use minimal size " }, | |
| { OPTION_SNAPVIEW, "auto", OPTION_STRING, "snapshot/movie view - 'auto' for default, or 'native' for per-screen pixel-aspect views" }, | |
| diff --git a/src/emu/emuopts.h b/src/emu/emuopts.h | |
| index 296e2e46590..b56cd08beef 100644 | |
| --- a/src/emu/emuopts.h | |
| +++ b/src/emu/emuopts.h | |
| @@ -60,6 +60,7 @@ | |
| #define OPTION_MNGWRITE "mngwrite" | |
| #define OPTION_AVIWRITE "aviwrite" | |
| #define OPTION_WAVWRITE "wavwrite" | |
| +#define OPTION_VGMWRITE "vgmwrite" | |
| #define OPTION_SNAPNAME "snapname" | |
| #define OPTION_SNAPSIZE "snapsize" | |
| #define OPTION_SNAPVIEW "snapview" | |
| @@ -345,6 +346,7 @@ public: | |
| const char *mng_write() const { return value(OPTION_MNGWRITE); } | |
| const char *avi_write() const { return value(OPTION_AVIWRITE); } | |
| const char *wav_write() const { return value(OPTION_WAVWRITE); } | |
| + const int vgm_write() const { return int_value(OPTION_VGMWRITE); } | |
| const char *snap_name() const { return value(OPTION_SNAPNAME); } | |
| const char *snap_size() const { return value(OPTION_SNAPSIZE); } | |
| const char *snap_view() const { return value(OPTION_SNAPVIEW); } | |
| diff --git a/src/emu/machine.cpp b/src/emu/machine.cpp | |
| index b70ffb69931..8136d3b786b 100644 | |
| --- a/src/emu/machine.cpp | |
| +++ b/src/emu/machine.cpp | |
| @@ -29,6 +29,7 @@ | |
| #include "corestr.h" | |
| #include "unzip.h" | |
| +#include "vgmwrite.h" | |
| #include <rapidjson/writer.h> | |
| #include <rapidjson/stringbuffer.h> | |
| @@ -200,6 +201,9 @@ void running_machine::start() | |
| for (device_t &device : device_enumerator(root_device())) | |
| device.resolve_post_map(); | |
| + // call the Initialisation for VGM logging (MUST be called before driver init) | |
| + vgm_start(*this); | |
| + | |
| // register callbacks for the devices, then start them | |
| add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&running_machine::reset_all_devices, this)); | |
| add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&running_machine::stop_all_devices, this)); | |
| @@ -1040,6 +1044,9 @@ void running_machine::stop_all_devices() | |
| // iterate over devices and stop them | |
| for (device_t &device : device_enumerator(root_device())) | |
| device.stop(); | |
| + | |
| + // stop VGM logging | |
| + vgm_stop(); | |
| } | |
| diff --git a/src/lib/util/vgmwrite.cpp b/src/lib/util/vgmwrite.cpp | |
| new file mode 100644 | |
| index 00000000000..8aef477ea77 | |
| --- /dev/null | |
| +++ b/src/lib/util/vgmwrite.cpp | |
| @@ -0,0 +1,2605 @@ | |
| +/* | |
| + vgmwrite.c | |
| + | |
| + VGM output module | |
| + | |
| + Written by Valley Bell | |
| + | |
| +*/ | |
| +#pragma GCC diagnostic ignored "-Wunused-function" | |
| + | |
| +#include "../../emu/emu.h" | |
| +#include "../../emu/emuopts.h" | |
| +#include "osdcomm.h" | |
| +#include <wchar.h> | |
| +#include "vgmwrite.h" | |
| + | |
| +#define DYNAMIC_HEADER_SIZE | |
| + | |
| +//#define print_info osd_printf_info | |
| +//#define print_warn osd_printf_warning | |
| +//#define print_err osd_printf_error | |
| +#define print_info hMachine->logerror | |
| +#define print_warn hMachine->logerror | |
| +#define print_err hMachine->logerror | |
| + | |
| +typedef uint16_t gd3char_t; // UTF-16 character, for VGM GD3 tag | |
| + | |
| +static uint8_t LOG_VGM_FILE = 0x00; | |
| + | |
| +typedef struct _vgm_file_header VGM_HEADER; | |
| +struct _vgm_file_header | |
| +{ | |
| + uint32_t fccVGM; | |
| + uint32_t lngEOFOffset; | |
| + uint32_t lngVersion; | |
| + uint32_t lngHzPSG; | |
| + uint32_t lngHz2413; | |
| + uint32_t lngGD3Offset; | |
| + uint32_t lngTotalSamples; | |
| + uint32_t lngLoopOffset; | |
| + uint32_t lngLoopSamples; | |
| + uint32_t lngRate; | |
| + uint16_t shtPSG_Feedback; | |
| + uint8_t bytPSG_SRWidth; | |
| + uint8_t bytPSG_Flags; | |
| + uint32_t lngHz2612; | |
| + uint32_t lngHz2151; | |
| + uint32_t lngDataOffset; | |
| + uint32_t lngHzSPCM; | |
| + uint32_t lngSPCMIntf; | |
| + | |
| + uint32_t lngHzRF5C68; | |
| + uint32_t lngHz2203; | |
| + uint32_t lngHz2608; | |
| + uint32_t lngHz2610; | |
| + uint32_t lngHz3812; | |
| + uint32_t lngHz3526; | |
| + uint32_t lngHz8950; | |
| + uint32_t lngHz262; | |
| + uint32_t lngHz278B; | |
| + uint32_t lngHz271; | |
| + uint32_t lngHz280B; | |
| + uint32_t lngHzRF5C164; | |
| + uint32_t lngHzPWM; | |
| + uint32_t lngHzAY8910; | |
| + uint8_t lngAYType; | |
| + uint8_t lngAYFlags; | |
| + uint8_t lngAYFlagsYM2203; | |
| + uint8_t lngAYFlagsYM2608; | |
| + uint8_t bytModifiers[0x04]; | |
| + | |
| + uint32_t lngHzGBDMG; // part of the LR35902 (GB Main CPU) | |
| + uint32_t lngHzNESAPU; // part of the N2A03 (NES Main CPU) | |
| + uint32_t lngHzMultiPCM; | |
| + uint32_t lngHzUPD7759; | |
| + uint32_t lngHzOKIM6258; | |
| + uint8_t bytOKI6258Flags; | |
| + uint8_t bytK054539Flags; | |
| + uint8_t bytC140Type; | |
| + uint8_t bytReservedFlags; | |
| + uint32_t lngHzOKIM6295; | |
| + uint32_t lngHzK051649; | |
| + uint32_t lngHzK054539; | |
| + uint32_t lngHzHuC6280; | |
| + uint32_t lngHzC140; | |
| + uint32_t lngHzK053260; | |
| + uint32_t lngHzPokey; | |
| + uint32_t lngHzQSound; | |
| + uint32_t lngHzSCSP; | |
| + uint32_t lngExtraOfs; | |
| + | |
| + uint32_t lngHzWSwan; | |
| + uint32_t lngHzVSU; | |
| + uint32_t lngHzSAA1099; | |
| + uint32_t lngHzES5503; | |
| + uint32_t lngHzES5506; | |
| + uint8_t bytES5503Chns; | |
| + uint8_t bytES5506Chns; | |
| + uint8_t bytC352ClkDiv; | |
| + uint8_t bytESReserved; | |
| + uint32_t lngHzX1_010; | |
| + uint32_t lngHzC352; | |
| + | |
| + uint32_t lngHzGA20; | |
| + uint8_t bytReserved[0x1C]; | |
| +}; // -> 0x100 Bytes | |
| +typedef struct _vgm_gd3_tag GD3_TAG; | |
| +struct _vgm_gd3_tag | |
| +{ | |
| + uint32_t fccGD3; | |
| + uint32_t lngVersion; | |
| + uint32_t lngTagLength; | |
| + gd3char_t strTrackNameE[0x70]; | |
| + gd3char_t strTrackNameJ[0x10]; // Japanese Names are not used | |
| + gd3char_t strGameNameE[0x70]; | |
| + gd3char_t strGameNameJ[0x10]; | |
| + gd3char_t strSystemNameE[0x30]; | |
| + gd3char_t strSystemNameJ[0x10]; | |
| + gd3char_t strAuthorNameE[0x30]; | |
| + gd3char_t strAuthorNameJ[0x10]; | |
| + gd3char_t strReleaseDate[0x10]; | |
| + gd3char_t strCreator[0x20]; | |
| + gd3char_t strNotes[0x50]; | |
| +}; // -> 0x200 Bytes | |
| + | |
| +typedef struct _vgm_rom_data_block VGM_ROM_DATA; | |
| +struct _vgm_rom_data_block | |
| +{ | |
| + uint8_t Type; | |
| + uint8_t dstart_msb; | |
| + uint32_t DataSize; | |
| + const void* Data; | |
| +}; | |
| +typedef struct _vgm_rom_init_command VGM_INIT_CMD; | |
| +struct _vgm_rom_init_command | |
| +{ | |
| + uint8_t CmdLen; | |
| + uint8_t Data[0x08]; | |
| +}; | |
| +typedef struct _vgm_file_inf VGM_INF; | |
| +struct _vgm_file_inf | |
| +{ | |
| + FILE* hFile; | |
| + VGM_HEADER Header; | |
| + uint8_t WroteHeader; | |
| + uint32_t HeaderBytes; | |
| + uint32_t BytesWrt; | |
| + uint32_t SmplsWrt; | |
| + uint32_t EvtDelay; | |
| + | |
| + uint32_t DataCount; | |
| + VGM_ROM_DATA DataBlk[0x20]; | |
| + //uint32_t CmdAlloc; | |
| + uint32_t CmdCount; | |
| + VGM_INIT_CMD Commands[0x100]; | |
| + | |
| + uint8_t NesMemEmpty; | |
| + uint8_t NesMem[0x4000]; | |
| +}; | |
| +typedef struct _vgm_chip VGM_CHIP; | |
| +typedef struct _vgm_chip_pcmcache VGM_PCMCACHE; | |
| +struct _vgm_chip | |
| +{ | |
| + uint16_t VgmID; | |
| + uint8_t ChipType; | |
| + uint8_t HadWrite; | |
| + VGM_PCMCACHE* PCMCache; | |
| +}; | |
| +struct _vgm_chip_pcmcache | |
| +{ | |
| + uint32_t Start; | |
| + uint32_t Next; | |
| + uint32_t Pos; | |
| + uint32_t CacheSize; | |
| + uint8_t* CacheData; | |
| +}; | |
| + | |
| + | |
| +class VGMTimer | |
| +{ | |
| +private: | |
| + emu_timer* m_timer; | |
| + | |
| + TIMER_CALLBACK_MEMBER(vgmfile_callback); | |
| + | |
| +public: | |
| + VGMTimer(running_machine &machine) | |
| + { | |
| + m_timer = machine.scheduler().timer_alloc(timer_expired_delegate(FUNC(VGMTimer::vgmfile_callback), this)); | |
| + m_timer->adjust(attotime::from_hz(44100), 0, attotime::from_hz(44100)); | |
| + } | |
| +}; | |
| + | |
| +#define MAX_VGM_FILES 0x10 | |
| +#define MAX_VGM_CHIPS 0x80 | |
| +static char vgm_namebase[0x80] = "vgmwrite"; | |
| +static VGM_INF VgmFile[MAX_VGM_FILES]; | |
| +static VGM_CHIP VgmChip[MAX_VGM_CHIPS]; | |
| +static VGM_PCMCACHE VgmPCache[MAX_VGM_CHIPS]; | |
| +static GD3_TAG VgmTag; | |
| +static VGMTimer* VgmTimerObj; | |
| +static running_machine* hMachine = nullptr; | |
| + | |
| +// Function Prototypes | |
| +static size_t str2utf16(gd3char_t* dststr, const char* srcstr, size_t max); | |
| +static size_t wcs2utf16(gd3char_t* dststr, const wchar_t* srcstr, size_t max); | |
| +static size_t utf16len(const gd3char_t* str); | |
| +static void vgm_header_postwrite(uint16_t vgm_id); | |
| +static void vgm_header_sizecheck(uint16_t vgm_id, uint32_t MinVer, uint32_t MinSize); | |
| +static void vgm_header_clear(uint16_t vgm_id); | |
| +static void vgm_setup_pcmcache(VGM_PCMCACHE* TempPC, uint32_t Size); | |
| +static void vgm_close(uint16_t vgm_id); | |
| +static void vgm_write_delay(uint16_t vgm_id); | |
| +static uint8_t vgm_nes_ram_check(VGM_INF* VI, uint32_t datasize, uint32_t* value1, uint32_t* value2, const uint8_t* data); | |
| +static void vgm_flush_pcm(VGM_CHIP* VC); | |
| + | |
| + | |
| +static size_t str2utf16(gd3char_t* dststr, const char* srcstr, size_t max) | |
| +{ | |
| + size_t srclen; | |
| + size_t wlen; | |
| + wchar_t* wstr; | |
| + | |
| + srclen = strlen(srcstr); | |
| + wstr = (wchar_t*)calloc(srclen, sizeof(wchar_t)); | |
| + | |
| + mbstowcs(wstr, srcstr, srclen + 1); | |
| + wlen = wcs2utf16(dststr, wstr, max); | |
| + | |
| + free(wstr); | |
| + return wlen; | |
| +} | |
| + | |
| +static size_t wcs2utf16(gd3char_t* dststr, const wchar_t* srcstr, size_t max) | |
| +{ | |
| + size_t wlen; | |
| + | |
| + max --; | |
| + wlen = wcslen(srcstr); | |
| + if (wlen > max) | |
| + wlen = max; | |
| + | |
| + if (sizeof(gd3char_t) == sizeof(wchar_t)) | |
| + { | |
| + wcsncpy((wchar_t*)dststr, srcstr, wlen + 1); | |
| + return wlen; | |
| + } | |
| + else | |
| + { | |
| + size_t idxSrc; | |
| + size_t idxDst; | |
| + | |
| + // return number of copied bytes | |
| + idxDst = 0; | |
| + for (idxSrc = 0; idxSrc < wlen && idxDst < max; idxSrc ++) | |
| + { | |
| + if (srcstr[idxSrc] >= 0x10000) | |
| + { | |
| +#define HI_SURROGATE_START 0xD800 | |
| +#define LO_SURROGATE_START 0xDC00 | |
| + // surrogate conversion from http://unicode.org/faq/utf_bom.html | |
| + gd3char_t X, W; | |
| + | |
| + X = (gd3char_t)srcstr[idxSrc]; | |
| + W = ((srcstr[idxSrc] >> 16) & 0x1F) - 1; | |
| + gd3char_t HiSurrogate = HI_SURROGATE_START | (W << 6) | (X >> 10); | |
| + gd3char_t LoSurrogate = LO_SURROGATE_START | (X & 0x3FF); | |
| + | |
| + dststr[idxDst] = HiSurrogate; idxDst ++; | |
| + dststr[idxDst] = LoSurrogate; idxDst ++; | |
| + } | |
| + else | |
| + { | |
| + // just truncate/copy | |
| + dststr[idxDst] = (gd3char_t)srcstr[idxSrc]; idxDst ++; | |
| + } | |
| + } | |
| + dststr[idxDst] = (gd3char_t)L'\0'; | |
| + return idxDst; | |
| + } | |
| +} | |
| + | |
| +static size_t utf16len(const gd3char_t* str) | |
| +{ | |
| + const gd3char_t* end_ptr = str; | |
| + | |
| + while(*end_ptr != 0) | |
| + end_ptr ++; | |
| + return end_ptr - str; | |
| +} | |
| + | |
| +void vgm_start(running_machine &machine) | |
| +{ | |
| + uint16_t curvgm; | |
| + | |
| + LOG_VGM_FILE = (uint8_t)machine.options().vgm_write(); | |
| + hMachine = &machine; | |
| + printf("VGM logging mode: %02X\n", LOG_VGM_FILE); | |
| + | |
| + // Reset all files | |
| + for (curvgm = 0x00; curvgm < MAX_VGM_FILES; curvgm ++) | |
| + { | |
| + VgmFile[curvgm].hFile = nullptr; | |
| + VgmFile[curvgm].DataCount = 0x00; | |
| + VgmFile[curvgm].CmdCount = 0x00; | |
| + VgmFile[curvgm].NesMemEmpty = 0x01; | |
| + } | |
| + for (curvgm = 0x00; curvgm < MAX_VGM_CHIPS; curvgm ++) | |
| + { | |
| + VgmChip[curvgm].ChipType = 0xFF; | |
| + VgmPCache[curvgm].CacheSize = 0x00; | |
| + VgmPCache[curvgm].CacheData = nullptr; | |
| + } | |
| + | |
| + // start the timer | |
| + // (done here because it makes save states with vgmwrite off compatible with | |
| + // saves that have it on) | |
| + VgmTimerObj = new VGMTimer(machine); | |
| + hMachine = &machine; | |
| + | |
| + if (! LOG_VGM_FILE) | |
| + return; | |
| + | |
| + VgmTag.fccGD3 = 0x20336447; // 'Gd3 ' | |
| + VgmTag.lngVersion = 0x00000100; | |
| + wcs2utf16(VgmTag.strTrackNameE, L"", 0x70); | |
| + wcs2utf16(VgmTag.strTrackNameJ, L"", 0x10); | |
| + wcs2utf16(VgmTag.strGameNameE, L"", 0x70); | |
| + wcs2utf16(VgmTag.strGameNameJ, L"", 0x10); | |
| + wcs2utf16(VgmTag.strSystemNameE, L"", 0x30); | |
| + wcs2utf16(VgmTag.strSystemNameJ, L"", 0x10); | |
| + wcs2utf16(VgmTag.strAuthorNameE, L"", 0x30); | |
| + wcs2utf16(VgmTag.strAuthorNameJ, L"", 0x10); | |
| + wcs2utf16(VgmTag.strReleaseDate, L"", 0x10); | |
| + wcs2utf16(VgmTag.strCreator, L"", 0x20); | |
| + wcs2utf16(VgmTag.strNotes, L"", 0x50); | |
| + | |
| + printf("VGM logging started ...\n"); | |
| + | |
| + return; | |
| +} | |
| + | |
| +void vgm_stop(void) | |
| +{ | |
| + uint16_t curchip; | |
| + uint16_t chip_unused; | |
| + uint16_t curvgm; | |
| + uint32_t clock_mask; | |
| + VGM_HEADER* VH; | |
| + VGM_CHIP* VC; | |
| + | |
| + if (! LOG_VGM_FILE) | |
| + return; | |
| + | |
| + chip_unused = 0x00; | |
| + for (curchip = 0x00; curchip < MAX_VGM_CHIPS; curchip ++) | |
| + { | |
| + VC = &VgmChip[curchip]; | |
| + if (VC->ChipType == 0xFF) | |
| + break; | |
| + | |
| + if (! VC->HadWrite) | |
| + { | |
| + chip_unused ++; | |
| + curvgm = VC->VgmID; | |
| + VH = &VgmFile[curvgm].Header; | |
| + // clock_mask - remove either the dual-chip bit or the entire clock | |
| + clock_mask = (VC->ChipType & 0x80) ? ~0x40000000 : 0x00000000; | |
| + | |
| + switch(VC->ChipType & 0x7F) | |
| + { | |
| + case VGMC_SN76496: | |
| + VH->lngHzPSG &= clock_mask; | |
| + if (! clock_mask) | |
| + { | |
| + VH->shtPSG_Feedback = 0x0000; | |
| + VH->bytPSG_SRWidth = 0x00; | |
| + VH->bytPSG_Flags = 0x00; | |
| + } | |
| + break; | |
| + case VGMC_YM2413: | |
| + VH->lngHz2413 &= clock_mask; | |
| + break; | |
| + case VGMC_YM2612: | |
| + VH->lngHz2612 &= clock_mask; | |
| + break; | |
| + case VGMC_YM2151: | |
| + VH->lngHz2151 &= clock_mask; | |
| + break; | |
| + case VGMC_SEGAPCM: | |
| + VH->lngHzSPCM &= clock_mask; | |
| + break; | |
| + case VGMC_RF5C68: | |
| + VH->lngHzRF5C68 &= clock_mask; | |
| + break; | |
| + case VGMC_YM2203: | |
| + VH->lngHz2203 &= clock_mask; | |
| + if (! clock_mask) | |
| + VH->lngAYFlagsYM2203 = 0x00; | |
| + break; | |
| + case VGMC_YM2608: | |
| + VH->lngHz2608 &= clock_mask; | |
| + if (! clock_mask) | |
| + VH->lngAYFlagsYM2608 = 0x00; | |
| + break; | |
| + case VGMC_YM2610: | |
| + VH->lngHz2610 &= clock_mask; | |
| + break; | |
| + case VGMC_YM3812: | |
| + VH->lngHz3812 &= clock_mask; | |
| + break; | |
| + case VGMC_YM3526: | |
| + VH->lngHz3526 &= clock_mask; | |
| + break; | |
| + case VGMC_Y8950: | |
| + VH->lngHz8950 &= clock_mask; | |
| + break; | |
| + case VGMC_YMF262: | |
| + VH->lngHz262 &= clock_mask; | |
| + break; | |
| + case VGMC_YMF278B: | |
| + VH->lngHz278B &= clock_mask; | |
| + break; | |
| + case VGMC_YMF271: | |
| + VH->lngHz271 &= clock_mask; | |
| + break; | |
| + case VGMC_YMZ280B: | |
| + VH->lngHz280B &= clock_mask; | |
| + break; | |
| + case VGMC_T6W28: | |
| + clock_mask = 0x00000000; | |
| + VH->lngHzPSG &= clock_mask; | |
| + if (! clock_mask) | |
| + { | |
| + VH->shtPSG_Feedback = 0x0000; | |
| + VH->bytPSG_SRWidth = 0x00; | |
| + VH->bytPSG_Flags = 0x00; | |
| + } | |
| + break; | |
| + case VGMC_RF5C164: | |
| + VH->lngHzRF5C164 &= clock_mask; | |
| + break; | |
| + case VGMC_PWM: | |
| + VH->lngHzPWM &= clock_mask; | |
| + break; | |
| + case VGMC_AY8910: | |
| + VH->lngHzAY8910 &= clock_mask; | |
| + if (! clock_mask) | |
| + { | |
| + VH->lngAYFlags = 0x00; | |
| + VH->lngAYType = 0x00; | |
| + } | |
| + break; | |
| + case VGMC_GBSOUND: | |
| + VH->lngHzGBDMG &= clock_mask; | |
| + break; | |
| + case VGMC_NESAPU: | |
| + VH->lngHzNESAPU &= clock_mask; | |
| + break; | |
| + case VGMC_MULTIPCM: | |
| + VH->lngHzMultiPCM &= clock_mask; | |
| + break; | |
| + case VGMC_UPD7759: | |
| + VH->lngHzUPD7759 &= clock_mask; | |
| + break; | |
| + case VGMC_OKIM6258: | |
| + VH->lngHzOKIM6258 &= clock_mask; | |
| + if (! clock_mask) | |
| + VH->bytOKI6258Flags = 0x00; | |
| + break; | |
| + case VGMC_OKIM6295: | |
| + VH->lngHzOKIM6295 &= clock_mask; | |
| + break; | |
| + case VGMC_K051649: | |
| + VH->lngHzK051649 &= clock_mask; | |
| + break; | |
| + case VGMC_K054539: | |
| + VH->lngHzK054539 &= clock_mask; | |
| + if (! clock_mask) | |
| + VH->bytK054539Flags = 0x00; | |
| + break; | |
| + case VGMC_C6280: | |
| + VH->lngHzHuC6280 &= clock_mask; | |
| + break; | |
| + case VGMC_C140: | |
| + VH->lngHzC140 &= clock_mask; | |
| + if (! clock_mask) | |
| + VH->bytC140Type = 0x00; | |
| + break; | |
| + case VGMC_K053260: | |
| + VH->lngHzK053260 &= clock_mask; | |
| + break; | |
| + case VGMC_POKEY: | |
| + VH->lngHzPokey &= clock_mask; | |
| + break; | |
| + case VGMC_QSOUND: | |
| + VH->lngHzQSound &= clock_mask; | |
| + break; | |
| + case VGMC_SCSP: | |
| + VH->lngHzSCSP &= clock_mask; | |
| + break; | |
| + case VGMC_WSWAN: | |
| + VH->lngHzWSwan &= clock_mask; | |
| + break; | |
| + case VGMC_VSU: | |
| + VH->lngHzVSU &= clock_mask; | |
| + break; | |
| + case VGMC_SAA1099: | |
| + VH->lngHzSAA1099 &= clock_mask; | |
| + break; | |
| + case VGMC_ES5503: | |
| + VH->lngHzES5503 &= clock_mask; | |
| + if (! clock_mask) | |
| + VH->bytES5503Chns = 0x00; | |
| + break; | |
| + case VGMC_ES5506: | |
| + VH->lngHzES5506 &= clock_mask; | |
| + if (! clock_mask) | |
| + VH->bytES5506Chns = 0x00; | |
| + break; | |
| + case VGMC_X1_010: | |
| + VH->lngHzX1_010 &= clock_mask; | |
| + break; | |
| + case VGMC_C352: | |
| + VH->lngHzC352 &= clock_mask; | |
| + if (! clock_mask) | |
| + VH->bytC352ClkDiv = 0x00; | |
| + break; | |
| + case VGMC_GA20: | |
| + VH->lngHzGA20 &= clock_mask; | |
| + break; | |
| + // case VGMC_OKIM6376: | |
| + // VH->lngHzOKIM6376 &= clock_mask; | |
| + // break; | |
| + } | |
| + } | |
| + if (VC->PCMCache != nullptr) | |
| + { | |
| + VC->PCMCache->CacheSize = 0x00; | |
| + free(VC->PCMCache->CacheData); | |
| + VC->PCMCache->CacheData = nullptr; | |
| + VC->PCMCache = nullptr; | |
| + } | |
| + } | |
| + if (chip_unused) | |
| + print_info("Header Data of %hu unused Chips removed.\n", chip_unused); | |
| + | |
| + for (curvgm = 0x00; curvgm < MAX_VGM_FILES; curvgm ++) | |
| + { | |
| + if (VgmFile[curvgm].hFile != nullptr) | |
| + vgm_close(curvgm); | |
| + } | |
| + delete VgmTimerObj; VgmTimerObj = nullptr; | |
| + print_info("VGM stopped.\n"); | |
| + | |
| + return; | |
| +} | |
| + | |
| +TIMER_CALLBACK_MEMBER(VGMTimer::vgmfile_callback) | |
| +{ | |
| + uint16_t curvgm; | |
| + | |
| + if (! LOG_VGM_FILE) | |
| + return; | |
| + | |
| + for (curvgm = 0x00; curvgm < MAX_VGM_FILES; curvgm ++) | |
| + { | |
| + if (VgmFile[curvgm].hFile != nullptr) | |
| + VgmFile[curvgm].EvtDelay ++; | |
| + } | |
| + | |
| + return; | |
| +} | |
| + | |
| +static void vgm_header_postwrite(uint16_t vgm_id) | |
| +{ | |
| + VGM_INF* VI; | |
| + VGM_HEADER* Header; | |
| + VGM_ROM_DATA* VR; | |
| + VGM_INIT_CMD* VC; | |
| + uint32_t curcmd; | |
| + uint32_t blocksize; | |
| + uint32_t templng; | |
| + | |
| + if (VgmFile[vgm_id].WroteHeader) | |
| + return; | |
| + | |
| + VI = &VgmFile[vgm_id]; | |
| + Header = &VI->Header; | |
| + | |
| + print_info("Writing VGM Header ...\n"); | |
| + fseek(VI->hFile, 0x00, SEEK_SET); | |
| + VI->BytesWrt = 0x00; | |
| + | |
| + fwrite(Header, 0x01, VI->HeaderBytes, VI->hFile); | |
| + VI->BytesWrt += VI->HeaderBytes; | |
| + | |
| + for (curcmd = 0x00; curcmd < VI->DataCount; curcmd ++) | |
| + { | |
| + VR = &VI->DataBlk[curcmd]; | |
| + blocksize = 0x08; | |
| + if (VR->Data != nullptr) | |
| + blocksize += VR->DataSize; | |
| + VR->DataSize &= 0x7FFFFFFF; | |
| + | |
| + fputc(0x67, VI->hFile); | |
| + fputc(0x66, VI->hFile); | |
| + fputc(VR->Type, VI->hFile); | |
| + fwrite(&blocksize, 0x04, 0x01, VI->hFile); // Data Block Size | |
| + fwrite(&VR->DataSize, 0x04, 0x01, VI->hFile); // ROM Size | |
| + templng = 0x00; | |
| + templng |= (VR->dstart_msb << 24); | |
| + fwrite(&templng, 0x04, 0x01, VI->hFile); // Data Base Address | |
| + if (VR->Data != nullptr) | |
| + fwrite(VR->Data, 0x01, VR->DataSize, VI->hFile); | |
| + VI->BytesWrt += 0x07 + (blocksize & 0x7FFFFFFF); | |
| + } | |
| + for (curcmd = 0x00; curcmd < VI->CmdCount; curcmd ++) | |
| + { | |
| + VC = &VI->Commands[curcmd]; | |
| + fwrite(VC->Data, 0x01, VC->CmdLen, VI->hFile); | |
| + VI->BytesWrt += VC->CmdLen; | |
| + } | |
| + VI->WroteHeader = 0x01; | |
| + | |
| + return; | |
| +} | |
| + | |
| +static void vgm_header_sizecheck(uint16_t vgm_id, uint32_t MinVer, uint32_t MinSize) | |
| +{ | |
| + VGM_INF* VI; | |
| + VGM_HEADER* Header; | |
| + | |
| + if (VgmFile[vgm_id].hFile == nullptr) | |
| + return; | |
| + | |
| + VI = &VgmFile[vgm_id]; | |
| + Header = &VI->Header; | |
| + | |
| + if (Header->lngVersion < MinVer) | |
| + Header->lngVersion = MinVer; | |
| + if (VI->HeaderBytes < MinSize) | |
| + VI->HeaderBytes = MinSize; | |
| + | |
| + return; | |
| +} | |
| + | |
| +static void vgm_header_clear(uint16_t vgm_id) | |
| +{ | |
| + VGM_INF* VI; | |
| + VGM_HEADER* Header; | |
| + | |
| + if (VgmFile[vgm_id].hFile == nullptr) | |
| + return; | |
| + | |
| + VI = &VgmFile[vgm_id]; | |
| + Header = &VI->Header; | |
| + memset(Header, 0x00, sizeof(VGM_HEADER)); | |
| + Header->fccVGM = 0x206D6756; // 'Vgm ' | |
| + Header->lngEOFOffset = 0x00000000; | |
| + Header->lngVersion = 0x00000151; | |
| + //Header->lngGD3Offset = 0x00000000; | |
| + //Header->lngTotalSamples = 0; | |
| + //Header->lngLoopOffset = 0x00000000; | |
| + //Header->lngLoopSamples = 0; | |
| +#ifdef DYNAMIC_HEADER_SIZE | |
| + VI->HeaderBytes = 0x38; | |
| + VI->WroteHeader = 0x00; | |
| +#else | |
| + VI->HeaderBytes = sizeof(VGM_HEADER); | |
| + VI->WroteHeader = 0x01; | |
| +#endif | |
| + Header->lngDataOffset = VI->HeaderBytes - 0x34; | |
| + | |
| + //fseek(VI->hFile, 0x00, SEEK_SET); | |
| + //fwrite(Header, 0x01, sizeof(VGM_HEADER), VI->hFile); | |
| + //VI->BytesWrt += sizeof(VGM_HEADER); | |
| + VI->BytesWrt = 0x00; | |
| + | |
| + return; | |
| +} | |
| + | |
| +uint16_t vgm_open(uint8_t chip_type, int clock) | |
| +{ | |
| + uint16_t chip_id; | |
| + uint16_t chip_file; | |
| + uint16_t curvgm; | |
| + uint32_t chip_val; | |
| + char vgm_name[0x20]; | |
| + uint8_t use_two; | |
| + | |
| + printf("vgm_open - Chip Type %d, Clock %d\n", chip_type, clock); | |
| + if (! LOG_VGM_FILE || chip_type == 0xFF) | |
| + return 0xFFFF; | |
| + | |
| + chip_id = 0xFFFF; | |
| + for (curvgm = 0x00; curvgm < MAX_VGM_CHIPS; curvgm ++) | |
| + { | |
| + if (VgmChip[curvgm].ChipType == 0xFF) | |
| + { | |
| + chip_id = curvgm; | |
| + break; | |
| + } | |
| + } | |
| + if (chip_id == 0xFFFF) | |
| + return 0xFFFF; | |
| + | |
| + if (LOG_VGM_FILE != 0xDD) | |
| + { | |
| + // prevent it from logging chips with known audible errors in VGM logs | |
| + if (chip_type == VGMC_ES5506) | |
| + return 0xFFFF; // logging needs to be reworked for this one | |
| + if (chip_type == VGMC_SCSP) | |
| + return 0xFFFF; // timing is horribly off (see Fighting Vipers) | |
| + else if (chip_type == VGMC_WSWAN) | |
| + return 0xFFFF; // WonderSwan emulation is horrible in general - even the logs are screwed | |
| + } | |
| + | |
| + chip_file = 0xFFFF; | |
| + for (curvgm = 0x00; curvgm < MAX_VGM_FILES; curvgm ++) | |
| + { | |
| + if (VgmFile[curvgm].hFile == nullptr) | |
| + continue; | |
| + | |
| + use_two = 0x01; | |
| + switch(chip_type) | |
| + { | |
| + case VGMC_SN76496: | |
| + chip_val = VgmFile[curvgm].Header.lngHzPSG; | |
| + break; | |
| + case VGMC_YM2413: | |
| + chip_val = VgmFile[curvgm].Header.lngHz2413; | |
| + break; | |
| + case VGMC_YM2612: | |
| + chip_val = VgmFile[curvgm].Header.lngHz2612; | |
| + break; | |
| + case VGMC_YM2151: | |
| + chip_val = VgmFile[curvgm].Header.lngHz2151; | |
| + break; | |
| + case VGMC_SEGAPCM: | |
| + chip_val = VgmFile[curvgm].Header.lngHzSPCM; | |
| + break; | |
| + case VGMC_RF5C68: | |
| + chip_val = VgmFile[curvgm].Header.lngHzRF5C68; | |
| + use_two = 0x00; | |
| + break; | |
| + case VGMC_YM2203: | |
| + chip_val = VgmFile[curvgm].Header.lngHz2203; | |
| + break; | |
| + case VGMC_YM2608: | |
| + chip_val = VgmFile[curvgm].Header.lngHz2608; | |
| + break; | |
| + case VGMC_YM2610: | |
| + chip_val = VgmFile[curvgm].Header.lngHz2610; | |
| + break; | |
| + case VGMC_YM3812: | |
| + chip_val = VgmFile[curvgm].Header.lngHz3812; | |
| + break; | |
| + case VGMC_YM3526: | |
| + chip_val = VgmFile[curvgm].Header.lngHz3526; | |
| + break; | |
| + case VGMC_Y8950: | |
| + chip_val = VgmFile[curvgm].Header.lngHz8950; | |
| + break; | |
| + case VGMC_YMF262: | |
| + chip_val = VgmFile[curvgm].Header.lngHz262; | |
| + break; | |
| + case VGMC_YMF278B: | |
| + chip_val = VgmFile[curvgm].Header.lngHz278B; | |
| + break; | |
| + case VGMC_YMF271: | |
| + chip_val = VgmFile[curvgm].Header.lngHz271; | |
| + break; | |
| + case VGMC_YMZ280B: | |
| + chip_val = VgmFile[curvgm].Header.lngHz280B; | |
| + break; | |
| + case VGMC_T6W28: | |
| + chip_val = VgmFile[curvgm].Header.lngHzPSG; | |
| + use_two = 0x00; | |
| + break; | |
| + case VGMC_RF5C164: | |
| + chip_val = VgmFile[curvgm].Header.lngHzRF5C164; | |
| + use_two = 0x00; | |
| + break; | |
| + case VGMC_PWM: | |
| + chip_val = VgmFile[curvgm].Header.lngHzPWM; | |
| + use_two = 0x00; | |
| + break; | |
| + case VGMC_AY8910: | |
| + chip_val = VgmFile[curvgm].Header.lngHzAY8910; | |
| + break; | |
| + case VGMC_GBSOUND: | |
| + chip_val = VgmFile[curvgm].Header.lngHzGBDMG; | |
| + break; | |
| + case VGMC_NESAPU: | |
| + chip_val = VgmFile[curvgm].Header.lngHzNESAPU; | |
| + break; | |
| + case VGMC_MULTIPCM: | |
| + chip_val = VgmFile[curvgm].Header.lngHzMultiPCM; | |
| + break; | |
| + case VGMC_UPD7759: | |
| + chip_val = VgmFile[curvgm].Header.lngHzUPD7759; | |
| + break; | |
| + case VGMC_OKIM6258: | |
| + chip_val = VgmFile[curvgm].Header.lngHzOKIM6258; | |
| + break; | |
| + case VGMC_OKIM6295: | |
| + chip_val = VgmFile[curvgm].Header.lngHzOKIM6295; | |
| + break; | |
| + case VGMC_K051649: | |
| + chip_val = VgmFile[curvgm].Header.lngHzK051649; | |
| + break; | |
| + case VGMC_K054539: | |
| + chip_val = VgmFile[curvgm].Header.lngHzK054539; | |
| + break; | |
| + case VGMC_C6280: | |
| + chip_val = VgmFile[curvgm].Header.lngHzHuC6280; | |
| + break; | |
| + case VGMC_C140: | |
| + chip_val = VgmFile[curvgm].Header.lngHzC140; | |
| + break; | |
| + case VGMC_K053260: | |
| + chip_val = VgmFile[curvgm].Header.lngHzK053260; | |
| + break; | |
| + case VGMC_POKEY: | |
| + chip_val = VgmFile[curvgm].Header.lngHzPokey; | |
| + break; | |
| + case VGMC_QSOUND: | |
| + chip_val = VgmFile[curvgm].Header.lngHzQSound; | |
| + use_two = 0x00; | |
| + break; | |
| + case VGMC_SCSP: | |
| + chip_val = VgmFile[curvgm].Header.lngHzSCSP; | |
| + //use_two = 0x00; | |
| + break; | |
| + case VGMC_WSWAN: | |
| + chip_val = VgmFile[curvgm].Header.lngHzWSwan; | |
| + break; | |
| + case VGMC_VSU: | |
| + chip_val = VgmFile[curvgm].Header.lngHzVSU; | |
| + break; | |
| + case VGMC_SAA1099: | |
| + chip_val = VgmFile[curvgm].Header.lngHzSAA1099; | |
| + break; | |
| + case VGMC_ES5503: | |
| + chip_val = VgmFile[curvgm].Header.lngHzES5503; | |
| + break; | |
| + case VGMC_ES5506: | |
| + chip_val = VgmFile[curvgm].Header.lngHzES5506; | |
| + break; | |
| + case VGMC_X1_010: | |
| + chip_val = VgmFile[curvgm].Header.lngHzX1_010; | |
| + break; | |
| + case VGMC_C352: | |
| + chip_val = VgmFile[curvgm].Header.lngHzC352; | |
| + use_two = 0x00; | |
| + break; | |
| + case VGMC_GA20: | |
| + chip_val = VgmFile[curvgm].Header.lngHzGA20; | |
| + break; | |
| + // case VGMC_OKIM6376: | |
| + // chip_val = VgmFile[curvgm].Header.lngHzOKIM6376; | |
| + // use_two = 0x00; | |
| + // break; | |
| + default: | |
| + return 0xFFFF; // unknown chip - don't log | |
| + } | |
| + if (! chip_val) | |
| + { | |
| + chip_file = curvgm; | |
| + break; | |
| + } | |
| + else if (use_two) | |
| + { | |
| + if (! (chip_val & 0x40000000) && (LOG_VGM_FILE & 0x01)) | |
| + { | |
| + if (clock != chip_val) | |
| + print_warn("VGM Log: Warning - 2-chip mode, but chip clocks different!\n"); | |
| + chip_file = curvgm; | |
| + clock = 0x40000000 | chip_val; | |
| + chip_type |= 0x80; | |
| + break; | |
| + } | |
| + } | |
| + } // end for(curvgm) | |
| + if (chip_file == 0xFFFF) | |
| + { | |
| + for (curvgm = 0x00; curvgm < MAX_VGM_FILES; curvgm ++) | |
| + { | |
| + if (VgmFile[curvgm].hFile == nullptr) | |
| + { | |
| + sprintf(vgm_name, "%s%hX.vgm", vgm_namebase, curvgm); | |
| + print_info("Opening %s ...\t", vgm_name); | |
| + VgmFile[curvgm].hFile = fopen(vgm_name, "wb"); | |
| + if (VgmFile[curvgm].hFile) | |
| + { | |
| + print_info("OK\n"); | |
| + chip_file = curvgm; | |
| + VgmFile[curvgm].BytesWrt = 0; | |
| + VgmFile[curvgm].SmplsWrt = 0; | |
| + VgmFile[curvgm].EvtDelay = 0; | |
| + vgm_header_clear(curvgm); | |
| + } | |
| + else | |
| + { | |
| + print_info("Failed to create the file!\n"); | |
| + } | |
| + break; | |
| + } | |
| + } | |
| + } | |
| + if (chip_file == 0xFFFF) | |
| + return 0xFFFF; | |
| + | |
| + VgmChip[chip_id].VgmID = chip_file; | |
| + VgmChip[chip_id].ChipType = chip_type; | |
| + VgmChip[chip_id].HadWrite = 0x00; | |
| + VgmChip[chip_id].PCMCache = nullptr; | |
| + | |
| + switch(chip_type & 0x7F) | |
| + { | |
| + case VGMC_SN76496: | |
| + VgmFile[chip_file].Header.lngHzPSG = clock; | |
| + break; | |
| + case VGMC_YM2413: | |
| + VgmFile[chip_file].Header.lngHz2413 = clock; | |
| + break; | |
| + case VGMC_YM2612: | |
| + VgmFile[chip_file].Header.lngHz2612 = clock; | |
| + break; | |
| + case VGMC_YM2151: | |
| + VgmFile[chip_file].Header.lngHz2151 = clock; | |
| + break; | |
| + case VGMC_SEGAPCM: | |
| + VgmFile[chip_file].Header.lngHzSPCM = clock; | |
| + break; | |
| + case VGMC_RF5C68: | |
| + VgmFile[chip_file].Header.lngHzRF5C68 = clock; | |
| + VgmChip[chip_id].PCMCache = &VgmPCache[chip_file]; | |
| + vgm_setup_pcmcache(VgmChip[chip_id].PCMCache, 0x400); | |
| + break; | |
| + case VGMC_YM2203: | |
| + VgmFile[chip_file].Header.lngHz2203 = clock; | |
| + break; | |
| + case VGMC_YM2608: | |
| + VgmFile[chip_file].Header.lngHz2608 = clock; | |
| + break; | |
| + case VGMC_YM2610: | |
| + VgmFile[chip_file].Header.lngHz2610 = clock; | |
| + break; | |
| + case VGMC_YM3812: | |
| + VgmFile[chip_file].Header.lngHz3812 = clock; | |
| + break; | |
| + case VGMC_YM3526: | |
| + VgmFile[chip_file].Header.lngHz3526 = clock; | |
| + break; | |
| + case VGMC_Y8950: | |
| + VgmFile[chip_file].Header.lngHz8950 = clock; | |
| + break; | |
| + case VGMC_YMF262: | |
| + VgmFile[chip_file].Header.lngHz262 = clock; | |
| + break; | |
| + case VGMC_YMF278B: | |
| + VgmFile[chip_file].Header.lngHz278B = clock; | |
| + break; | |
| + case VGMC_YMF271: | |
| + VgmFile[chip_file].Header.lngHz271 = clock; | |
| + break; | |
| + case VGMC_YMZ280B: | |
| + VgmFile[chip_file].Header.lngHz280B = clock; | |
| + break; | |
| + case VGMC_T6W28: | |
| + VgmFile[chip_file].Header.lngHzPSG = clock | 0xC0000000; // Cheat to use 2 SN76489 chips | |
| + break; | |
| + case VGMC_RF5C164: | |
| + VgmFile[chip_file].Header.lngHzRF5C164 = clock; | |
| + VgmChip[chip_id].PCMCache = &VgmPCache[chip_file]; | |
| + vgm_setup_pcmcache(VgmChip[chip_id].PCMCache, 0x400); | |
| + break; | |
| + case VGMC_PWM: | |
| + VgmFile[chip_file].Header.lngHzPWM = clock; | |
| + break; | |
| + case VGMC_AY8910: | |
| + VgmFile[chip_file].Header.lngHzAY8910 = clock; | |
| + break; | |
| + case VGMC_GBSOUND: | |
| + VgmFile[chip_file].Header.lngHzGBDMG = clock; | |
| + break; | |
| + case VGMC_NESAPU: | |
| + VgmFile[chip_file].Header.lngHzNESAPU = clock; | |
| + break; | |
| + case VGMC_MULTIPCM: | |
| + VgmFile[chip_file].Header.lngHzMultiPCM = clock; | |
| + break; | |
| + case VGMC_UPD7759: | |
| + VgmFile[chip_file].Header.lngHzUPD7759 = clock; | |
| + break; | |
| + case VGMC_OKIM6258: | |
| + VgmFile[chip_file].Header.lngHzOKIM6258 = clock; | |
| + break; | |
| + case VGMC_OKIM6295: | |
| + VgmFile[chip_file].Header.lngHzOKIM6295 = clock; | |
| + break; | |
| + case VGMC_K051649: | |
| + VgmFile[chip_file].Header.lngHzK051649 = clock; | |
| + break; | |
| + case VGMC_K054539: | |
| + VgmFile[chip_file].Header.lngHzK054539 = clock; | |
| + break; | |
| + case VGMC_C6280: | |
| + VgmFile[chip_file].Header.lngHzHuC6280 = clock; | |
| + break; | |
| + case VGMC_C140: | |
| + VgmFile[chip_file].Header.lngHzC140 = clock; | |
| + break; | |
| + case VGMC_K053260: | |
| + VgmFile[chip_file].Header.lngHzK053260 = clock; | |
| + break; | |
| + case VGMC_POKEY: | |
| + VgmFile[chip_file].Header.lngHzPokey = clock; | |
| + break; | |
| + case VGMC_QSOUND: | |
| + VgmFile[chip_file].Header.lngHzQSound = clock; | |
| + break; | |
| + case VGMC_SCSP: | |
| + VgmFile[chip_file].Header.lngHzSCSP = clock; | |
| + VgmChip[chip_id].PCMCache = &VgmPCache[chip_file]; | |
| + vgm_setup_pcmcache(VgmChip[chip_id].PCMCache, 0x4000); | |
| + break; | |
| + case VGMC_WSWAN: | |
| + VgmFile[chip_file].Header.lngHzWSwan = clock; | |
| + break; | |
| + case VGMC_VSU: | |
| + VgmFile[chip_file].Header.lngHzVSU = clock; | |
| + break; | |
| + case VGMC_SAA1099: | |
| + VgmFile[chip_file].Header.lngHzSAA1099 = clock; | |
| + break; | |
| + case VGMC_ES5503: | |
| + VgmFile[chip_file].Header.lngHzES5503 = clock; | |
| + VgmChip[chip_id].PCMCache = &VgmPCache[chip_file]; | |
| + vgm_setup_pcmcache(VgmChip[chip_id].PCMCache, 0x1000); | |
| + break; | |
| + case VGMC_ES5506: | |
| + VgmFile[chip_file].Header.lngHzES5506 = clock; | |
| + break; | |
| + case VGMC_X1_010: | |
| + VgmFile[chip_file].Header.lngHzX1_010 = clock; | |
| + break; | |
| + case VGMC_C352: | |
| + VgmFile[chip_file].Header.lngHzC352 = clock; | |
| + break; | |
| + case VGMC_GA20: | |
| + VgmFile[chip_file].Header.lngHzGA20 = clock; | |
| + break; | |
| +// case VGMC_OKIM6376: | |
| +// VgmFile[chip_file].Header.lngHzOKIM6376 = clock; | |
| +// break; | |
| + } | |
| + | |
| + switch(chip_type & 0x7F) | |
| + { | |
| + case VGMC_SN76496: | |
| + case VGMC_YM2413: | |
| + case VGMC_YM2612: | |
| + case VGMC_YM2151: | |
| + case VGMC_SEGAPCM: | |
| + case VGMC_T6W28: | |
| + vgm_header_sizecheck(chip_file, 0x00000151, 0x40); | |
| + break; | |
| + case VGMC_RF5C68: | |
| + case VGMC_YM2203: | |
| + case VGMC_YM2608: | |
| + case VGMC_YM2610: | |
| + case VGMC_YM3812: | |
| + case VGMC_YM3526: | |
| + case VGMC_Y8950: | |
| + case VGMC_YMF262: | |
| + case VGMC_YMF278B: | |
| + case VGMC_YMF271: | |
| + case VGMC_YMZ280B: | |
| + case VGMC_RF5C164: | |
| + case VGMC_PWM: | |
| + case VGMC_AY8910: | |
| + vgm_header_sizecheck(chip_file, 0x00000151, 0x80); | |
| + break; | |
| + case VGMC_GBSOUND: | |
| + case VGMC_NESAPU: | |
| + case VGMC_MULTIPCM: | |
| + case VGMC_UPD7759: | |
| + case VGMC_OKIM6258: | |
| + case VGMC_OKIM6295: | |
| + case VGMC_K051649: | |
| + case VGMC_K054539: | |
| + case VGMC_C6280: | |
| + case VGMC_C140: | |
| + case VGMC_K053260: | |
| + case VGMC_POKEY: | |
| + case VGMC_QSOUND: | |
| +// case VGMC_OKIM6376: | |
| + vgm_header_sizecheck(chip_file, 0x00000161, 0xC0); | |
| + break; | |
| + case VGMC_SCSP: | |
| + vgm_header_sizecheck(chip_file, 0x00000171, 0xC0); | |
| + break; | |
| + case VGMC_WSWAN: | |
| + case VGMC_VSU: | |
| + case VGMC_SAA1099: | |
| + case VGMC_ES5503: | |
| + case VGMC_ES5506: | |
| + case VGMC_X1_010: | |
| + case VGMC_C352: | |
| + vgm_header_sizecheck(chip_file, 0x00000171, 0xE0); | |
| + break; | |
| + case VGMC_GA20: | |
| + vgm_header_sizecheck(chip_file, 0x00000171, 0x100); | |
| + break; | |
| + } | |
| + | |
| + return chip_id; | |
| +} | |
| + | |
| +static void vgm_setup_pcmcache(VGM_PCMCACHE* TempPC, uint32_t Size) | |
| +{ | |
| + TempPC->CacheSize = Size; | |
| + if (TempPC->CacheData != nullptr) | |
| + free(TempPC->CacheData); | |
| + TempPC->CacheData = (uint8_t*)malloc(Size); | |
| + | |
| + return; | |
| +} | |
| + | |
| +void vgm_header_set(uint16_t chip_id, uint8_t attr, uint32_t data) | |
| +{ | |
| + VGM_HEADER* VH; | |
| + uint8_t bitcnt; | |
| + | |
| + if (! LOG_VGM_FILE || chip_id == 0xFFFF) | |
| + return; | |
| + if (VgmChip[chip_id].ChipType == 0xFF) | |
| + return; | |
| + | |
| + VH = &VgmFile[VgmChip[chip_id].VgmID].Header; | |
| + switch(VgmChip[chip_id].ChipType & 0x7F) // Write the Header data | |
| + { | |
| + case VGMC_SN76496: | |
| + case VGMC_T6W28: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Reserved | |
| + break; | |
| + case 0x01: // Shift Register Width (Feedback Mask) | |
| + bitcnt = 0x00; // Convert the BitMask to BitCount | |
| + while(data) | |
| + { | |
| + data >>= 1; | |
| + bitcnt ++; | |
| + } | |
| + VH->bytPSG_SRWidth = bitcnt; | |
| + break; | |
| + case 0x02: // Feedback Pattern (White Noise Tap #1) | |
| + VH->shtPSG_Feedback = (uint16_t)data; | |
| + break; | |
| + case 0x03: // Feedback Pattern (White Noise Tap #2) | |
| + // must be called after #1 | |
| + VH->shtPSG_Feedback |= (uint16_t)data; | |
| + break; | |
| + case 0x04: // Negate Channels Flag | |
| + VH->bytPSG_Flags &= ~(0x01 << 1); | |
| + VH->bytPSG_Flags |= (data & 0x01) << 1; | |
| + break; | |
| + case 0x05: // Stereo Flag (On/Off) | |
| + // 0 is Stereo and 1 is mono | |
| + VH->bytPSG_Flags &= ~(0x01 << 2); | |
| + VH->bytPSG_Flags |= (~data & 0x01) << 2; | |
| + break; | |
| + case 0x06: // Clock Divider (On/Off) | |
| + VH->bytPSG_Flags &= ~(0x01 << 3); | |
| + bitcnt = (data == 1) ? 0x01 : 0x00; | |
| + VH->bytPSG_Flags |= (bitcnt & 0x01) << 3; | |
| + break; | |
| + case 0x07: // Freq 0 is Max | |
| + VH->bytPSG_Flags &= ~(0x01 << 0); | |
| + VH->bytPSG_Flags |= (data & 0x01) << 0; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YM2413: | |
| + break; | |
| + case VGMC_YM2612: | |
| + break; | |
| + case VGMC_YM2151: | |
| + break; | |
| + case VGMC_SEGAPCM: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Reserved | |
| + break; | |
| + case 0x01: // Sega PCM Interface | |
| + VH->lngSPCMIntf = data; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_RF5C68: | |
| + break; | |
| + case VGMC_YM2203: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Reserved | |
| + break; | |
| + case 0x01: // Flags | |
| + VH->lngAYFlagsYM2203 = data & 0xFF; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YM2608: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Reserved | |
| + break; | |
| + case 0x01: // Flags | |
| + VH->lngAYFlagsYM2608 = data & 0xFF; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YM2610: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Chip Type (set YM2610B mode) | |
| + VH->lngHz2610 = (VH->lngHz2610 & 0x7FFFFFFF) | (data << 31); | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YM3812: | |
| + break; | |
| + case VGMC_YM3526: | |
| + break; | |
| + case VGMC_Y8950: | |
| + break; | |
| + case VGMC_YMF262: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // is Part of OPL4 | |
| + if (data) | |
| + { | |
| + VgmChip[chip_id].ChipType = VGMC_YMF278B; | |
| + VH->lngHz262 = 0x00; | |
| + } | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YMF278B: | |
| + break; | |
| + case VGMC_YMF271: | |
| + break; | |
| + case VGMC_YMZ280B: | |
| + break; | |
| + case VGMC_RF5C164: | |
| + break; | |
| + case VGMC_PWM: | |
| + break; | |
| + case VGMC_AY8910: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Device Type | |
| + VH->lngAYType = data & 0xFF; | |
| + break; | |
| + case 0x01: // Flags | |
| + VH->lngAYFlags = data & 0xFF; | |
| + break; | |
| + case 0x10: // Resistor Loads | |
| + case 0x11: | |
| + case 0x12: | |
| + print_info("AY8910: Resistor Load %hu = %u\n", attr & 0x0F, data); | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_GBSOUND: | |
| + break; | |
| + case VGMC_NESAPU: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // FDS Enable | |
| + VH->lngHzNESAPU = (VH->lngHzNESAPU & 0x7FFFFFFF) | (data << 31); | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_MULTIPCM: | |
| + break; | |
| + case VGMC_UPD7759: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Chip Type (set master/slave mode) | |
| + VH->lngHzUPD7759 = (VH->lngHzUPD7759 & 0x7FFFFFFF) | (data << 31); | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_OKIM6258: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Reserved | |
| + break; | |
| + case 0x01: // Clock Divider | |
| + VH->bytOKI6258Flags &= ~(0x03 << 0); | |
| + VH->bytOKI6258Flags |= (data & 0x03) << 0; | |
| + break; | |
| + case 0x02: // ADPCM Type | |
| + VH->bytOKI6258Flags &= ~(0x01 << 2); | |
| + VH->bytOKI6258Flags |= (data & 0x01) << 2; | |
| + break; | |
| + case 0x03: // 12-Bit Output | |
| + VH->bytOKI6258Flags &= ~(0x01 << 3); | |
| + VH->bytOKI6258Flags |= (data & 0x01) << 3; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_OKIM6295: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Chip Type (pin 7 state) | |
| + VH->lngHzOKIM6295 = (VH->lngHzOKIM6295 & 0x7FFFFFFF) | (data << 31); | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_K051649: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Chip Type (K051649/SCC or K052539/SCC+) | |
| + VH->lngHzK051649 = (VH->lngHzK051649 & 0x7FFFFFFF) | (data << 31); | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_K054539: | |
| + switch(attr) | |
| + { | |
| + case 0x01: // Control Flags | |
| + VH->bytK054539Flags = data; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_C6280: | |
| + break; | |
| + case VGMC_C140: | |
| + switch(attr) | |
| + { | |
| + case 0x01: // Banking Type | |
| + VH->bytC140Type = data; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_K053260: | |
| + break; | |
| + case VGMC_POKEY: | |
| + break; | |
| + case VGMC_QSOUND: | |
| + break; | |
| + case VGMC_SCSP: | |
| + break; | |
| + case VGMC_WSWAN: | |
| + break; | |
| + case VGMC_VSU: | |
| + break; | |
| + case VGMC_SAA1099: | |
| + break; | |
| + case VGMC_ES5503: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Reserved | |
| + break; | |
| + case 0x01: // Channels | |
| + VH->bytES5503Chns = data & 0xFF; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_ES5506: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Chip Type (5505/5506) | |
| + VH->lngHzES5506 = (VH->lngHzES5506 & 0x7FFFFFFF) | (data << 31); | |
| + break; | |
| + case 0x01: // Channels | |
| + VH->bytES5506Chns = data & 0xFF; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_X1_010: | |
| + break; | |
| + case VGMC_C352: | |
| + switch(attr) | |
| + { | |
| + case 0x00: // Reserved | |
| + break; | |
| + case 0x01: // Clock Divider | |
| + VH->bytC352ClkDiv = (data / 4) & 0xFF; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_GA20: | |
| + break; | |
| +// case VGMC_OKIM6376: | |
| +// break; | |
| + } | |
| + | |
| + return; | |
| +} | |
| + | |
| +static void vgm_close(uint16_t vgm_id) | |
| +{ | |
| + VGM_INF* VI; | |
| + VGM_HEADER* Header; | |
| + | |
| + if (! LOG_VGM_FILE || vgm_id == 0xFFFF) | |
| + return; | |
| + | |
| + VI = &VgmFile[vgm_id]; | |
| + Header = &VI->Header; | |
| + | |
| + if (! VI->WroteHeader) | |
| + { | |
| + fclose(VI->hFile); | |
| + VI->hFile = nullptr; | |
| + return; | |
| + } | |
| + | |
| + vgm_write_delay(vgm_id); | |
| + fputc(0x66, VI->hFile); // Write EOF Command | |
| + VI->BytesWrt ++; | |
| + | |
| + // GD3 Tag | |
| + Header->lngGD3Offset = VI->BytesWrt - 0x00000014; | |
| + fwrite(&VgmTag.fccGD3, 0x04, 0x01, VI->hFile); | |
| + fwrite(&VgmTag.lngVersion, 0x04, 0x01, VI->hFile); | |
| + fwrite(&VgmTag.lngTagLength, 0x04, 0x01, VI->hFile); | |
| + fwrite(VgmTag.strTrackNameE, sizeof(gd3char_t), utf16len(VgmTag.strTrackNameE) + 1, VI->hFile); | |
| + fwrite(VgmTag.strTrackNameJ, sizeof(gd3char_t), utf16len(VgmTag.strTrackNameJ) + 1, VI->hFile); | |
| + fwrite(VgmTag.strGameNameE, sizeof(gd3char_t), utf16len(VgmTag.strGameNameE) + 1, VI->hFile); | |
| + fwrite(VgmTag.strGameNameJ, sizeof(gd3char_t), utf16len(VgmTag.strGameNameJ) + 1, VI->hFile); | |
| + fwrite(VgmTag.strSystemNameE, sizeof(gd3char_t), utf16len(VgmTag.strSystemNameE) + 1, VI->hFile); | |
| + fwrite(VgmTag.strSystemNameJ, sizeof(gd3char_t), utf16len(VgmTag.strSystemNameJ) + 1, VI->hFile); | |
| + fwrite(VgmTag.strAuthorNameE, sizeof(gd3char_t), utf16len(VgmTag.strAuthorNameE) + 1, VI->hFile); | |
| + fwrite(VgmTag.strAuthorNameJ, sizeof(gd3char_t), utf16len(VgmTag.strAuthorNameJ) + 1, VI->hFile); | |
| + fwrite(VgmTag.strReleaseDate, sizeof(gd3char_t), utf16len(VgmTag.strReleaseDate) + 1, VI->hFile); | |
| + fwrite(VgmTag.strCreator, sizeof(gd3char_t), utf16len(VgmTag.strCreator) + 1, VI->hFile); | |
| + fwrite(VgmTag.strNotes, sizeof(gd3char_t), utf16len(VgmTag.strNotes) + 1, VI->hFile); | |
| + VI->BytesWrt += 0x0C + VgmTag.lngTagLength; | |
| + | |
| + // Rewrite Header | |
| + Header->lngTotalSamples = VI->SmplsWrt; | |
| + Header->lngEOFOffset = VI->BytesWrt - 0x00000004; | |
| + Header->lngDataOffset = VI->HeaderBytes - 0x34; | |
| + | |
| + fseek(VI->hFile, 0x00, SEEK_SET); | |
| + fwrite(Header, 0x01, VI->HeaderBytes, VI->hFile); | |
| + | |
| + fclose(VI->hFile); | |
| + VI->hFile = nullptr; | |
| + | |
| + print_info("VGM %02hX closed. %u Bytes, %u Samples written.\n", vgm_id, VI->BytesWrt, VI->SmplsWrt); | |
| + | |
| + return; | |
| +} | |
| + | |
| +static void vgm_write_delay(uint16_t vgm_id) | |
| +{ | |
| + VGM_INF* VI; | |
| + uint16_t delaywrite; | |
| + | |
| + VI = &VgmFile[vgm_id]; | |
| + if (! VI->WroteHeader && VI->EvtDelay) | |
| + vgm_header_postwrite(vgm_id); // write post-header data | |
| + | |
| + if (VI->EvtDelay) | |
| + { | |
| + for (delaywrite = 0x00; delaywrite < MAX_VGM_CHIPS; delaywrite ++) | |
| + { | |
| + if (VgmChip[delaywrite].ChipType != 0xFF) | |
| + vgm_flush_pcm(&VgmChip[delaywrite]); | |
| + } | |
| + } | |
| + | |
| + while(VI->EvtDelay) | |
| + { | |
| + if (VI->EvtDelay > 0x0000FFFF) | |
| + delaywrite = 0xFFFF; | |
| + else | |
| + delaywrite = (uint16_t)VI->EvtDelay; | |
| + | |
| + if (delaywrite <= 0x0010) | |
| + { | |
| + fputc(0x6F + delaywrite, VI->hFile); | |
| + VI->BytesWrt += 0x01; | |
| + } | |
| + else | |
| + { | |
| + fputc(0x61, VI->hFile); | |
| + fwrite(&delaywrite, 0x02, 0x01, VI->hFile); | |
| + VI->BytesWrt += 0x03; | |
| + } | |
| + VI->SmplsWrt += delaywrite; | |
| + | |
| + VI->EvtDelay -= delaywrite; | |
| + } | |
| + | |
| + return; | |
| +} | |
| + | |
| +void vgm_write(uint16_t chip_id, uint8_t port, uint16_t r, uint8_t v) | |
| +{ | |
| + VGM_CHIP* VC; | |
| + VGM_INF* VI; | |
| + VGM_PCMCACHE* VPC; | |
| + int8_t cm; // "Cheat Mode" to support 2 instances of 1 chip within 1 file | |
| + uint16_t curchip; | |
| + VGM_INIT_CMD WriteCmd; | |
| + uint32_t mem_addr; | |
| + | |
| + if (! LOG_VGM_FILE || chip_id == 0xFFFF) | |
| + return; | |
| + if (VgmChip[chip_id].ChipType == 0xFF) | |
| + return; | |
| + | |
| + VC = &VgmChip[chip_id]; | |
| + VI = &VgmFile[VC->VgmID]; | |
| + if (VI->hFile == nullptr) | |
| + return; | |
| + | |
| + if (! VC->HadWrite) | |
| + { | |
| + VC->HadWrite = 0x01; | |
| + if (VC->ChipType & 0x80) | |
| + { | |
| + for (curchip = 0x00; curchip < chip_id; curchip ++) | |
| + { | |
| + if (VgmChip[curchip].ChipType == (VC->ChipType & 0x7F)) | |
| + VgmChip[curchip].HadWrite = 0x01; | |
| + } | |
| + } | |
| + } | |
| + | |
| + WriteCmd.CmdLen = 0x00; | |
| + cm = (VC->ChipType & 0x80) ? 0x50 : 0x00; | |
| + switch(VC->ChipType & 0x7F) // Write the data | |
| + { | |
| + case VGMC_T6W28: | |
| + cm = ~port & 0x01; | |
| + port = 0x00; | |
| + // fall through | |
| + case VGMC_SN76496: | |
| + switch(port) | |
| + { | |
| + case 0x00: | |
| + cm = cm ? -0x20 : 0x00; | |
| + WriteCmd.Data[0x00] = 0x50 + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.CmdLen = 0x02; | |
| + break; | |
| + case 0x01: | |
| + cm = cm ? -0x10 : 0x00; | |
| + WriteCmd.Data[0x00] = 0x4F + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.CmdLen = 0x02; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YM2413: | |
| + WriteCmd.Data[0x00] = 0x51 + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_YM2612: | |
| + WriteCmd.Data[0x00] = 0x52 + (port & 0x01) + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_YM2151: | |
| + WriteCmd.Data[0x00] = 0x54 + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_SEGAPCM: | |
| + r |= (VC->ChipType & 0x80) << 8; | |
| + WriteCmd.Data[0x00] = 0xC0; // Write Memory | |
| + WriteCmd.Data[0x01] = (r >> 0) & 0xFF; // offset low | |
| + WriteCmd.Data[0x02] = (r >> 8) & 0xFF; // offset high | |
| + WriteCmd.Data[0x03] = v; // data | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + case VGMC_RF5C68: | |
| + switch(port) | |
| + { | |
| + case 0x00: | |
| + WriteCmd.Data[0x00] = 0xB0; // Write Register | |
| + WriteCmd.Data[0x01] = r; // Register | |
| + WriteCmd.Data[0x02] = v; // Value | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case 0x01: | |
| + WriteCmd.Data[0x00] = 0xC1; // Write Memory | |
| + WriteCmd.Data[0x01] = (r >> 0) & 0xFF; // offset low | |
| + WriteCmd.Data[0x02] = (r >> 8) & 0xFF; // offset high | |
| + WriteCmd.Data[0x03] = v; // Data | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YM2203: | |
| + WriteCmd.Data[0x00] = 0x55 + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_YM2608: | |
| + WriteCmd.Data[0x00] = 0x56 + (port & 0x01) + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_YM2610: | |
| + WriteCmd.Data[0x00] = 0x58 + (port & 0x01) + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_YM3812: | |
| + WriteCmd.Data[0x00] = 0x5A + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_YM3526: | |
| + WriteCmd.Data[0x00] = 0x5B + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_Y8950: | |
| + WriteCmd.Data[0x00] = 0x5C + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_YMF262: | |
| + WriteCmd.Data[0x00] = 0x5E + (port & 0x01) + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_YMF278B: | |
| + WriteCmd.Data[0x00] = 0xD0; | |
| + WriteCmd.Data[0x01] = port | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = r; | |
| + WriteCmd.Data[0x03] = v; | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + case VGMC_YMF271: | |
| + WriteCmd.Data[0x00] = 0xD1; | |
| + WriteCmd.Data[0x01] = port | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = r; | |
| + WriteCmd.Data[0x03] = v; | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + case VGMC_YMZ280B: | |
| + WriteCmd.Data[0x00] = 0x5D + cm; | |
| + WriteCmd.Data[0x01] = r; | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_RF5C164: | |
| + switch(port) | |
| + { | |
| + case 0x00: | |
| + WriteCmd.Data[0x00] = 0xB1; // Write Register | |
| + WriteCmd.Data[0x01] = r; // Register | |
| + WriteCmd.Data[0x02] = v; // Value | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case 0x01: | |
| + WriteCmd.Data[0x00] = 0xC2; // Write Memory | |
| + WriteCmd.Data[0x01] = (r >> 0) & 0xFF; // offset low | |
| + WriteCmd.Data[0x02] = (r >> 8) & 0xFF; // offset high | |
| + WriteCmd.Data[0x03] = v; // Data | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_PWM: | |
| + WriteCmd.Data[0x00] = 0xB2; | |
| + WriteCmd.Data[0x01] = (port << 4) | ((r & 0xF00) >> 8); | |
| + WriteCmd.Data[0x02] = r & 0xFF; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_AY8910: | |
| + WriteCmd.Data[0x00] = 0xA0; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_GBSOUND: | |
| + WriteCmd.Data[0x00] = 0xB3; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_NESAPU: | |
| + WriteCmd.Data[0x00] = 0xB4; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_MULTIPCM: | |
| + switch(port) | |
| + { | |
| + case 0x00: // Register Write | |
| + WriteCmd.Data[0x00] = 0xB5; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case 0x01: // Bank Write | |
| + WriteCmd.Data[0x00] = 0xC3; | |
| + WriteCmd.Data[0x01] = v | (VC->ChipType & 0x80); // Both/Left/Right Offset | |
| + WriteCmd.Data[0x02] = (r >> 0) & 0xFF; // offset low | |
| + WriteCmd.Data[0x03] = (r >> 8) & 0xFF; // offset high | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_UPD7759: | |
| + WriteCmd.Data[0x00] = 0xB6; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_OKIM6258: | |
| + WriteCmd.Data[0x00] = 0xB7; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_OKIM6295: | |
| + WriteCmd.Data[0x00] = 0xB8; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_K051649: | |
| + WriteCmd.Data[0x00] = 0xD2; | |
| + WriteCmd.Data[0x01] = port | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = r; | |
| + WriteCmd.Data[0x03] = v; | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + case VGMC_K054539: | |
| + WriteCmd.Data[0x00] = 0xD3; | |
| + WriteCmd.Data[0x01] = (r >> 8) | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = r & 0xFF; | |
| + WriteCmd.Data[0x03] = v; | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + case VGMC_C6280: | |
| + WriteCmd.Data[0x00] = 0xB9; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_C140: | |
| + WriteCmd.Data[0x00] = 0xD4; | |
| + WriteCmd.Data[0x01] = (r >> 8) | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = r & 0xFF; | |
| + WriteCmd.Data[0x03] = v; | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + case VGMC_K053260: | |
| + WriteCmd.Data[0x00] = 0xBA; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_POKEY: | |
| + WriteCmd.Data[0x00] = 0xBB; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_QSOUND: | |
| + WriteCmd.Data[0x00] = 0xC4; | |
| + WriteCmd.Data[0x01] = (r & 0xFF00) >> 8; // Data MSB | |
| + WriteCmd.Data[0x02] = (r & 0x00FF) >> 0; // Data LSB | |
| + WriteCmd.Data[0x03] = v; // Command | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + case VGMC_SCSP: | |
| + switch(port & 0x80) | |
| + { | |
| + case 0x00: // Register Write | |
| + WriteCmd.Data[0x00] = 0xC5; | |
| + WriteCmd.Data[0x01] = ((r & 0xFF00) >> 8) | (VC->ChipType & 0x80); // Data MSB | |
| + WriteCmd.Data[0x02] = (r & 0x00FF) >> 0; // Data LSB | |
| + WriteCmd.Data[0x03] = v; // Command | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + case 0x80: // Memory Write | |
| + //vgm_write_delay(VC->VgmID); | |
| + | |
| + VPC = VC->PCMCache; | |
| + mem_addr = ((port & 0x7F) << 16) | (r << 0); | |
| + | |
| + // optimize consecutive Memory Writes | |
| + if (VPC->Start == 0xFFFFFFFF || mem_addr != VPC->Next) | |
| + { | |
| + // flush cache to file | |
| + vgm_flush_pcm(VC); | |
| + vgm_write_delay(VC->VgmID); | |
| + //printf("Mem Cache Start: %06X\n", mem_addr); | |
| + VPC->Start = mem_addr; | |
| + VPC->Next = VPC->Start; | |
| + VPC->Pos = 0x00; | |
| + } | |
| + VPC->CacheData[VPC->Pos] = v; | |
| + VPC->Pos ++; | |
| + VPC->Next ++; | |
| + if (VPC->Pos >= VPC->CacheSize) | |
| + VPC->Next = 0xFFFFFFFF; | |
| + return; | |
| + } | |
| + break; | |
| + case VGMC_WSWAN: | |
| + switch(port) | |
| + { | |
| + case 0x00: | |
| + WriteCmd.Data[0x00] = 0xBC; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case 0x01: | |
| + WriteCmd.Data[0x00] = 0xC6; // Write Memory | |
| + WriteCmd.Data[0x01] = (r >> 0) & 0xFF; // offset low | |
| + WriteCmd.Data[0x02] = (r >> 8) & 0xFF; // offset high | |
| + WriteCmd.Data[0x03] = v; // Data | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_VSU: | |
| + r |= (VC->ChipType & 0x80) << 8; | |
| + WriteCmd.Data[0x00] = 0xC7; | |
| + WriteCmd.Data[0x01] = (r & 0xFF00) >> 8; // Offset MSB | |
| + WriteCmd.Data[0x02] = (r & 0x00FF) >> 0; // Offset LSB | |
| + WriteCmd.Data[0x03] = v; // Data | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + case VGMC_SAA1099: | |
| + WriteCmd.Data[0x00] = 0xBD; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case VGMC_ES5503: | |
| + switch(port & 0x80) | |
| + { | |
| + case 0x00: // register write | |
| + r |= (VC->ChipType & 0x80) << 8; | |
| + WriteCmd.Data[0x00] = 0xD5; | |
| + WriteCmd.Data[0x01] = (r & 0xFF00) >> 8; // Chip Select | |
| + WriteCmd.Data[0x02] = (r & 0x00FF) >> 0; // Register | |
| + WriteCmd.Data[0x03] = v; | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + case 0x80: // RAM write | |
| + VPC = VC->PCMCache; | |
| + mem_addr = ((port & 0x7F) << 16) | (r << 0); | |
| + | |
| + // optimize consecutive Memory Writes | |
| + if (VPC->Start == 0xFFFFFFFF || mem_addr != VPC->Next) | |
| + { | |
| + // flush cache to file | |
| + vgm_flush_pcm(VC); | |
| + vgm_write_delay(VC->VgmID); | |
| + VPC->Start = mem_addr; | |
| + VPC->Next = VPC->Start; | |
| + VPC->Pos = 0x00; | |
| + } | |
| + VPC->CacheData[VPC->Pos] = v; | |
| + VPC->Pos ++; | |
| + VPC->Next ++; | |
| + if (VPC->Pos >= VPC->CacheSize) | |
| + VPC->Next = 0xFFFFFFFF; | |
| + return; | |
| + } | |
| + break; | |
| + case VGMC_ES5506: | |
| + switch(port & 0x80) | |
| + { | |
| + case 0x00: // 8-bit register write | |
| + WriteCmd.Data[0x00] = 0xBE; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| + case 0x80: // 16-bit register write | |
| + WriteCmd.Data[0x00] = 0xD6; | |
| + WriteCmd.Data[0x01] = v | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = (r & 0xFF00) >> 8; // Data MSB | |
| + WriteCmd.Data[0x03] = (r & 0x00FF) >> 0; // Data LSB | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_X1_010: | |
| + r |= (VC->ChipType & 0x80) << 8; | |
| + WriteCmd.Data[0x00] = 0xC8; | |
| + WriteCmd.Data[0x01] = (r & 0xFF00) >> 8; // Offset MSB | |
| + WriteCmd.Data[0x02] = (r & 0x00FF) >> 0; // Offset LSB | |
| + WriteCmd.Data[0x03] = v; | |
| + WriteCmd.CmdLen = 0x04; | |
| + break; | |
| + /*case VGMC_C352: | |
| + if (port == 0x01) | |
| + { | |
| + v = 0x06/2; // flags register (channel 0) | |
| + r = 0xFFFF; | |
| + } | |
| + WriteCmd.Data[0x00] = 0xC9; | |
| + WriteCmd.Data[0x01] = v; // Register | |
| + WriteCmd.Data[0x02] = (r & 0xFF00) >> 8; // Data MSB | |
| + WriteCmd.Data[0x03] = (r & 0x00FF) >> 0; // Data LSB | |
| + WriteCmd.CmdLen = 0x04; | |
| + break;*/ | |
| + case VGMC_C352: | |
| + port |= (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x00] = 0xE1; | |
| + WriteCmd.Data[0x01] = port; // Register MSB | |
| + WriteCmd.Data[0x02] = v; // Register LSB | |
| + WriteCmd.Data[0x03] = (r & 0xFF00) >> 8; // Data MSB | |
| + WriteCmd.Data[0x04] = (r & 0x00FF) >> 0; // Data LSB | |
| + WriteCmd.CmdLen = 0x05; | |
| + break; | |
| + case VGMC_GA20: | |
| + WriteCmd.Data[0x00] = 0xBF; | |
| + WriteCmd.Data[0x01] = r | (VC->ChipType & 0x80); | |
| + WriteCmd.Data[0x02] = v; | |
| + WriteCmd.CmdLen = 0x03; | |
| + break; | |
| +// case VGMC_OKIM6376: | |
| +// WriteCmd.Data[0x00] = 0x31; | |
| +// WriteCmd.Data[0x01] = v; | |
| +// WriteCmd.CmdLen = 0x02; | |
| +// break; | |
| + } | |
| + | |
| + vgm_write_delay(VC->VgmID); | |
| + | |
| + if (! VI->WroteHeader) | |
| + { | |
| + /*if (VI->CmdCount >= VI->CmdAlloc) | |
| + { | |
| + VI->CmdAlloc += 0x100; | |
| + VI->Commands = (VGM_INIT_CMD*)realloc(VI->Commands, sizeof(VGM_INIT_CMD) * VI->CmdAlloc); | |
| + }*/ | |
| + | |
| + // most commands sent at time 0 come from soundchip_reset(), | |
| + // so I check if the command is "worth" being written | |
| + cm = 0x00; | |
| + switch(VC->ChipType & 0x7F) | |
| + { | |
| + case VGMC_YM2203: | |
| + case VGMC_YM2608: // (not on YM2610 and YM2612) | |
| + if (r >= 0x2D && r <= 0x2F) | |
| + cm = 0x01; // Prescaler Select | |
| + break; | |
| + case VGMC_OKIM6258: | |
| + if (r >= 0x08 && r <= 0x0F) | |
| + cm = 0x01; // OKIM6258 clock change | |
| + break; | |
| + case VGMC_OKIM6295: | |
| + if (r >= 0x08 && r <= 0x0F) | |
| + cm = 0x01; // OKIM6295 clock change and configuration | |
| + break; | |
| + } | |
| + | |
| + if (cm && VI->CmdCount < 0x100) | |
| + { | |
| + VI->Commands[VI->CmdCount] = WriteCmd; | |
| + VI->CmdCount ++; | |
| + } | |
| + return; | |
| + } | |
| + | |
| + if (VI->hFile != nullptr) | |
| + { | |
| + fwrite(WriteCmd.Data, 0x01, WriteCmd.CmdLen, VI->hFile); | |
| + VI->BytesWrt += WriteCmd.CmdLen; | |
| + } | |
| + | |
| + return; | |
| +} | |
| + | |
| +void vgm_write_large_data(uint16_t chip_id, uint8_t type, uint32_t datasize, uint32_t value1, uint32_t value2, const void* data) | |
| +{ | |
| + // datasize - ROM/RAM size | |
| + // value1 - Start Address | |
| + // value2 - Bytes to Write (0 -> write from Start Address to end of ROM/RAM) | |
| + | |
| + VGM_INF* VI; | |
| + VGM_ROM_DATA* VR; | |
| + uint32_t finalsize; | |
| + uint8_t blk_type; | |
| + uint8_t dstart_msb; | |
| + | |
| + if (! LOG_VGM_FILE || chip_id == 0xFFFF) | |
| + return; | |
| + if (VgmChip[chip_id].ChipType == 0xFF) | |
| + return; | |
| + | |
| + VI = &VgmFile[VgmChip[chip_id].VgmID]; | |
| + if (VI->hFile == nullptr) | |
| + return; | |
| + | |
| + blk_type = 0x00; | |
| + dstart_msb = 0x00; | |
| + switch(VgmChip[chip_id].ChipType & 0x7F) // Write the data | |
| + { | |
| + case VGMC_SN76496: | |
| + case VGMC_T6W28: | |
| + break; | |
| + case VGMC_YM2413: | |
| + break; | |
| + case VGMC_YM2612: | |
| + break; | |
| + case VGMC_YM2151: | |
| + break; | |
| + case VGMC_SEGAPCM: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Image | |
| + blk_type = 0x80; // Type: SegaPCM ROM Image | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_RF5C68: | |
| + break; | |
| + case VGMC_YM2203: | |
| + break; | |
| + case VGMC_YM2608: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // PCM ROM Data | |
| + blk_type = 0x81; // Type: YM2608 DELTA-T ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YM2610: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // PCM ROM Data A | |
| + blk_type = 0x82; // Type: YM2610 ADPCM ROM Data | |
| + break; | |
| + case 0x02: // PCM ROM Data B | |
| + blk_type = 0x83; // Type: YM2610 DELTA-T ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YM3812: | |
| + break; | |
| + case VGMC_YM3526: | |
| + break; | |
| + case VGMC_Y8950: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // DELTA-T Memory | |
| + blk_type = 0x88; // Type: Y8950 DELTA-T ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YMF262: | |
| + break; | |
| + case VGMC_YMF278B: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x84; // Type: YMF278B ROM Data | |
| + break; | |
| + case 0x02: // RAM Data | |
| + blk_type = 0x87; // Type: YMF278B RAM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YMF271: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x85; // Type: YMF271 ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_YMZ280B: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x86; // Type: YMZ280B ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_RF5C164: | |
| + break; | |
| + case VGMC_PWM: | |
| + break; | |
| + case VGMC_AY8910: | |
| + break; | |
| + case VGMC_GBSOUND: | |
| + break; | |
| + case VGMC_NESAPU: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // RAM Data | |
| + if (vgm_nes_ram_check(VI, datasize, &value1, &value2, (uint8_t*)data)) | |
| + blk_type = 0xC2; | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_MULTIPCM: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x89; // Type: MultiPCM ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_UPD7759: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x8A; // Type: UPD7759 ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_OKIM6258: | |
| + break; | |
| + case VGMC_OKIM6295: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x8B; // Type: OKIM6295 ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_K051649: | |
| + break; | |
| + case VGMC_K054539: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x8C; // Type: K054539 ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_C6280: | |
| + break; | |
| + case VGMC_C140: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x8D; // Type: C140 ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_K053260: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x8E; // Type: K053260 ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_POKEY: | |
| + break; | |
| + case VGMC_QSOUND: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x8F; // Type: QSound ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_SCSP: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // RAM Data | |
| + vgm_flush_pcm(&VgmChip[chip_id]); | |
| + blk_type = 0xE0; // Type: YMF292/SCSP RAM Data | |
| + break; | |
| + case 0x02: // ROM Data | |
| + blk_type = 0x06; // Type: unused/invalid | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_WSWAN: | |
| + break; | |
| + case VGMC_VSU: | |
| + break; | |
| + case VGMC_SAA1099: | |
| + break; | |
| + case VGMC_ES5503: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // RAM Data | |
| + vgm_flush_pcm(&VgmChip[chip_id]); | |
| + blk_type = 0xE1; // Type: ES5503 RAM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_ES5506: | |
| + // "type" tells us the actual region | |
| + blk_type = 0x90; // Type: ES5506 ROM Data | |
| + dstart_msb = type << 4; | |
| + break; | |
| + case VGMC_X1_010: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x91; // Type: X1-010 ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_C352: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x92; // Type: C352 ROM Data | |
| + break; | |
| + } | |
| + break; | |
| + case VGMC_GA20: | |
| + switch(type) | |
| + { | |
| + case 0x00: | |
| + break; | |
| + case 0x01: // ROM Data | |
| + blk_type = 0x93; // Type: GA20 ROM Data | |
| + break; | |
| + } | |
| + break; | |
| +// case VGMC_OKIM6376: | |
| +// switch(type) | |
| +// { | |
| +// case 0x00: | |
| +// break; | |
| +// case 0x01: // ROM Data | |
| +// //blk_type = 0x8C; // Type: OKIM6376 ROM Data | |
| +// break; | |
| +// } | |
| +// break; | |
| + } | |
| + | |
| + if (! blk_type) | |
| + return; | |
| + | |
| + if (data == nullptr) | |
| + print_info("ROM Data %02X: (0x%X bytes) is NULL!\n", blk_type, datasize); | |
| + | |
| + vgm_write_delay(VgmChip[chip_id].VgmID); | |
| + | |
| + if (! VI->WroteHeader) | |
| + { | |
| + switch(blk_type & 0xC0) | |
| + { | |
| + case 0x80: // ROM Image | |
| + if (VI->DataCount < 0x20) | |
| + { | |
| + VR = &VI->DataBlk[VI->DataCount]; | |
| + VI->DataCount ++; | |
| + | |
| + VR->Type = blk_type; | |
| + VR->dstart_msb = dstart_msb; | |
| + VR->DataSize = datasize | ((VgmChip[chip_id].ChipType & 0x80) << 24); | |
| + VR->Data = data; | |
| + } | |
| + break; | |
| + case 0xC0: // RAM Writes | |
| + break; | |
| + } | |
| + return; | |
| + } | |
| + | |
| + fputc(0x67, VI->hFile); | |
| + fputc(0x66, VI->hFile); | |
| + fputc(blk_type, VI->hFile); | |
| + | |
| + switch(blk_type & 0xC0) | |
| + { | |
| + case 0x00: // Normal Data Block | |
| + if (! value2) | |
| + value2 = datasize - value1; | |
| + if (data == nullptr) | |
| + { | |
| + value1 = 0x00; | |
| + value2 = 0x00; | |
| + } | |
| + finalsize = value2; | |
| + finalsize |= (VgmChip[chip_id].ChipType & 0x80) << 24; | |
| + | |
| + fwrite(&finalsize, 0x04, 0x01, VI->hFile); // Data Block Size | |
| + fwrite(data, 0x01, value2, VI->hFile); | |
| + VI->BytesWrt += 0x07 + (finalsize & 0x7FFFFFFF); | |
| + break; | |
| + case 0x80: // ROM Image | |
| + // Value 1 & 2 are used to write parts of the image (and save space) | |
| + if (! value2) | |
| + value2 = datasize - value1; | |
| + if (data == nullptr) | |
| + { | |
| + value1 = 0x00; | |
| + value2 = 0x00; | |
| + } | |
| + finalsize = 0x08 + value2; | |
| + finalsize |= (VgmChip[chip_id].ChipType & 0x80) << 24; | |
| + value1 |= (dstart_msb << 24); | |
| + | |
| + fwrite(&finalsize, 0x04, 0x01, VI->hFile); // Data Block Size | |
| + fwrite(&datasize, 0x04, 0x01, VI->hFile); // ROM Size | |
| + fwrite(&value1, 0x04, 0x01, VI->hFile); // Data Base Address | |
| + fwrite(data, 0x01, value2, VI->hFile); | |
| + VI->BytesWrt += 0x07 + (finalsize & 0x7FFFFFFF); | |
| + break; | |
| + case 0xC0: // RAM Writes | |
| + if (! value2) | |
| + value2 = datasize - value1; | |
| + if (data == nullptr) | |
| + { | |
| + value1 = 0x00; | |
| + value2 = 0x00; | |
| + } | |
| + | |
| + if (! (blk_type & 0x20)) | |
| + { | |
| + finalsize = 0x02 + value2; | |
| + finalsize |= (VgmChip[chip_id].ChipType & 0x80) << 24; | |
| + | |
| + fwrite(&finalsize, 0x04, 0x01, VI->hFile); // Data Block Size | |
| + fwrite(&value1, 0x02, 0x01, VI->hFile); // Data Address | |
| + } | |
| + else | |
| + { | |
| + finalsize = 0x04 + value2; | |
| + finalsize |= (VgmChip[chip_id].ChipType & 0x80) << 24; | |
| + | |
| + fwrite(&finalsize, 0x04, 0x01, VI->hFile); // Data Block Size | |
| + fwrite(&value1, 0x04, 0x01, VI->hFile); // Data Address | |
| + } | |
| + fwrite(data, 0x01, value2, VI->hFile); | |
| + VI->BytesWrt += 0x07 + (finalsize & 0x7FFFFFFF); | |
| + break; | |
| + } | |
| + | |
| + return; | |
| +} | |
| + | |
| +static uint8_t vgm_nes_ram_check(VGM_INF* VI, uint32_t datasize, uint32_t* value1, uint32_t* value2, const uint8_t* data) | |
| +{ | |
| + uint16_t CurPos; | |
| + uint16_t DataStart; | |
| + uint16_t DataEnd; | |
| + | |
| + if (VI->NesMemEmpty) | |
| + { | |
| + VI->NesMemEmpty = 0x00; | |
| + memcpy(VI->NesMem, data, 0x4000); | |
| + | |
| + *value1 = 0xC000; | |
| + *value2 = 0x4000; | |
| + return 0x02; | |
| + } | |
| + | |
| + DataStart = *value1 & 0x3FFF; | |
| + if (! *value2) | |
| + DataEnd = 0x4000; | |
| + else | |
| + DataEnd = DataStart + *value2; | |
| + if (DataEnd > 0x4000) | |
| + DataEnd = 0x4000; | |
| + | |
| + for (CurPos = DataStart; CurPos < DataEnd; CurPos ++) | |
| + { | |
| + if (VI->NesMem[CurPos] != data[CurPos]) | |
| + { | |
| + memcpy(VI->NesMem + DataStart, data + DataStart, DataEnd - DataStart); | |
| + return 0x01; | |
| + } | |
| + } | |
| + | |
| + return 0x00; | |
| +} | |
| + | |
| +uint16_t vgm_get_chip_idx(uint8_t chip_type, uint8_t Num) | |
| +{ | |
| + // This is a small helper-function, that allows drivers/machines to | |
| + // make writes for their chips. | |
| + // e.g. NeoGeo CD rewrites the YM2610's PCM-RAM after changes | |
| + uint16_t curchip; | |
| + | |
| + for (curchip = 0x00; curchip < MAX_VGM_CHIPS; curchip ++) | |
| + { | |
| + if ((VgmChip[curchip].ChipType & 0x7F) == chip_type && | |
| + (VgmChip[curchip].ChipType >> 7) == Num) | |
| + return curchip; | |
| + } | |
| + | |
| + return 0xFFFF; | |
| +} | |
| + | |
| +static void vgm_flush_pcm(VGM_CHIP* VC) | |
| +{ | |
| + VGM_INF* VI; | |
| + VGM_PCMCACHE* VPC; | |
| + uint8_t SingleWrt; | |
| + uint8_t CmdType; | |
| + uint32_t finalsize; | |
| + | |
| + VI = &VgmFile[VC->VgmID]; | |
| + if (! LOG_VGM_FILE || VI->hFile == nullptr) | |
| + return; | |
| + | |
| + VPC = VC->PCMCache; | |
| + if (VPC == nullptr || VPC->Start == 0xFFFFFFFF || ! VPC->Pos) | |
| + return; | |
| + //print_info("Flushing PCM Data: Chip %02X, Addr %06X, Size %06X\n", | |
| + // VC->ChipType, VPC->Start, VPC->Pos); | |
| + | |
| + if (VPC->Pos == 0x01 && (VC->ChipType & 0x7F) != VGMC_SCSP) | |
| + SingleWrt = 0x01; | |
| + else | |
| + SingleWrt = 0x00; | |
| + | |
| + if (SingleWrt) | |
| + { | |
| + switch(VC->ChipType & 0x7F) | |
| + { | |
| + case VGMC_RF5C68: | |
| + CmdType = 0xC1; | |
| + break; | |
| + case VGMC_RF5C164: | |
| + CmdType = 0xC2; | |
| + break; | |
| + default: | |
| + CmdType = 0xFF; | |
| + break; | |
| + } | |
| + } | |
| + else | |
| + { | |
| + switch(VC->ChipType & 0x7F) | |
| + { | |
| + case VGMC_RF5C68: | |
| + CmdType = 0xC0; | |
| + break; | |
| + case VGMC_RF5C164: | |
| + CmdType = 0xC1; | |
| + break; | |
| + case VGMC_SCSP: | |
| + CmdType = 0xE0; | |
| + break; | |
| + case VGMC_ES5503: | |
| + CmdType = 0xE1; | |
| + break; | |
| + default: | |
| + CmdType = 0xFF; | |
| + break; | |
| + } | |
| + } | |
| + | |
| + if (SingleWrt) | |
| + { | |
| + // it would be a waste of space to write a data block for 1 byte of data | |
| + fputc(CmdType, VI->hFile); // Write Memory | |
| + fputc((VPC->Start >> 0) & 0xFF, VI->hFile); // offset low | |
| + fputc((VPC->Start >> 8) & 0xFF, VI->hFile); // offset high | |
| + fputc(VPC->CacheData[0x00], VI->hFile); // Data | |
| + VI->BytesWrt += 0x04; | |
| + } | |
| + else | |
| + { | |
| + // calling vgm_write_large_data doesn't work if vgm_flush_pcm is | |
| + // called from vgm_write_delay | |
| + fputc(0x67, VI->hFile); | |
| + fputc(0x66, VI->hFile); | |
| + fputc(CmdType, VI->hFile); | |
| + if (! (CmdType & 0x20)) | |
| + { | |
| + finalsize = 0x02 + VPC->Pos; | |
| + finalsize |= (VC->ChipType & 0x80) << 24; | |
| + | |
| + fwrite(&finalsize, 0x04, 0x01, VI->hFile); // Data Block Size | |
| + fwrite(&VPC->Start, 0x02, 0x01, VI->hFile); // Data Address | |
| + } | |
| + else | |
| + { | |
| + finalsize = 0x04 + VPC->Pos; | |
| + finalsize |= (VC->ChipType & 0x80) << 24; | |
| + | |
| + fwrite(&finalsize, 0x04, 0x01, VI->hFile); // Data Block Size | |
| + fwrite(&VPC->Start, 0x04, 0x01, VI->hFile); // Data Address | |
| + } | |
| + fwrite(VPC->CacheData, 0x01, VPC->Pos, VI->hFile); | |
| + VI->BytesWrt += 0x07 + (finalsize & 0x7FFFFFFF); | |
| + } | |
| + | |
| + VPC->Start = 0xFFFFFFFF; | |
| + | |
| + return; | |
| +} | |
| + | |
| +void vgm_change_rom_data(uint32_t oldsize, const void* olddata, uint32_t newsize, const void* newdata) | |
| +{ | |
| + VGM_INF* VI; | |
| + VGM_ROM_DATA* VR; | |
| + uint16_t curvgm; | |
| + uint8_t curdblk; | |
| + | |
| + print_info("Changing ROM Data (address %p, 0x%X bytes -> address %p, 0x%X bytes) ...\n", | |
| + olddata, oldsize, newdata, newsize); | |
| + for (curvgm = 0x00; curvgm < MAX_VGM_FILES; curvgm ++) | |
| + { | |
| + VI = &VgmFile[curvgm]; | |
| + if (VI->hFile == nullptr) | |
| + continue; | |
| + | |
| + for (curdblk = 0x00; curdblk < VI->DataCount; curdblk ++) | |
| + { | |
| + VR = &VI->DataBlk[curdblk]; | |
| + if ((VR->DataSize & 0x7FFFFFFF) == oldsize && VR->Data == olddata) | |
| + { | |
| + print_info("Match found in VGM %02hX, Block Type %02X\n", curvgm, VR->Type); | |
| + VR->DataSize &= 0x80000000; // keep "chip select" bit | |
| + VR->DataSize |= newsize; | |
| + VR->Data = newdata; | |
| + } | |
| + } | |
| + } | |
| + | |
| + return; | |
| +} | |
| + | |
| +void vgm_dump_sample_rom(uint16_t chip_id, uint8_t type, memory_region* region) | |
| +{ | |
| + if (region == nullptr) | |
| + return; | |
| + | |
| + print_info("VGM - Dumping ROM-Region %s: size 0x%X, ptr %p\n", region->name(), region->bytes(), region->base()); | |
| + vgm_write_large_data(chip_id, type, region->bytes(), 0x00, 0x00, region->base()); | |
| + | |
| + return; | |
| +} | |
| + | |
| +void vgm_dump_sample_rom(uint16_t chip_id, uint8_t type, address_space& space) | |
| +{ | |
| + uint32_t dataSize = space.addrmask() + 1; | |
| + const void* dataPtr = space.get_read_ptr(0); | |
| + | |
| + print_info("VGM - Dumping ROM-Space %s: size 0x%X, space-ptr %p\n", space.name(), dataSize, dataPtr); | |
| + vgm_write_large_data(chip_id, type, dataSize, 0x00, 0x00, dataPtr); | |
| + | |
| + return; | |
| +} | |
| diff --git a/src/lib/util/vgmwrite.h b/src/lib/util/vgmwrite.h | |
| new file mode 100644 | |
| index 00000000000..3ea23ca2583 | |
| --- /dev/null | |
| +++ b/src/lib/util/vgmwrite.h | |
| @@ -0,0 +1,66 @@ | |
| +#pragma once | |
| + | |
| +#ifndef __VGMWRITE_H__ | |
| +#define __VGMWRITE_H__ | |
| + | |
| +void vgm_start(running_machine &machine); | |
| +void vgm_stop(void); | |
| +uint16_t vgm_open(uint8_t chip_type, int clock); | |
| +void vgm_header_set(uint16_t chip_id, uint8_t attr, uint32_t data); | |
| +void vgm_write(uint16_t chip_id, uint8_t port, uint16_t r, uint8_t v); | |
| +void vgm_write_large_data(uint16_t chip_id, uint8_t type, uint32_t datasize, uint32_t value1, uint32_t value2, const void* data); | |
| +uint16_t vgm_get_chip_idx(uint8_t chip_type, uint8_t Num); | |
| +void vgm_change_rom_data(uint32_t oldsize, const void* olddata, uint32_t newsize, const void* newdata); | |
| +void vgm_dump_sample_rom(uint16_t chip_id, uint8_t type, memory_region* region); | |
| +void vgm_dump_sample_rom(uint16_t chip_id, uint8_t type, address_space& space); | |
| + | |
| +// VGM Chip Constants | |
| +// v1.00 | |
| +#define VGMC_SN76496 0x00 | |
| +#define VGMC_YM2413 0x01 | |
| +#define VGMC_YM2612 0x02 | |
| +#define VGMC_YM2151 0x03 | |
| +// v1.51 | |
| +#define VGMC_SEGAPCM 0x04 | |
| +#define VGMC_RF5C68 0x05 | |
| +#define VGMC_YM2203 0x06 | |
| +#define VGMC_YM2608 0x07 | |
| +#define VGMC_YM2610 0x08 | |
| +#define VGMC_YM3812 0x09 | |
| +#define VGMC_YM3526 0x0A | |
| +#define VGMC_Y8950 0x0B | |
| +#define VGMC_YMF262 0x0C | |
| +#define VGMC_YMF278B 0x0D | |
| +#define VGMC_YMF271 0x0E | |
| +#define VGMC_YMZ280B 0x0F | |
| +#define VGMC_T6W28 0x7F // note: emulated via 2xSN76496 | |
| +#define VGMC_RF5C164 0x10 | |
| +#define VGMC_PWM 0x11 | |
| +#define VGMC_AY8910 0x12 | |
| +// v1.61 | |
| +#define VGMC_GBSOUND 0x13 | |
| +#define VGMC_NESAPU 0x14 | |
| +#define VGMC_MULTIPCM 0x15 | |
| +#define VGMC_UPD7759 0x16 | |
| +#define VGMC_OKIM6258 0x17 | |
| +#define VGMC_OKIM6295 0x18 | |
| +#define VGMC_K051649 0x19 | |
| +#define VGMC_K054539 0x1A | |
| +#define VGMC_C6280 0x1B | |
| +#define VGMC_C140 0x1C | |
| +#define VGMC_K053260 0x1D | |
| +#define VGMC_POKEY 0x1E | |
| +#define VGMC_QSOUND 0x1F | |
| +// v1.71 | |
| +#define VGMC_SCSP 0x20 | |
| +#define VGMC_WSWAN 0x21 | |
| +#define VGMC_VSU 0x22 | |
| +#define VGMC_SAA1099 0x23 | |
| +#define VGMC_ES5503 0x24 | |
| +#define VGMC_ES5506 0x25 | |
| +#define VGMC_X1_010 0x26 | |
| +#define VGMC_C352 0x27 | |
| +#define VGMC_GA20 0x28 | |
| + | |
| +//#define VGMC_OKIM6376 0xFF | |
| +#endif /* __VGMWRITE_H__ */ | |
| -- | |
| 2.25.1 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment