win32.c 15.6 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3 4 5 6 7
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

8
#include "win32.h"
9

10
#include "runtime.h"
11 12 13 14

#include <wincrypt.h>
#include <strsafe.h>

15 16 17
#define GIT_HASH_CNG_DLL_NAME           "bcrypt.dll"

/* BCRYPT_SHA1_ALGORITHM */
18 19
#define GIT_HASH_CNG_SHA1_TYPE          L"SHA1"
#define GIT_HASH_CNG_SHA256_TYPE        L"SHA256"
20 21 22 23 24 25 26

/* BCRYPT_OBJECT_LENGTH */
#define GIT_HASH_CNG_HASH_OBJECT_LEN    L"ObjectLength"

/* BCRYPT_HASH_REUSEABLE_FLAGS */
#define GIT_HASH_CNG_HASH_REUSABLE      0x00000020

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
/* Definitions */

/* CryptoAPI is available for hashing on Windows XP and newer.  */
struct cryptoapi_provider {
	HCRYPTPROV handle;
};

/*
 * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is
 * preferred, however it is only available on Windows 2008 and newer and
 * must therefore be dynamically loaded, and we must inline constants that
 * would not exist when building in pre-Windows 2008 environments.
 */

/* Function declarations for CNG */
typedef NTSTATUS (WINAPI *cng_open_algorithm_provider_fn)(
	HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm,
	LPCWSTR pszAlgId,
	LPCWSTR pszImplementation,
	DWORD dwFlags);

typedef NTSTATUS (WINAPI *cng_get_property_fn)(
	HANDLE /* BCRYPT_HANDLE */ hObject,
	LPCWSTR pszProperty,
	PUCHAR pbOutput,
	ULONG cbOutput,
	ULONG *pcbResult,
	ULONG dwFlags);

typedef NTSTATUS (WINAPI *cng_create_hash_fn)(
	HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
	HANDLE /* BCRYPT_HASH_HANDLE */ *phHash,
	PUCHAR pbHashObject, ULONG cbHashObject,
	PUCHAR pbSecret,
	ULONG cbSecret,
	ULONG dwFlags);

typedef NTSTATUS (WINAPI *cng_finish_hash_fn)(
	HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
	PUCHAR pbOutput,
	ULONG cbOutput,
	ULONG dwFlags);

typedef NTSTATUS (WINAPI *cng_hash_data_fn)(
	HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
	PUCHAR pbInput,
	ULONG cbInput,
	ULONG dwFlags);

typedef NTSTATUS (WINAPI *cng_destroy_hash_fn)(
	HANDLE /* BCRYPT_HASH_HANDLE */ hHash);

typedef NTSTATUS (WINAPI *cng_close_algorithm_provider_fn)(
	HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
	ULONG dwFlags);

struct cng_provider {
	/* DLL for CNG */
	HINSTANCE dll;

	/* Function pointers for CNG */
	cng_open_algorithm_provider_fn open_algorithm_provider;
	cng_get_property_fn get_property;
	cng_create_hash_fn create_hash;
	cng_finish_hash_fn finish_hash;
	cng_hash_data_fn hash_data;
	cng_destroy_hash_fn destroy_hash;
	cng_close_algorithm_provider_fn close_algorithm_provider;

	HANDLE /* BCRYPT_ALG_HANDLE */ sha1_handle;
	DWORD sha1_object_size;

	HANDLE /* BCRYPT_ALG_HANDLE */ sha256_handle;
	DWORD sha256_object_size;
};

typedef struct {
	git_hash_win32_provider_t type;

	union {
		struct cryptoapi_provider cryptoapi;
		struct cng_provider cng;
	} provider;
} hash_win32_provider;

/* Hash provider definition */

static hash_win32_provider hash_provider = {0};
115 116 117

/* Hash initialization */

118
/* Initialize CNG, if available */
119
GIT_INLINE(int) cng_provider_init(void)
120 121 122 123 124
{
	char dll_path[MAX_PATH];
	DWORD dll_path_len, size_len;

	/* Only use CNG on Windows 2008 / Vista SP1  or better (Windows 6.0 SP1) */
125
	if (!git_has_win32_version(6, 0, 1)) {
126
		git_error_set(GIT_ERROR_SHA, "CryptoNG is not supported on this platform");
127
		return -1;
128
	}
129 130

	/* Load bcrypt.dll explicitly from the system directory */
131 132
	if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 ||
		dll_path_len > MAX_PATH ||
133 134
		StringCchCat(dll_path, MAX_PATH, "\\") < 0 ||
		StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 ||
135
		(hash_provider.provider.cng.dll = LoadLibrary(dll_path)) == NULL) {
136
		git_error_set(GIT_ERROR_SHA, "CryptoNG library could not be loaded");
137
		return -1;
138
	}
139 140

	/* Load the function addresses */
141 142 143 144 145 146 147
	if ((hash_provider.provider.cng.open_algorithm_provider = (cng_open_algorithm_provider_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptOpenAlgorithmProvider"))) == NULL ||
		(hash_provider.provider.cng.get_property = (cng_get_property_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptGetProperty"))) == NULL ||
		(hash_provider.provider.cng.create_hash = (cng_create_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptCreateHash"))) == NULL ||
		(hash_provider.provider.cng.finish_hash = (cng_finish_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptFinishHash"))) == NULL ||
		(hash_provider.provider.cng.hash_data = (cng_hash_data_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptHashData"))) == NULL ||
		(hash_provider.provider.cng.destroy_hash = (cng_destroy_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptDestroyHash"))) == NULL ||
		(hash_provider.provider.cng.close_algorithm_provider = (cng_close_algorithm_provider_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptCloseAlgorithmProvider"))) == NULL) {
148
		FreeLibrary(hash_provider.provider.cng.dll);
149

150
		git_error_set(GIT_ERROR_OS, "CryptoNG functions could not be loaded");
151 152 153 154
		return -1;
	}

	/* Load the SHA1 algorithm */
155 156
	if (hash_provider.provider.cng.open_algorithm_provider(&hash_provider.provider.cng.sha1_handle, GIT_HASH_CNG_SHA1_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0 ||
	    hash_provider.provider.cng.get_property(hash_provider.provider.cng.sha1_handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_provider.provider.cng.sha1_object_size, sizeof(DWORD), &size_len, 0) < 0) {
157
		git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized");
158
		goto on_error;
159 160
	}

161 162 163 164 165
	/* Load the SHA256 algorithm */
	if (hash_provider.provider.cng.open_algorithm_provider(&hash_provider.provider.cng.sha256_handle, GIT_HASH_CNG_SHA256_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0 ||
	    hash_provider.provider.cng.get_property(hash_provider.provider.cng.sha256_handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_provider.provider.cng.sha256_object_size, sizeof(DWORD), &size_len, 0) < 0) {
		git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized");
		goto on_error;
166 167
	}

168
	hash_provider.type = GIT_HASH_WIN32_CNG;
169
	return 0;
170 171 172 173 174 175 176 177 178 179 180 181

on_error:
	if (hash_provider.provider.cng.sha1_handle)
		hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha1_handle, 0);

	if (hash_provider.provider.cng.sha256_handle)
		hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha256_handle, 0);

	if (hash_provider.provider.cng.dll)
		FreeLibrary(hash_provider.provider.cng.dll);

	return -1;
182 183
}

184
GIT_INLINE(void) cng_provider_shutdown(void)
185
{
186 187
	if (hash_provider.type == GIT_HASH_WIN32_INVALID)
		return;
188

189 190 191 192 193
	hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha1_handle, 0);
	hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha256_handle, 0);
	FreeLibrary(hash_provider.provider.cng.dll);

	hash_provider.type = GIT_HASH_WIN32_INVALID;
194 195
}

196
/* Initialize CryptoAPI */
197
GIT_INLINE(int) cryptoapi_provider_init(void)
198
{
199
	if (!CryptAcquireContext(&hash_provider.provider.cryptoapi.handle, NULL, 0, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
200
		git_error_set(GIT_ERROR_OS, "legacy hash context could not be started");
201
		return -1;
202
	}
203

204
	hash_provider.type = GIT_HASH_WIN32_CRYPTOAPI;
205 206 207
	return 0;
}

208
GIT_INLINE(void) cryptoapi_provider_shutdown(void)
209
{
210 211 212 213
	if (hash_provider.type == GIT_HASH_WIN32_INVALID)
		return;

	CryptReleaseContext(hash_provider.provider.cryptoapi.handle, 0);
214

215
	hash_provider.type = GIT_HASH_WIN32_INVALID;
216 217
}

218
static void hash_provider_shutdown(void)
219
{
220 221 222 223
	if (hash_provider.type == GIT_HASH_WIN32_CNG)
		cng_provider_shutdown();
	else if (hash_provider.type == GIT_HASH_WIN32_CRYPTOAPI)
		cryptoapi_provider_shutdown();
224 225
}

226
static int hash_provider_init(void)
227 228 229
{
	int error = 0;

230
	if (hash_provider.type != GIT_HASH_WIN32_INVALID)
231 232
		return 0;

233 234
	if ((error = cng_provider_init()) < 0)
		error = cryptoapi_provider_init();
235

236
	if (!error)
237
		error = git_runtime_shutdown_register(hash_provider_shutdown);
238 239 240 241

	return error;
}

242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
git_hash_win32_provider_t git_hash_win32_provider(void)
{
	return hash_provider.type;
}

int git_hash_win32_set_provider(git_hash_win32_provider_t provider)
{
	if (provider == hash_provider.type)
		return 0;

	hash_provider_shutdown();

	if (provider == GIT_HASH_WIN32_CNG)
		return cng_provider_init();
	else if (provider == GIT_HASH_WIN32_CRYPTOAPI)
		return cryptoapi_provider_init();

	git_error_set(GIT_ERROR_SHA, "unsupported win32 provider");
	return -1;
}

263 264
/* CryptoAPI: available in Windows XP and newer */

265
GIT_INLINE(int) hash_cryptoapi_init(git_hash_win32_ctx *ctx)
266 267 268 269
{
	if (ctx->ctx.cryptoapi.valid)
		CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);

270
	if (!CryptCreateHash(hash_provider.provider.cryptoapi.handle, ctx->algorithm, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) {
271
		ctx->ctx.cryptoapi.valid = 0;
272
		git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created");
273 274 275 276 277 278 279
		return -1;
	}

	ctx->ctx.cryptoapi.valid = 1;
	return 0;
}

280
GIT_INLINE(int) hash_cryptoapi_update(git_hash_win32_ctx *ctx, const void *_data, size_t len)
281
{
282 283
	const BYTE *data = (BYTE *)_data;

Edward Thomson committed
284
	GIT_ASSERT(ctx->ctx.cryptoapi.valid);
285

286 287 288
	while (len > 0) {
		DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len;

289
		if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) {
290
			git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated");
291
			return -1;
292
		}
293 294 295 296

		data += chunk;
		len -= chunk;
	}
297 298 299 300

	return 0;
}

301
GIT_INLINE(int) hash_cryptoapi_final(unsigned char *out, git_hash_win32_ctx *ctx)
302
{
303
	DWORD len = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE;
304 305
	int error = 0;

Edward Thomson committed
306
	GIT_ASSERT(ctx->ctx.cryptoapi.valid);
307

308
	if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out, &len, 0)) {
309
		git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished");
310
		error = -1;
311
	}
312 313 314 315 316 317 318

	CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
	ctx->ctx.cryptoapi.valid = 0;

	return error;
}

319
GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_win32_ctx *ctx)
320 321 322 323 324
{
	if (ctx->ctx.cryptoapi.valid)
		CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
}

325 326 327 328 329 330 331 332 333 334 335 336
GIT_INLINE(int) hash_sha1_cryptoapi_ctx_init_init(git_hash_win32_ctx *ctx)
{
	ctx->algorithm = CALG_SHA1;
	return hash_cryptoapi_init(ctx);
}

GIT_INLINE(int) hash_sha256_cryptoapi_ctx_init(git_hash_win32_ctx *ctx)
{
	ctx->algorithm = CALG_SHA_256;
	return hash_cryptoapi_init(ctx);
}

337 338
/* CNG: Available in Windows Server 2008 and newer */

339
GIT_INLINE(int) hash_sha1_cng_ctx_init(git_hash_win32_ctx *ctx)
340
{
341
	if ((ctx->ctx.cng.hash_object = git__malloc(hash_provider.provider.cng.sha1_object_size)) == NULL)
342
		return -1;
343

344
	if (hash_provider.provider.cng.create_hash(hash_provider.provider.cng.sha1_handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_provider.provider.cng.sha1_object_size, NULL, 0, 0) < 0) {
345
		git__free(ctx->ctx.cng.hash_object);
346

347
		git_error_set(GIT_ERROR_OS, "sha1 implementation could not be created");
348
		return -1;
349 350
	}

351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
	ctx->algorithm = CALG_SHA1;
	return 0;
}

GIT_INLINE(int) hash_sha256_cng_ctx_init(git_hash_win32_ctx *ctx)
{
	if ((ctx->ctx.cng.hash_object = git__malloc(hash_provider.provider.cng.sha256_object_size)) == NULL)
		return -1;

	if (hash_provider.provider.cng.create_hash(hash_provider.provider.cng.sha256_handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_provider.provider.cng.sha256_object_size, NULL, 0, 0) < 0) {
		git__free(ctx->ctx.cng.hash_object);

		git_error_set(GIT_ERROR_OS, "sha256 implementation could not be created");
		return -1;
	}
366

367
	ctx->algorithm = CALG_SHA_256;
368
	return 0;
369 370
}

371
GIT_INLINE(int) hash_cng_init(git_hash_win32_ctx *ctx)
372
{
373 374
	BYTE hash[GIT_HASH_SHA256_SIZE];
	ULONG size = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE;
375 376 377 378 379

	if (!ctx->ctx.cng.updated)
		return 0;

	/* CNG needs to be finished to restart */
380
	if (hash_provider.provider.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, size, 0) < 0) {
381
		git_error_set(GIT_ERROR_OS, "hash implementation could not be finished");
382
		return -1;
383
	}
384 385 386 387 388 389

	ctx->ctx.cng.updated = 0;

	return 0;
}

390
GIT_INLINE(int) hash_cng_update(git_hash_win32_ctx *ctx, const void *_data, size_t len)
391
{
392 393 394 395 396
	PBYTE data = (PBYTE)_data;

	while (len > 0) {
		ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len;

397
		if (hash_provider.provider.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) {
398
			git_error_set(GIT_ERROR_OS, "hash could not be updated");
399
			return -1;
400
		}
401 402 403 404

		data += chunk;
		len -= chunk;
	}
405 406 407 408

	return 0;
}

409
GIT_INLINE(int) hash_cng_final(unsigned char *out, git_hash_win32_ctx *ctx)
410
{
411 412 413
	ULONG size = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE;

	if (hash_provider.provider.cng.finish_hash(ctx->ctx.cng.hash_handle, out, size, 0) < 0) {
414
		git_error_set(GIT_ERROR_OS, "hash could not be finished");
415
		return -1;
416
	}
417 418 419 420 421 422

	ctx->ctx.cng.updated = 0;

	return 0;
}

423
GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_win32_ctx *ctx)
424
{
425
	hash_provider.provider.cng.destroy_hash(ctx->ctx.cng.hash_handle);
426 427 428 429 430
	git__free(ctx->ctx.cng.hash_object);
}

/* Indirection between CryptoAPI and CNG */

431
GIT_INLINE(int) hash_sha1_win32_ctx_init(git_hash_win32_ctx *ctx)
432
{
433 434 435 436 437 438 439 440 441 442 443 444 445
	GIT_ASSERT_ARG(hash_provider.type);

	memset(ctx, 0x0, sizeof(git_hash_win32_ctx));
	return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_sha1_cng_ctx_init(ctx) : hash_sha1_cryptoapi_ctx_init_init(ctx);
}

GIT_INLINE(int) hash_sha256_win32_ctx_init(git_hash_win32_ctx *ctx)
{
	GIT_ASSERT_ARG(hash_provider.type);

	memset(ctx, 0x0, sizeof(git_hash_win32_ctx));
	return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_sha256_cng_ctx_init(ctx) : hash_sha256_cryptoapi_ctx_init(ctx);
}
446

447 448 449 450 451 452 453 454 455 456 457 458
GIT_INLINE(int) hash_win32_init(git_hash_win32_ctx *ctx)
{
	return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx);
}

GIT_INLINE(int) hash_win32_update(git_hash_win32_ctx *ctx, const void *data, size_t len)
{
	return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len);
}

GIT_INLINE(int) hash_win32_final(unsigned char *out, git_hash_win32_ctx *ctx)
{
Edward Thomson committed
459
	GIT_ASSERT_ARG(ctx);
460 461 462 463 464 465 466 467 468 469
	return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx);
}

GIT_INLINE(void) hash_win32_cleanup(git_hash_win32_ctx *ctx)
{
	if (hash_provider.type == GIT_HASH_WIN32_CNG)
		hash_ctx_cng_cleanup(ctx);
	else if(hash_provider.type == GIT_HASH_WIN32_CRYPTOAPI)
		hash_ctx_cryptoapi_cleanup(ctx);
}
470

471
#ifdef GIT_SHA1_WIN32
472

473 474 475 476
int git_hash_sha1_global_init(void)
{
	return hash_provider_init();
}
477

478 479 480 481
int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
{
	GIT_ASSERT_ARG(ctx);
	return hash_sha1_win32_ctx_init(&ctx->win32);
482 483
}

484
int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
485
{
Edward Thomson committed
486
	GIT_ASSERT_ARG(ctx);
487
	return hash_win32_init(&ctx->win32);
488 489
}

490
int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len)
491
{
Edward Thomson committed
492
	GIT_ASSERT_ARG(ctx);
493
	return hash_win32_update(&ctx->win32, data, len);
494 495
}

496
int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx)
497
{
Edward Thomson committed
498
	GIT_ASSERT_ARG(ctx);
499
	return hash_win32_final(out, &ctx->win32);
500 501
}

502
void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
503
{
Edward Thomson committed
504 505
	if (!ctx)
		return;
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
	hash_win32_cleanup(&ctx->win32);
}

#endif

#ifdef GIT_SHA256_WIN32

int git_hash_sha256_global_init(void)
{
	return hash_provider_init();
}

int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx)
{
	GIT_ASSERT_ARG(ctx);
	return hash_sha256_win32_ctx_init(&ctx->win32);
}

int git_hash_sha256_init(git_hash_sha256_ctx *ctx)
{
	GIT_ASSERT_ARG(ctx);
	return hash_win32_init(&ctx->win32);
}

int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len)
{
	GIT_ASSERT_ARG(ctx);
	return hash_win32_update(&ctx->win32, data, len);
}

int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx)
{
	GIT_ASSERT_ARG(ctx);
	return hash_win32_final(out, &ctx->win32);
540
}
541 542 543 544 545 546 547 548 549

void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx)
{
	if (!ctx)
		return;
	hash_win32_cleanup(&ctx->win32);
}

#endif