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