diff options
Diffstat (limited to 'win32/main.c')
-rw-r--r-- | win32/main.c | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/win32/main.c b/win32/main.c new file mode 100644 index 0000000..dd60702 --- /dev/null +++ b/win32/main.c @@ -0,0 +1,484 @@ +#include <stddef.h> +#include <stdbool.h> +#include <windows.h> +#include <shlwapi.h> +#include <windowsx.h> +#include <commctrl.h> + +#include "fmdriver/fmdriver_fmp.h" +#include "libopna/opna.h" +#include "libopna/opnatimer.h" +#include "fmdsp/fmdsp.h" +#include "soundout.h" + +enum { + ID_OPENFILE = 0x10, + TIMER_FMDSP = 1, +}; + +enum { + SRATE = 55467, + SECTLEN = 4096, + PPZ8MIX = 0xa000, + FONT_ROM_SIZE = 0x84000, + FONT_ROM_FILESIZE = 0x46800, +}; + +#define ENABLE_WM_DROPFILES +// #define ENABLE_IDROPTARGET + +static struct { + HINSTANCE hinst; + HANDLE heap; + struct sound_state *sound; + struct opna opna; + struct opna_timer opna_timer; + struct ppz8 ppz8; + struct fmdriver_work work; + struct driver_fmp *fmp; + struct fmdsp fmdsp; + uint8_t vram[PC98_W*PC98_H]; + uint8_t font[FONT_ROM_SIZE]; + void *drum_rom; + uint8_t opna_adpcm_ram[OPNA_ADPCM_RAM_SIZE]; + void *ppz8_buf; +} g; + + +static void opna_int_cb(void *userptr) { + struct fmdriver_work *work = (struct fmdriver_work *)userptr; + work->driver_opna_interrupt(work); +} + +static void opna_mix_cb(void *userptr, int16_t *buf, unsigned samples) { + struct ppz8 *ppz8 = (struct ppz8 *)userptr; + ppz8_mix(ppz8, buf, samples); +} + +static void opna_writereg_libopna(struct fmdriver_work *work, unsigned addr, unsigned data) { + struct opna_timer *timer = (struct opna_timer *)work->opna; + opna_timer_writereg(timer, addr, data); +} + +static uint8_t opna_status_libopna(struct fmdriver_work *work, bool a1) { + struct opna_timer *timer = (struct opna_timer *)work->opna; + uint8_t status = opna_timer_status(timer); + if (!a1) { + status &= 0x83; + } + return status; +} + +static void sound_cb(void *p, int16_t *buf, unsigned frames) { + struct opna_timer *timer = (struct opna_timer *)p; + ZeroMemory(buf, sizeof(int16_t)*frames*2); + opna_timer_mix(timer, buf, frames); +} + +static void on_timer(HWND hwnd, UINT id) { + if (id == TIMER_FMDSP) { + InvalidateRect(hwnd, 0, FALSE); + } +} + +static HANDLE pvisearch(const wchar_t *filename, const char *pviname_a) { + enum { + WPVINAMELEN = 8*2+1+3+1, + }; + wchar_t pviname[WPVINAMELEN]; + wchar_t pvipath[PATH_MAX]; + if (MultiByteToWideChar(932, MB_ERR_INVALID_CHARS, + pviname_a, -1, pviname, WPVINAMELEN) == 0) { + return INVALID_HANDLE_VALUE; + } + lstrcat(pviname, L".PVI"); + if (lstrlen(filename) >= PATH_MAX) return INVALID_HANDLE_VALUE; + lstrcpy(pvipath, filename); + PathRemoveFileSpec(pvipath); + if (lstrlen(pvipath) + lstrlen(pviname) + 1 >= PATH_MAX) { + return INVALID_HANDLE_VALUE; + } + lstrcat(pvipath, L"\\"); + lstrcat(pvipath, pviname); + return CreateFile(pvipath, GENERIC_READ, + 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); +} + +static void loadrom(void) { + const wchar_t *path = L"ym2608_adpcm_rom.bin"; + wchar_t exepath[MAX_PATH]; + if (GetModuleFileName(0, exepath, MAX_PATH)) { + PathRemoveFileSpec(exepath); + if ((lstrlen(exepath) + lstrlen(path) + 1) < MAX_PATH) { + lstrcat(exepath, L"\\"); + lstrcat(exepath, path); + path = exepath; + } + } + HANDLE file = CreateFile(path, GENERIC_READ, + 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (file == INVALID_HANDLE_VALUE) goto err; + DWORD filesize = GetFileSize(file, 0); + if (filesize != OPNA_ROM_SIZE) goto err_file; + void *buf = HeapAlloc(g.heap, 0, OPNA_ROM_SIZE); + if (!buf) goto err_file; + DWORD readbytes; + if (!ReadFile(file, buf, OPNA_ROM_SIZE, &readbytes, 0) + || readbytes != OPNA_ROM_SIZE) goto err_buf; + CloseHandle(file); + g.drum_rom = buf; + return; +err_buf: + HeapFree(g.heap, 0, buf); +err_file: + CloseHandle(file); +err: + return; +} + +static bool loadpvi(struct fmdriver_work *work, + struct driver_fmp *fmp, + const wchar_t *filename) { + if (!fmp->pvi_name[0]) return true; + HANDLE pvifile = pvisearch(filename, fmp->pvi_name); + if (pvifile == INVALID_HANDLE_VALUE) goto err; + DWORD filesize = GetFileSize(pvifile, 0); + if (filesize == INVALID_FILE_SIZE) goto err_file; + void *data = HeapAlloc(g.heap, 0, filesize); + if (!data) goto err_file; + DWORD readbytes; + if (!ReadFile(pvifile, data, filesize, &readbytes, 0) + || readbytes != filesize) goto err_data; + if (!fmp_adpcm_load(work, data, filesize)) goto err_data; + HeapFree(g.heap, 0, data); + CloseHandle(pvifile); + return true; +err_data: + HeapFree(g.heap, 0, data); +err_file: + CloseHandle(pvifile); +err: + return false; +} + +static bool loadppzpvi(struct fmdriver_work *work, + struct driver_fmp *fmp, + const wchar_t *filename) { + if (!fmp->ppz_name[0]) return true; + HANDLE pvifile = pvisearch(filename, fmp->ppz_name); + if (pvifile == INVALID_HANDLE_VALUE) goto err; + DWORD filesize = GetFileSize(pvifile, 0); + if (filesize == INVALID_FILE_SIZE) goto err_file; + void *data = HeapAlloc(g.heap, 0, filesize); + if (!data) goto err_file; + void *buf = HeapAlloc(g.heap, 0, ppz8_pvi_decodebuf_samples(filesize) * sizeof(int16_t)); + if (!buf) goto err_data; + DWORD readbytes; + if (!ReadFile(pvifile, data, filesize, &readbytes, 0) + || readbytes != filesize) goto err_buf; + if (!ppz8_pvi_load(work->ppz8, 0, data, filesize, buf)) goto err_buf; + if (g.ppz8_buf) HeapFree(g.heap, 0, g.ppz8_buf); + g.ppz8_buf = buf; + HeapFree(g.heap, 0, data); + CloseHandle(pvifile); + return true; +err_buf: + HeapFree(g.heap, 0, buf); +err_data: + HeapFree(g.heap, 0, data); +err_file: + CloseHandle(pvifile); +err: + return false; +} + +static void openfile(HWND hwnd, const wchar_t *path) { + HANDLE file = CreateFile(path, GENERIC_READ, + 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (file == INVALID_HANDLE_VALUE) { + MessageBox(hwnd, L"Cannot open file", L"Error", MB_ICONSTOP); + return; + } + LARGE_INTEGER li; + if (!GetFileSizeEx(file, &li)) { + MessageBox(hwnd, L"Cannot open file", L"Error", MB_ICONSTOP); + goto err_file; + } + if (li.QuadPart > 0xffff) { + MessageBox(hwnd, L"Invalid File (Filesize too large)", L"Error", MB_ICONSTOP); + goto err_file; + } + void *fmpdata = HeapAlloc(g.heap, 0, li.QuadPart); + if (!fmpdata) { + MessageBox(hwnd, L"Cannot allocate memory for file", L"Error", MB_ICONSTOP); + goto err_file; + } + DWORD readbytes; + if (!ReadFile(file, fmpdata, li.QuadPart, &readbytes, 0) || readbytes != li.QuadPart) { + MessageBox(hwnd, L"Cannot read file", L"Error", MB_ICONSTOP); + goto err_fmpdata; + } + struct driver_fmp *fmp = HeapAlloc(g.heap, HEAP_ZERO_MEMORY, sizeof(struct driver_fmp)); + if (!fmp) { + MessageBox(hwnd, L"Cannot allocate memory for fmp", L"Error", MB_ICONSTOP); + goto err_fmpdata; + } + if (!fmp_load(fmp, fmpdata, li.QuadPart)) { + MessageBox(hwnd, L"Invalid File (not FMP data)", L"Error", MB_ICONSTOP); + goto err_fmp; + } + if (g.sound) { + g.sound->pause(g.sound, 1); + } + if (g.fmp) HeapFree(g.heap, 0, g.fmp); + g.fmp = fmp; + opna_reset(&g.opna); + if (g.drum_rom) opna_drum_set_rom(&g.opna.drum, g.drum_rom); + opna_adpcm_set_ram_256k(&g.opna.adpcm, g.opna_adpcm_ram); + opna_timer_reset(&g.opna_timer, &g.opna); + ppz8_init(&g.ppz8, SRATE, PPZ8MIX); + ZeroMemory(&g.work, sizeof(g.work)); + g.work.opna_writereg = opna_writereg_libopna; + g.work.opna_status = opna_status_libopna; + g.work.opna = &g.opna_timer; + g.work.ppz8 = &g.ppz8; + g.work.ppz8_functbl = &ppz8_functbl; + opna_timer_set_int_callback(&g.opna_timer, opna_int_cb, &g.work); + opna_timer_set_mix_callback(&g.opna_timer, opna_mix_cb, &g.ppz8); + fmp_init(&g.work, g.fmp); + loadpvi(&g.work, g.fmp, path); + loadppzpvi(&g.work, g.fmp, path); + if (!g.sound) { + g.sound = sound_init(hwnd, SRATE, SECTLEN, + sound_cb, &g.opna_timer); + } + fmdsp_vram_init(&g.fmdsp, &g.work, g.font, g.vram); + if (!g.sound) goto err_fmp; + g.sound->pause(g.sound, 0); + CloseHandle(file); + return; +err_fmp: + HeapFree(g.heap, 0, fmp); +err_fmpdata: + HeapFree(g.heap, 0, fmpdata); +err_file: + CloseHandle(file); +} + +static void openfiledialog(HWND hwnd) { + wchar_t path[MAX_PATH] = {0}; + OPENFILENAME ofn = {0}; + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = hwnd; + ofn.hInstance = g.hinst; + ofn.lpstrFilter = L"FMP files (*.opi;*.ovi;*.ozi;*.m26;*.m86)\0" + "*.opi;*.ovi;*.ozi;*.m26;*.m86\0" + "All Files (*.*)\0" + "*.*\0\0"; + ofn.lpstrFile = path; + ofn.nMaxFile = sizeof(path)/sizeof(path[0]); + ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + if (!GetOpenFileName(&ofn)) return; + openfile(hwnd, path); +} + +#ifdef ENABLE_IDROPTARGET +struct fmplayer_droptarget { + IDropTarget idt; + ULONG refcnt; +}; + +static HRESULT fmplayer_droptarget_addref( + IDropTarget *ptr) { + struct fmplayer_droptarget *this_ = (struct fmplayer_droptarget *)ptr; + return ++this_->refcnt; +} + +static HRESULT fmplayer_droptarget_release( + IDropTarget *ptr) { + struct fmplayer_droptarget *this_ = (struct fmplayer_droptarget *)ptr; + ULONG refcnt = --this_->refcnt; + if (!refcnt) HeapFree(g.heap, 0, this_); + return refcnt; +} + +static HRESULT fmplayer_droptarget_queryinterface( + IDropTarget *ptr, GUID *iid, void **obj) { + struct fmplayer_droptarget *this_ = (struct fmplayer_droptarget *)ptr; + if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IDropTarget)) { + *obj = ptr; + fmplayer_droptarget_addref(ptr); + return S_OK; + } else { + *obj = 0; + return E_NOINTERFACE; + } +} + +struct fmplayer_droptarget *fmplayer_droptarget(void) { + struct fmplayer_droptarget *fdt = HeapAlloc(g.heap, HEAP_ZERO_MEMORY, sizeof(*fdt)); + if (!fdt) return 0; + static bool vtblinit = false; + static IDropTargetVtbl vtbl; + if (!vtblinit) { + vtbl.QueryInterface = fmplayer_droptarget_queryinterface; + vtbl.AddRef = fmplayer_droptarget_addref; + vtbl.Release = fmplayer_droptarget_release; + vtblinit = true; + } + fdt->idt.lpVtbl = &vtbl; + fdt->refcnt = 1; +} +#endif // ENABLE_IDROPTARGET + +#ifdef ENABLE_WM_DROPFILES +static void on_dropfiles(HWND hwnd, HDROP hdrop) { + wchar_t path[MAX_PATH] = {0}; + if (DragQueryFile(hdrop, 0, path, sizeof(path)/sizeof(path[0]))) { + openfile(hwnd, path); + } + DragFinish(hdrop); +} +#endif // ENABLE_WM_DROPFILES + +static bool on_create(HWND hwnd, CREATESTRUCT *cs) { + (void)cs; + HWND button = CreateWindowEx( + 0, + L"BUTTON", + L"&Open", + WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 10, 10, + 40, 25, + hwnd, (HMENU)ID_OPENFILE, g.hinst, 0 + ); + NONCLIENTMETRICS ncm; + ncm.cbSize = sizeof(ncm); + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0); + HFONT font = CreateFontIndirect(&ncm.lfMessageFont); + SetWindowFont(button, font, TRUE); + loadrom(); + fmdsp_init(&g.fmdsp); + fmdsp_vram_init(&g.fmdsp, &g.work, g.font, g.vram); + SetTimer(hwnd, TIMER_FMDSP, 50, 0); +#ifdef ENABLE_WM_DROPFILES + DragAcceptFiles(hwnd, TRUE); +#endif + return true; +} + +static void on_command(HWND hwnd, int id, HWND hwnd_c, UINT code) { + (void)code; + (void)hwnd_c; + switch (id) { + case ID_OPENFILE: + openfiledialog(hwnd); + break; + } +} + +static void on_destroy(HWND hwnd) { + (void)hwnd; + if (g.sound) g.sound->delete(g.sound); + if (g.fmp) HeapFree(g.heap, 0, g.fmp); + if (g.drum_rom) HeapFree(g.heap, 0, g.drum_rom); + if (g.ppz8_buf) HeapFree(g.heap, 0, g.ppz8_buf); + PostQuitMessage(0); +} + +static void on_paint(HWND hwnd) { + fmdsp_update(&g.fmdsp, &g.work, g.vram); + PAINTSTRUCT ps; + static BITMAPINFO *bi = 0; + if (!bi) { + bi = HeapAlloc(g.heap, HEAP_ZERO_MEMORY, + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*FMDSP_PALETTE_COLORS); + if (!bi) return; + bi->bmiHeader.biSize = sizeof(bi->bmiHeader); + bi->bmiHeader.biWidth = PC98_W; + bi->bmiHeader.biHeight = -PC98_H; + bi->bmiHeader.biPlanes = 1; + bi->bmiHeader.biBitCount = 8; + bi->bmiHeader.biCompression = BI_RGB; + bi->bmiHeader.biClrUsed = FMDSP_PALETTE_COLORS; + } + for (int p = 0; p < FMDSP_PALETTE_COLORS; p++) { + bi->bmiColors[p].rgbRed = g.fmdsp.palette[p*3+0]; + bi->bmiColors[p].rgbGreen = g.fmdsp.palette[p*3+1]; + bi->bmiColors[p].rgbBlue = g.fmdsp.palette[p*3+2]; + } + HDC dc = BeginPaint(hwnd, &ps); + HDC mdc = CreateCompatibleDC(dc); + HBITMAP bitmap = CreateDIBitmap( + dc, + &bi->bmiHeader, CBM_INIT, + g.vram, + bi, DIB_RGB_COLORS); + SelectObject(mdc, bitmap); + BitBlt(dc, 0, 80, 640, 400, mdc, 0, 0, SRCCOPY); + DeleteDC(mdc); + DeleteObject(bitmap); + EndPaint(hwnd, &ps); +} + +static LRESULT CALLBACK wndproc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam +) { + switch (msg) { + HANDLE_MSG(hwnd, WM_DESTROY, on_destroy); + HANDLE_MSG(hwnd, WM_CREATE, on_create); + HANDLE_MSG(hwnd, WM_COMMAND, on_command); + HANDLE_MSG(hwnd, WM_PAINT, on_paint); + HANDLE_MSG(hwnd, WM_TIMER, on_timer); +#ifdef ENABLE_WM_DROPFILES + HANDLE_MSG(hwnd, WM_DROPFILES, on_dropfiles); +#endif // ENABLE_WM_DROPFILES + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +static ATOM register_class(HINSTANCE hinst) { + WNDCLASS wc = {0}; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = wndproc; + wc.hInstance = hinst; + wc.hIcon = LoadIcon(g.hinst, MAKEINTRESOURCE(1)); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); + wc.lpszClassName = L"TWinc"; + return RegisterClass(&wc); +} + +int CALLBACK wWinMain(HINSTANCE hinst, HINSTANCE hpinst, + wchar_t *cmdline, int cmdshow) { + (void)hpinst; + (void)cmdline; + g.hinst = hinst; + g.heap = GetProcessHeap(); + ATOM wcatom = register_class(g.hinst); + DWORD style = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + DWORD exStyle = 0; + RECT wr; + wr.left = 0; + wr.right = 640; + wr.top = 0; + wr.bottom = 480; + AdjustWindowRectEx(&wr, style, 0, exStyle); + HWND hwnd = CreateWindowEx( + exStyle, + (wchar_t*)((uintptr_t)wcatom), L"FMPlayer/Win32", + style, + CW_USEDEFAULT, CW_USEDEFAULT, + wr.right-wr.left, wr.bottom-wr.top, + 0, 0, g.hinst, 0 + ); + ShowWindow(hwnd, cmdshow); + + MSG msg = {0}; + while (GetMessage(&msg, 0, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return msg.wParam; +} + |