1 /* Copyright (C) 2017 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "precompiled.h"
19
20 #include "DLLInterface.h"
21
22 #include "General/AtlasEventLoop.h"
23
24 #include "General/Datafile.h"
25
26 #include "ActorEditor/ActorEditor.h"
27 #include "ScenarioEditor/ScenarioEditor.h"
28
29 #include "GameInterface/MessagePasser.h"
30
31 #include "wx/config.h"
32 #include "wx/debugrpt.h"
33 #include "wx/file.h"
34
35 // wx and libxml both want to define ATTRIBUTE_PRINTF (with similar
36 // meanings), so undef it to avoid a warning
37 #undef ATTRIBUTE_PRINTF
38 #include <libxml/parser.h>
39
40 #ifndef LIBXML_THREAD_ENABLED
41 #error libxml2 must have threading support enabled
42 #endif
43
44 #ifdef __WXGTK__
45 #include <X11/Xlib.h>
46 #endif
47
48 // If enabled, we'll try to use wxDebugReport to report fatal exceptions.
49 // But this is broken on Linux and can cause the UI to deadlock (see comment
50 // in OnFatalException), and it's never especially useful, so don't use it.
51 #define USE_WX_FATAL_EXCEPTION_REPORT 0
52
53 // Shared memory allocation functions
ShareableMalloc(size_t n)54 ATLASDLLIMPEXP void* ShareableMalloc(size_t n)
55 {
56 // TODO: make sure this is thread-safe everywhere. (It is in MSVC with the
57 // multithreaded CRT.)
58 return malloc(n);
59 }
ShareableFree(void * p)60 ATLASDLLIMPEXP void ShareableFree(void* p)
61 {
62 free(p);
63 }
64 // Define the function pointers that we'll use when calling those functions.
65 // (The game loads the addresses of the above functions, then does the same.)
66 namespace AtlasMessage
67 {
68 void* (*ShareableMallocFptr) (size_t) = &ShareableMalloc;
69 void (*ShareableFreeFptr) (void*) = &ShareableFree;
70 }
71
72
73 // Global variables, to remember state between DllMain and StartWindow and OnInit
74 wxString g_InitialWindowType;
75 bool g_IsLoaded = false;
76
77 #ifdef __WXMSW__
78 HINSTANCE g_Module;
79
DllMain(HINSTANCE hModule,DWORD fdwReason,LPVOID WXUNUSED (lpReserved))80 BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD fdwReason, LPVOID WXUNUSED(lpReserved))
81 {
82 switch (fdwReason)
83 {
84 case DLL_PROCESS_ATTACH:
85 g_Module = hModule;
86 return TRUE;
87
88 case DLL_PROCESS_DETACH:
89 if (g_IsLoaded)
90 {
91 wxEntryCleanup();
92 g_IsLoaded = false;
93 }
94 break;
95 }
96
97 return TRUE;
98 }
99 #endif // __WXMSW__
100
101 using namespace AtlasMessage;
102
103 MessagePasser* AtlasMessage::g_MessagePasser = NULL;
104
Atlas_SetMessagePasser(MessagePasser * passer)105 ATLASDLLIMPEXP void Atlas_SetMessagePasser(MessagePasser* passer)
106 {
107 g_MessagePasser = passer;
108 }
109
110 bool g_HasSetDataDirectory = false;
Atlas_SetDataDirectory(const wchar_t * path)111 ATLASDLLIMPEXP void Atlas_SetDataDirectory(const wchar_t* path)
112 {
113 Datafile::SetDataDirectory(path);
114 g_HasSetDataDirectory = true;
115 }
116
117 wxString g_ConfigDir;
Atlas_SetConfigDirectory(const wchar_t * path)118 ATLASDLLIMPEXP void Atlas_SetConfigDirectory(const wchar_t* path)
119 {
120 wxFileName config (path);
121 g_ConfigDir = config.GetPath(wxPATH_GET_SEPARATOR);
122 }
123
Atlas_StartWindow(const wchar_t * type)124 ATLASDLLIMPEXP void Atlas_StartWindow(const wchar_t* type)
125 {
126 // Initialise libxml2
127 // (If we're executed from the game instead, it has the responsibility to initialise libxml2)
128 LIBXML_TEST_VERSION
129
130 g_InitialWindowType = type;
131 #ifdef __WXMSW__
132 wxEntry(g_Module);
133 #else
134 #ifdef __WXGTK__
135 // Because we do GL calls from a secondary thread, Xlib needs to
136 // be told to support multiple threads safely
137 int status = XInitThreads();
138 if (status == 0)
139 {
140 fprintf(stderr, "Error enabling thread-safety via XInitThreads\n");
141 }
142 #endif
143 int argc = 1;
144 char atlas[] = "atlas";
145 char *argv[] = {atlas, NULL};
146 #ifndef __WXOSX__
147 wxEntry(argc, argv);
148 #else
149 // Fix for OS X init (see http://trac.wildfiregames.com/ticket/2427 )
150 // If we launched from in-game, SDL started NSApplication which will
151 // break some things in wxWidgets
152 wxEntryStart(argc, argv);
153 wxTheApp->OnInit();
154 wxTheApp->OnRun();
155 wxTheApp->OnExit();
156 wxEntryCleanup();
157 #endif
158
159 #endif
160 }
161
Atlas_DisplayError(const wchar_t * text,size_t WXUNUSED (flags))162 ATLASDLLIMPEXP void Atlas_DisplayError(const wchar_t* text, size_t WXUNUSED(flags))
163 {
164 // This is called from the game thread.
165 // wxLog appears to be thread-safe, so that's okay.
166 wxLogError(L"%s", text);
167
168 // TODO: wait for user response (if possible) before returning,
169 // and return their status (break/continue/debug/etc), but only in
170 // cases where we're certain it won't deadlock (i.e. the UI event loop
171 // is still running and won't block before showing the dialog to the user)
172 // and where it matters (i.e. errors, not warnings (unless they're going to
173 // turn into errors after continuing))
174
175 // TODO: 'text' (or at least some copy of it) appears to get leaked when
176 // this function is called
177 }
178
179 class AtlasDLLApp : public wxApp
180 {
181 public:
182
183 #ifdef __WXOSX__
OSXIsGUIApplication()184 virtual bool OSXIsGUIApplication()
185 {
186 return false;
187 }
188 #endif
189
OnInit()190 virtual bool OnInit()
191 {
192 // _CrtSetBreakAlloc(5632);
193
194 #if wxUSE_ON_FATAL_EXCEPTION && USE_WX_FATAL_EXCEPTION_REPORT
195 if (! wxIsDebuggerRunning())
196 wxHandleFatalExceptions();
197 #endif
198
199 #ifndef __WXMSW__ // On Windows we use the registry so don't attempt to set the path.
200 // When launching a standalone executable g_ConfigDir may not be
201 // set. In this case we default to the XDG base dir spec and use
202 // 0ad/config/ as the config directory.
203 wxString configPath;
204 if (!g_ConfigDir.IsEmpty())
205 {
206 configPath = g_ConfigDir;
207 }
208 else
209 {
210 wxString xdgConfigHome;
211 if (wxGetEnv(_T("XDG_CONFIG_HOME"), &xdgConfigHome) && !xdgConfigHome.IsEmpty())
212 configPath = xdgConfigHome + _T("/0ad/config/");
213 else
214 configPath = wxFileName::GetHomeDir() + _T("/.config/0ad/config/");
215 }
216 #endif
217
218 // Initialise the global config file
219 wxConfigBase::Set(new wxConfig(_T("Atlas Editor"), _T("Wildfire Games")
220 #ifndef __WXMSW__ // On Windows we use wxRegConfig and setting this changes the Registry key
221 , configPath + _T("atlas.ini")
222 #endif
223 ));
224
225 if (! g_HasSetDataDirectory)
226 {
227 // Assume that the .exe is located in .../binaries/system. (We can't
228 // just use the cwd, since that isn't correct when being executed by
229 // dragging-and-dropping onto the program in Explorer.)
230 Datafile::SetSystemDirectory(argv[0]);
231 }
232
233 // Display the appropriate window
234 wxFrame* frame;
235 if (g_InitialWindowType == _T("ActorEditor"))
236 {
237 frame = new ActorEditor(NULL);
238 }
239 else if (g_InitialWindowType == _T("ScenarioEditor"))
240 {
241 frame = new ScenarioEditor(NULL);
242 }
243 else
244 {
245 wxFAIL_MSG(_("Internal error: invalid window type"));
246 return false;
247 }
248
249 frame->Show();
250 SetTopWindow(frame);
251
252 AtlasWindow* win = wxDynamicCast(frame, AtlasWindow);
253 if (win)
254 {
255 // One argument => argv[1] is probably a filename to open
256 if (argc > 1)
257 {
258 wxString filename = argv[1];
259
260 if (filename[0] != _T('-')) // ignore -options
261 {
262 if (wxFile::Exists(filename))
263 {
264 win->OpenFile(filename);
265 }
266 else
267 wxLogError(_("Cannot find file '%s'"), filename.c_str());
268 }
269 }
270 }
271
272 return true;
273 }
274
275 #if wxUSE_DEBUGREPORT && USE_WX_FATAL_EXCEPTION_REPORT
OnFatalException()276 virtual void OnFatalException()
277 {
278 // NOTE: At least on Linux, this might be called from a thread other
279 // than the UI thread, so it's not safe to use any wx objects here
280
281 wxDebugReport report;
282 wxDebugReportPreviewStd preview;
283
284 report.AddAll();
285
286 if (preview.Show(report))
287 {
288 wxString dir = report.GetDirectory(); // save the string, since it gets cleared by Process
289 report.Process();
290 OpenDirectory(dir);
291 }
292 }
293 #endif // wxUSE_DEBUGREPORT
294
295 /* Disabled (and should be removed if it turns out to be unnecessary)
296 - see MessagePasserImpl.cpp for information
297 virtual int MainLoop()
298 {
299 // Override the default MainLoop so that we can provide our own event loop
300
301 wxEventLoop* old = m_mainLoop;
302 m_mainLoop = new AtlasEventLoop;
303
304 int ret = m_mainLoop->Run();
305
306 delete m_mainLoop;
307 m_mainLoop = old;
308 return ret;
309 }
310 */
311
312 private:
313
OpenDirectory(const wxString & dir)314 bool OpenDirectory(const wxString& dir)
315 {
316 // Open a directory on the filesystem - used so people can find the
317 // debug report files generated in OnFatalException easily
318
319 #ifdef __WXMSW__
320 // Code largely copied from wxLaunchDefaultBrowser:
321
322 typedef HINSTANCE (WINAPI *LPShellExecute)(HWND hwnd, const wxChar* lpOperation,
323 const wxChar* lpFile,
324 const wxChar* lpParameters,
325 const wxChar* lpDirectory,
326 INT nShowCmd);
327
328 HINSTANCE hShellDll = ::LoadLibrary(_T("shell32.dll"));
329 if (hShellDll == NULL)
330 return false;
331
332 LPShellExecute lpShellExecute =
333 (LPShellExecute) ::GetProcAddress(hShellDll,
334 wxString(_T("ShellExecute")
335 # ifdef _UNICODE
336 _T("W")
337 # else
338 _T("A")
339 # endif
340 ).mb_str(wxConvLocal));
341
342 if (lpShellExecute == NULL)
343 return false;
344
345 /*HINSTANCE nResult =*/ (*lpShellExecute)(NULL, _T("explore"), dir.c_str(), NULL, NULL, SW_SHOWNORMAL);
346 // ignore return value, since we're not going to do anything if this fails
347
348 ::FreeLibrary(hShellDll);
349
350 return true;
351 #else
352 // Figure out what goes for "default browser" on unix/linux/whatever
353 // open an xterm perhaps? :)
354 (void)dir;
355 return false;
356 #endif
357 }
358 };
359
360 IMPLEMENT_APP_NO_MAIN(AtlasDLLApp);
361