new home directory
[feisty_meow.git] / nucleus / library / versions / version_checker.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : check_version                                                     *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 1996-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "version_checker.h"
16
17 #include <application/windoze_helper.h>
18 #include <basis/astring.h>
19 #include <basis/environment.h>
20 #include <basis/functions.h>
21 #include <basis/guards.h>
22 #include <basis/utf_conversion.h>
23 #include <configuration/application_configuration.h>
24 #include <filesystem/filename.h>
25 #include <loggers/critical_events.h>
26
27 #include <stdio.h>
28
29 using namespace basis;
30 using namespace configuration;
31 using namespace loggers;
32 using namespace structures;
33
34 #ifndef BOOT_STRAPPING
35   // pull in the version specified for this build.
36   #include <__build_version.h>
37 //why was this include "on hold"?
38 #else
39   // plug in a fake version for our bootstrapping process.
40   #define __build_FILE_VERSION "108.420.1024.10008"
41 #endif
42
43 #ifdef _MSC_VER
44   #include <direct.h>
45   #include <winver.h>
46 #endif
47
48 #if defined(_MSC_VER)
49 //#ifdef __WIN32__
50   // ensures that we handle the data properly regardless of unicode settings.
51   #ifdef UNICODE
52     #define render_ptr(ptr) from_unicode_temp( (UTF16 *) ptr)
53   #else
54     #define render_ptr(ptr) ( (char *) ptr)
55   #endif
56 #endif
57
58 //////////////
59
60 namespace versions {
61
62 version_checker::version_checker(const astring &library_file_name,
63     const version &expected_version, const astring &version_complaint)
64 : _library_file_name(new astring(library_file_name)),
65   _expected_version(new version(expected_version)),
66   _version_complaint(new astring(version_complaint))
67 {}
68
69 version_checker::~version_checker()
70 {
71   WHACK(_library_file_name);
72   WHACK(_expected_version);
73   WHACK(_version_complaint);
74 }
75
76 astring version_checker::text_form() const
77 {
78   return astring(class_name()) + ": library_file_name=" + *_library_file_name
79       + ", expected_version=" + _expected_version->text_form()
80       + ", complaint_message=" + *_version_complaint;
81 }
82
83 bool version_checker::good_version() const
84 {
85   astring version_disabler = environment::TMP();
86   version_disabler += "/no_version_check.txt";
87   FILE *always_okay = fopen(version_disabler.s(), "r");
88   if (always_okay) {
89     fclose(always_okay);
90     return true;
91   }
92
93   version version_found = retrieve_version(*_library_file_name);
94   if (version_found.compatible(*_expected_version)) return true;  // success.
95   complain_wrong_version(*_library_file_name, *_expected_version,
96       version_found);
97   return false;
98 }
99
100 bool version_checker::loaded(const astring &library_file_name)
101 {
102 //#ifdef __WIN32__
103 #if defined(_MSC_VER)
104   return bool(get_handle(library_file_name) != 0); 
105 #else
106 //temp code. 
107   return true || library_file_name.t();
108 #endif
109 }
110
111 void *version_checker::get_handle(const astring &library_file_name)
112 {
113 //#ifdef __WIN32__
114 #if defined(_MSC_VER)
115   return GetModuleHandle(to_unicode_temp(library_file_name));
116 #else
117   if (library_file_name.t()) return NULL_POINTER; else return NULL_POINTER;
118 #endif
119 }
120
121 astring version_checker::module_name(const void *module_handle)
122 {
123 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
124   if (module_handle) {}
125   return application_configuration::application_name();
126 #elif defined(_MSC_VER)
127 //#elif defined(__WIN32__)
128   flexichar low_buff[MAX_ABS_PATH + 1];
129   GetModuleFileName((HMODULE)module_handle, low_buff, MAX_ABS_PATH - 1);
130   astring buff = from_unicode_temp(low_buff);
131   buff.to_lower();
132   return buff;
133 #else
134   #pragma message("module_name unknown for this operating system.")
135   return application_configuration::application_name();
136 #endif
137 }
138
139 astring version_checker::get_name(const void *to_find)
140 { return module_name(to_find); }
141
142 bool version_checker::retrieve_version_info(const astring &filename,
143     byte_array &to_fill)
144 {
145   to_fill.reset();  // clear the buffer.
146
147   // determine the required size of the version info buffer.
148   int required_size;
149 #if defined(_MSC_VER)
150 //#ifdef __WIN32__
151   un_long module_handle;  // filled with the dll or exe handle.
152   required_size = GetFileVersionInfoSize(to_unicode_temp(filename), &module_handle);
153 #else
154   required_size = 0 && filename.t();
155 #endif
156   if (!required_size) return false;
157   to_fill.reset(required_size);  // resize the buffer.
158   
159   // read the version info into our buffer.
160   bool success = false;
161 #if defined(_MSC_VER)
162 //#ifdef __WIN32__
163   success = GetFileVersionInfo(to_unicode_temp(filename), module_handle,
164       required_size, to_fill.access());
165 #else
166   success = false;
167 #endif
168   return success;
169 }
170
171 bool version_checker::get_language(byte_array &version_chunk,
172     basis::un_short &high, basis::un_short &low)
173 {
174   high = 0;
175   low = 0;
176 #if defined(_MSC_VER)
177 //#ifdef __WIN32__
178   // determine the language that the version's written in.
179   basis::un_int data_size;
180   void *pointer_to_language_structure;
181   // query the information from the version blob.
182   if (!VerQueryValue(version_chunk.access(),
183       to_unicode_temp("\\VarFileInfo\\Translation"),
184       &pointer_to_language_structure, &data_size))
185     return false;
186   // get the low & high shorts of the structure.
187   high = LOWORD(*(unsigned int *)pointer_to_language_structure);
188   low = HIWORD(*(unsigned int *)pointer_to_language_structure);
189 #else
190   high = 0 && version_chunk.length();
191   low = 0;
192 #endif
193
194   return true;
195 }
196
197 version version_checker::retrieve_version(const astring &filename)
198 {
199 //#ifdef UNIX
200 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
201
202   // totally bogus stand-in; this just returns the version we were built with
203   // rather than the version that's actually tagged on the file.
204
205 //hmmm: fix this!  get the version header back.
206 //  return version(__build_FILE_VERSION);
207   return version();
208
209 #endif
210
211   byte_array version_info_found(0, NULL_POINTER);
212   if (!retrieve_version_info(filename, version_info_found))
213     return version(0, 0, 0, 0);
214
215   basis::un_short high, low;  // holds the language of the version data.
216   if (!get_language(version_info_found, high, low))
217     return version(0, 0, 0, 0);
218
219   // retrieve the file version from version info using the appropriate
220   // language.
221   astring root_key(astring::SPRINTF, "\\StringFileInfo\\%04x%04x", high, low);
222   astring file_version_key(root_key + astring("\\FileVersion"));
223
224   astring version_string;
225 #ifdef _MSC_VER
226   abyte *file_version_pointer;
227   basis::un_int data_size;
228   if (!VerQueryValue(version_info_found.access(),
229       to_unicode_temp(file_version_key),
230       (LPVOID *)&file_version_pointer, &data_size))
231     return version(0, 0, 0, 0);
232   version_string = render_ptr(file_version_pointer);
233   // clean any spaces out of the string; people sometimes format these
234   // very badly.
235   for (int i = 0; i < version_string.length(); i++) {
236     if (version_string[i] == ' ') {
237       version_string.zap(i, i);
238       i--;  // skip back a beat.
239     }
240   }
241 #else
242   return version(0, 0, 0, 0);
243 //tmp.
244 #endif
245   return version::from_text(version_string);
246 }
247
248 bool version_checker::get_record(const astring &filename, 
249     version_record &to_fill)
250 {
251   to_fill = version_record();
252   byte_array version_info_found(0, NULL_POINTER);
253   if (!retrieve_version_info(filename, version_info_found))
254     return false;
255
256   basis::un_short high, low;  // holds the language of the version data.
257   if (!get_language(version_info_found, high, low))
258     return false;
259
260   // set the root key for all accesses of the version chunk.
261   astring root_key(astring::SPRINTF, "\\StringFileInfo\\%04x%04x", high, low);
262
263   // reports whether all lookups succeeded or not.
264   bool total_success = true;
265
266   // the various version pieces are retrieved...
267
268 //#ifdef __WIN32__
269 #ifdef _MSC_VER
270   basis::un_int data_size;
271   void *data_pointer;
272
273   // file version.
274   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
275       + astring("\\FileVersion")), &data_pointer, &data_size))
276     total_success = false;
277   else
278     to_fill.file_version = version::from_text(render_ptr(data_pointer));
279
280   // company name.
281   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
282       + astring("\\CompanyName")), &data_pointer, &data_size))
283     total_success = false;
284   else
285     to_fill.company_name = render_ptr(data_pointer);
286
287   // file description.
288   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
289       + astring("\\FileDescription")), &data_pointer, &data_size))
290     total_success = false;
291   else
292     to_fill.description = render_ptr(data_pointer);
293
294   // internal name.
295   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
296       + astring("\\InternalName")), &data_pointer, &data_size))
297     total_success = false;
298   else
299     to_fill.internal_name = render_ptr(data_pointer);
300
301   // copyright info.
302   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
303       + astring("\\LegalCopyright")), &data_pointer, &data_size))
304     total_success = false;
305   else
306     to_fill.copyright = render_ptr(data_pointer);
307
308   // trademark info.
309   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
310       + astring("\\LegalTrademarks")), &data_pointer, &data_size))
311     total_success = false;
312   else
313     to_fill.trademarks = render_ptr(data_pointer);
314
315   // original file name.
316   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
317       + astring("\\OriginalFilename")), &data_pointer, &data_size))
318     total_success = false;
319   else
320     to_fill.original_name = render_ptr(data_pointer);
321
322   // product name.
323   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
324       + astring("\\ProductName")), &data_pointer, &data_size))
325     total_success = false;
326   else
327     to_fill.product_name = render_ptr(data_pointer);
328
329   // product version.
330   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
331       + astring("\\ProductVersion")), &data_pointer, &data_size))
332     total_success = false;
333   else
334     to_fill.product_version = version::from_text(render_ptr(data_pointer));
335 #else
336   // hmmm: chunks missing in version check.
337 #endif
338
339   return total_success;
340 }
341
342 void version_checker::complain_wrong_version(const astring &library_file_name,
343     const version &expected_version, const version &version_found) const
344 {
345   astring to_show("There has been a Version Mismatch: The module \"");
346   // use embedded module handle to retrieve name of dll or exe.
347   astring module_name = get_name(version::__global_module_handle());
348   if (!module_name) module_name = "Unknown";
349   to_show += module_name;
350   to_show += astring("\" cannot load.  This is because the file \"");
351   to_show += library_file_name;
352   to_show += astring("\" was expected to have a version of [");
353
354   to_show += expected_version.flex_text_form(version::DOTS);
355
356   to_show += astring("] but it instead had a version of [");
357   to_show += version_found.flex_text_form(version::DOTS);
358
359   to_show += astring("].  ");
360   to_show += *_version_complaint;
361 //#ifdef __UNIX__
362 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
363   continuable_error("version checking", "failure", to_show.s());
364 #elif defined(_MSC_VER)
365   MessageBox(0, to_unicode_temp(to_show),
366       to_unicode_temp("version_checking::failure"), MB_OK);
367 #endif
368 }
369
370 void version_checker::complain_cannot_load(const astring &lib_file) const
371 {
372   astring to_show("There has been a failure in Version Checking: The file \"");
373   to_show += lib_file;
374   to_show += astring("\" could not be loaded or found.  ");
375   to_show += *_version_complaint;
376   continuable_error("version checking", "loading dll", to_show.s());
377 }
378
379 } //namespace.
380
381