Implement GPAK data loading

This commit is contained in:
Crypto137 2023-08-06 18:01:14 +03:00
parent e22825a204
commit d0396cf19d
9 changed files with 1332 additions and 10 deletions

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Milosz Krajewski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -18,6 +18,8 @@ After getting the client, you need to set up a web server to serve SiteConfig.xm
6. Compile MHServerEmu with Visual Studio or any other tool you prefer.
7. Copy `Calligraphy.sip` and `mu_cdata.sip` located in `Marvel Heroes\Data\Game` to `MHServerEmu\Assets\GPAK`.
Now you can actually start everything and get in-game.
1. Start Apache by running ```Apache24\bin\httpd.exe```.

View file

@ -0,0 +1 @@
Copy Calligraphy.sip and mu_cdata.sip from Marvel Heroes\Data\Game to here

View file

@ -1,5 +1,6 @@
using System.Text;
using MHServerEmu.Common;
using MHServerEmu.GameServer.Data.Gpak;
namespace MHServerEmu.GameServer.Data
{
@ -8,6 +9,9 @@ namespace MHServerEmu.GameServer.Data
private static readonly Logger Logger = LogManager.CreateLogger();
public static bool IsInitialized { get; private set; }
public static GpakFile Calligraphy { get; private set; }
public static GpakFile Resource { get; private set; }
public static Dictionary<ulong, Prototype> PrototypeDataDict { get; private set; }
public static ulong[] GlobalEnumRefTable { get; private set; }
@ -16,17 +20,23 @@ namespace MHServerEmu.GameServer.Data
static Database()
{
Logger.Info("Loading prototypes...");
long startTime = ((DateTimeOffset)DateTime.UtcNow).ToUnixTimeMilliseconds();
Calligraphy = new("Calligraphy.sip");
Resource = new("mu_cdata.sip");
Logger.Trace("Loading prototypes...");
PrototypeDataDict = LoadPrototypeData($"{Directory.GetCurrentDirectory()}\\Assets\\PrototypeDataTable.bin");
Logger.Info($"Loaded {PrototypeDataDict.Count} prototypes");
GlobalEnumRefTable = LoadPrototypeEnumRefTable($"{Directory.GetCurrentDirectory()}\\Assets\\GlobalEnumRefTable.bin");
ResourceEnumRefTable = LoadPrototypeEnumRefTable($"{Directory.GetCurrentDirectory()}\\Assets\\ResourceEnumRefTable.bin");
PropertyIdPowerRefTable = LoadPrototypeEnumRefTable($"{Directory.GetCurrentDirectory()}\\Assets\\PropertyIdPowerRefTable.bin");
if (PrototypeDataDict.Count > 0 && GlobalEnumRefTable.Length > 0 && ResourceEnumRefTable.Length > 0 && PropertyIdPowerRefTable.Length > 0)
if (VerifyData())
{
// -1 is here because the first entry is 0 to offset values and align with the data we get from the game
Logger.Info($"Loaded {PrototypeDataDict.Count} prototypes");
long loadTime = ((DateTimeOffset)DateTime.UtcNow).ToUnixTimeMilliseconds() - startTime;
Logger.Info($"Finished loading in {loadTime} ms");
IsInitialized = true;
}
else
@ -36,6 +46,24 @@ namespace MHServerEmu.GameServer.Data
}
}
public static void ExportGpakEntries()
{
Logger.Info("Exporting Calligraphy entries...");
Calligraphy.ExportEntries("Calligraphy.tsv");
Logger.Info("Exporting Resource entries...");
Resource.ExportEntries("mu_cdata.tsv");
Logger.Info("Finished exporting GPAK entries");
}
public static void ExportGpakData()
{
Logger.Info("Exporting Calligraphy data...");
Calligraphy.ExportData();
Logger.Info("Exporting Resource data...");
Resource.ExportData();
Logger.Info("Finished exporting GPAK data");
}
private static Dictionary<ulong, Prototype> LoadPrototypeData(string path)
{
Dictionary<ulong, Prototype> prototypeDict = new();
@ -97,5 +125,15 @@ namespace MHServerEmu.GameServer.Data
return Array.Empty<ulong>();
}
}
private static bool VerifyData()
{
return Calligraphy.Entries.Length > 0
&& Resource.Entries.Length > 0
&& PrototypeDataDict.Count > 0
&& GlobalEnumRefTable.Length > 0
&& ResourceEnumRefTable.Length > 0
&& PropertyIdPowerRefTable.Length > 0;
}
}
}

View file

@ -1,4 +1,6 @@
using System.Text;
using System.IO;
using System.Text;
using K4os.Compression.LZ4;
using MHServerEmu.Common;
namespace MHServerEmu.GameServer.Data.Gpak
@ -10,7 +12,7 @@ namespace MHServerEmu.GameServer.Data.Gpak
public int Header { get; } // KAPG
public int Field1 { get; }
public GpakEntry[] Entries { get; }
public GpakEntry[] Entries { get; } = Array.Empty<GpakEntry>();
public GpakFile(string gpakFileName)
{
@ -18,15 +20,18 @@ namespace MHServerEmu.GameServer.Data.Gpak
if (File.Exists(path))
{
Logger.Trace($"Loading {gpakFileName}...");
using (FileStream fileStream = File.OpenRead(path))
{
byte[] buffer = new byte[4096];
// Header
Header = ReadInt(fileStream, buffer);
Field1 = ReadInt(fileStream, buffer);
Entries = new GpakEntry[ReadInt(fileStream, buffer)];
Entries = new GpakEntry[ReadInt(fileStream, buffer)];
// Entry metadata
for (int i = 0; i < Entries.Length; i++)
{
ulong id = ReadUlong(fileStream, buffer);
@ -42,12 +47,46 @@ namespace MHServerEmu.GameServer.Data.Gpak
Entries[i] = new(id, filePath, field2, offset, compressedSize, uncompressedSize);
}
Logger.Debug($"Loaded {Entries.Length} GPAK entries");
// Entry data
foreach (GpakEntry entry in Entries)
{
byte[] compressedData = new byte[entry.CompressedSize];
byte[] uncompressedData = new byte[entry.UncompressedSize];
fileStream.Read(compressedData, 0, compressedData.Length);
LZ4Codec.Decode(compressedData, uncompressedData);
entry.Data = uncompressedData;
}
}
Logger.Info($"Loaded {Entries.Length} GPAK entries from {gpakFileName}");
}
else
{
Logger.Warn($"{gpakFileName} not found");
Logger.Error($"{gpakFileName} not found");
}
}
public void ExportEntries(string fileName)
{
using (StreamWriter streamWriter = new($"{GpakDirectory}\\{fileName}"))
{
foreach (GpakEntry entry in Entries)
{
string entryString = $"{entry.Id}\t{entry.FilePath}\t{entry.Field2}\t{entry.Offset}\t{entry.CompressedSize}\t{entry.UncompressedSize}";
streamWriter.WriteLine(entryString);
}
}
}
public void ExportData()
{
foreach (GpakEntry entry in Entries)
{
string uncompressedFilePath = $"{GpakDirectory}\\{entry.FilePath}";
string uncompressedFileDirectory = Path.GetDirectoryName(uncompressedFilePath);
if (Directory.Exists(uncompressedFileDirectory) == false) Directory.CreateDirectory(uncompressedFileDirectory);
File.WriteAllBytes($"{GpakDirectory}\\{entry.FilePath}", entry.Data);
}
}

View file

@ -15,9 +15,15 @@
<Reference Include="Google.ProtocolBuffers">
<HintPath>..\..\dep\protobuf-csharp\Google.ProtocolBuffers.dll</HintPath>
</Reference>
<Reference Include="K4os.Compression.LZ4">
<HintPath>..\..\dep\K4os.Compression.LZ4\K4os.Compression.LZ4.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Update="Assets\GPAK\IMPORTANT">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Assets\Packets\DangerRoomBeginLoading.bin">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View file

@ -47,6 +47,10 @@ namespace MHServerEmu
_frontendServer.Shutdown();
else if (input.Equals("parse", StringComparison.OrdinalIgnoreCase))
PacketHelper.ParseServerMessagesFromAllPacketFiles();
else if (input.Equals("exportgpakentries", StringComparison.OrdinalIgnoreCase))
Database.ExportGpakEntries();
else if (input.Equals("exportgpakdata", StringComparison.OrdinalIgnoreCase))
Database.ExportGpakData();
}
_frontendServer.Shutdown();