Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FSH/QFS editor #28

Merged
merged 9 commits into from
Apr 2, 2024
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
3 changes: 3 additions & 0 deletions BuildTargets/CompileOptions.targets
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<PropertyGroup>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
</ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions BuildTargets/GlobalDirectives.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(MSBuildProjectExtension)'=='.csproj'">
<ExtraDefineConstants>EnableFullFshFormat</ExtraDefineConstants>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Issues](https://img.shields.io/github/issues/TheXDS/Vivianne)](https://github.com/TheXDS/Vivianne/issues)
[![MIT](https://img.shields.io/github/license/TheXDS/Vivianne)](https://mit-license.org/)

Vivianne is a modern Need For Speed 3/4 All-in-one VIV/FSH/QFS editor that aims to provide you with tools to edit textures, car performance and fedata files inside VIVs, as well as serial number scan, FSH<->QFS conversion and Car re-classificator. Mod installer/manager planned as well.
Vivianne is a modern Need For Speed 3/4 All-in-one VIV and FSH/QFS editor that aims to provide you with tools to edit textures, car performance and fedata files inside VIVs, as well as serial number scan, FSH<->QFS conversion and Car re-classificator. Mod installer/manager planned as well.

> **WARNING: This is a very early, work in progress app. A lot of things will not work correctly.**

Expand Down
13 changes: 13 additions & 0 deletions docs/BUILDING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Building Vivianne
By defualt, Vivianne is being developed to support Need For Speed III. It doesn't mean however, that other games that may use the same file formats supported by Vivianne could not be edited. Such is the case for other versions of Need For Speed and other contemporary Electronic Arts games, such as Sim City.

## Extended FSH support
It's been observed that Need For Speed III uses a subset of all the available data formats supported by FSH files, using them exclusively for textures and some data related to car dashboards using the *Footer* of a texture.

Other games use varying pixel formats for textures; NFS3 uses almost exclusively 32-bit RGBA and 16-bit RGB-565 textures, with just a handful of 256 color textures with a palette. Some of the available formats observed inside FSH files for other Electronic Arts games include 24-bit color, 16-bit with alpha, varying palette color depths and DXT3/4 compressed textures. Some games don't just store texture data, and include binary and text entries in FSH files as well.

I don't know if Need For Speed III supports any of these additional FSH pixel formats (I could reasonably assume that it wouldn't support binary and text data in FSH files) or if it would be able to safely ignore anything that it could not use. Therefore, a compilation flag is required to enable support for these extra pixel and data formats.

To enable the extended FSH format support, either:
1. Edit the `./BuildTargets/GlobalDirectives.targets` file to include `EnableFullFshFormat` inside the `ExtraDefineConstants` tag
2. Include the `-p:ExtraDefineConstants=EnableFullFshFormat` parameter when building Vivianne from sources using `dotnet`.
9 changes: 9 additions & 0 deletions src/App/Vivianne.Wpf/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Windows;
using TheXDS.Ganymede.Helpers;
using TheXDS.Ganymede.Services;

namespace Vivianne.Wpf
{
Expand All @@ -7,5 +9,12 @@ namespace Vivianne.Wpf
/// </summary>
public partial class App : Application
{
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// </summary>
public App()
{
UiThread.SetProxy(new DispatcherUiThreadProxy());
}
}
}
68 changes: 68 additions & 0 deletions src/App/Vivianne.Wpf/Helpers/ScrollHookHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using TheXDS.MCART.Math;

namespace TheXDS.Vivianne.Helpers;

internal class ScrollHookHelper
{
private const double MouseWheelZoom = 0.125;
private readonly Border brdContent;
private readonly ScrollViewer scvContent;
private readonly RangeBase rngZoom;
private Point _scrollMousePoint;
private double _hOff = 1;
private double _vOff = 1;

public ScrollHookHelper(Border brdContent, ScrollViewer scvContent, RangeBase rngZoom)
{
this.brdContent = brdContent ?? throw new ArgumentNullException(nameof(brdContent));
this.scvContent = scvContent ?? throw new ArgumentNullException(nameof(scvContent));
this.rngZoom = rngZoom ?? throw new ArgumentNullException(nameof(rngZoom));
brdContent.MouseLeftButtonDown += Sv_MouseLeftButtonDown;
brdContent.PreviewMouseMove += Sv_PreviewMouseMove;
brdContent.PreviewMouseLeftButtonUp += Sv_PreviewMouseLeftButtonUp;
brdContent.PreviewMouseWheel += BrdContent_PreviewMouseWheel;
}

private void BrdContent_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
rngZoom.Value = (rngZoom.Value + (e.Delta > 0 ? MouseWheelZoom : -MouseWheelZoom)).Clamp(1.0, 10.0);
rngZoom.GetBindingExpression(RangeBase.ValueProperty).UpdateSource();
e.Handled = true;
}

private void Sv_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
brdContent.CaptureMouse();
_scrollMousePoint = e.GetPosition(scvContent);
_hOff = scvContent.HorizontalOffset;
_vOff = scvContent.VerticalOffset;
}

private void Sv_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (brdContent.IsMouseCaptured)
{
var newXOffset = _hOff + (_scrollMousePoint.X - e.GetPosition(scvContent).X);
var newYOffset = _vOff + (_scrollMousePoint.Y - e.GetPosition(scvContent).Y);
scvContent.ScrollToHorizontalOffset(newXOffset);
scvContent.ScrollToVerticalOffset(newYOffset);
}
}

private void Sv_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
brdContent.ReleaseMouseCapture();
}

~ScrollHookHelper()
{
brdContent.MouseLeftButtonDown -= Sv_MouseLeftButtonDown;
brdContent.PreviewMouseMove -= Sv_PreviewMouseMove;
brdContent.PreviewMouseLeftButtonUp -= Sv_PreviewMouseLeftButtonUp;
brdContent.PreviewMouseWheel -= BrdContent_PreviewMouseWheel;
}
}
1 change: 1 addition & 0 deletions src/App/Vivianne.Wpf/Resources/ControlTemplates.xaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/ControlTemplates/CurveEditor.xaml"/>
<ResourceDictionary Source="/Resources/ControlTemplates/CheckBox.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
6 changes: 6 additions & 0 deletions src/App/Vivianne.Wpf/Resources/ControlTemplates/CheckBox.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/ControlTemplates/CheckBox/ToggleSwitch.xaml"/>
<ResourceDictionary Source="/Resources/ControlTemplates/CheckBox/LedIndicator.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="LedIndicator" TargetType="{x:Type CheckBox}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="IsHitTestVisible" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<DockPanel
Background="Transparent"
x:Name="templateRoot"
SnapsToDevicePixels="True">
<DockPanel.Resources>
<Color x:Key="ledOnColor">#00ff00</Color>
<Color x:Key="ledOffColor">#004000</Color>
<Color x:Key="shadowColor">#404040</Color>
</DockPanel.Resources>
<Grid
Margin="4"
DockPanel.Dock="Right"
Height="8"
Width="8"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Ellipse x:Name="redLed">
<Ellipse.Stroke>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="{DynamicResource ledOffColor}"/>
<GradientStop Color="{DynamicResource shadowColor}" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Stroke>
<Ellipse.Fill>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="White"/>
<GradientStop Color="{DynamicResource ledOffColor}" Offset="0.5"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse
Visibility="Collapsed"
x:Name="greenLed">
<Ellipse.Stroke>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="{DynamicResource ledOnColor}"/>
<GradientStop Color="{DynamicResource shadowColor}" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Stroke>
<Ellipse.Fill>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="White"/>
<GradientStop Color="{DynamicResource ledOnColor}" Offset="0.5"/>
</LinearGradientBrush>
</Ellipse.Fill>
<Ellipse.Effect>
<DropShadowEffect
ShadowDepth="0"
BlurRadius="10"
Color="{DynamicResource ledOnColor}">
</DropShadowEffect>
</Ellipse.Effect>
</Ellipse>
</Grid>
<ContentPresenter
x:Name="contentPresenter"
Focusable="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Visibility" TargetName="redLed" Value="Collapsed"/>
<Setter Property="Visibility" TargetName="greenLed" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="ToggleSwitch" TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<DockPanel x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True">
<Grid x:Name="visualSwitch" Height="16" Width="32" DockPanel.Dock="right">
<Rectangle RadiusX="8" RadiusY="8">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="#80808080"/>
<GradientStop Color="Transparent" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle RadiusX="8" RadiusY="8">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="#0000f0"/>
<GradientStop Color="#00d0ff" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
<Rectangle.OpacityMask>
<VisualBrush Stretch="UniformToFill" ViewboxUnits="Absolute" ViewportUnits="RelativeToBoundingBox">
<VisualBrush.Visual >
<Rectangle x:Name="rctFill" Width="0.25" Height="1" Fill="black"/>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.OpacityMask>
</Rectangle>
<Ellipse x:Name="thumb" Width="16" Fill="white" HorizontalAlignment="Left">
<Ellipse.Stroke>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="#f0f0f0"/>
<GradientStop Color="#808080" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Stroke>
<Ellipse.RenderTransform>
<TranslateTransform x:Name="htrans" X="0"/>
</Ellipse.RenderTransform>
</Ellipse>
</Grid>
<ContentPresenter
x:Name="contentPresenter"
Focusable="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Trigger.EnterActions>
<StopStoryboard BeginStoryboardName="disableSwitchAnimation"/>
<BeginStoryboard x:Name="enableSwitchAnimation">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="rctFill"
Storyboard.TargetProperty="Width"
Duration="0:0:0.1" From="0.25" To="0.75"/>
<DoubleAnimation
Storyboard.TargetName="thumb"
Storyboard.TargetProperty="(Ellipse.RenderTransform).(TranslateTransform.X)"
Duration="0:0:0.1" From="0" To="16"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="enableSwitchAnimation"/>
<BeginStoryboard x:Name="disableSwitchAnimation">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="rctFill"
Storyboard.TargetProperty="Width"
Duration="0:0:0.1" From="0.75" To="0.25"/>
<DoubleAnimation
Storyboard.TargetName="thumb"
Storyboard.TargetProperty="(Ellipse.RenderTransform).(TranslateTransform.X)"
Duration="0:0:0.1" From="16" To="0"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" TargetName="visualSwitch" Value="0.5"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Loading