Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 66 additions & 25 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,27 @@ jobs:
- name: Build Windows
run: dotnet build Client/Simitone/Simitone.Windows/Simitone.Windows.csproj -c ${{ inputs.configuration || 'Release' }} --no-restore

- name: Build Windows Launcher (Native AOT)
run: |
dotnet publish Client/Simitone/Simitone.Launcher/Simitone.Launcher.csproj `
-c ${{ inputs.configuration || 'Release' }} `
-r win-x64 `
-o publish/launcher-win

- name: Create Windows release structure
shell: pwsh
run: |
$config = "${{ inputs.configuration || 'Release' }}"
$buildOutput = "Client/Simitone/Simitone.Windows/bin/$config/net9.0-windows"
New-Item -ItemType Directory -Path "release/Simitone-Windows/lib" -Force
Copy-Item -Path "publish/launcher-win/Simitone.exe" -Destination "release/Simitone-Windows/Simitone.exe"
Copy-Item -Path "$buildOutput/*" -Destination "release/Simitone-Windows/lib/" -Recurse

- name: Upload Windows artifacts
uses: actions/upload-artifact@v4
with:
name: Simitone-Windows-${{ inputs.configuration || 'Release' }}
path: Client/Simitone/Simitone.Windows/bin/${{ inputs.configuration || 'Release' }}/net9.0-windows/
path: release/Simitone-Windows/
if-no-files-found: error

build-linux:
Expand All @@ -91,21 +107,14 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libsdl2-2.0-0 libopenal1 libgtk-3-0 libgtk-3-dev libglib2.0-0 libcairo2 libpango-1.0-0 libgdk-pixbuf2.0-0 libatk1.0-0
sudo apt-get install -y libsdl2-2.0-0 libopenal1 libgtk-3-0 libgtk-3-dev libglib2.0-0 libcairo2 libpango-1.0-0 libgdk-pixbuf2.0-0 libatk1.0-0 imagemagick

- name: Restore dependencies
run: dotnet restore Client/Simitone/Simitone.Desktop/Simitone.Desktop.csproj

- name: Build Linux
run: dotnet build Client/Simitone/Simitone.Desktop/Simitone.Desktop.csproj -c ${{ inputs.configuration || 'Release' }} --no-restore

- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: Simitone-Linux-x64-${{ inputs.configuration || 'Release' }}
path: Client/Simitone/Simitone.Desktop/bin/${{ inputs.configuration || 'Release' }}/net9.0/
if-no-files-found: error

- name: Publish Linux (self-contained)
run: |
dotnet publish Client/Simitone/Simitone.Desktop/Simitone.Desktop.csproj \
Expand All @@ -116,12 +125,27 @@ jobs:
/p:TreatWarningsAsErrors=false \
/p:WarningsAsErrors=""

- name: Build Linux Launcher (Native C)
run: |
mkdir -p publish/launcher-native
gcc -O2 -s Client/Simitone/Simitone.Launcher/launcher.c -o publish/launcher-native/Simitone

- name: Upload Self-Contained Linux artifacts
- name: Create Linux release structure
run: |
mkdir -p release/Simitone-Linux-x64/lib
cp publish/launcher-native/Simitone release/Simitone-Linux-x64/Simitone
chmod +x release/Simitone-Linux-x64/Simitone
cp -r publish/linux-x64/* release/Simitone-Linux-x64/lib/
cp Client/Simitone/Simitone.Launcher/simitone.desktop release/Simitone-Linux-x64/
chmod +x release/Simitone-Linux-x64/simitone.desktop
# Convert icon to PNG
convert Client/Simitone/Simitone.Windows/Icon.ico -resize 256x256 release/Simitone-Linux-x64/lib/Resources/icon.png || true

- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: Simitone-SelfContained-Linux-x64-${{ inputs.configuration || 'Release' }}
path: publish/linux-x64/
name: Simitone-Linux-x64-${{ inputs.configuration || 'Release' }}
path: release/Simitone-Linux-x64/
if-no-files-found: error

build-macos:
Expand Down Expand Up @@ -150,13 +174,6 @@ jobs:
- name: Build macOS
run: dotnet build Client/Simitone/Simitone.Desktop/Simitone.Desktop.csproj -c ${{ inputs.configuration || 'Release' }} -r osx-x64 --no-restore /p:TreatWarningsAsErrors=false /p:WarningsAsErrors="" /p:NoWarn=NU1605

- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
name: Simitone-macOS-x64-${{ inputs.configuration || 'Release' }}
path: Client/Simitone/Simitone.Desktop/bin/${{ inputs.configuration || 'Release' }}/net9.0/
if-no-files-found: error

- name: Publish macOS Intel (self-contained)
run: |
dotnet publish Client/Simitone/Simitone.Desktop/Simitone.Desktop.csproj \
Expand All @@ -177,17 +194,41 @@ jobs:
/p:TreatWarningsAsErrors=false \
/p:WarningsAsErrors=""

- name: Upload Self-Contained macOS Intel artifacts
- name: Build macOS Launcher (Native C) for Intel
run: |
mkdir -p publish/launcher-native-x64
clang -O2 -target x86_64-apple-macos10.12 Client/Simitone/Simitone.Launcher/launcher.c -o publish/launcher-native-x64/Simitone

- name: Build macOS Launcher (Native C) for ARM
run: |
mkdir -p publish/launcher-native-arm64
clang -O2 -target arm64-apple-macos11 Client/Simitone/Simitone.Launcher/launcher.c -o publish/launcher-native-arm64/Simitone

- name: Create macOS Intel release structure
run: |
mkdir -p release/Simitone-macOS-x64/lib
cp publish/launcher-native-x64/Simitone release/Simitone-macOS-x64/Simitone
chmod +x release/Simitone-macOS-x64/Simitone
cp -r publish/osx-x64/* release/Simitone-macOS-x64/lib/

- name: Create macOS ARM release structure
run: |
mkdir -p release/Simitone-macOS-arm64/lib
cp publish/launcher-native-arm64/Simitone release/Simitone-macOS-arm64/Simitone
chmod +x release/Simitone-macOS-arm64/Simitone
cp -r publish/osx-arm64/* release/Simitone-macOS-arm64/lib/

- name: Upload macOS Intel artifacts
uses: actions/upload-artifact@v4
with:
name: Simitone-SelfContained-macOS-x64-${{ inputs.configuration || 'Release' }}
path: publish/osx-x64/
name: Simitone-macOS-x64-${{ inputs.configuration || 'Release' }}
path: release/Simitone-macOS-x64/
if-no-files-found: error

- name: Upload Self-Contained macOS ARM artifacts
- name: Upload macOS ARM artifacts
uses: actions/upload-artifact@v4
with:
name: Simitone-SelfContained-macOS-arm64-${{ inputs.configuration || 'Release' }}
path: publish/osx-arm64/
name: Simitone-macOS-arm64-${{ inputs.configuration || 'Release' }}
path: release/Simitone-macOS-arm64/
if-no-files-found: error

80 changes: 80 additions & 0 deletions Client/Simitone/Simitone.Launcher/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System.Diagnostics;
using System.Runtime.InteropServices;

// Get the directory where this launcher executable is located
string? exePath = Environment.ProcessPath;
if (string.IsNullOrEmpty(exePath))
{
Console.Error.WriteLine("Error: Could not determine launcher location.");
Environment.Exit(1);
}

string baseDir = Path.GetDirectoryName(exePath) ?? ".";
string libDir = Path.Combine(baseDir, "lib");

// Determine the target executable name based on platform
string targetExeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Simitone.exe" : "Simitone";
string targetExePath = Path.Combine(libDir, targetExeName);

// Verify the target executable exists
if (!File.Exists(targetExePath))
{
string message = $"Error: Cannot find Simitone executable.\nExpected location: {targetExePath}\n\nPlease ensure the 'lib' folder exists and contains the game files.";

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// On Windows, show a message box since we're a WinExe (no console)
ShowWindowsMessageBox(message, "Simitone Launcher Error");
}
else
{
Console.Error.WriteLine(message);
}
Environment.Exit(1);
}

// Set up the process to launch
var startInfo = new ProcessStartInfo(targetExePath)
{
WorkingDirectory = libDir,
UseShellExecute = false
};

// Forward all command-line arguments
foreach (var arg in args)
{
startInfo.ArgumentList.Add(arg);
}

try
{
using var process = Process.Start(startInfo);
// Exit immediately - don't wait for the game to close
}
catch (Exception ex)
{
string message = $"Error: Failed to launch Simitone.\n{ex.Message}";

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
ShowWindowsMessageBox(message, "Simitone Launcher Error");
}
else
{
Console.Error.WriteLine(message);
}
Environment.Exit(1);
}

// Windows MessageBox helper using P/Invoke
static void ShowWindowsMessageBox(string message, string title)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
MessageBox(IntPtr.Zero, message, title, 0x10); // MB_ICONERROR
}
}

// P/Invoke declaration for Windows MessageBox
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
23 changes: 23 additions & 0 deletions Client/Simitone/Simitone.Launcher/Simitone.Launcher.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Simitone</AssemblyName>

<!-- Native AOT for small, fast native executable -->
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<IlcOptimizationPreference>Size</IlcOptimizationPreference>

<!-- Windows-specific: use Windows subsystem (no console), include icon -->
<ApplicationIcon Condition="$([MSBuild]::IsOSPlatform('Windows'))">..\Simitone.Windows\Icon.ico</ApplicationIcon>

<!-- Trim unused code for smaller size -->
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode>
</PropertyGroup>

</Project>
96 changes: 96 additions & 0 deletions Client/Simitone/Simitone.Launcher/launcher.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Simitone Native Launcher for Linux/macOS
*
* This is a minimal native executable that launches the actual Simitone
* application from the lib/ subdirectory. This provides a clean user
* experience with a single executable at the top level.
*
* Compile with:
* Linux: gcc -O2 -s launcher.c -o Simitone
* macOS: clang -O2 launcher.c -o Simitone
*/

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <limits.h>
#include <errno.h>

#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif

/* Get the directory containing this executable */
static int get_exe_dir(char *buf, size_t size) {
#ifdef __APPLE__
uint32_t bufsize = (uint32_t)size;
if (_NSGetExecutablePath(buf, &bufsize) != 0) {
return -1;
}
/* Resolve symlinks and get real path */
char *real = realpath(buf, NULL);
if (real) {
strncpy(buf, real, size - 1);
buf[size - 1] = '\0';
free(real);
}
#else
/* Linux: read /proc/self/exe */
ssize_t len = readlink("/proc/self/exe", buf, size - 1);
if (len == -1) {
return -1;
}
buf[len] = '\0';
#endif

/* Get directory part */
char *dir = dirname(buf);
if (dir != buf) {
memmove(buf, dir, strlen(dir) + 1);
}
return 0;
}

int main(int argc, char *argv[]) {
char exe_dir[PATH_MAX];
char lib_dir[PATH_MAX];
char target_exe[PATH_MAX];

/* Get the directory where this launcher is located */
if (get_exe_dir(exe_dir, sizeof(exe_dir)) != 0) {
fprintf(stderr, "Error: Could not determine launcher location.\n");
return 1;
}

/* Build path to lib directory */
snprintf(lib_dir, sizeof(lib_dir), "%s/lib", exe_dir);

/* Build path to target executable */
snprintf(target_exe, sizeof(target_exe), "%s/lib/Simitone", exe_dir);

/* Check if target exists */
if (access(target_exe, X_OK) != 0) {
fprintf(stderr, "Error: Cannot find Simitone executable.\n");
fprintf(stderr, "Expected location: %s\n", target_exe);
fprintf(stderr, "\nPlease ensure the 'lib' folder exists and contains the game files.\n");
return 1;
}

/* Change to lib directory so relative paths work correctly */
if (chdir(lib_dir) != 0) {
fprintf(stderr, "Error: Could not change to lib directory: %s\n", strerror(errno));
return 1;
}

/* Replace this process with the actual Simitone executable */
/* argv[0] will be replaced with the target path */
argv[0] = target_exe;
execv(target_exe, argv);

/* If we get here, execv failed */
fprintf(stderr, "Error: Failed to launch Simitone: %s\n", strerror(errno));
return 1;
}
11 changes: 11 additions & 0 deletions Client/Simitone/Simitone.Launcher/simitone.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=Simitone
Comment=The Sims 1 recreation using FreeSO engine
Exec=./Simitone
Icon=./lib/Resources/icon.png
Terminal=false
Categories=Game;Simulation;
Keywords=sims;simulation;freeso;
StartupNotify=true
14 changes: 14 additions & 0 deletions Client/Simitone/Simitone.Windows/Simitone.Windows.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,18 @@
<Copy SourceFiles="@(MGDesktopGL)" DestinationFolder="$(OutputPath)Monogame\WindowsGL" SkipUnchangedFiles="true" />
</Target>

<!-- Exclude server executables and duplicate content from publish to avoid NETSDK1152 errors -->
<Target Name="RemoveConflictingPublishFiles" AfterTargets="ComputeFilesToPublish">
<ItemGroup>
<!-- Remove server executable outputs (watchdog, api.core) -->
<ResolvedFileToPublish Remove="@(ResolvedFileToPublish)"
Condition="$([System.String]::Copy('%(ResolvedFileToPublish.Identity)').Contains('FSO.Server.Updater')) or
$([System.String]::Copy('%(ResolvedFileToPublish.Identity)').Contains('FSO.Server.Api.Core'))" />
<!-- Remove duplicate TEX_1.png from tso.content (keep the one from Simitone.Client) -->
<ResolvedFileToPublish Remove="@(ResolvedFileToPublish)"
Condition="$([System.String]::Copy('%(ResolvedFileToPublish.Identity)').Contains('tso.content')) and
$([System.String]::Copy('%(ResolvedFileToPublish.Identity)').Contains('TEX_1.png'))" />
</ItemGroup>
</Target>

</Project>
Loading
Loading