<commit_before>/**
  * Touhou Community Reliant Automatic Patcher
  * Main DLL
  *
  * ----
  *
  * DLL and engine initialization.
  */

#include "thcrap.h"
#include "plugin.h"
#include "binhack.h"
#include "sha256.h"
#include "bp_file.h"
#include "dialog.h"
#include "textdisp.h"
#include "win32_patch.h"

// Static global variables
// -----------------------
static HMODULE hThcrap = NULL;
static char *dll_dir = NULL;
static const char *update_url_message =
	"The new version can be found at\n"
	"\n"
	"\t";
static const char *mbox_copy_message =
	"\n"
	"\n"
	"(Press Ctrl+C to copy the text of this message box and the URL)";
// -----------------------

json_t* identify_by_hash(const char *fn, size_t *file_size, json_t *versions)
{
	unsigned char *file_buffer;
	SHA256_CTX sha256_ctx;
	BYTE hash[32];
	char hash_str[65];
	int i;

	file_buffer = file_read(fn, file_size);
	if(!file_buffer) {
		return NULL;
	}
	sha256_init(&sha256_ctx);
	sha256_update(&sha256_ctx, file_buffer, *file_size);
	sha256_final(&sha256_ctx, hash);
	SAFE_FREE(file_buffer);
	
	for(i = 0; i < 32; i++) {
		sprintf(hash_str + (i * 2), "%02x", hash[i]);
	}
	return json_object_get(json_object_get(versions, "hashes"), hash_str);
}

json_t* identify_by_size(size_t file_size, json_t *versions)
{
	return json_object_numkey_get(json_object_get(versions, "sizes"), file_size);
}

int IsLatestBuild(const char *build, const char **latest, json_t *run_ver)
{
	json_t *json_latest;
	size_t json_latest_count;
	size_t i;

	if(!build || !run_ver || !latest) {
		return -1;
	}
	
	json_latest = json_object_get(run_ver, "latest");
	if(!json_latest) {
		return -1;
	}

	json_latest_count = json_array_size(json_latest);
	if(json_latest_count == 0) {
		*latest = json_string_value(json_latest);
	}

	for(i = 0; i < json_latest_count; i++) {
		if(json_is_array(json_latest)) {
			*latest = json_array_get_string(json_latest, i);
		}
		if(*latest && !strcmp(*latest, build)) {
			return 1;
		}
	}
	return 0;
}

json_t* identify(const char *exe_fn)
{
	size_t exe_size;
	json_t *run_ver = NULL;
	json_t *versions_js = NULL;
	const char *game = NULL;
	const char *build = NULL;
	const char *variety = NULL;
	
	// Result of the EXE identification (array)
	json_t *id_array = NULL;
	int size_cmp = 0;

	versions_js = stack_json_resolve("versions.js", NULL);
	if(!versions_js) {
		goto end;
	}
	log_printf("Hashing executable... ");

	id_array = identify_by_hash(exe_fn, &exe_size, versions_js);
	if(!id_array) {
		size_cmp = 1;
		log_printf("failed!\n");
		log_printf("File size lookup... ");
		id_array = identify_by_size(exe_size, versions_js);

		if(!id_array) {
			log_printf("failed!\n");
			goto end;
		}
	}

	if(json_array_size(id_array) >= 3) {
		game = json_array_get_string(id_array, 0);
		build = json_array_get_string(id_array, 1);
		variety = json_array_get_string(id_array, 2);
	}

	if(!game || !build) {
		log_printf("Invalid version format!");
		goto end;
	}

	// Store build in the runconfig to be recalled later for
	// version-dependent patch file resolving. Needs be directly written to
	// run_cfg because we already require it down below to resolve ver_fn.
	json_object_set(run_cfg, "build", json_array_get(id_array, 1));

	log_printf("→ %s %s %s\n", game, build, variety);

	if(stricmp(PathFindExtensionA(game), ".js")) {
		size_t ver_fn_len = strlen(game) + 1 + strlen(".js") + 1;
		VLA(char, ver_fn, ver_fn_len);
		sprintf(ver_fn, "%s.js", game);
		run_ver = stack_json_resolve(ver_fn, NULL);
		VLA_FREE(ver_fn);
	} else {
		run_ver = stack_json_resolve(game, NULL);
	}

	if(!run_ver) {
		// Create a dummy configuration with at least a "game" key
		run_ver = json_object();
		json_object_set_new(run_ver, "game", json_string(game));
	}

	// Pretty game title
	{
		const char *game_title = json_object_get_string(run_ver, "title");
		if(game_title) {
			game = game_title;
		}
	}

	if(size_cmp) {
		int ret = log_mboxf("Unknown version detected", MB_YESNO | MB_ICONQUESTION,
			"You have attached %s to an unknown game version.\n"
			"According to the file size, this is most likely\n"
			"\n"
			"\t%s %s %s\n"
			"\n"
			"but we haven't tested this exact variety yet and thus can't confirm that the patches will work.\n"
			"They might crash the game, damage your save files or cause even worse problems.\n"
			"\n"
			"Please send <%s> to\n"
			"\n"
			"\t<EMAIL>\n"
			"\n"
			"We will take a look at it, and add support if possible.\n"
			"\n"
			"Apply patches for the identified game version regardless (on your own risk)?",
			PROJECT_NAME_SHORT(), game, build, variety, exe_fn
		);
		if(ret == IDNO) {
			json_decref(run_ver);
			run_ver = NULL;
		}
	} else {
		// Old version nagbox
		const char *latest = NULL;
		if(IsLatestBuild(build, &latest, run_ver) == 0) {
			const char *url_update = json_object_get_string(run_ver, "url_update");
			log_mboxf("Old version detected", MB_OK | MB_ICONINFORMATION, 
				"You are running an old version of %s (%s).\n"
				"\n"
				"While %s is confirmed to work with this version, we recommend updating "
				"your game to the latest official version (%s).%s%s%s%s",
				game, build, PROJECT_NAME_SHORT(), latest,
				url_update ? "\n\n": "",
				url_update ? update_url_message : "",
				url_update ? url_update : "",
				url_update ? mbox_copy_message : ""
			);
		}
	}
end:
	json_decref(versions_js);
	return run_ver;
}

int thcrap_init(const char *setup_fn)
{
	json_t *run_ver = NULL;
	HMODULE hProc = GetModuleHandle(NULL);

	size_t exe_fn_len = GetModuleFileNameU(NULL, NULL, 0) + 1;
	size_t game_dir_len = GetCurrentDirectory(0, NULL) + 1;
	VLA(char, exe_fn, exe_fn_len);
	VLA(char, game_dir, game_dir_len);
	GetModuleFileNameU(NULL, exe_fn, exe_fn_len);
	GetCurrentDirectory(game_dir_len, game_dir);

	SetCurrentDirectory(dll_dir);

	run_cfg = json_load_file_report(setup_fn);

	{
		json_t *console_val = json_object_get(run_cfg, "console");
		log_init(json_is_true(console_val));
	}

	log_printf("Run configuration file: %s\n\n", setup_fn);

	win32_patch(hProc);
	textdisp_patch(hProc);
	dialog_patch(hProc);
	strings_patch(hProc);
	
	log_printf("EXE file name: %s\n", exe_fn);

	run_ver = identify(exe_fn);
	// Alright, smash our run configuration on top
	if(run_ver) {
		json_object_merge(run_ver, run_cfg);
		json_decref(run_cfg);
		run_cfg = run_ver;<commit_msg>Store run configuration file name in the run configuration.<commit_after>/**
  * Touhou Community Reliant Automatic Patcher
  * Main DLL
  *
  * ----
  *
  * DLL and engine initialization.
  */

#include "thcrap.h"
#include "plugin.h"
#include "binhack.h"
#include "sha256.h"
#include "bp_file.h"
#include "dialog.h"
#include "textdisp.h"
#include "win32_patch.h"

// Static global variables
// -----------------------
static HMODULE hThcrap = NULL;
static char *dll_dir = NULL;
static const char *update_url_message =
	"The new version can be found at\n"
	"\n"
	"\t";
static const char *mbox_copy_message =
	"\n"
	"\n"
	"(Press Ctrl+C to copy the text of this message box and the URL)";
// -----------------------

json_t* identify_by_hash(const char *fn, size_t *file_size, json_t *versions)
{
	unsigned char *file_buffer;
	SHA256_CTX sha256_ctx;
	BYTE hash[32];
	char hash_str[65];
	int i;

	file_buffer = file_read(fn, file_size);
	if(!file_buffer) {
		return NULL;
	}
	sha256_init(&sha256_ctx);
	sha256_update(&sha256_ctx, file_buffer, *file_size);
	sha256_final(&sha256_ctx, hash);
	SAFE_FREE(file_buffer);
	
	for(i = 0; i < 32; i++) {
		sprintf(hash_str + (i * 2), "%02x", hash[i]);
	}
	return json_object_get(json_object_get(versions, "hashes"), hash_str);
}

json_t* identify_by_size(size_t file_size, json_t *versions)
{
	return json_object_numkey_get(json_object_get(versions, "sizes"), file_size);
}

int IsLatestBuild(const char *build, const char **latest, json_t *run_ver)
{
	json_t *json_latest;
	size_t json_latest_count;
	size_t i;

	if(!build || !run_ver || !latest) {
		return -1;
	}
	
	json_latest = json_object_get(run_ver, "latest");
	if(!json_latest) {
		return -1;
	}

	json_latest_count = json_array_size(json_latest);
	if(json_latest_count == 0) {
		*latest = json_string_value(json_latest);
	}

	for(i = 0; i < json_latest_count; i++) {
		if(json_is_array(json_latest)) {
			*latest = json_array_get_string(json_latest, i);
		}
		if(*latest && !strcmp(*latest, build)) {
			return 1;
		}
	}
	return 0;
}

json_t* identify(const char *exe_fn)
{
	size_t exe_size;
	json_t *run_ver = NULL;
	json_t *versions_js = NULL;
	const char *game = NULL;
	const char *build = NULL;
	const char *variety = NULL;
	
	// Result of the EXE identification (array)
	json_t *id_array = NULL;
	int size_cmp = 0;

	versions_js = stack_json_resolve("versions.js", NULL);
	if(!versions_js) {
		goto end;
	}
	log_printf("Hashing executable... ");

	id_array = identify_by_hash(exe_fn, &exe_size, versions_js);
	if(!id_array) {
		size_cmp = 1;
		log_printf("failed!\n");
		log_printf("File size lookup... ");
		id_array = identify_by_size(exe_size, versions_js);

		if(!id_array) {
			log_printf("failed!\n");
			goto end;
		}
	}

	if(json_array_size(id_array) >= 3) {
		game = json_array_get_string(id_array, 0);
		build = json_array_get_string(id_array, 1);
		variety = json_array_get_string(id_array, 2);
	}

	if(!game || !build) {
		log_printf("Invalid version format!");
		goto end;
	}

	// Store build in the runconfig to be recalled later for
	// version-dependent patch file resolving. Needs be directly written to
	// run_cfg because we already require it down below to resolve ver_fn.
	json_object_set(run_cfg, "build", json_array_get(id_array, 1));

	log_printf("→ %s %s %s\n", game, build, variety);

	if(stricmp(PathFindExtensionA(game), ".js")) {
		size_t ver_fn_len = strlen(game) + 1 + strlen(".js") + 1;
		VLA(char, ver_fn, ver_fn_len);
		sprintf(ver_fn, "%s.js", game);
		run_ver = stack_json_resolve(ver_fn, NULL);
		VLA_FREE(ver_fn);
	} else {
		run_ver = stack_json_resolve(game, NULL);
	}

	if(!run_ver) {
		// Create a dummy configuration with at least a "game" key
		run_ver = json_object();
		json_object_set_new(run_ver, "game", json_string(game));
	}

	// Pretty game title
	{
		const char *game_title = json_object_get_string(run_ver, "title");
		if(game_title) {
			game = game_title;
		}
	}

	if(size_cmp) {
		int ret = log_mboxf("Unknown version detected", MB_YESNO | MB_ICONQUESTION,
			"You have attached %s to an unknown game version.\n"
			"According to the file size, this is most likely\n"
			"\n"
			"\t%s %s %s\n"
			"\n"
			"but we haven't tested this exact variety yet and thus can't confirm that the patches will work.\n"
			"They might crash the game, damage your save files or cause even worse problems.\n"
			"\n"
			"Please send <%s> to\n"
			"\n"
			"\t<EMAIL>@thpatch.net\n"
			"\n"
			"We will take a look at it, and add support if possible.\n"
			"\n"
			"Apply patches for the identified game version regardless (on your own risk)?",
			PROJECT_NAME_SHORT(), game, build, variety, exe_fn
		);
		if(ret == IDNO) {
			json_decref(run_ver);
			run_ver = NULL;
		}
	} else {
		// Old version nagbox
		const char *latest = NULL;
		if(IsLatestBuild(build, &latest, run_ver) == 0) {
			const char *url_update = json_object_get_string(run_ver, "url_update");
			log_mboxf("Old version detected", MB_OK | MB_ICONINFORMATION, 
				"You are running an old version of %s (%s).\n"
				"\n"
				"While %s is confirmed to work with this version, we recommend updating "
				"your game to the latest official version (%s).%s%s%s%s",
				game, build, PROJECT_NAME_SHORT(), latest,
				url_update ? "\n\n": "",
				url_update ? update_url_message : "",
				url_update ? url_update : "",
				url_update ? mbox_copy_message : ""
			);
		}
	}
end:
	json_decref(versions_js);
	return run_ver;
}

int thcrap_init(const char *setup_fn)
{
	json_t *run_ver = NULL;
	HMODULE hProc = GetModuleHandle(NULL);

	size_t exe_fn_len = GetModuleFileNameU(NULL, NULL, 0) + 1;
	size_t game_dir_len = GetCurrentDirectory(0, NULL) + 1;
	VLA(char, exe_fn, exe_fn_len);
	VLA(char, game_dir, game_dir_len);
	GetModuleFileNameU(NULL, exe_fn, exe_fn_len);
	GetCurrentDirectory(game_dir_len, game_dir);

	SetCurrentDirectory(dll_dir);

	run_cfg = json_load_file_report(setup_fn);

	{
		json_t *console_val = json_object_get(run_cfg, "console");
		log_init(json_is_true(console_val));
	}

	json_object_set_new(run_cfg, "run_cfg_fn", json_string(setup_fn));
	log_printf("Run configuration file: %s\n\n", setup_fn);

	win32_patch(hProc);
	textdisp_patch(hProc);
	dialog_patch(hProc);
	strings_patch(hProc);
	
	log_printf("EXE file name: %s\n", exe_fn);

	run_ver = identify(exe_fn);
	// Alright, smash our run configuration on top
	if(run_ver) {
		json_object_merge(run_ver, run_cfg);
		json_decref(run_cfg);
		run_cfg = run_ver;
{'fraction_non_alphanumeric': 0.1414442700156986, 'fraction_numerical': 0.009576138147566719, 'mean_word_length': 4.15621205989478, 'pattern_counts': {'":': 2, '<': 11, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2083140', 'n_tokens_mistral': 5293, 'n_tokens_neox': 4846, 'n_words': 1182}