1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
5 // Functions specific to Microsoft Windows, especially those that require
8 #define DE_NOT_IN_MODULE
9 #include "deark-config.h"
16 #include <sys/types.h>
18 // This file is overloaded, in that it contains functions intended to only
19 // be used internally, as well as functions intended only for the
20 // command-line utility. That's why we need both deark-user.h and
22 #include "deark-private.h"
23 #include "deark-user.h"
25 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
26 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
29 // Windows-specific contextual data, mainly for console settings.
30 struct de_platform_data
{
32 int msgs_HANDLE_is_console
;
33 WORD orig_console_attribs
;
34 WORD inverse_console_attribs
;
37 void de_vsnprintf(char *buf
, size_t buflen
, const char *fmt
, va_list ap
)
39 _vsnprintf_s(buf
, buflen
, _TRUNCATE
, fmt
, ap
);
42 char *de_strdup(deark
*c
, const char *s
)
48 de_err(c
, "Memory allocation failed");
55 i64
de_strtoll(const char *string
, char **endptr
, int base
)
57 return _strtoi64(string
, endptr
, base
);
60 void de_utf8_to_oem(deark
*c
, const char *src
, char *dst
, size_t dstlen
)
65 srcW
= de_utf8_to_utf16_strdup(c
, src
);
67 // FIXME: An issue is that WideCharToMultiByte translates some printable
68 // Unicode characters to OEM graphics characters below 0x20. For example, for
69 // CP437, U+000A (LINE FEED) and U+25D9 (INVERSE WHITE CIRCLE) are both
70 // translated to 0x0a.
71 // The printf-like functions we will use on the translated string interpret
72 // bytes below 0x20 as ASCII control characters, so U+25D9 will end up being
73 // misinterpreted as a newline.
74 // I am not sure what to do about this. It might be possible to change the
75 // mode that printf uses, but we at least need newlines to work.
76 // Ideally, we should probably redesign some things so that de_utf8_to_oem()
77 // is not used with strings that contain newlines. But that's a lot of work
78 // for an obscure feature.
79 ret
= WideCharToMultiByte(CP_OEMCP
, 0, srcW
, -1, dst
, (int)dstlen
, NULL
, NULL
);
87 static char *de_utf16_to_utf8_strdup(deark
*c
, const WCHAR
*src
)
93 // Calculate the size required by the target string.
94 ret
= WideCharToMultiByte(CP_UTF8
, 0, src
, -1, NULL
, 0, NULL
, NULL
);
95 if(ret
<1) return NULL
;
98 dst
= (char*)de_malloc(c
, dstlen
);
100 ret
= WideCharToMultiByte(CP_UTF8
, 0, src
, -1, dst
, dstlen
, NULL
, NULL
);
108 wchar_t *de_utf8_to_utf16_strdup(deark
*c
, const char *src
)
114 // Calculate the size required by the target string.
115 ret
= MultiByteToWideChar(CP_UTF8
, 0, src
, -1, NULL
, 0);
117 de_err(c
, "Encoding conversion failed");
123 dst
= (WCHAR
*)de_mallocarray(c
, dstlen
, sizeof(WCHAR
));
125 ret
= MultiByteToWideChar(CP_UTF8
, 0, src
, -1, dst
, dstlen
);
128 de_err(c
, "Encoding conversion failed");
135 // Convert a string from utf8 to utf16, then write it to a FILE
136 // (e.g. using fputws).
137 void de_utf8_to_utf16_to_FILE(deark
*c
, const char *src
, FILE *f
)
139 #define DST_SMALL_SIZE 1024
140 WCHAR dst_small
[DST_SMALL_SIZE
];
144 ret
= MultiByteToWideChar(CP_UTF8
, 0, src
, -1, dst_small
, DST_SMALL_SIZE
);
146 // Our "small" buffer was big enough for the converted string.
147 fputws(dst_small
, f
);
151 // Our "small" buffer was not big enough. Do it the slow way.
152 // (Unfortunately, MultiByteToWideChar doesn't have a way to automatically
153 // tell us the required buffer size in the case that the supplied buffer
154 // was not big enough. So we end up calling it three times, when two should
155 // have been sufficient. But this is a rare code path.)
156 dst_large
= de_utf8_to_utf16_strdup(c
, src
);
157 fputws(dst_large
, f
);
158 de_free(c
, dst_large
);
161 static FILE* de_fopenW(deark
*c
, const WCHAR
*fnW
, const WCHAR
*modeW
,
162 char *errmsg
, size_t errmsg_len
)
167 errcode
= _wfopen_s(&f
, fnW
, modeW
);
172 strerror_s(errmsg
, (size_t)errmsg_len
, (int)errcode
);
178 static int de_examine_file_by_fd(deark
*c
, int fd
, i64
*len
,
179 char *errmsg
, size_t errmsg_len
, unsigned int *returned_flags
)
181 struct __stat64 stbuf
;
186 de_zeromem(&stbuf
, sizeof(struct __stat64
));
188 if(0 != _fstat64(fd
, &stbuf
)) {
189 strerror_s(errmsg
, (size_t)errmsg_len
, errno
);
193 if(!(stbuf
.st_mode
& _S_IFREG
)) {
194 de_strlcpy(errmsg
, "Not a regular file", errmsg_len
);
198 *len
= (i64
)stbuf
.st_size
;
206 FILE* de_fopen_for_read(deark
*c
, const char *fn
, i64
*len
,
207 char *errmsg
, size_t errmsg_len
, unsigned int *returned_flags
)
213 fnW
= de_utf8_to_utf16_strdup(c
, fn
);
215 f
= de_fopenW(c
, fnW
, L
"rb", errmsg
, errmsg_len
);
223 ret
= de_examine_file_by_fd(c
, _fileno(f
), len
, errmsg
, errmsg_len
,
233 // flags: 0x1 = append instead of overwriting
234 FILE* de_fopen_for_write(deark
*c
, const char *fn
,
235 char *errmsg
, size_t errmsg_len
, int overwrite_mode
,
242 // A simple check to make it harder to accidentally overwrite the input
243 // file. (But it can easily be defeated.)
244 // TODO?: Make this more robust.
245 if(c
->input_filename
&& !de_strcasecmp(fn
, c
->input_filename
)) {
246 de_err(c
, "Refusing to write to %s: Same as input filename", fn
);
248 de_strlcpy(errmsg
, "", errmsg_len
);
252 modeW
= (flags
&0x1) ? L
"ab" : L
"wb";
253 fnW
= de_utf8_to_utf16_strdup(c
, fn
);
255 if(overwrite_mode
==DE_OVERWRITEMODE_NEVER
) {
256 DWORD fa
= GetFileAttributesW(fnW
);
257 if(fa
!= INVALID_FILE_ATTRIBUTES
) {
258 de_strlcpy(errmsg
, "Output file already exists", errmsg_len
);
263 f_ret
= de_fopenW(c
, fnW
, modeW
, errmsg
, errmsg_len
);
270 int de_fseek(FILE *fp
, i64 offs
, int whence
)
272 return _fseeki64(fp
, (__int64
)offs
, whence
);
275 i64
de_ftell(FILE *fp
)
277 return (i64
)_ftelli64(fp
);
280 int de_fclose(FILE *fp
)
285 static void update_file_time(dbuf
*f
)
288 HANDLE fh
= INVALID_HANDLE_VALUE
;
293 if(f
->btype
!=DBUF_TYPE_OFILE
) return;
294 if(!f
->fi_copy
) return;
295 if(!f
->fi_copy
->timestamp
[DE_TIMESTAMPIDX_MODIFY
].is_valid
) return;
299 ft
= de_timestamp_to_FILETIME(&f
->fi_copy
->timestamp
[DE_TIMESTAMPIDX_MODIFY
]);
301 fnW
= de_utf8_to_utf16_strdup(c
, f
->name
);
302 fh
= CreateFileW(fnW
, FILE_WRITE_ATTRIBUTES
, FILE_SHARE_READ
, NULL
,
303 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
304 if(fh
==INVALID_HANDLE_VALUE
) goto done
;
306 wrtime
.dwHighDateTime
= (DWORD
)(((u64
)ft
)>>32);
307 wrtime
.dwLowDateTime
= (DWORD
)(((u64
)ft
)&0xffffffffULL
);
308 SetFileTime(fh
, NULL
, NULL
, &wrtime
);
311 if(fh
!= INVALID_HANDLE_VALUE
) {
317 void de_update_file_attribs(dbuf
*f
, u8 preserve_file_times
)
319 // [Updating file permissions not implemented on Windows.]
321 if(preserve_file_times
) {
326 char **de_convert_args_to_utf8(int argc
, wchar_t **argvW
)
331 argvUTF8
= (char**)de_mallocarray(NULL
, argc
, sizeof(char*));
333 // Convert parameters to UTF-8
334 for(i
=0;i
<argc
;i
++) {
335 argvUTF8
[i
] = de_utf16_to_utf8_strdup(NULL
, argvW
[i
]);
341 void de_free_utf8_args(int argc
, char **argv
)
345 for(i
=0;i
<argc
;i
++) {
346 de_free(NULL
, argv
[i
]);
351 struct de_platform_data
*de_platformdata_create(void)
353 struct de_platform_data
*plctx
;
355 plctx
= de_malloc(NULL
, sizeof(struct de_platform_data
));
359 void de_platformdata_destroy(struct de_platform_data
*plctx
)
362 de_free(NULL
, plctx
);
365 // Set the plctx->msgs_HANDLE field, for later use.
366 // n: 1=stdout, 2=stderr
367 void de_winconsole_init_handle(struct de_platform_data
*plctx
, int n
)
372 plctx
->msgs_HANDLE
= GetStdHandle((n
==2)?STD_ERROR_HANDLE
:STD_OUTPUT_HANDLE
);
374 b
= GetConsoleMode(plctx
->msgs_HANDLE
, &consolemode
);
375 plctx
->msgs_HANDLE_is_console
= b
? 1 : 0;
378 // Does plctx->msgs_HANDLE seem to be a Windows console?
379 int de_winconsole_is_console(struct de_platform_data
*plctx
)
381 return plctx
->msgs_HANDLE_is_console
;
384 void de_winconsole_set_UTF8_CP(struct de_platform_data
*plctx
)
386 // I hate to do this, but it's the least-bad fix I've found for some issues
387 // that have cropped up in the wake of Cygwin+Mintty using Windows 10's
388 // ConPTY features (as of Cygwin 3.1.0 - Dec. 2019).
390 // Note that, somewhat ironically, we only change the console code page if
391 // the output is *not* going directly to a console.
393 // Unfortunately, rude as it is not to do so, we can't restore the original
394 // code page settings when we're done. If we restore the code page, we have to
395 // do it after all of the output has reached the console. But if our output
396 // is being piped through a pager, some of it likely won't reach the console
397 // until after our program ends.
399 SetConsoleOutputCP(65001);
402 // Save current attribs to plctx.
403 // Returns 1 on success.
404 void de_winconsole_record_current_attributes(struct de_platform_data
*plctx
)
406 CONSOLE_SCREEN_BUFFER_INFO csbi
;
408 if(GetConsoleScreenBufferInfo(plctx
->msgs_HANDLE
, &csbi
)) {
409 plctx
->orig_console_attribs
= csbi
.wAttributes
;
412 plctx
->orig_console_attribs
= 0x0007;
415 plctx
->inverse_console_attribs
=
416 (plctx
->orig_console_attribs
&0xff00) |
417 ((plctx
->orig_console_attribs
&0x000f)<<4) |
418 ((plctx
->orig_console_attribs
&0x00f0)>>4);
421 // If we think this computer supports 24-bit color ANSI, enable it (if needed)
423 // Otherwise return 0.
424 int de_winconsole_try_enable_ansi24(struct de_platform_data
*plctx
)
426 // Note: Maybe we should check for Windows 10 (e.g. using
427 // IsWindows10OrGreater()) before calling SetConsoleMode(), but maybe that's
428 // just be a waste of time. Also, IsWindows10OrGreater() is fragile because
429 // it requires a .manifest file with certain properties.
431 // TODO: This is not correct. AFAIK, there is a range of Windows 10 builds
432 // that support ANSI codes, but do not support 24-bit color ANSI.
433 // I don't know a *good* way to detect 24-bit color support.
434 // Querying the Windows build number is possible, but requires some hackery,
435 // because Microsoft *really* does not want applications to do that.
436 return de_winconsole_enable_ansi(plctx
);
439 int de_winconsole_enable_ansi(struct de_platform_data
*plctx
)
444 if(!plctx
->msgs_HANDLE_is_console
) return 1;
446 b
= GetConsoleMode(plctx
->msgs_HANDLE
, &oldmode
);
448 if(oldmode
& ENABLE_VIRTUAL_TERMINAL_PROCESSING
) return 1; // Already enabled
450 // The ENABLE_VIRTUAL_TERMINAL_PROCESSING mode is what enables interpretation
451 // of ANSI escape codes.
453 // Note: This mode seems to be specific to the console window, not to the specific
454 // I/O handle that we pass to SetConsoleMode.
455 // I.e. if both stderr and stdout refer to the console, it doesn't matter which
457 // And if we write an ANSI code to stderr, it could also affect stdout.
458 // That's not what we want, but it shouldn't cause much of a problem for us.
460 // Note: This mode seems to get reset automatically when the process ends.
461 // It doesn't affect future processes that run in the same console.
462 // So we don't have to try to set it back when we're done.
464 b
= SetConsoleMode(plctx
->msgs_HANDLE
, oldmode
| ENABLE_VIRTUAL_TERMINAL_PROCESSING
);
469 void de_winconsole_highlight(struct de_platform_data
*plctx
, int x
)
472 SetConsoleTextAttribute(plctx
->msgs_HANDLE
, plctx
->inverse_console_attribs
);
475 SetConsoleTextAttribute(plctx
->msgs_HANDLE
, plctx
->orig_console_attribs
);
479 // Note: Need to keep this function in sync with the implementation in deark-unix.c.
480 void de_current_time_to_timestamp(struct de_timestamp
*ts
)
485 GetSystemTimeAsFileTime(&ft1
);
486 ft
= (i64
)(((u64
)ft1
.dwHighDateTime
)<<32 | ft1
.dwLowDateTime
);
487 de_FILETIME_to_timestamp(ft
, ts
, 0x1);
490 void de_exitprocess(int s
)