Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fecf4223a | ||
|
|
2953ec44fb | ||
|
|
77399b4524 | ||
|
|
c0e2bb81e5 | ||
|
|
55ebcc1857 | ||
|
|
292ede8461 | ||
|
|
63ed8d7ca4 | ||
|
|
ce7c6f3802 | ||
|
|
0b478cab18 | ||
|
|
b7f5f24e6f | ||
|
|
150331d2e4 | ||
|
|
4d1aec9ed8 | ||
|
|
0cb325820b | ||
|
|
2a862b28be | ||
|
|
c2935f49e9 | ||
|
|
22043f8f38 | ||
|
|
3020a818f0 | ||
|
|
30177e8d7f | ||
|
|
3ad49838be | ||
|
|
3e480abd44 | ||
|
|
49f6b28aef | ||
|
|
b81d13b582 | ||
|
|
04eb3cb640 | ||
|
|
0ac75a088a | ||
|
|
cd652a72a1 | ||
|
|
828ff30dbf | ||
|
|
f452fe8a71 | ||
|
|
15e29a3b8a |
70
.github/workflows/dotnet-desktop.yml
vendored
70
.github/workflows/dotnet-desktop.yml
vendored
@@ -1,46 +1,80 @@
|
||||
name: Build & Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
pull_request:
|
||||
branches:
|
||||
- release/wf
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
build-release:
|
||||
if: ${{ github.event.pull_request.merged == true }}
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
PROJECT_NAME: SpineViewer
|
||||
VERSION: ${{ github.ref_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
with:
|
||||
fetch-tags: true
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Extract version from csproj
|
||||
shell: pwsh
|
||||
run: |
|
||||
[xml]$proj = Get-Content "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj"
|
||||
$VERSION_NUM = $proj.Project.PropertyGroup.Version
|
||||
$VERSION_TAG = "v$VERSION_NUM"
|
||||
"VERSION=$VERSION_TAG" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Check Version Tag
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (-not $env:VERSION) {
|
||||
Write-Error "Version tag not found in csproj file."
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Version tag found: $env:VERSION"
|
||||
|
||||
- name: Tag merge commit
|
||||
shell: pwsh
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag $env:VERSION
|
||||
git push --tags
|
||||
|
||||
- name: Publish FrameworkDependent version
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet publish ${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}.csproj -c Release -r win-x64 --sc false -p:PublishSingleFile=true -o publish/${{ env.PROJECT_NAME }}-${{ env.VERSION }}
|
||||
|
||||
dotnet publish "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj" -c Release -r win-x64 --sc false -o "publish\$env:PROJECT_NAME-$env:VERSION"
|
||||
|
||||
- name: Publish SelfContained version
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet publish ${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}.csproj -c Release -r win-x64 --sc true -p:PublishSingleFile=true -o publish/${{ env.PROJECT_NAME }}-${{ env.VERSION }}-SelfContained
|
||||
|
||||
dotnet publish "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj" -c Release -r win-x64 --sc true -o "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained"
|
||||
|
||||
- name: Create release directory
|
||||
run: mkdir release
|
||||
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Path release -Force | Out-Null
|
||||
|
||||
- name: Compress FrameworkDependent version
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path "publish/${env:PROJECT_NAME}-${env:VERSION}" -DestinationPath "release/${env:PROJECT_NAME}-${env:VERSION}.zip" -Force
|
||||
|
||||
Compress-Archive -Path "publish\$env:PROJECT_NAME-$env:VERSION\*" -DestinationPath "release\$env:PROJECT_NAME-$env:VERSION.zip" -Force
|
||||
|
||||
- name: Compress SelfContained version
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path "publish/${env:PROJECT_NAME}-${env:VERSION}-SelfContained" -DestinationPath "release/${env:PROJECT_NAME}-${env:VERSION}-SelfContained.zip" -Force
|
||||
|
||||
Compress-Archive -Path "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained\*" -DestinationPath "release\$env:PROJECT_NAME-$env:VERSION-SelfContained.zip" -Force
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
@@ -51,7 +85,7 @@ jobs:
|
||||
release_name: Release ${{ env.VERSION }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
|
||||
- name: Upload FrameworkDependent zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
@@ -61,7 +95,7 @@ jobs:
|
||||
asset_path: release/${{ env.PROJECT_NAME }}-${{ env.VERSION }}.zip
|
||||
asset_name: ${{ env.PROJECT_NAME }}-${{ env.VERSION }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
|
||||
- name: Upload SelfContained zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
|
||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,5 +1,25 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.12.17
|
||||
|
||||
- 动画边界检测的帧率提高到100帧每秒
|
||||
|
||||
## v0.12.16
|
||||
|
||||
- 导出多个时允许自动时长
|
||||
|
||||
## v0.12.15
|
||||
|
||||
- 修复附件类型枚举量字符串大小写问题
|
||||
|
||||
## v0.12.14
|
||||
|
||||
- 修复 v38 文件读取的小 bug
|
||||
|
||||
## v0.12.13
|
||||
|
||||
- 导出文件名增加额外的随机字符串
|
||||
|
||||
## v0.12.12
|
||||
|
||||
- 修复 2.1 版本遗漏的 SkinnedMeshAttachment 附件渲染
|
||||
|
||||
@@ -51,6 +51,17 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
[RotateMode.ChainScale] = "chainScale",
|
||||
};
|
||||
|
||||
private static readonly Dictionary<AttachmentType, string> AttachmentTypeJsonValue = new()
|
||||
{
|
||||
[AttachmentType.Region] = "region",
|
||||
[AttachmentType.Boundingbox] = "bounding",
|
||||
[AttachmentType.Mesh] = "mesh",
|
||||
[AttachmentType.Linkedmesh] = "linkedmesh",
|
||||
[AttachmentType.Path] = "path",
|
||||
[AttachmentType.Point] = "point",
|
||||
[AttachmentType.Clipping] = "clipping",
|
||||
};
|
||||
|
||||
private BinaryReader reader = null;
|
||||
private JsonObject root = null;
|
||||
private bool nonessential = false;
|
||||
@@ -298,7 +309,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
var name = reader.ReadStringRef() ?? keyName;
|
||||
var type = (AttachmentType)reader.ReadByte();
|
||||
attachment["name"] = name;
|
||||
attachment["type"] = type.ToString();
|
||||
attachment["type"] = AttachmentTypeJsonValue[type];
|
||||
switch (type)
|
||||
{
|
||||
case AttachmentType.Region:
|
||||
@@ -586,7 +597,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
["compress"] = reader.ReadBoolean(),
|
||||
["stretch"] = reader.ReadBoolean(),
|
||||
};
|
||||
if (frameCount > 1) ReadCurve(o);
|
||||
if (frameIdx < frameCount - 1) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
}
|
||||
@@ -613,7 +624,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
["scaleMix"] = reader.ReadFloat(),
|
||||
["shearMix"] = reader.ReadFloat(),
|
||||
};
|
||||
if (frameCount > 1) ReadCurve(o);
|
||||
if (frameIdx < frameCount - 1) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,9 +52,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
private static readonly Dictionary<AttachmentType, string> AttachmentTypeJsonValue = new()
|
||||
{
|
||||
[AttachmentType.Region] = "region",
|
||||
[AttachmentType.Boundingbox] = "boundingBox",
|
||||
[AttachmentType.Boundingbox] = "boundingbox",
|
||||
[AttachmentType.Mesh] = "mesh",
|
||||
[AttachmentType.Linkedmesh] = "linkedMesh",
|
||||
[AttachmentType.Linkedmesh] = "linkedmesh",
|
||||
[AttachmentType.Path] = "path",
|
||||
[AttachmentType.Point] = "point",
|
||||
[AttachmentType.Clipping] = "clipping",
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
[AttachmentType.Region] = "region",
|
||||
[AttachmentType.Boundingbox] = "bounding",
|
||||
[AttachmentType.Mesh] = "mesh",
|
||||
[AttachmentType.Linkedmesh] = "linkedMesh",
|
||||
[AttachmentType.Linkedmesh] = "linkedmesh",
|
||||
[AttachmentType.Path] = "path",
|
||||
[AttachmentType.Point] = "point",
|
||||
[AttachmentType.Clipping] = "clipping",
|
||||
@@ -1069,14 +1069,14 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
};
|
||||
ReadCurve(frame, 1);
|
||||
frame = o;
|
||||
end = reader.ReadVarInt();
|
||||
if (end > 0)
|
||||
{
|
||||
frame["offset"] = reader.ReadVarInt();
|
||||
frame["vertices"] = ReadFloatArray(end);
|
||||
}
|
||||
ReadCurve(frame, 1);
|
||||
frame = o;
|
||||
frames.Add(frame);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -222,10 +222,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
// 按 100 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
|
||||
@@ -222,10 +222,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
// 按 100 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
|
||||
@@ -219,10 +219,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
// 按 100 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
|
||||
@@ -217,10 +217,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
// 按 100 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
|
||||
@@ -216,10 +216,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
// 按 100 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
|
||||
@@ -216,10 +216,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
//tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
// 按 100 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
|
||||
@@ -216,10 +216,10 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform(Skeleton.Physics.Update);
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
// 按 100 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
for (float tick = 0, delta = 0.01f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
var noteSuffix = FileNameNoteSuffix;
|
||||
if (!string.IsNullOrWhiteSpace(noteSuffix)) noteSuffix = $"_{noteSuffix}";
|
||||
|
||||
var filename = $"ffmpeg_{timestamp}_{FPS:f0}{noteSuffix}{Suffix}";
|
||||
var filename = $"ffmpeg_{timestamp}_{Guid.NewGuid().ToString()[..6]}_{FPS:f0}{noteSuffix}{Suffix}";
|
||||
|
||||
// 导出单个时必定提供输出文件夹
|
||||
var savePath = Path.Combine(OutputDir, filename);
|
||||
@@ -86,7 +86,7 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
{
|
||||
if (worker?.CancellationPending == true) break; // 取消的日志在 GetFrames 里输出
|
||||
|
||||
var filename = $"{spine.Name}_{timestamp}_{FPS:f0}{noteSuffix}{Suffix}";
|
||||
var filename = $"{spine.Name}_{timestamp}_{spine.ID[..6]}_{FPS:f0}{noteSuffix}{Suffix}";
|
||||
|
||||
// 如果提供了输出文件夹, 则全部导出到输出文件夹, 否则导出到各自的文件夹下
|
||||
var savePath = Path.Combine(OutputDir ?? spine.AssetsDir, filename);
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
protected override void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
|
||||
{
|
||||
// 导出单个时必定提供输出文件夹
|
||||
var filename = $"frame_{timestamp}{ImageFormat.GetSuffix()}";
|
||||
var filename = $"frame_{timestamp}_{Guid.NewGuid().ToString()[..6]}{ImageFormat.GetSuffix()}";
|
||||
var savePath = Path.Combine(OutputDir, filename);
|
||||
|
||||
worker?.ReportProgress(0, $"{Properties.Resources.process} 0/1");
|
||||
@@ -78,7 +78,7 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
var spine = spinesToRender[i];
|
||||
|
||||
// 逐个导出时如果提供了输出文件夹, 则全部导出到输出文件夹, 否则输出到各自的文件夹
|
||||
var filename = $"{spine.Name}_{timestamp}{ImageFormat.GetSuffix()}";
|
||||
var filename = $"{spine.Name}_{timestamp}_{spine.ID[..6]}{ImageFormat.GetSuffix()}";
|
||||
var savePath = Path.Combine(OutputDir ?? spine.AssetsDir, filename);
|
||||
|
||||
try
|
||||
|
||||
@@ -22,14 +22,16 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
|
||||
protected override void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
|
||||
{
|
||||
var uniqueSuffix = Guid.NewGuid().ToString()[..6];
|
||||
|
||||
// 导出单个时必定提供输出文件夹,
|
||||
var saveDir = Path.Combine(OutputDir, $"frames_{timestamp}_{FPS:f0}");
|
||||
var saveDir = Path.Combine(OutputDir, $"frames_{timestamp}_{uniqueSuffix}_{FPS:f0}");
|
||||
Directory.CreateDirectory(saveDir);
|
||||
|
||||
int frameIdx = 0;
|
||||
foreach (var frame in GetFrames(spinesToRender, worker))
|
||||
{
|
||||
var filename = $"frames_{timestamp}_{FPS:f0}_{frameIdx:d6}{Suffix}";
|
||||
var filename = $"frames_{timestamp}_{uniqueSuffix}_{FPS:f0}_{frameIdx:d6}{Suffix}";
|
||||
var savePath = Path.Combine(saveDir, filename);
|
||||
|
||||
try
|
||||
@@ -56,14 +58,14 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
if (worker?.CancellationPending == true) break; // 取消的日志在 GetFrames 里输出
|
||||
|
||||
// 如果提供了输出文件夹, 则全部导出到输出文件夹, 否则导出到各自的文件夹下
|
||||
var subDir = $"{spine.Name}_{timestamp}_{FPS:f0}";
|
||||
var subDir = $"{spine.Name}_{timestamp}_{spine.ID[..6]}_{FPS:f0}";
|
||||
var saveDir = Path.Combine(OutputDir ?? spine.AssetsDir, subDir);
|
||||
Directory.CreateDirectory(saveDir);
|
||||
|
||||
int frameIdx = 0;
|
||||
foreach (var frame in GetFrames(spine, worker))
|
||||
{
|
||||
var filename = $"{spine.Name}_{timestamp}_{FPS:f0}_{frameIdx:d6}{Suffix}";
|
||||
var filename = $"{spine.Name}_{timestamp}_{spine.ID[..6]}_{FPS:f0}_{frameIdx:d6}{Suffix}";
|
||||
var savePath = Path.Combine(saveDir, filename);
|
||||
|
||||
try
|
||||
|
||||
@@ -30,15 +30,6 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
/// </summary>
|
||||
public bool KeepLast { get; set; } = true;
|
||||
|
||||
public override string? Validate()
|
||||
{
|
||||
if (base.Validate() is string error)
|
||||
return error;
|
||||
if (IsExportSingle && Duration < 0)
|
||||
return Properties.Resources.negativeDuration;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成单个模型的帧序列
|
||||
/// </summary>
|
||||
@@ -93,8 +84,16 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
/// </summary>
|
||||
protected IEnumerable<SFMLImageVideoFrame> GetFrames(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
|
||||
{
|
||||
// 导出单个时必须根据 Duration 决定导出时长
|
||||
// 导出单个时取所有模型的所有轨道时长最大值
|
||||
var duration = Duration;
|
||||
if (duration < 0)
|
||||
{
|
||||
duration = spinesToRender.Select(
|
||||
sp => sp.GetTrackIndices().Select(
|
||||
i => sp.GetAnimationDuration(sp.GetAnimation(i))
|
||||
).DefaultIfEmpty(0).Max()
|
||||
).Max();
|
||||
}
|
||||
|
||||
float delta = 1f / FPS;
|
||||
int total = (int)(duration * FPS); // 完整帧的数量
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.12.12</Version>
|
||||
<Version>0.12.17</Version>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||
|
||||
Reference in New Issue
Block a user