Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/installer/installer.cpp
7197 views
1
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "installer.h"
5
#include "installer_params.h"
6
7
#include "common/error.h"
8
#include "common/file_system.h"
9
#include "common/log.h"
10
#include "common/path.h"
11
#include "common/progress_callback.h"
12
#include "common/scoped_guard.h"
13
#include "common/string_util.h"
14
15
#include "common/windows_headers.h"
16
17
#include "7zAlloc.h"
18
#include "7zCrc.h"
19
20
#include <algorithm>
21
#include <cstdio>
22
#include <cstring>
23
#include <memory>
24
#include <set>
25
#include <string>
26
#include <vector>
27
28
#include <KnownFolders.h>
29
#include <ShlObj.h>
30
#include <Shobjidl.h>
31
#include <shellapi.h>
32
#include <wrl/client.h>
33
34
static constexpr size_t kInputBufSize = static_cast<size_t>(1) << 18;
35
static constexpr ISzAlloc g_Alloc = {SzAlloc, SzFree};
36
37
// TODO: Get this outta here
38
static const char* SZErrorToString(int res)
39
{
40
// clang-format off
41
switch (res)
42
{
43
case SZ_OK: return "SZ_OK";
44
case SZ_ERROR_DATA: return "SZ_ERROR_DATA";
45
case SZ_ERROR_MEM: return "SZ_ERROR_MEM";
46
case SZ_ERROR_CRC: return "SZ_ERROR_CRC";
47
case SZ_ERROR_UNSUPPORTED: return "SZ_ERROR_UNSUPPORTED";
48
case SZ_ERROR_PARAM: return "SZ_ERROR_PARAM";
49
case SZ_ERROR_INPUT_EOF: return "SZ_ERROR_INPUT_EOF";
50
case SZ_ERROR_OUTPUT_EOF: return "SZ_ERROR_OUTPUT_EOF";
51
case SZ_ERROR_READ: return "SZ_ERROR_READ";
52
case SZ_ERROR_WRITE: return "SZ_ERROR_WRITE";
53
case SZ_ERROR_PROGRESS: return "SZ_ERROR_PROGRESS";
54
case SZ_ERROR_FAIL: return "SZ_ERROR_FAIL";
55
case SZ_ERROR_THREAD: return "SZ_ERROR_THREAD";
56
case SZ_ERROR_ARCHIVE: return "SZ_ERROR_ARCHIVE";
57
case SZ_ERROR_NO_ARCHIVE: return "SZ_ERROR_NO_ARCHIVE";
58
default: return "SZ_UNKNOWN";
59
}
60
// clang-format on
61
}
62
63
// Based on 7-zip SfxSetup.c FindSignature() by Igor Pavlov (public domain)
64
static bool FindSignature(CSzFile* stream, s64* start_offset)
65
{
66
// How much we're reading at once.
67
static constexpr size_t READ_BUFFER_SIZE = 1 << 15;
68
69
// How much of the .exe file to search through for the 7z signature.
70
// This should be more than enough for any reasonable SFX stub.
71
static constexpr size_t SIGNATURE_SEARCH_LIMIT = (1 << 22);
72
73
u8 buf[READ_BUFFER_SIZE];
74
size_t num_prev_bytes = 0;
75
*start_offset = 0;
76
for (;;)
77
{
78
if (*start_offset > static_cast<s64>(SIGNATURE_SEARCH_LIMIT))
79
return false;
80
81
size_t processed = READ_BUFFER_SIZE - num_prev_bytes;
82
if (File_Read(stream, buf + num_prev_bytes, &processed) != 0)
83
return false;
84
85
processed += num_prev_bytes;
86
if (processed < k7zStartHeaderSize || (processed == k7zStartHeaderSize && num_prev_bytes != 0))
87
return false;
88
89
processed -= k7zStartHeaderSize;
90
91
for (size_t pos = 0; pos <= processed; pos++)
92
{
93
for (; pos <= processed && buf[pos] != '7'; pos++)
94
;
95
if (pos > processed)
96
break;
97
98
if (std::memcmp(buf + pos, k7zSignature, k7zSignatureSize) == 0)
99
{
100
u32 file_value;
101
std::memcpy(&file_value, buf + pos + 8, sizeof(file_value));
102
if (CrcCalc(buf + pos + 12, 20) == file_value)
103
{
104
*start_offset += pos;
105
return true;
106
}
107
}
108
}
109
110
*start_offset += processed;
111
num_prev_bytes = k7zStartHeaderSize;
112
std::memmove(buf, buf + processed, k7zStartHeaderSize);
113
}
114
}
115
116
Installer::Installer(Win32ProgressCallback* progress, std::string destination_directory)
117
: m_destination_directory(std::move(destination_directory)), m_progress(progress)
118
{
119
m_staging_directory = Path::Combine(m_destination_directory, "staging");
120
121
progress->SetTitle("DuckStation Installer");
122
progress->SetStatusText("Preparing installation...");
123
}
124
125
Installer::~Installer()
126
{
127
CloseArchiveStream();
128
}
129
130
bool Installer::RecursiveDeleteDirectory(const char* path, bool remove_dir)
131
{
132
if (!remove_dir)
133
return false;
134
135
Microsoft::WRL::ComPtr<IFileOperation> fo;
136
HRESULT hr = CoCreateInstance(CLSID_FileOperation, NULL, CLSCTX_ALL, IID_PPV_ARGS(fo.ReleaseAndGetAddressOf()));
137
if (FAILED(hr))
138
{
139
m_progress->FormatError("CoCreateInstance() for IFileOperation failed: {}",
140
Error::CreateHResult(hr).GetDescription());
141
return false;
142
}
143
144
Microsoft::WRL::ComPtr<IShellItem> item;
145
hr = SHCreateItemFromParsingName(StringUtil::UTF8StringToWideString(path).c_str(), NULL,
146
IID_PPV_ARGS(item.ReleaseAndGetAddressOf()));
147
if (FAILED(hr))
148
{
149
m_progress->FormatError("SHCreateItemFromParsingName() for delete failed: {}",
150
Error::CreateHResult(hr).GetDescription());
151
return false;
152
}
153
154
hr = fo->SetOperationFlags(FOF_NOCONFIRMATION | FOF_SILENT);
155
if (FAILED(hr))
156
{
157
m_progress->FormatWarning("IFileOperation::SetOperationFlags() failed: {}",
158
Error::CreateHResult(hr).GetDescription());
159
}
160
161
hr = fo->DeleteItem(item.Get(), nullptr);
162
if (FAILED(hr))
163
{
164
m_progress->FormatError("IFileOperation::DeleteItem() failed: {}", Error::CreateHResult(hr).GetDescription());
165
return false;
166
}
167
168
item.Reset();
169
hr = fo->PerformOperations();
170
if (FAILED(hr))
171
{
172
m_progress->FormatError("IFileOperation::PerformOperations() failed: {}",
173
Error::CreateHResult(hr).GetDescription());
174
return false;
175
}
176
177
return true;
178
}
179
180
bool Installer::OpenArchiveStream()
181
{
182
FileInStream_CreateVTable(&m_archive_stream);
183
LookToRead2_CreateVTable(&m_look_stream, False);
184
CrcGenerateTable();
185
186
m_look_stream.buf = (Byte*)ISzAlloc_Alloc(&g_Alloc, kInputBufSize);
187
if (!m_look_stream.buf)
188
{
189
m_progress->DisplayError("Failed to allocate input buffer.");
190
return false;
191
}
192
193
Error error;
194
std::string program_path = FileSystem::GetProgramPath(&error);
195
if (program_path.empty())
196
{
197
m_progress->FormatModalError("Failed to get program path: {}", error.GetDescription());
198
return false;
199
}
200
201
m_progress->FormatInformation("Program/archive path: {}", program_path);
202
203
WRes wres = InFile_OpenW(&m_archive_stream.file, FileSystem::GetWin32Path(program_path).c_str());
204
if (wres != 0)
205
{
206
m_progress->FormatModalError("Failed to open '{}': {}", program_path, static_cast<int>(wres));
207
return false;
208
}
209
210
m_archive_stream_opened = true;
211
212
s64 archive_start_pos = 0;
213
if (!FindSignature(&m_archive_stream.file, &archive_start_pos))
214
{
215
m_progress->ModalError("Failed to find 7z archive signature in installer. Please try re-downloading from "
216
"duckstation.org, and if you are still having difficulties, chat to us on Discord.");
217
return false;
218
}
219
220
m_progress->FormatInformation("Found 7z archive in installer at offset {}", archive_start_pos);
221
222
// seek to archive start
223
wres = File_Seek(&m_archive_stream.file, &archive_start_pos, SZ_SEEK_SET);
224
if (wres != 0)
225
{
226
m_progress->FormatModalError("Failed to seek to archive start (error {} [{}]).", SZErrorToString(wres),
227
static_cast<int>(wres));
228
return false;
229
}
230
231
m_look_stream.bufSize = kInputBufSize;
232
m_look_stream.realStream = &m_archive_stream.vt;
233
LookToRead2_INIT(&m_look_stream);
234
SzArEx_Init(&m_archive);
235
236
SRes res = SzArEx_Open(&m_archive, &m_look_stream.vt, &g_Alloc, &g_Alloc);
237
if (res != SZ_OK)
238
{
239
m_progress->FormatModalError("SzArEx_Open() failed: {} [{}]", SZErrorToString(res), static_cast<int>(res));
240
return false;
241
}
242
243
m_archive_opened = true;
244
return ParseArchive();
245
}
246
247
void Installer::CloseArchiveStream()
248
{
249
if (m_archive_opened)
250
{
251
SzArEx_Free(&m_archive, &g_Alloc);
252
m_archive_opened = false;
253
}
254
255
if (m_look_stream.buf)
256
{
257
ISzAlloc_Free(&g_Alloc, m_look_stream.buf);
258
m_look_stream.buf = nullptr;
259
}
260
261
if (m_archive_stream_opened)
262
{
263
File_Close(&m_archive_stream.file);
264
m_archive_stream_opened = false;
265
}
266
}
267
268
bool Installer::ParseArchive()
269
{
270
std::vector<UInt16> filename_buffer;
271
272
for (u32 file_index = 0; file_index < m_archive.NumFiles; file_index++)
273
{
274
// skip directories, we handle them ourselves
275
if (SzArEx_IsDir(&m_archive, file_index))
276
continue;
277
278
size_t filename_len = SzArEx_GetFileNameUtf16(&m_archive, file_index, nullptr);
279
if (filename_len <= 1)
280
continue;
281
282
filename_buffer.resize(filename_len);
283
SzArEx_GetFileNameUtf16(&m_archive, file_index, filename_buffer.data());
284
285
// TODO: This won't work on Linux (4-byte wchar_t).
286
FileToUpdate entry;
287
entry.file_index = file_index;
288
entry.destination_filename = StringUtil::WideStringToUTF8String(reinterpret_cast<wchar_t*>(filename_buffer.data()));
289
if (entry.destination_filename.empty())
290
continue;
291
292
// replace forward slashes with backslashes
293
for (size_t i = 0; i < entry.destination_filename.length(); i++)
294
{
295
if (entry.destination_filename[i] == '/' || entry.destination_filename[i] == '\\')
296
entry.destination_filename[i] = FS_OSPATH_SEPARATOR_CHARACTER;
297
}
298
299
// should never have a leading slash. just in case.
300
while (entry.destination_filename[0] == FS_OSPATH_SEPARATOR_CHARACTER)
301
entry.destination_filename.erase(0, 1);
302
303
// skip directories (we sort them out later)
304
if (!entry.destination_filename.empty() && entry.destination_filename.back() != FS_OSPATH_SEPARATOR_CHARACTER)
305
m_update_paths.push_back(std::move(entry));
306
}
307
308
if (m_update_paths.empty())
309
{
310
m_progress->ModalError("No files found in update zip.");
311
return false;
312
}
313
314
for (const FileToUpdate& ftu : m_update_paths)
315
{
316
const size_t len = ftu.destination_filename.length();
317
for (size_t i = 0; i < len; i++)
318
{
319
if (ftu.destination_filename[i] == FS_OSPATH_SEPARATOR_CHARACTER)
320
{
321
std::string dir(ftu.destination_filename.begin(), ftu.destination_filename.begin() + i);
322
while (!dir.empty() && dir[dir.length() - 1] == FS_OSPATH_SEPARATOR_CHARACTER)
323
dir.erase(dir.length() - 1);
324
325
if (std::find(m_update_directories.begin(), m_update_directories.end(), dir) == m_update_directories.end())
326
m_update_directories.push_back(std::move(dir));
327
}
328
}
329
}
330
331
std::sort(m_update_directories.begin(), m_update_directories.end());
332
for (const std::string& dir : m_update_directories)
333
m_progress->FormatDebugMessage("Directory: {}", dir);
334
335
return true;
336
}
337
338
bool Installer::PrepareStagingDirectory()
339
{
340
if (FileSystem::DirectoryExists(m_staging_directory.c_str()))
341
{
342
m_progress->DisplayWarning("Update staging directory already exists, removing");
343
if (!RecursiveDeleteDirectory(m_staging_directory.c_str(), true) ||
344
FileSystem::DirectoryExists(m_staging_directory.c_str()))
345
{
346
m_progress->ModalError("Failed to remove old staging directory");
347
return false;
348
}
349
}
350
if (!FileSystem::CreateDirectory(m_staging_directory.c_str(), false))
351
{
352
m_progress->FormatModalError("Failed to create staging directory {}", m_staging_directory);
353
return false;
354
}
355
356
// create subdirectories in staging directory
357
for (const std::string& subdir : m_update_directories)
358
{
359
m_progress->FormatInformation("Creating subdirectory in staging: {}", subdir);
360
361
const std::string staging_subdir = Path::Combine(m_staging_directory, subdir);
362
if (!FileSystem::CreateDirectory(staging_subdir.c_str(), false))
363
{
364
m_progress->FormatModalError("Failed to create staging subdirectory {}", staging_subdir);
365
return false;
366
}
367
}
368
369
return true;
370
}
371
372
bool Installer::StageUpdate()
373
{
374
m_progress->SetProgressRange(static_cast<u32>(m_update_paths.size()));
375
m_progress->SetProgressValue(0);
376
377
UInt32 block_index = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */
378
Byte* out_buffer = 0; /* it must be 0 before first call for each new archive. */
379
size_t out_buffer_size = 0; /* it can have any value before first call (if outBuffer = 0) */
380
const ScopedGuard out_buffer_guard([&out_buffer]() {
381
if (out_buffer)
382
ISzAlloc_Free(&g_Alloc, out_buffer);
383
});
384
385
Error error;
386
387
for (const FileToUpdate& ftu : m_update_paths)
388
{
389
m_progress->FormatStatusText("Extracting '{}'...", ftu.destination_filename);
390
m_progress->FormatInformation("Extracting '{}'...", ftu.destination_filename);
391
392
size_t out_offset = 0;
393
size_t extracted_size = 0;
394
SRes res = SzArEx_Extract(&m_archive, &m_look_stream.vt, ftu.file_index, &block_index, &out_buffer,
395
&out_buffer_size, &out_offset, &extracted_size, &g_Alloc, &g_Alloc);
396
if (res != SZ_OK)
397
{
398
m_progress->FormatModalError("Failed to decompress file '{}' from 7z (file index={}, error={})",
399
ftu.destination_filename, ftu.file_index, SZErrorToString(res));
400
return false;
401
}
402
403
const std::string destination_file = Path::Combine(m_staging_directory, ftu.destination_filename);
404
if (!FileSystem::WriteBinaryFile(destination_file.c_str(),
405
std::span<const u8>(out_buffer + out_offset, extracted_size), &error))
406
{
407
m_progress->FormatModalError("Failed to write output file '{}': {}", ftu.destination_filename,
408
error.GetDescription());
409
FileSystem::DeleteFile(destination_file.c_str());
410
return false;
411
}
412
413
m_progress->IncrementProgressValue();
414
}
415
416
return true;
417
}
418
419
bool Installer::CommitUpdate()
420
{
421
m_progress->SetStatusText("Committing installation...");
422
423
// create directories in target
424
for (const std::string& subdir : m_update_directories)
425
{
426
const std::string dest_subdir = Path::Combine(m_destination_directory, subdir);
427
if (!FileSystem::DirectoryExists(dest_subdir.c_str()) && !FileSystem::CreateDirectory(dest_subdir.c_str(), false))
428
{
429
m_progress->FormatModalError("Failed to create target directory '{}'", dest_subdir);
430
return false;
431
}
432
}
433
434
// move files to target
435
for (const FileToUpdate& ftu : m_update_paths)
436
{
437
const std::string staging_file_name = Path::Combine(m_staging_directory, ftu.destination_filename);
438
const std::string dest_file_name = Path::Combine(m_destination_directory, ftu.destination_filename);
439
m_progress->FormatInformation("Moving '{}' to '{}'", staging_file_name, dest_file_name);
440
441
Error error;
442
const bool result = MoveFileExW(FileSystem::GetWin32Path(staging_file_name).c_str(),
443
FileSystem::GetWin32Path(dest_file_name).c_str(), MOVEFILE_REPLACE_EXISTING);
444
if (!result)
445
error.SetWin32(GetLastError());
446
if (!result)
447
{
448
m_progress->FormatModalError("Failed to rename '{}' to '{}': {}", staging_file_name, dest_file_name,
449
error.GetDescription());
450
return false;
451
}
452
}
453
454
return true;
455
}
456
457
void Installer::CleanupStagingDirectory()
458
{
459
// remove staging directory itself
460
if (!RecursiveDeleteDirectory(m_staging_directory.c_str(), true))
461
m_progress->FormatError("Failed to remove staging directory '{}'", m_staging_directory);
462
}
463
464
bool Installer::CheckForEmptyDirectory(const std::string& directory)
465
{
466
if (!FileSystem::DirectoryExists(directory.c_str()))
467
return true;
468
469
FileSystem::FindResultsArray results;
470
if (!FileSystem::FindFiles(directory.c_str(), "*",
471
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES, &results))
472
{
473
return true;
474
}
475
476
return results.empty();
477
}
478
479
bool Installer::Install()
480
{
481
m_progress->FormatInformation("Destination directory: '{}'", m_destination_directory);
482
m_progress->FormatInformation("Staging directory: '{}'", m_staging_directory);
483
484
if (!OpenArchiveStream())
485
return false;
486
487
// Create destination directory if it doesn't exist
488
if (!FileSystem::DirectoryExists(m_destination_directory.c_str()))
489
{
490
m_progress->FormatStatusText("Creating directory '{}'...", m_destination_directory);
491
492
// Only create one level of parent - the Programs directory if it doesn't already exist.
493
const std::string parent_directory = std::string(Path::GetDirectory(m_destination_directory));
494
if (!FileSystem::DirectoryExists(parent_directory.c_str()))
495
{
496
m_progress->FormatStatusText("Creating parent directory '{}'", parent_directory.c_str());
497
498
Error error;
499
if (!FileSystem::CreateDirectory(parent_directory.c_str(), false, &error))
500
{
501
m_progress->FormatModalError("Failed to create parent directory '{}': {}", parent_directory,
502
error.GetDescription());
503
return false;
504
}
505
}
506
507
Error error;
508
if (!FileSystem::CreateDirectory(m_destination_directory.c_str(), false, &error))
509
{
510
m_progress->FormatModalError("Failed to create destination directory '{}': {}", m_destination_directory,
511
error.GetDescription());
512
return false;
513
}
514
}
515
516
if (!PrepareStagingDirectory())
517
{
518
m_progress->ModalError("Failed to prepare staging directory.");
519
CleanupStagingDirectory();
520
return false;
521
}
522
523
if (!StageUpdate())
524
{
525
m_progress->ModalError("Failed to stage installation files.");
526
CleanupStagingDirectory();
527
return false;
528
}
529
530
if (!CommitUpdate())
531
{
532
m_progress->ModalError("Failed to commit installation.");
533
CleanupStagingDirectory();
534
return false;
535
}
536
537
CleanupStagingDirectory();
538
539
CreateUninstallerEntry();
540
541
return true;
542
}
543
544
bool Installer::CreateShellLink(const std::string& link_path, const std::string& target_path)
545
{
546
const std::wstring target_path_w = StringUtil::UTF8StringToWideString(target_path);
547
const std::wstring link_path_w = StringUtil::UTF8StringToWideString(link_path);
548
const std::wstring working_dir_w = StringUtil::UTF8StringToWideString(m_destination_directory);
549
550
Microsoft::WRL::ComPtr<IShellLinkW> shell_link;
551
HRESULT hr =
552
CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(shell_link.GetAddressOf()));
553
if (FAILED(hr))
554
{
555
m_progress->FormatModalError("CoCreateInstance(CLSID_ShellLink) failed: {}",
556
Error::CreateHResult(hr).GetDescription());
557
return false;
558
}
559
560
shell_link->SetPath(target_path_w.c_str());
561
shell_link->SetWorkingDirectory(working_dir_w.c_str());
562
shell_link->SetDescription(L"PlayStation 1 Emulator");
563
shell_link->SetIconLocation(target_path_w.c_str(), 0);
564
565
Microsoft::WRL::ComPtr<IPersistFile> persist_file;
566
hr = shell_link.As(&persist_file);
567
if (FAILED(hr))
568
{
569
m_progress->FormatModalError("IShellLink::QueryInterface(IPersistFile) failed: {}",
570
Error::CreateHResult(hr).GetDescription());
571
return false;
572
}
573
574
hr = persist_file->Save(link_path_w.c_str(), TRUE);
575
if (FAILED(hr))
576
{
577
m_progress->FormatModalError("IPersistFile::Save() failed: {}", Error::CreateHResult(hr).GetDescription());
578
return false;
579
}
580
581
m_progress->FormatInformation("Created shortcut: {}", link_path);
582
return true;
583
}
584
585
bool Installer::CreateDesktopShortcut()
586
{
587
m_progress->SetStatusText("Creating desktop shortcut...");
588
589
PWSTR desktop_folder = nullptr;
590
HRESULT hr = SHGetKnownFolderPath(FOLDERID_Desktop, 0, nullptr, &desktop_folder);
591
if (FAILED(hr))
592
{
593
m_progress->FormatModalError("SHGetKnownFolderPath(FOLDERID_Desktop) failed: {}",
594
Error::CreateHResult(hr).GetDescription());
595
return false;
596
}
597
598
const std::string shortcut_path =
599
Path::Combine(StringUtil::WideStringToUTF8String(desktop_folder), INSTALLER_SHORTCUT_FILENAME);
600
CoTaskMemFree(desktop_folder);
601
602
const std::string target_path = Path::Combine(m_destination_directory, INSTALLER_PROGRAM_FILENAME);
603
return CreateShellLink(shortcut_path, target_path);
604
}
605
606
bool Installer::CreateStartMenuShortcut()
607
{
608
m_progress->SetStatusText("Creating Start Menu shortcut...");
609
610
PWSTR programs_folder = nullptr;
611
HRESULT hr = SHGetKnownFolderPath(FOLDERID_Programs, 0, nullptr, &programs_folder);
612
if (FAILED(hr))
613
{
614
m_progress->FormatModalError("SHGetKnownFolderPath(FOLDERID_Programs) failed: {}",
615
Error::CreateHResult(hr).GetDescription());
616
return false;
617
}
618
619
const std::string shortcut_path =
620
Path::Combine(StringUtil::WideStringToUTF8String(programs_folder), INSTALLER_SHORTCUT_FILENAME);
621
CoTaskMemFree(programs_folder);
622
623
const std::string target_path = Path::Combine(m_destination_directory, INSTALLER_PROGRAM_FILENAME);
624
return CreateShellLink(shortcut_path, target_path);
625
}
626
627
bool Installer::CreateUninstallerEntry()
628
{
629
m_progress->SetStatusText("Creating uninstaller entry...");
630
631
HKEY key;
632
LSTATUS status =
633
RegCreateKeyExW(HKEY_CURRENT_USER, INSTALLER_UNINSTALL_REG_KEY, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr);
634
if (status != ERROR_SUCCESS)
635
{
636
m_progress->FormatModalError("Failed to create uninstaller registry key: {}",
637
Error::CreateWin32(status).GetDescription());
638
return false;
639
}
640
641
// Estimate the installed size of the application.
642
s64 install_size = 0;
643
if (FileSystem::FindResultsArray results; FileSystem::FindFiles(
644
m_destination_directory.c_str(), "*",
645
FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS | FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS,
646
&results))
647
{
648
for (const FILESYSTEM_FIND_DATA& fd : results)
649
{
650
if (!(fd.Attributes & (FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY | FILESYSTEM_FILE_ATTRIBUTE_LINK)))
651
install_size += fd.Size;
652
}
653
}
654
655
const std::string display_name = "DuckStation";
656
const std::string uninstall_path = Path::Combine(m_destination_directory, INSTALLER_UNINSTALLER_FILENAME);
657
const std::string install_location = m_destination_directory;
658
const std::string display_icon = Path::Combine(m_destination_directory, INSTALLER_PROGRAM_FILENAME);
659
const std::string publisher = "Stenzek";
660
const DWORD no_modify = 1;
661
const DWORD no_repair = 1;
662
663
const std::wstring display_name_w = StringUtil::UTF8StringToWideString(display_name);
664
const std::wstring uninstall_path_w = StringUtil::UTF8StringToWideString(uninstall_path);
665
const std::wstring install_location_w = StringUtil::UTF8StringToWideString(install_location);
666
const std::wstring display_icon_w = StringUtil::UTF8StringToWideString(display_icon);
667
const std::wstring publisher_w = StringUtil::UTF8StringToWideString(publisher);
668
669
RegSetValueExW(key, L"DisplayName", 0, REG_SZ, reinterpret_cast<const BYTE*>(display_name_w.c_str()),
670
static_cast<DWORD>((display_name_w.length() + 1) * sizeof(wchar_t)));
671
RegSetValueExW(key, L"UninstallString", 0, REG_SZ, reinterpret_cast<const BYTE*>(uninstall_path_w.c_str()),
672
static_cast<DWORD>((uninstall_path_w.length() + 1) * sizeof(wchar_t)));
673
RegSetValueExW(key, L"InstallLocation", 0, REG_SZ, reinterpret_cast<const BYTE*>(install_location_w.c_str()),
674
static_cast<DWORD>((install_location_w.length() + 1) * sizeof(wchar_t)));
675
RegSetValueExW(key, L"DisplayIcon", 0, REG_SZ, reinterpret_cast<const BYTE*>(display_icon_w.c_str()),
676
static_cast<DWORD>((display_icon_w.length() + 1) * sizeof(wchar_t)));
677
RegSetValueExW(key, L"Publisher", 0, REG_SZ, reinterpret_cast<const BYTE*>(publisher_w.c_str()),
678
static_cast<DWORD>((publisher_w.length() + 1) * sizeof(wchar_t)));
679
RegSetValueExW(key, L"NoModify", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&no_modify), sizeof(no_modify));
680
RegSetValueExW(key, L"NoRepair", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&no_repair), sizeof(no_repair));
681
682
if (const DWORD install_size_kb = static_cast<DWORD>(install_size / 1024); install_size_kb > 0)
683
{
684
RegSetValueExW(key, L"EstimatedSize", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&install_size_kb),
685
sizeof(install_size_kb));
686
}
687
688
RegCloseKey(key);
689
690
m_progress->FormatInformation("Created uninstaller entry");
691
return true;
692
}
693
694
bool Installer::LaunchApplication(const std::string& directory, Error* error)
695
{
696
const std::string exe_path = Path::Combine(directory, INSTALLER_PROGRAM_FILENAME);
697
const std::wstring exe_path_w = StringUtil::UTF8StringToWideString(exe_path);
698
const std::wstring working_dir_w = StringUtil::UTF8StringToWideString(directory);
699
700
SHELLEXECUTEINFOW sei = {};
701
sei.cbSize = sizeof(sei);
702
sei.fMask = SEE_MASK_DEFAULT;
703
sei.lpVerb = L"open";
704
sei.lpFile = exe_path_w.c_str();
705
sei.lpDirectory = working_dir_w.c_str();
706
sei.nShow = SW_SHOWNORMAL;
707
708
if (!ShellExecuteExW(&sei))
709
{
710
Error::SetStringFmt(error, "ShellExecuteExW() failed: ", GetLastError());
711
return false;
712
}
713
714
return true;
715
}
716
717