libragephoto: add ragephoto .NET Package

This commit is contained in:
Syping 2025-10-07 12:29:53 +02:00
parent 3b8b641465
commit f492fc9b93
7 changed files with 436 additions and 0 deletions

4
.gitattributes vendored
View file

@ -13,7 +13,11 @@ CMakeLists.txt text eol=lf
*.pc.in text eol=lf
# Windows development files
*.cs text eol=crlf
*.csproj text eol=crlf
*.rc.in text encoding=cp1252 eol=crlf
*.resx text eol=crlf
*.sln text eol=crlf
# Documentation files
Doxyfile.in text eol=lf

312
src/dotnet/RagePhoto.cs Normal file
View file

@ -0,0 +1,312 @@
using System;
using System.Runtime.InteropServices;
namespace Syping.RagePhoto {
public class Photo : IDisposable {
private bool _disposed;
private readonly IntPtr _instance;
private const String _library = "libragephoto";
[DllImport(_library)]
private static extern IntPtr ragephoto_open();
[DllImport(_library)]
private static extern void ragephoto_clear(IntPtr instance);
[DllImport(_library)]
private static extern void ragephoto_close(IntPtr instance);
[DllImport(_library)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ragephoto_load(IntPtr instance, Byte[] data, UIntPtr size);
[DllImport(_library)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ragephoto_loadfile(IntPtr instance, [MarshalAs(UnmanagedType.LPUTF8Str)] String filename);
[DllImport(_library)]
private static extern Int32 ragephoto_error(IntPtr instance);
[DllImport(_library)]
private static extern IntPtr ragephoto_getphotodata(IntPtr instance);
[DllImport(_library)]
private static extern IntPtr ragephoto_getphotodesc(IntPtr instance);
[DllImport(_library)]
private static extern UInt32 ragephoto_getphotoformat(IntPtr instance);
[DllImport(_library)]
private static extern IntPtr ragephoto_getphotojpeg(IntPtr instance);
[DllImport(_library)]
private static extern IntPtr ragephoto_getphotojson(IntPtr instance);
[DllImport(_library)]
private static extern IntPtr ragephoto_getphotoheader(IntPtr instance);
[DllImport(_library)]
private static extern UInt64 ragephoto_getphotosign(IntPtr instance);
[DllImport(_library)]
private static extern UInt32 ragephoto_getphotosize(IntPtr instance);
[DllImport(_library)]
private static extern IntPtr ragephoto_getphototitle(IntPtr instance);
[DllImport(_library)]
private static extern UIntPtr ragephoto_getsavesize(IntPtr instance);
[DllImport(_library)]
private static extern UIntPtr ragephoto_getsavesizef(IntPtr instance, UInt32 photoFormat);
[DllImport(_library)]
private static extern bool ragephoto_save(IntPtr instance, [Out] Byte[] data);
[DllImport(_library)]
private static extern bool ragephoto_savef(IntPtr instance, [Out] Byte[] data, UInt32 photoFormat);
[DllImport(_library)]
private static extern void ragephoto_setbufferdefault(IntPtr instance);
[DllImport(_library)]
private static extern void ragephoto_setbufferoffsets(IntPtr instance);
[DllImport(_library)]
private static extern void ragephoto_setphotodesc(IntPtr instance, [MarshalAs(UnmanagedType.LPUTF8Str)] String description, UInt32 bufferSize);
[DllImport(_library)]
private static extern void ragephoto_setphotoformat(IntPtr instance, UInt32 photoFormat);
[DllImport(_library)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ragephoto_setphotojpeg(IntPtr instance, Byte[] jpeg, UInt32 size, UInt32 bufferSize);
[DllImport(_library)]
private static extern void ragephoto_setphotojson(IntPtr instance, [MarshalAs(UnmanagedType.LPUTF8Str)] String json, UInt32 bufferSize);
[DllImport(_library)]
private static extern void ragephoto_setphotoheader2(IntPtr instance, [MarshalAs(UnmanagedType.LPUTF8Str)] String header, UInt32 headerSum, UInt32 headerSum2);
[DllImport(_library)]
private static extern void ragephoto_setphototitle(IntPtr instance, [MarshalAs(UnmanagedType.LPUTF8Str)] String title, UInt32 bufferSize);
[DllImport(_library)]
[return: MarshalAs(UnmanagedType.LPStr)]
private static extern String ragephoto_version();
public enum DefaultSize : UInt32 {
DEFAULT_GTA5_PHOTOBUFFER = 524288U,
DEFAULT_RDR2_PHOTOBUFFER = 1048576U,
DEFAULT_DESCBUFFER = 256U,
DEFAULT_JSONBUFFER = 3072U,
DEFAULT_TITLBUFFER = 256U,
GTA5_HEADERSIZE = 264U,
RDR2_HEADERSIZE = 272U
}
public enum PhotoError : Int32 {
DescBufferTight = 39,
DescMallocError = 31,
DescReadError = 32,
HeaderBufferTight = 35,
HeaderMallocError = 4,
IncompatibleFormat = 2,
IncompleteChecksum = 7,
IncompleteDescBuffer = 30,
IncompleteDescMarker = 28,
IncompleteDescOffset = 11,
IncompleteEOF = 8,
IncompleteHeader = 3,
IncompleteJendMarker = 33,
IncompleteJpegMarker = 12,
IncompleteJsonBuffer = 20,
IncompleteJsonMarker = 18,
IncompleteJsonOffset = 9,
IncompletePhotoBuffer = 14,
IncompletePhotoSize = 15,
IncompleteTitleBuffer = 25,
IncompleteTitleMarker = 23,
IncompleteTitleOffset = 10,
IncorrectDescMarker = 29,
IncorrectJendMarker = 34,
IncorrectJpegMarker = 13,
IncorrectJsonMarker = 19,
IncorrectTitleMarker = 24,
JsonBufferTight = 37,
JsonMallocError = 21,
JsonReadError = 22,
NoError = 255,
NoFormatIdentifier = 1,
PhotoBufferTight = 36,
PhotoMallocError = 16,
PhotoReadError = 17,
TitleBufferTight = 38,
TitleMallocError = 26,
TitleReadError = 27,
UnicodeInitError = 5,
UnicodeHeaderError = 6,
Uninitialised = 0
}
public enum PhotoFormat : UInt32 {
GTA5 = 0x01000000U,
RDR2 = 0x04000000U
}
public Photo() {
try {
_instance = ragephoto_open();
if (_instance == IntPtr.Zero)
throw new RagePhotoException(this, "Failed to initialize libragephoto");
}
catch (Exception exception) {
throw new RagePhotoException(this, "Failed to initialize libragephoto", exception);
}
}
~Photo() {
Dispose(false);
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (_disposed)
return;
ragephoto_close(_instance);
_disposed = true;
}
public void Clear() {
ragephoto_clear(_instance);
}
public void Load(Byte[] data) {
UIntPtr size = (UIntPtr)data.LongLength;
bool result = ragephoto_load(_instance, data, size);
if (!result)
throw new RagePhotoException(this, string.Format("Failed to load Photo: {0}", Error.ToString()));
}
public void LoadFile(String filePath) {
bool result = ragephoto_loadfile(_instance, filePath);
if (!result)
throw new RagePhotoException(this, string.Format("Failed to load Photo: {0}", Error.ToString()));
}
public RagePhotoData Data {
get => Marshal.PtrToStructure<RagePhotoData>(ragephoto_getphotodata(_instance));
}
public String Description {
get {
IntPtr descPtr = ragephoto_getphotodesc(_instance);
return descPtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringUTF8(descPtr);
}
set => ragephoto_setphotodesc(_instance, value, 0);
}
public PhotoError Error {
get => (PhotoError)ragephoto_error(_instance);
}
public PhotoFormat Format {
get => (PhotoFormat)ragephoto_getphotoformat(_instance);
set => ragephoto_setphotoformat(_instance, (UInt32)value);
}
public Byte[] Jpeg {
get {
UInt32 size = ragephoto_getphotosize(_instance);
if (size == 0)
return Array.Empty<Byte>();
IntPtr jpegPtr = ragephoto_getphotojpeg(_instance);
byte[] jpeg = new Byte[size];
Marshal.Copy(jpegPtr, jpeg, 0, (Int32)size);
return jpeg;
}
set {
UInt32 bufferSize;
UInt32 size = (UInt32)value.Length;
switch (Format) {
case PhotoFormat.GTA5:
if (size > (UInt32)DefaultSize.DEFAULT_GTA5_PHOTOBUFFER)
bufferSize = size;
else
bufferSize = (UInt32)DefaultSize.DEFAULT_GTA5_PHOTOBUFFER;
break;
case PhotoFormat.RDR2:
if (size > (UInt32)DefaultSize.DEFAULT_RDR2_PHOTOBUFFER)
bufferSize = size;
else
bufferSize = (UInt32)DefaultSize.DEFAULT_RDR2_PHOTOBUFFER;
break;
default:
bufferSize = size;
break;
}
bool result = ragephoto_setphotojpeg(_instance, value, size, bufferSize);
if (!result)
throw new RagePhotoException(this, string.Format("Failed to set Jpeg: {0}", Error.ToString()));
}
}
public UInt32 JpegSize {
get => ragephoto_getphotosize(_instance);
}
public String Json {
get {
IntPtr jsonPtr = ragephoto_getphotojson(_instance);
return jsonPtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringUTF8(jsonPtr);
}
set => ragephoto_setphotojson(_instance, value, 0);
}
public String Header {
get {
IntPtr headerPtr = ragephoto_getphotoheader(_instance);
return headerPtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringUTF8(headerPtr);
}
}
public UIntPtr SaveSize {
get => ragephoto_getsavesize(_instance);
}
public UInt64 Sign {
get => ragephoto_getphotosign(_instance);
}
public String Title {
get {
IntPtr titlePtr = ragephoto_getphototitle(_instance);
return titlePtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringUTF8(titlePtr);
}
set => ragephoto_setphototitle(_instance, value, 0);
}
public UIntPtr GetSaveSize() {
return ragephoto_getsavesize(_instance);
}
public UIntPtr GetSaveSize(PhotoFormat photoFormat) {
return ragephoto_getsavesizef(_instance, (UInt32)photoFormat);
}
public Byte[] Save() {
Byte[] photo = new Byte[Environment.Is64BitProcess ? (UInt64)GetSaveSize() : (UInt32)GetSaveSize()];
bool result = ragephoto_save(_instance, photo);
if (!result)
throw new RagePhotoException(this, string.Format("Failed to save Photo: {0}", Error.ToString()));
return photo;
}
public Byte[] Save(PhotoFormat photoFormat) {
Byte[] photo = new Byte[Environment.Is64BitProcess ? (UInt64)GetSaveSize(photoFormat) : (UInt32)GetSaveSize(photoFormat)];
bool result = ragephoto_savef(_instance, photo, (UInt32)photoFormat);
if (!result)
throw new RagePhotoException(this, string.Format("Failed to save Photo: {0}", Error.ToString()));
return photo;
}
public void SetBufferDefault() {
ragephoto_setbufferdefault(_instance);
}
public void SetBufferOffsets() {
ragephoto_setbufferoffsets(_instance);
}
public void SetHeader(String header, UInt32 headerSum, UInt32 headerSum2 = 0) {
ragephoto_setphotoheader2(_instance, header, headerSum, headerSum2);
}
public bool SetJpeg(Byte[] jpeg, UInt32 bufferSize = 0) {
UInt32 size = (UInt32)jpeg.Length;
return ragephoto_setphotojpeg(_instance, jpeg, size, bufferSize);
}
public static String Version {
get => ragephoto_version();
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using System.Runtime.InteropServices;
namespace Syping.RagePhoto {
[StructLayout(LayoutKind.Sequential)]
public readonly struct RagePhotoData {
readonly IntPtr jpeg;
[MarshalAs(UnmanagedType.LPUTF8Str)]
readonly String description;
[MarshalAs(UnmanagedType.LPUTF8Str)]
readonly String json;
[MarshalAs(UnmanagedType.LPUTF8Str)]
readonly String header;
[MarshalAs(UnmanagedType.LPUTF8Str)]
readonly String title;
readonly Int32 error;
readonly UInt32 descBuffer;
readonly UInt32 descOffset;
readonly UInt32 endOfFile;
readonly UInt32 headerSum;
readonly UInt32 headerSum2;
readonly UInt32 jpegBuffer;
readonly UInt32 jpegSize;
readonly UInt32 jsonBuffer;
readonly UInt32 jsonOffset;
readonly UInt32 photoFormat;
readonly UInt32 titlBuffer;
readonly UInt32 titlOffset;
}
}

View file

@ -0,0 +1,17 @@
using System;
namespace Syping.RagePhoto {
public class RagePhotoException : Exception {
private readonly Photo _photo;
public RagePhotoException(Photo photo, String message) : base(message) {
_photo = photo;
}
public RagePhotoException(Photo photo, String message, Exception innerException) : base(message, innerException) {
_photo = photo;
}
}
}

View file

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>Syping.RagePhoto.Core</AssemblyName>
<RootNamespace>Syping.RagePhoto</RootNamespace>
<Version>0.6.0</Version>
<AssemblyVersion>0.6.0.0</AssemblyVersion>
<FileVersion>0.6.0.0</FileVersion>
<Authors>Syping</Authors>
<Copyright>Copyright © 2025 Syping</Copyright>
<Description>Open Source RAGE Photo Parser for GTA V and RDR 2</Description>
</PropertyGroup>
<ItemGroup>
<NativeDlls Include="runtimes\**\libragephoto.dll" />
</ItemGroup>
<ItemGroup>
<None Include="@(NativeDlls)" Pack="true" PackagePath="runtimes\%(RecursiveDir)%(Filename)%(Extension)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="Syping.RagePhoto.Core.targets" Pack="true" PackagePath="buildTransitive\Syping.RagePhoto.Core.targets">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35919.96 d17.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Syping.RagePhoto.Core", "Syping.RagePhoto.Core.csproj", "{00E62D4F-74D6-4167-ABB1-852FFEB65C84}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{00E62D4F-74D6-4167-ABB1-852FFEB65C84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{00E62D4F-74D6-4167-ABB1-852FFEB65C84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{00E62D4F-74D6-4167-ABB1-852FFEB65C84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{00E62D4F-74D6-4167-ABB1-852FFEB65C84}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E9528D0F-BA8C-4ED2-853E-60E3D6D373E1}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,17 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Condition="'$(Platform)' == 'ARM64' and '$(OS)' == 'Windows_NT'">
<Content Include="$(MSBuildThisFileDirectory)..\runtimes\win-arm64\native\libragephoto.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(Platform)' == 'x64' and '$(OS)' == 'Windows_NT'">
<Content Include="$(MSBuildThisFileDirectory)..\runtimes\win-x64\native\libragephoto.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(Platform)' == 'x86' and '$(OS)' == 'Windows_NT'">
<Content Include="$(MSBuildThisFileDirectory)..\runtimes\win-x86\native\libragephoto.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>