Skip to content

Commit 6a479de

Browse files
committed
Hot reloading preview application for GTA:SA skins
1 parent e141525 commit 6a479de

14 files changed

+1094
-0
lines changed

RenderWarePreviewer.sln

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.1.32421.90
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderWarePreviewer", "RenderWarePreviewer\RenderWarePreviewer.csproj", "{458151D7-52A5-4318-9FE1-6F5911EFEA05}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{458151D7-52A5-4318-9FE1-6F5911EFEA05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{458151D7-52A5-4318-9FE1-6F5911EFEA05}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{458151D7-52A5-4318-9FE1-6F5911EFEA05}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{458151D7-52A5-4318-9FE1-6F5911EFEA05}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {8118437E-82CA-44D9-B987-EAA0688BCB51}
24+
EndGlobalSection
25+
EndGlobal

RenderWarePreviewer/App.xaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Application x:Class="RenderWarePreviewer.App"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:local="clr-namespace:RenderWarePreviewer"
5+
StartupUri="MainWindow.xaml">
6+
<Application.Resources>
7+
8+
</Application.Resources>
9+
</Application>

RenderWarePreviewer/App.xaml.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Configuration;
4+
using System.Data;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using System.Windows;
8+
9+
namespace RenderWarePreviewer
10+
{
11+
/// <summary>
12+
/// Interaction logic for App.xaml
13+
/// </summary>
14+
public partial class App : Application
15+
{
16+
}
17+
}

RenderWarePreviewer/AssemblyInfo.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Windows;
2+
3+
[assembly: ThemeInfo(
4+
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5+
//(used if a resource is not found in the page,
6+
// or application resource dictionaries)
7+
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8+
//(used if a resource is not found in the page,
9+
// app, or any theme specific resource dictionaries)
10+
)]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Numerics;
2+
using System.Windows.Media.Media3D;
3+
4+
namespace RenderWarePreviewer.Extensions
5+
{
6+
public static class VectorExtensions
7+
{
8+
public static Vector3 ToVector3(this Vector3D value) => new Vector3((float)value.X, (float)value.Y, (float)value.Z);
9+
public static Vector3D ToVector3D(this Vector3 value) => new Vector3D(value.X, value.Y, value.Z);
10+
11+
public static Vector3 ToVector3(this Point3D value) => new Vector3((float)value.X, (float)value.Y, (float)value.Z);
12+
public static Point3D ToPoint3D(this Vector3 value) => new Point3D(value.X, value.Y, value.Z);
13+
}
14+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
using BCnEncoder.Decoder;
2+
using BCnEncoder.ImageSharp;
3+
using RenderWareIo;
4+
using RenderWareIo.Structs.Dff;
5+
using RenderWareIo.Structs.Ide;
6+
using RenderWareIo.Structs.Img;
7+
using RenderWareIo.Structs.Txd;
8+
using SixLabors.ImageSharp;
9+
using SixLabors.ImageSharp.PixelFormats;
10+
using System.Collections.Generic;
11+
using System.IO;
12+
using System.Linq;
13+
14+
namespace RenderWarePreviewer.Helpers
15+
{
16+
public class AssetHelper
17+
{
18+
private string? gtaPath;
19+
private ImgFile? img;
20+
21+
public AssetHelper()
22+
{
23+
24+
}
25+
26+
public void SetGtaPath(string path)
27+
{
28+
var imgPath = Path.Join(path, "models", "gta3.img");
29+
if (!File.Exists(imgPath))
30+
throw new FileNotFoundException($"No img file found at {path}");
31+
32+
if (this.img != null)
33+
this.img.Dispose();
34+
35+
this.gtaPath = path;
36+
this.img = new ImgFile(imgPath);
37+
}
38+
39+
public Dff GetDff(string name)
40+
{
41+
if (this.img == null)
42+
throw new FileNotFoundException("No img file found");
43+
44+
var key = SanitizeName(name) + ".dff";
45+
if (!this.img.Img.DataEntries.ContainsKey(key))
46+
throw new FileNotFoundException($"No {name} file found in txd");
47+
48+
var data = this.img.Img.DataEntries[key];
49+
var stream = GetReadStream(data);
50+
51+
var dff = new Dff();
52+
dff.Read(stream);
53+
54+
return dff;
55+
}
56+
57+
public Txd GetTxd(string name)
58+
{
59+
if (this.img == null)
60+
throw new FileNotFoundException("No img file found");
61+
62+
var key = SanitizeName(name) + ".txd";
63+
if (!this.img.Img.DataEntries.ContainsKey(key))
64+
throw new FileNotFoundException($"No {name} file found in txd");
65+
66+
var data = this.img.Img.DataEntries[key];
67+
var stream = GetReadStream(data);
68+
69+
var txd = new Txd();
70+
txd.Read(stream);
71+
72+
return txd;
73+
}
74+
75+
public Dictionary<string, Image<Rgba32>> GetImages(Txd txd)
76+
{
77+
var images = new Dictionary<string, Image<Rgba32>>();
78+
79+
foreach (var texture in txd.TextureContainer.Textures)
80+
{
81+
string sanitizedName = SanitizeName(texture.Data.TextureName);
82+
string format = texture.Data.TextureFormatString;
83+
84+
Image<Rgba32> rgbaImage = new(texture.Data.Width, texture.Data.Height);
85+
86+
switch (format)
87+
{
88+
case "RGB32":
89+
{
90+
var image = Image.LoadPixelData<Bgra32>(texture.Data.Data, texture.Data.Width, texture.Data.Height);
91+
rgbaImage = image.CloneAs<Rgba32>();
92+
image.Dispose();
93+
break;
94+
}
95+
case "RGBA32":
96+
{
97+
var image = Image.LoadPixelData<Rgba32>(texture.Data.Data, texture.Data.Width, texture.Data.Height);
98+
rgbaImage = image;
99+
break;
100+
}
101+
case "DXT1":
102+
{
103+
BcDecoder decoder = new();
104+
Image<Rgba32> image = decoder.DecodeRawToImageRgba32(texture.Data.Data, texture.Data.Width, texture.Data.Height, BCnEncoder.Shared.CompressionFormat.Bc1);
105+
rgbaImage = image;
106+
break;
107+
}
108+
}
109+
110+
images[sanitizedName] = rgbaImage;
111+
}
112+
113+
return images;
114+
}
115+
116+
public IEnumerable<Ped> GetSkins()
117+
{
118+
var ide = new IdeFile(Path.Join(this.gtaPath, "data", "peds.ide"));
119+
return ide.Ide.Peds.Where(x => x.Id > 0);
120+
}
121+
122+
public string SanitizeName(string name)
123+
{
124+
return name.Replace("_", "").Trim('\0').ToLower();
125+
}
126+
127+
private Stream GetReadStream(DataEntry data)
128+
{
129+
var stream = new MemoryStream();
130+
stream.Write(data.Data);
131+
stream.Position = 0;
132+
return stream;
133+
}
134+
}
135+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using Microsoft.Win32;
2+
using System;
3+
using System.IO;
4+
using System.Runtime.InteropServices;
5+
6+
namespace RenderWarePreviewer.Helpers
7+
{
8+
public static class GtaDirectoryHelper
9+
{
10+
private static readonly string configDirectory = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "RenderWarePreviewer");
11+
private static readonly string gtaPathFile = Path.Join(configDirectory, "gtapath.txt");
12+
13+
public static string? GetGtaDirectory()
14+
{
15+
string? gtaDirectory = GetGtaDirectoryFromFiles();
16+
17+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && gtaDirectory == null)
18+
{
19+
using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Multi Theft Auto: San Andreas All\Common");
20+
gtaDirectory = key?.GetValue("GTA:SA Path")?.ToString();
21+
}
22+
23+
return gtaDirectory;
24+
}
25+
26+
private static string? GetGtaDirectoryFromFiles()
27+
{
28+
if (File.Exists(gtaPathFile))
29+
return File.ReadAllText(gtaPathFile);
30+
31+
return null;
32+
}
33+
34+
public static void StoreGtaDirectory(string path)
35+
{
36+
if (!Directory.Exists(configDirectory))
37+
Directory.CreateDirectory(configDirectory);
38+
39+
File.WriteAllText(gtaPathFile, path);
40+
}
41+
}
42+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
using RenderWareIo.Structs.Dff;
2+
using SixLabors.ImageSharp.PixelFormats;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Runtime.InteropServices;
7+
using System.Windows;
8+
using System.Windows.Media;
9+
using System.Windows.Media.Imaging;
10+
using System.Windows.Media.Media3D;
11+
12+
namespace RenderWarePreviewer.Helpers
13+
{
14+
public static class MeshHelper
15+
{
16+
private static Dictionary<string, MeshGeometry3D> pyramids = new();
17+
18+
public static GeometryModel3D GetModel(Dff dff, SixLabors.ImageSharp.Image<Rgba32>? image)
19+
{
20+
return GetModel(GetMesh(dff), image);
21+
}
22+
23+
public static GeometryModel3D GetModel(MeshGeometry3D mesh, SixLabors.ImageSharp.Image<Rgba32>? image)
24+
{
25+
return GetModel(mesh, GetMaterial(image));
26+
}
27+
28+
public static GeometryModel3D GetModel(MeshGeometry3D mesh, System.Windows.Media.Media3D.Material material)
29+
{
30+
return new GeometryModel3D
31+
{
32+
Geometry = mesh,
33+
Material = material
34+
};
35+
}
36+
37+
public static GeometryModel3D GetPyramid(System.Windows.Media.Color color, float scale)
38+
{
39+
var key = $"{color}{scale}";
40+
if (pyramids.ContainsKey(key))
41+
return GetModel(pyramids[key], GetMaterial(color));
42+
43+
var mesh = new MeshGeometry3D();
44+
foreach (var position in new Point3D[]
45+
{
46+
new Point3D(-scale, -scale, 0),
47+
new Point3D(scale, -scale, 0),
48+
new Point3D(scale, scale, 0),
49+
new Point3D(-scale, scale, 0),
50+
new Point3D(0, 0, scale),
51+
})
52+
mesh.Positions.Add(position);
53+
54+
foreach (var triangleIndex in new int[]
55+
{
56+
0, 1, 2,
57+
0, 2, 3,
58+
0, 1, 4,
59+
1, 2, 4,
60+
2, 3, 4,
61+
3, 0, 4
62+
})
63+
mesh.TriangleIndices.Add(triangleIndex);
64+
65+
foreach (var _ in mesh.Positions)
66+
mesh.Normals.Add(new Vector3D(0, 0, 1));
67+
68+
pyramids[key] = mesh;
69+
70+
return GetModel(mesh, GetMaterial(color));
71+
}
72+
73+
public static DiffuseMaterial GetMaterial(SixLabors.ImageSharp.Image<Rgba32>? image)
74+
{
75+
var material = new DiffuseMaterial
76+
{
77+
Color = System.Windows.Media.Color.FromArgb(255, 255, 255, 255)
78+
};
79+
if (image != null)
80+
{
81+
material.Brush = new ImageBrush(GetBitMap(image));
82+
}
83+
else
84+
{
85+
material.Brush = new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 150, 150, 150));
86+
}
87+
return material;
88+
}
89+
90+
public static DiffuseMaterial GetMaterial(System.Windows.Media.Color color)
91+
{
92+
var material = new DiffuseMaterial
93+
{
94+
Color = System.Windows.Media.Color.FromArgb(255, 255, 255, 255),
95+
Brush = new SolidColorBrush(color)
96+
};
97+
return material;
98+
}
99+
100+
public static MeshGeometry3D GetMesh(Dff dff)
101+
{
102+
var mesh = new MeshGeometry3D();
103+
foreach (var vertex in dff.Clump.GeometryList.Geometries.SelectMany(x => x.MorphTargets.SelectMany(y => y.Vertices)))
104+
{
105+
mesh.Positions.Add(new Point3D(vertex.X, vertex.Y, vertex.Z));
106+
}
107+
108+
foreach (var triangle in dff.Clump.GeometryList.Geometries.SelectMany(x => x.Triangles))
109+
{
110+
mesh.TriangleIndices.Add(triangle.VertexIndexThree);
111+
mesh.TriangleIndices.Add(triangle.VertexIndexTwo);
112+
mesh.TriangleIndices.Add(triangle.VertexIndexOne);
113+
}
114+
115+
foreach (var normal in dff.Clump.GeometryList.Geometries.SelectMany(x => x.MorphTargets.SelectMany(y => y.Normals)))
116+
{
117+
mesh.Normals.Add(new Vector3D(normal.X, normal.Y, normal.Z));
118+
}
119+
120+
foreach (var uv in dff.Clump.GeometryList.Geometries.SelectMany(x => x.TexCoords))
121+
{
122+
mesh.TextureCoordinates.Add(new Point(uv.X, uv.Y));
123+
}
124+
return mesh;
125+
}
126+
127+
private static BitmapSource GetBitMap(SixLabors.ImageSharp.Image<Rgba32> image)
128+
{
129+
Rgba32[] rgbaArray = new Rgba32[image.Width * image.Height];
130+
Span<Rgba32> span = new Span<Rgba32>(rgbaArray, 0, rgbaArray.Length);
131+
image.CopyPixelDataTo(span);
132+
var data = MemoryMarshal.AsBytes(span).ToArray();
133+
var bitmap = new RgbaBitmapSource(data, image.Width);
134+
//File.WriteAllBytes("output.rgba", data);
135+
136+
return bitmap;
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)