Update to 14.0-beta1

This commit is contained in:
dP
2024-02-04 02:18:17 +05:30
parent 79037e2c65
commit 33ef333b57
1325 changed files with 138461 additions and 70983 deletions
-1
View File
@@ -1,4 +1,3 @@
add_subdirectory(macosx)
add_subdirectory(os2)
add_subdirectory(unix)
add_subdirectory(windows)
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
+3 -2
View File
@@ -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 */
+3 -1
View File
@@ -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
View File
@@ -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 ];
}
+49
View File
@@ -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;
}
+61 -33
View File
@@ -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>();
}
+5 -4
View File
@@ -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);
+36
View File
@@ -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();
}
+8 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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());
}
+64
View File
@@ -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__ */
+36
View File
@@ -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
View File
@@ -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__) */
+33
View File
@@ -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);
}
+6
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+6 -3
View File
@@ -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 */
+59
View File
@@ -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;
}
+5 -6
View File
@@ -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
+31 -35
View File
@@ -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);
+14 -14
View File
@@ -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) {
+35
View File
@@ -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
View File
@@ -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&#1860504
* 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
View File
@@ -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 */
+88
View File
@@ -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;
}