platform_windows.odin 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. // +build windows
  2. package sysinfo
  3. import sys "core:sys/windows"
  4. import "base:intrinsics"
  5. import "core:strings"
  6. import "core:unicode/utf16"
  7. import "core:fmt"
  8. import "base:runtime"
  9. @(private)
  10. version_string_buf: [1024]u8
  11. @(init, private)
  12. init_os_version :: proc () {
  13. /*
  14. NOTE(Jeroen):
  15. `GetVersionEx` will return 6.2 for Windows 10 unless the program is manifested for Windows 10.
  16. `RtlGetVersion` will return the true version.
  17. Rather than include the WinDDK, we ask the kernel directly.
  18. `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion` is for the minor build version (Update Build Release)
  19. */
  20. os_version.platform = .Windows
  21. osvi: sys.OSVERSIONINFOEXW
  22. osvi.dwOSVersionInfoSize = size_of(osvi)
  23. status := sys.RtlGetVersion(&osvi)
  24. if status != 0 {
  25. return
  26. }
  27. product_type: sys.Windows_Product_Type
  28. sys.GetProductInfo(
  29. osvi.dwMajorVersion, osvi.dwMinorVersion,
  30. u32(osvi.wServicePackMajor), u32(osvi.wServicePackMinor),
  31. &product_type,
  32. )
  33. os_version.major = int(osvi.dwMajorVersion)
  34. os_version.minor = int(osvi.dwMinorVersion)
  35. os_version.build[0] = int(osvi.dwBuildNumber)
  36. b := strings.builder_from_bytes(version_string_buf[:])
  37. strings.write_string(&b, "Windows ")
  38. switch osvi.dwMajorVersion {
  39. case 10:
  40. switch osvi.wProductType {
  41. case 1: // VER_NT_WORKSTATION:
  42. if osvi.dwBuildNumber < 22000 {
  43. strings.write_string(&b, "10 ")
  44. } else {
  45. strings.write_string(&b, "11 ")
  46. }
  47. format_windows_product_type(&b, product_type)
  48. case: // Server or Domain Controller
  49. switch osvi.dwBuildNumber {
  50. case 14393:
  51. strings.write_string(&b, "2016 Server")
  52. case 17763:
  53. strings.write_string(&b, "2019 Server")
  54. case 20348:
  55. strings.write_string(&b, "2022 Server")
  56. case:
  57. strings.write_string(&b, "Unknown Server")
  58. }
  59. }
  60. case 6:
  61. switch osvi.dwMinorVersion {
  62. case 0:
  63. switch osvi.wProductType {
  64. case 1: // VER_NT_WORKSTATION
  65. strings.write_string(&b, "Windows Vista ")
  66. format_windows_product_type(&b, product_type)
  67. case 3:
  68. strings.write_string(&b, "Windows Server 2008")
  69. }
  70. case 1:
  71. switch osvi.wProductType {
  72. case 1: // VER_NT_WORKSTATION:
  73. strings.write_string(&b, "Windows 7 ")
  74. format_windows_product_type(&b, product_type)
  75. case 3:
  76. strings.write_string(&b, "Windows Server 2008 R2")
  77. }
  78. case 2:
  79. switch osvi.wProductType {
  80. case 1: // VER_NT_WORKSTATION:
  81. strings.write_string(&b, "Windows 8 ")
  82. format_windows_product_type(&b, product_type)
  83. case 3:
  84. strings.write_string(&b, "Windows Server 2012")
  85. }
  86. case 3:
  87. switch osvi.wProductType {
  88. case 1: // VER_NT_WORKSTATION:
  89. strings.write_string(&b, "Windows 8.1 ")
  90. format_windows_product_type(&b, product_type)
  91. case 3:
  92. strings.write_string(&b, "Windows Server 2012 R2")
  93. }
  94. }
  95. case 5:
  96. switch osvi.dwMinorVersion {
  97. case 0:
  98. strings.write_string(&b, "Windows 2000")
  99. case 1:
  100. strings.write_string(&b, "Windows XP")
  101. case 2:
  102. strings.write_string(&b, "Windows Server 2003")
  103. }
  104. }
  105. // Grab DisplayVersion
  106. os_version.version = format_display_version(&b)
  107. // Grab build number and UBR
  108. os_version.build[1] = format_build_number(&b, int(osvi.dwBuildNumber))
  109. // Finish the string
  110. os_version.as_string = strings.to_string(b)
  111. format_windows_product_type :: proc (b: ^strings.Builder, prod_type: sys.Windows_Product_Type) {
  112. #partial switch prod_type {
  113. case .ULTIMATE:
  114. strings.write_string(b, "Ultimate")
  115. case .HOME_BASIC:
  116. strings.write_string(b, "Home Basic")
  117. case .HOME_PREMIUM:
  118. strings.write_string(b, "Home Premium")
  119. case .ENTERPRISE:
  120. strings.write_string(b, "Enterprise")
  121. case .CORE:
  122. strings.write_string(b, "Home Basic")
  123. case .HOME_BASIC_N:
  124. strings.write_string(b, "Home Basic N")
  125. case .EDUCATION:
  126. strings.write_string(b, "Education")
  127. case .EDUCATION_N:
  128. strings.write_string(b, "Education N")
  129. case .BUSINESS:
  130. strings.write_string(b, "Business")
  131. case .STANDARD_SERVER:
  132. strings.write_string(b, "Standard Server")
  133. case .DATACENTER_SERVER:
  134. strings.write_string(b, "Datacenter")
  135. case .SMALLBUSINESS_SERVER:
  136. strings.write_string(b, "Windows Small Business Server")
  137. case .ENTERPRISE_SERVER:
  138. strings.write_string(b, "Enterprise Server")
  139. case .STARTER:
  140. strings.write_string(b, "Starter")
  141. case .DATACENTER_SERVER_CORE:
  142. strings.write_string(b, "Datacenter Server Core")
  143. case .STANDARD_SERVER_CORE:
  144. strings.write_string(b, "Server Standard Core")
  145. case .ENTERPRISE_SERVER_CORE:
  146. strings.write_string(b, "Enterprise Server Core")
  147. case .BUSINESS_N:
  148. strings.write_string(b, "Business N")
  149. case .HOME_SERVER:
  150. strings.write_string(b, "Home Server")
  151. case .SERVER_FOR_SMALLBUSINESS:
  152. strings.write_string(b, "Windows Server 2008 for Windows Essential Server Solutions")
  153. case .SMALLBUSINESS_SERVER_PREMIUM:
  154. strings.write_string(b, "Small Business Server Premium")
  155. case .HOME_PREMIUM_N:
  156. strings.write_string(b, "Home Premium N")
  157. case .ENTERPRISE_N:
  158. strings.write_string(b, "Enterprise N")
  159. case .ULTIMATE_N:
  160. strings.write_string(b, "Ultimate N")
  161. case .HYPERV:
  162. strings.write_string(b, "HyperV")
  163. case .STARTER_N:
  164. strings.write_string(b, "Starter N")
  165. case .PROFESSIONAL:
  166. strings.write_string(b, "Professional")
  167. case .PROFESSIONAL_N:
  168. strings.write_string(b, "Professional N")
  169. case:
  170. strings.write_string(b, "Unknown Edition")
  171. }
  172. }
  173. // Grab Windows DisplayVersion (like 20H02)
  174. format_display_version :: proc (b: ^strings.Builder) -> (version: string) {
  175. dv, ok := read_reg_string(
  176. sys.HKEY_LOCAL_MACHINE,
  177. "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
  178. "DisplayVersion",
  179. )
  180. defer delete(dv) // It'll be interned into `version_string_buf`
  181. if ok {
  182. strings.write_string(b, " (version: ")
  183. l := strings.builder_len(b^)
  184. strings.write_string(b, dv)
  185. version = strings.to_string(b^)[l:][:len(dv)]
  186. strings.write_rune(b, ')')
  187. }
  188. return
  189. }
  190. // Grab build number and UBR
  191. format_build_number :: proc (b: ^strings.Builder, major_build: int) -> (ubr: int) {
  192. res, ok := read_reg_i32(
  193. sys.HKEY_LOCAL_MACHINE,
  194. "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
  195. "UBR",
  196. )
  197. if ok {
  198. ubr = int(res)
  199. strings.write_string(b, ", build: ")
  200. strings.write_int(b, major_build)
  201. strings.write_rune(b, '.')
  202. strings.write_int(b, ubr)
  203. }
  204. return
  205. }
  206. }
  207. @(init)
  208. init_ram :: proc() {
  209. state: sys.MEMORYSTATUSEX
  210. state.dwLength = size_of(state)
  211. ok := sys.GlobalMemoryStatusEx(&state)
  212. if !ok {
  213. return
  214. }
  215. ram = RAM{
  216. total_ram = int(state.ullTotalPhys),
  217. free_ram = int(state.ullAvailPhys),
  218. total_swap = int(state.ullTotalPageFil),
  219. free_swap = int(state.ullAvailPageFil),
  220. }
  221. }
  222. @(init, private)
  223. init_gpu_info :: proc() {
  224. GPU_INFO_BASE :: "SYSTEM\\ControlSet001\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\"
  225. gpu_list: [dynamic]GPU
  226. gpu_index: int
  227. for {
  228. key := fmt.tprintf("%v\\%04d", GPU_INFO_BASE, gpu_index)
  229. if vendor, ok := read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "ProviderName"); ok {
  230. append(&gpu_list, GPU{vendor_name = vendor})
  231. } else {
  232. break
  233. }
  234. if desc, ok := read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "DriverDesc"); ok {
  235. gpu_list[gpu_index].model_name = desc
  236. }
  237. if vram, ok := read_reg_i64(sys.HKEY_LOCAL_MACHINE, key, "HardwareInformation.qwMemorySize"); ok {
  238. gpu_list[gpu_index].total_ram = int(vram)
  239. }
  240. gpu_index += 1
  241. }
  242. gpus = gpu_list[:]
  243. }
  244. @(private)
  245. read_reg_string :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: string, ok: bool) {
  246. if len(subkey) == 0 || len(val) == 0 {
  247. return
  248. }
  249. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  250. BUF_SIZE :: 1024
  251. key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
  252. val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
  253. utf16.encode_string(key_name_wide, subkey)
  254. utf16.encode_string(val_name_wide, val)
  255. result_wide := make([]u16, BUF_SIZE, context.temp_allocator)
  256. result_size := sys.DWORD(BUF_SIZE * size_of(u16))
  257. status := sys.RegGetValueW(
  258. hkey,
  259. &key_name_wide[0],
  260. &val_name_wide[0],
  261. sys.RRF_RT_REG_SZ,
  262. nil,
  263. raw_data(result_wide[:]),
  264. &result_size,
  265. )
  266. if status != 0 {
  267. // Couldn't retrieve string
  268. return
  269. }
  270. // Result string will be allocated for the caller.
  271. result_utf8 := make([]u8, BUF_SIZE * 4, context.temp_allocator)
  272. utf16.decode_to_utf8(result_utf8, result_wide[:result_size])
  273. return strings.clone_from_cstring(cstring(raw_data(result_utf8))), true
  274. }
  275. @(private)
  276. read_reg_i32 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i32, ok: bool) {
  277. if len(subkey) == 0 || len(val) == 0 {
  278. return
  279. }
  280. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  281. BUF_SIZE :: 1024
  282. key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
  283. val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
  284. utf16.encode_string(key_name_wide, subkey)
  285. utf16.encode_string(val_name_wide, val)
  286. result_size := sys.DWORD(size_of(i32))
  287. status := sys.RegGetValueW(
  288. hkey,
  289. &key_name_wide[0],
  290. &val_name_wide[0],
  291. sys.RRF_RT_REG_DWORD,
  292. nil,
  293. &res,
  294. &result_size,
  295. )
  296. return res, status == 0
  297. }
  298. @(private)
  299. read_reg_i64 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i64, ok: bool) {
  300. if len(subkey) == 0 || len(val) == 0 {
  301. return
  302. }
  303. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
  304. BUF_SIZE :: 1024
  305. key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
  306. val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
  307. utf16.encode_string(key_name_wide, subkey)
  308. utf16.encode_string(val_name_wide, val)
  309. result_size := sys.DWORD(size_of(i64))
  310. status := sys.RegGetValueW(
  311. hkey,
  312. &key_name_wide[0],
  313. &val_name_wide[0],
  314. sys.RRF_RT_REG_QWORD,
  315. nil,
  316. &res,
  317. &result_size,
  318. )
  319. return res, status == 0
  320. }