changeset 79:8f90d5addda9 v2.0

*: refactor... basically everything! The Win32 GUI version is now unicode-friendly. HOWEVER, ANSI still very much works. you can configure which version to use through `-DUNICODE=0/1` in CFLAGS. the CLI is also friendlier and uses a more sane interface as well. note: the command line flags (which were optional before) are now required. Unicode filenames will not work on Windows because Windows sucks.
author Paper <paper@paper.us.eu.org>
date Wed, 20 Mar 2024 17:06:26 -0400
parents fae1d67d8cfd
children 719570851563
files Makefile include/common.h src/common.c src/gui.c src/main.c
diffstat 5 files changed, 532 insertions(+), 310 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sun Oct 01 03:03:29 2023 -0400
+++ b/Makefile	Wed Mar 20 17:06:26 2024 -0400
@@ -1,21 +1,16 @@
-CFLAGS=-Wall -O2 -fstack-protector -fdata-sections -ffunction-sections
-LDFLAGS=
+_CFLAGS = -Wall -O2 -Iinclude $(CFLAGS)
+_LDFLAGS = $(LDFLAGS)
 
-ifeq ($(shell uname -s),Darwin)  # macOS is the odd one...
-	LDFLAGS+=-Wl,-dead_strip
-else
-	LDFLAGS+=-Wl,--gc-sections
-endif
+.c.o:
+	$(CC) -c $(_CFLAGS) $< -o $@
 
-src/%.o : src/%.c
-	$(CC) -c $(CFLAGS) $< -o $@
-
+# this is a unix-like version, barely functional on windows.
 msvpvf: src/main.o src/common.o
-	$(CC) $(CFLAGS) -o $@ $^  $(LDFLAGS)
+	$(CC) $(_CFLAGS) -o $@ $^  $(_LDFLAGS)
 
-# GUI is windows-only, please use cross-compiler!
+# windows-specific
 gui: src/gui.o src/common.o
-	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) -mwindows -lshlwapi
+	$(CC) -o $@ $^ -mwindows -lole32 -lshlwapi -luuid -lmsvcrt $(_LDFLAGS)
 
 clean:
-	rm -f src/*.o *.exe msvpvf gui
+	rm -f src/main.o src/common.o src/gui.o msvpvf gui
--- a/include/common.h	Sun Oct 01 03:03:29 2023 -0400
+++ b/include/common.h	Wed Mar 20 17:06:26 2024 -0400
@@ -1,2 +1,21 @@
-void set_data(unsigned char* magic, int version, FILE* target);
-int copy_file(char* source_file, char* target_file);
+#ifndef msvpvf_common_h
+#define msvpvf_common_h
+
+#include <stdio.h>
+#include <stdint.h>
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(x) \
+	(sizeof(x)/sizeof((x)[0]))
+#endif
+
+enum types {
+	TYPES_UNKNOWN = 0,
+	TYPES_VF,
+	TYPES_VEG
+};
+
+int set_file_information(FILE* target, uint8_t version, enum types type);
+int get_file_information(FILE* input, uint8_t* version, enum types* type);
+
+#endif /* msvpvf_common_h */
--- a/src/common.c	Sun Oct 01 03:03:29 2023 -0400
+++ b/src/common.c	Wed Mar 20 17:06:26 2024 -0400
@@ -1,39 +1,51 @@
-#include <stdio.h>
-#include <stdint.h>
+#include "common.h"
+
+#include <string.h>
+
+/* hardcoded magic values; stored at 0x18... */
+static const uint8_t magic_veg[16] = {0xEF, 0x29, 0xC4, 0x46, 0x4A, 0x90, 0xD2, 0x11, 0x87, 0x22, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A};
+static const uint8_t magic_vf[16]  = {0xF6, 0x1B, 0x3C, 0x53, 0x35, 0xD6, 0xF3, 0x43, 0x8A, 0x90, 0x64, 0xB8, 0x87, 0x23, 0x1F, 0x7F};
+
+int set_file_information(FILE* target, uint8_t version, enum types type) {
+	const uint8_t* magic = (type == TYPES_VF) ? magic_vf : magic_veg;
 
-void set_data(unsigned char* magic, uint16_t version, FILE* target) {
-	int i;
-	fseek(target, 0x46, SEEK_SET);
-	fputc(version, target);
-	for (i=0; i<=sizeof(*magic); ++i) {
-		fseek(target, 0x18+i, SEEK_SET);
-		fputc(magic[i], target);
-	}
+	if (fseek(target, 0x46, SEEK_SET))
+		return -1;
+
+	if (fputc(version, target) == EOF)
+		return -1;
+
+	if (fseek(target, 0x18, SEEK_SET))
+		return -1;
+
+	if (fwrite(magic, sizeof(*magic), 16, target) < 16)
+		return -1;
+
+	return 0;
 }
 
-int copy_file(char* source_file, char* target_file) {
-	char ch[4096];
-	FILE *source, *target;
+int get_file_information(FILE* input, uint8_t* version, enum types* type) {
+	uint8_t magic[16] = {0};
 
-	source = fopen(source_file, "rb");
+	if (fseek(input, 0x46, SEEK_SET))
+		return -1;
+
+	*version = fgetc(input);
+
+	if (fseek(input, 0x18, SEEK_SET))
+		return -1;
 
-	if (source == NULL)
-		return 1;
+	/* read the WHOLE magic, then memcmp */
+	if (fread(magic, sizeof(*magic), ARRAYSIZE(magic), input) < ARRAYSIZE(magic))
+		return -1;
 
-	target = fopen(target_file, "wb");
-
-	if (target == NULL) {
-		fclose(source);
-		return 1;
+	if (!memcmp(magic, magic_veg, ARRAYSIZE(magic))) {
+		*type = TYPES_VEG;
+	} else if (!memcmp(magic, magic_vf, ARRAYSIZE(magic))) {
+		*type = TYPES_VF;
+	} else {
+		*type = TYPES_UNKNOWN;
 	}
 
-	while (!feof(source)) {
-		size_t b = fread(ch, 1, sizeof(ch), source);
-		if (b)
-			fwrite(ch, 1, b, target);
-	}
-
-	fclose(target);
-	fclose(source);
 	return 0;
 }
--- a/src/gui.c	Sun Oct 01 03:03:29 2023 -0400
+++ b/src/gui.c	Wed Mar 20 17:06:26 2024 -0400
@@ -1,8 +1,6 @@
 /**
  * msvpvf GUI for Windows 
- * Copyright (c) Paper 2022
- *
- * View this file with 4-tab spacing; if you don't it will be a formatting nightmare.
+ * Copyright (c) Paper 2022-2024
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -22,203 +20,375 @@
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  * SOFTWARE.
 **/
-#include <stdio.h>
-#include <windows.h>
-#include <shellapi.h>
+
+/* change these macros to configure the window size */
+#define WINDOW_WIDTH  225
+#define WINDOW_HEIGHT 200
+
+/* mingw */
+#ifdef UNICODE
+#define NTDDI_VERSION 0x06000000
+#define _WIN32_WINNT  0x0600
+#else
+#define _WIN32_WINNT  0x0400
+#endif
+
+#include <windef.h>
+#include <winbase.h>
 #include <shlwapi.h>
+#include <shobjidl.h>
+
 #include <stdint.h>
-#include <stdbool.h>
-#include <commdlg.h>
-#include "../include/common.h"
-#define _WIN32_WINNT 0x0400
-#define ARRAYSIZE(a) \
-	sizeof(a)/sizeof(a[0])
+#include <stdio.h>
+
+#include "common.h"
+
+/* we use COM when `UNICODE=1` to avoid file paths being cut off */
+#define COM_INITFLAGS (COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
+
 #define OPEN_FILE_BUTTON	0
 #define COMBOBOX		1
 #define LISTBOX			2
 #define SAVE_FILE_BUTTON	3
 #define VERSION			4
-#ifdef _MSC_VER
-#define strdup(p) _strdup(p)
+
+/* adjust for these functions for Unicode */
+#if UNICODE
+#define _tstrncpy wcsncpy
+#define _tstrdup _wcsdup
+#define _tfopen _wfopen
+#define _sntprintf _snwprintf
+#else
+#define _tstrncpy strncpy
+#define _tstrdup _strdup
+#define _tfopen fopen
+#define _sntprintf _snprintf
 #endif
 
-HWND hWndListBox, hWndComboBox, hWndVersion;
-int16_t version = 13;
-enum types {
-	vf,
-	veg
-} type;
-char file_name[257] = {'\0'};
+/* enumerate over this */
+static const enum types types[] = {TYPES_VF, TYPES_VEG};
+
+static LPTSTR file_path = NULL;
+static uint8_t version = 13;
+static enum types type = TYPES_VEG;
+
+/* we edit this from display_file() */
+static HWND hwnd_version = NULL;
 
-void display_file(char* path) {
-	/* Read the file to memory */
-	FILE* file;
-	file = fopen(path, "rb");
-	fseek(file, 0x46, SEEK_SET);
-	int f_version = fgetc(file)+1;
-	fseek(file, 0x18, SEEK_SET);
-	TCHAR p[32];
-	switch (fgetc(file)) {
-		case 0xEF:
-			snprintf(p, 32, "File version: %s %d", "VEGAS Pro", f_version);
-			break;
-		case 0xF6:
-			snprintf(p, 32, "File version: %s %d", "Movie Studio", f_version);
-			break;
-		default:
-			snprintf(p, 32, "File version: %s %d", "Unknown", f_version);
-			break;
+static inline LPCTSTR type_to_prefix(enum types type) {
+	switch (type) {
+		case TYPES_VF: return TEXT("MS");
+		case TYPES_VEG: return TEXT("PRO");
+		case TYPES_UNKNOWN:
+		default: return TEXT("UNK");
 	}
-	SendMessage(hWndVersion, WM_SETTEXT, (WPARAM)0, (LPARAM)p);
-	fclose(file);
+}
+
+static inline LPCTSTR type_to_string(enum types type) {
+	switch (type) {
+		case TYPES_VF: return TEXT("Movie Studio");
+		case TYPES_VEG: return TEXT("Vegas Pro");
+		case TYPES_UNKNOWN:
+		default: return TEXT("Unknown");
+	}
+}
+
+static inline LPCTSTR type_to_extension(enum types type) {
+	switch (type) {
+		case TYPES_VF: return TEXT("vf");
+		case TYPES_VEG:
+		case TYPES_UNKNOWN:
+		default: return TEXT("veg");
+	}
 }
 
-char* open_file(HWND hWnd) {
-	OPENFILENAME ofn;
-	char* filename = calloc(256, sizeof(char));
+/* these functions are designed to *not* use global variables,
+ * to make everything a bit more simple... */
+static int display_file(LPCTSTR path) {
+	/* Read the file to memory */
+	FILE* file = _tfopen(path, TEXT("rb"));
+
+	uint8_t file_version = 0;
+	enum types file_type = TYPES_UNKNOWN;
 
-	ZeroMemory(&ofn, OPENFILENAME_SIZE_VERSION_400);
-	ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
-	ofn.hwndOwner = hWnd;
-	ofn.lpstrFile = filename;
-	ofn.lpstrFile[0] = '\0';
-	ofn.nMaxFile = 256;
-	ofn.lpstrFilter = "Project files\0*.veg;*.vf\0All files\0*.*\0";
-	ofn.nFilterIndex = 1;
+	get_file_information(file, &file_version, &file_type);
 
-	if (GetOpenFileName(&ofn) == 0) {
-		return " ";
+	int needed = _sntprintf(NULL, 0, TEXT("File version: %s %u"), type_to_string(file_type), file_version);
+	LPTSTR text = calloc(needed + 1, sizeof(TCHAR));
+	if (!text) /* out of memory... lol */
+		exit(1);
+	_sntprintf(text, needed + 1, TEXT("File version: %s %u"), type_to_string(file_type), file_version);
+
+	if (!SendMessage(hwnd_version, WM_SETTEXT, (WPARAM)0, (LPARAM)text)) {
+		free(text);
+		return -1;
 	}
 
-	display_file(filename);
+	free(text);
+	fclose(file);
 
-	return filename;
+	return 0;
 }
 
-void save_file(HWND hWnd, char* input_file) {
-	if (strcmp(input_file, " ") == 0) {
+static int open_file(HWND hWnd, LPTSTR* filepath) {
+#if UNICODE
+	if (CoInitializeEx(NULL, COM_INITFLAGS) != S_OK)
+		return -1; /* what */
+
+	COMDLG_FILTERSPEC filters[] = {{L"Project files", L"*.veg;*.vf"}, {L"All files", L"*.*"}};
+
+	IFileDialog* pfd = NULL;
+	IShellItem* result = NULL;
+
+	if (SUCCEEDED(CoCreateInstance(&CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog, (LPVOID*)&pfd))) {
+		pfd->lpVtbl->SetFileTypes(pfd, ARRAYSIZE(filters), filters);
+		pfd->lpVtbl->SetFileTypeIndex(pfd, 1);
+		pfd->lpVtbl->Show(pfd, hWnd);
+
+		if (!SUCCEEDED(pfd->lpVtbl->GetResult(pfd, &result)) || !result) {
+			pfd->lpVtbl->Release(pfd);
+			return -1;
+		}
+
+		if (!SUCCEEDED(result->lpVtbl->GetDisplayName(result, SIGDN_FILESYSPATH, filepath))) {
+			pfd->lpVtbl->Release(pfd);
+			result->lpVtbl->Release(result);
+			*filepath = NULL; /* might memleak? */
+			return -1;
+		}
+
+		result->lpVtbl->Release(result);
+		pfd->lpVtbl->Release(pfd);
+	} else {
+#endif
+		/* initialize our buffer */
+		*filepath = calloc(MAX_PATH + 1, sizeof(TCHAR));
+
+		OPENFILENAME ofn = {0};
+
+		/* NT 4.0 compat */
+		ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+		ofn.hwndOwner = hWnd;
+		ofn.lpstrFile = *filepath;
+		ofn.nMaxFile = MAX_PATH;
+		ofn.lpstrFilter = TEXT("Project files\0*.veg;*.vf\0All files\0*.*\0");
+
+		if (!GetOpenFileName(&ofn))
+			return -1;
+#if UNICODE
+	}
+
+	CoUninitialize();
+#endif
+
+	return display_file(*filepath);;
+}
+
+static int save_file(HWND hWnd, LPCTSTR input, uint8_t version, enum types type) {
+	if (!input) {
 		MessageBox(hWnd,
 					TEXT("Please open a file first!"),
 					TEXT("Invalid input file!"),
 					MB_ICONEXCLAMATION);
-		return;
-	}
-	OPENFILENAME ofn;
-	char output_file[256] = {'\0'}, *input_basename = strdup(input_file);
-	PathStripPath(input_basename);
-	int amt_of_ch = snprintf(output_file, 256, "PRO_V%d_", version);
-	if (256-amt_of_ch > 0)
-		strncat(output_file, input_basename, 256-amt_of_ch);
-
-	ZeroMemory(&ofn, OPENFILENAME_SIZE_VERSION_400);
-	ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
-	ofn.hwndOwner = hWnd;
-	ofn.lpstrFile = output_file;
-	ofn.nMaxFile = 256;
-	ofn.lpstrFilter = "Movie Studio project files\0*.vf\0VEGAS Pro project files\0*.veg\0All files\0*.*\0";
-	ofn.nFilterIndex = (int)type+1;
-
-	if (GetSaveFileName(&ofn) == 0) {
-		return;
+		return -1;
 	}
 
-	if (CopyFile((TCHAR*)input_file, (TCHAR*)output_file, 0) == 0) {
-		MessageBox(hWnd, TEXT("Failed to copy original project file! Does the destination file already exist?"), TEXT("Saving project failed!"), MB_ICONEXCLAMATION);
-                return;
-	}
-	FILE* output = fopen(output_file, "r+b");
-	if (output == NULL) {
-		MessageBox(hWnd, TEXT("Failed to save project file!"), TEXT("Saving project failed!"), MB_ICONEXCLAMATION); 
-		return;
+	LPTSTR output_template = NULL;
+	LPTSTR output = NULL;
+
+	{
+		LPTSTR input_basename = PathFindFileName(input);
+		int input_basename_len = PathFindExtension(input_basename) - input_basename;
+
+		int needed = _sntprintf(NULL, 0, TEXT("%s_V%u_%.*s.%s"), type_to_prefix(type), version, input_basename_len, input_basename, type_to_extension(type));
+		output_template = calloc(needed + 1, sizeof(TCHAR));
+		_sntprintf(output_template, needed + 1, TEXT("%s_V%u_%.*s.%s"), type_to_prefix(type), version, input_basename_len, input_basename, type_to_extension(type));
+
+		free(input_basename);
 	}
 
-	unsigned char magic_veg[] = {0xEF, 0x29, 0xC4, 0x46, 0x4A, 0x90, 0xD2, 0x11, 0x87, 0x22, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A};
-	unsigned char magic_vf[]  = {0xF6, 0x1B, 0x3C, 0x53, 0x35, 0xD6, 0xF3, 0x43, 0x8A, 0x90, 0x64, 0xB8, 0x87, 0x23, 0x1F, 0x7F};
+	{
+		/* File dialog */
+#if UNICODE
+		int com_initialized = SUCCEEDED(CoInitializeEx(NULL, COM_INITFLAGS));
+
+		COMDLG_FILTERSPEC filters[] = {{L"Movie Studio project files", L"*.vf"}, {L"Vegas Pro project files", L"*.veg"}, {L"All files", L"*.*"}};
+
+		IFileDialog* pfd = NULL;
+		IShellItem* result = NULL;
+		if (com_initialized && SUCCEEDED(CoCreateInstance(&CLSID_FileSaveDialog, NULL, CLSCTX_INPROC_SERVER, &IID_IFileSaveDialog, (LPVOID*)&pfd))) {
+			pfd->lpVtbl->SetFileTypes(pfd, ARRAYSIZE(filters), filters);
+			pfd->lpVtbl->SetFileTypeIndex(pfd, (type == TYPES_UNKNOWN) ? ARRAYSIZE(filters) : type - TYPES_UNKNOWN);
+			pfd->lpVtbl->SetFileName(pfd, output_template);
+			pfd->lpVtbl->Show(pfd, hWnd);
+
+			if (!SUCCEEDED(pfd->lpVtbl->GetResult(pfd, &result))) {
+				pfd->lpVtbl->Release(pfd);
+				free(output_template);
+				return -1;
+			}
+
+			if (!SUCCEEDED(result->lpVtbl->GetDisplayName(result, SIGDN_FILESYSPATH, &output))) {
+				pfd->lpVtbl->Release(pfd);
+				result->lpVtbl->Release(result);
+				free(output_template);
+				output = NULL; /* might memleak ? */
+				return -1;
+			}
+
+			result->lpVtbl->Release(result);
+			pfd->lpVtbl->Release(pfd);
+			free(output_template);
+		} else {
+#endif
+			/* fallback to OPENFILENAME if COM fucks up for whatever reason (or we're on ANSI)... */
+			output = calloc(MAX_PATH + 1, sizeof(TCHAR));
+			_tstrncpy(output, output_template, MAX_PATH);
+			free(output_template);
 
-	set_data(type == veg ? magic_veg : magic_vf, version-1, output);
+			OPENFILENAME ofn = {0};
+
+			ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+			ofn.hwndOwner = hWnd;
+			ofn.lpstrFile = output;
+			ofn.nMaxFile = MAX_PATH;
+			ofn.lpstrFilter = TEXT("Movie Studio project files\0*.vf\0Vegas Pro project files\0*.veg\0All files\0*.*\0");
+			ofn.nFilterIndex = (type == TYPES_UNKNOWN) ? 3 : type - TYPES_UNKNOWN;
+
+			if (!GetSaveFileName(&ofn))
+				return -1;
+#if UNICODE
+		}
+
+		if (com_initialized)
+			CoUninitialize();
+#endif
+	}
 
-	fclose(output);
+	if (!CopyFile(input, output, 0)) {
+		MessageBox(hWnd, TEXT("Failed to copy original project file! Does the destination file already exist?"), TEXT("Saving project failed!"), MB_ICONEXCLAMATION | MB_OK);
+		free(output);
+		return -1;
+	}
+
+	FILE* output_file = _tfopen(output, TEXT("ab"));
+	if (!output_file) {
+		MessageBox(hWnd, TEXT("Failed to save project file!"), TEXT("Saving project failed!"), MB_ICONEXCLAMATION | MB_OK);
+		free(output);
+		return -1;
+	}
+
+	free(output);
+
+	set_file_information(output_file, version, type);
+
+	fclose(output_file);
+
+	return 0;
 }
 
+/* TODO: use a resource file instead... */
 void AddControls(HWND hWnd) {
+	/* Open File */
+	HWND open_button = CreateWindow(TEXT("Button"), TEXT("Open"), WS_VISIBLE | WS_CHILD, WINDOW_WIDTH * 7 / 18, WINDOW_HEIGHT / 40, WINDOW_WIDTH * 2 / 9, WINDOW_HEIGHT / 10, hWnd, (HMENU)OPEN_FILE_BUTTON, NULL, NULL);
+
 	/* Versions */
-	hWndComboBox = CreateWindow("ComboBox", NULL,
+	HWND combobox = CreateWindow(TEXT("ComboBox"), NULL,
 								 CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD | WS_VISIBLE | WS_OVERLAPPED | WS_VSCROLL,
-								 (int)((225 - 50)/2), 30, 50, 200, 
+								 WINDOW_WIDTH * 7 / 18, WINDOW_HEIGHT * 3 / 20, WINDOW_WIDTH * 2 / 9, WINDOW_HEIGHT, 
 								 hWnd, (HMENU)COMBOBOX, NULL, NULL);
 
-	TCHAR versions[][10] = {TEXT("8"),  TEXT("9"),  TEXT("10"), 
-							TEXT("11"), TEXT("12"), TEXT("13"), 
-							TEXT("14"), TEXT("15"), TEXT("16"), 
-							TEXT("17"), TEXT("18"), TEXT("19")};
+	static LPCTSTR versions[] = {TEXT("8"),  TEXT("9"),  TEXT("10"), 
+	                             TEXT("11"), TEXT("12"), TEXT("13"), 
+	                             TEXT("14"), TEXT("15"), TEXT("16"), 
+	                             TEXT("17"), TEXT("18"), TEXT("19"),
+	                             TEXT("20"), TEXT("21")}; /* wuss 9+10 */
 
-	int i = 0;
-	for (i = 0; i < ARRAYSIZE(versions); i++) {
-		SendMessage(hWndComboBox, (UINT)CB_ADDSTRING, (WPARAM)0, (LPARAM)versions[i]);
-	}
-	SendMessage(hWndComboBox, CB_SETCURSEL, (WPARAM)3, (LPARAM)0);
-	/* Open File */
-	HWND open_button = CreateWindow("Button", "Open", WS_VISIBLE | WS_CHILD, (int)((225 - 50)/2), 5, 50, 20, hWnd, (HMENU)OPEN_FILE_BUTTON, NULL, NULL);
+	for (size_t i = 0; i < ARRAYSIZE(versions); i++)
+		SendMessage(combobox, (UINT)CB_ADDSTRING, (WPARAM)0, (LPARAM)versions[i]);
+
+	SendMessage(combobox, CB_SETCURSEL, (WPARAM)3, (LPARAM)0);
+
 	/* Type */
-	TCHAR listbox_items[][13] = {TEXT("VEGAS Pro"), TEXT("Movie Studio")};
-	hWndListBox = CreateWindow("Listbox", NULL, WS_VISIBLE | WS_CHILD | LBS_STANDARD | LBS_NOTIFY, (int)((225 - 100)/2), 55, 100, 40, hWnd, (HMENU)LISTBOX, NULL, NULL);
-	for (i = 0; i < ARRAYSIZE(listbox_items); i++) {
-		int pos = (int)SendMessage(hWndListBox, LB_ADDSTRING, i, (LPARAM) listbox_items[i]);
-		SendMessage(hWndListBox, LB_SETITEMDATA, pos, (LPARAM) i);
+	HWND listbox = CreateWindow(TEXT("Listbox"), NULL, WS_VISIBLE | WS_CHILD | LBS_STANDARD | LBS_NOTIFY, WINDOW_WIDTH * 5 / 18, WINDOW_HEIGHT * 11 / 40, WINDOW_WIDTH * 4 / 9, WINDOW_HEIGHT / 5, hWnd, (HMENU)LISTBOX, NULL, NULL);
+
+	for (size_t i = 0; i < ARRAYSIZE(types); i++) {
+		LRESULT pos = SendMessage(listbox, LB_ADDSTRING, i, (LPARAM)type_to_string(types[i]));
+		SendMessage(listbox, LB_SETITEMDATA, pos, (LPARAM)types[i]);
+		if (types[i] == type)
+			SendMessage(listbox, LB_SETCURSEL, (WPARAM)pos, (LPARAM)0);
 	}
-	SendMessage(hWndListBox, LB_SETCURSEL, (WPARAM)0, (LPARAM)0);
+
 	/* Save File */
-	HWND save_button = CreateWindow("Button", "Save", WS_VISIBLE | WS_CHILD, (int)((225 - 50)/2), 90, 50, 20, hWnd, (HMENU)SAVE_FILE_BUTTON, NULL, NULL);
+	HWND save_button = CreateWindow(TEXT("Button"), TEXT("Save"), WS_VISIBLE | WS_CHILD, WINDOW_WIDTH * 7 / 18, WINDOW_HEIGHT * 9 / 20, WINDOW_WIDTH * 2 / 9, WINDOW_HEIGHT / 10, hWnd, (HMENU)SAVE_FILE_BUTTON, NULL, NULL);
+
 	/* Version and Type display */
-	hWndVersion = CreateWindow("Edit", "", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_READONLY | ES_CENTER | ES_MULTILINE | SS_CENTER, (int)((225 - 150)/2), 120, 150, 40, hWnd, (HMENU)VERSION, NULL, NULL);
-	if (open_button == NULL || save_button == NULL || hWndListBox == NULL || hWndComboBox == NULL)
+	hwnd_version = CreateWindow(TEXT("Edit"), TEXT(""), WS_VISIBLE | WS_CHILD | WS_BORDER | ES_READONLY | ES_CENTER | ES_MULTILINE | SS_CENTER, WINDOW_WIDTH / 6, WINDOW_HEIGHT * 3 / 5, WINDOW_WIDTH * 2 / 3, WINDOW_HEIGHT / 5, hWnd, (HMENU)VERSION, NULL, NULL);
+	if (!open_button || !save_button || !listbox || !combobox || !hwnd_version)
 		MessageBox(hWnd, TEXT("how did you even trigger this"), TEXT("GUI could not be initialized!"), MB_ICONEXCLAMATION); 
 }
 
-bool CALLBACK SetFont(HWND child, LPARAM font) {
-	SendMessage(child, WM_SETFONT, font, true);
-	return true;
+int CALLBACK SetFont(HWND child, LPARAM font) {
+	SendMessage(child, WM_SETFONT, font, 1);
+	return 1;
 }
 
-LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) {
+LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 	switch(msg) {
 		case WM_COMMAND:
-			if(HIWORD(wParam) == CBN_SELCHANGE) {
-				if (LOWORD(wParam) == COMBOBOX)
-					version = (int16_t)(8+SendMessage((HWND) lParam, (UINT) CB_GETCURSEL, (WPARAM) 0, (LPARAM) 0));
-				if (LOWORD(wParam) == LISTBOX)
-					type = SendMessage((HWND) lParam, (UINT) LB_GETCURSEL, (WPARAM) 0, (LPARAM) 0);
+			if (HIWORD(wParam) == CBN_SELCHANGE) {
+				switch (LOWORD(wParam)) {
+					case COMBOBOX:
+						version = (uint8_t)(8+SendMessage((HWND)lParam, (UINT)CB_GETCURSEL, (WPARAM)0, (LPARAM)0));
+						break;
+					case LISTBOX: {
+						LRESULT i = SendMessage((HWND)lParam, (UINT)LB_GETCURSEL, (WPARAM)0, (LPARAM)0);
+						type = (enum types)SendMessage((HWND)lParam, (UINT)LB_GETITEMDATA, (WPARAM)i, (LPARAM)0);
+						break;
+					}
+					default:
+						break;
+				}
 			}
-			switch(wParam) {
+
+			switch (wParam) {
 				case OPEN_FILE_BUTTON:
-					strncpy(file_name, open_file(hWnd), 256);
-				case COMBOBOX:
-				case LISTBOX:
-				case VERSION:
+					/* free(NULL) == no-op */
+					free(file_path);
+					open_file(hWnd, &file_path);
 					break;
 				case SAVE_FILE_BUTTON:
-					save_file(hWnd, file_name);
+					save_file(hWnd, file_path, version, type);
+					break;
+				default:
+					break;
 			}
 			break;
-		case WM_CREATE: {
+		case WM_CREATE:
 			AddControls(hWnd);
 			EnumChildWindows(hWnd, (WNDENUMPROC)SetFont, (LPARAM)GetStockObject(DEFAULT_GUI_FONT));
 			break;
-		}
 		case WM_DESTROY:
 			PostQuitMessage(0);
 			break;
 		case WM_DROPFILES: {
 			/* Drag and drop support */
-			HDROP hDrop = (HDROP)wParam;
-			DragQueryFile(hDrop, 0, file_name, 256);
-			display_file(file_name);
+			free(file_path);
+
+			HDROP drop = (HDROP)wParam;
+
+			int needed = DragQueryFile(drop, 0, NULL, 0);
+			file_path = malloc((needed + 1) * sizeof(TCHAR));
+			DragQueryFile(drop, 0, file_path, needed + 1);
+			file_path[needed] = L'\0';
+
+			display_file(file_path);
 			break;
 		}
 		default:
 			return DefWindowProc(hWnd, msg, wParam, lParam);
 	}
-	return false;
+	return 0;
 }
 
 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR args, int ncmdshow) {
@@ -227,12 +397,12 @@
 	wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
 	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
 	wc.hInstance = hInstance;
-	wc.lpszClassName = "msvpvf";
+	wc.lpszClassName = TEXT("msvpvf");
 	wc.lpfnWndProc = WindowProcedure;
 
 	if (!RegisterClass(&wc)) return -1;
 
-	CreateWindowEx(WS_EX_ACCEPTFILES, TEXT("msvpvf"), TEXT("Movie Studio / Vegas Pro version spoofer"), WS_OVERLAPPED | WS_VISIBLE | WS_MINIMIZEBOX | WS_SYSMENU, 100, 100, 225, 200, NULL, NULL, hInstance, NULL);
+	CreateWindowEx(WS_EX_ACCEPTFILES, TEXT("msvpvf"), TEXT("Movie Studio / Vegas Pro version spoofer"), WS_OVERLAPPED | WS_VISIBLE | WS_MINIMIZEBOX | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL);
 
 	MSG msg = {0};
 
--- a/src/main.c	Sun Oct 01 03:03:29 2023 -0400
+++ b/src/main.c	Wed Mar 20 17:06:26 2024 -0400
@@ -1,160 +1,186 @@
 #include <stdio.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
+//#include <unistd.h>
 #include <getopt.h>
 #include <libgen.h>
-#include "../include/common.h"
-#ifdef _MSC_VER
-#define strdup(p) _strdup(p)
+
+#include "common.h"
+
+/* non-portable functions */
+static inline char* msvpvf_internal_strdup(const char* str) {
+	size_t len = strlen(str) + 1;
+
+	char* copy = malloc(len);
+	if (!copy)
+		return NULL;
+
+	memcpy(copy, str, len);
+	return copy;
+}
+
+/* source needs read permissions, target needs write permissions, both must be in binary mode */
+static inline int copy_file(FILE* source, FILE* target) {
+	char ch[4096];
+
+	while (!feof(source)) {
+		size_t b = fread(ch, 1, sizeof(ch), source);
+		if (b)
+			fwrite(ch, 1, b, target);
+	}
+
+	return 0;
+}
+
+static inline const char* type_to_string(enum types type) {
+	switch (type) {
+		case TYPES_VF: return "Movie Studio";
+		case TYPES_VEG: return "Vegas Pro";
+		case TYPES_UNKNOWN:
+		default: return "Unknown";
+	}
+}
+
+static inline const char* type_to_extension(enum types type) {
+	switch (type) {
+		case TYPES_VF: return "vf";
+		case TYPES_VEG:
+		case TYPES_UNKNOWN:
+		default: return "veg";
+	}
+}
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0]))
 #endif
 
+static const char* help_text =
+	"msvpvf by Paper\n"
+	"usage: %s <input>... (arguments)\n"
+	"\n"
+	"arguments:\n"
+	"  -t, --type       one of [vf,veg] (required)\n"
+	"  -v, --version    version to convert to (required)\n"
+	"  -h, --help       show this screen (optional)\n";
+
 static struct option options_long[] = {
-	{"input", required_argument, NULL, 'i'},
-	{"output", required_argument, NULL, 'o'},
 	{"version", required_argument, NULL, 'v'},
 	{"type", required_argument, NULL, 't'},
 	{"help", 0, NULL, 'h'}
 };
 
-char* strremove(char* str, const char* sub) {
-    size_t len = strlen(sub);
-    if (len > 0) {
-        char* p = str;
-        while ((p = strstr(p, sub)) != NULL) {
-            memmove(p, p + len, strlen(p + len) + 1);
-        }
-    }
-    return str;
-}
+int main(int argc, char *argv[]) {
+	uint8_t version = 0;
+	enum types type = TYPES_UNKNOWN;
 
-int main(int argc, char *argv[]) {
-	int c, option_index = 0;
-	unsigned char magic[16];
-	FILE* outfile;
-	struct arguments {
-		char input[256];
-		char output[256];
-		int version;
-		char type[4];
-	} args;
-	strcpy(args.input, " ");
-	strcpy(args.output, " ");
-	args.version = -1;
-	strcpy(args.type, " ");
-
-	while ((c = getopt_long(argc, argv, "i:o:v:t:h", options_long, &option_index)) != -1)
-		switch(c) {
-			case 'i':
-				strncpy(args.input, optarg, sizeof(args.input)-1);  /* subtract 1 to make sure it's "null-safe" */
-				break;
-			case 'o':
-				strncpy(args.output, optarg, sizeof(args.output)-1);
-				break;
+	int c;
+	int option_index = 0;
+	while ((c = getopt_long(argc, argv, "v:t:h", options_long, &option_index)) != -1) {
+		/* option argument */
+		switch (c) {
 			case 'v':
-				args.version = abs(atoi(strdup(optarg)));  /* abs() for possible negative inputs */
+				version = atoi(optarg);
 				break;
 			case 't':
-				strncpy(args.type, optarg, sizeof(args.type)-1);
+				if (!strcmp(optarg, "vf")) {
+					type = TYPES_VF;
+				} else if (!strcmp(optarg, "veg")) {
+					type = TYPES_VEG;
+				} else {
+					fprintf(stderr, "[ERROR]: Received an invalid type parameter!\n");
+					printf(help_text, argv[0]);
+				}
+
 				break;
 			case 'h':
 			default:
-				printf("msvpvf by Paper\nusage: %s (-i/--input) infile [(-o/--output) outfile] (-v/--version) version (-t/--type) [vf, veg]\n", argv[0]);
-				return 0;
+				printf(help_text, argv[0]);
+				break;
 		}
-	if (argc == 1) {
-		printf("msvpvf by Paper\nusage: %s (-i/--input) infile [(-o/--output) outfile] (-v/--version) version (-t/--type) [vf, veg]\n", argv[0]);
-		return 0;
 	}
-	if (strcmp(args.input, " ") == 0) {
-		printf("Input file name?\n");
-		fflush(stdout);
-		fgets(args.input, sizeof(args.input)-1, stdin);
-		args.input[strcspn(args.input, "\r\n")] = 0;
-	}
-	if (access(args.input, F_OK) != 0) {
-		fprintf(stderr, "Input file \"%s\" doesn't exist! Exiting.", args.input);
+
+	if (argc <= optind) {
+		fprintf(stderr, "[ERROR]: Missing input file!\n");
+		printf(help_text, argv[0]);
 		return 1;
 	}
-	FILE* input_file = fopen(args.input, "r");
-	if (fgetc(input_file) == EOF) {
-		fprintf(stderr, "Input file \"%s\" is empty.", args.input);
-		fclose(input_file);
+
+	if (!version || !type) {
+		printf(help_text, (argc > 0) ? argv[0] : "msvpvf");
 		return 1;
 	}
-	fseek(input_file, 0x46, SEEK_SET);
-	printf("Input file version: %d\n", fgetc(input_file));
-	fseek(input_file, 0x18, SEEK_SET);
-	int file_version = fgetc(input_file);
-	printf("Input file type: ");
-	switch (file_version) {
-		case 0xEF:
-			printf("VEGAS Pro\n\n");
-			break;
-		case 0xF6:
-			printf("Movie Studio\n\n");
-			break;
-		default:
-			printf("Unknown\n\n");
-			break;
-	}
-	int* ptr = &args.version;
-	if (args.version == -1) {
-		printf("What version of VEGAS would you like to spoof to?: ");
-		fflush(stdout);
-		scanf("%d", ptr);
-	}
-	if (strcmp(args.type, " ") == 0) {
-		printf("Would you like it to be VEGAS Pro or Movie Studio? [veg/vf]: ");
-		fflush(stdout);
-		scanf("%3s", args.type);
+
+	/* this progressively */
+	while (optind < argc) {
+		const char* input = argv[optind++];
+		FILE* output_file = NULL;
+
+		{
+			uint8_t file_version = 0;
+			enum types file_type = TYPES_UNKNOWN;
+
+			printf("Input file name: %s\n", input);
+			/* print information about the input file */
+			FILE* input_file = fopen(input, "rb");
+			if (!input_file) {
+				fprintf(stderr, "[ERROR]: Error opening input file %s!\n\n", input);
+				continue;
+			}
+
+			if (fgetc(input_file) == EOF) {
+				fprintf(stderr, "[ERROR]: Input file \"%s\" is empty.\n", input);
+				fclose(input_file);
+				continue;
+			}
+
+			if (get_file_information(input_file, &file_version, &file_type)) {
+				fprintf(stderr, "[ERROR]: Failed to get file information for input file \"%s\"!\n", input);
+				fclose(input_file);
+				continue;
+			}
+
+			printf("Input file version: %u\n", file_version);
+			printf("Input file type: %s\n\n", type_to_string(file_type));
+
+			{
+				/* open the output file... */
+				char* basec = msvpvf_internal_strdup(input);
+				char* bname = basename(basec);
+				int ext = strrchr(bname, '.') - bname;
+
+				/* create the output filename */
+				int needed_size = snprintf(NULL, 0, "V%u_%.*s.%s", version, ext, bname, type_to_extension(type));
+				char* output = malloc((needed_size + 1) * sizeof(char));
+				if (!output) {
+					fprintf(stderr, "[ERROR]: Failed to allocate memory for output string!\n");
+					free(basec);
+					return 1;
+				}
+
+				snprintf(output, needed_size + 1, "V%u_%.*s.%s", version, ext, bname, type_to_extension(type));
+				printf("Output filename: %s\n", output);
+
+				output_file = fopen(output, "w+b");
+				if (!output_file) {
+					fprintf(stderr, "[ERROR]: Failed to open output file %s! Do you have write permissions?\n", output);
+					free(basec);
+					free(output);
+					continue;
+				}
+				free(basec);
+				free(output);
+			}
+
+			copy_file(input_file, output_file);
+
+			fflush(stdout);
+			fclose(input_file);
+		}
+
+		set_file_information(output_file, version, type);
+		fclose(output_file);
 	}
-	fflush(stdout);
-	if (strcmp(args.output, " ") == 0) { /* string manipulation hell */
-		char* temp = (char*)calloc(256, sizeof(char));
-		temp[0] = '\0';
-		char str_version[4];
-		sprintf(str_version, "V%d", args.version);
-		strncat(temp, str_version, 4);
-		strncat(temp, "_", 2);
-		strncat(temp, basename(args.input), 248);
-		strcpy(temp, strremove(temp, strrchr(basename(args.input), ('.')))); /* remove file extension */
-		strncat(temp, ".", 2);
-		strncat(temp, args.type, 4);
-		strncpy(args.output, temp, 255);
-		free(temp);
-	}
-	if (strcmp(args.type, "veg") == 0) {
-		const unsigned char T[] = {0xEF, 0x29, 0xC4, 0x46, 0x4A, 0x90, 0xD2, 0x11, 0x87, 0x22, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A};
-		for (option_index = 0; option_index <= 15; option_index++) { /* this line is repeated, but it's probably best to just leave it be. */
-			magic[option_index] = T[option_index];
-		}
-	} else if (strcmp(args.type, "vf") == 0) {
-		const unsigned char T[] = {0xF6, 0x1B, 0x3C, 0x53, 0x35, 0xD6, 0xF3, 0x43, 0x8A, 0x90, 0x64, 0xB8, 0x87, 0x23, 0x1F, 0x7F};
-		for (option_index = 0; option_index <= 15; option_index++) {
-			magic[option_index] = T[option_index];
-		}
-	} else {
-		fprintf(stderr, "Type %s is invalid!", args.type);
-		return 1;
-	}
-	copy_file(args.input, args.output);
-#ifdef _WIN32 /* disallowed characters in filenames */
-	if (strcspn(args.input, "<>:\"/\\|?*") == strlen(args.input)+1) {
-#elif defined(__unix__)
-	if (strcspn(args.input, "/") == strlen(args.input)+1) {
-#else
-	if (NULL) {
-#endif
-		fprintf(stderr, "Invalid output filename detected! Exiting...");
-		return 1;
-	}
-	outfile = fopen(args.output, "r+b");
-	if (outfile == NULL) {
-		fprintf(stderr, "Failed to open file %s! Do you have write permissions?", args.output);
-		return 1;
-	}
-	set_data(&magic, args.version, outfile);
-	fclose(outfile);
+
 	return 0;
 }