view src/config.c @ 12:dd427b7cc459 default tip

json: replace with nxjson library more lightweight, reduces the binary size by about 40 kb
author Paper <paper@paper.us.eu.org>
date Fri, 15 Mar 2024 20:46:18 -0400
parents e6a594f16403
children
line wrap: on
line source

/**
 * config.c:
 *
 * Functions to load/save/edit the config.
**/

#include "dirtools.h"
#include "config.h"
#include "ini.h"
#include "plugin.h" /* g_plugin */

/* bypass intptr_t check */
#ifndef _MSC_VER
#define _MSC_VER 1201
#define WGSDK_UGLY_MSC_HACK 1
#endif

#include <Winamp/wa_ipc.h>

#ifdef WGSDK_UGLY_MSC_HACK
#undef _MSC_VER
#endif

#include <shlwapi.h>
#include <shlobj.h>

#include <assert.h>

#define MAX_LINE_LENGTH 256

#ifndef IPC_GETPLUGINDIRECTORYW
/* requires Winamp 5.58+ */
#define IPC_GETPLUGINDIRECTORYW 1336
#endif

/* set defaults */
struct config config = {
	.display_title = 1,
	.display_artist_name = 1,
	.display_album_name = 1,
	.display_album_art = 1,
	.display_song_info = 1,
	.show_elapsed_time = 1
};

/* must be free'd by the caller */
LPWSTR cfg_get_path() {
	LPWSTR plugins_folder = NULL;
	DWORD winamp_version = SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETVERSION);

	if (winamp_version >= 0x5580) {
		/* Native wide string version of IPC_GETPLUGINDIRECTORY */
		LPCWSTR w_plugins_folder = (LPCWSTR)SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW);
		size_t len = wcslen(w_plugins_folder);

		plugins_folder = calloc(len + 1, sizeof(WCHAR));
		if (!plugins_folder)
			return NULL;

		wcsncpy(plugins_folder, w_plugins_folder, len);
	} else if (winamp_version >= 0x5110) {
		/* ANSI string version of IPC_GETPLUGINDIRECTORY, convert */
		LPCSTR plugins_folder_ansi = (LPCSTR)SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORY);
		int required_size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, plugins_folder_ansi, -1, NULL, 0);
		if (required_size < 0)
			return NULL;

		plugins_folder = calloc(required_size, sizeof(WCHAR));
		if (!plugins_folder)
			return NULL;

		int size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, plugins_folder_ansi, -1, plugins_folder, required_size);
		if (size < 0)
			return NULL;
	} else if (winamp_version >= 0x2900) {
		/* Ancient winamp, use IPC_GETINIDIRECTORY and find out from there */
		LPCSTR ini_directory = (LPCSTR)SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETINIDIRECTORY);

		int required_size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ini_directory, -1, NULL, 0);
		if (required_size < 0)
			return NULL;

		LPWSTR ini_directory_w = calloc(required_size, sizeof(WCHAR));
		if (!ini_directory_w)
			return NULL;

		int size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ini_directory, -1, ini_directory_w, required_size);
		if (size < 0) {
			free(ini_directory_w);
			return NULL;
		}

		plugins_folder = dirtools_concat_paths(ini_directory_w, L"Plugins");
		free(ini_directory_w);
	} else {
		/* please, please for the love of God update winamp. */
		WCHAR appdata[MAX_PATH] = {L'\0'};

		HRESULT res = SHGetFolderPathW(
			g_plugin.hwndParent, CSIDL_APPDATA, NULL,
			SHGFP_TYPE_CURRENT, appdata
		);

		if (res != S_OK)
			return NULL;

		plugins_folder = dirtools_concat_paths(appdata, L"Winamp\\Plugins");
	}

	LPWSTR final = dirtools_concat_paths(plugins_folder, L"wgsdk");
	free(plugins_folder);

	return final;
}

static int cfg_ini_handler(void* data, const char* section, const char* key, const char* value) {
	struct config* config = (struct config*)data;

	char* ptr; // used with strtol

	if (!strcmp(section, "Display configuration")) {
		if (!strcmp(key, "Display title")) {
			config->display_title = !!strtol(value, &ptr, 10);
			return 1;
		} else if (!strcmp(key, "Display artist name")) {
			config->display_artist_name = !!strtol(value, &ptr, 10);
			return 1;
		} else if (!strcmp(key, "Display album name")) {
			config->display_album_name = !!strtol(value, &ptr, 10);
			return 1;
		} else if (!strcmp(key, "Display album art")) {
			config->display_album_art = !!strtol(value, &ptr, 10);
			return 1;
		} else if (!strcmp(key, "Display song information")) {
			config->display_song_info = !!strtol(value, &ptr, 10);
			return 1;
		} else if (!strcmp(key, "Show elapsed time")) {
			config->show_elapsed_time = !!strtol(value, &ptr, 10);
			return 1;
		} else return 0;
	} else return 0;
}

char* cfg_ini_reader(char* str, int num, void* stream) {
	HANDLE file = (HANDLE)stream;
	DWORD bytes_read;

	return (ReadFile(file, str, num, &bytes_read, NULL) && num == bytes_read) ? str : NULL;
}

int cfg_load(struct config* restrict config) {
	LPWSTR fold_path = cfg_get_path();
	assert(fold_path);

	LPWSTR path = dirtools_concat_paths(fold_path, L"config.ini");
	free(fold_path);

	HANDLE file = CreateFileW(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (file == INVALID_HANDLE_VALUE) /* file doesn't exist? */
		return 0;

	if (ini_parse_stream(cfg_ini_reader, file, cfg_ini_handler, config))
		return 1;

	CloseHandle(file);

	free(path);
	return 0;
}

void cfg_write_int_key(HANDLE file, const char* key, int value) {
	DWORD bytes_written;

	/* write the key */
	size_t len = strlen(key) * sizeof(*key);
	WriteFile(file, key, len, &bytes_written, NULL);
	assert(bytes_written == len);

	static const char equals[] = {'='};
	WriteFile(file, equals, sizeof(equals), &bytes_written, NULL);
	assert(bytes_written == sizeof(equals));

	char buf[16] = {'\0'};
	_itoa(value, buf, 10);
	len = strlen(buf) * sizeof(buf[0]);
	WriteFile(file, (buf), len, &bytes_written, NULL);
	assert(bytes_written == len);

	static const char ln[] = {'\r', '\n'};
	WriteFile(file, ln, sizeof(ln), &bytes_written, NULL);
	assert(bytes_written == sizeof(ln));
}

void cfg_write_header(HANDLE file, const char* header) {
	DWORD bytes_written = 0;

	static const char l_bracket[] = {'['};
	WriteFile(file, l_bracket, sizeof(l_bracket), &bytes_written, NULL);
	assert(bytes_written == sizeof(l_bracket));

	const size_t len = strlen(header) * sizeof(*header);
	WriteFile(file, header, len, &bytes_written, NULL);
	assert(bytes_written == len);

	static const char r_bracket[] = {']'};
	WriteFile(file, r_bracket, sizeof(r_bracket), &bytes_written, NULL);
	assert(bytes_written == sizeof(r_bracket));

	static const char ln[] = {'\r', '\n'};
	WriteFile(file, ln, sizeof(ln), &bytes_written, NULL);
	assert(bytes_written == sizeof(ln));
}

int cfg_save(const struct config* config) {
	LPWSTR fold_path = cfg_get_path();
	assert(fold_path);
	assert(!dirtools_create_directory(fold_path));

	LPWSTR path = dirtools_concat_paths(fold_path, L"config.ini");
	free(fold_path);

	/* dirty little hack that lets me not use goto */
	do {
		HANDLE file = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if (file == INVALID_HANDLE_VALUE) /* huh */
			break;

		cfg_write_header(file, "Display configuration");

		cfg_write_int_key(file, "Display title", config->display_title);
		cfg_write_int_key(file, "Display artist name", config->display_artist_name);
		cfg_write_int_key(file, "Display album name", config->display_album_name);
		cfg_write_int_key(file, "Display album art", config->display_album_art);
		cfg_write_int_key(file, "Display song information", config->display_song_info);
		cfg_write_int_key(file, "Show elapsed time", config->show_elapsed_time);

		CloseHandle(file);
	} while (0);

	free(path);
	return 0;
}

#undef WritePrivateProfileIntW