Skip to content

Commit ac528ec

Browse files
committed
msix installer kind of works, but you cannot install msix by double clicking.. so need to look other options.
modified methods to handle readonly folder for (msix) installation
1 parent 0f1929a commit ac528ec

11 files changed

+214
-26
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,6 @@ ASALocalRun/
329329
# MFractors (Xamarin productivity tool) working folder
330330
.mfractor/
331331
/Paths.cmd
332+
_AppxTemp/
333+
AppxBuild/
334+
*.msix

AppxManifest.xml

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
3+
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
4+
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
5+
IgnorableNamespaces="uap rescap">
6+
7+
8+
<Identity Name="com.unitycoder.UnityLauncherPro"
9+
Publisher="CN=UnityCoder"
10+
Version="1.0.0.0" />
11+
12+
<Properties>
13+
<DisplayName>UnityLauncherPro</DisplayName>
14+
<PublisherDisplayName>unitycoder.com</PublisherDisplayName>
15+
<Logo>Installer/icon.png</Logo>
16+
</Properties>
17+
18+
<Dependencies>
19+
<TargetDeviceFamily Name="Windows.Desktop"
20+
MinVersion="10.0.17763.0"
21+
MaxVersionTested="10.0.22621.0" />
22+
</Dependencies>
23+
24+
<Resources>
25+
<Resource Language="en-us" />
26+
</Resources>
27+
28+
<Applications>
29+
<Application Id="App"
30+
Executable="UnityLauncherPro.exe"
31+
EntryPoint="Windows.FullTrustApplication">
32+
<uap:VisualElements
33+
DisplayName="UnityLauncherPro"
34+
Description="Custom Unity Project Launcher"
35+
BackgroundColor="transparent"
36+
Square150x150Logo="Installer/Square150x150Logo.png"
37+
Square44x44Logo="Installer/Square44x44Logo.png">
38+
<uap:DefaultTile Wide310x150Logo="Installer/Wide310x150Logo.png" />
39+
<uap:SplashScreen Image="Installer/SplashScreen620x300.png" />
40+
</uap:VisualElements>
41+
</Application>
42+
</Applications>
43+
44+
<Capabilities>
45+
<rescap:Capability Name="runFullTrust" />
46+
</Capabilities>
47+
</Package>

BuildMsix.cmd

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
@echo off
2+
setlocal
3+
pushd "%~dp0"
4+
5+
:: -----------------------------
6+
:: Generate date-based version
7+
:: -----------------------------
8+
for /f %%a in ('powershell -NoProfile -Command "Get-Date -Format yyyyMMdd"') do set DATE=%%a
9+
10+
:: -----------------------------
11+
:: Configuration
12+
:: -----------------------------
13+
set APPNAME=UnityLauncherPro
14+
set OUTPUT=%APPNAME%_%DATE%.msix
15+
set BUILD_DIR=_AppxTemp
16+
set MANIFEST=AppxManifest.xml
17+
18+
:: -----------------------------
19+
:: Clean previous build
20+
:: -----------------------------
21+
if exist "%OUTPUT%" del /f /q "%OUTPUT%"
22+
if exist "%BUILD_DIR%" rmdir /s /q "%BUILD_DIR%"
23+
mkdir "%BUILD_DIR%"
24+
25+
:: -----------------------------
26+
:: Copy required files
27+
:: -----------------------------
28+
echo Copying files to %BUILD_DIR%...
29+
30+
xcopy /y "UnityLauncherPro\\bin\\Release\\UnityLauncherPro.exe" "%BUILD_DIR%\\"
31+
xcopy /y "UnityLauncherPro\\Images\\icon.ico" "%BUILD_DIR%\\"
32+
copy /y "AppxManifest.xml" "%BUILD_DIR%\AppxManifest.xml"
33+
xcopy /y /s /e "Installer" "%BUILD_DIR%\Installer\" >nul
34+
35+
:: if exist "UnityLauncherPro\\Scripts" xcopy /y /s /e "UnityLauncherPro\\Scripts" "%BUILD_DIR%\\Scripts\\"
36+
:: if exist "UnityLauncherPro\\Images" xcopy /y /s /e "UnityLauncherPro\\Images" "%BUILD_DIR%\\Images\\"
37+
38+
:: -----------------------------
39+
:: Validate AppxManifest.xml
40+
:: -----------------------------
41+
if not exist "%BUILD_DIR%\\AppxManifest.xml" (
42+
echo ❌ ERROR: AppxManifest.xml not found in %BUILD_DIR%.
43+
pause
44+
goto :end
45+
)
46+
47+
:: -----------------------------
48+
:: Build MSIX
49+
:: -----------------------------
50+
echo Building MSIX package...
51+
makeappx pack /d "%BUILD_DIR%" /p "%OUTPUT%"
52+
if %errorlevel% neq 0 (
53+
echo ❌ MSIX build failed.
54+
pause
55+
goto :end
56+
)
57+
58+
:: -----------------------------
59+
:: Done
60+
:: -----------------------------
61+
echo MSIX created: %OUTPUT%
62+
echo Run this to install:
63+
echo powershell -Command "Add-AppxPackage '%OUTPUT%'"
64+
echo.
65+
pause
66+
67+
:end
68+
popd
69+
endlocal

Installer/SplashScreen620x300.png

4.65 KB
Loading

Installer/Square150x150Logo.png

3.75 KB
Loading

Installer/Square44x44Logo.png

3.56 KB
Loading

Installer/Wide310x150Logo.png

3.9 KB
Loading

Installer/icon.png

108 Bytes
Loading

UnityLauncherPro/MainWindow.xaml.cs

+59-25
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,28 @@ public partial class MainWindow : Window
4343
public static readonly string projectNameFile = "ProjectName.txt";
4444
public static string preferredVersion = null;
4545
public static int projectNameSetting = 0; // 0 = folder or ProjectName.txt if exists, 1=ProductName
46-
public static readonly string initScriptFileFullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Scripts", "InitializeProject.cs");
46+
47+
// required setup for exe and msix builds
48+
public static string InitScriptFileFullPath
49+
{
50+
get
51+
{
52+
string basePath;
53+
if (AppDomain.CurrentDomain.BaseDirectory.Contains(@"\WindowsApps\"))
54+
{
55+
// MSIX: Use user-writable folder
56+
basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "UnityLauncherPro", "Scripts");
57+
}
58+
else
59+
{
60+
// EXE: Use app folder
61+
basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Scripts");
62+
}
63+
// Ensure directory exists
64+
if (!Directory.Exists(basePath)) Directory.CreateDirectory(basePath);
65+
return Path.Combine(basePath, "InitializeProject.cs");
66+
}
67+
}
4768

4869
const string contextRegRoot = "Software\\Classes\\Directory\\Background\\shell";
4970
const string githubURL = "https://github.com/unitycoder/UnityLauncherPro";
@@ -2131,7 +2152,7 @@ void CreateNewEmptyProject(string targetFolder = null)
21312152
Console.WriteLine("Create project " + NewProject.newVersion + " : " + rootFolder);
21322153
if (string.IsNullOrEmpty(rootFolder)) return;
21332154

2134-
var p = Tools.FastCreateProject(NewProject.newVersion, rootFolder, NewProject.newProjectName, NewProject.templateZipPath, NewProject.platformsForThisUnity, NewProject.selectedPlatform, (bool)chkUseInitScript.IsChecked, initScriptFileFullPath, NewProject.forceDX11);
2155+
var p = Tools.FastCreateProject(NewProject.newVersion, rootFolder, NewProject.newProjectName, NewProject.templateZipPath, NewProject.platformsForThisUnity, NewProject.selectedPlatform, (bool)chkUseInitScript.IsChecked, InitScriptFileFullPath, NewProject.forceDX11);
21352156

21362157
// add to list (just in case new project fails to start, then folder is already generated..)
21372158
if (p != null) AddNewProjectToList(p);
@@ -2154,7 +2175,7 @@ void CreateNewEmptyProject(string targetFolder = null)
21542175
{
21552176
newVersion = GetSelectedUnity().Version == null ? preferredVersion : GetSelectedUnity().Version;
21562177
}
2157-
var p = Tools.FastCreateProject(newVersion, rootFolder, null, null, null, null, (bool)chkUseInitScript.IsChecked, initScriptFileFullPath);
2178+
var p = Tools.FastCreateProject(newVersion, rootFolder, null, null, null, null, (bool)chkUseInitScript.IsChecked, InitScriptFileFullPath);
21582179

21592180
if (p != null) AddNewProjectToList(p);
21602181
}
@@ -3472,16 +3493,31 @@ public int Compare(Object a, Object b)
34723493

34733494
private void btnExploreScriptsFolder_Click(object sender, RoutedEventArgs e)
34743495
{
3475-
if (Tools.LaunchExplorer(Path.GetDirectoryName(initScriptFileFullPath)) == false)
3496+
// handle exe vs msix locations
3497+
try
34763498
{
3477-
// if failed, open parent folder (probably path was using URL or no scripts yet)
3478-
var parentPath = Directory.GetParent(Path.GetDirectoryName(initScriptFileFullPath)).FullName;
3479-
if (Tools.LaunchExplorer(parentPath) == false)
3499+
string scriptFolder = Path.GetDirectoryName(InitScriptFileFullPath);
3500+
3501+
if (!Tools.LaunchExplorer(scriptFolder))
34803502
{
3481-
// if still failed, open exe folder
3482-
Tools.LaunchExplorer(AppDomain.CurrentDomain.BaseDirectory);
3503+
// fallback: try parent of script folder
3504+
string parentPath = Directory.GetParent(scriptFolder)?.FullName;
3505+
3506+
if (string.IsNullOrEmpty(parentPath) || !Tools.LaunchExplorer(parentPath))
3507+
{
3508+
// final fallback: show %LOCALAPPDATA% for MSIX, or exe dir for EXE builds
3509+
string fallback = AppDomain.CurrentDomain.BaseDirectory.Contains(@"\WindowsApps\")
3510+
? Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
3511+
: AppDomain.CurrentDomain.BaseDirectory;
3512+
3513+
Tools.LaunchExplorer(fallback);
3514+
}
34833515
}
34843516
}
3517+
catch (Exception ex)
3518+
{
3519+
Console.WriteLine("Failed to open folder: " + ex.Message);
3520+
}
34853521
}
34863522

34873523
private void txtCustomInitFileURL_PreviewKeyDown(object sender, KeyEventArgs e)
@@ -3823,7 +3859,7 @@ private void tabControl_PreviewKeyDown(object sender, KeyEventArgs e)
38233859

38243860
private void btnFetchLatestInitScript_Click(object sender, RoutedEventArgs e)
38253861
{
3826-
Tools.DownloadInitScript(initScriptFileFullPath, txtCustomInitFileURL.Text);
3862+
Tools.DownloadInitScript(InitScriptFileFullPath, txtCustomInitFileURL.Text);
38273863
}
38283864

38293865
private void btnHubLogs_Click(object sender, RoutedEventArgs e)
@@ -3910,26 +3946,23 @@ private void OnPipeConnection(IAsyncResult result)
39103946

39113947
private void CheckCustomIcon()
39123948
{
3913-
string customIconPath = Path.Combine(Environment.CurrentDirectory, "icon.ico");
3914-
//Console.WriteLine(customIconPath);
3949+
// Use app-specific writable folder (same as used for themes/scripts)
3950+
string baseFolder = AppDomain.CurrentDomain.BaseDirectory.Contains(@"\WindowsApps\")
3951+
? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "UnityLauncherPro")
3952+
: AppDomain.CurrentDomain.BaseDirectory;
3953+
3954+
string customIconPath = Path.Combine(baseFolder, "icon.ico");
39153955

39163956
if (File.Exists(customIconPath))
39173957
{
39183958
try
39193959
{
3920-
// Load the custom icon using System.Drawing.Icon
3921-
using (var icon = new System.Drawing.Icon(customIconPath))
3960+
using (var icon = new Icon(customIconPath))
39223961
{
3923-
// Convert the icon to a BitmapSource and assign it to the WPF window's Icon property
3924-
this.Icon = Imaging.CreateBitmapSourceFromHIcon(
3925-
icon.Handle,
3926-
Int32Rect.Empty,
3927-
BitmapSizeOptions.FromEmptyOptions() // Use BitmapSizeOptions here
3928-
);
3929-
3930-
// window icon
3962+
// Set WPF window icon
3963+
this.Icon = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
39313964
IconImage.Source = this.Icon;
3932-
// tray icon
3965+
// Tray icon
39333966
notifyIcon.Icon = new Icon(customIconPath);
39343967
}
39353968
}
@@ -3939,13 +3972,14 @@ private void CheckCustomIcon()
39393972
Debug.WriteLine($"Failed to load custom icon: {ex.Message}");
39403973
}
39413974
}
3942-
else // no custom icon found
3975+
else
39433976
{
3977+
// Fallback to default embedded icon
39443978
notifyIcon.Icon = new Icon(Application.GetResourceStream(new Uri("pack://application:,,,/Images/icon.ico")).Stream);
3945-
//Debug.WriteLine("Custom icon not found. Using default.");
39463979
}
39473980
}
39483981

3982+
39493983
private void chkCheckSRP_Checked(object sender, RoutedEventArgs e)
39503984
{
39513985
if (this.IsActive == false) return; // dont run code on window init

UnityLauncherPro/ThemeEditor.xaml.cs

+20-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ private void GridThemeColors_SelectionChanged(object sender, SelectionChangedEve
109109

110110
private void BtnSaveTheme_Click(object sender, RoutedEventArgs e)
111111
{
112-
var themeFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Themes");
112+
var themeFolder = GetThemeFolder();
113113

114114
if (Directory.Exists(themeFolder) == false) Directory.CreateDirectory(themeFolder);
115115

@@ -287,5 +287,24 @@ private void TxtColorField_PreviewKeyDown(object sender, KeyEventArgs e)
287287
break;
288288
}
289289
}
290+
291+
private string GetThemeFolder()
292+
{
293+
string exeDir = AppDomain.CurrentDomain.BaseDirectory;
294+
295+
// If running inside MSIX (installed to Program Files\WindowsApps), use local app data
296+
if (exeDir.Contains(@"\WindowsApps\"))
297+
{
298+
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
299+
return Path.Combine(localAppData, "UnityLauncherPro", "Themes");
300+
}
301+
else
302+
{
303+
// Standalone .exe build, use app folder
304+
return Path.Combine(exeDir, "Themes");
305+
}
306+
}
307+
308+
290309
} // class
291310
} // namespace

UnityLauncherPro/Tools.cs

+16
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,22 @@ public static async Task DownloadInitScript(string currentInitScriptFullPath, st
854854
}
855855
}
856856

857+
public static string GetInitScriptFolder()
858+
{
859+
string exeDir = AppDomain.CurrentDomain.BaseDirectory;
860+
861+
// Check for MSIX install
862+
if (exeDir.Contains(@"\WindowsApps\"))
863+
{
864+
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
865+
return Path.Combine(localAppData, "UnityLauncherPro", "Scripts");
866+
}
867+
else
868+
{
869+
return Path.Combine(exeDir, "Scripts");
870+
}
871+
}
872+
857873
static void DeleteTempFile(string path)
858874
{
859875
if (File.Exists(path) == true)

0 commit comments

Comments
 (0)