Update to 14.0-beta1
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
add_subdirectory(macosx)
|
||||
add_subdirectory(os2)
|
||||
add_subdirectory(unix)
|
||||
add_subdirectory(windows)
|
||||
|
||||
@@ -7,5 +7,10 @@ add_files(
|
||||
osx_stdafx.h
|
||||
string_osx.cpp
|
||||
string_osx.h
|
||||
survey_osx.cpp
|
||||
CONDITION APPLE
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
target_sources(openttd PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/osx_main.cpp)
|
||||
endif()
|
||||
|
||||
+133
-156
@@ -9,17 +9,23 @@
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../crashlog.h"
|
||||
#include "../../fileio_func.h"
|
||||
#include "../../string_func.h"
|
||||
#include "../../gamelog.h"
|
||||
#include "../../saveload/saveload.h"
|
||||
#include "../../video/video_driver.hpp"
|
||||
#include "macos.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <mach-o/arch.h>
|
||||
#include <dlfcn.h>
|
||||
#include <cxxabi.h>
|
||||
#include <execinfo.h>
|
||||
|
||||
#ifdef WITH_UNOFFICIAL_BREAKPAD
|
||||
# include <client/mac/handler/exception_handler.h>
|
||||
#endif
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
@@ -31,15 +37,11 @@
|
||||
#define IS_ALIGNED(addr) (((uintptr_t)(addr) & 0xf) == 0)
|
||||
#endif
|
||||
|
||||
/* printf format specification for 32/64-bit addresses. */
|
||||
#ifdef __LP64__
|
||||
#define PRINTF_PTR "0x%016lx"
|
||||
#else
|
||||
#define PRINTF_PTR "0x%08lx"
|
||||
#endif
|
||||
|
||||
#define MAX_STACK_FRAMES 64
|
||||
|
||||
/** The signals we want our crash handler to handle. */
|
||||
static constexpr int _signals_to_handle[] = { SIGSEGV, SIGABRT, SIGFPE, SIGBUS, SIGILL, SIGSYS, SIGQUIT };
|
||||
|
||||
/**
|
||||
* OSX implementation for the crash logger.
|
||||
*/
|
||||
@@ -47,108 +49,65 @@ class CrashLogOSX : public CrashLog {
|
||||
/** Signal that has been thrown. */
|
||||
int signum;
|
||||
|
||||
char filename_log[MAX_PATH]; ///< Path of crash.log
|
||||
char filename_save[MAX_PATH]; ///< Path of crash.sav
|
||||
char filename_screenshot[MAX_PATH]; ///< Path of crash.(png|bmp|pcx)
|
||||
|
||||
char *LogOSVersion(char *buffer, const char *last) const override
|
||||
void SurveyCrash(nlohmann::json &survey) const override
|
||||
{
|
||||
int ver_maj, ver_min, ver_bug;
|
||||
GetMacOSVersion(&ver_maj, &ver_min, &ver_bug);
|
||||
|
||||
const NXArchInfo *arch = NXGetLocalArchInfo();
|
||||
|
||||
return buffer + seprintf(buffer, last,
|
||||
"Operating system:\n"
|
||||
" Name: Mac OS X\n"
|
||||
" Release: %d.%d.%d\n"
|
||||
" Machine: %s\n"
|
||||
" Min Ver: %d\n"
|
||||
" Max Ver: %d\n",
|
||||
ver_maj, ver_min, ver_bug,
|
||||
arch != nullptr ? arch->description : "unknown",
|
||||
MAC_OS_X_VERSION_MIN_REQUIRED,
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED
|
||||
);
|
||||
survey["id"] = signum;
|
||||
survey["reason"] = strsignal(signum);
|
||||
}
|
||||
|
||||
char *LogError(char *buffer, const char *last, const char *message) const override
|
||||
void SurveyStacktrace(nlohmann::json &survey) const override
|
||||
{
|
||||
return buffer + seprintf(buffer, last,
|
||||
"Crash reason:\n"
|
||||
" Signal: %s (%d)\n"
|
||||
" Message: %s\n\n",
|
||||
strsignal(this->signum),
|
||||
this->signum,
|
||||
message == nullptr ? "<none>" : message
|
||||
);
|
||||
void *trace[64];
|
||||
int trace_size = backtrace(trace, lengthof(trace));
|
||||
|
||||
survey = nlohmann::json::array();
|
||||
|
||||
char **messages = backtrace_symbols(trace, trace_size);
|
||||
for (int i = 0; i < trace_size; i++) {
|
||||
survey.push_back(messages[i]);
|
||||
}
|
||||
free(messages);
|
||||
}
|
||||
|
||||
char *LogStacktrace(char *buffer, const char *last) const override
|
||||
#ifdef WITH_UNOFFICIAL_BREAKPAD
|
||||
static bool MinidumpCallback(const char *dump_dir, const char *minidump_id, void *context, bool succeeded)
|
||||
{
|
||||
/* As backtrace() is only implemented in 10.5 or later,
|
||||
* we're rolling our own here. Mostly based on
|
||||
* http://stackoverflow.com/questions/289820/getting-the-current-stack-trace-on-mac-os-x
|
||||
* and some details looked up in the Darwin sources. */
|
||||
buffer += seprintf(buffer, last, "\nStacktrace:\n");
|
||||
CrashLogOSX *crashlog = reinterpret_cast<CrashLogOSX *>(context);
|
||||
|
||||
void **frame;
|
||||
#if defined(__ppc__) || defined(__ppc64__)
|
||||
/* Apple says __builtin_frame_address can be broken on PPC. */
|
||||
__asm__ volatile("mr %0, r1" : "=r" (frame));
|
||||
#else
|
||||
frame = (void **)__builtin_frame_address(0);
|
||||
crashlog->crashdump_filename = crashlog->CreateFileName(".dmp");
|
||||
std::rename(fmt::format("{}/{}.dmp", dump_dir, minidump_id).c_str(), crashlog->crashdump_filename.c_str());
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
bool WriteCrashDump() override
|
||||
{
|
||||
return google_breakpad::ExceptionHandler::WriteMinidump(_personal_dir, MinidumpCallback, this);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; frame != nullptr && i < MAX_STACK_FRAMES; i++) {
|
||||
/* Get IP for current stack frame. */
|
||||
#if defined(__ppc__) || defined(__ppc64__)
|
||||
void *ip = frame[2];
|
||||
#else
|
||||
void *ip = frame[1];
|
||||
#endif
|
||||
if (ip == nullptr) break;
|
||||
/* virtual */ bool TryExecute(std::string_view section_name, std::function<bool()> &&func) override
|
||||
{
|
||||
this->try_execute_active = true;
|
||||
|
||||
/* Print running index. */
|
||||
buffer += seprintf(buffer, last, " [%02d]", i);
|
||||
/* Setup a longjump in case a crash happens. */
|
||||
if (setjmp(this->internal_fault_jmp_buf) != 0) {
|
||||
fmt::print("Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
|
||||
|
||||
Dl_info dli;
|
||||
bool dl_valid = dladdr(ip, &dli) != 0;
|
||||
|
||||
const char *fname = "???";
|
||||
if (dl_valid && dli.dli_fname) {
|
||||
/* Valid image name? Extract filename from the complete path. */
|
||||
const char *s = strrchr(dli.dli_fname, '/');
|
||||
if (s != nullptr) {
|
||||
fname = s + 1;
|
||||
} else {
|
||||
fname = dli.dli_fname;
|
||||
}
|
||||
/* Reset the signals and continue on. The handler is responsible for dealing with the crash. */
|
||||
sigset_t sigs;
|
||||
sigemptyset(&sigs);
|
||||
for (int signum : _signals_to_handle) {
|
||||
sigaddset(&sigs, signum);
|
||||
}
|
||||
/* Print image name and IP. */
|
||||
buffer += seprintf(buffer, last, " %-20s " PRINTF_PTR, fname, (uintptr_t)ip);
|
||||
sigprocmask(SIG_UNBLOCK, &sigs, nullptr);
|
||||
|
||||
/* Print function offset if information is available. */
|
||||
if (dl_valid && dli.dli_sname != nullptr && dli.dli_saddr != nullptr) {
|
||||
/* Try to demangle a possible C++ symbol. */
|
||||
int status = -1;
|
||||
char *func_name = abi::__cxa_demangle(dli.dli_sname, nullptr, 0, &status);
|
||||
|
||||
long int offset = (intptr_t)ip - (intptr_t)dli.dli_saddr;
|
||||
buffer += seprintf(buffer, last, " (%s + %ld)", func_name != nullptr ? func_name : dli.dli_sname, offset);
|
||||
|
||||
free(func_name);
|
||||
}
|
||||
buffer += seprintf(buffer, last, "\n");
|
||||
|
||||
/* Get address of next stack frame. */
|
||||
void **next = (void **)frame[0];
|
||||
/* Frame address not increasing or not aligned? Broken stack, exit! */
|
||||
if (next <= frame || !IS_ALIGNED(next)) break;
|
||||
frame = next;
|
||||
this->try_execute_active = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return buffer + seprintf(buffer, last, "\n");
|
||||
bool res = func();
|
||||
this->try_execute_active = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -156,44 +115,7 @@ public:
|
||||
* A crash log is always generated by signal.
|
||||
* @param signum the signal that was caused by the crash.
|
||||
*/
|
||||
CrashLogOSX(int signum) : signum(signum)
|
||||
{
|
||||
filename_log[0] = '\0';
|
||||
filename_save[0] = '\0';
|
||||
filename_screenshot[0] = '\0';
|
||||
}
|
||||
|
||||
/** Generate the crash log. */
|
||||
bool MakeCrashLog()
|
||||
{
|
||||
char buffer[65536];
|
||||
bool ret = true;
|
||||
|
||||
printf("Crash encountered, generating crash log...\n");
|
||||
this->FillCrashLog(buffer, lastof(buffer));
|
||||
printf("%s\n", buffer);
|
||||
printf("Crash log generated.\n\n");
|
||||
|
||||
printf("Writing crash log to disk...\n");
|
||||
if (!this->WriteCrashLog(buffer, filename_log, lastof(filename_log))) {
|
||||
filename_log[0] = '\0';
|
||||
ret = false;
|
||||
}
|
||||
|
||||
printf("Writing crash savegame...\n");
|
||||
if (!this->WriteSavegame(filename_save, lastof(filename_save))) {
|
||||
filename_save[0] = '\0';
|
||||
ret = false;
|
||||
}
|
||||
|
||||
printf("Writing crash screenshot...\n");
|
||||
if (!this->WriteScreenshot(filename_screenshot, lastof(filename_screenshot))) {
|
||||
filename_screenshot[0] = '\0';
|
||||
ret = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
CrashLogOSX(int signum) : signum(signum) {}
|
||||
|
||||
/** Show a dialog with the crash information. */
|
||||
void DisplayCrashDialog() const
|
||||
@@ -201,61 +123,116 @@ public:
|
||||
static const char crash_title[] =
|
||||
"A serious fault condition occurred in the game. The game will shut down.";
|
||||
|
||||
char message[1024];
|
||||
seprintf(message, lastof(message),
|
||||
"Please send the generated crash information and the last (auto)save to the developers. "
|
||||
"This will greatly help debugging. The correct place to do this is https://github.com/OpenTTD/OpenTTD/issues.\n\n"
|
||||
"Generated file(s):\n%s\n%s\n%s",
|
||||
this->filename_log, this->filename_save, this->filename_screenshot);
|
||||
std::string message = fmt::format(
|
||||
"Please send crash.json.log, crash.dmp, and crash.sav to the developers. "
|
||||
"This will greatly help debugging.\n\n"
|
||||
"https://github.com/OpenTTD/OpenTTD/issues.\n\n"
|
||||
"{}\n{}\n{}\n{}",
|
||||
this->crashlog_filename, this->crashdump_filename, this->savegame_filename, this->screenshot_filename);
|
||||
|
||||
ShowMacDialog(crash_title, message, "Quit");
|
||||
ShowMacDialog(crash_title, message.c_str(), "Quit");
|
||||
}
|
||||
|
||||
/** Buffer to track the long jump set setup. */
|
||||
jmp_buf internal_fault_jmp_buf;
|
||||
|
||||
/** Whether we are in a TryExecute block. */
|
||||
bool try_execute_active = false;
|
||||
|
||||
/** Points to the current crash log. */
|
||||
static CrashLogOSX *current;
|
||||
};
|
||||
|
||||
/** The signals we want our crash handler to handle. */
|
||||
static const int _signals_to_handle[] = { SIGSEGV, SIGABRT, SIGFPE, SIGBUS, SIGILL, SIGSYS };
|
||||
/* static */ CrashLogOSX *CrashLogOSX::current = nullptr;
|
||||
|
||||
/**
|
||||
* Set a signal handler for all signals we want to capture.
|
||||
*
|
||||
* @param handler The handler to use.
|
||||
* @return sigset_t A sigset_t containing all signals we want to capture.
|
||||
*/
|
||||
static sigset_t SetSignals(void(*handler)(int))
|
||||
{
|
||||
sigset_t sigs;
|
||||
sigemptyset(&sigs);
|
||||
for (int signum : _signals_to_handle) {
|
||||
sigaddset(&sigs, signum);
|
||||
}
|
||||
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_flags = SA_RESTART;
|
||||
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_handler = handler;
|
||||
sa.sa_mask = sigs;
|
||||
|
||||
for (int signum : _signals_to_handle) {
|
||||
sigaction(signum, &sa, nullptr);
|
||||
}
|
||||
|
||||
return sigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for a crash that happened during the handling of a crash.
|
||||
*
|
||||
*/
|
||||
static void CDECL HandleInternalCrash(int)
|
||||
{
|
||||
if (CrashLogOSX::current == nullptr || !CrashLogOSX::current->try_execute_active) {
|
||||
fmt::print("Something went seriously wrong when creating the crash log. Aborting.\n");
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
longjmp(CrashLogOSX::current->internal_fault_jmp_buf, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for the crash handler.
|
||||
* @note Not static so it shows up in the backtrace.
|
||||
*
|
||||
* @param signum the signal that caused us to crash.
|
||||
*/
|
||||
void CDECL HandleCrash(int signum)
|
||||
static void CDECL HandleCrash(int signum)
|
||||
{
|
||||
/* Disable all handling of signals by us, so we don't go into infinite loops. */
|
||||
for (const int *i = _signals_to_handle; i != endof(_signals_to_handle); i++) {
|
||||
signal(*i, SIG_DFL);
|
||||
if (CrashLogOSX::current != nullptr) {
|
||||
CrashLog::AfterCrashLogCleanup();
|
||||
_exit(2);
|
||||
}
|
||||
|
||||
if (GamelogTestEmergency()) {
|
||||
/* Capture crashing during the handling of a crash. */
|
||||
sigset_t sigs = SetSignals(HandleInternalCrash);
|
||||
sigset_t old_sigset;
|
||||
sigprocmask(SIG_UNBLOCK, &sigs, &old_sigset);
|
||||
|
||||
if (_gamelog.TestEmergency()) {
|
||||
ShowMacDialog("A serious fault condition occurred in the game. The game will shut down.",
|
||||
"As you loaded an emergency savegame no crash information will be generated.\n",
|
||||
"Quit");
|
||||
abort();
|
||||
_exit(3);
|
||||
}
|
||||
|
||||
if (SaveloadCrashWithMissingNewGRFs()) {
|
||||
ShowMacDialog("A serious fault condition occurred in the game. The game will shut down.",
|
||||
"As you loaded an savegame for which you do not have the required NewGRFs no crash information will be generated.\n",
|
||||
"Quit");
|
||||
abort();
|
||||
_exit(3);
|
||||
}
|
||||
|
||||
CrashLogOSX log(signum);
|
||||
log.MakeCrashLog();
|
||||
CrashLogOSX *log = new CrashLogOSX(signum);
|
||||
CrashLogOSX::current = log;
|
||||
log->MakeCrashLog();
|
||||
if (VideoDriver::GetInstance() == nullptr || VideoDriver::GetInstance()->HasGUI()) {
|
||||
log.DisplayCrashDialog();
|
||||
log->DisplayCrashDialog();
|
||||
}
|
||||
|
||||
CrashLog::AfterCrashLogCleanup();
|
||||
abort();
|
||||
_exit(2);
|
||||
}
|
||||
|
||||
/* static */ void CrashLog::InitialiseCrashLog()
|
||||
{
|
||||
for (const int *i = _signals_to_handle; i != endof(_signals_to_handle); i++) {
|
||||
signal(*i, HandleCrash);
|
||||
}
|
||||
SetSignals(HandleCrash);
|
||||
}
|
||||
|
||||
/* static */ void CrashLog::InitThread()
|
||||
|
||||
+76
-96
@@ -10,85 +10,40 @@
|
||||
#include "../../stdafx.h"
|
||||
#include "../../debug.h"
|
||||
#include "font_osx.h"
|
||||
#include "../../core/math_func.hpp"
|
||||
#include "../../blitter/factory.hpp"
|
||||
#include "../../error_func.h"
|
||||
#include "../../fileio_func.h"
|
||||
#include "../../fontdetection.h"
|
||||
#include "../../string_func.h"
|
||||
#include "../../strings_func.h"
|
||||
#include "../../zoom_func.h"
|
||||
#include "macos.h"
|
||||
#include <cmath>
|
||||
|
||||
#include "../../table/control_codes.h"
|
||||
|
||||
#include "safeguards.h"
|
||||
|
||||
|
||||
#ifdef WITH_FREETYPE
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
extern FT_Library _library;
|
||||
|
||||
|
||||
FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
|
||||
{
|
||||
FT_Error err = FT_Err_Cannot_Open_Resource;
|
||||
|
||||
/* Get font reference from name. */
|
||||
UInt8 file_path[PATH_MAX];
|
||||
OSStatus os_err = -1;
|
||||
CFAutoRelease<CFStringRef> name(CFStringCreateWithCString(kCFAllocatorDefault, font_name, kCFStringEncodingUTF8));
|
||||
|
||||
/* Simply creating the font using CTFontCreateWithNameAndSize will *always* return
|
||||
* something, no matter the name. As such, we can't use it to check for existence.
|
||||
* We instead query the list of all font descriptors that match the given name which
|
||||
* does not do this stupid name fallback. */
|
||||
CFAutoRelease<CTFontDescriptorRef> name_desc(CTFontDescriptorCreateWithNameAndSize(name.get(), 0.0));
|
||||
CFAutoRelease<CFSetRef> mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast<const void **>(reinterpret_cast<const void *const *>(&kCTFontNameAttribute)), 1, &kCFTypeSetCallBacks));
|
||||
CFAutoRelease<CFArrayRef> descs(CTFontDescriptorCreateMatchingFontDescriptors(name_desc.get(), mandatory_attribs.get()));
|
||||
|
||||
/* Loop over all matches until we can get a path for one of them. */
|
||||
for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()) && os_err != noErr; i++) {
|
||||
CFAutoRelease<CTFontRef> font(CTFontCreateWithFontDescriptor((CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i), 0.0, nullptr));
|
||||
CFAutoRelease<CFURLRef> fontURL((CFURLRef)CTFontCopyAttribute(font.get(), kCTFontURLAttribute));
|
||||
if (CFURLGetFileSystemRepresentation(fontURL.get(), true, file_path, lengthof(file_path))) os_err = noErr;
|
||||
}
|
||||
|
||||
if (os_err == noErr) {
|
||||
Debug(fontcache, 3, "Font path for {}: {}", font_name, file_path);
|
||||
err = FT_New_Face(_library, (const char *)file_path, 0, face);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#endif /* WITH_FREETYPE */
|
||||
|
||||
|
||||
bool SetFallbackFont(FontCacheSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback)
|
||||
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, int, MissingGlyphSearcher *callback)
|
||||
{
|
||||
/* Determine fallback font using CoreText. This uses the language isocode
|
||||
* to find a suitable font. CoreText is available from 10.5 onwards. */
|
||||
char lang[16];
|
||||
if (strcmp(language_isocode, "zh_TW") == 0) {
|
||||
std::string lang;
|
||||
if (language_isocode == "zh_TW") {
|
||||
/* Traditional Chinese */
|
||||
strecpy(lang, "zh-Hant", lastof(lang));
|
||||
} else if (strcmp(language_isocode, "zh_CN") == 0) {
|
||||
lang = "zh-Hant";
|
||||
} else if (language_isocode == "zh_CN") {
|
||||
/* Simplified Chinese */
|
||||
strecpy(lang, "zh-Hans", lastof(lang));
|
||||
lang = "zh-Hans";
|
||||
} else {
|
||||
/* Just copy the first part of the isocode. */
|
||||
strecpy(lang, language_isocode, lastof(lang));
|
||||
char *sep = strchr(lang, '_');
|
||||
if (sep != nullptr) *sep = '\0';
|
||||
lang = language_isocode.substr(0, language_isocode.find('_'));
|
||||
}
|
||||
|
||||
/* Create a font descriptor matching the wanted language and latin (english) glyphs.
|
||||
* Can't use CFAutoRelease here for everything due to the way the dictionary has to be created. */
|
||||
CFStringRef lang_codes[2];
|
||||
lang_codes[0] = CFStringCreateWithCString(kCFAllocatorDefault, lang, kCFStringEncodingUTF8);
|
||||
lang_codes[0] = CFStringCreateWithCString(kCFAllocatorDefault, lang.c_str(), kCFStringEncodingUTF8);
|
||||
lang_codes[1] = CFSTR("en");
|
||||
CFArrayRef lang_arr = CFArrayCreate(kCFAllocatorDefault, (const void **)lang_codes, lengthof(lang_codes), &kCFTypeArrayCallBacks);
|
||||
CFAutoRelease<CFDictionaryRef> lang_attribs(CFDictionaryCreate(kCFAllocatorDefault, const_cast<const void **>(reinterpret_cast<const void *const *>(&kCTFontLanguagesAttribute)), (const void **)&lang_arr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
|
||||
@@ -225,14 +180,10 @@ void CoreTextFontCache::SetFontSize(int pixels)
|
||||
Debug(fontcache, 2, "Loaded font '{}' with size {}", this->font_name, pixels);
|
||||
}
|
||||
|
||||
GlyphID CoreTextFontCache::MapCharToGlyph(WChar key)
|
||||
GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key, bool allow_fallback)
|
||||
{
|
||||
assert(IsPrintable(key));
|
||||
|
||||
if (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) {
|
||||
return this->parent->MapCharToGlyph(key);
|
||||
}
|
||||
|
||||
/* Convert characters outside of the Basic Multilingual Plane into surrogate pairs. */
|
||||
UniChar chars[2];
|
||||
if (key >= 0x010000U) {
|
||||
@@ -247,10 +198,14 @@ GlyphID CoreTextFontCache::MapCharToGlyph(WChar key)
|
||||
return glyph[0];
|
||||
}
|
||||
|
||||
if (allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) {
|
||||
return this->parent->MapCharToGlyph(key);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const void *CoreTextFontCache::InternalGetFontTable(uint32 tag, size_t &length)
|
||||
const void *CoreTextFontCache::InternalGetFontTable(uint32_t tag, size_t &length)
|
||||
{
|
||||
CFAutoRelease<CFDataRef> data(CTFontCopyTable(this->font.get(), (CTFontTableTag)tag, kCTFontTableOptionNoOptions));
|
||||
if (!data) return nullptr;
|
||||
@@ -272,7 +227,7 @@ const Sprite *CoreTextFontCache::InternalGetGlyph(GlyphID key, bool use_aa)
|
||||
} else {
|
||||
bounds = CTFontGetBoundingRectsForGlyphs(this->font.get(), kCTFontOrientationDefault, &glyph, nullptr, 1);
|
||||
}
|
||||
if (CGRectIsNull(bounds)) usererror("Unable to render font glyph");
|
||||
if (CGRectIsNull(bounds)) UserError("Unable to render font glyph");
|
||||
|
||||
uint bb_width = (uint)std::ceil(bounds.size.width) + 1; // Sometimes the glyph bounds are too tight and cut of the last pixel after rounding.
|
||||
uint bb_height = (uint)std::ceil(bounds.size.height);
|
||||
@@ -283,16 +238,17 @@ const Sprite *CoreTextFontCache::InternalGetGlyph(GlyphID key, bool use_aa)
|
||||
uint height = std::max(1U, bb_height + shadow);
|
||||
|
||||
/* Limit glyph size to prevent overflows later on. */
|
||||
if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) usererror("Font glyph is too large");
|
||||
if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) UserError("Font glyph is too large");
|
||||
|
||||
SpriteLoader::Sprite sprite;
|
||||
SpriteLoader::SpriteCollection spritecollection;
|
||||
SpriteLoader::Sprite &sprite = spritecollection[ZOOM_LVL_NORMAL];
|
||||
sprite.AllocateData(ZOOM_LVL_NORMAL, width * height);
|
||||
sprite.type = ST_FONT;
|
||||
sprite.type = SpriteType::Font;
|
||||
sprite.colours = (use_aa ? SCC_PAL | SCC_ALPHA : SCC_PAL);
|
||||
sprite.width = width;
|
||||
sprite.height = height;
|
||||
sprite.x_offs = (int16)std::round(CGRectGetMinX(bounds));
|
||||
sprite.y_offs = this->ascender - (int16)std::ceil(CGRectGetMaxY(bounds));
|
||||
sprite.x_offs = (int16_t)std::round(CGRectGetMinX(bounds));
|
||||
sprite.y_offs = this->ascender - (int16_t)std::ceil(CGRectGetMaxY(bounds));
|
||||
|
||||
if (bounds.size.width > 0) {
|
||||
/* Glyph is not a white-space glyph. Render it to a bitmap context. */
|
||||
@@ -335,13 +291,47 @@ const Sprite *CoreTextFontCache::InternalGetGlyph(GlyphID key, bool use_aa)
|
||||
}
|
||||
|
||||
GlyphEntry new_glyph;
|
||||
new_glyph.sprite = BlitterFactory::GetCurrentBlitter()->Encode(&sprite, SimpleSpriteAlloc);
|
||||
new_glyph.sprite = BlitterFactory::GetCurrentBlitter()->Encode(spritecollection, SimpleSpriteAlloc);
|
||||
new_glyph.width = (byte)std::round(CTFontGetAdvancesForGlyphs(this->font.get(), kCTFontOrientationDefault, &glyph, nullptr, 1));
|
||||
this->SetGlyphPtr(key, &new_glyph);
|
||||
|
||||
return new_glyph.sprite;
|
||||
}
|
||||
|
||||
static CTFontDescriptorRef LoadFontFromFile(const std::string &font_name)
|
||||
{
|
||||
if (!MacOSVersionIsAtLeast(10, 6, 0)) return nullptr;
|
||||
|
||||
/* Might be a font file name, try load it. Direct font loading is
|
||||
* only supported starting on OSX 10.6. */
|
||||
CFAutoRelease<CFStringRef> path;
|
||||
|
||||
/* See if this is an absolute path. */
|
||||
if (FileExists(font_name)) {
|
||||
path.reset(CFStringCreateWithCString(kCFAllocatorDefault, font_name.c_str(), kCFStringEncodingUTF8));
|
||||
} else {
|
||||
/* Scan the search-paths to see if it can be found. */
|
||||
std::string full_font = FioFindFullPath(BASE_DIR, font_name);
|
||||
if (!full_font.empty()) {
|
||||
path.reset(CFStringCreateWithCString(kCFAllocatorDefault, full_font.c_str(), kCFStringEncodingUTF8));
|
||||
}
|
||||
}
|
||||
|
||||
if (path) {
|
||||
/* Try getting a font descriptor to see if the system can use it. */
|
||||
CFAutoRelease<CFURLRef> url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle, false));
|
||||
CFAutoRelease<CFArrayRef> descs(CTFontManagerCreateFontDescriptorsFromURL(url.get()));
|
||||
|
||||
if (descs && CFArrayGetCount(descs.get()) > 0) {
|
||||
CTFontDescriptorRef font_ref = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0);
|
||||
CFRetain(font_ref);
|
||||
return font_ref;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the TrueType font.
|
||||
* If a CoreText font description is present, e.g. from the automatic font
|
||||
@@ -362,33 +352,9 @@ void LoadCoreTextFont(FontSize fs)
|
||||
}
|
||||
|
||||
if (!font_ref && MacOSVersionIsAtLeast(10, 6, 0)) {
|
||||
/* Might be a font file name, try load it. Direct font loading is
|
||||
* only supported starting on OSX 10.6. */
|
||||
CFAutoRelease<CFStringRef> path;
|
||||
|
||||
/* See if this is an absolute path. */
|
||||
if (FileExists(settings->font)) {
|
||||
path.reset(CFStringCreateWithCString(kCFAllocatorDefault, settings->font.c_str(), kCFStringEncodingUTF8));
|
||||
} else {
|
||||
/* Scan the search-paths to see if it can be found. */
|
||||
std::string full_font = FioFindFullPath(BASE_DIR, settings->font.c_str());
|
||||
if (!full_font.empty()) {
|
||||
path.reset(CFStringCreateWithCString(kCFAllocatorDefault, full_font.c_str(), kCFStringEncodingUTF8));
|
||||
}
|
||||
}
|
||||
|
||||
if (path) {
|
||||
/* Try getting a font descriptor to see if the system can use it. */
|
||||
CFAutoRelease<CFURLRef> url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle, false));
|
||||
CFAutoRelease<CFArrayRef> descs(CTFontManagerCreateFontDescriptorsFromURL(url.get()));
|
||||
|
||||
if (descs && CFArrayGetCount(descs.get()) > 0) {
|
||||
font_ref.reset((CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0));
|
||||
CFRetain(font_ref.get());
|
||||
} else {
|
||||
ShowInfoF("Unable to load file '%s' for %s font, using default OS font selection instead", settings->font.c_str(), FontSizeToName(fs));
|
||||
}
|
||||
}
|
||||
/* Might be a font file name, try load it. */
|
||||
font_ref.reset(LoadFontFromFile(settings->font));
|
||||
if (!font_ref) ShowInfo("Unable to load file '{}' for {} font, using default OS font selection instead", settings->font, FontSizeToName(fs));
|
||||
}
|
||||
|
||||
if (!font_ref) {
|
||||
@@ -410,9 +376,23 @@ void LoadCoreTextFont(FontSize fs)
|
||||
}
|
||||
|
||||
if (!font_ref) {
|
||||
ShowInfoF("Unable to use '%s' for %s font, using sprite font instead", settings->font.c_str(), FontSizeToName(fs));
|
||||
ShowInfo("Unable to use '{}' for {} font, using sprite font instead", settings->font, FontSizeToName(fs));
|
||||
return;
|
||||
}
|
||||
|
||||
new CoreTextFontCache(fs, std::move(font_ref), settings->size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a TrueType font from a file.
|
||||
* @param fs The font size to load.
|
||||
* @param file_name Path to the font file.
|
||||
* @param size Requested font size.
|
||||
*/
|
||||
void LoadCoreTextFont(FontSize fs, const std::string &file_name, uint size)
|
||||
{
|
||||
CFAutoRelease<CTFontDescriptorRef> font_ref{LoadFontFromFile(file_name)};
|
||||
if (font_ref) {
|
||||
new CoreTextFontCache(fs, std::move(font_ref), size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,18 +23,19 @@ class CoreTextFontCache : public TrueTypeFontCache {
|
||||
|
||||
void SetFontSize(int pixels);
|
||||
const Sprite *InternalGetGlyph(GlyphID key, bool use_aa) override;
|
||||
const void *InternalGetFontTable(uint32 tag, size_t &length) override;
|
||||
const void *InternalGetFontTable(uint32_t tag, size_t &length) override;
|
||||
public:
|
||||
CoreTextFontCache(FontSize fs, CFAutoRelease<CTFontDescriptorRef> &&font, int pixels);
|
||||
~CoreTextFontCache() {}
|
||||
|
||||
void ClearFontCache() override;
|
||||
GlyphID MapCharToGlyph(WChar key) override;
|
||||
GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override;
|
||||
std::string GetFontName() override { return font_name; }
|
||||
bool IsBuiltInFont() override { return false; }
|
||||
const void *GetOSHandle() override { return font.get(); }
|
||||
};
|
||||
|
||||
void LoadCoreTextFont(FontSize fs);
|
||||
void LoadCoreTextFont(FontSize fs, const std::string &file_name, uint size);
|
||||
|
||||
#endif /* FONT_OSX_H */
|
||||
|
||||
@@ -22,7 +22,7 @@ void GetMacOSVersion(int *return_major, int *return_minor, int *return_bugfix);
|
||||
* @param bugfix bugfix version of the os. This would be 11 in the case of 10.4.11.
|
||||
* @return true if the running os is at least what we asked, false otherwise.
|
||||
*/
|
||||
static inline bool MacOSVersionIsAtLeast(long major, long minor, long bugfix)
|
||||
inline bool MacOSVersionIsAtLeast(long major, long minor, long bugfix)
|
||||
{
|
||||
int version_major, version_minor, version_bugfix;
|
||||
GetMacOSVersion(&version_major, &version_minor, &version_bugfix);
|
||||
@@ -38,6 +38,8 @@ bool IsMonospaceFont(CFStringRef name);
|
||||
|
||||
void MacOSSetThreadName(const char *name);
|
||||
|
||||
uint64_t MacOSGetPhysicalMemory();
|
||||
|
||||
|
||||
/** Deleter that calls CFRelease rather than deleting the pointer. */
|
||||
template <typename T> struct CFDeleter {
|
||||
|
||||
+14
-28
@@ -14,7 +14,6 @@
|
||||
#include "../../string_func.h"
|
||||
#include "../../fileio_func.h"
|
||||
#include <pthread.h>
|
||||
#include <array>
|
||||
|
||||
#define Rect OTTDRect
|
||||
#define Point OTTDPoint
|
||||
@@ -94,21 +93,7 @@ void GetMacOSVersion(int *return_major, int *return_minor, int *return_bugfix)
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_SDL
|
||||
|
||||
/**
|
||||
* Show the system dialogue message (SDL on MacOSX).
|
||||
*
|
||||
* @param title Window title.
|
||||
* @param message Message text.
|
||||
* @param buttonLabel Button text.
|
||||
*/
|
||||
void ShowMacDialog(const char *title, const char *message, const char *buttonLabel)
|
||||
{
|
||||
NSRunAlertPanel([ NSString stringWithUTF8String:title ], [ NSString stringWithUTF8String:message ], [ NSString stringWithUTF8String:buttonLabel ], nil, nil);
|
||||
}
|
||||
|
||||
#elif defined WITH_COCOA
|
||||
#ifdef WITH_COCOA
|
||||
|
||||
extern void CocoaDialog(const char *title, const char *message, const char *buttonLabel);
|
||||
|
||||
@@ -136,7 +121,7 @@ void ShowMacDialog(const char *title, const char *message, const char *buttonLab
|
||||
*/
|
||||
void ShowMacDialog(const char *title, const char *message, const char *buttonLabel)
|
||||
{
|
||||
fprintf(stderr, "%s: %s\n", title, message);
|
||||
fmt::print(stderr, "{}: {}\n", title, message);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -158,9 +143,9 @@ void ShowOSErrorBox(const char *buf, bool system)
|
||||
}
|
||||
}
|
||||
|
||||
void OSOpenBrowser(const char *url)
|
||||
void OSOpenBrowser(const std::string &url)
|
||||
{
|
||||
[ [ NSWorkspace sharedWorkspace ] openURL:[ NSURL URLWithString:[ NSString stringWithUTF8String:url ] ] ];
|
||||
[ [ NSWorkspace sharedWorkspace ] openURL:[ NSURL URLWithString:[ NSString stringWithUTF8String:url.c_str() ] ] ];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,25 +169,21 @@ const char *GetCurrentLocale(const char *)
|
||||
/**
|
||||
* Return the contents of the clipboard (COCOA).
|
||||
*
|
||||
* @param buffer Clipboard content.
|
||||
* @param last The pointer to the last element of the destination buffer
|
||||
* @return Whether clipboard is empty or not.
|
||||
* @return The (optional) clipboard contents.
|
||||
*/
|
||||
bool GetClipboardContents(char *buffer, const char *last)
|
||||
std::optional<std::string> GetClipboardContents()
|
||||
{
|
||||
NSPasteboard *pb = [ NSPasteboard generalPasteboard ];
|
||||
NSArray *types = [ NSArray arrayWithObject:NSPasteboardTypeString ];
|
||||
NSString *bestType = [ pb availableTypeFromArray:types ];
|
||||
|
||||
/* Clipboard has no text data available. */
|
||||
if (bestType == nil) return false;
|
||||
if (bestType == nil) return std::nullopt;
|
||||
|
||||
NSString *string = [ pb stringForType:NSPasteboardTypeString ];
|
||||
if (string == nil || [ string length ] == 0) return false;
|
||||
if (string == nil || [ string length ] == 0) return std::nullopt;
|
||||
|
||||
strecpy(buffer, [ string UTF8String ], last);
|
||||
|
||||
return true;
|
||||
return [ string UTF8String ];
|
||||
}
|
||||
|
||||
/** Set the application's bundle directory.
|
||||
@@ -272,3 +253,8 @@ void MacOSSetThreadName(const char *name)
|
||||
[ cur performSelector:@selector(setName:) withObject:[ NSString stringWithUTF8String:name ] ];
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t MacOSGetPhysicalMemory()
|
||||
{
|
||||
return [ [ NSProcessInfo processInfo ] physicalMemory ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file unix_main.cpp Main entry for Mac OSX. */
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../openttd.h"
|
||||
#include "../../crashlog.h"
|
||||
#include "../../core/random_func.hpp"
|
||||
#include "../../string_func.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "macos.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
void CocoaSetupAutoreleasePool();
|
||||
void CocoaReleaseAutoreleasePool();
|
||||
|
||||
int CDECL main(int argc, char *argv[])
|
||||
{
|
||||
/* Make sure our arguments contain only valid UTF-8 characters. */
|
||||
for (int i = 0; i < argc; i++) StrMakeValidInPlace(argv[i]);
|
||||
|
||||
CocoaSetupAutoreleasePool();
|
||||
/* This is passed if we are launched by double-clicking */
|
||||
if (argc >= 2 && strncmp(argv[1], "-psn", 4) == 0) {
|
||||
argv[1] = nullptr;
|
||||
argc = 1;
|
||||
}
|
||||
|
||||
CrashLog::InitialiseCrashLog();
|
||||
|
||||
SetRandomSeed(time(nullptr));
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
int ret = openttd_main(argc, argv);
|
||||
|
||||
CocoaReleaseAutoreleasePool();
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "../../fontcache.h"
|
||||
#include "../../zoom_func.h"
|
||||
#include "macos.h"
|
||||
#include <cmath>
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
@@ -25,10 +24,10 @@
|
||||
extern "C" {
|
||||
typedef const struct __CTRunDelegate * CTRunDelegateRef;
|
||||
|
||||
typedef void (*CTRunDelegateDeallocateCallback) (void* refCon);
|
||||
typedef CGFloat (*CTRunDelegateGetAscentCallback) (void* refCon);
|
||||
typedef CGFloat (*CTRunDelegateGetDescentCallback) (void* refCon);
|
||||
typedef CGFloat (*CTRunDelegateGetWidthCallback) (void* refCon);
|
||||
typedef void (*CTRunDelegateDeallocateCallback) (void *refCon);
|
||||
typedef CGFloat (*CTRunDelegateGetAscentCallback) (void *refCon);
|
||||
typedef CGFloat (*CTRunDelegateGetDescentCallback) (void *refCon);
|
||||
typedef CGFloat (*CTRunDelegateGetWidthCallback) (void *refCon);
|
||||
typedef struct {
|
||||
CFIndex version;
|
||||
CTRunDelegateDeallocateCallback dealloc;
|
||||
@@ -44,7 +43,7 @@ extern "C" {
|
||||
|
||||
extern const CFStringRef kCTRunDelegateAttributeName AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
|
||||
|
||||
CTRunDelegateRef CTRunDelegateCreate(const CTRunDelegateCallbacks* callbacks, void* refCon) AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
|
||||
CTRunDelegateRef CTRunDelegateCreate(const CTRunDelegateCallbacks *callbacks, void *refCon) AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
|
||||
}
|
||||
#endif /* HAVE_OSX_109_SDK */
|
||||
|
||||
@@ -61,7 +60,7 @@ class CoreTextParagraphLayout : public ParagraphLayouter {
|
||||
private:
|
||||
const CoreTextParagraphLayoutFactory::CharType *text_buffer;
|
||||
ptrdiff_t length;
|
||||
const FontMap& font_map;
|
||||
const FontMap &font_map;
|
||||
|
||||
CFAutoRelease<CTTypesetterRef> typesetter;
|
||||
|
||||
@@ -72,7 +71,7 @@ public:
|
||||
class CoreTextVisualRun : public ParagraphLayouter::VisualRun {
|
||||
private:
|
||||
std::vector<GlyphID> glyphs;
|
||||
std::vector<float> positions;
|
||||
std::vector<Point> positions;
|
||||
std::vector<int> glyph_to_char;
|
||||
|
||||
int total_advance = 0;
|
||||
@@ -82,9 +81,9 @@ public:
|
||||
CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff);
|
||||
CoreTextVisualRun(CoreTextVisualRun &&other) = default;
|
||||
|
||||
const GlyphID *GetGlyphs() const override { return &this->glyphs[0]; }
|
||||
const float *GetPositions() const override { return &this->positions[0]; }
|
||||
const int *GetGlyphToCharMap() const override { return &this->glyph_to_char[0]; }
|
||||
const std::vector<GlyphID> &GetGlyphs() const override { return this->glyphs; }
|
||||
const std::vector<Point> &GetPositions() const override { return this->positions; }
|
||||
const std::vector<int> &GetGlyphToCharMap() const override { return this->glyph_to_char; }
|
||||
|
||||
const Font *GetFont() const override { return this->font; }
|
||||
int GetLeading() const override { return this->font->fc->GetHeight(); }
|
||||
@@ -103,8 +102,7 @@ public:
|
||||
|
||||
/* Extract font information for this run. */
|
||||
CFRange chars = CTRunGetStringRange(run);
|
||||
auto map = fontMapping.begin();
|
||||
while (map < fontMapping.end() - 1 && map->first <= chars.location) map++;
|
||||
auto map = fontMapping.upper_bound(chars.location);
|
||||
|
||||
this->emplace_back(run, map->second, buff);
|
||||
}
|
||||
@@ -115,7 +113,7 @@ public:
|
||||
int CountRuns() const override { return this->size(); }
|
||||
const VisualRun &GetVisualRun(int run) const override { return this->at(run); }
|
||||
|
||||
int GetInternalCharLength(WChar c) const override
|
||||
int GetInternalCharLength(char32_t c) const override
|
||||
{
|
||||
/* CoreText uses UTF-16 internally which means we need to account for surrogate pairs. */
|
||||
return c >= 0x010000U ? 2 : 1;
|
||||
@@ -140,7 +138,7 @@ public:
|
||||
static CGFloat SpriteFontGetWidth(void *ref_con)
|
||||
{
|
||||
FontSize fs = (FontSize)((size_t)ref_con >> 24);
|
||||
WChar c = (WChar)((size_t)ref_con & 0xFFFFFF);
|
||||
char32_t c = (char32_t)((size_t)ref_con & 0xFFFFFF);
|
||||
|
||||
return GetGlyphWidth(fs, c);
|
||||
}
|
||||
@@ -170,6 +168,9 @@ static CTRunDelegateCallbacks _sprite_font_callback = {
|
||||
CFAutoRelease<CFStringRef> base(CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, buff, length, kCFAllocatorNull));
|
||||
CFAttributedStringReplaceString(str.get(), CFRangeMake(0, 0), base.get());
|
||||
|
||||
const UniChar replacment_char = 0xFFFC;
|
||||
CFAutoRelease<CFStringRef> replacment_str(CFStringCreateWithCharacters(kCFAllocatorDefault, &replacment_char, 1));
|
||||
|
||||
/* Apply font and colour ranges to our string. This is important to make sure
|
||||
* that we get proper glyph boundaries on style changes. */
|
||||
int last = 0;
|
||||
@@ -187,14 +188,16 @@ static CTRunDelegateCallbacks _sprite_font_callback = {
|
||||
}
|
||||
CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, i.first - last), kCTFontAttributeName, font);
|
||||
|
||||
CGColorRef color = CGColorCreateGenericGray((uint8)i.second->colour / 255.0f, 1.0f); // We don't care about the real colours, just that they are different.
|
||||
CGColorRef color = CGColorCreateGenericGray((uint8_t)i.second->colour / 255.0f, 1.0f); // We don't care about the real colours, just that they are different.
|
||||
CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, i.first - last), kCTForegroundColorAttributeName, color);
|
||||
CGColorRelease(color);
|
||||
|
||||
/* Install a size callback for our special sprite glyphs. */
|
||||
/* Install a size callback for our special private-use sprite glyphs in case the font does not provide them. */
|
||||
for (ssize_t c = last; c < i.first; c++) {
|
||||
if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END) {
|
||||
if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END && i.second->fc->MapCharToGlyph(buff[c], false) == 0) {
|
||||
CFAutoRelease<CTRunDelegateRef> del(CTRunDelegateCreate(&_sprite_font_callback, (void *)(size_t)(buff[c] | (i.second->fc->GetSize() << 24))));
|
||||
/* According to the offical documentation, if a run delegate is used, the char should always be 0xFFFC. */
|
||||
CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacment_str.get());
|
||||
CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get());
|
||||
}
|
||||
}
|
||||
@@ -237,25 +240,25 @@ CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font
|
||||
|
||||
CGPoint pts[this->glyphs.size()];
|
||||
CTRunGetPositions(run, CFRangeMake(0, 0), pts);
|
||||
this->positions.resize(this->glyphs.size() * 2 + 2);
|
||||
this->positions.reserve(this->glyphs.size() + 1);
|
||||
|
||||
/* Convert glyph array to our data type. At the same time, substitute
|
||||
* the proper glyphs for our private sprite glyphs. */
|
||||
CGGlyph gl[this->glyphs.size()];
|
||||
CTRunGetGlyphs(run, CFRangeMake(0, 0), gl);
|
||||
for (size_t i = 0; i < this->glyphs.size(); i++) {
|
||||
if (buff[this->glyph_to_char[i]] >= SCC_SPRITE_START && buff[this->glyph_to_char[i]] <= SCC_SPRITE_END) {
|
||||
if (buff[this->glyph_to_char[i]] >= SCC_SPRITE_START && buff[this->glyph_to_char[i]] <= SCC_SPRITE_END && (gl[i] == 0 || gl[i] == 3)) {
|
||||
/* A glyph of 0 indidicates not found, while apparently 3 is what char 0xFFFC maps to. */
|
||||
this->glyphs[i] = font->fc->MapCharToGlyph(buff[this->glyph_to_char[i]]);
|
||||
this->positions[i * 2 + 0] = pts[i].x;
|
||||
this->positions[i * 2 + 1] = (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2; // Align sprite font to centre
|
||||
this->positions.emplace_back(pts[i].x, (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2); // Align sprite font to centre
|
||||
} else {
|
||||
this->glyphs[i] = gl[i];
|
||||
this->positions[i * 2 + 0] = pts[i].x;
|
||||
this->positions[i * 2 + 1] = pts[i].y;
|
||||
this->positions.emplace_back(pts[i].x, pts[i].y);
|
||||
}
|
||||
}
|
||||
this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nullptr, nullptr, nullptr));
|
||||
this->positions[this->glyphs.size() * 2] = this->positions[0] + this->total_advance;
|
||||
/* End-of-run position. */
|
||||
this->positions.emplace_back(this->positions.front().x + this->total_advance, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,7 +281,7 @@ int CoreTextParagraphLayout::CoreTextLine::GetLeading() const
|
||||
*/
|
||||
int CoreTextParagraphLayout::CoreTextLine::GetWidth() const
|
||||
{
|
||||
if (this->size() == 0) return 0;
|
||||
if (this->empty()) return 0;
|
||||
|
||||
int total_width = 0;
|
||||
for (const auto &run : *this) {
|
||||
@@ -322,15 +325,15 @@ void MacOSSetCurrentLocaleName(const char *iso_code)
|
||||
* @param s2 Second string to compare.
|
||||
* @return 1 if s1 < s2, 2 if s1 == s2, 3 if s1 > s2, or 0 if not supported by the OS.
|
||||
*/
|
||||
int MacOSStringCompare(const char *s1, const char *s2)
|
||||
int MacOSStringCompare(std::string_view s1, std::string_view s2)
|
||||
{
|
||||
static bool supported = MacOSVersionIsAtLeast(10, 5, 0);
|
||||
if (!supported) return 0;
|
||||
|
||||
CFStringCompareFlags flags = kCFCompareCaseInsensitive | kCFCompareNumerically | kCFCompareLocalized | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
|
||||
|
||||
CFAutoRelease<CFStringRef> cf1(CFStringCreateWithCString(kCFAllocatorDefault, s1, kCFStringEncodingUTF8));
|
||||
CFAutoRelease<CFStringRef> cf2(CFStringCreateWithCString(kCFAllocatorDefault, s2, kCFStringEncodingUTF8));
|
||||
CFAutoRelease<CFStringRef> cf1(CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)s1.data(), s1.size(), kCFStringEncodingUTF8, false));
|
||||
CFAutoRelease<CFStringRef> cf2(CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)s2.data(), s2.size(), kCFStringEncodingUTF8, false));
|
||||
|
||||
/* If any CFString could not be created (e.g., due to UTF8 invalid chars), return OS unsupported functionality */
|
||||
if (cf1 == nullptr || cf2 == nullptr) return 0;
|
||||
@@ -338,6 +341,31 @@ int MacOSStringCompare(const char *s1, const char *s2)
|
||||
return (int)CFStringCompareWithOptionsAndLocale(cf1.get(), cf2.get(), CFRangeMake(0, CFStringGetLength(cf1.get())), flags, _osx_locale.get()) + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search if a string is contained in another string using the current locale.
|
||||
*
|
||||
* @param str String to search in.
|
||||
* @param value String to search for.
|
||||
* @param case_insensitive Search case-insensitive.
|
||||
* @return 1 if value was found, 0 if it was not found, or -1 if not supported by the OS.
|
||||
*/
|
||||
int MacOSStringContains(const std::string_view str, const std::string_view value, bool case_insensitive)
|
||||
{
|
||||
static bool supported = MacOSVersionIsAtLeast(10, 5, 0);
|
||||
if (!supported) return -1;
|
||||
|
||||
CFStringCompareFlags flags = kCFCompareLocalized | kCFCompareWidthInsensitive;
|
||||
if (case_insensitive) flags |= kCFCompareCaseInsensitive;
|
||||
|
||||
CFAutoRelease<CFStringRef> cf_str(CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)str.data(), str.size(), kCFStringEncodingUTF8, false));
|
||||
CFAutoRelease<CFStringRef> cf_value(CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)value.data(), value.size(), kCFStringEncodingUTF8, false));
|
||||
|
||||
/* If any CFString could not be created (e.g., due to UTF8 invalid chars), return OS unsupported functionality */
|
||||
if (cf_str == nullptr || cf_value == nullptr) return -1;
|
||||
|
||||
return CFStringFindWithOptionsAndLocale(cf_str.get(), cf_value.get(), CFRangeMake(0, CFStringGetLength(cf_str.get())), flags, _osx_locale.get(), nullptr) ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
/* virtual */ void OSXStringIterator::SetString(const char *s)
|
||||
{
|
||||
@@ -353,7 +381,7 @@ int MacOSStringCompare(const char *s1, const char *s2)
|
||||
while (*s != '\0') {
|
||||
size_t idx = s - string_base;
|
||||
|
||||
WChar c = Utf8Consume(&s);
|
||||
char32_t c = Utf8Consume(&s);
|
||||
if (c < 0x10000) {
|
||||
utf16_str.push_back((UniChar)c);
|
||||
} else {
|
||||
@@ -369,7 +397,7 @@ int MacOSStringCompare(const char *s1, const char *s2)
|
||||
/* Query CoreText for word and cluster break information. */
|
||||
this->str_info.resize(utf16_to_utf8.size());
|
||||
|
||||
if (utf16_str.size() > 0) {
|
||||
if (!utf16_str.empty()) {
|
||||
CFAutoRelease<CFStringRef> str(CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &utf16_str[0], utf16_str.size(), kCFAllocatorNull));
|
||||
|
||||
/* Get cluster breaks. */
|
||||
@@ -444,9 +472,9 @@ int MacOSStringCompare(const char *s1, const char *s2)
|
||||
return this->utf16_to_utf8[this->cur_pos];
|
||||
}
|
||||
|
||||
/* static */ StringIterator *OSXStringIterator::Create()
|
||||
/* static */ std::unique_ptr<StringIterator> OSXStringIterator::Create()
|
||||
{
|
||||
if (!MacOSVersionIsAtLeast(10, 5, 0)) return nullptr;
|
||||
|
||||
return new OSXStringIterator();
|
||||
return std::make_unique<OSXStringIterator>();
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include "../../gfx_layout.h"
|
||||
#include "../../string_base.h"
|
||||
#include <vector>
|
||||
|
||||
/** String iterator using CoreText as a backend. */
|
||||
class OSXStringIterator : public StringIterator {
|
||||
@@ -33,7 +32,7 @@ public:
|
||||
size_t Next(IterType what) override;
|
||||
size_t Prev(IterType what) override;
|
||||
|
||||
static StringIterator *Create();
|
||||
static std::unique_ptr<StringIterator> Create();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -62,8 +61,9 @@ public:
|
||||
* @param c The character to add.
|
||||
* @return The number of buffer spaces that were used.
|
||||
*/
|
||||
static size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, WChar c)
|
||||
static size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, char32_t c)
|
||||
{
|
||||
assert(buff < buffer_last);
|
||||
if (c >= 0x010000U) {
|
||||
/* Character is encoded using surrogates in UTF-16. */
|
||||
if (buff + 1 <= buffer_last) {
|
||||
@@ -83,7 +83,8 @@ public:
|
||||
|
||||
void MacOSResetScriptCache(FontSize size);
|
||||
void MacOSSetCurrentLocaleName(const char *iso_code);
|
||||
int MacOSStringCompare(const char *s1, const char *s2);
|
||||
int MacOSStringCompare(std::string_view s1, std::string_view s2);
|
||||
int MacOSStringContains(const std::string_view str, const std::string_view value, bool case_insensitive);
|
||||
|
||||
void MacOSRegisterExternalFont(const char *file_path);
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file survey_osx.cpp OSX implementation of OS-specific survey information. */
|
||||
|
||||
#include "../../stdafx.h"
|
||||
|
||||
#include "../../3rdparty/fmt/format.h"
|
||||
#include "../../survey.h"
|
||||
#include "macos.h"
|
||||
|
||||
#include <mach-o/arch.h>
|
||||
#include <thread>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
void SurveyOS(nlohmann::json &json)
|
||||
{
|
||||
int ver_maj, ver_min, ver_bug;
|
||||
GetMacOSVersion(&ver_maj, &ver_min, &ver_bug);
|
||||
|
||||
const NXArchInfo *arch = NXGetLocalArchInfo();
|
||||
|
||||
json["os"] = "MacOS";
|
||||
json["release"] = fmt::format("{}.{}.{}", ver_maj, ver_min, ver_bug);
|
||||
json["machine"] = arch != nullptr ? arch->description : "unknown";
|
||||
json["min_ver"] = MAC_OS_X_VERSION_MIN_REQUIRED;
|
||||
json["max_ver"] = MAC_OS_X_VERSION_MAX_ALLOWED;
|
||||
|
||||
json["memory"] = SurveyMemoryToText(MacOSGetPhysicalMemory());
|
||||
json["hardware_concurrency"] = std::thread::hardware_concurrency();
|
||||
}
|
||||
@@ -1,14 +1,20 @@
|
||||
add_files(
|
||||
crashlog_unix.cpp
|
||||
CONDITION UNIX AND NOT APPLE AND NOT OPTION_OS2
|
||||
survey_unix.cpp
|
||||
CONDITION UNIX AND NOT APPLE
|
||||
)
|
||||
|
||||
add_files(
|
||||
library_loader_unix.cpp
|
||||
unix.cpp
|
||||
CONDITION UNIX AND NOT OPTION_OS2
|
||||
CONDITION UNIX
|
||||
)
|
||||
|
||||
add_files(
|
||||
font_unix.cpp
|
||||
CONDITION Fontconfig_FOUND
|
||||
)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_sources(openttd PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/unix_main.cpp)
|
||||
endif()
|
||||
|
||||
+138
-101
@@ -9,28 +9,37 @@
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../crashlog.h"
|
||||
#include "../../fileio_func.h"
|
||||
#include "../../string_func.h"
|
||||
#include "../../gamelog.h"
|
||||
#include "../../saveload/saveload.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#if defined(__GLIBC__)
|
||||
/* Execinfo (and thus making stacktraces) is a GNU extension */
|
||||
# include <execinfo.h>
|
||||
#elif defined(SUNOS)
|
||||
# include <ucontext.h>
|
||||
# include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#if defined(__NetBSD__)
|
||||
#ifdef WITH_UNOFFICIAL_BREAKPAD
|
||||
# include <client/linux/handler/exception_handler.h>
|
||||
#endif
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
# include <emscripten.h>
|
||||
/* We avoid abort(), as it is a SIGBART, and use _exit() instead. But emscripten doesn't know _exit(). */
|
||||
# define _exit emscripten_force_exit
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/** The signals we want our crash handler to handle. */
|
||||
static constexpr int _signals_to_handle[] = { SIGSEGV, SIGABRT, SIGFPE, SIGBUS, SIGILL, SIGQUIT };
|
||||
|
||||
/**
|
||||
* Unix implementation for the crash logger.
|
||||
*/
|
||||
@@ -38,97 +47,69 @@ class CrashLogUnix : public CrashLog {
|
||||
/** Signal that has been thrown. */
|
||||
int signum;
|
||||
|
||||
char *LogOSVersion(char *buffer, const char *last) const override
|
||||
void SurveyCrash(nlohmann::json &survey) const override
|
||||
{
|
||||
struct utsname name;
|
||||
if (uname(&name) < 0) {
|
||||
return buffer + seprintf(buffer, last, "Could not get OS version: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
return buffer + seprintf(buffer, last,
|
||||
"Operating system:\n"
|
||||
" Name: %s\n"
|
||||
" Release: %s\n"
|
||||
" Version: %s\n"
|
||||
" Machine: %s\n",
|
||||
name.sysname,
|
||||
name.release,
|
||||
name.version,
|
||||
name.machine
|
||||
);
|
||||
survey["id"] = signum;
|
||||
survey["reason"] = strsignal(signum);
|
||||
}
|
||||
|
||||
char *LogError(char *buffer, const char *last, const char *message) const override
|
||||
void SurveyStacktrace([[maybe_unused]] nlohmann::json &survey) const override
|
||||
{
|
||||
return buffer + seprintf(buffer, last,
|
||||
"Crash reason:\n"
|
||||
" Signal: %s (%d)\n"
|
||||
" Message: %s\n\n",
|
||||
strsignal(this->signum),
|
||||
this->signum,
|
||||
message == nullptr ? "<none>" : message
|
||||
);
|
||||
}
|
||||
|
||||
#if defined(SUNOS)
|
||||
/** Data needed while walking up the stack */
|
||||
struct StackWalkerParams {
|
||||
char **bufptr; ///< Buffer
|
||||
const char *last; ///< End of buffer
|
||||
int counter; ///< We are at counter-th stack level
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used while walking up the stack.
|
||||
* @param pc program counter
|
||||
* @param sig 'active' signal (unused)
|
||||
* @param params parameters
|
||||
* @return always 0, continue walking up the stack
|
||||
*/
|
||||
static int SunOSStackWalker(uintptr_t pc, int sig, void *params)
|
||||
{
|
||||
StackWalkerParams *wp = (StackWalkerParams *)params;
|
||||
|
||||
/* Resolve program counter to file and nearest symbol (if possible) */
|
||||
Dl_info dli;
|
||||
if (dladdr((void *)pc, &dli) != 0) {
|
||||
*wp->bufptr += seprintf(*wp->bufptr, wp->last, " [%02i] %s(%s+0x%x) [0x%x]\n",
|
||||
wp->counter, dli.dli_fname, dli.dli_sname, (int)((byte *)pc - (byte *)dli.dli_saddr), (uint)pc);
|
||||
} else {
|
||||
*wp->bufptr += seprintf(*wp->bufptr, wp->last, " [%02i] [0x%x]\n", wp->counter, (uint)pc);
|
||||
}
|
||||
wp->counter++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
char *LogStacktrace(char *buffer, const char *last) const override
|
||||
{
|
||||
buffer += seprintf(buffer, last, "Stacktrace:\n");
|
||||
#if defined(__GLIBC__)
|
||||
void *trace[64];
|
||||
int trace_size = backtrace(trace, lengthof(trace));
|
||||
|
||||
survey = nlohmann::json::array();
|
||||
|
||||
char **messages = backtrace_symbols(trace, trace_size);
|
||||
for (int i = 0; i < trace_size; i++) {
|
||||
buffer += seprintf(buffer, last, " [%02i] %s\n", i, messages[i]);
|
||||
survey.push_back(messages[i]);
|
||||
}
|
||||
free(messages);
|
||||
#elif defined(SUNOS)
|
||||
ucontext_t uc;
|
||||
if (getcontext(&uc) != 0) {
|
||||
buffer += seprintf(buffer, last, " getcontext() failed\n\n");
|
||||
return buffer;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_UNOFFICIAL_BREAKPAD
|
||||
static bool MinidumpCallback(const google_breakpad::MinidumpDescriptor &descriptor, void *context, bool succeeded)
|
||||
{
|
||||
CrashLogUnix *crashlog = reinterpret_cast<CrashLogUnix *>(context);
|
||||
|
||||
crashlog->crashdump_filename = crashlog->CreateFileName(".dmp");
|
||||
std::rename(descriptor.path(), crashlog->crashdump_filename.c_str());
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
bool WriteCrashDump() override
|
||||
{
|
||||
return google_breakpad::ExceptionHandler::WriteMinidump(_personal_dir, MinidumpCallback, this);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* virtual */ bool TryExecute(std::string_view section_name, std::function<bool()> &&func) override
|
||||
{
|
||||
this->try_execute_active = true;
|
||||
|
||||
/* Setup a longjump in case a crash happens. */
|
||||
if (setjmp(this->internal_fault_jmp_buf) != 0) {
|
||||
fmt::print("Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
|
||||
|
||||
/* Reset the signals and continue on. The handler is responsible for dealing with the crash. */
|
||||
sigset_t sigs;
|
||||
sigemptyset(&sigs);
|
||||
for (int signum : _signals_to_handle) {
|
||||
sigaddset(&sigs, signum);
|
||||
}
|
||||
sigprocmask(SIG_UNBLOCK, &sigs, nullptr);
|
||||
|
||||
this->try_execute_active = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
StackWalkerParams wp = { &buffer, last, 0 };
|
||||
walkcontext(&uc, &CrashLogUnix::SunOSStackWalker, &wp);
|
||||
#else
|
||||
buffer += seprintf(buffer, last, " Not supported.\n");
|
||||
#endif
|
||||
return buffer + seprintf(buffer, last, "\n");
|
||||
bool res = func();
|
||||
this->try_execute_active = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* A crash log is always generated by signal.
|
||||
@@ -138,48 +119,104 @@ public:
|
||||
signum(signum)
|
||||
{
|
||||
}
|
||||
|
||||
/** Buffer to track the long jump set setup. */
|
||||
jmp_buf internal_fault_jmp_buf;
|
||||
|
||||
/** Whether we are in a TryExecute block. */
|
||||
bool try_execute_active = false;
|
||||
|
||||
/** Points to the current crash log. */
|
||||
static CrashLogUnix *current;
|
||||
};
|
||||
|
||||
/** The signals we want our crash handler to handle. */
|
||||
static const int _signals_to_handle[] = { SIGSEGV, SIGABRT, SIGFPE, SIGBUS, SIGILL };
|
||||
/* static */ CrashLogUnix *CrashLogUnix::current = nullptr;
|
||||
|
||||
/**
|
||||
* Set a signal handler for all signals we want to capture.
|
||||
*
|
||||
* @param handler The handler to use.
|
||||
* @return sigset_t A sigset_t containing all signals we want to capture.
|
||||
*/
|
||||
static sigset_t SetSignals(void(*handler)(int))
|
||||
{
|
||||
sigset_t sigs;
|
||||
sigemptyset(&sigs);
|
||||
for (int signum : _signals_to_handle) {
|
||||
sigaddset(&sigs, signum);
|
||||
}
|
||||
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_flags = SA_RESTART;
|
||||
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_handler = handler;
|
||||
sa.sa_mask = sigs;
|
||||
|
||||
for (int signum : _signals_to_handle) {
|
||||
sigaction(signum, &sa, nullptr);
|
||||
}
|
||||
|
||||
return sigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for a crash that happened during the handling of a crash.
|
||||
*
|
||||
* @param signum the signal that caused us to crash.
|
||||
*/
|
||||
static void CDECL HandleInternalCrash([[maybe_unused]] int signum)
|
||||
{
|
||||
if (CrashLogUnix::current == nullptr || !CrashLogUnix::current->try_execute_active) {
|
||||
fmt::print("Something went seriously wrong when creating the crash log. Aborting.\n");
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
longjmp(CrashLogUnix::current->internal_fault_jmp_buf, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for the crash handler.
|
||||
* @note Not static so it shows up in the backtrace.
|
||||
*
|
||||
* @param signum the signal that caused us to crash.
|
||||
*/
|
||||
static void CDECL HandleCrash(int signum)
|
||||
{
|
||||
/* Disable all handling of signals by us, so we don't go into infinite loops. */
|
||||
for (const int *i = _signals_to_handle; i != endof(_signals_to_handle); i++) {
|
||||
signal(*i, SIG_DFL);
|
||||
if (CrashLogUnix::current != nullptr) {
|
||||
CrashLog::AfterCrashLogCleanup();
|
||||
_exit(2);
|
||||
}
|
||||
|
||||
if (GamelogTestEmergency()) {
|
||||
printf("A serious fault condition occurred in the game. The game will shut down.\n");
|
||||
printf("As you loaded an emergency savegame no crash information will be generated.\n");
|
||||
abort();
|
||||
/* Capture crashing during the handling of a crash. */
|
||||
sigset_t sigs = SetSignals(HandleInternalCrash);
|
||||
sigset_t old_sigset;
|
||||
sigprocmask(SIG_UNBLOCK, &sigs, &old_sigset);
|
||||
|
||||
if (_gamelog.TestEmergency()) {
|
||||
fmt::print("A serious fault condition occurred in the game. The game will shut down.\n");
|
||||
fmt::print("As you loaded an emergency savegame no crash information will be generated.\n");
|
||||
_exit(3);
|
||||
}
|
||||
|
||||
if (SaveloadCrashWithMissingNewGRFs()) {
|
||||
printf("A serious fault condition occurred in the game. The game will shut down.\n");
|
||||
printf("As you loaded an savegame for which you do not have the required NewGRFs\n");
|
||||
printf("no crash information will be generated.\n");
|
||||
abort();
|
||||
fmt::print("A serious fault condition occurred in the game. The game will shut down.\n");
|
||||
fmt::print("As you loaded an savegame for which you do not have the required NewGRFs\n");
|
||||
fmt::print("no crash information will be generated.\n");
|
||||
_exit(3);
|
||||
}
|
||||
|
||||
CrashLogUnix log(signum);
|
||||
log.MakeCrashLog();
|
||||
CrashLogUnix *log = new CrashLogUnix(signum);
|
||||
CrashLogUnix::current = log;
|
||||
log->MakeCrashLog();
|
||||
|
||||
CrashLog::AfterCrashLogCleanup();
|
||||
abort();
|
||||
_exit(2);
|
||||
}
|
||||
|
||||
/* static */ void CrashLog::InitialiseCrashLog()
|
||||
{
|
||||
for (const int *i = _signals_to_handle; i != endof(_signals_to_handle); i++) {
|
||||
signal(*i, HandleCrash);
|
||||
}
|
||||
SetSignals(HandleCrash);
|
||||
}
|
||||
|
||||
/* static */ void CrashLog::InitThread()
|
||||
|
||||
+40
-39
@@ -17,13 +17,27 @@
|
||||
|
||||
#include "safeguards.h"
|
||||
|
||||
#ifdef WITH_FREETYPE
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
extern FT_Library _library;
|
||||
|
||||
/**
|
||||
* Split the font name into the font family and style. These fields are separated by a comma,
|
||||
* but the style does not necessarily need to exist.
|
||||
* @param font_name The font name.
|
||||
* @return The font family and style.
|
||||
*/
|
||||
static std::tuple<std::string, std::string> SplitFontFamilyAndStyle(std::string_view font_name)
|
||||
{
|
||||
auto separator = font_name.find(',');
|
||||
if (separator == std::string_view::npos) return { std::string(font_name), std::string() };
|
||||
|
||||
auto begin = font_name.find_first_not_of("\t ", separator + 1);
|
||||
if (begin == std::string_view::npos) return { std::string(font_name.substr(0, separator)), std::string() };
|
||||
|
||||
return { std::string(font_name.substr(0, separator)), std::string(font_name.substr(begin)) };
|
||||
}
|
||||
|
||||
FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
|
||||
{
|
||||
@@ -37,39 +51,26 @@ FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
|
||||
auto fc_instance = FcConfigReference(nullptr);
|
||||
assert(fc_instance != nullptr);
|
||||
|
||||
FcPattern *match;
|
||||
FcPattern *pat;
|
||||
FcFontSet *fs;
|
||||
FcResult result;
|
||||
char *font_style;
|
||||
char *font_family;
|
||||
|
||||
/* Split & strip the font's style */
|
||||
font_family = stredup(font_name);
|
||||
font_style = strchr(font_family, ',');
|
||||
if (font_style != nullptr) {
|
||||
font_style[0] = '\0';
|
||||
font_style++;
|
||||
while (*font_style == ' ' || *font_style == '\t') font_style++;
|
||||
}
|
||||
auto [font_family, font_style] = SplitFontFamilyAndStyle(font_name);
|
||||
|
||||
/* Resolve the name and populate the information structure */
|
||||
pat = FcNameParse((FcChar8 *)font_family);
|
||||
if (font_style != nullptr) FcPatternAddString(pat, FC_STYLE, (FcChar8 *)font_style);
|
||||
FcPattern *pat = FcNameParse((FcChar8 *)font_family.data());
|
||||
if (!font_style.empty()) FcPatternAddString(pat, FC_STYLE, (FcChar8 *)font_style.data());
|
||||
FcConfigSubstitute(nullptr, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
fs = FcFontSetCreate();
|
||||
match = FcFontMatch(nullptr, pat, &result);
|
||||
FcFontSet *fs = FcFontSetCreate();
|
||||
FcResult result;
|
||||
FcPattern *match = FcFontMatch(nullptr, pat, &result);
|
||||
|
||||
if (fs != nullptr && match != nullptr) {
|
||||
int i;
|
||||
FcChar8 *family;
|
||||
FcChar8 *style;
|
||||
FcChar8 *file;
|
||||
int32_t index;
|
||||
FcFontSetAdd(fs, match);
|
||||
|
||||
for (i = 0; err != FT_Err_Ok && i < fs->nfont; i++) {
|
||||
for (int i = 0; err != FT_Err_Ok && i < fs->nfont; i++) {
|
||||
/* Try the new filename */
|
||||
if (FcPatternGetString(fs->fonts[i], FC_FILE, 0, &file) == FcResultMatch &&
|
||||
FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, &family) == FcResultMatch &&
|
||||
@@ -77,19 +78,18 @@ FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
|
||||
FcPatternGetInteger(fs->fonts[i], FC_INDEX, 0, &index) == FcResultMatch) {
|
||||
|
||||
/* The correct style? */
|
||||
if (font_style != nullptr && strcasecmp(font_style, (char *)style) != 0) continue;
|
||||
if (!font_style.empty() && !StrEqualsIgnoreCase(font_style, (char *)style)) continue;
|
||||
|
||||
/* Font config takes the best shot, which, if the family name is spelled
|
||||
* wrongly a 'random' font, so check whether the family name is the
|
||||
* same as the supplied name */
|
||||
if (strcasecmp(font_family, (char *)family) == 0) {
|
||||
if (StrEqualsIgnoreCase(font_family, (char *)family)) {
|
||||
err = FT_New_Face(_library, (char *)file, index, face);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(font_family);
|
||||
FcPatternDestroy(pat);
|
||||
FcFontSetDestroy(fs);
|
||||
FcConfigDestroy(fc_instance);
|
||||
@@ -97,10 +97,7 @@ FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
|
||||
return err;
|
||||
}
|
||||
|
||||
#endif /* WITH_FREETYPE */
|
||||
|
||||
|
||||
bool SetFallbackFont(FontCacheSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback)
|
||||
bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, int, MissingGlyphSearcher *callback)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
@@ -112,15 +109,12 @@ bool SetFallbackFont(FontCacheSettings *settings, const char *language_isocode,
|
||||
/* Fontconfig doesn't handle full language isocodes, only the part
|
||||
* before the _ of e.g. en_GB is used, so "remove" everything after
|
||||
* the _. */
|
||||
char lang[16];
|
||||
seprintf(lang, lastof(lang), ":lang=%s", language_isocode);
|
||||
char *split = strchr(lang, '_');
|
||||
if (split != nullptr) *split = '\0';
|
||||
std::string lang = fmt::format(":lang={}", language_isocode.substr(0, language_isocode.find('_')));
|
||||
|
||||
/* First create a pattern to match the wanted language. */
|
||||
FcPattern *pat = FcNameParse((FcChar8 *)lang);
|
||||
/* We only want to know the filename. */
|
||||
FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_SPACING, FC_SLANT, FC_WEIGHT, nullptr);
|
||||
FcPattern *pat = FcNameParse((const FcChar8 *)lang.c_str());
|
||||
/* We only want to know these attributes. */
|
||||
FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, nullptr);
|
||||
/* Get the list of filenames matching the wanted language. */
|
||||
FcFontSet *fs = FcFontList(nullptr, pat, os);
|
||||
|
||||
@@ -131,6 +125,7 @@ bool SetFallbackFont(FontCacheSettings *settings, const char *language_isocode,
|
||||
if (fs != nullptr) {
|
||||
int best_weight = -1;
|
||||
const char *best_font = nullptr;
|
||||
int best_index = 0;
|
||||
|
||||
for (int i = 0; i < fs->nfont; i++) {
|
||||
FcPattern *font = fs->fonts[i];
|
||||
@@ -154,20 +149,26 @@ bool SetFallbackFont(FontCacheSettings *settings, const char *language_isocode,
|
||||
FcPatternGetInteger(font, FC_WEIGHT, 0, &value);
|
||||
if (value <= best_weight) continue;
|
||||
|
||||
callback->SetFontNames(settings, (const char *)file);
|
||||
/* Possible match based on attributes, get index. */
|
||||
int32_t index;
|
||||
res = FcPatternGetInteger(font, FC_INDEX, 0, &index);
|
||||
if (res != FcResultMatch) continue;
|
||||
|
||||
callback->SetFontNames(settings, (const char *)file, &index);
|
||||
|
||||
bool missing = callback->FindMissingGlyphs();
|
||||
Debug(fontcache, 1, "Font \"{}\" misses{} glyphs", file, missing ? "" : " no");
|
||||
Debug(fontcache, 1, "Font \"{}\" misses{} glyphs", (char *)file, missing ? "" : " no");
|
||||
|
||||
if (!missing) {
|
||||
best_weight = value;
|
||||
best_font = (const char *)file;
|
||||
best_index = index;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_font != nullptr) {
|
||||
ret = true;
|
||||
callback->SetFontNames(settings, best_font);
|
||||
callback->SetFontNames(settings, best_font, &best_index);
|
||||
InitFontCache(callback->Monospace());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file library_loader_unix.cpp Implementation of the LibraryLoader for Linux / MacOS */
|
||||
|
||||
#include "../../stdafx.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "../../library_loader.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/* Emscripten cannot dynamically load other files. */
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
|
||||
void *LibraryLoader::OpenLibrary(const std::string &)
|
||||
{
|
||||
this->error = "Dynamic loading is not supported on this platform.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LibraryLoader::CloseLibrary()
|
||||
{
|
||||
}
|
||||
|
||||
void *LibraryLoader::GetSymbol(const std::string &)
|
||||
{
|
||||
this->error = "Dynamic loading is not supported on this platform.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void *LibraryLoader::OpenLibrary(const std::string &filename)
|
||||
{
|
||||
void *h = dlopen(filename.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||
if (h == nullptr) {
|
||||
this->error = dlerror();
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
void LibraryLoader::CloseLibrary()
|
||||
{
|
||||
dlclose(this->handle);
|
||||
}
|
||||
|
||||
void *LibraryLoader::GetSymbol(const std::string &symbol_name)
|
||||
{
|
||||
void *p = dlsym(this->handle, symbol_name.c_str());
|
||||
if (p == nullptr) {
|
||||
this->error = dlerror();
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
#endif /* __EMSCRIPTEN__ */
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file survey_unix.cpp Unix implementation of OS-specific survey information. */
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../survey.h"
|
||||
|
||||
#include <sys/utsname.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
void SurveyOS(nlohmann::json &json)
|
||||
{
|
||||
struct utsname name;
|
||||
if (uname(&name) < 0) {
|
||||
json["os"] = "Unix";
|
||||
return;
|
||||
}
|
||||
|
||||
json["os"] = name.sysname;
|
||||
json["release"] = name.release;
|
||||
json["machine"] = name.machine;
|
||||
json["version"] = name.version;
|
||||
|
||||
long pages = sysconf(_SC_PHYS_PAGES);
|
||||
long page_size = sysconf(_SC_PAGE_SIZE);
|
||||
json["memory"] = SurveyMemoryToText(pages * page_size);
|
||||
json["hardware_concurrency"] = std::thread::hardware_concurrency();
|
||||
}
|
||||
+43
-104
@@ -9,9 +9,6 @@
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../textbuf_gui.h"
|
||||
#include "../../openttd.h"
|
||||
#include "../../crashlog.h"
|
||||
#include "../../core/random_func.hpp"
|
||||
#include "../../debug.h"
|
||||
#include "../../string_func.h"
|
||||
#include "../../fios.h"
|
||||
@@ -23,6 +20,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#ifdef WITH_SDL2
|
||||
#include <SDL.h>
|
||||
@@ -50,62 +48,43 @@
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#ifndef NO_THREADS
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
# if defined(WITH_SDL)
|
||||
/* the mac implementation needs this file included in the same file as main() */
|
||||
# include <SDL.h>
|
||||
# endif
|
||||
|
||||
# include "../macosx/macos.h"
|
||||
#endif
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
bool FiosIsRoot(const char *path)
|
||||
bool FiosIsRoot(const std::string &path)
|
||||
{
|
||||
return path[1] == '\0';
|
||||
return path == PATHSEP;
|
||||
}
|
||||
|
||||
void FiosGetDrives(FileList &file_list)
|
||||
void FiosGetDrives(FileList &)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool FiosGetDiskFreeSpace(const char *path, uint64 *tot)
|
||||
std::optional<uint64_t> FiosGetDiskFreeSpace(const std::string &path)
|
||||
{
|
||||
uint64 free = 0;
|
||||
|
||||
#ifdef __APPLE__
|
||||
struct statfs s;
|
||||
|
||||
if (statfs(path, &s) != 0) return false;
|
||||
free = (uint64)s.f_bsize * s.f_bavail;
|
||||
if (statfs(path.c_str(), &s) == 0) return static_cast<uint64_t>(s.f_bsize) * s.f_bavail;
|
||||
#elif defined(HAS_STATVFS)
|
||||
struct statvfs s;
|
||||
|
||||
if (statvfs(path, &s) != 0) return false;
|
||||
free = (uint64)s.f_frsize * s.f_bavail;
|
||||
if (statvfs(path.c_str(), &s) == 0) return static_cast<uint64_t>(s.f_frsize) * s.f_bavail;
|
||||
#endif
|
||||
if (tot != nullptr) *tot = free;
|
||||
return true;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb)
|
||||
bool FiosIsValidFile(const std::string &path, const struct dirent *ent, struct stat *sb)
|
||||
{
|
||||
char filename[MAX_PATH];
|
||||
int res;
|
||||
assert(path[strlen(path) - 1] == PATHSEPCHAR);
|
||||
if (strlen(path) > 2) assert(path[strlen(path) - 2] != PATHSEPCHAR);
|
||||
res = seprintf(filename, lastof(filename), "%s%s", path, ent->d_name);
|
||||
assert(path.back() == PATHSEPCHAR);
|
||||
if (path.size() > 2) assert(path[path.size() - 2] != PATHSEPCHAR);
|
||||
std::string filename = fmt::format("{}{}", path, ent->d_name);
|
||||
|
||||
/* Could we fully concatenate the path and filename? */
|
||||
if (res >= (int)lengthof(filename) || res < 0) return false;
|
||||
|
||||
return stat(filename, sb) == 0;
|
||||
return stat(filename.c_str(), sb) == 0;
|
||||
}
|
||||
|
||||
bool FiosIsHiddenFile(const struct dirent *ent)
|
||||
@@ -116,7 +95,6 @@ bool FiosIsHiddenFile(const struct dirent *ent)
|
||||
#ifdef WITH_ICONV
|
||||
|
||||
#include <iconv.h>
|
||||
#include <errno.h>
|
||||
#include "../../debug.h"
|
||||
#include "../../string_func.h"
|
||||
|
||||
@@ -146,29 +124,30 @@ static const char *GetLocalCode()
|
||||
* Convert between locales, which from and which to is set in the calling
|
||||
* functions OTTD2FS() and FS2OTTD().
|
||||
*/
|
||||
static const char *convert_tofrom_fs(iconv_t convd, const char *name, char *outbuf, size_t outlen)
|
||||
static std::string convert_tofrom_fs(iconv_t convd, const std::string &name)
|
||||
{
|
||||
/* There are different implementations of iconv. The older ones,
|
||||
* e.g. SUSv2, pass a const pointer, whereas the newer ones, e.g.
|
||||
* IEEE 1003.1 (2004), pass a non-const pointer. */
|
||||
#ifdef HAVE_NON_CONST_ICONV
|
||||
char *inbuf = const_cast<char*>(name);
|
||||
char *inbuf = const_cast<char*>(name.data());
|
||||
#else
|
||||
const char *inbuf = name;
|
||||
const char *inbuf = name.data();
|
||||
#endif
|
||||
|
||||
size_t inlen = strlen(name);
|
||||
char *buf = outbuf;
|
||||
|
||||
strecpy(outbuf, name, outbuf + outlen);
|
||||
/* If the output is UTF-32, then 1 ASCII character becomes 4 bytes. */
|
||||
size_t inlen = name.size();
|
||||
std::string buf(inlen * 4, '\0');
|
||||
|
||||
size_t outlen = buf.size();
|
||||
char *outbuf = buf.data();
|
||||
iconv(convd, nullptr, nullptr, nullptr, nullptr);
|
||||
if (iconv(convd, &inbuf, &inlen, &outbuf, &outlen) == (size_t)(-1)) {
|
||||
Debug(misc, 0, "[iconv] error converting '{}'. Errno {}", name, errno);
|
||||
return name;
|
||||
}
|
||||
|
||||
*outbuf = '\0';
|
||||
/* FIX: invalid characters will abort conversion, but they shouldn't occur? */
|
||||
buf.resize(outbuf - buf.data());
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -180,8 +159,6 @@ static const char *convert_tofrom_fs(iconv_t convd, const char *name, char *outb
|
||||
std::string OTTD2FS(const std::string &name)
|
||||
{
|
||||
static iconv_t convd = (iconv_t)(-1);
|
||||
char buf[1024] = {};
|
||||
|
||||
if (convd == (iconv_t)(-1)) {
|
||||
const char *env = GetLocalCode();
|
||||
convd = iconv_open(env, INTERNALCODE);
|
||||
@@ -191,7 +168,7 @@ std::string OTTD2FS(const std::string &name)
|
||||
}
|
||||
}
|
||||
|
||||
return convert_tofrom_fs(convd, name.c_str(), buf, lengthof(buf));
|
||||
return convert_tofrom_fs(convd, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,8 +179,6 @@ std::string OTTD2FS(const std::string &name)
|
||||
std::string FS2OTTD(const std::string &name)
|
||||
{
|
||||
static iconv_t convd = (iconv_t)(-1);
|
||||
char buf[1024] = {};
|
||||
|
||||
if (convd == (iconv_t)(-1)) {
|
||||
const char *env = GetLocalCode();
|
||||
convd = iconv_open(INTERNALCODE, env);
|
||||
@@ -213,97 +188,62 @@ std::string FS2OTTD(const std::string &name)
|
||||
}
|
||||
}
|
||||
|
||||
return convert_tofrom_fs(convd, name.c_str(), buf, lengthof(buf));
|
||||
return convert_tofrom_fs(convd, name);
|
||||
}
|
||||
|
||||
#endif /* WITH_ICONV */
|
||||
|
||||
void ShowInfo(const char *str)
|
||||
void ShowInfoI(const std::string &str)
|
||||
{
|
||||
fprintf(stderr, "%s\n", str);
|
||||
fmt::print(stderr, "{}\n", str);
|
||||
}
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
void ShowOSErrorBox(const char *buf, bool system)
|
||||
void ShowOSErrorBox(const char *buf, bool)
|
||||
{
|
||||
/* All unix systems, except OSX. Only use escape codes on a TTY. */
|
||||
if (isatty(fileno(stderr))) {
|
||||
fprintf(stderr, "\033[1;31mError: %s\033[0;39m\n", buf);
|
||||
fmt::print(stderr, "\033[1;31mError: {}\033[0;39m\n", buf);
|
||||
} else {
|
||||
fprintf(stderr, "Error: %s\n", buf);
|
||||
fmt::print(stderr, "Error: {}\n", buf);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_COCOA
|
||||
void CocoaSetupAutoreleasePool();
|
||||
void CocoaReleaseAutoreleasePool();
|
||||
#endif
|
||||
|
||||
int CDECL main(int argc, char *argv[])
|
||||
{
|
||||
/* Make sure our arguments contain only valid UTF-8 characters. */
|
||||
for (int i = 0; i < argc; i++) StrMakeValidInPlace(argv[i]);
|
||||
|
||||
#ifdef WITH_COCOA
|
||||
CocoaSetupAutoreleasePool();
|
||||
/* This is passed if we are launched by double-clicking */
|
||||
if (argc >= 2 && strncmp(argv[1], "-psn", 4) == 0) {
|
||||
argv[1] = nullptr;
|
||||
argc = 1;
|
||||
}
|
||||
#endif
|
||||
CrashLog::InitialiseCrashLog();
|
||||
|
||||
SetRandomSeed(time(nullptr));
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
int ret = openttd_main(argc, argv);
|
||||
|
||||
#ifdef WITH_COCOA
|
||||
CocoaReleaseAutoreleasePool();
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef WITH_COCOA
|
||||
bool GetClipboardContents(char *buffer, const char *last)
|
||||
std::optional<std::string> GetClipboardContents()
|
||||
{
|
||||
#ifdef WITH_SDL2
|
||||
if (SDL_HasClipboardText() == SDL_FALSE) {
|
||||
return false;
|
||||
}
|
||||
if (SDL_HasClipboardText() == SDL_FALSE) return std::nullopt;
|
||||
|
||||
char *clip = SDL_GetClipboardText();
|
||||
if (clip != nullptr) {
|
||||
strecpy(buffer, clip, last);
|
||||
std::string result = clip;
|
||||
SDL_free(clip);
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
void OSOpenBrowser(const char *url)
|
||||
void OSOpenBrowser(const std::string &url)
|
||||
{
|
||||
/* Implementation in pre.js */
|
||||
EM_ASM({ if(window["openttd_open_url"]) window.openttd_open_url($0, $1) }, url, strlen(url));
|
||||
EM_ASM({ if (window["openttd_open_url"]) window.openttd_open_url($0, $1) }, url.c_str(), url.size());
|
||||
}
|
||||
#elif !defined( __APPLE__)
|
||||
void OSOpenBrowser(const char *url)
|
||||
void OSOpenBrowser(const std::string &url)
|
||||
{
|
||||
pid_t child_pid = fork();
|
||||
if (child_pid != 0) return;
|
||||
|
||||
const char *args[3];
|
||||
args[0] = "xdg-open";
|
||||
args[1] = url;
|
||||
args[1] = url.c_str();
|
||||
args[2] = nullptr;
|
||||
execvp(args[0], const_cast<char * const *>(args));
|
||||
Debug(misc, 0, "Failed to open url: {}", url);
|
||||
@@ -311,12 +251,11 @@ void OSOpenBrowser(const char *url)
|
||||
}
|
||||
#endif /* __APPLE__ */
|
||||
|
||||
void SetCurrentThreadName(const char *threadName) {
|
||||
#if !defined(NO_THREADS) && defined(__GLIBC__)
|
||||
#if __GLIBC_PREREQ(2, 12)
|
||||
void SetCurrentThreadName([[maybe_unused]] const char *threadName)
|
||||
{
|
||||
#if defined(__GLIBC__)
|
||||
if (threadName) pthread_setname_np(pthread_self(), threadName);
|
||||
#endif /* __GLIBC_PREREQ(2, 12) */
|
||||
#endif /* !defined(NO_THREADS) && defined(__GLIBC__) */
|
||||
#endif /* defined(__GLIBC__) */
|
||||
#if defined(__APPLE__)
|
||||
MacOSSetThreadName(threadName);
|
||||
#endif /* defined(__APPLE__) */
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file unix_main.cpp Main entry for Unix. */
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../openttd.h"
|
||||
#include "../../crashlog.h"
|
||||
#include "../../core/random_func.hpp"
|
||||
#include "../../string_func.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
int CDECL main(int argc, char *argv[])
|
||||
{
|
||||
/* Make sure our arguments contain only valid UTF-8 characters. */
|
||||
for (int i = 0; i < argc; i++) StrMakeValidInPlace(argv[i]);
|
||||
|
||||
CrashLog::InitialiseCrashLog();
|
||||
|
||||
SetRandomSeed(time(nullptr));
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
return openttd_main(argc, argv);
|
||||
}
|
||||
@@ -2,9 +2,15 @@ add_files(
|
||||
crashlog_win.cpp
|
||||
font_win32.cpp
|
||||
font_win32.h
|
||||
library_loader_win.cpp
|
||||
string_uniscribe.cpp
|
||||
string_uniscribe.h
|
||||
survey_win.cpp
|
||||
win32.cpp
|
||||
win32.h
|
||||
CONDITION WIN32
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_sources(openttd PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/win32_main.cpp)
|
||||
endif()
|
||||
|
||||
+224
-440
@@ -10,7 +10,6 @@
|
||||
#include "../../stdafx.h"
|
||||
#include "../../crashlog.h"
|
||||
#include "win32.h"
|
||||
#include "../../core/alloc_func.hpp"
|
||||
#include "../../core/math_func.hpp"
|
||||
#include "../../string_func.h"
|
||||
#include "../../fileio_func.h"
|
||||
@@ -18,20 +17,66 @@
|
||||
#include "../../gamelog.h"
|
||||
#include "../../saveload/saveload.h"
|
||||
#include "../../video/video_driver.hpp"
|
||||
#include "../../library_loader.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <signal.h>
|
||||
#include <psapi.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# include <dbghelp.h>
|
||||
#else
|
||||
# include <setjmp.h>
|
||||
#endif
|
||||
|
||||
#ifdef WITH_UNOFFICIAL_BREAKPAD
|
||||
# include <client/windows/handler/exception_handler.h>
|
||||
#endif
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/* printf format specification for 32/64-bit addresses. */
|
||||
#ifdef _M_AMD64
|
||||
#define PRINTF_PTR "0x%016IX"
|
||||
#else
|
||||
#define PRINTF_PTR "0x%08X"
|
||||
#endif
|
||||
/** Exception code used for custom abort. */
|
||||
static constexpr DWORD CUSTOM_ABORT_EXCEPTION = 0xE1212012;
|
||||
|
||||
/** A map between exception code and its name. */
|
||||
static const std::map<DWORD, std::string> exception_code_to_name{
|
||||
{EXCEPTION_ACCESS_VIOLATION, "EXCEPTION_ACCESS_VIOLATION"},
|
||||
{EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"},
|
||||
{EXCEPTION_BREAKPOINT, "EXCEPTION_BREAKPOINT"},
|
||||
{EXCEPTION_DATATYPE_MISALIGNMENT, "EXCEPTION_DATATYPE_MISALIGNMENT"},
|
||||
{EXCEPTION_FLT_DENORMAL_OPERAND, "EXCEPTION_FLT_DENORMAL_OPERAND"},
|
||||
{EXCEPTION_FLT_DIVIDE_BY_ZERO, "EXCEPTION_FLT_DIVIDE_BY_ZERO"},
|
||||
{EXCEPTION_FLT_INEXACT_RESULT, "EXCEPTION_FLT_INEXACT_RESULT"},
|
||||
{EXCEPTION_FLT_INVALID_OPERATION, "EXCEPTION_FLT_INVALID_OPERATION"},
|
||||
{EXCEPTION_FLT_OVERFLOW, "EXCEPTION_FLT_OVERFLOW"},
|
||||
{EXCEPTION_FLT_STACK_CHECK, "EXCEPTION_FLT_STACK_CHECK"},
|
||||
{EXCEPTION_FLT_UNDERFLOW, "EXCEPTION_FLT_UNDERFLOW"},
|
||||
{EXCEPTION_GUARD_PAGE, "EXCEPTION_GUARD_PAGE"},
|
||||
{EXCEPTION_ILLEGAL_INSTRUCTION, "EXCEPTION_ILLEGAL_INSTRUCTION"},
|
||||
{EXCEPTION_IN_PAGE_ERROR, "EXCEPTION_IN_PAGE_ERROR"},
|
||||
{EXCEPTION_INT_DIVIDE_BY_ZERO, "EXCEPTION_INT_DIVIDE_BY_ZERO"},
|
||||
{EXCEPTION_INT_OVERFLOW, "EXCEPTION_INT_OVERFLOW"},
|
||||
{EXCEPTION_INVALID_DISPOSITION, "EXCEPTION_INVALID_DISPOSITION"},
|
||||
{EXCEPTION_INVALID_HANDLE, "EXCEPTION_INVALID_HANDLE"},
|
||||
{EXCEPTION_NONCONTINUABLE_EXCEPTION, "EXCEPTION_NONCONTINUABLE_EXCEPTION"},
|
||||
{EXCEPTION_PRIV_INSTRUCTION, "EXCEPTION_PRIV_INSTRUCTION"},
|
||||
{EXCEPTION_SINGLE_STEP, "EXCEPTION_SINGLE_STEP"},
|
||||
{EXCEPTION_STACK_OVERFLOW, "EXCEPTION_STACK_OVERFLOW"},
|
||||
{STATUS_UNWIND_CONSOLIDATE, "STATUS_UNWIND_CONSOLIDATE"},
|
||||
};
|
||||
|
||||
/**
|
||||
* Forcefully try to terminate the application.
|
||||
*
|
||||
* @param exit_code The exit code to return.
|
||||
*/
|
||||
[[noreturn]] static void ImmediateExitProcess(uint exit_code)
|
||||
{
|
||||
/* TerminateProcess may fail in some special edge cases; fall back to ExitProcess in this case. */
|
||||
TerminateProcess(GetCurrentProcess(), exit_code);
|
||||
ExitProcess(exit_code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows implementation for the crash logger.
|
||||
@@ -40,27 +85,69 @@ class CrashLogWindows : public CrashLog {
|
||||
/** Information about the encountered exception */
|
||||
EXCEPTION_POINTERS *ep;
|
||||
|
||||
char *LogOSVersion(char *buffer, const char *last) const override;
|
||||
char *LogError(char *buffer, const char *last, const char *message) const override;
|
||||
char *LogStacktrace(char *buffer, const char *last) const override;
|
||||
char *LogRegisters(char *buffer, const char *last) const override;
|
||||
char *LogModules(char *buffer, const char *last) const override;
|
||||
public:
|
||||
#if defined(_MSC_VER)
|
||||
int WriteCrashDump(char *filename, const char *filename_last) const override;
|
||||
char *AppendDecodedStacktrace(char *buffer, const char *last) const;
|
||||
#else
|
||||
char *AppendDecodedStacktrace(char *buffer, const char *last) const { return buffer; }
|
||||
#endif /* _MSC_VER */
|
||||
void SurveyCrash(nlohmann::json &survey) const override
|
||||
{
|
||||
survey["id"] = ep->ExceptionRecord->ExceptionCode;
|
||||
if (exception_code_to_name.count(ep->ExceptionRecord->ExceptionCode) > 0) {
|
||||
survey["reason"] = exception_code_to_name.at(ep->ExceptionRecord->ExceptionCode);
|
||||
} else {
|
||||
survey["reason"] = "Unknown exception code";
|
||||
}
|
||||
}
|
||||
|
||||
/** Buffer for the generated crash log */
|
||||
char crashlog[65536];
|
||||
/** Buffer for the filename of the crash log */
|
||||
char crashlog_filename[MAX_PATH];
|
||||
/** Buffer for the filename of the crash dump */
|
||||
char crashdump_filename[MAX_PATH];
|
||||
/** Buffer for the filename of the crash screenshot */
|
||||
char screenshot_filename[MAX_PATH];
|
||||
void SurveyStacktrace(nlohmann::json &survey) const override;
|
||||
public:
|
||||
|
||||
#ifdef WITH_UNOFFICIAL_BREAKPAD
|
||||
static bool MinidumpCallback(const wchar_t *dump_dir, const wchar_t *minidump_id, void *context, EXCEPTION_POINTERS *, MDRawAssertionInfo *, bool succeeded)
|
||||
{
|
||||
CrashLogWindows *crashlog = reinterpret_cast<CrashLogWindows *>(context);
|
||||
|
||||
crashlog->crashdump_filename = crashlog->CreateFileName(".dmp");
|
||||
std::rename(fmt::format("{}/{}.dmp", FS2OTTD(dump_dir), FS2OTTD(minidump_id)).c_str(), crashlog->crashdump_filename.c_str());
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
bool WriteCrashDump() override
|
||||
{
|
||||
return google_breakpad::ExceptionHandler::WriteMinidump(OTTD2FS(_personal_dir), MinidumpCallback, this);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
/* virtual */ bool TryExecute(std::string_view section_name, std::function<bool()> &&func) override
|
||||
{
|
||||
this->try_execute_active = true;
|
||||
bool res;
|
||||
|
||||
__try {
|
||||
res = func();
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
fmt::print("Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
|
||||
res = false;
|
||||
}
|
||||
|
||||
this->try_execute_active = false;
|
||||
return res;
|
||||
}
|
||||
#else
|
||||
/* virtual */ bool TryExecute(std::string_view section_name, std::function<bool()> &&func) override
|
||||
{
|
||||
this->try_execute_active = true;
|
||||
|
||||
/* Setup a longjump in case a crash happens. */
|
||||
if (setjmp(this->internal_fault_jmp_buf) != 0) {
|
||||
fmt::print("Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
|
||||
|
||||
this->try_execute_active = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool res = func();
|
||||
this->try_execute_active = false;
|
||||
return res;
|
||||
}
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
/**
|
||||
* A crash log is always generated when it's generated.
|
||||
@@ -69,306 +156,29 @@ public:
|
||||
CrashLogWindows(EXCEPTION_POINTERS *ep = nullptr) :
|
||||
ep(ep)
|
||||
{
|
||||
this->crashlog[0] = '\0';
|
||||
this->crashlog_filename[0] = '\0';
|
||||
this->crashdump_filename[0] = '\0';
|
||||
this->screenshot_filename[0] = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Points to the current crash log.
|
||||
*/
|
||||
#if !defined(_MSC_VER)
|
||||
/** Buffer to track the long jump set setup. */
|
||||
jmp_buf internal_fault_jmp_buf;
|
||||
#endif
|
||||
|
||||
/** Whether we are in a TryExecute block. */
|
||||
bool try_execute_active = false;
|
||||
|
||||
/** Points to the current crash log. */
|
||||
static CrashLogWindows *current;
|
||||
};
|
||||
|
||||
/* static */ CrashLogWindows *CrashLogWindows::current = nullptr;
|
||||
|
||||
/* virtual */ char *CrashLogWindows::LogOSVersion(char *buffer, const char *last) const
|
||||
{
|
||||
_OSVERSIONINFOA os;
|
||||
os.dwOSVersionInfoSize = sizeof(os);
|
||||
GetVersionExA(&os);
|
||||
|
||||
return buffer + seprintf(buffer, last,
|
||||
"Operating system:\n"
|
||||
" Name: Windows\n"
|
||||
" Release: %d.%d.%d (%s)\n",
|
||||
(int)os.dwMajorVersion,
|
||||
(int)os.dwMinorVersion,
|
||||
(int)os.dwBuildNumber,
|
||||
os.szCSDVersion
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/* virtual */ char *CrashLogWindows::LogError(char *buffer, const char *last, const char *message) const
|
||||
{
|
||||
return buffer + seprintf(buffer, last,
|
||||
"Crash reason:\n"
|
||||
" Exception: %.8X\n"
|
||||
#ifdef _M_AMD64
|
||||
" Location: %.16IX\n"
|
||||
#else
|
||||
" Location: %.8X\n"
|
||||
#endif
|
||||
" Message: %s\n\n",
|
||||
(int)ep->ExceptionRecord->ExceptionCode,
|
||||
(size_t)ep->ExceptionRecord->ExceptionAddress,
|
||||
message == nullptr ? "<none>" : message
|
||||
);
|
||||
}
|
||||
|
||||
struct DebugFileInfo {
|
||||
uint32 size;
|
||||
uint32 crc32;
|
||||
SYSTEMTIME file_time;
|
||||
};
|
||||
|
||||
static uint32 *_crc_table;
|
||||
|
||||
static void MakeCRCTable(uint32 *table)
|
||||
{
|
||||
uint32 crc, poly = 0xEDB88320L;
|
||||
int i;
|
||||
int j;
|
||||
|
||||
_crc_table = table;
|
||||
|
||||
for (i = 0; i != 256; i++) {
|
||||
crc = i;
|
||||
for (j = 8; j != 0; j--) {
|
||||
crc = (crc & 1 ? (crc >> 1) ^ poly : crc >> 1);
|
||||
}
|
||||
table[i] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32 CalcCRC(byte *data, uint size, uint32 crc)
|
||||
{
|
||||
for (; size > 0; size--) {
|
||||
crc = ((crc >> 8) & 0x00FFFFFF) ^ _crc_table[(crc ^ *data++) & 0xFF];
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static void GetFileInfo(DebugFileInfo *dfi, const wchar_t *filename)
|
||||
{
|
||||
HANDLE file;
|
||||
memset(dfi, 0, sizeof(*dfi));
|
||||
|
||||
file = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, 0);
|
||||
if (file != INVALID_HANDLE_VALUE) {
|
||||
byte buffer[1024];
|
||||
DWORD numread;
|
||||
uint32 filesize = 0;
|
||||
FILETIME write_time;
|
||||
uint32 crc = (uint32)-1;
|
||||
|
||||
for (;;) {
|
||||
if (ReadFile(file, buffer, sizeof(buffer), &numread, nullptr) == 0 || numread == 0) {
|
||||
break;
|
||||
}
|
||||
filesize += numread;
|
||||
crc = CalcCRC(buffer, numread, crc);
|
||||
}
|
||||
dfi->size = filesize;
|
||||
dfi->crc32 = crc ^ (uint32)-1;
|
||||
|
||||
if (GetFileTime(file, nullptr, nullptr, &write_time)) {
|
||||
FileTimeToSystemTime(&write_time, &dfi->file_time);
|
||||
}
|
||||
CloseHandle(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static char *PrintModuleInfo(char *output, const char *last, HMODULE mod)
|
||||
{
|
||||
wchar_t buffer[MAX_PATH];
|
||||
DebugFileInfo dfi;
|
||||
|
||||
GetModuleFileName(mod, buffer, MAX_PATH);
|
||||
GetFileInfo(&dfi, buffer);
|
||||
output += seprintf(output, last, " %-20s handle: %p size: %d crc: %.8X date: %d-%.2d-%.2d %.2d:%.2d:%.2d\n",
|
||||
FS2OTTD(buffer).c_str(),
|
||||
mod,
|
||||
dfi.size,
|
||||
dfi.crc32,
|
||||
dfi.file_time.wYear,
|
||||
dfi.file_time.wMonth,
|
||||
dfi.file_time.wDay,
|
||||
dfi.file_time.wHour,
|
||||
dfi.file_time.wMinute,
|
||||
dfi.file_time.wSecond
|
||||
);
|
||||
return output;
|
||||
}
|
||||
|
||||
/* virtual */ char *CrashLogWindows::LogModules(char *output, const char *last) const
|
||||
{
|
||||
MakeCRCTable(AllocaM(uint32, 256));
|
||||
output += seprintf(output, last, "Module information:\n");
|
||||
|
||||
HANDLE proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
|
||||
if (proc != nullptr) {
|
||||
HMODULE modules[100];
|
||||
DWORD needed;
|
||||
BOOL res = EnumProcessModules(proc, modules, sizeof(modules), &needed);
|
||||
CloseHandle(proc);
|
||||
if (res) {
|
||||
size_t count = std::min<DWORD>(needed / sizeof(HMODULE), lengthof(modules));
|
||||
|
||||
for (size_t i = 0; i != count; i++) output = PrintModuleInfo(output, last, modules[i]);
|
||||
return output + seprintf(output, last, "\n");
|
||||
}
|
||||
}
|
||||
output = PrintModuleInfo(output, last, nullptr);
|
||||
return output + seprintf(output, last, "\n");
|
||||
}
|
||||
|
||||
/* virtual */ char *CrashLogWindows::LogRegisters(char *buffer, const char *last) const
|
||||
{
|
||||
buffer += seprintf(buffer, last, "Registers:\n");
|
||||
#ifdef _M_AMD64
|
||||
buffer += seprintf(buffer, last,
|
||||
" RAX: %.16I64X RBX: %.16I64X RCX: %.16I64X RDX: %.16I64X\n"
|
||||
" RSI: %.16I64X RDI: %.16I64X RBP: %.16I64X RSP: %.16I64X\n"
|
||||
" R8: %.16I64X R9: %.16I64X R10: %.16I64X R11: %.16I64X\n"
|
||||
" R12: %.16I64X R13: %.16I64X R14: %.16I64X R15: %.16I64X\n"
|
||||
" RIP: %.16I64X EFLAGS: %.8lX\n",
|
||||
ep->ContextRecord->Rax,
|
||||
ep->ContextRecord->Rbx,
|
||||
ep->ContextRecord->Rcx,
|
||||
ep->ContextRecord->Rdx,
|
||||
ep->ContextRecord->Rsi,
|
||||
ep->ContextRecord->Rdi,
|
||||
ep->ContextRecord->Rbp,
|
||||
ep->ContextRecord->Rsp,
|
||||
ep->ContextRecord->R8,
|
||||
ep->ContextRecord->R9,
|
||||
ep->ContextRecord->R10,
|
||||
ep->ContextRecord->R11,
|
||||
ep->ContextRecord->R12,
|
||||
ep->ContextRecord->R13,
|
||||
ep->ContextRecord->R14,
|
||||
ep->ContextRecord->R15,
|
||||
ep->ContextRecord->Rip,
|
||||
ep->ContextRecord->EFlags
|
||||
);
|
||||
#elif defined(_M_IX86)
|
||||
buffer += seprintf(buffer, last,
|
||||
" EAX: %.8X EBX: %.8X ECX: %.8X EDX: %.8X\n"
|
||||
" ESI: %.8X EDI: %.8X EBP: %.8X ESP: %.8X\n"
|
||||
" EIP: %.8X EFLAGS: %.8X\n",
|
||||
(int)ep->ContextRecord->Eax,
|
||||
(int)ep->ContextRecord->Ebx,
|
||||
(int)ep->ContextRecord->Ecx,
|
||||
(int)ep->ContextRecord->Edx,
|
||||
(int)ep->ContextRecord->Esi,
|
||||
(int)ep->ContextRecord->Edi,
|
||||
(int)ep->ContextRecord->Ebp,
|
||||
(int)ep->ContextRecord->Esp,
|
||||
(int)ep->ContextRecord->Eip,
|
||||
(int)ep->ContextRecord->EFlags
|
||||
);
|
||||
#elif defined(_M_ARM64)
|
||||
buffer += seprintf(buffer, last,
|
||||
" X0: %.16I64X X1: %.16I64X X2: %.16I64X X3: %.16I64X\n"
|
||||
" X4: %.16I64X X5: %.16I64X X6: %.16I64X X7: %.16I64X\n"
|
||||
" X8: %.16I64X X9: %.16I64X X10: %.16I64X X11: %.16I64X\n"
|
||||
" X12: %.16I64X X13: %.16I64X X14: %.16I64X X15: %.16I64X\n"
|
||||
" X16: %.16I64X X17: %.16I64X X18: %.16I64X X19: %.16I64X\n"
|
||||
" X20: %.16I64X X21: %.16I64X X22: %.16I64X X23: %.16I64X\n"
|
||||
" X24: %.16I64X X25: %.16I64X X26: %.16I64X X27: %.16I64X\n"
|
||||
" X28: %.16I64X Fp: %.16I64X Lr: %.16I64X\n",
|
||||
ep->ContextRecord->X0,
|
||||
ep->ContextRecord->X1,
|
||||
ep->ContextRecord->X2,
|
||||
ep->ContextRecord->X3,
|
||||
ep->ContextRecord->X4,
|
||||
ep->ContextRecord->X5,
|
||||
ep->ContextRecord->X6,
|
||||
ep->ContextRecord->X7,
|
||||
ep->ContextRecord->X8,
|
||||
ep->ContextRecord->X9,
|
||||
ep->ContextRecord->X10,
|
||||
ep->ContextRecord->X11,
|
||||
ep->ContextRecord->X12,
|
||||
ep->ContextRecord->X13,
|
||||
ep->ContextRecord->X14,
|
||||
ep->ContextRecord->X15,
|
||||
ep->ContextRecord->X16,
|
||||
ep->ContextRecord->X17,
|
||||
ep->ContextRecord->X18,
|
||||
ep->ContextRecord->X19,
|
||||
ep->ContextRecord->X20,
|
||||
ep->ContextRecord->X21,
|
||||
ep->ContextRecord->X22,
|
||||
ep->ContextRecord->X23,
|
||||
ep->ContextRecord->X24,
|
||||
ep->ContextRecord->X25,
|
||||
ep->ContextRecord->X26,
|
||||
ep->ContextRecord->X27,
|
||||
ep->ContextRecord->X28,
|
||||
ep->ContextRecord->Fp,
|
||||
ep->ContextRecord->Lr
|
||||
);
|
||||
#endif
|
||||
|
||||
buffer += seprintf(buffer, last, "\n Bytes at instruction pointer:\n");
|
||||
#ifdef _M_AMD64
|
||||
byte *b = (byte*)ep->ContextRecord->Rip;
|
||||
#elif defined(_M_IX86)
|
||||
byte *b = (byte*)ep->ContextRecord->Eip;
|
||||
#elif defined(_M_ARM64)
|
||||
byte *b = (byte*)ep->ContextRecord->Pc;
|
||||
#endif
|
||||
for (int i = 0; i != 24; i++) {
|
||||
if (IsBadReadPtr(b, 1)) {
|
||||
buffer += seprintf(buffer, last, " ??"); // OCR: WAS: , 0);
|
||||
} else {
|
||||
buffer += seprintf(buffer, last, " %.2X", *b);
|
||||
}
|
||||
b++;
|
||||
}
|
||||
return buffer + seprintf(buffer, last, "\n\n");
|
||||
}
|
||||
|
||||
/* virtual */ char *CrashLogWindows::LogStacktrace(char *buffer, const char *last) const
|
||||
{
|
||||
buffer += seprintf(buffer, last, "Stack trace:\n");
|
||||
#ifdef _M_AMD64
|
||||
uint32 *b = (uint32*)ep->ContextRecord->Rsp;
|
||||
#elif defined(_M_IX86)
|
||||
uint32 *b = (uint32*)ep->ContextRecord->Esp;
|
||||
#elif defined(_M_ARM64)
|
||||
uint32 *b = (uint32*)ep->ContextRecord->Sp;
|
||||
#endif
|
||||
for (int j = 0; j != 24; j++) {
|
||||
for (int i = 0; i != 8; i++) {
|
||||
if (IsBadReadPtr(b, sizeof(uint32))) {
|
||||
buffer += seprintf(buffer, last, " ????????"); // OCR: WAS - , 0);
|
||||
} else {
|
||||
buffer += seprintf(buffer, last, " %.8X", *b);
|
||||
}
|
||||
b++;
|
||||
}
|
||||
buffer += seprintf(buffer, last, "\n");
|
||||
}
|
||||
return buffer + seprintf(buffer, last, "\n");
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
static const uint MAX_SYMBOL_LEN = 512;
|
||||
static const uint MAX_FRAMES = 64;
|
||||
|
||||
#pragma warning(disable:4091)
|
||||
#include <dbghelp.h>
|
||||
#pragma warning(default:4091)
|
||||
|
||||
char *CrashLogWindows::AppendDecodedStacktrace(char *buffer, const char *last) const
|
||||
/* virtual */ void CrashLogWindows::SurveyStacktrace(nlohmann::json &survey) const
|
||||
{
|
||||
DllLoader dbghelp(L"dbghelp.dll");
|
||||
LibraryLoader dbghelp("dbghelp.dll");
|
||||
struct ProcPtrs {
|
||||
BOOL (WINAPI * pSymInitialize)(HANDLE, PCSTR, BOOL);
|
||||
BOOL (WINAPI * pSymSetOptions)(DWORD);
|
||||
@@ -380,21 +190,21 @@ char *CrashLogWindows::AppendDecodedStacktrace(char *buffer, const char *last) c
|
||||
BOOL (WINAPI * pSymGetSymFromAddr64)(HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64);
|
||||
BOOL (WINAPI * pSymGetLineFromAddr64)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64);
|
||||
} proc = {
|
||||
dbghelp.GetProcAddress("SymInitialize"),
|
||||
dbghelp.GetProcAddress("SymSetOptions"),
|
||||
dbghelp.GetProcAddress("SymCleanup"),
|
||||
dbghelp.GetProcAddress("StackWalk64"),
|
||||
dbghelp.GetProcAddress("SymFunctionTableAccess64"),
|
||||
dbghelp.GetProcAddress("SymGetModuleBase64"),
|
||||
dbghelp.GetProcAddress("SymGetModuleInfo64"),
|
||||
dbghelp.GetProcAddress("SymGetSymFromAddr64"),
|
||||
dbghelp.GetProcAddress("SymGetLineFromAddr64"),
|
||||
dbghelp.GetFunction("SymInitialize"),
|
||||
dbghelp.GetFunction("SymSetOptions"),
|
||||
dbghelp.GetFunction("SymCleanup"),
|
||||
dbghelp.GetFunction("StackWalk64"),
|
||||
dbghelp.GetFunction("SymFunctionTableAccess64"),
|
||||
dbghelp.GetFunction("SymGetModuleBase64"),
|
||||
dbghelp.GetFunction("SymGetModuleInfo64"),
|
||||
dbghelp.GetFunction("SymGetSymFromAddr64"),
|
||||
dbghelp.GetFunction("SymGetLineFromAddr64"),
|
||||
};
|
||||
|
||||
buffer += seprintf(buffer, last, "\nDecoded stack trace:\n");
|
||||
survey = nlohmann::json::array();
|
||||
|
||||
/* Try to load the functions from the DLL, if that fails because of a too old dbghelp.dll, just skip it. */
|
||||
if (dbghelp.Success()) {
|
||||
if (!dbghelp.HasError()) {
|
||||
/* Initialize symbol handler. */
|
||||
HANDLE hCur = GetCurrentProcess();
|
||||
proc.pSymInitialize(hCur, nullptr, TRUE);
|
||||
@@ -426,7 +236,8 @@ char *CrashLogWindows::AppendDecodedStacktrace(char *buffer, const char *last) c
|
||||
memcpy(&ctx, ep->ContextRecord, sizeof(ctx));
|
||||
|
||||
/* Allocate space for symbol info. */
|
||||
IMAGEHLP_SYMBOL64 *sym_info = (IMAGEHLP_SYMBOL64*)alloca(sizeof(IMAGEHLP_SYMBOL64) + MAX_SYMBOL_LEN - 1);
|
||||
char sym_info_raw[sizeof(IMAGEHLP_SYMBOL64) + MAX_SYMBOL_LEN - 1];
|
||||
IMAGEHLP_SYMBOL64 *sym_info = (IMAGEHLP_SYMBOL64*)sym_info_raw;
|
||||
sym_info->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
|
||||
sym_info->MaxNameLength = MAX_SYMBOL_LEN;
|
||||
|
||||
@@ -441,7 +252,7 @@ char *CrashLogWindows::AppendDecodedStacktrace(char *buffer, const char *last) c
|
||||
hCur, GetCurrentThread(), &frame, &ctx, nullptr, proc.pSymFunctionTableAccess64, proc.pSymGetModuleBase64, nullptr)) break;
|
||||
|
||||
if (frame.AddrPC.Offset == frame.AddrReturn.Offset) {
|
||||
buffer += seprintf(buffer, last, " <infinite loop>\n");
|
||||
survey.push_back("<infinite loop>");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -455,67 +266,31 @@ char *CrashLogWindows::AppendDecodedStacktrace(char *buffer, const char *last) c
|
||||
}
|
||||
|
||||
/* Print module and instruction pointer. */
|
||||
buffer += seprintf(buffer, last, "[%02d] %-20s " PRINTF_PTR, num, mod_name, frame.AddrPC.Offset);
|
||||
std::string message = fmt::format("{:20s} {:X}", mod_name, frame.AddrPC.Offset);
|
||||
|
||||
/* Get symbol name and line info if possible. */
|
||||
DWORD64 offset;
|
||||
if (proc.pSymGetSymFromAddr64(hCur, frame.AddrPC.Offset, &offset, sym_info)) {
|
||||
buffer += seprintf(buffer, last, " %s + %I64u", sym_info->Name, offset);
|
||||
message += fmt::format(" {} + {}", sym_info->Name, offset);
|
||||
|
||||
DWORD line_offs;
|
||||
IMAGEHLP_LINE64 line;
|
||||
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
if (proc.pSymGetLineFromAddr64(hCur, frame.AddrPC.Offset, &line_offs, &line)) {
|
||||
buffer += seprintf(buffer, last, " (%s:%d)", line.FileName, line.LineNumber);
|
||||
message += fmt::format(" ({}:{})", line.FileName, line.LineNumber);
|
||||
}
|
||||
}
|
||||
buffer += seprintf(buffer, last, "\n");
|
||||
|
||||
survey.push_back(message);
|
||||
}
|
||||
|
||||
proc.pSymCleanup(hCur);
|
||||
}
|
||||
|
||||
return buffer + seprintf(buffer, last, "\n*** End of additional info ***\n");
|
||||
}
|
||||
|
||||
/* virtual */ int CrashLogWindows::WriteCrashDump(char *filename, const char *filename_last) const
|
||||
#else
|
||||
/* virtual */ void CrashLogWindows::SurveyStacktrace(nlohmann::json &) const
|
||||
{
|
||||
int ret = 0;
|
||||
DllLoader dbghelp(L"dbghelp.dll");
|
||||
if (dbghelp.Success()) {
|
||||
typedef BOOL (WINAPI *MiniDumpWriteDump_t)(HANDLE, DWORD, HANDLE,
|
||||
MINIDUMP_TYPE,
|
||||
CONST PMINIDUMP_EXCEPTION_INFORMATION,
|
||||
CONST PMINIDUMP_USER_STREAM_INFORMATION,
|
||||
CONST PMINIDUMP_CALLBACK_INFORMATION);
|
||||
MiniDumpWriteDump_t funcMiniDumpWriteDump = dbghelp.GetProcAddress("MiniDumpWriteDump");
|
||||
if (funcMiniDumpWriteDump != nullptr) {
|
||||
this->CreateFileName(filename, filename_last, ".dmp");
|
||||
HANDLE file = CreateFile(OTTD2FS(filename).c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, 0);
|
||||
HANDLE proc = GetCurrentProcess();
|
||||
DWORD procid = GetCurrentProcessId();
|
||||
MINIDUMP_EXCEPTION_INFORMATION mdei;
|
||||
MINIDUMP_USER_STREAM userstream;
|
||||
MINIDUMP_USER_STREAM_INFORMATION musi;
|
||||
|
||||
userstream.Type = LastReservedStream + 1;
|
||||
userstream.Buffer = (void*)this->crashlog;
|
||||
userstream.BufferSize = (ULONG)strlen(this->crashlog) + 1;
|
||||
|
||||
musi.UserStreamCount = 1;
|
||||
musi.UserStreamArray = &userstream;
|
||||
|
||||
mdei.ThreadId = GetCurrentThreadId();
|
||||
mdei.ExceptionPointers = ep;
|
||||
mdei.ClientPointers = false;
|
||||
|
||||
funcMiniDumpWriteDump(proc, procid, file, MiniDumpWithDataSegs, &mdei, &musi, nullptr);
|
||||
ret = 1;
|
||||
} else {
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
/* Not supported. */
|
||||
}
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
@@ -538,15 +313,15 @@ static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
|
||||
|
||||
if (CrashLogWindows::current != nullptr) {
|
||||
CrashLog::AfterCrashLogCleanup();
|
||||
ExitProcess(2);
|
||||
ImmediateExitProcess(2);
|
||||
}
|
||||
|
||||
if (GamelogTestEmergency()) {
|
||||
if (_gamelog.TestEmergency()) {
|
||||
static const wchar_t _emergency_crash[] =
|
||||
L"A serious fault condition occurred in the game. The game will shut down.\n"
|
||||
L"As you loaded an emergency savegame no crash information will be generated.\n";
|
||||
MessageBox(nullptr, _emergency_crash, L"Fatal Application Failure", MB_ICONERROR);
|
||||
ExitProcess(3);
|
||||
ImmediateExitProcess(3);
|
||||
}
|
||||
|
||||
if (SaveloadCrashWithMissingNewGRFs()) {
|
||||
@@ -555,16 +330,12 @@ static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
|
||||
L"As you loaded an savegame for which you do not have the required NewGRFs\n"
|
||||
L"no crash information will be generated.\n";
|
||||
MessageBox(nullptr, _saveload_crash, L"Fatal Application Failure", MB_ICONERROR);
|
||||
ExitProcess(3);
|
||||
ImmediateExitProcess(3);
|
||||
}
|
||||
|
||||
CrashLogWindows *log = new CrashLogWindows(ep);
|
||||
CrashLogWindows::current = log;
|
||||
char *buf = log->FillCrashLog(log->crashlog, lastof(log->crashlog));
|
||||
log->WriteCrashDump(log->crashdump_filename, lastof(log->crashdump_filename));
|
||||
log->AppendDecodedStacktrace(buf, lastof(log->crashlog));
|
||||
log->WriteCrashLog(log->crashlog, log->crashlog_filename, lastof(log->crashlog_filename));
|
||||
log->WriteScreenshot(log->screenshot_filename, lastof(log->screenshot_filename));
|
||||
log->MakeCrashLog();
|
||||
|
||||
/* Close any possible log files */
|
||||
CloseConsoleLogIfActive();
|
||||
@@ -587,9 +358,32 @@ static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
static void CDECL CustomAbort(int signal)
|
||||
static LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS *ep)
|
||||
{
|
||||
RaiseException(0xE1212012, 0, 0, nullptr);
|
||||
if (CrashLogWindows::current != nullptr && CrashLogWindows::current->try_execute_active) {
|
||||
#if defined(_MSC_VER)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
#else
|
||||
longjmp(CrashLogWindows::current->internal_fault_jmp_buf, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ep->ExceptionRecord->ExceptionCode == 0xC0000374 /* heap corruption */) {
|
||||
return ExceptionHandler(ep);
|
||||
}
|
||||
if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) {
|
||||
return ExceptionHandler(ep);
|
||||
}
|
||||
if (ep->ExceptionRecord->ExceptionCode == CUSTOM_ABORT_EXCEPTION) {
|
||||
return ExceptionHandler(ep);
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
static void CDECL CustomAbort(int)
|
||||
{
|
||||
RaiseException(CUSTOM_ABORT_EXCEPTION, 0, 0, nullptr);
|
||||
}
|
||||
|
||||
/* static */ void CrashLog::InitialiseCrashLog()
|
||||
@@ -603,6 +397,7 @@ static void CDECL CustomAbort(int signal)
|
||||
_set_abort_behavior(0, _WRITE_ABORT_MSG);
|
||||
#endif
|
||||
SetUnhandledExceptionFilter(ExceptionHandler);
|
||||
AddVectoredExceptionHandler(1, VectoredExceptionHandler);
|
||||
}
|
||||
|
||||
/* static */ void CrashLog::InitThread()
|
||||
@@ -639,16 +434,10 @@ static bool _expanded;
|
||||
|
||||
static const wchar_t _crash_desc[] =
|
||||
L"A serious fault condition occurred in the game. The game will shut down.\n"
|
||||
L"Please send the crash information and the crash.dmp file (if any) to the developers.\n"
|
||||
L"This will greatly help debugging. The correct place to do this is https://github.com/OpenTTD/OpenTTD/issues. "
|
||||
L"The information contained in the report is displayed below.\n"
|
||||
L"Press \"Emergency save\" to attempt saving the game. Generated file(s):\n"
|
||||
L"%s";
|
||||
|
||||
static const wchar_t _save_succeeded[] =
|
||||
L"Emergency save succeeded.\nIts location is '%s'.\n"
|
||||
L"Be aware that critical parts of the internal game state may have become "
|
||||
L"corrupted. The saved game is not guaranteed to work.";
|
||||
L"Please send crash.json.log, crash.dmp, and crash.sav to the developers.\n"
|
||||
L"This will greatly help debugging.\n\n"
|
||||
L"https://github.com/OpenTTD/OpenTTD/issues\n\n"
|
||||
L"%s\n%s\n%s\n%s\n";
|
||||
|
||||
static const wchar_t * const _expand_texts[] = {L"S&how report >>", L"&Hide report <<" };
|
||||
|
||||
@@ -673,48 +462,56 @@ static void SetWndSize(HWND wnd, int mode)
|
||||
}
|
||||
}
|
||||
|
||||
static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM)
|
||||
{
|
||||
switch (msg) {
|
||||
case WM_INITDIALOG: {
|
||||
std::string crashlog = CrashLogWindows::current->survey.dump(4);
|
||||
size_t crashlog_length = crashlog.size() + 1;
|
||||
/* Reserve extra space for LF to CRLF conversion. */
|
||||
crashlog_length += std::count(crashlog.begin(), crashlog.end(), '\n');
|
||||
|
||||
const size_t filename_count = 4;
|
||||
const size_t filename_buf_length = MAX_PATH + 1;
|
||||
const size_t crash_desc_buf_length = lengthof(_crash_desc) + filename_buf_length * filename_count + 1;
|
||||
|
||||
/* We need to put the crash-log in a separate buffer because the default
|
||||
* buffer in MB_TO_WIDE is not large enough (512 chars) */
|
||||
wchar_t filenamebuf[MAX_PATH * 2];
|
||||
wchar_t crash_msgW[lengthof(CrashLogWindows::current->crashlog)];
|
||||
/* Convert unix -> dos newlines because the edit box only supports that properly :( */
|
||||
const char *unix_nl = CrashLogWindows::current->crashlog;
|
||||
char dos_nl[lengthof(CrashLogWindows::current->crashlog)];
|
||||
char *p = dos_nl;
|
||||
WChar c;
|
||||
while ((c = Utf8Consume(&unix_nl)) && p < lastof(dos_nl) - 4) { // 4 is max number of bytes per character
|
||||
* buffer in MB_TO_WIDE is not large enough (512 chars).
|
||||
* Use VirtualAlloc to allocate pages for the buffer to avoid overflowing the stack.
|
||||
* Avoid the heap in case the crash is because the heap became corrupted. */
|
||||
const size_t total_length = crash_desc_buf_length * sizeof(wchar_t) +
|
||||
crashlog_length * sizeof(wchar_t) +
|
||||
filename_buf_length * sizeof(wchar_t) * filename_count +
|
||||
crashlog_length;
|
||||
void *raw_buffer = VirtualAlloc(nullptr, total_length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
|
||||
wchar_t *crash_desc_buf = reinterpret_cast<wchar_t *>(raw_buffer);
|
||||
wchar_t *crashlog_buf = crash_desc_buf + crash_desc_buf_length;
|
||||
wchar_t *filename_buf = crashlog_buf + crashlog_length;
|
||||
char *crashlog_dos_nl = reinterpret_cast<char *>(filename_buf + filename_buf_length * filename_count);
|
||||
|
||||
/* Convert unix -> dos newlines because the edit box only supports that properly. */
|
||||
const char *crashlog_unix_nl = crashlog.data();
|
||||
char *p = crashlog_dos_nl;
|
||||
char32_t c;
|
||||
while ((c = Utf8Consume(&crashlog_unix_nl))) {
|
||||
if (c == '\n') p += Utf8Encode(p, '\r');
|
||||
p += Utf8Encode(p, c);
|
||||
}
|
||||
*p = '\0';
|
||||
|
||||
/* Add path to crash.log and crash.dmp (if any) to the crash window text */
|
||||
size_t len = wcslen(_crash_desc) + 2;
|
||||
len += wcslen(convert_to_fs(CrashLogWindows::current->crashlog_filename, filenamebuf, lengthof(filenamebuf))) + 2;
|
||||
len += wcslen(convert_to_fs(CrashLogWindows::current->crashdump_filename, filenamebuf, lengthof(filenamebuf))) + 2;
|
||||
len += wcslen(convert_to_fs(CrashLogWindows::current->screenshot_filename, filenamebuf, lengthof(filenamebuf))) + 1;
|
||||
_snwprintf(
|
||||
crash_desc_buf,
|
||||
crash_desc_buf_length,
|
||||
_crash_desc,
|
||||
convert_to_fs(CrashLogWindows::current->crashlog_filename, filename_buf + filename_buf_length * 0, filename_buf_length),
|
||||
convert_to_fs(CrashLogWindows::current->crashdump_filename, filename_buf + filename_buf_length * 1, filename_buf_length),
|
||||
convert_to_fs(CrashLogWindows::current->savegame_filename, filename_buf + filename_buf_length * 2, filename_buf_length),
|
||||
convert_to_fs(CrashLogWindows::current->screenshot_filename, filename_buf + filename_buf_length * 3, filename_buf_length)
|
||||
);
|
||||
|
||||
wchar_t *text = AllocaM(wchar_t, len);
|
||||
int printed = _snwprintf(text, len, _crash_desc, convert_to_fs(CrashLogWindows::current->crashlog_filename, filenamebuf, lengthof(filenamebuf)));
|
||||
if (printed < 0 || (size_t)printed > len) {
|
||||
MessageBox(wnd, L"Catastrophic failure trying to display crash message. Could not perform text formatting.", L"OpenTTD", MB_ICONERROR);
|
||||
return FALSE;
|
||||
}
|
||||
if (convert_to_fs(CrashLogWindows::current->crashdump_filename, filenamebuf, lengthof(filenamebuf))[0] != L'\0') {
|
||||
wcscat(text, L"\n");
|
||||
wcscat(text, filenamebuf);
|
||||
}
|
||||
if (convert_to_fs(CrashLogWindows::current->screenshot_filename, filenamebuf, lengthof(filenamebuf))[0] != L'\0') {
|
||||
wcscat(text, L"\n");
|
||||
wcscat(text, filenamebuf);
|
||||
}
|
||||
|
||||
SetDlgItemText(wnd, 10, text);
|
||||
SetDlgItemText(wnd, 11, convert_to_fs(dos_nl, crash_msgW, lengthof(crash_msgW)));
|
||||
SetDlgItemText(wnd, 10, crash_desc_buf);
|
||||
SetDlgItemText(wnd, 11, convert_to_fs(crashlog_dos_nl, crashlog_buf, crashlog_length));
|
||||
SendDlgItemMessage(wnd, 11, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), FALSE);
|
||||
SetWndSize(wnd, -1);
|
||||
} return TRUE;
|
||||
@@ -722,20 +519,7 @@ static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARA
|
||||
switch (wParam) {
|
||||
case 12: // Close
|
||||
CrashLog::AfterCrashLogCleanup();
|
||||
ExitProcess(2);
|
||||
case 13: // Emergency save
|
||||
wchar_t filenamebuf[MAX_PATH * 2];
|
||||
char filename[MAX_PATH];
|
||||
if (CrashLogWindows::current->WriteSavegame(filename, lastof(filename))) {
|
||||
convert_to_fs(filename, filenamebuf, lengthof(filenamebuf));
|
||||
size_t len = lengthof(_save_succeeded) + wcslen(filenamebuf) + 1;
|
||||
wchar_t *text = AllocaM(wchar_t, len);
|
||||
_snwprintf(text, len, _save_succeeded, filenamebuf);
|
||||
MessageBox(wnd, text, L"Save successful", MB_ICONINFORMATION);
|
||||
} else {
|
||||
MessageBox(wnd, L"Save failed", L"Save failed", MB_ICONINFORMATION);
|
||||
}
|
||||
break;
|
||||
ImmediateExitProcess(2);
|
||||
case 15: // Expand window to show crash-message
|
||||
_expanded = !_expanded;
|
||||
SetWndSize(wnd, _expanded);
|
||||
@@ -744,7 +528,7 @@ static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARA
|
||||
return TRUE;
|
||||
case WM_CLOSE:
|
||||
CrashLog::AfterCrashLogCleanup();
|
||||
ExitProcess(2);
|
||||
ImmediateExitProcess(2);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
|
||||
+126
-351
@@ -12,10 +12,13 @@
|
||||
#include "../../blitter/factory.hpp"
|
||||
#include "../../core/alloc_func.hpp"
|
||||
#include "../../core/math_func.hpp"
|
||||
#include "../../core/mem_func.hpp"
|
||||
#include "../../error_func.h"
|
||||
#include "../../fileio_func.h"
|
||||
#include "../../fontdetection.h"
|
||||
#include "../../fontcache.h"
|
||||
#include "../../fontcache/truetypefontcache.h"
|
||||
#include "../../fontdetection.h"
|
||||
#include "../../library_loader.h"
|
||||
#include "../../string_func.h"
|
||||
#include "../../strings_func.h"
|
||||
#include "../../zoom_func.h"
|
||||
@@ -30,249 +33,22 @@
|
||||
|
||||
#include "safeguards.h"
|
||||
|
||||
#ifdef WITH_FREETYPE
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
extern FT_Library _library;
|
||||
|
||||
/**
|
||||
* Get the short DOS 8.3 format for paths.
|
||||
* FreeType doesn't support Unicode filenames and Windows' fopen (as used
|
||||
* by FreeType) doesn't support UTF-8 filenames. So we have to convert the
|
||||
* filename into something that isn't UTF-8 but represents the Unicode file
|
||||
* name. This is the short DOS 8.3 format. This does not contain any
|
||||
* characters that fopen doesn't support.
|
||||
* @param long_path the path in system encoding.
|
||||
* @return the short path in ANSI (ASCII).
|
||||
*/
|
||||
static const char *GetShortPath(const wchar_t *long_path)
|
||||
{
|
||||
static char short_path[MAX_PATH];
|
||||
wchar_t short_path_w[MAX_PATH];
|
||||
GetShortPathName(long_path, short_path_w, lengthof(short_path_w));
|
||||
WideCharToMultiByte(CP_ACP, 0, short_path_w, -1, short_path, lengthof(short_path), nullptr, nullptr);
|
||||
return short_path;
|
||||
}
|
||||
|
||||
/* Get the font file to be loaded into Freetype by looping the registry
|
||||
* location where windows lists all installed fonts. Not very nice, will
|
||||
* surely break if the registry path changes, but it works. Much better
|
||||
* solution would be to use CreateFont, and extract the font data from it
|
||||
* by GetFontData. The problem with this is that the font file needs to be
|
||||
* kept in memory then until the font is no longer needed. This could mean
|
||||
* an additional memory usage of 30MB (just for fonts!) when using an eastern
|
||||
* font for all font sizes */
|
||||
static const wchar_t *FONT_DIR_NT = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
|
||||
FT_Error GetFontByFaceName(const char *font_name, FT_Face *face)
|
||||
{
|
||||
FT_Error err = FT_Err_Cannot_Open_Resource;
|
||||
HKEY hKey;
|
||||
LONG ret;
|
||||
wchar_t vbuffer[MAX_PATH], dbuffer[256];
|
||||
wchar_t *pathbuf;
|
||||
const char *font_path;
|
||||
uint index;
|
||||
size_t path_len;
|
||||
|
||||
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey);
|
||||
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
Debug(fontcache, 0, "Cannot open registry key HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Convert font name to file system encoding. */
|
||||
wchar_t *font_namep = wcsdup(OTTD2FS(font_name).c_str());
|
||||
|
||||
for (index = 0;; index++) {
|
||||
wchar_t *s;
|
||||
DWORD vbuflen = lengthof(vbuffer);
|
||||
DWORD dbuflen = lengthof(dbuffer);
|
||||
|
||||
ret = RegEnumValue(hKey, index, vbuffer, &vbuflen, nullptr, nullptr, (byte *)dbuffer, &dbuflen);
|
||||
if (ret != ERROR_SUCCESS) goto registry_no_font_found;
|
||||
|
||||
/* The font names in the registry are of the following 3 forms:
|
||||
* - ADMUI3.fon
|
||||
* - Book Antiqua Bold (TrueType)
|
||||
* - Batang & BatangChe & Gungsuh & GungsuhChe (TrueType)
|
||||
* We will strip the font-type '()' if any and work with the font name
|
||||
* itself, which must match exactly; if...
|
||||
* TTC files, font files which contain more than one font are separated
|
||||
* by '&'. Our best bet will be to do substr match for the fontname
|
||||
* and then let FreeType figure out which index to load */
|
||||
s = wcschr(vbuffer, L'(');
|
||||
if (s != nullptr) s[-1] = '\0';
|
||||
|
||||
if (wcschr(vbuffer, L'&') == nullptr) {
|
||||
if (wcsicmp(vbuffer, font_namep) == 0) break;
|
||||
} else {
|
||||
if (wcsstr(vbuffer, font_namep) != nullptr) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_FONTS, nullptr, SHGFP_TYPE_CURRENT, vbuffer))) {
|
||||
Debug(fontcache, 0, "SHGetFolderPath cannot return fonts directory");
|
||||
goto folder_error;
|
||||
}
|
||||
|
||||
/* Some fonts are contained in .ttc files, TrueType Collection fonts. These
|
||||
* contain multiple fonts inside this single file. GetFontData however
|
||||
* returns the whole file, so we need to check each font inside to get the
|
||||
* proper font. */
|
||||
path_len = wcslen(vbuffer) + wcslen(dbuffer) + 2; // '\' and terminating nul.
|
||||
pathbuf = AllocaM(wchar_t, path_len);
|
||||
_snwprintf(pathbuf, path_len, L"%s\\%s", vbuffer, dbuffer);
|
||||
|
||||
/* Convert the path into something that FreeType understands. */
|
||||
font_path = GetShortPath(pathbuf);
|
||||
|
||||
index = 0;
|
||||
do {
|
||||
err = FT_New_Face(_library, font_path, index, face);
|
||||
if (err != FT_Err_Ok) break;
|
||||
|
||||
if (strncasecmp(font_name, (*face)->family_name, strlen((*face)->family_name)) == 0) break;
|
||||
/* Try english name if font name failed */
|
||||
if (strncasecmp(font_name + strlen(font_name) + 1, (*face)->family_name, strlen((*face)->family_name)) == 0) break;
|
||||
err = FT_Err_Cannot_Open_Resource;
|
||||
|
||||
} while ((FT_Long)++index != (*face)->num_faces);
|
||||
|
||||
|
||||
folder_error:
|
||||
registry_no_font_found:
|
||||
free(font_namep);
|
||||
RegCloseKey(hKey);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonts can have localised names and when the system locale is the same as
|
||||
* one of those localised names Windows will always return that localised name
|
||||
* instead of allowing to get the non-localised (English US) name of the font.
|
||||
* This will later on give problems as freetype uses the non-localised name of
|
||||
* the font and we need to compare based on that name.
|
||||
* Windows furthermore DOES NOT have an API to get the non-localised name nor
|
||||
* can we override the system locale. This means that we have to actually read
|
||||
* the font itself to gather the font name we want.
|
||||
* Based on: http://blogs.msdn.com/michkap/archive/2006/02/13/530814.aspx
|
||||
* @param logfont the font information to get the english name of.
|
||||
* @return the English name (if it could be found).
|
||||
*/
|
||||
static std::string GetEnglishFontName(const ENUMLOGFONTEX *logfont)
|
||||
{
|
||||
static char font_name[MAX_PATH];
|
||||
const char *ret_font_name = nullptr;
|
||||
uint pos = 0;
|
||||
HDC dc;
|
||||
HGDIOBJ oldfont;
|
||||
byte *buf;
|
||||
DWORD dw;
|
||||
uint16 format, count, stringOffset, platformId, encodingId, languageId, nameId, length, offset;
|
||||
|
||||
HFONT font = CreateFontIndirect(&logfont->elfLogFont);
|
||||
if (font == nullptr) goto err1;
|
||||
|
||||
dc = GetDC(nullptr);
|
||||
oldfont = SelectObject(dc, font);
|
||||
dw = GetFontData(dc, 'eman', 0, nullptr, 0);
|
||||
if (dw == GDI_ERROR) goto err2;
|
||||
|
||||
buf = MallocT<byte>(dw);
|
||||
dw = GetFontData(dc, 'eman', 0, buf, dw);
|
||||
if (dw == GDI_ERROR) goto err3;
|
||||
|
||||
format = buf[pos++] << 8;
|
||||
format += buf[pos++];
|
||||
assert(format == 0);
|
||||
count = buf[pos++] << 8;
|
||||
count += buf[pos++];
|
||||
stringOffset = buf[pos++] << 8;
|
||||
stringOffset += buf[pos++];
|
||||
for (uint i = 0; i < count; i++) {
|
||||
platformId = buf[pos++] << 8;
|
||||
platformId += buf[pos++];
|
||||
encodingId = buf[pos++] << 8;
|
||||
encodingId += buf[pos++];
|
||||
languageId = buf[pos++] << 8;
|
||||
languageId += buf[pos++];
|
||||
nameId = buf[pos++] << 8;
|
||||
nameId += buf[pos++];
|
||||
if (nameId != 1) {
|
||||
pos += 4; // skip length and offset
|
||||
continue;
|
||||
}
|
||||
length = buf[pos++] << 8;
|
||||
length += buf[pos++];
|
||||
offset = buf[pos++] << 8;
|
||||
offset += buf[pos++];
|
||||
|
||||
/* Don't buffer overflow */
|
||||
length = std::min<uint16>(length, MAX_PATH - 1);
|
||||
for (uint j = 0; j < length; j++) font_name[j] = buf[stringOffset + offset + j];
|
||||
font_name[length] = '\0';
|
||||
|
||||
if ((platformId == 1 && languageId == 0) || // Macintosh English
|
||||
(platformId == 3 && languageId == 0x0409)) { // Microsoft English (US)
|
||||
ret_font_name = font_name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
err3:
|
||||
free(buf);
|
||||
err2:
|
||||
SelectObject(dc, oldfont);
|
||||
ReleaseDC(nullptr, dc);
|
||||
DeleteObject(font);
|
||||
err1:
|
||||
return ret_font_name == nullptr ? FS2OTTD((const wchar_t *)logfont->elfFullName) : std::string(ret_font_name);
|
||||
}
|
||||
#endif /* WITH_FREETYPE */
|
||||
|
||||
class FontList {
|
||||
protected:
|
||||
wchar_t **fonts;
|
||||
uint items;
|
||||
uint capacity;
|
||||
|
||||
public:
|
||||
FontList() : fonts(nullptr), items(0), capacity(0) { };
|
||||
|
||||
~FontList() {
|
||||
if (this->fonts == nullptr) return;
|
||||
|
||||
for (uint i = 0; i < this->items; i++) {
|
||||
free(this->fonts[i]);
|
||||
}
|
||||
|
||||
free(this->fonts);
|
||||
}
|
||||
|
||||
bool Add(const wchar_t *font) {
|
||||
for (uint i = 0; i < this->items; i++) {
|
||||
if (wcscmp(this->fonts[i], font) == 0) return false;
|
||||
}
|
||||
|
||||
if (this->items == this->capacity) {
|
||||
this->capacity += 10;
|
||||
this->fonts = ReallocT(this->fonts, this->capacity);
|
||||
}
|
||||
|
||||
this->fonts[this->items++] = wcsdup(font);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct EFCParam {
|
||||
FontCacheSettings *settings;
|
||||
LOCALESIGNATURE locale;
|
||||
MissingGlyphSearcher *callback;
|
||||
FontList fonts;
|
||||
std::vector<std::wstring> fonts;
|
||||
|
||||
bool Add(const std::wstring_view &font)
|
||||
{
|
||||
for (const auto &entry : this->fonts) {
|
||||
if (font.compare(entry) == 0) return false;
|
||||
}
|
||||
|
||||
this->fonts.emplace_back(font);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXTMETRICEX *metric, DWORD type, LPARAM lParam)
|
||||
@@ -280,7 +56,7 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT
|
||||
EFCParam *info = (EFCParam *)lParam;
|
||||
|
||||
/* Skip duplicates */
|
||||
if (!info->fonts.Add((const wchar_t *)logfont->elfFullName)) return 1;
|
||||
if (!info->Add(logfont->elfFullName)) return 1;
|
||||
/* Only use TrueType fonts */
|
||||
if (!(type & TRUETYPE_FONTTYPE)) return 1;
|
||||
/* Don't use SYMBOL fonts */
|
||||
@@ -308,38 +84,13 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT
|
||||
char font_name[MAX_PATH];
|
||||
convert_from_fs((const wchar_t *)logfont->elfFullName, font_name, lengthof(font_name));
|
||||
|
||||
#ifdef WITH_FREETYPE
|
||||
/* Add english name after font name */
|
||||
std::string english_name = GetEnglishFontName(logfont);
|
||||
strecpy(font_name + strlen(font_name) + 1, english_name.c_str(), lastof(font_name));
|
||||
|
||||
/* Check whether we can actually load the font. */
|
||||
bool ft_init = _library != nullptr;
|
||||
bool found = false;
|
||||
FT_Face face;
|
||||
/* Init FreeType if needed. */
|
||||
if ((ft_init || FT_Init_FreeType(&_library) == FT_Err_Ok) && GetFontByFaceName(font_name, &face) == FT_Err_Ok) {
|
||||
FT_Done_Face(face);
|
||||
found = true;
|
||||
}
|
||||
if (!ft_init) {
|
||||
/* Uninit FreeType if we did the init. */
|
||||
FT_Done_FreeType(_library);
|
||||
_library = nullptr;
|
||||
}
|
||||
|
||||
if (!found) return 1;
|
||||
#else
|
||||
const char *english_name = font_name;
|
||||
#endif /* WITH_FREETYPE */
|
||||
|
||||
info->callback->SetFontNames(info->settings, font_name, &logfont->elfLogFont);
|
||||
if (info->callback->FindMissingGlyphs()) return 1;
|
||||
Debug(fontcache, 1, "Fallback font: {} ({})", font_name, english_name);
|
||||
Debug(fontcache, 1, "Fallback font: {}", font_name);
|
||||
return 0; // stop enumerating
|
||||
}
|
||||
|
||||
bool SetFallbackFont(FontCacheSettings *settings, const char *language_isocode, int winlangid, MissingGlyphSearcher *callback)
|
||||
bool SetFallbackFont(FontCacheSettings *settings, const std::string &, int winlangid, MissingGlyphSearcher *callback)
|
||||
{
|
||||
Debug(fontcache, 1, "Trying fallback fonts");
|
||||
EFCParam langInfo;
|
||||
@@ -377,7 +128,7 @@ bool SetFallbackFont(FontCacheSettings *settings, const char *language_isocode,
|
||||
Win32FontCache::Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels) : TrueTypeFontCache(fs, pixels), logfont(logfont)
|
||||
{
|
||||
this->dc = CreateCompatibleDC(nullptr);
|
||||
this->SetFontSize(fs, pixels);
|
||||
this->SetFontSize(pixels);
|
||||
}
|
||||
|
||||
Win32FontCache::~Win32FontCache()
|
||||
@@ -387,7 +138,7 @@ Win32FontCache::~Win32FontCache()
|
||||
DeleteObject(this->font);
|
||||
}
|
||||
|
||||
void Win32FontCache::SetFontSize(FontSize fs, int pixels)
|
||||
void Win32FontCache::SetFontSize(int pixels)
|
||||
{
|
||||
if (pixels == 0) {
|
||||
/* Try to determine a good height based on the minimal height recommended by the font. */
|
||||
@@ -399,7 +150,7 @@ void Win32FontCache::SetFontSize(FontSize fs, int pixels)
|
||||
HGDIOBJ old = SelectObject(this->dc, temp);
|
||||
|
||||
UINT size = GetOutlineTextMetrics(this->dc, 0, nullptr);
|
||||
LPOUTLINETEXTMETRIC otm = (LPOUTLINETEXTMETRIC)AllocaM(BYTE, size);
|
||||
LPOUTLINETEXTMETRIC otm = (LPOUTLINETEXTMETRIC)new BYTE[size];
|
||||
GetOutlineTextMetrics(this->dc, size, otm);
|
||||
|
||||
/* Font height is minimum height plus the difference between the default
|
||||
@@ -408,6 +159,7 @@ void Win32FontCache::SetFontSize(FontSize fs, int pixels)
|
||||
/* Clamp() is not used as scaled_height could be greater than MAX_FONT_SIZE, which is not permitted in Clamp(). */
|
||||
pixels = std::min(std::max(std::min<int>(otm->otmusMinimumPPEM, MAX_FONT_MIN_REC_SIZE) + diff, scaled_height), MAX_FONT_SIZE);
|
||||
|
||||
delete[] (BYTE*)otm;
|
||||
SelectObject(dc, old);
|
||||
DeleteObject(temp);
|
||||
}
|
||||
@@ -430,7 +182,7 @@ void Win32FontCache::SetFontSize(FontSize fs, int pixels)
|
||||
|
||||
/* Query the font metrics we needed. */
|
||||
UINT otmSize = GetOutlineTextMetrics(this->dc, 0, nullptr);
|
||||
POUTLINETEXTMETRIC otm = (POUTLINETEXTMETRIC)AllocaM(BYTE, otmSize);
|
||||
POUTLINETEXTMETRIC otm = (POUTLINETEXTMETRIC)new BYTE[otmSize];
|
||||
GetOutlineTextMetrics(this->dc, otmSize, otm);
|
||||
|
||||
this->units_per_em = otm->otmEMSquare;
|
||||
@@ -443,6 +195,7 @@ void Win32FontCache::SetFontSize(FontSize fs, int pixels)
|
||||
this->fontname = FS2OTTD((LPWSTR)((BYTE *)otm + (ptrdiff_t)otm->otmpFaceName));
|
||||
|
||||
Debug(fontcache, 2, "Loaded font '{}' with size {}", this->fontname, pixels);
|
||||
delete[] (BYTE*)otm;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,7 +204,7 @@ void Win32FontCache::SetFontSize(FontSize fs, int pixels)
|
||||
void Win32FontCache::ClearFontCache()
|
||||
{
|
||||
/* GUI scaling might have changed, determine font size anew if it was automatically selected. */
|
||||
if (this->font != nullptr) this->SetFontSize(this->fs, this->req_size);
|
||||
if (this->font != nullptr) this->SetFontSize(this->req_size);
|
||||
|
||||
this->TrueTypeFontCache::ClearFontCache();
|
||||
}
|
||||
@@ -461,22 +214,9 @@ void Win32FontCache::ClearFontCache()
|
||||
GLYPHMETRICS gm;
|
||||
MAT2 mat = { {0, 1}, {0, 0}, {0, 0}, {0, 1} };
|
||||
|
||||
/* Make a guess for the needed memory size. */
|
||||
DWORD size = this->glyph_size.cy * Align(aa ? this->glyph_size.cx : std::max(this->glyph_size.cx / 8l, 1l), 4); // Bitmap data is DWORD-aligned rows.
|
||||
byte *bmp = AllocaM(byte, size);
|
||||
size = GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, size, bmp, &mat);
|
||||
|
||||
if (size == GDI_ERROR) {
|
||||
/* No dice with the guess. First query size of needed glyph memory, then allocate the
|
||||
* memory and query again. This dance is necessary as some glyphs will only render with
|
||||
* the exact matching size; e.g. the space glyph has no pixels and must be requested
|
||||
* with size == 0, anything else fails. Unfortunately, a failed call doesn't return any
|
||||
* info about the size and thus the triple GetGlyphOutline()-call. */
|
||||
size = GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, 0, nullptr, &mat);
|
||||
if (size == GDI_ERROR) usererror("Unable to render font glyph");
|
||||
bmp = AllocaM(byte, size);
|
||||
GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, size, bmp, &mat);
|
||||
}
|
||||
/* Call GetGlyphOutline with zero size initially to get required memory size. */
|
||||
DWORD size = GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, 0, nullptr, &mat);
|
||||
if (size == GDI_ERROR) UserError("Unable to render font glyph");
|
||||
|
||||
/* Add 1 scaled pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel. */
|
||||
uint shadow = (this->fs == FS_NORMAL) ? ScaleGUITrad(1) : 0;
|
||||
@@ -484,12 +224,17 @@ void Win32FontCache::ClearFontCache()
|
||||
uint height = std::max(1U, (uint)gm.gmBlackBoxY + shadow);
|
||||
|
||||
/* Limit glyph size to prevent overflows later on. */
|
||||
if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) usererror("Font glyph is too large");
|
||||
if (width > MAX_GLYPH_DIM || height > MAX_GLYPH_DIM) UserError("Font glyph is too large");
|
||||
|
||||
/* Call GetGlyphOutline again with size to actually render the glyph. */
|
||||
byte *bmp = new byte[size];
|
||||
GetGlyphOutline(this->dc, key, GGO_GLYPH_INDEX | (aa ? GGO_GRAY8_BITMAP : GGO_BITMAP), &gm, size, bmp, &mat);
|
||||
|
||||
/* GDI has rendered the glyph, now we allocate a sprite and copy the image into it. */
|
||||
SpriteLoader::Sprite sprite;
|
||||
SpriteLoader::SpriteCollection spritecollection;
|
||||
SpriteLoader::Sprite &sprite = spritecollection[ZOOM_LVL_NORMAL];
|
||||
sprite.AllocateData(ZOOM_LVL_NORMAL, width * height);
|
||||
sprite.type = ST_FONT;
|
||||
sprite.type = SpriteType::Font;
|
||||
sprite.colours = (aa ? SCC_PAL | SCC_ALPHA : SCC_PAL);
|
||||
sprite.width = width;
|
||||
sprite.height = height;
|
||||
@@ -527,22 +272,20 @@ void Win32FontCache::ClearFontCache()
|
||||
}
|
||||
|
||||
GlyphEntry new_glyph;
|
||||
new_glyph.sprite = BlitterFactory::GetCurrentBlitter()->Encode(&sprite, SimpleSpriteAlloc);
|
||||
new_glyph.sprite = BlitterFactory::GetCurrentBlitter()->Encode(spritecollection, SimpleSpriteAlloc);
|
||||
new_glyph.width = gm.gmCellIncX;
|
||||
|
||||
this->SetGlyphPtr(key, &new_glyph);
|
||||
|
||||
delete[] bmp;
|
||||
|
||||
return new_glyph.sprite;
|
||||
}
|
||||
|
||||
/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(WChar key)
|
||||
/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(char32_t key, bool allow_fallback)
|
||||
{
|
||||
assert(IsPrintable(key));
|
||||
|
||||
if (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) {
|
||||
return this->parent->MapCharToGlyph(key);
|
||||
}
|
||||
|
||||
/* Convert characters outside of the BMP into surrogate pairs. */
|
||||
WCHAR chars[2];
|
||||
if (key >= 0x010000U) {
|
||||
@@ -555,10 +298,11 @@ void Win32FontCache::ClearFontCache()
|
||||
WORD glyphs[2] = { 0, 0 };
|
||||
GetGlyphIndicesW(this->dc, chars, key >= 0x010000U ? 2 : 1, glyphs, GGI_MARK_NONEXISTING_GLYPHS);
|
||||
|
||||
return glyphs[0] != 0xFFFF ? glyphs[0] : 0;
|
||||
if (glyphs[0] != 0xFFFF) return glyphs[0];
|
||||
return allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END ? this->parent->MapCharToGlyph(key) : 0;
|
||||
}
|
||||
|
||||
/* virtual */ const void *Win32FontCache::InternalGetFontTable(uint32 tag, size_t &length)
|
||||
/* virtual */ const void *Win32FontCache::InternalGetFontTable(uint32_t tag, size_t &length)
|
||||
{
|
||||
DWORD len = GetFontData(this->dc, tag, 0, nullptr, 0);
|
||||
|
||||
@@ -573,6 +317,66 @@ void Win32FontCache::ClearFontCache()
|
||||
}
|
||||
|
||||
|
||||
static bool TryLoadFontFromFile(const std::string &font_name, LOGFONT &logfont)
|
||||
{
|
||||
wchar_t fontPath[MAX_PATH] = {};
|
||||
|
||||
/* See if this is an absolute path. */
|
||||
if (FileExists(font_name)) {
|
||||
convert_to_fs(font_name, fontPath, lengthof(fontPath));
|
||||
} else {
|
||||
/* Scan the search-paths to see if it can be found. */
|
||||
std::string full_font = FioFindFullPath(BASE_DIR, font_name);
|
||||
if (!full_font.empty()) {
|
||||
convert_to_fs(font_name, fontPath, lengthof(fontPath));
|
||||
}
|
||||
}
|
||||
|
||||
if (fontPath[0] != 0) {
|
||||
if (AddFontResourceEx(fontPath, FR_PRIVATE, 0) != 0) {
|
||||
/* Try a nice little undocumented function first for getting the internal font name.
|
||||
* Some documentation is found at: http://www.undocprint.org/winspool/getfontresourceinfo */
|
||||
static LibraryLoader _gdi32("gdi32.dll");
|
||||
typedef BOOL(WINAPI *PFNGETFONTRESOURCEINFO)(LPCTSTR, LPDWORD, LPVOID, DWORD);
|
||||
static PFNGETFONTRESOURCEINFO GetFontResourceInfo = _gdi32.GetFunction("GetFontResourceInfoW");
|
||||
|
||||
if (GetFontResourceInfo != nullptr) {
|
||||
/* Try to query an array of LOGFONTs that describe the file. */
|
||||
DWORD len = 0;
|
||||
if (GetFontResourceInfo(fontPath, &len, nullptr, 2) && len >= sizeof(LOGFONT)) {
|
||||
LOGFONT *buf = (LOGFONT *)new byte[len];
|
||||
if (GetFontResourceInfo(fontPath, &len, buf, 2)) {
|
||||
logfont = *buf; // Just use first entry.
|
||||
}
|
||||
delete[](byte *)buf;
|
||||
}
|
||||
}
|
||||
|
||||
/* No dice yet. Use the file name as the font face name, hoping it matches. */
|
||||
if (logfont.lfFaceName[0] == 0) {
|
||||
wchar_t fname[_MAX_FNAME];
|
||||
_wsplitpath(fontPath, nullptr, nullptr, fname, nullptr);
|
||||
|
||||
wcsncpy_s(logfont.lfFaceName, lengthof(logfont.lfFaceName), fname, _TRUNCATE);
|
||||
logfont.lfWeight = strcasestr(font_name.c_str(), " bold") != nullptr || strcasestr(font_name.c_str(), "-bold") != nullptr ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return logfont.lfFaceName[0] != 0;
|
||||
}
|
||||
|
||||
static void LoadWin32Font(FontSize fs, const LOGFONT &logfont, uint size, const char *font_name)
|
||||
{
|
||||
HFONT font = CreateFontIndirect(&logfont);
|
||||
if (font == nullptr) {
|
||||
ShowInfo("Unable to use '{}' for {} font, Win32 reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), GetLastError());
|
||||
return;
|
||||
}
|
||||
DeleteObject(font);
|
||||
|
||||
new Win32FontCache(fs, logfont, size);
|
||||
}
|
||||
/**
|
||||
* Loads the GDI font.
|
||||
* If a GDI font description is present, e.g. from the automatic font
|
||||
@@ -597,50 +401,8 @@ void LoadWin32Font(FontSize fs)
|
||||
logfont = *(const LOGFONT *)settings->os_handle;
|
||||
} else if (strchr(font_name, '.') != nullptr) {
|
||||
/* Might be a font file name, try load it. */
|
||||
|
||||
wchar_t fontPath[MAX_PATH] = {};
|
||||
|
||||
/* See if this is an absolute path. */
|
||||
if (FileExists(settings->font)) {
|
||||
convert_to_fs(font_name, fontPath, lengthof(fontPath));
|
||||
} else {
|
||||
/* Scan the search-paths to see if it can be found. */
|
||||
std::string full_font = FioFindFullPath(BASE_DIR, font_name);
|
||||
if (!full_font.empty()) {
|
||||
convert_to_fs(font_name, fontPath, lengthof(fontPath));
|
||||
}
|
||||
}
|
||||
|
||||
if (fontPath[0] != 0) {
|
||||
if (AddFontResourceEx(fontPath, FR_PRIVATE, 0) != 0) {
|
||||
/* Try a nice little undocumented function first for getting the internal font name.
|
||||
* Some documentation is found at: http://www.undocprint.org/winspool/getfontresourceinfo */
|
||||
static DllLoader _gdi32(L"gdi32.dll");
|
||||
typedef BOOL(WINAPI *PFNGETFONTRESOURCEINFO)(LPCTSTR, LPDWORD, LPVOID, DWORD);
|
||||
static PFNGETFONTRESOURCEINFO GetFontResourceInfo = _gdi32.GetProcAddress("GetFontResourceInfoW");
|
||||
|
||||
if (GetFontResourceInfo != nullptr) {
|
||||
/* Try to query an array of LOGFONTs that describe the file. */
|
||||
DWORD len = 0;
|
||||
if (GetFontResourceInfo(fontPath, &len, nullptr, 2) && len >= sizeof(LOGFONT)) {
|
||||
LOGFONT *buf = (LOGFONT *)AllocaM(byte, len);
|
||||
if (GetFontResourceInfo(fontPath, &len, buf, 2)) {
|
||||
logfont = *buf; // Just use first entry.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* No dice yet. Use the file name as the font face name, hoping it matches. */
|
||||
if (logfont.lfFaceName[0] == 0) {
|
||||
wchar_t fname[_MAX_FNAME];
|
||||
_wsplitpath(fontPath, nullptr, nullptr, fname, nullptr);
|
||||
|
||||
wcsncpy_s(logfont.lfFaceName, lengthof(logfont.lfFaceName), fname, _TRUNCATE);
|
||||
logfont.lfWeight = strcasestr(font_name, " bold") != nullptr || strcasestr(font_name, "-bold") != nullptr ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts.
|
||||
}
|
||||
} else {
|
||||
ShowInfoF("Unable to load file '%s' for %s font, using default windows font selection instead", font_name, FontSizeToName(fs));
|
||||
}
|
||||
if (!TryLoadFontFromFile(settings->font, logfont)) {
|
||||
ShowInfo("Unable to load file '{}' for {} font, using default windows font selection instead", font_name, FontSizeToName(fs));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,12 +411,25 @@ void LoadWin32Font(FontSize fs)
|
||||
convert_to_fs(font_name, logfont.lfFaceName, lengthof(logfont.lfFaceName));
|
||||
}
|
||||
|
||||
HFONT font = CreateFontIndirect(&logfont);
|
||||
if (font == nullptr) {
|
||||
ShowInfoF("Unable to use '%s' for %s font, Win32 reported error 0x%lX, using sprite font instead", font_name, FontSizeToName(fs), GetLastError());
|
||||
return;
|
||||
LoadWin32Font(fs, logfont, settings->size, font_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a TrueType font from a file.
|
||||
* @param fs The font size to load.
|
||||
* @param file_name Path to the font file.
|
||||
* @param size Requested font size.
|
||||
*/
|
||||
void LoadWin32Font(FontSize fs, const std::string &file_name, uint size)
|
||||
{
|
||||
LOGFONT logfont;
|
||||
MemSetT(&logfont, 0);
|
||||
logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH;
|
||||
logfont.lfCharSet = DEFAULT_CHARSET;
|
||||
logfont.lfOutPrecision = OUT_OUTLINE_PRECIS;
|
||||
logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
||||
|
||||
if (TryLoadFontFromFile(file_name, logfont)) {
|
||||
LoadWin32Font(fs, logfont, size, file_name.c_str());
|
||||
}
|
||||
DeleteObject(font);
|
||||
|
||||
new Win32FontCache(fs, logfont, settings->size);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include "../../fontcache/truetypefontcache.h"
|
||||
#include "win32.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
/** Font cache for fonts that are based on a Win32 font. */
|
||||
class Win32FontCache : public TrueTypeFontCache {
|
||||
private:
|
||||
@@ -23,21 +25,22 @@ private:
|
||||
SIZE glyph_size; ///< Maximum size of regular glyphs.
|
||||
std::string fontname; ///< Cached copy of loaded font facename
|
||||
|
||||
void SetFontSize(FontSize fs, int pixels);
|
||||
void SetFontSize(int pixels);
|
||||
|
||||
protected:
|
||||
const void *InternalGetFontTable(uint32 tag, size_t &length) override;
|
||||
const void *InternalGetFontTable(uint32_t tag, size_t &length) override;
|
||||
const Sprite *InternalGetGlyph(GlyphID key, bool aa) override;
|
||||
|
||||
public:
|
||||
Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels);
|
||||
~Win32FontCache();
|
||||
void ClearFontCache() override;
|
||||
GlyphID MapCharToGlyph(WChar key) override;
|
||||
GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override;
|
||||
std::string GetFontName() override { return this->fontname; }
|
||||
const void *GetOSHandle() override { return &this->logfont; }
|
||||
};
|
||||
|
||||
void LoadWin32Font(FontSize fs);
|
||||
void LoadWin32Font(FontSize fs, const std::string &file_name, uint size);
|
||||
|
||||
#endif /* FONT_WIN32_H */
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file library_loader_win.cpp Implementation of the LibraryLoader for Windows */
|
||||
|
||||
#include "../../stdafx.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "../../library_loader.h"
|
||||
#include "../../3rdparty/fmt/format.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
static std::string GetLoadError()
|
||||
{
|
||||
auto error_code = GetLastError();
|
||||
|
||||
wchar_t buffer[512];
|
||||
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, lengthof(buffer), nullptr) == 0) {
|
||||
return fmt::format("Unknown error {}", error_code);
|
||||
}
|
||||
|
||||
return FS2OTTD(buffer);
|
||||
}
|
||||
|
||||
void *LibraryLoader::OpenLibrary(const std::string &filename)
|
||||
{
|
||||
void *h = ::LoadLibraryW(OTTD2FS(filename).c_str());
|
||||
if (h == nullptr) {
|
||||
this->error = GetLoadError();
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
void LibraryLoader::CloseLibrary()
|
||||
{
|
||||
HMODULE handle = static_cast<HMODULE>(this->handle);
|
||||
|
||||
::FreeLibrary(handle);
|
||||
}
|
||||
|
||||
void *LibraryLoader::GetSymbol(const std::string &symbol_name)
|
||||
{
|
||||
HMODULE handle = static_cast<HMODULE>(this->handle);
|
||||
|
||||
void *p = reinterpret_cast<void *>(::GetProcAddress(handle, symbol_name.c_str()));
|
||||
if (p == nullptr) {
|
||||
this->error = GetLoadError();
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
@@ -44,17 +44,16 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_DEFAULT
|
||||
// Dialog
|
||||
//
|
||||
|
||||
100 DIALOG DISCARDABLE 0, 0, 305, 101
|
||||
100 DIALOG DISCARDABLE 0, 0, 305, 109
|
||||
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
CAPTION "Fatal Application Failure"
|
||||
FONT 8, "MS Sans Serif"
|
||||
BEGIN
|
||||
PUSHBUTTON "&Close",12,7,82,60,14
|
||||
PUSHBUTTON "&Emergency save",13,158,82,60,14
|
||||
PUSHBUTTON "",15,238,82,60,14
|
||||
EDITTEXT 11,7,103,291,118,ES_MULTILINE | ES_READONLY | WS_VSCROLL |
|
||||
PUSHBUTTON "&Close",12,7,90,60,14
|
||||
PUSHBUTTON "",15,238,90,60,14
|
||||
EDITTEXT 11,7,111,291,118,ES_MULTILINE | ES_READONLY | WS_VSCROLL |
|
||||
WS_HSCROLL | NOT WS_TABSTOP
|
||||
LTEXT "",10,36,5,262,72
|
||||
LTEXT "",10,36,5,262,80
|
||||
ICON 100,IDC_STATIC,9,9,20,20
|
||||
END
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "../../table/control_codes.h"
|
||||
#include "../../zoom_func.h"
|
||||
#include "win32.h"
|
||||
#include <vector>
|
||||
|
||||
#include <windows.h>
|
||||
#include <usp10.h>
|
||||
@@ -55,7 +54,7 @@ struct UniscribeRun {
|
||||
};
|
||||
|
||||
/** Break a string into language formatting ranges. */
|
||||
static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutFactory::CharType *buff, int32 length);
|
||||
static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutFactory::CharType *buff, int32_t length);
|
||||
/** Generate and place glyphs for a run of characters. */
|
||||
static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *buff, UniscribeRun &range);
|
||||
|
||||
@@ -75,7 +74,7 @@ public:
|
||||
class UniscribeVisualRun : public ParagraphLayouter::VisualRun {
|
||||
private:
|
||||
std::vector<GlyphID> glyphs;
|
||||
std::vector<float> positions;
|
||||
std::vector<Point> positions;
|
||||
std::vector<WORD> char_to_glyph;
|
||||
|
||||
int start_pos;
|
||||
@@ -83,19 +82,15 @@ public:
|
||||
int num_glyphs;
|
||||
Font *font;
|
||||
|
||||
mutable int *glyph_to_char = nullptr;
|
||||
mutable std::vector<int> glyph_to_char;
|
||||
|
||||
public:
|
||||
UniscribeVisualRun(const UniscribeRun &range, int x);
|
||||
UniscribeVisualRun(UniscribeVisualRun &&other) noexcept;
|
||||
~UniscribeVisualRun() override
|
||||
{
|
||||
free(this->glyph_to_char);
|
||||
}
|
||||
|
||||
const GlyphID *GetGlyphs() const override { return &this->glyphs[0]; }
|
||||
const float *GetPositions() const override { return &this->positions[0]; }
|
||||
const int *GetGlyphToCharMap() const override;
|
||||
const std::vector<GlyphID> &GetGlyphs() const override { return this->glyphs; }
|
||||
const std::vector<Point> &GetPositions() const override { return this->positions; }
|
||||
const std::vector<int> &GetGlyphToCharMap() const override;
|
||||
|
||||
const Font *GetFont() const override { return this->font; }
|
||||
int GetLeading() const override { return this->font->fc->GetHeight(); }
|
||||
@@ -111,7 +106,7 @@ public:
|
||||
int CountRuns() const override { return (uint)this->size(); }
|
||||
const VisualRun &GetVisualRun(int run) const override { return this->at(run); }
|
||||
|
||||
int GetInternalCharLength(WChar c) const override
|
||||
int GetInternalCharLength(char32_t c) const override
|
||||
{
|
||||
/* Uniscribe uses UTF-16 internally which means we need to account for surrogate pairs. */
|
||||
return c >= 0x010000U ? 2 : 1;
|
||||
@@ -152,7 +147,7 @@ static HFONT HFontFromFont(Font *font)
|
||||
logfont.lfHeight = font->fc->GetHeight();
|
||||
logfont.lfWeight = FW_NORMAL;
|
||||
logfont.lfCharSet = DEFAULT_CHARSET;
|
||||
convert_to_fs(font->fc->GetFontName().c_str(), logfont.lfFaceName, lengthof(logfont.lfFaceName));
|
||||
convert_to_fs(font->fc->GetFontName(), logfont.lfFaceName, lengthof(logfont.lfFaceName));
|
||||
|
||||
return CreateFontIndirect(&logfont);
|
||||
}
|
||||
@@ -195,9 +190,11 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b
|
||||
for (int i = 0; i < range.len; i++) {
|
||||
if (buff[range.pos + i] >= SCC_SPRITE_START && buff[range.pos + i] <= SCC_SPRITE_END) {
|
||||
auto pos = range.char_to_glyph[i];
|
||||
range.ft_glyphs[pos] = range.font->fc->MapCharToGlyph(buff[range.pos + i]);
|
||||
range.offsets[pos].dv = (range.font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(range.font->fc->GetSize()))) / 2; // Align sprite font to centre
|
||||
range.advances[pos] = range.font->fc->GetGlyphWidth(range.ft_glyphs[pos]);
|
||||
if (range.ft_glyphs[pos] == 0) { // Font doesn't have our special glyph, so remap.
|
||||
range.ft_glyphs[pos] = range.font->fc->MapCharToGlyph(buff[range.pos + i]);
|
||||
range.offsets[pos].dv = (range.font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(range.font->fc->GetSize()))) / 2; // Align sprite font to centre
|
||||
range.advances[pos] = range.font->fc->GetGlyphWidth(range.ft_glyphs[pos]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +245,7 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutFactory::CharType *buff, int32 length)
|
||||
static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutFactory::CharType *buff, int32_t length)
|
||||
{
|
||||
/* Itemize text. */
|
||||
SCRIPT_CONTROL control;
|
||||
@@ -281,7 +278,7 @@ static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutF
|
||||
|
||||
/* static */ ParagraphLayouter *UniscribeParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping)
|
||||
{
|
||||
int32 length = buff_end - buff;
|
||||
int32_t length = buff_end - buff;
|
||||
/* Can't layout an empty string. */
|
||||
if (length == 0) return nullptr;
|
||||
|
||||
@@ -292,7 +289,7 @@ static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutF
|
||||
|
||||
/* Itemize text. */
|
||||
std::vector<SCRIPT_ITEM> items = UniscribeItemizeString(buff, length);
|
||||
if (items.size() == 0) return nullptr;
|
||||
if (items.empty()) return nullptr;
|
||||
|
||||
/* Build ranges from the items and the font map. A range is a run of text
|
||||
* that is part of a single item and formatted using a single font style. */
|
||||
@@ -416,7 +413,7 @@ static std::vector<SCRIPT_ITEM> UniscribeItemizeString(UniscribeParagraphLayoutF
|
||||
UniscribeRun run = *i_run;
|
||||
|
||||
/* Partial run after line break (either start or end)? Reshape run to get the first/last glyphs right. */
|
||||
if (i_run == last_run - 1 && remaining_offset < (last_run - 1)->len) {
|
||||
if (i_run == last_run - 1 && remaining_offset <= (last_run - 1)->len) {
|
||||
run.len = remaining_offset - 1;
|
||||
|
||||
if (!UniscribeShapeRun(this->text_buffer, run)) return nullptr;
|
||||
@@ -477,30 +474,29 @@ int UniscribeParagraphLayout::UniscribeLine::GetWidth() const
|
||||
UniscribeParagraphLayout::UniscribeVisualRun::UniscribeVisualRun(const UniscribeRun &range, int x) : glyphs(range.ft_glyphs), char_to_glyph(range.char_to_glyph), start_pos(range.pos), total_advance(range.total_advance), font(range.font)
|
||||
{
|
||||
this->num_glyphs = (int)glyphs.size();
|
||||
this->positions.resize(this->num_glyphs * 2 + 2);
|
||||
this->positions.reserve(this->num_glyphs + 1);
|
||||
|
||||
int advance = 0;
|
||||
int advance = x;
|
||||
for (int i = 0; i < this->num_glyphs; i++) {
|
||||
this->positions[i * 2 + 0] = range.offsets[i].du + advance + x;
|
||||
this->positions[i * 2 + 1] = range.offsets[i].dv;
|
||||
this->positions.emplace_back(range.offsets[i].du + advance, range.offsets[i].dv);
|
||||
|
||||
advance += range.advances[i];
|
||||
}
|
||||
this->positions[this->num_glyphs * 2] = advance + x;
|
||||
/* End-of-run position. */
|
||||
this->positions.emplace_back(advance, 0);
|
||||
}
|
||||
|
||||
UniscribeParagraphLayout::UniscribeVisualRun::UniscribeVisualRun(UniscribeVisualRun&& other) noexcept
|
||||
: glyphs(std::move(other.glyphs)), positions(std::move(other.positions)), char_to_glyph(std::move(other.char_to_glyph)),
|
||||
start_pos(other.start_pos), total_advance(other.total_advance), num_glyphs(other.num_glyphs), font(other.font)
|
||||
start_pos(other.start_pos), total_advance(other.total_advance), num_glyphs(other.num_glyphs), font(other.font),
|
||||
glyph_to_char(std::move(other.glyph_to_char))
|
||||
{
|
||||
this->glyph_to_char = other.glyph_to_char;
|
||||
other.glyph_to_char = nullptr;
|
||||
}
|
||||
|
||||
const int *UniscribeParagraphLayout::UniscribeVisualRun::GetGlyphToCharMap() const
|
||||
const std::vector<int> &UniscribeParagraphLayout::UniscribeVisualRun::GetGlyphToCharMap() const
|
||||
{
|
||||
if (this->glyph_to_char == nullptr) {
|
||||
this->glyph_to_char = CallocT<int>(this->GetGlyphCount());
|
||||
if (this->glyph_to_char.empty()) {
|
||||
this->glyph_to_char.resize(this->GetGlyphCount());
|
||||
|
||||
/* The char to glyph array contains the first glyph index of the cluster that is associated
|
||||
* with each character. It is possible for a cluster to be formed of several chars. */
|
||||
@@ -535,7 +531,7 @@ const int *UniscribeParagraphLayout::UniscribeVisualRun::GetGlyphToCharMap() con
|
||||
while (*s != '\0') {
|
||||
size_t idx = s - string_base;
|
||||
|
||||
WChar c = Utf8Consume(&s);
|
||||
char32_t c = Utf8Consume(&s);
|
||||
if (c < 0x10000) {
|
||||
utf16_str.push_back((wchar_t)c);
|
||||
} else {
|
||||
@@ -551,11 +547,11 @@ const int *UniscribeParagraphLayout::UniscribeVisualRun::GetGlyphToCharMap() con
|
||||
/* Query Uniscribe for word and cluster break information. */
|
||||
this->str_info.resize(utf16_to_utf8.size());
|
||||
|
||||
if (utf16_str.size() > 0) {
|
||||
if (!utf16_str.empty()) {
|
||||
/* Itemize string into language runs. */
|
||||
std::vector<SCRIPT_ITEM> runs = UniscribeItemizeString(&utf16_str[0], (int32)utf16_str.size());
|
||||
std::vector<SCRIPT_ITEM> runs = UniscribeItemizeString(&utf16_str[0], (int32_t)utf16_str.size());
|
||||
|
||||
for (std::vector<SCRIPT_ITEM>::const_iterator run = runs.begin(); runs.size() > 0 && run != runs.end() - 1; run++) {
|
||||
for (std::vector<SCRIPT_ITEM>::const_iterator run = runs.begin(); !runs.empty() && run != runs.end() - 1; run++) {
|
||||
/* Get information on valid word and character break.s */
|
||||
int len = (run + 1)->iCharPos - run->iCharPos;
|
||||
std::vector<SCRIPT_LOGATTR> attr(len);
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include "../../gfx_layout.h"
|
||||
#include "../../string_base.h"
|
||||
#include <vector>
|
||||
|
||||
|
||||
void UniscribeResetScriptCache(FontSize size);
|
||||
@@ -29,23 +28,24 @@ public:
|
||||
static const bool SUPPORTS_RTL = true;
|
||||
|
||||
/**
|
||||
* Get the actual ParagraphLayout for the given buffer.
|
||||
* @param buff The begin of the buffer.
|
||||
* @param buff_end The location after the last element in the buffer.
|
||||
* @param fontMapping THe mapping of the fonts.
|
||||
* @return The ParagraphLayout instance.
|
||||
*/
|
||||
* Get the actual ParagraphLayout for the given buffer.
|
||||
* @param buff The begin of the buffer.
|
||||
* @param buff_end The location after the last element in the buffer.
|
||||
* @param fontMapping THe mapping of the fonts.
|
||||
* @return The ParagraphLayout instance.
|
||||
*/
|
||||
static ParagraphLayouter *GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping);
|
||||
|
||||
/**
|
||||
* Append a wide character to the internal buffer.
|
||||
* @param buff The buffer to append to.
|
||||
* @param buffer_last The end of the buffer.
|
||||
* @param c The character to add.
|
||||
* @return The number of buffer spaces that were used.
|
||||
*/
|
||||
static size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, WChar c)
|
||||
* Append a wide character to the internal buffer.
|
||||
* @param buff The buffer to append to.
|
||||
* @param buffer_last The end of the buffer.
|
||||
* @param c The character to add.
|
||||
* @return The number of buffer spaces that were used.
|
||||
*/
|
||||
static size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, char32_t c)
|
||||
{
|
||||
assert(buff < buffer_last);
|
||||
if (c >= 0x010000U) {
|
||||
/* Character is encoded using surrogates in UTF-16. */
|
||||
if (buff + 1 <= buffer_last) {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file survey_win.cpp Windows implementation of OS-specific survey information. */
|
||||
|
||||
#include "../../stdafx.h"
|
||||
|
||||
#include "../../3rdparty/fmt/format.h"
|
||||
#include "../../survey.h"
|
||||
|
||||
#include <thread>
|
||||
#include <windows.h>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
void SurveyOS(nlohmann::json &json)
|
||||
{
|
||||
_OSVERSIONINFOA os;
|
||||
os.dwOSVersionInfoSize = sizeof(os);
|
||||
GetVersionExA(&os);
|
||||
|
||||
json["os"] = "Windows";
|
||||
json["release"] = fmt::format("{}.{}.{} ({})", os.dwMajorVersion, os.dwMinorVersion, os.dwBuildNumber, os.szCSDVersion);
|
||||
|
||||
MEMORYSTATUSEX status;
|
||||
status.dwLength = sizeof(status);
|
||||
GlobalMemoryStatusEx(&status);
|
||||
|
||||
json["memory"] = SurveyMemoryToText(status.ullTotalPhys);
|
||||
json["hardware_concurrency"] = std::thread::hardware_concurrency();
|
||||
}
|
||||
+96
-142
@@ -19,18 +19,15 @@
|
||||
#define NO_SHOBJIDL_SORTDIRECTION // Avoid multiple definition of SORT_ASCENDING
|
||||
#include <shlobj.h> /* SHGetFolderPath */
|
||||
#include <shellapi.h>
|
||||
#include <WinNls.h>
|
||||
#include "win32.h"
|
||||
#include "../../fios.h"
|
||||
#include "../../core/alloc_func.hpp"
|
||||
#include "../../openttd.h"
|
||||
#include "../../core/random_func.hpp"
|
||||
#include "../../string_func.h"
|
||||
#include "../../crashlog.h"
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include "../../language.h"
|
||||
#include "../../thread.h"
|
||||
#include <array>
|
||||
#include "../../library_loader.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
@@ -50,13 +47,13 @@ bool MyShowCursor(bool show, bool toggle)
|
||||
return !show;
|
||||
}
|
||||
|
||||
void ShowOSErrorBox(const char *buf, bool system)
|
||||
void ShowOSErrorBox(const char *buf, bool)
|
||||
{
|
||||
MyShowCursor(true);
|
||||
MessageBox(GetActiveWindow(), OTTD2FS(buf).c_str(), L"Error!", MB_ICONSTOP | MB_TASKMODAL);
|
||||
}
|
||||
|
||||
void OSOpenBrowser(const char *url)
|
||||
void OSOpenBrowser(const std::string &url)
|
||||
{
|
||||
ShellExecute(GetActiveWindow(), L"open", OTTD2FS(url).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
}
|
||||
@@ -173,9 +170,9 @@ int closedir(DIR *d)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool FiosIsRoot(const char *file)
|
||||
bool FiosIsRoot(const std::string &file)
|
||||
{
|
||||
return file[3] == '\0'; // C:\...
|
||||
return file.size() == 3; // C:\...
|
||||
}
|
||||
|
||||
void FiosGetDrives(FileList &file_list)
|
||||
@@ -188,25 +185,26 @@ void FiosGetDrives(FileList &file_list)
|
||||
FiosItem *fios = &file_list.emplace_back();
|
||||
fios->type = FIOS_TYPE_DRIVE;
|
||||
fios->mtime = 0;
|
||||
seprintf(fios->name, lastof(fios->name), "%c:", s[0] & 0xFF);
|
||||
strecpy(fios->title, fios->name, lastof(fios->title));
|
||||
fios->name += (char)(s[0] & 0xFF);
|
||||
fios->name += ':';
|
||||
fios->title = fios->name;
|
||||
while (*s++ != '\0') { /* Nothing */ }
|
||||
}
|
||||
}
|
||||
|
||||
bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb)
|
||||
bool FiosIsValidFile(const std::string &, const struct dirent *ent, struct stat *sb)
|
||||
{
|
||||
/* hectonanoseconds between Windows and POSIX epoch */
|
||||
static const int64 posix_epoch_hns = 0x019DB1DED53E8000LL;
|
||||
static const int64_t posix_epoch_hns = 0x019DB1DED53E8000LL;
|
||||
const WIN32_FIND_DATA *fd = &ent->dir->fd;
|
||||
|
||||
sb->st_size = ((uint64) fd->nFileSizeHigh << 32) + fd->nFileSizeLow;
|
||||
sb->st_size = ((uint64_t) fd->nFileSizeHigh << 32) + fd->nFileSizeLow;
|
||||
/* UTC FILETIME to seconds-since-1970 UTC
|
||||
* we just have to subtract POSIX epoch and scale down to units of seconds.
|
||||
* http://www.gamedev.net/community/forums/topic.asp?topic_id=294070&whichpage=1�
|
||||
* XXX - not entirely correct, since filetimes on FAT aren't UTC but local,
|
||||
* this won't entirely be correct, but we use the time only for comparison. */
|
||||
sb->st_mtime = (time_t)((*(const uint64*)&fd->ftLastWriteTime - posix_epoch_hns) / 1E7);
|
||||
sb->st_mtime = (time_t)((*(const uint64_t*)&fd->ftLastWriteTime - posix_epoch_hns) / 1E7);
|
||||
sb->st_mode = (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)? S_IFDIR : S_IFREG;
|
||||
|
||||
return true;
|
||||
@@ -217,47 +215,17 @@ bool FiosIsHiddenFile(const struct dirent *ent)
|
||||
return (ent->dir->fd.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != 0;
|
||||
}
|
||||
|
||||
bool FiosGetDiskFreeSpace(const char *path, uint64 *tot)
|
||||
std::optional<uint64_t> FiosGetDiskFreeSpace(const std::string &path)
|
||||
{
|
||||
UINT sem = SetErrorMode(SEM_FAILCRITICALERRORS); // disable 'no-disk' message box
|
||||
|
||||
ULARGE_INTEGER bytes_free;
|
||||
bool retval = GetDiskFreeSpaceEx(OTTD2FS(path).c_str(), &bytes_free, nullptr, nullptr);
|
||||
if (retval && tot != nullptr) *tot = bytes_free.QuadPart;
|
||||
|
||||
SetErrorMode(sem); // reset previous setting
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int ParseCommandLine(char *line, char **argv, int max_argc)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
do {
|
||||
/* skip whitespace */
|
||||
while (*line == ' ' || *line == '\t') line++;
|
||||
|
||||
/* end? */
|
||||
if (*line == '\0') break;
|
||||
|
||||
/* special handling when quoted */
|
||||
if (*line == '"') {
|
||||
argv[n++] = ++line;
|
||||
while (*line != '"') {
|
||||
if (*line == '\0') return n;
|
||||
line++;
|
||||
}
|
||||
} else {
|
||||
argv[n++] = line;
|
||||
while (*line != ' ' && *line != '\t') {
|
||||
if (*line == '\0') return n;
|
||||
line++;
|
||||
}
|
||||
}
|
||||
*line++ = '\0';
|
||||
} while (n != max_argc);
|
||||
|
||||
return n;
|
||||
if (retval) return bytes_free.QuadPart;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void CreateConsole()
|
||||
@@ -317,7 +285,7 @@ void CreateConsole()
|
||||
static const char *_help_msg;
|
||||
|
||||
/** Callback function to handle the window */
|
||||
static INT_PTR CALLBACK HelpDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
static INT_PTR CALLBACK HelpDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM)
|
||||
{
|
||||
switch (msg) {
|
||||
case WM_INITDIALOG: {
|
||||
@@ -352,21 +320,21 @@ static INT_PTR CALLBACK HelpDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void ShowInfo(const char *str)
|
||||
void ShowInfoI(const std::string &str)
|
||||
{
|
||||
if (_has_console) {
|
||||
fprintf(stderr, "%s\n", str);
|
||||
fmt::print(stderr, "{}\n", str);
|
||||
} else {
|
||||
bool old;
|
||||
ReleaseCapture();
|
||||
_left_button_clicked = _left_button_down = false;
|
||||
|
||||
old = MyShowCursor(true);
|
||||
if (strlen(str) > 2048) {
|
||||
if (str.size() > 2048) {
|
||||
/* The minimum length of the help message is 2048. Other messages sent via
|
||||
* ShowInfo are much shorter, or so long they need this way of displaying
|
||||
* them anyway. */
|
||||
_help_msg = str;
|
||||
_help_msg = str.c_str();
|
||||
DialogBox(GetModuleHandle(nullptr), MAKEINTRESOURCE(101), nullptr, HelpDialogFunc);
|
||||
} else {
|
||||
/* We need to put the text in a separate buffer because the default
|
||||
@@ -378,47 +346,6 @@ void ShowInfo(const char *str)
|
||||
}
|
||||
}
|
||||
|
||||
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
||||
{
|
||||
int argc;
|
||||
char *argv[64]; // max 64 command line arguments
|
||||
|
||||
/* Set system timer resolution to 1ms. */
|
||||
timeBeginPeriod(1);
|
||||
|
||||
CrashLog::InitialiseCrashLog();
|
||||
|
||||
/* Convert the command line to UTF-8. We need a dedicated buffer
|
||||
* for this because argv[] points into this buffer and this needs to
|
||||
* be available between subsequent calls to FS2OTTD(). */
|
||||
char *cmdline = stredup(FS2OTTD(GetCommandLine()).c_str());
|
||||
|
||||
/* Set the console codepage to UTF-8. */
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
|
||||
#if defined(_DEBUG)
|
||||
CreateConsole();
|
||||
#endif
|
||||
|
||||
_set_error_mode(_OUT_TO_MSGBOX); // force assertion output to messagebox
|
||||
|
||||
/* setup random seed to something quite random */
|
||||
SetRandomSeed(GetTickCount());
|
||||
|
||||
argc = ParseCommandLine(cmdline, argv, lengthof(argv));
|
||||
|
||||
/* Make sure our arguments contain only valid UTF-8 characters. */
|
||||
for (int i = 0; i < argc; i++) StrMakeValidInPlace(argv[i]);
|
||||
|
||||
openttd_main(argc, argv);
|
||||
|
||||
/* Restore system timer resolution. */
|
||||
timeEndPeriod(1);
|
||||
|
||||
free(cmdline);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *getcwd(char *buf, size_t size)
|
||||
{
|
||||
wchar_t path[MAX_PATH];
|
||||
@@ -472,7 +399,7 @@ void DetermineBasePaths(const char *exe)
|
||||
} else {
|
||||
/* Use the folder of the config file as working directory. */
|
||||
wchar_t config_dir[MAX_PATH];
|
||||
wcsncpy(path, convert_to_fs(_config_file.c_str(), path, lengthof(path)), lengthof(path));
|
||||
wcsncpy(path, convert_to_fs(_config_file, path, lengthof(path)), lengthof(path));
|
||||
if (!GetFullPathName(path, lengthof(config_dir), config_dir, nullptr)) {
|
||||
Debug(misc, 0, "GetFullPathName failed ({})", GetLastError());
|
||||
_searchpaths[SP_WORKING_DIR].clear();
|
||||
@@ -508,26 +435,19 @@ void DetermineBasePaths(const char *exe)
|
||||
}
|
||||
|
||||
|
||||
bool GetClipboardContents(char *buffer, const char *last)
|
||||
std::optional<std::string> GetClipboardContents()
|
||||
{
|
||||
HGLOBAL cbuf;
|
||||
const char *ptr;
|
||||
if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return std::nullopt;
|
||||
|
||||
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
|
||||
OpenClipboard(nullptr);
|
||||
cbuf = GetClipboardData(CF_UNICODETEXT);
|
||||
OpenClipboard(nullptr);
|
||||
HGLOBAL cbuf = GetClipboardData(CF_UNICODETEXT);
|
||||
|
||||
ptr = (const char*)GlobalLock(cbuf);
|
||||
int out_len = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)ptr, -1, buffer, (last - buffer) + 1, nullptr, nullptr);
|
||||
GlobalUnlock(cbuf);
|
||||
CloseClipboard();
|
||||
std::string result = FS2OTTD(static_cast<LPCWSTR>(GlobalLock(cbuf)));
|
||||
GlobalUnlock(cbuf);
|
||||
CloseClipboard();
|
||||
|
||||
if (out_len == 0) return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (result.empty()) return std::nullopt;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -544,10 +464,9 @@ std::string FS2OTTD(const std::wstring &name)
|
||||
int name_len = (name.length() >= INT_MAX) ? INT_MAX : (int)name.length();
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, name.c_str(), name_len, nullptr, 0, nullptr, nullptr);
|
||||
if (len <= 0) return std::string();
|
||||
char *utf8_buf = AllocaM(char, len + 1);
|
||||
utf8_buf[len] = '\0';
|
||||
WideCharToMultiByte(CP_UTF8, 0, name.c_str(), name_len, utf8_buf, len, nullptr, nullptr);
|
||||
return std::string(utf8_buf, static_cast<size_t>(len));
|
||||
std::string utf8_buf(len, '\0'); // len includes terminating null
|
||||
WideCharToMultiByte(CP_UTF8, 0, name.c_str(), name_len, utf8_buf.data(), len, nullptr, nullptr);
|
||||
return utf8_buf;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -562,10 +481,9 @@ std::wstring OTTD2FS(const std::string &name)
|
||||
int name_len = (name.length() >= INT_MAX) ? INT_MAX : (int)name.length();
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), name_len, nullptr, 0);
|
||||
if (len <= 0) return std::wstring();
|
||||
wchar_t *system_buf = AllocaM(wchar_t, len + 1);
|
||||
system_buf[len] = L'\0';
|
||||
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), name_len, system_buf, len);
|
||||
return std::wstring(system_buf, static_cast<size_t>(len));
|
||||
std::wstring system_buf(len, L'\0'); // len includes terminating null
|
||||
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), name_len, system_buf.data(), len);
|
||||
return system_buf;
|
||||
}
|
||||
|
||||
|
||||
@@ -597,10 +515,10 @@ char *convert_from_fs(const wchar_t *name, char *utf8_buf, size_t buflen)
|
||||
* @param console_cp convert to the console encoding instead of the normal system encoding.
|
||||
* @return pointer to system_buf. If conversion fails the string is of zero-length
|
||||
*/
|
||||
wchar_t *convert_to_fs(const char *name, wchar_t *system_buf, size_t buflen)
|
||||
wchar_t *convert_to_fs(const std::string_view name, wchar_t *system_buf, size_t buflen)
|
||||
{
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, name, -1, system_buf, (int)buflen);
|
||||
if (len == 0) system_buf[0] = '\0';
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, name.data(), (int)name.size(), system_buf, (int)buflen);
|
||||
system_buf[len] = '\0';
|
||||
|
||||
return system_buf;
|
||||
}
|
||||
@@ -625,26 +543,24 @@ const char *GetCurrentLocale(const char *)
|
||||
|
||||
static WCHAR _cur_iso_locale[16] = L"";
|
||||
|
||||
void Win32SetCurrentLocaleName(const char *iso_code)
|
||||
void Win32SetCurrentLocaleName(std::string iso_code)
|
||||
{
|
||||
/* Convert the iso code into the format that windows expects. */
|
||||
char iso[16];
|
||||
if (strcmp(iso_code, "zh_TW") == 0) {
|
||||
strecpy(iso, "zh-Hant", lastof(iso));
|
||||
} else if (strcmp(iso_code, "zh_CN") == 0) {
|
||||
strecpy(iso, "zh-Hans", lastof(iso));
|
||||
if (iso_code == "zh_TW") {
|
||||
iso_code = "zh-Hant";
|
||||
} else if (iso_code == "zh_CN") {
|
||||
iso_code = "zh-Hans";
|
||||
} else {
|
||||
/* Windows expects a '-' between language and country code, but we use a '_'. */
|
||||
strecpy(iso, iso_code, lastof(iso));
|
||||
for (char *c = iso; *c != '\0'; c++) {
|
||||
if (*c == '_') *c = '-';
|
||||
for (char &c : iso_code) {
|
||||
if (c == '_') c = '-';
|
||||
}
|
||||
}
|
||||
|
||||
MultiByteToWideChar(CP_UTF8, 0, iso, -1, _cur_iso_locale, lengthof(_cur_iso_locale));
|
||||
MultiByteToWideChar(CP_UTF8, 0, iso_code.c_str(), -1, _cur_iso_locale, lengthof(_cur_iso_locale));
|
||||
}
|
||||
|
||||
int OTTDStringCompare(const char *s1, const char *s2)
|
||||
int OTTDStringCompare(std::string_view s1, std::string_view s2)
|
||||
{
|
||||
typedef int (WINAPI *PFNCOMPARESTRINGEX)(LPCWSTR, DWORD, LPCWCH, int, LPCWCH, int, LPVOID, LPVOID, LPARAM);
|
||||
static PFNCOMPARESTRINGEX _CompareStringEx = nullptr;
|
||||
@@ -658,24 +574,24 @@ int OTTDStringCompare(const char *s1, const char *s2)
|
||||
#endif
|
||||
|
||||
if (first_time) {
|
||||
static DllLoader _kernel32(L"Kernel32.dll");
|
||||
_CompareStringEx = _kernel32.GetProcAddress("CompareStringEx");
|
||||
static LibraryLoader _kernel32("Kernel32.dll");
|
||||
_CompareStringEx = _kernel32.GetFunction("CompareStringEx");
|
||||
first_time = false;
|
||||
}
|
||||
|
||||
if (_CompareStringEx != nullptr) {
|
||||
/* CompareStringEx takes UTF-16 strings, even in ANSI-builds. */
|
||||
int len_s1 = MultiByteToWideChar(CP_UTF8, 0, s1, -1, nullptr, 0);
|
||||
int len_s2 = MultiByteToWideChar(CP_UTF8, 0, s2, -1, nullptr, 0);
|
||||
int len_s1 = MultiByteToWideChar(CP_UTF8, 0, s1.data(), (int)s1.size(), nullptr, 0);
|
||||
int len_s2 = MultiByteToWideChar(CP_UTF8, 0, s2.data(), (int)s2.size(), nullptr, 0);
|
||||
|
||||
if (len_s1 != 0 && len_s2 != 0) {
|
||||
LPWSTR str_s1 = AllocaM(WCHAR, len_s1);
|
||||
LPWSTR str_s2 = AllocaM(WCHAR, len_s2);
|
||||
std::wstring str_s1(len_s1, L'\0'); // len includes terminating null
|
||||
std::wstring str_s2(len_s2, L'\0');
|
||||
|
||||
MultiByteToWideChar(CP_UTF8, 0, s1, -1, str_s1, len_s1);
|
||||
MultiByteToWideChar(CP_UTF8, 0, s2, -1, str_s2, len_s2);
|
||||
MultiByteToWideChar(CP_UTF8, 0, s1.data(), (int)s1.size(), str_s1.data(), len_s1);
|
||||
MultiByteToWideChar(CP_UTF8, 0, s2.data(), (int)s2.size(), str_s2.data(), len_s2);
|
||||
|
||||
int result = _CompareStringEx(_cur_iso_locale, LINGUISTIC_IGNORECASE | SORT_DIGITSASNUMBERS, str_s1, -1, str_s2, -1, nullptr, nullptr, 0);
|
||||
int result = _CompareStringEx(_cur_iso_locale, LINGUISTIC_IGNORECASE | SORT_DIGITSASNUMBERS, str_s1.c_str(), -1, str_s2.c_str(), -1, nullptr, nullptr, 0);
|
||||
if (result != 0) return result;
|
||||
}
|
||||
}
|
||||
@@ -687,6 +603,44 @@ int OTTDStringCompare(const char *s1, const char *s2)
|
||||
return CompareString(MAKELCID(_current_language->winlangid, SORT_DEFAULT), NORM_IGNORECASE, s1_buf, -1, s2_buf, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search if a string is contained in another string using the current locale.
|
||||
*
|
||||
* @param str String to search in.
|
||||
* @param value String to search for.
|
||||
* @param case_insensitive Search case-insensitive.
|
||||
* @return 1 if value was found, 0 if it was not found, or -1 if not supported by the OS.
|
||||
*/
|
||||
int Win32StringContains(const std::string_view str, const std::string_view value, bool case_insensitive)
|
||||
{
|
||||
typedef int (WINAPI *PFNFINDNLSSTRINGEX)(LPCWSTR, DWORD, LPCWSTR, int, LPCWSTR, int, LPINT, LPNLSVERSIONINFO, LPVOID, LPARAM);
|
||||
static PFNFINDNLSSTRINGEX _FindNLSStringEx = nullptr;
|
||||
static bool first_time = true;
|
||||
|
||||
if (first_time) {
|
||||
static LibraryLoader _kernel32("Kernel32.dll");
|
||||
_FindNLSStringEx = _kernel32.GetFunction("FindNLSStringEx");
|
||||
first_time = false;
|
||||
}
|
||||
|
||||
if (_FindNLSStringEx != nullptr) {
|
||||
int len_str = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
|
||||
int len_value = MultiByteToWideChar(CP_UTF8, 0, value.data(), (int)value.size(), nullptr, 0);
|
||||
|
||||
if (len_str != 0 && len_value != 0) {
|
||||
std::wstring str_str(len_str, L'\0'); // len includes terminating null
|
||||
std::wstring str_value(len_value, L'\0');
|
||||
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), str_str.data(), len_str);
|
||||
MultiByteToWideChar(CP_UTF8, 0, value.data(), (int)value.size(), str_value.data(), len_value);
|
||||
|
||||
return _FindNLSStringEx(_cur_iso_locale, FIND_FROMSTART | (case_insensitive ? LINGUISTIC_IGNORECASE : 0), str_str.data(), -1, str_value.data(), -1, nullptr, nullptr, nullptr, 0) >= 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // Failure indication.
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* Based on code from MSDN: https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx */
|
||||
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
||||
|
||||
+3
-45
@@ -10,55 +10,13 @@
|
||||
#ifndef WIN32_H
|
||||
#define WIN32_H
|
||||
|
||||
#include <windows.h>
|
||||
bool MyShowCursor(bool show, bool toggle = false);
|
||||
|
||||
class DllLoader {
|
||||
public:
|
||||
explicit DllLoader(LPCTSTR filename)
|
||||
{
|
||||
this->hmodule = ::LoadLibrary(filename);
|
||||
if (this->hmodule == nullptr) this->success = false;
|
||||
}
|
||||
|
||||
|
||||
~DllLoader()
|
||||
{
|
||||
::FreeLibrary(this->hmodule);
|
||||
}
|
||||
|
||||
bool Success() { return this->success; }
|
||||
|
||||
class ProcAddress {
|
||||
public:
|
||||
explicit ProcAddress(void *p) : p(p) {}
|
||||
|
||||
template <typename T, typename = std::enable_if_t<std::is_function_v<T>>>
|
||||
operator T *() const
|
||||
{
|
||||
return reinterpret_cast<T *>(this->p);
|
||||
}
|
||||
|
||||
private:
|
||||
void *p;
|
||||
};
|
||||
|
||||
ProcAddress GetProcAddress(const char *proc_name)
|
||||
{
|
||||
void *p = reinterpret_cast<void *>(::GetProcAddress(this->hmodule, proc_name));
|
||||
if (p == nullptr) this->success = false;
|
||||
return ProcAddress(p);
|
||||
}
|
||||
|
||||
private:
|
||||
HMODULE hmodule = nullptr;
|
||||
bool success = true;
|
||||
};
|
||||
|
||||
char *convert_from_fs(const wchar_t *name, char *utf8_buf, size_t buflen);
|
||||
wchar_t *convert_to_fs(const char *name, wchar_t *utf16_buf, size_t buflen);
|
||||
wchar_t *convert_to_fs(const std::string_view name, wchar_t *utf16_buf, size_t buflen);
|
||||
|
||||
void Win32SetCurrentLocaleName(const char *iso_code);
|
||||
int OTTDStringCompare(const char *s1, const char *s2);
|
||||
int OTTDStringCompare(std::string_view s1, std::string_view s2);
|
||||
int Win32StringContains(const std::string_view str, const std::string_view value, bool case_insensitive);
|
||||
|
||||
#endif /* WIN32_H */
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file win32_main.cpp Implementation main for Windows. */
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include "../../openttd.h"
|
||||
#include "../../core/random_func.hpp"
|
||||
#include "../../string_func.h"
|
||||
#include "../../crashlog.h"
|
||||
#include "../../debug.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
static int ParseCommandLine(char *line, char **argv, int max_argc)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
do {
|
||||
/* skip whitespace */
|
||||
while (*line == ' ' || *line == '\t') line++;
|
||||
|
||||
/* end? */
|
||||
if (*line == '\0') break;
|
||||
|
||||
/* special handling when quoted */
|
||||
if (*line == '"') {
|
||||
argv[n++] = ++line;
|
||||
while (*line != '"') {
|
||||
if (*line == '\0') return n;
|
||||
line++;
|
||||
}
|
||||
} else {
|
||||
argv[n++] = line;
|
||||
while (*line != ' ' && *line != '\t') {
|
||||
if (*line == '\0') return n;
|
||||
line++;
|
||||
}
|
||||
}
|
||||
*line++ = '\0';
|
||||
} while (n != max_argc);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void CreateConsole();
|
||||
|
||||
int APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
{
|
||||
/* Set system timer resolution to 1ms. */
|
||||
timeBeginPeriod(1);
|
||||
|
||||
CrashLog::InitialiseCrashLog();
|
||||
|
||||
/* Convert the command line to UTF-8. */
|
||||
std::string cmdline = FS2OTTD(GetCommandLine());
|
||||
|
||||
/* Set the console codepage to UTF-8. */
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
|
||||
#if defined(_DEBUG)
|
||||
CreateConsole();
|
||||
#endif
|
||||
|
||||
_set_error_mode(_OUT_TO_MSGBOX); // force assertion output to messagebox
|
||||
|
||||
/* setup random seed to something quite random */
|
||||
SetRandomSeed(GetTickCount());
|
||||
|
||||
char *argv[64]; // max 64 command line arguments
|
||||
int argc = ParseCommandLine(cmdline.data(), argv, lengthof(argv));
|
||||
|
||||
/* Make sure our arguments contain only valid UTF-8 characters. */
|
||||
for (int i = 0; i < argc; i++) StrMakeValidInPlace(argv[i]);
|
||||
|
||||
int ret = openttd_main(argc, argv);
|
||||
|
||||
/* Restore system timer resolution. */
|
||||
timeEndPeriod(1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user