From 940469b9e4399e3ef27099533c48ef891b097762 Mon Sep 17 00:00:00 2001 From: Jerome Lacoste Date: Wed, 18 Apr 2018 19:23:25 +0200 Subject: [PATCH 1/2] u3d/internals: add code to find the precise build number for the unity installation --- lib/u3d/installation.rb | 95 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/lib/u3d/installation.rb b/lib/u3d/installation.rb index 28358111..19d34db7 100644 --- a/lib/u3d/installation.rb +++ b/lib/u3d/installation.rb @@ -109,6 +109,10 @@ def version plist['CFBundleVersion'] end + def build_number + plist['UnityBuildNumber'] + end + def default_log_file "#{ENV['HOME']}/Library/Logs/Unity/Editor.log" end @@ -163,6 +167,39 @@ def plist end end + class LinuxInstallationHelper + STRINGS_FULL_VERSION_MATCHER = /^[0-9\.abfp]+_[0-9a-f]{12}/ + + def find_build_number(root) + known_rev_locations.each do |p| + rev = find_build_number_in("#{root}#{p}") + return rev if rev + end + end + + private + + def strings(path) + command = "strings #{path.shellescape}" + `#{command}`.split("\n") + end + + # sorted by order of speed to fetch the strings data + def known_rev_locations + ['/Editor/BugReporter/unity.bugreporter', + '/Editor/Data/PlaybackEngines/WebGLSupport/BuildTools/lib/UnityNativeJs/UnityNative.js.mem', + '/Editor/Data/PlaybackEngines/LinuxStandaloneSupport/Variations/linux32_headless_nondevelopment_mono/LinuxPlayer', + '/Editor/Unity'] + end + + def find_build_number_in(path = nil) + return nil unless File.exist? path + str = strings(path) + lines = str.select { |l| l =~ STRINGS_FULL_VERSION_MATCHER } + lines.empty? ? nil : lines[0].split('_')[1] + end + end + class LinuxInstallation < Installation def version # I don't find an easy way to extract the version on Linux @@ -176,6 +213,10 @@ def version version end + def build_number + @build_number ||= LinuxInstallationHelper.new.find_build_number(root_path) + end + def default_log_file "#{ENV['HOME']}/.config/unity3d/Editor.log" end @@ -220,6 +261,56 @@ def clean_install? end end + class WindowsInstallationHelper + def build_number(exe_path) + s = string_file_info("Unity Version", exe_path) + if s + a = s.split("_") + return a[1] if a.count > 1 + end + nil + end + + private + + def string_file_info(info, path) + require "Win32API" + get_file_version_info_size = Win32API.new('version.dll', 'GetFileVersionInfoSize', 'PP', 'L') + get_file_version_info = Win32API.new('version.dll', 'GetFileVersionInfo', 'PIIP', 'I') + ver_query_value = Win32API.new('version.dll', 'VerQueryValue', 'PPPP', 'I') + rtl_move_memory = Win32API.new('kernel32.dll', 'RtlMoveMemory', 'PLL', 'I') + + file = path.tr("/", "\\") + + buf = [0].pack('L') + version_size = get_file_version_info_size.call(file + "\0", buf) + raise Exception if version_size.zero? # TODO: use GetLastError + + version_info = 0.chr * version_size + version_ok = get_file_version_info.call(file, 0, version_size, version_info) + raise Exception if version_ok.zero? # TODO: use GetLastError + + # hardcoding lang codepage + struct_path = "\\StringFileInfo\\040904b0\\#{info}" + + addr = [0].pack('L') + size = [0].pack('L') + query_ok = ver_query_value.call(version_info, struct_path + "\0", addr, size) + raise Exception if query_ok.zero? + + raddr = addr.unpack('L')[0] + rsize = size.unpack('L')[0] + + info = Array.new(rsize, 0).pack('L*') + rtl_move_memory.call(info, raddr, info.length) + info.strip + rescue StandardError => e + UI.verbose("Failure to find '#{info}' under '#{path}': #{e}") + UI.verbose(e.backtrace) + nil + end + end + class WindowsInstallation < Installation def version path = "#{root_path}/Editor/Data/" @@ -228,6 +319,10 @@ def version PlaybackEngineUtils.unity_version(package) end + def build_number + @build_number ||= WindowsInstallationHelper.new.build_number(exe_path) + end + def default_log_file if @logfile.nil? begin From 977feb54d1d823f5b584c027c35aee6e3b5642d8 Mon Sep 17 00:00:00 2001 From: Jerome Lacoste Date: Wed, 18 Apr 2018 19:28:56 +0200 Subject: [PATCH 2/2] u3d/list: display build number in list --- lib/u3d/commands.rb | 3 ++- spec/support/installations.rb | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/u3d/commands.rb b/lib/u3d/commands.rb index 696b331a..892df467 100644 --- a/lib/u3d/commands.rb +++ b/lib/u3d/commands.rb @@ -56,7 +56,8 @@ def list_installed(options: {}) sorted_keys = vcomparators.sort.map { |v| v.version.to_s } sorted_keys.each do |k| u = map[k] - UI.message "Version #{u.version.ljust(30)}(#{u.root_path})" + version = "#{u.version.ljust(13)} [#{u.build_number}]".ljust(30) + UI.message "Version #{version}(#{u.root_path})" packages = u.packages next unless options[:packages] && packages && !packages.empty? UI.message 'Packages:' diff --git a/spec/support/installations.rb b/spec/support/installations.rb index a3270151..c9f5faa4 100644 --- a/spec/support/installations.rb +++ b/spec/support/installations.rb @@ -25,6 +25,7 @@ def macinstall_5_6_default unity = double("MacInstallation") # allow(unity).to receive(:path) { '/Applications/Unity/Unity.app' } allow(unity).to receive(:version) { '5.6.0f1' } + allow(unity).to receive(:build_number) { 'bf5cca3e2788' } allow(unity).to receive(:clean_install?) { false } allow(unity).to receive(:root_path) { '/Applications/Unity' } return unity @@ -34,6 +35,7 @@ def macinstall_5_6_custom_with_space unity = double("MacInstallation") # allow(unity).to receive(:path) { '/Applications/Unity 5.6.0f1/Unity.app' } allow(unity).to receive(:version) { '5.6.0f1' } + allow(unity).to receive(:build_number) { 'bf5cca3e2788' } allow(unity).to receive(:clean_install?) { false } allow(unity).to receive(:root_path) { '/Applications/Unity 5.6.0f1' } return unity @@ -43,6 +45,7 @@ def linux_5_6_standard unity = double("LinuxInstallation") # allow(unity).to receive(:path) { '/opt/unity-editor-5.6.0f1' } allow(unity).to receive(:version) { '5.6.0f1' } + allow(unity).to receive(:build_number) { 'bf5cca3e2788' } allow(unity).to receive(:clean_install?) { true } allow(unity).to receive(:root_path) { '/opt/unity-editor-5.6.0f1' } return unity @@ -52,6 +55,7 @@ def linux_5_6_debian unity = double("LinuxInstallation") # allow(unity).to receive(:path) { '/opt/Unity' } allow(unity).to receive(:version) { '5.6.0f2' } + allow(unity).to receive(:build_number) { 'a7535b2c1eb6' } allow(unity).to receive(:clean_install?) { false } allow(unity).to receive(:root_path) { '/opt/Unity' } return unity @@ -61,6 +65,7 @@ def linux_2017_1_weird unity = double("LinuxInstallation") # allow(unity).to receive(:path) { '/opt/unity-editor-2017.1.0xf3Linux' } allow(unity).to receive(:version) { '2017.1.0f3' } + allow(unity).to receive(:build_number) { '061bcf22327f' } allow(unity).to receive(:clean_install?) { false } allow(unity).to receive(:root_path) { '/opt/unity-editor-2017.1.0xf3Linux' } return unity @@ -70,6 +75,7 @@ def windows_5_6_32bits_default unity = double("WindowsInstallation") # allow(unity).to receive(:path) { 'C:/Program Files (x86)/Unity' } allow(unity).to receive(:version) { '5.6.0f1' } + allow(unity).to receive(:build_number) { 'bf5cca3e2788' } allow(unity).to receive(:root_path) { 'C:/Program Files (x86)/Unity' } return unity end @@ -78,6 +84,7 @@ def windows_2017_1_64bits_renamed unity = double("WindowsInstallation") # allow(unity).to receive(:path) { 'C:/Program Files/Unity_2017.1.0f3' } allow(unity).to receive(:version) { '2017.1.0f3' } + allow(unity).to receive(:build_number) { '472613c02cf7' } allow(unity).to receive(:root_path) { 'C:/Program Files/Unity_2017.1.0f3' } return unity end @@ -85,6 +92,7 @@ def windows_2017_1_64bits_renamed def fake_linux(version) unity = double("LinuxInstallation") allow(unity).to receive(:version) { version } + allow(unity).to receive(:build_number) { 'build_number' } allow(unity).to receive(:root_path) { 'foo' } allow(unity).to receive(:packages) { false } return unity @@ -93,6 +101,7 @@ def fake_linux(version) def fake_installation(version, packages: []) unity = double("Installation") allow(unity).to receive(:version) { version } + allow(unity).to receive(:build_number) { 'build_number' } allow(unity).to receive(:root_path) { 'foo' } allow(unity).to receive(:packages) { packages } allow(unity).to receive(:package_installed?) { |arg| packages.include?(arg) }