FModel 3.0.4

This commit is contained in:
iAmAsval 2020-02-22 04:03:43 +01:00
parent 584d5e4c2e
commit 791fcd4008
9 changed files with 230 additions and 299 deletions

View File

@ -7,7 +7,7 @@
mc:Ignorable="d"
Title="About"
Height="180"
Width="510"
Width="550"
Style="{StaticResource {x:Type Window}}"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
@ -20,7 +20,7 @@
<Label x:Name="AboutTitle_Lbl" Content="FModel" HorizontalAlignment="Center" VerticalAlignment="Top" FontSize="24" FontWeight="Bold" Margin="0,5,0,0"/>
<Label Content="A powerful .PAK file explorer fully dedicated to Fortnite." HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,33,0,0"/>
<Label Content="Big thanks to:" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,60,0,0"/>
<Label Content="• Waddlesworth • Maiky • FunGames • PsychoPast • TSG • FireMonkey" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,75,0,0" FontSize="11"/>
<Label Content="• Waddlesworth • Maiky • FunGames • Not Officer • PsychoPast • TSG • FireMonkey" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,75,0,0" FontSize="11"/>
<Label HorizontalAlignment="Left" Margin="10,0,0,10" VerticalAlignment="Bottom" Padding="0" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Cursor="Hand">
<TextBlock>

View File

@ -1,6 +1,5 @@
using FModel.Methods.Auth;
using FModel.Methods.Utilities;
using Newtonsoft.Json;
using PakReader;
using System;
using System.Collections.Generic;
@ -226,7 +225,7 @@ namespace FModel.Methods.Assets
string txtNativeString = GetValueFromParam(line, "NativeString=\"", "\",");
string translations = GetValueFromParam(line, "LocalizedStrings=(", "))");
if (!translations.EndsWith(")")) { translations = translations + ")"; }
if (!translations.EndsWith(")")) { translations += ")"; }
if (!HotfixLocResDict.ContainsKey(txtNamespace))
HotfixLocResDict[txtNamespace] = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>();
@ -248,7 +247,9 @@ namespace FModel.Methods.Assets
catch (IndexOutOfRangeException)
{
string[] langParts = match.Value.Substring(1, match.Value.Length - 2).Split(new string[] { "\", \"" }, StringSplitOptions.None);
HotfixLocResDict[txtNamespace][txtKey][txtNativeString][langParts[0]] = langParts[1];
if (langParts.Length > 1)
// 02/22/2020 legendary trad in spanish is miss-typed and cause crash ("es",Legendario""),
HotfixLocResDict[txtNamespace][txtKey][txtNativeString][langParts[0]] = langParts[1];
}
}
}

View File

@ -274,12 +274,17 @@ namespace FModel.Methods.PAKs
List<FPakEntry> entries = new List<FPakEntry>();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
FPakEntry entry = new FPakEntry(
reader.ReadString(),
reader.ReadInt64(),
reader.ReadInt64(),
reader.ReadInt64(),
reader.ReadBytes(20), null, 0, 0, 0);
// we must follow this order
long offset = reader.ReadInt64();
long size = reader.ReadInt64();
long uncompressedSize = reader.ReadInt64();
bool encrypted = reader.ReadBoolean();
long structSize = reader.ReadInt32();
string name = reader.ReadString();
long compressionMethodIndex = reader.ReadInt32();
// we actually only need name and uncompressedSize to compare
FPakEntry entry = new FPakEntry(name, offset, size, uncompressedSize, new byte[20], null, 0, 0, 0);
entries.Add(entry);
}
BackupEntries = entries.ToArray();

View File

@ -245,19 +245,8 @@ namespace PakReader
}
}
public abstract class BasePakEntry
{
public long Pos;
public long Size;
public long UncompressedSize;
public bool Encrypted;
public int StructSize;
}
public struct FPakEntry : IEquatable<FPakEntry>
{
const byte Flag_None = 0x00;
const byte Flag_Encrypted = 0x01;
const byte Flag_Deleted = 0x02;
@ -338,32 +327,6 @@ namespace PakReader
StructSize = (int)(reader.BaseStream.Position - StartOffset);
}
internal FPakEntry(BinaryReader reader, string mountPoint)
{
CompressionBlocks = null;
CompressionBlockSize = 0;
Flags = 0;
Name = mountPoint + reader.ReadFString(FPakInfo.MAX_PACKAGE_PATH).Replace(".umap", ".uasset");
var StartOffset = reader.BaseStream.Position;
Offset = reader.ReadInt64();
Size = reader.ReadInt64();
UncompressedSize = reader.ReadInt64();
CompressionMethodIndex = reader.ReadUInt32();
Hash = reader.ReadBytes(20);
if (CompressionMethodIndex != 0)
{
CompressionBlocks = reader.ReadTArray(() => new FPakCompressedBlock(reader));
}
Flags = reader.ReadByte();
CompressionBlockSize = reader.ReadUInt32();
// Used to seek ahead to the file data instead of parsing the entry again
StructSize = (int)(reader.BaseStream.Position - StartOffset);
}
internal FPakEntry(string name, long offset, long size, long uncompressedSize, byte[] hash, FPakCompressedBlock[] compressionBlocks, uint compressionBlockSize, uint compressionMethodIndex, byte flags)
{
Name = name;
@ -375,7 +338,7 @@ namespace PakReader
CompressionBlockSize = compressionBlockSize;
CompressionMethodIndex = compressionMethodIndex;
Flags = flags;
StructSize = (int)GetSize(PAK_VERSION.PAK_LATEST, compressionMethodIndex, (uint)compressionBlocks.Length);
StructSize = compressionBlocks != null ? (int)GetSize(PAK_VERSION.PAK_LATEST, compressionMethodIndex, (uint)compressionBlocks.Length) : 0;
}
public static long GetSize(PAK_VERSION version, uint CompressionMethodIndex = 0, uint CompressionBlocksCount = 0)
@ -419,6 +382,32 @@ namespace PakReader
public override int GetHashCode() => FProp.Default.FDiffFileSize ? (Name, UncompressedSize).GetHashCode() : (Name).GetHashCode();
}
public struct FPathHashIndexEntry
{
public string Filename { get; }
public int Location { get; }
public FPathHashIndexEntry(BinaryReader reader)
{
Filename = reader.ReadFString();
Location = reader.ReadInt32();
}
}
public struct FPakDirectoryEntry
{
public string Directory { get; }
public FPathHashIndexEntry[] Entries { get; }
public FPakDirectoryEntry(BinaryReader reader)
{
Directory = reader.ReadFString();
Entries = reader.ReadTArray(() => new FPathHashIndexEntry(reader));
}
}
public struct FPakCompressedBlock
{
public long CompressedStart;

View File

@ -10,7 +10,7 @@ namespace PakReader
readonly Stream Stream;
readonly BinaryReader Reader;
readonly byte[] Aes;
public readonly string MountPoint;
public string MountPoint;
public FPakEntry[] FileInfos;
public readonly string Name;
@ -85,39 +85,28 @@ namespace PakReader
throw new FileLoadException("The AES key is invalid");
}
}
infoReader.BaseStream.Seek(0, SeekOrigin.Begin);
}
if (!ParseFiles) return;
// Pak index reading time :)
infoReader.BaseStream.Seek(0, SeekOrigin.Begin);
MountPoint = infoReader.ReadFString(FPakInfo.MAX_PACKAGE_PATH);
bool badMountPoint = false;
if (!MountPoint.StartsWith("../../.."))
{
badMountPoint = true;
}
else
{
MountPoint = MountPoint.Substring(8);
}
if (MountPoint[0] != '/' || ((MountPoint.Length > 1) && (MountPoint[1] == '.')))
{
badMountPoint = true;
}
if (badMountPoint)
{
DebugHelper.WriteLine($".PAKs: WARNING: Pak \"{Name}\" has strange mount point \"{MountPoint}\", mounting to root");
MountPoint = "/";
}
if (info.Version >= (int)PAK_VERSION.PAK_PATH_HASH_INDEX)
{
ReadIndexUpdated(infoReader, MountPoint, info, Aes, Stream.Length);
ReadIndexUpdated(infoReader, info, Aes);
}
else
{
MountPoint = infoReader.ReadFString();
if (MountPoint.StartsWith("../../.."))
{
MountPoint = MountPoint.Substring(8);
}
else
{
MountPoint = "/";
}
FileInfos = new FPakEntry[infoReader.ReadInt32()];
for (int i = 0; i < FileInfos.Length; i++)
{
@ -126,127 +115,64 @@ namespace PakReader
}
}
void ReadIndexUpdated(BinaryReader reader, string mountPoint, FPakInfo info, byte[] key, long totalSize)
private void ReadIndexUpdated(BinaryReader reader, FPakInfo info, byte[] aesKey)
{
int NumEntries = reader.ReadInt32();
ulong PathHashSeed = reader.ReadUInt64();
bool bReaderHasPathHashIndex = false;
long PathHashIndexOffset = -1; // INDEX_NONE
long PathHashIndexSize = 0;
FSHAHash PathHashIndexHash = default;
bReaderHasPathHashIndex = reader.ReadInt32() != 0;
if (bReaderHasPathHashIndex)
MountPoint = reader.ReadFString();
if (MountPoint.StartsWith("../../.."))
{
PathHashIndexOffset = reader.ReadInt64();
PathHashIndexSize = reader.ReadInt64();
PathHashIndexHash = new FSHAHash(reader);
bReaderHasPathHashIndex = bReaderHasPathHashIndex && PathHashIndexOffset != -1;
}
bool bReaderHasFullDirectoryIndex = false;
long FullDirectoryIndexOffset = -1; // INDEX_NONE
long FullDirectoryIndexSize = 0;
FSHAHash FullDirectoryIndexHash = default;
bReaderHasFullDirectoryIndex = reader.ReadInt32() != 0;
if (bReaderHasFullDirectoryIndex)
{
FullDirectoryIndexOffset = reader.ReadInt64();
FullDirectoryIndexSize = reader.ReadInt64();
FullDirectoryIndexHash = new FSHAHash(reader);
bReaderHasFullDirectoryIndex = bReaderHasFullDirectoryIndex && FullDirectoryIndexOffset != -1;
}
byte[] EncodedPakEntries = reader.ReadTArray(() => reader.ReadByte());
int FilesNum = reader.ReadInt32();
if (FilesNum < 0)
// Should not be possible for any values in the PrimaryIndex to be invalid, since we verified the index hash
throw new FileLoadException("Corrupt pak PrimaryIndex detected!");
FPakEntry[] Files = new FPakEntry[FilesNum]; // from what i can see, there aren't any???
if (FilesNum > 0)
for (int FileIndex = 0; FileIndex < FilesNum; ++FileIndex)
Files[FileIndex] = new FPakEntry(reader, mountPoint, (PAK_VERSION)info.Version);
// Decide which SecondaryIndex(es) to load
bool bWillUseFullDirectoryIndex;
bool bWillUsePathHashIndex;
bool bReadFullDirectoryIndex;
if (bReaderHasPathHashIndex && bReaderHasFullDirectoryIndex)
{
bWillUseFullDirectoryIndex = false;
bWillUsePathHashIndex = !bWillUseFullDirectoryIndex;
bool bWantToReadFullDirectoryIndex = false;
bReadFullDirectoryIndex = bReaderHasFullDirectoryIndex && bWantToReadFullDirectoryIndex;
}
else if (bReaderHasPathHashIndex)
{
bWillUsePathHashIndex = true;
bWillUseFullDirectoryIndex = false;
bReadFullDirectoryIndex = false;
}
else if (bReaderHasFullDirectoryIndex)
{
// We don't support creating the PathHash Index at runtime; we want to move to having only the PathHashIndex, so supporting not having it at all is not useful enough to write
bWillUsePathHashIndex = false;
bWillUseFullDirectoryIndex = true;
bReadFullDirectoryIndex = true;
MountPoint = MountPoint.Substring(8);
}
else
// It should not be possible for PrimaryIndexes to be built without a PathHashIndex AND without a FullDirectoryIndex; CreatePakFile in UnrealPak.exe has a check statement for it.
{
MountPoint = "/";
}
var filesNum = reader.ReadInt32();
reader.ReadUInt64();
if (reader.ReadInt32() == 0)
{
throw new FileLoadException("No path hash index");
}
//reader.ReadInt64();
//reader.ReadInt64();
reader.BaseStream.Position += 20L + 8L + 8L;
if (reader.ReadInt32() == 0)
{
throw new FileLoadException("No directory index");
}
var position = reader.ReadInt64();
var directoryIndexSize = reader.ReadInt64();
reader.BaseStream.Position += 20L;
var encodedPakEntries = reader.ReadTArray(reader.ReadByte);
var files = reader.ReadInt32();
if (files < 0)
{
throw new FileLoadException("Corrupt pak PrimaryIndex detected!");
// Load the Secondary Index(es)
byte[] PathHashIndexData;
Dictionary<ulong, int> PathHashIndex;
BinaryReader PathHashIndexReader = default;
if (bWillUsePathHashIndex)
{
if (PathHashIndexOffset < 0 || totalSize < (PathHashIndexOffset + PathHashIndexSize))
// Should not be possible for these values (which came from the PrimaryIndex) to be invalid, since we verified the index hash of the PrimaryIndex
throw new FileLoadException("Corrupt pak PrimaryIndex detected!");
Reader.BaseStream.Position = PathHashIndexOffset;
PathHashIndexData = Reader.ReadBytes((int)PathHashIndexSize);
{
if (!DecryptAndValidateIndex(info.bEncryptedIndex != 0, ref PathHashIndexData, key, PathHashIndexHash, out var ComputedHash))
throw new FileLoadException("Corrupt pak PrimaryIndex detected!");
}
PathHashIndexReader = new BinaryReader(new MemoryStream(PathHashIndexData));
PathHashIndex = ReadPathHashIndex(PathHashIndexReader);
}
var DirectoryIndex = new Dictionary<string, Dictionary<string, int>>();
if (!bReadFullDirectoryIndex)
{
DirectoryIndex = ReadDirectoryIndex(PathHashIndexReader);
}
if (DirectoryIndex.Count == 0)
{
if (totalSize < (FullDirectoryIndexOffset + FullDirectoryIndexSize) || FullDirectoryIndexOffset < 0)
throw new FileLoadException("Corrupt pak PrimaryIndex detected!");
Reader.BaseStream.Position = FullDirectoryIndexOffset;
byte[] FullDirectoryIndexData = Reader.ReadBytes((int)FullDirectoryIndexSize);
Reader.BaseStream.Position = position;
var directoryIndexData = Reader.ReadBytes((int)directoryIndexSize);
{
if (!DecryptAndValidateIndex(info.bEncryptedIndex != 0, ref FullDirectoryIndexData, key, FullDirectoryIndexHash, out var ComputedHash))
throw new FileLoadException("Corrupt pak PrimaryIndex detected!");
}
var SecondaryIndexReader = new BinaryReader(new MemoryStream(FullDirectoryIndexData));
DirectoryIndex = ReadDirectoryIndex(SecondaryIndexReader);
if (info.bEncryptedIndex != 0)
{
directoryIndexData = AESDecryptor.DecryptAES(directoryIndexData, aesKey);
}
var entries = new List<FPakEntry>(NumEntries);
foreach (var stringDict in DirectoryIndex)
var directoryIndexReader = new BinaryReader(new MemoryStream(directoryIndexData));
var directoryEntries = directoryIndexReader.ReadTArray(() => new FPakDirectoryEntry(directoryIndexReader));
var entries = new List<FPakEntry>(filesNum);
foreach (var directoryEntry in directoryEntries)
{
foreach (var stringInt in stringDict.Value)
foreach (var hashIndexEntry in directoryEntry.Entries)
{
string path = stringDict.Key + stringInt.Key;
FPakEntry entry = GetEntry(mountPoint + path, stringInt.Value, EncodedPakEntries);
entries.Add(entry);
string path = MountPoint + directoryEntry.Directory + hashIndexEntry.Filename;
entries.Add(GetEntry(path, hashIndexEntry.Location, encodedPakEntries));
}
}
this.FileInfos = entries.ToArray();
@ -380,53 +306,8 @@ namespace PakReader
return new FPakEntry(name, Offset, Size, UncompressedSize, new byte[20], CompressionBlocks, CompressionBlockSize, CompressionMethodIndex, (byte)((Encrypted ? 0x01 : 0x00) | (Deleted ? 0x02 : 0x00)));
}
else
{
pakLocation = -(pakLocation + 1);
//pakLocation = -(pakLocation + 1);
throw new FileLoadException("list indexes aren't supported");
}
}
Dictionary<ulong, int> ReadPathHashIndex(BinaryReader reader)
{
var ret = new Dictionary<ulong, int>();
var keys = reader.ReadTArray(() => (reader.ReadUInt64(), reader.ReadInt32()));
foreach (var (k, v) in keys)
{
ret[k] = v;
}
return ret;
}
Dictionary<string, Dictionary<string, int>> ReadDirectoryIndex(BinaryReader reader)
{
var ret = new Dictionary<string, Dictionary<string, int>>();
var keys = reader.ReadTArray(() => (reader.ReadFString(), ReadFPakDirectory(reader)));
foreach (var (k, v) in keys)
{
ret[k] = v;
}
return ret;
}
Dictionary<string, int> ReadFPakDirectory(BinaryReader reader)
{
var ret = new Dictionary<string, int>();
var keys = reader.ReadTArray(() => (reader.ReadFString(), reader.ReadInt32()));
foreach (var (k, v) in keys)
{
ret[k] = v;
}
return ret;
}
bool DecryptAndValidateIndex(bool bEncryptedIndex, ref byte[] IndexData, byte[] aesKey, FSHAHash ExpectedHash, out FSHAHash OutHash)
{
if (bEncryptedIndex)
{
IndexData = AESDecryptor.DecryptAES(IndexData, aesKey);
}
OutHash = ExpectedHash;
return true;
}
public Stream GetPackageStream(FPakEntry entry)

View File

@ -1,64 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<SyntaxDefinition name="Ini" extensions=".cfg;.conf;.ini;.iss;"
xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<!--
INI/INF syntax highlighting
Written by Ezra Altahan
Created 18/07/2009 | Updated 16/10/2016
Version 1.0
<Color name="Bool" foreground="#61AFEF"/>
<Color name="Digits" foreground="#D19A66"/>
<Color name="Comment" foreground="SeaGreen"/>
<Color name="Punctuation" foreground="#BBBBBB"/>
<Color name="String" foreground="#98C379"/>
<Color name="String2" foreground="#98C379"/>
hello@exr.be
https://github.com/ei
-->
<Color name="Section" foreground="#7F848E"/>
<SyntaxDefinition name="INI" extensions=".ini;.inf;.wer;.dof">
<Color name="PropertyName" foreground="#E06C6C"/>
<Environment>
<Default color="Black" bgcolor="#FFFFFF"/>
<Selection color="Black" bgcolor="#C3C3FF"/>
<LineNumbers color="Gray" bgcolor="#FFFFFF"/>
<CaretMarker color="#F0F0F1"/>
<VRuler color="#E0E0E5"/>
<RuleSet ignoreCase="true">
<FoldLine color="#A0A0A0" bgcolor="#FFFFFF"/>
<FoldMarker color="Black" bgcolor="#FFFFFF"/>
<SelectedFoldLine color="Black" bgcolor="#FFFFFF"/>
<Span color="String" multiline="false" >
<Begin>'</Begin>
<End>'</End>
</Span>
<EOLMarkers color="#CACAD2"/>
<SpaceMarkers color="#B6B6C0"/>
<TabMarkers color="#B6B6C0"/>
<InvalidLines color="#B6B6C0"/>
</Environment>
<Keywords color="Bool" >
<Word>true</Word>
<Word>false</Word>
</Keywords>
<Properties>
<Property name="LineComment" value=";"/>
</Properties>
<Span color="String2" multiline="false" >
<Begin>"</Begin>
<End>"</End>
</Span>
<Digits name="Digits" color="#ED6464"/>
<!-- span for escape sequences -->
<Span color="Comment" multiline="false">
<Begin>;</Begin>
</Span>
<Span color="Comment" multiline="false">
<Begin>\#</Begin>
</Span>
<RuleSets>
<RuleSet ignorecase="false">
<Span color="Section" multiline="false">
<Begin>\[</Begin>
<End>\]</End>
</Span>
<Delimiters>&amp;|\/"',;=:-</Delimiters>
<Rule color="PropertyName">
[a-zA-Z]
</Rule>
<Span name="LineComment1" stopateol="true" color="Green" bold="false" italic="false">
<Begin>;</Begin>
</Span>
<Rule color="Punctuation">
[?,.;()\[\]{}+=_\-/%*&lt;&gt;^+~!|&amp;]+
</Rule>
<Span name="LineComment2" stopateol="true" color="Green" bold="false" italic="false">
<Begin>#</Begin>
</Span>
<Rule color="Digits">
\b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?
</Rule>
<Span name="String" stopateol="true" color="#6495ED" bold="false" italic="false" escapecharacter="\">
<Begin>"</Begin>
<End>"</End>
</Span>
</RuleSet>
<Span name="Variable" stopateol="true" color="Goldenrod" bold="true" italic="false">
<Begin startofline="true">[</Begin>
<End>]</End>
</Span>
<MarkPrevious color="Crimson" bold="false" italic="false">=</MarkPrevious>
</RuleSet>
</RuleSets>
</SyntaxDefinition>
</SyntaxDefinition>

View File

@ -1,24 +1,76 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Syntaxdefinition for Json by alek kowalczyk
Update by zuijin in 2019.12.20
-->
<SyntaxDefinition name="Json" extensions=".json" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Digits" foreground="#ED6464"/>
<Color name="Value" foreground="#6495ED"/>
<Color name="ParamName" foreground="Goldenrod"/>
<RuleSet ignoreCase="false">
<Keywords color="Digits" >
<Word>true</Word>
<Word>false</Word>
<Word>null</Word>
</Keywords>
<Span color="ParamName">
<Color name="Bool" foreground="#61AFEF" exampleText="true | false" />
<Color name="Number" foreground="#D19A66" exampleText="3.14" />
<Color name="String" foreground="#98C379" exampleText="" />
<Color name="Null" foreground="#7F848E" exampleText="" />
<Color name="FieldName" foreground="#E06C6C" />
<Color name="Punctuation" foreground="#BBBBBB" />
<RuleSet name="String">
<Span begin="\\" end="."/>
</RuleSet>
<RuleSet name="Object">
<Span color="FieldName" ruleSet="String">
<Begin>"</Begin>
<End>(?=:)</End>
</Span>
<Span color="Value" multiline="true">
<Begin>
(?&lt;=:)\040"[^"]*
</Begin>
<End>"</End>
</Span>
<Rule color="Digits">\b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?</Rule>
<Span color="FieldName" ruleSet="String">
<Begin>'</Begin>
<End>'</End>
</Span>
<Span color="Punctuation" ruleSet="Expression">
<Begin>:</Begin>
</Span>
<Span color="Punctuation">
<Begin>,</Begin>
</Span>
</RuleSet>
<RuleSet name="Array">
<Import ruleSet="Expression"/>
<Span color="Punctuation">
<Begin>,</Begin>
</Span>
</RuleSet>
<RuleSet name="Expression">
<Keywords color="Bool" >
<Word>true</Word>
<Word>false</Word>
</Keywords>
<Keywords color="Null" >
<Word>null</Word>
</Keywords>
<Span color="String" ruleSet="String">
<Begin>"</Begin>
<End>"</End>
</Span>
<Span color="String" ruleSet="String">
<Begin>'</Begin>
<End>'</End>
</Span>
<Span color="Punctuation" ruleSet="Object" multiline="true">
<Begin>\{</Begin>
<End>\}</End>
</Span>
<Span color="Punctuation" ruleSet="Array" multiline="true">
<Begin>\[</Begin>
<End>\]</End>
</Span>
<Rule color="Number">
\b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?
</Rule>
</RuleSet>
<RuleSet>
<Import ruleSet="Expression"/>
</RuleSet>
</SyntaxDefinition>

View File

@ -51,5 +51,5 @@ using System.Windows;
// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut
// en utilisant '*', comme indiqué ci-dessous :
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.0.3.0")]
[assembly: AssemblyFileVersion("3.0.3.0")]
[assembly: AssemblyVersion("3.0.4.0")]
[assembly: AssemblyFileVersion("3.0.4.0")]

View File

@ -14,7 +14,7 @@
alt="Releases">
</a>
<a href="https://github.com/iAmAsval/FModel/releases/latest">
<img src="https://img.shields.io/github/downloads/iAmAsval/FModel/latest/total.svg?label=v3.0.3%20Downloads"
<img src="https://img.shields.io/github/downloads/iAmAsval/FModel/latest/total.svg?label=v3.0.4%20Downloads"
alt="Downloads">
</a>
<a href="https://twitter.com/AsvalFN"><img src="https://img.shields.io/badge/Twitter-@AsvalFN-1da1f2.svg?logo=twitter"></a>
@ -139,6 +139,14 @@ For x32 users, you just have to clone or download the repository and build FMode
<a href="https://github.com/FunGamesLeaks" title="Github">🔧</a>
<a href="https://twitter.com/FunGamesLeaks" title="Twitter">🐦</a>
</td>
<td align="center">
<a href="https://github.com/NotOfficer">
<img src="https://avatars1.githubusercontent.com/u/29897990?s=200&v=4" width="100px;" alt="Not Officer"/><br>
<sub><b>Not Officer</b></sub>
</a><br>
<a href="https://github.com/NotOfficer" title="Github">🔧</a>
<a href="https://twitter.com/Not0fficer" title="Twitter">🐦</a>
</td>
<td align="center">
<a href="https://github.com/PsychoPast">
<img src="https://avatars0.githubusercontent.com/u/33565739?s=200&v=4" width="100px;" alt="PsychoPast"/><br>