Skip to content

Commit

Permalink
Game report: add precise version and name for systems. (#30729)
Browse files Browse the repository at this point in the history
* Add precise versions for Windows systems.

* Add Android system version retrieval.

* Simplify formatting for Windows version.

* Add Linux system version.

* make operating_system_version() return '<unknown>' if the version can't be determined.

- Invert condition on Linux version function;

* Simplify Android version retrieval.

* Extract shell execution function.

- will be reused by Linux, Mac and BSD.

* Add MacOS system version.

* Sort methods alphabetically.

* Add BSD version retrieval.

* Fix structure init. Fix error in macos function.

* Fix typo in output...
  • Loading branch information
neitsa authored and ZhilkinSerg committed May 23, 2019
1 parent 1fb5681 commit 63b649f
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 2 deletions.
242 changes: 240 additions & 2 deletions src/debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <cstdio>
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
Expand All @@ -18,6 +19,8 @@
#include <set>
#include <sstream>
#include <type_traits>
#include <utility>
#include <vector>

#include "cursesdef.h"
#include "filesystem.h"
Expand Down Expand Up @@ -59,6 +62,11 @@
# endif
#endif // TILES

#if defined(__ANDROID__)
// used by android_version() function for __system_property_get().
#include <sys/system_properties.h>
#endif

// Static defines {{{1
// ---------------------------------------------------------------------

Expand Down Expand Up @@ -788,7 +796,7 @@ std::string game_info::operating_system()
return "Windows";
#elif defined(__linux__)
return "Linux";
#elif defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__) && defined(__MACH__) // unix; BSD; MacOs
#elif defined(unix) || defined(__unix__) || defined(__unix) || ( defined(__APPLE__) && defined(__MACH__) ) // unix; BSD; MacOs
#if defined(__APPLE__) && defined(__MACH__)
// The following include is **only** needed for the TARGET_xxx defines below and is only included if both of the above defines are true.
// The whole function only relying on compiler defines, it is probably more meaningful to include it here and not mingle with the
Expand All @@ -804,7 +812,7 @@ std::string game_info::operating_system()
/* OSX */
return "MacOs";
#endif // TARGET_IPHONE_SIMULATOR
#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#elif defined(BSD) // defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
return "BSD";
#else
return "Unix";
Expand All @@ -814,6 +822,231 @@ std::string game_info::operating_system()
#endif
}

/** Get a precise version number for Android systems.
* @note see:
* - https://stackoverflow.com/a/19777977/507028
* - https://github.com/pytorch/cpuinfo/blob/master/test/build.prop/galaxy-s7-us.log
* @returns If successful, a string containing the Android system version, otherwise an empty string.
*/
std::string android_version()
{
std::string output;

#if defined (__ANDROID__)
// buffer used for the __system_property_get() function.
// note: according to android sources, it can't be greater than 92 chars (see 'PROP_VALUE_MAX' define in system_properties.h)
std::vector<char> buffer( 255 );

system_properties = {
// The manufacturer of the product/hardware; e.g. "Samsung", this is different than the carrier.
{ "ro.product.manufacturer", "Manufacturer" },
// The end-user-visible name for the end product; .e.g. "SAMSUNG-SM-G930A" for a Samsung S7.
{ "ro.product.model", "Model" },
// The Android system version; e.g. "6.0.1"
{ "ro.build.version.release", "Release" },
// The internal value used by the underlying source control to represent this build; e.g "G930AUCS4APK1" for a Samsung S7 on 6.0.1.
{ "ro.build.version.incremental", "Incremental" },
};

for( const auto &entry : system_properties ) {
int len = __system_property_get( entry.first, &buffer[0] );
std::string value;
if( len <= 0 ) {
// failed to get the property
value = "<unknown>";
} else {
value = std::string( buffer.begin(), buffer.end() );
}
output.append( string_format( "%s: %s; ", entry.second, value ) );
}
#endif
return output;
}

#if defined (__linux__) || defined(unix) || defined(__unix__) || defined(__unix) || ( defined(__APPLE__) && defined(__MACH__) ) || defined(BSD) // linux; unix; MacOs; BSD
/** Execute a command with the shell by using `popen()`.
* @param command The full command to execute.
* @note The output buffer is limited to 512 characters.
* @returns The result of the command (only stdout) or an empty string if there was a problem.
*/
std::string shell_exec( const std::string &command )
{
std::vector<char> buffer( 512 );
std::string output;
try {
std::unique_ptr<FILE, decltype( &pclose )> pipe( popen( command.c_str(), "r" ), pclose );
if( pipe ) {
while( fgets( buffer.data(), buffer.size(), pipe.get() ) != nullptr ) {
output += buffer.data();
}
}
} catch( ... ) {
output = "";
}
return output;
}
#endif

/** Get a precise version number for BSD systems.
* @note The code shells-out to call `uname -a`.
* @returns If successful, a string containing the Linux system version, otherwise an empty string.
*/
std::string bsd_version()
{
std::string output;
#if defined(BSD)
output = shell_exec( "uname -a" );
if( !output.empty() ) {
// remove trailing '\n', if any.
output.erase( std::remove( output.begin(), output.end(), '\n' ),
output.end() );
}
#endif
return output;
}

/** Get a precise version number for Linux systems.
* @note The code shells-out to call `lsb_release -a`.
* @returns If successful, a string containing the Linux system version, otherwise an empty string.
*/
std::string linux_version()
{
std::string output;
#if defined(__linux__)
output = shell_exec( "lsb_release -a" );
if( !output.empty() ) {
// replace '\n' and '\t' in output.
std::vector<std::pair<std::string, std::string>> to_replace = {
{"\n", "; "},
{"\t", " "},
};
for( const auto &e : to_replace ) {
std::string::size_type pos;
while( ( pos = output.find( e.first ) ) != std::string::npos ) {
output.replace( pos, e.first.length(), e.second );
}
}
}
#endif
return output;
}

/** Get a precise version number for MacOs systems.
* @note The code shells-out to call `sw_vers` with various options.
* @returns If successful, a string containing the MacOS system version, otherwise an empty string.
*/
std::string mac_os_version()
{
std::string output;
#if defined(__APPLE__) && defined(__MACH__) && !defined(BSD)
std::vector<std::pair<std::string, std::string>> commands = {
{ "sw_vers -productName", "Name" },
{ "sw_vers -productVersion", "Version" },
{ "sw_vers -buildVersion", "Build" },
};

for( const auto &entry : commands ) {
std::string command_result = shell_exec( entry.first );
if( command_result.empty() ) {
command_result = "<unknown>";
} else {
// remove trailing '\n', if any.
command_result.erase( std::remove( command_result.begin(), command_result.end(), '\n' ),
command_result.end() );
}
output.append( string_format( "%s: %s; ", entry.second, command_result ) );
}
#endif
return output;
}

/** Get a precise version number for Windows systems.
* @note Since Windows 10 all version-related APIs lie about the underlying system if the application is not Manifested (see VerifyVersionInfoA
* or GetVersionEx documentation for further explanation). In this function we use the registry or the native RtlGetVersion which both
* report correct versions and are compatible down to XP.
* @returns If successful, a string containing the Windows system version number, otherwise an empty string.
*/
std::string windows_version()
{
std::string output;
#if defined (_WIN32)
HKEY handle_key;
bool success = RegOpenKeyExA( HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)",
0,
KEY_QUERY_VALUE, &handle_key ) == ERROR_SUCCESS;
if( success ) {
DWORD value_type;
constexpr DWORD c_buffer_size = 512;
std::vector<BYTE> byte_buffer( c_buffer_size );
DWORD buffer_size = c_buffer_size;
DWORD major_version = 0;
success = RegQueryValueExA( handle_key, "CurrentMajorVersionNumber", nullptr, &value_type,
&byte_buffer[0], &buffer_size ) == ERROR_SUCCESS && value_type == REG_DWORD;
if( success ) {
major_version = *reinterpret_cast<const DWORD *>( &byte_buffer[0] );
output.append( std::to_string( major_version ) );
}
if( success ) {
buffer_size = c_buffer_size;
success = RegQueryValueExA( handle_key, "CurrentMinorVersionNumber", nullptr, &value_type,
&byte_buffer[0], &buffer_size ) == ERROR_SUCCESS && value_type == REG_DWORD;
if( success ) {
const DWORD minor_version = *reinterpret_cast<const DWORD *>( &byte_buffer[0] );
output.append( "." );
output.append( std::to_string( minor_version ) );
}
}
if( success && major_version == 10 ) {
buffer_size = c_buffer_size;
success = RegQueryValueExA( handle_key, "ReleaseId", nullptr, &value_type, &byte_buffer[0],
&buffer_size ) == ERROR_SUCCESS && value_type == REG_SZ;
if( success ) {
output.append( " " );
output.append( std::string( byte_buffer.begin(), byte_buffer.end() ) );
}
}

RegCloseKey( handle_key );
}

if( !success ) {
output = "";
typedef LONG( WINAPI * RtlGetVersion )( PRTL_OSVERSIONINFOW );
const HMODULE handle_ntdll = GetModuleHandleA( "ntdll" );
if( handle_ntdll != nullptr ) {
const auto rtl_get_version_func = reinterpret_cast<RtlGetVersion>( GetProcAddress( handle_ntdll,
"RtlGetVersion" ) );
if( rtl_get_version_func != nullptr ) {
RTL_OSVERSIONINFOW os_version_info = RTL_OSVERSIONINFOW();
os_version_info.dwOSVersionInfoSize = sizeof( RTL_OSVERSIONINFOW );
if( rtl_get_version_func( &os_version_info ) == 0 ) { // NT_STATUS_SUCCESS = 0
output.append( string_format( "%i.%i %i", os_version_info.dwMajorVersion,
os_version_info.dwMinorVersion, os_version_info.dwBuildNumber ) );
}
}
}
}
#endif
return output;
}

std::string game_info::operating_system_version()
{
#if defined(__ANDROID__)
return android_version();
#elif defined(BSD)
return bsd_version();
#elif defined(__linux__)
return linux_version();
#elif defined(__APPLE__) && defined(__MACH__) && !defined(BSD)
return mac_os_version();
#elif defined(_WIN32)
return windows_version();
#else
return "<unknown>";
#endif
}

std::string game_info::bitness()
{
if( sizeof( void * ) == 8 ) {
Expand Down Expand Up @@ -865,9 +1098,14 @@ std::string game_info::mods_loaded()

std::string game_info::game_report()
{
std::string os_version = operating_system_version();
if( os_version.empty() ) {
os_version = "<unknown>";
}
std::stringstream report;
report <<
"- OS: " << operating_system() << " [" << bitness() << "]\n" <<
" - OS Version: " << os_version << "\n" <<
"- Game Version: " << game_version() << "\n" <<
"- Graphics Version: " << graphics_version() << "\n" <<
"- Mods loaded: [\n " << mods_loaded() << "\n]\n";
Expand Down
3 changes: 3 additions & 0 deletions src/debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ namespace game_info
/** Return the name of the current operating system.
*/
std::string operating_system();
/** Return a detailed version of the operating system; e.g. "Ubuntu 18.04" or "(Windows) 10 1809".
*/
std::string operating_system_version();
/** Return the "bitness" of the game (not necessarily of the operating system); either: 64-bit, 32-bit or Unknown.
*/
std::string bitness();
Expand Down

6 comments on commit 63b649f

@smickles
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/debug.cpp: In function 'std::__cxx11::string windows_version()':
src/debug.cpp:1018:65: error: cast between incompatible function types from 'FARPROC' {aka 'long long int (*)()'} to 'RtlGetVersion' {aka 'long int (*)(_OSVERSIONINFOW*)'} [-Werror=cast-function-type]
                                               "RtlGetVersion" ) );
                                                                 ^
cc1plus.exe: all warnings being treated as errors
make: *** [Makefile:768: objwin/tiles/debug.o] Error 1

@ZhilkinSerg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which build environment are you getting this on?

@ZhilkinSerg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see - you can get this error using MSYS2 (and probably CYGWIN and MinGW too).

@jbytheway
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe try casting it to a void* first and then to the desired function pointer type?

@ZhilkinSerg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't work - there are would be long long int and long int incompatibility.

@jbytheway
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed that doesn't work. But you can do it like this:
https://godbolt.org/z/HrZKHZ

Please sign in to comment.