mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2025-12-09 11:11:24 +04:00
Compare commits
37 Commits
backup0103
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8536f938d | ||
|
|
fcfb8d0e8a | ||
|
|
4951550420 | ||
|
|
5ea0cb143f | ||
|
|
75b1548f55 | ||
|
|
3b1fd5ef97 | ||
|
|
60fd173c47 | ||
|
|
3c9b46cbca | ||
|
|
c9d0366637 | ||
|
|
6c9e1d0b98 | ||
|
|
3738881c46 | ||
|
|
c27b77cbfc | ||
|
|
f4442897a6 | ||
|
|
2a78bbebda | ||
|
|
a774db37a6 | ||
|
|
c1ea70efe0 | ||
|
|
be60d8d72f | ||
|
|
f2bed4b141 | ||
|
|
7f0246f996 | ||
|
|
055694a4b4 | ||
|
|
fca052365f | ||
|
|
5c52ab2b2b | ||
|
|
77e7f7652c | ||
|
|
35af4da326 | ||
|
|
4b1c4bf3aa | ||
|
|
4ea756a1a4 | ||
|
|
b9e15541c5 | ||
|
|
67c9020b96 | ||
|
|
476017e9c1 | ||
|
|
ee77738713 | ||
|
|
8e31f43abf | ||
|
|
f5bacc018c | ||
|
|
a6057bf072 | ||
|
|
a419be1fce | ||
|
|
8c4fc8f096 | ||
|
|
135777a4c6 | ||
|
|
76ef68635e |
3
Common/Common.csproj
Normal file
3
Common/Common.csproj
Normal file
@@ -0,0 +1,3 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
</Project>
|
||||
91
Common/Extensions.cs
Normal file
91
Common/Extensions.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace Common;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static int ReadInt32LittleEndian(this Stream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static uint ReadUInt32LittleEndian(this Stream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static ushort ReadUInt16LittleEndian(this Stream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[2];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadUInt16LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static float ReadFloatLittleEndian(this Stream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
||||
}
|
||||
|
||||
public static string ReadNullTerminatedString(this Stream fs)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var b = fs.ReadByte();
|
||||
if (b == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
sb.Append((char)b);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string ReadNullTerminated1251String(this Stream fs)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var b = (byte)fs.ReadByte();
|
||||
if (b == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
sb.Append(Encoding.GetEncoding("windows-1251").GetString([b]));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string ReadLengthPrefixedString(this Stream fs)
|
||||
{
|
||||
var len = fs.ReadInt32LittleEndian();
|
||||
|
||||
if (len == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var buffer = new byte[len];
|
||||
|
||||
fs.ReadExactly(buffer, 0, len);
|
||||
|
||||
return Encoding.ASCII.GetString(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
3
Common/IndexedEdge.cs
Normal file
3
Common/IndexedEdge.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Common;
|
||||
|
||||
public record IndexedEdge(ushort Index1, ushort Index2);
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
namespace Common;
|
||||
|
||||
[DebuggerDisplay("AsInt = {AsInt}, AsFloat = {AsFloat}")]
|
||||
public class IntFloatValue(Span<byte> span)
|
||||
@@ -1,3 +1,3 @@
|
||||
namespace MissionTmaLib;
|
||||
namespace Common;
|
||||
|
||||
public record Vector3(float X, float Y, float Z);
|
||||
31
CpDatLib/CpDatEntry.cs
Normal file
31
CpDatLib/CpDatEntry.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace CpDatLib;
|
||||
|
||||
public record CpDatEntry(
|
||||
string ArchiveFile,
|
||||
string ArchiveEntryName,
|
||||
int Magic1,
|
||||
int Magic2,
|
||||
string Description,
|
||||
DatEntryType Type,
|
||||
int ChildCount, // игра не хранит это число в объекте, но оно есть в файле
|
||||
List<CpDatEntry> Children
|
||||
);
|
||||
|
||||
// Magic3 seems to be a type
|
||||
// 0 - chassis
|
||||
// 1 - turret (у зданий почему-то дефлектор тоже 1), может быть потому, что дефлектор вращается так же как башня у юнитов
|
||||
// 2 - armour
|
||||
// 3 - part
|
||||
// 4 - cannon
|
||||
// 5 - ammo
|
||||
|
||||
public enum DatEntryType
|
||||
{
|
||||
Unspecified = -1,
|
||||
Chassis = 0,
|
||||
Turret = 1,
|
||||
Armour = 2,
|
||||
Part = 3,
|
||||
Cannon = 4,
|
||||
Ammo = 5,
|
||||
}
|
||||
5
CpDatLib/CpDatLib.csproj
Normal file
5
CpDatLib/CpDatLib.csproj
Normal file
@@ -0,0 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
3
CpDatLib/CpDatParseResult.cs
Normal file
3
CpDatLib/CpDatParseResult.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace CpDatLib;
|
||||
|
||||
public record CpDatParseResult(CpDatScheme? Scheme, string? Error);
|
||||
74
CpDatLib/CpDatParser.cs
Normal file
74
CpDatLib/CpDatParser.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Common;
|
||||
|
||||
namespace CpDatLib;
|
||||
|
||||
public class CpDatParser
|
||||
{
|
||||
public static CpDatParseResult Parse(string filePath)
|
||||
{
|
||||
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
return Parse(fs);
|
||||
}
|
||||
|
||||
public static CpDatParseResult Parse(Stream fs)
|
||||
{
|
||||
Span<byte> f0f1 = stackalloc byte[4];
|
||||
|
||||
if (fs.Length < 8)
|
||||
return new CpDatParseResult(null, "File too small to be a valid \"cp\" .dat file.");
|
||||
|
||||
fs.ReadExactly(f0f1);
|
||||
|
||||
if (f0f1[0] != 0xf1 || f0f1[1] != 0xf0)
|
||||
{
|
||||
return new CpDatParseResult(null, "File does not start with expected header bytes f1_f0");
|
||||
}
|
||||
|
||||
var schemeType = (SchemeType)fs.ReadInt32LittleEndian();
|
||||
|
||||
var entryLength =
|
||||
0x6c + 4; // нам нужно прочитать 0x6c (108) байт - это root, и ещё 4 байта - кол-во вложенных объектов
|
||||
if ((fs.Length - 8) % entryLength != 0)
|
||||
{
|
||||
return new CpDatParseResult(null, "File size is not valid according to expected entry length.");
|
||||
}
|
||||
|
||||
CpDatEntry root = ReadEntryRecursive(fs);
|
||||
|
||||
var scheme = new CpDatScheme(schemeType, root);
|
||||
|
||||
return new CpDatParseResult(scheme, null);
|
||||
}
|
||||
|
||||
private static CpDatEntry ReadEntryRecursive(Stream fs)
|
||||
{
|
||||
var str1 = fs.ReadNullTerminatedString();
|
||||
|
||||
fs.Seek(32 - str1.Length - 1, SeekOrigin.Current); // -1 ignore null terminator
|
||||
|
||||
var str2 = fs.ReadNullTerminatedString();
|
||||
|
||||
fs.Seek(32 - str2.Length - 1, SeekOrigin.Current); // -1 ignore null terminator
|
||||
var magic1 = fs.ReadInt32LittleEndian();
|
||||
var magic2 = fs.ReadInt32LittleEndian();
|
||||
|
||||
var descriptionString = fs.ReadNullTerminated1251String();
|
||||
|
||||
fs.Seek(32 - descriptionString.Length - 1, SeekOrigin.Current); // -1 ignore null terminator
|
||||
var type = (DatEntryType)fs.ReadInt32LittleEndian();
|
||||
|
||||
// игра не читает количество внутрь схемы, вместо этого она сразу рекурсией читает нужно количество вложенных объектов
|
||||
var childCount = fs.ReadInt32LittleEndian();
|
||||
|
||||
List<CpDatEntry> children = new List<CpDatEntry>(childCount);
|
||||
|
||||
for (var i = 0; i < childCount; i++)
|
||||
{
|
||||
var child = ReadEntryRecursive(fs);
|
||||
children.Add(child);
|
||||
}
|
||||
|
||||
return new CpDatEntry(str1, str2, magic1, magic2, descriptionString, type, childCount, Children: children);
|
||||
}
|
||||
}
|
||||
3
CpDatLib/CpDatScheme.cs
Normal file
3
CpDatLib/CpDatScheme.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace CpDatLib;
|
||||
|
||||
public record CpDatScheme(SchemeType Type, CpDatEntry Root);
|
||||
29
CpDatLib/CpEntryType.cs
Normal file
29
CpDatLib/CpEntryType.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace CpDatLib;
|
||||
|
||||
public enum SchemeType : uint
|
||||
{
|
||||
ClassBuilding = 0x80000000,
|
||||
ClassRobot = 0x01000000,
|
||||
ClassAnimal = 0x20000000,
|
||||
|
||||
BunkerSmall = 0x80010000,
|
||||
BunkerMedium = 0x80020000,
|
||||
BunkerLarge = 0x80040000,
|
||||
Generator = 0x80000002,
|
||||
Mine = 0x80000004,
|
||||
Storage = 0x80000008,
|
||||
Plant = 0x80000010,
|
||||
Hangar = 0x80000040,
|
||||
TowerMedium = 0x80100000,
|
||||
TowerLarge = 0x80200000,
|
||||
MainTeleport = 0x80000200,
|
||||
Institute = 0x80000400,
|
||||
Bridge = 0x80001000,
|
||||
Ruine = 0x80002000,
|
||||
|
||||
RobotTransport = 0x01002000,
|
||||
RobotBuilder = 0x01004000,
|
||||
RobotBattleunit = 0x01008000,
|
||||
RobotHq = 0x01010000,
|
||||
RobotHero = 0x01020000,
|
||||
}
|
||||
19
Directory.Build.props
Normal file
19
Directory.Build.props
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
<!-- Enable Central Package Management -->
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
|
||||
<!-- Enforce package version consistency -->
|
||||
<EnablePackageVersionOverride>false</EnablePackageVersionOverride>
|
||||
|
||||
<!-- Suppress package version warnings -->
|
||||
<NoWarn>$(NoWarn);NU1507;CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
16
Directory.Packages.props
Normal file
16
Directory.Packages.props
Normal file
@@ -0,0 +1,16 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<!-- Package versions used across the solution -->
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageVersion Include="NativeFileDialogSharp" Version="0.5.0" />
|
||||
<PackageVersion Include="Silk.NET" Version="2.22.0" />
|
||||
<PackageVersion Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" />
|
||||
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
373
LICENSE
Normal file
373
LICENSE
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
82
MaterialLib/Interpolate.c
Normal file
82
MaterialLib/Interpolate.c
Normal file
@@ -0,0 +1,82 @@
|
||||
|
||||
void __cdecl
|
||||
Interpolate(MaterialExternal *src1_ptr,MaterialExternal *src2_ptr,float progress,
|
||||
MaterialExternal *dst_ptr,uint targetFlags)
|
||||
|
||||
{
|
||||
if ((targetFlags & 2) == 0) {
|
||||
(dst_ptr->stage).diffuse.R = (src1_ptr->stage).diffuse.R;
|
||||
(dst_ptr->stage).diffuse.G = (src1_ptr->stage).diffuse.G;
|
||||
(dst_ptr->stage).diffuse.B = (src1_ptr->stage).diffuse.B;
|
||||
}
|
||||
else {
|
||||
(dst_ptr->stage).diffuse.R =
|
||||
((src2_ptr->stage).diffuse.R - (src1_ptr->stage).diffuse.R) * progress +
|
||||
(src1_ptr->stage).diffuse.R;
|
||||
(dst_ptr->stage).diffuse.G =
|
||||
((src2_ptr->stage).diffuse.G - (src1_ptr->stage).diffuse.G) * progress +
|
||||
(src1_ptr->stage).diffuse.G;
|
||||
(dst_ptr->stage).diffuse.B =
|
||||
((src2_ptr->stage).diffuse.B - (src1_ptr->stage).diffuse.B) * progress +
|
||||
(src1_ptr->stage).diffuse.B;
|
||||
}
|
||||
if ((targetFlags & 1) == 0) {
|
||||
(dst_ptr->stage).ambient.R = (src1_ptr->stage).ambient.R;
|
||||
(dst_ptr->stage).ambient.G = (src1_ptr->stage).ambient.G;
|
||||
(dst_ptr->stage).ambient.B = (src1_ptr->stage).ambient.B;
|
||||
}
|
||||
else {
|
||||
(dst_ptr->stage).ambient.R =
|
||||
((src2_ptr->stage).ambient.R - (src1_ptr->stage).ambient.R) * progress +
|
||||
(src1_ptr->stage).ambient.R;
|
||||
(dst_ptr->stage).ambient.G =
|
||||
((src2_ptr->stage).ambient.G - (src1_ptr->stage).ambient.G) * progress +
|
||||
(src1_ptr->stage).ambient.G;
|
||||
(dst_ptr->stage).ambient.B =
|
||||
((src2_ptr->stage).ambient.B - (src1_ptr->stage).ambient.B) * progress +
|
||||
(src1_ptr->stage).ambient.B;
|
||||
}
|
||||
if ((targetFlags & 4) == 0) {
|
||||
(dst_ptr->stage).specular.R = (src1_ptr->stage).specular.R;
|
||||
(dst_ptr->stage).specular.G = (src1_ptr->stage).specular.G;
|
||||
(dst_ptr->stage).specular.B = (src1_ptr->stage).specular.B;
|
||||
}
|
||||
else {
|
||||
(dst_ptr->stage).specular.R =
|
||||
((src2_ptr->stage).specular.R - (src1_ptr->stage).specular.R) * progress +
|
||||
(src1_ptr->stage).specular.R;
|
||||
(dst_ptr->stage).specular.G =
|
||||
((src2_ptr->stage).specular.G - (src1_ptr->stage).specular.G) * progress +
|
||||
(src1_ptr->stage).specular.G;
|
||||
(dst_ptr->stage).specular.B =
|
||||
((src2_ptr->stage).specular.B - (src1_ptr->stage).specular.B) * progress +
|
||||
(src1_ptr->stage).specular.B;
|
||||
}
|
||||
if ((targetFlags & 8) == 0) {
|
||||
(dst_ptr->stage).emissive.R = (src1_ptr->stage).emissive.R;
|
||||
(dst_ptr->stage).emissive.G = (src1_ptr->stage).emissive.G;
|
||||
(dst_ptr->stage).emissive.B = (src1_ptr->stage).emissive.B;
|
||||
}
|
||||
else {
|
||||
(dst_ptr->stage).emissive.R =
|
||||
((src2_ptr->stage).emissive.R - (src1_ptr->stage).emissive.R) * progress +
|
||||
(src1_ptr->stage).emissive.R;
|
||||
(dst_ptr->stage).emissive.G =
|
||||
((src2_ptr->stage).emissive.G - (src1_ptr->stage).emissive.G) * progress +
|
||||
(src1_ptr->stage).emissive.G;
|
||||
(dst_ptr->stage).emissive.B =
|
||||
((src2_ptr->stage).emissive.B - (src1_ptr->stage).emissive.B) * progress +
|
||||
(src1_ptr->stage).emissive.B;
|
||||
}
|
||||
if ((targetFlags & 0x10) != 0) {
|
||||
(dst_ptr->stage).ambient.A =
|
||||
((src2_ptr->stage).ambient.A - (src1_ptr->stage).ambient.A) * progress +
|
||||
(src1_ptr->stage).ambient.A;
|
||||
(dst_ptr->stage).Power = (src1_ptr->stage).Power;
|
||||
return;
|
||||
}
|
||||
(dst_ptr->stage).ambient.A = (src1_ptr->stage).ambient.A;
|
||||
(dst_ptr->stage).Power = (src1_ptr->stage).Power;
|
||||
return;
|
||||
}
|
||||
|
||||
167
MaterialLib/MaterialFile.cs
Normal file
167
MaterialLib/MaterialFile.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
namespace MaterialLib;
|
||||
|
||||
public class MaterialFile
|
||||
{
|
||||
// === Metadata (not from file content) ===
|
||||
public string FileName { get; set; }
|
||||
public int Version { get; set; } // From NRes ElementCount
|
||||
public int Magic1 { get; set; } // From NRes Magic1
|
||||
|
||||
// === Derived from Version/ElementCount ===
|
||||
public int MaterialRenderingType { get; set; } // (Version >> 2) & 0xF - 0=Standard, 1=Special, 2=Particle
|
||||
public bool SupportsBumpMapping { get; set; } // (Version & 2) != 0
|
||||
public int IsParticleEffect { get; set; } // Version & 40 - 0=Normal, 8=Particle/Effect
|
||||
|
||||
// === File Content (in read order) ===
|
||||
// Read order: StageCount (ushort), AnimCount (ushort), then conditionally blend modes and params
|
||||
|
||||
// Global Blend Modes (read if Magic1 >= 2)
|
||||
public BlendMode SourceBlendMode { get; set; } // Default: Unknown (0xFF)
|
||||
public BlendMode DestBlendMode { get; set; } // Default: Unknown (0xFF)
|
||||
|
||||
// Global Parameters (read if Magic1 > 2 and > 3 respectively)
|
||||
public float GlobalAlphaMultiplier { get; set; } // Default: 1.0 (always 1.0 in all 628 materials)
|
||||
public float GlobalEmissiveIntensity { get; set; } // Default: 0.0 (0=no glow, rare values: 1000, 10000)
|
||||
|
||||
public List<MaterialStage> Stages { get; set; } = new();
|
||||
public List<MaterialAnimation> Animations { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend modes for material rendering. These control how source and destination colors are combined.
|
||||
/// Formula: FinalColor = (SourceColor * SourceBlend) [operation] (DestColor * DestBlend)
|
||||
/// Maps to Direct3D D3DBLEND values.
|
||||
/// </summary>
|
||||
public enum BlendMode : byte
|
||||
{
|
||||
/// <summary>Blend factor is (0, 0, 0, 0) - results in black/transparent</summary>
|
||||
Zero = 1,
|
||||
|
||||
/// <summary>Blend factor is (1, 1, 1, 1) - uses full color value</summary>
|
||||
One = 2,
|
||||
|
||||
/// <summary>Blend factor is (Rs, Gs, Bs, As) - uses source color</summary>
|
||||
SrcColor = 3,
|
||||
|
||||
/// <summary>Blend factor is (1-Rs, 1-Gs, 1-Bs, 1-As) - uses inverted source color</summary>
|
||||
InvSrcColor = 4,
|
||||
|
||||
/// <summary>Blend factor is (As, As, As, As) - uses source alpha for all channels (standard transparency)</summary>
|
||||
SrcAlpha = 5,
|
||||
|
||||
/// <summary>Blend factor is (1-As, 1-As, 1-As, 1-As) - uses inverted source alpha</summary>
|
||||
InvSrcAlpha = 6,
|
||||
|
||||
/// <summary>Blend factor is (Ad, Ad, Ad, Ad) - uses destination alpha</summary>
|
||||
DestAlpha = 7,
|
||||
|
||||
/// <summary>Blend factor is (1-Ad, 1-Ad, 1-Ad, 1-Ad) - uses inverted destination alpha</summary>
|
||||
InvDestAlpha = 8,
|
||||
|
||||
/// <summary>Blend factor is (Rd, Gd, Bd, Ad) - uses destination color</summary>
|
||||
DestColor = 9,
|
||||
|
||||
/// <summary>Blend factor is (1-Rd, 1-Gd, 1-Bd, 1-Ad) - uses inverted destination color</summary>
|
||||
InvDestColor = 10,
|
||||
|
||||
/// <summary>Blend factor is (f, f, f, 1) where f = min(As, 1-Ad) - saturates source alpha</summary>
|
||||
SrcAlphaSat = 11,
|
||||
|
||||
/// <summary>Blend factor is (As, As, As, As) for both source and dest - obsolete in D3D9+</summary>
|
||||
BothSrcAlpha = 12,
|
||||
|
||||
/// <summary>Blend factor is (1-As, 1-As, 1-As, 1-As) for both source and dest - obsolete in D3D9+</summary>
|
||||
BothInvSrcAlpha = 13,
|
||||
|
||||
/// <summary>Unknown or uninitialized blend mode (0xFF default value)</summary>
|
||||
Unknown = 0xFF
|
||||
}
|
||||
|
||||
public class MaterialStage
|
||||
{
|
||||
// === FILE READ ORDER (34 bytes per stage) ===
|
||||
// This matches the order bytes are read from the file (decompiled.c lines 159-217)
|
||||
// NOT the C struct memory layout (which is Diffuse, Ambient, Specular, Emissive)
|
||||
|
||||
// 1. Ambient Color (4 bytes, read first from file)
|
||||
public float AmbientR;
|
||||
public float AmbientG;
|
||||
public float AmbientB;
|
||||
public float AmbientA; // Scaled by 0.01 when read from file
|
||||
|
||||
// 2. Diffuse Color (4 bytes, read second from file)
|
||||
public float DiffuseR;
|
||||
public float DiffuseG;
|
||||
public float DiffuseB;
|
||||
public float DiffuseA;
|
||||
|
||||
// 3. Specular Color (4 bytes, read third from file)
|
||||
public float SpecularR;
|
||||
public float SpecularG;
|
||||
public float SpecularB;
|
||||
public float SpecularA;
|
||||
|
||||
// 4. Emissive Color (4 bytes, read fourth from file)
|
||||
public float EmissiveR;
|
||||
public float EmissiveG;
|
||||
public float EmissiveB;
|
||||
public float EmissiveA;
|
||||
|
||||
// 5. Power (1 byte → float, read fifth from file)
|
||||
public float Power;
|
||||
|
||||
// 6. Texture Stage Index (1 byte, read sixth from file)
|
||||
// 255 = not set/default, 0-47 = reference to specific texture stage
|
||||
public int TextureStageIndex;
|
||||
|
||||
// 7. Texture Name (16 bytes, read seventh from file)
|
||||
public string TextureName { get; set; }
|
||||
}
|
||||
|
||||
public class MaterialAnimation
|
||||
{
|
||||
// === File Read Order ===
|
||||
|
||||
// Combined field (4 bytes): bits 3-31 = Target, bits 0-2 = LoopMode
|
||||
public AnimationTarget Target;
|
||||
public AnimationLoopMode LoopMode;
|
||||
|
||||
// Key count (2 bytes), then keys
|
||||
public List<AnimKey> Keys { get; set; } = new();
|
||||
|
||||
// Cached description for UI (computed once during parsing)
|
||||
public string TargetDescription { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
[Flags]
|
||||
public enum AnimationTarget : int
|
||||
{
|
||||
// NOTE: This is a BITSET (flags enum). Multiple flags can be combined.
|
||||
// When a flag is SET, that component is INTERPOLATED between stages.
|
||||
// When a flag is NOT SET, that component is COPIED from the source stage (no interpolation).
|
||||
// If ALL flags are 0, the ENTIRE stage is copied without any interpolation.
|
||||
|
||||
Ambient = 1, // 0x01 - Interpolates Ambient RGB (Interpolate.c lines 23-37)
|
||||
Diffuse = 2, // 0x02 - Interpolates Diffuse RGB (Interpolate.c lines 7-21)
|
||||
Specular = 4, // 0x04 - Interpolates Specular RGB (Interpolate.c lines 39-53)
|
||||
Emissive = 8, // 0x08 - Interpolates Emissive RGB (Interpolate.c lines 55-69)
|
||||
Power = 16 // 0x10 - Interpolates Ambient.A and sets Power (Interpolate.c lines 71-76)
|
||||
}
|
||||
|
||||
|
||||
public enum AnimationLoopMode : int
|
||||
{
|
||||
Loop = 0,
|
||||
PingPong = 1,
|
||||
Clamp = 2,
|
||||
Random = 3
|
||||
}
|
||||
|
||||
public struct AnimKey
|
||||
{
|
||||
// === File Read Order (6 bytes per key) ===
|
||||
public ushort StageIndex; // Read first
|
||||
public ushort DurationMs; // Read second
|
||||
public ushort InterpolationCurve; // Read third - Always 0 (linear interpolation) in all 1848 keys
|
||||
}
|
||||
5
MaterialLib/MaterialLib.csproj
Normal file
5
MaterialLib/MaterialLib.csproj
Normal file
@@ -0,0 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
176
MaterialLib/MaterialParser.cs
Normal file
176
MaterialLib/MaterialParser.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace MaterialLib;
|
||||
|
||||
public static class MaterialParser
|
||||
{
|
||||
public static MaterialFile ReadFromStream(Stream fs, string fileName, int elementCount, int magic1)
|
||||
{
|
||||
var file = new MaterialFile
|
||||
{
|
||||
FileName = fileName,
|
||||
Version = elementCount,
|
||||
Magic1 = magic1
|
||||
};
|
||||
|
||||
// Derived fields
|
||||
file.MaterialRenderingType = elementCount >> 2 & 0xf;
|
||||
file.SupportsBumpMapping = (elementCount & 2) != 0;
|
||||
file.IsParticleEffect = elementCount & 40;
|
||||
|
||||
// Reading content
|
||||
var stageCount = fs.ReadUInt16LittleEndian();
|
||||
var animCount = fs.ReadUInt16LittleEndian();
|
||||
|
||||
uint magic = (uint)magic1;
|
||||
|
||||
// Defaults found in C code
|
||||
file.GlobalAlphaMultiplier = 1.0f; // field8_0x15c
|
||||
file.GlobalEmissiveIntensity = 0.0f; // field9_0x160
|
||||
file.SourceBlendMode = BlendMode.Unknown; // field6_0x154
|
||||
file.DestBlendMode = BlendMode.Unknown; // field7_0x158
|
||||
|
||||
if (magic >= 2)
|
||||
{
|
||||
file.SourceBlendMode = (BlendMode)fs.ReadByte();
|
||||
file.DestBlendMode = (BlendMode)fs.ReadByte();
|
||||
}
|
||||
|
||||
if (magic > 2)
|
||||
{
|
||||
file.GlobalAlphaMultiplier = fs.ReadFloatLittleEndian();
|
||||
}
|
||||
|
||||
if (magic > 3)
|
||||
{
|
||||
file.GlobalEmissiveIntensity = fs.ReadFloatLittleEndian();
|
||||
}
|
||||
|
||||
// --- 2. Material Stages ---
|
||||
const float Inv255 = 1.0f / 255.0f;
|
||||
const float Field7Mult = 0.01f;
|
||||
Span<byte> textureNameBuffer = stackalloc byte[16];
|
||||
|
||||
for (int i = 0; i < stageCount; i++)
|
||||
{
|
||||
var stage = new MaterialStage();
|
||||
|
||||
// === FILE READ ORDER (matches decompiled.c lines 159-217) ===
|
||||
|
||||
// 1. Ambient (4 bytes, A scaled by 0.01) - Lines 159-168
|
||||
stage.AmbientR = fs.ReadByte() * Inv255;
|
||||
stage.AmbientG = fs.ReadByte() * Inv255;
|
||||
stage.AmbientB = fs.ReadByte() * Inv255;
|
||||
stage.AmbientA = fs.ReadByte() * Field7Mult; // 0.01 scaling
|
||||
|
||||
// 2. Diffuse (4 bytes) - Lines 171-180
|
||||
stage.DiffuseR = fs.ReadByte() * Inv255;
|
||||
stage.DiffuseG = fs.ReadByte() * Inv255;
|
||||
stage.DiffuseB = fs.ReadByte() * Inv255;
|
||||
stage.DiffuseA = fs.ReadByte() * Inv255;
|
||||
|
||||
// 3. Specular (4 bytes) - Lines 183-192
|
||||
stage.SpecularR = fs.ReadByte() * Inv255;
|
||||
stage.SpecularG = fs.ReadByte() * Inv255;
|
||||
stage.SpecularB = fs.ReadByte() * Inv255;
|
||||
stage.SpecularA = fs.ReadByte() * Inv255;
|
||||
|
||||
// 4. Emissive (4 bytes) - Lines 195-204
|
||||
stage.EmissiveR = fs.ReadByte() * Inv255;
|
||||
stage.EmissiveG = fs.ReadByte() * Inv255;
|
||||
stage.EmissiveB = fs.ReadByte() * Inv255;
|
||||
stage.EmissiveA = fs.ReadByte() * Inv255;
|
||||
|
||||
// 5. Power (1 byte → float) - Line 207
|
||||
stage.Power = (float)fs.ReadByte();
|
||||
|
||||
// 6. Texture Stage Index (1 byte) - Line 210
|
||||
stage.TextureStageIndex = fs.ReadByte();
|
||||
|
||||
// 7. Texture Name (16 bytes) - Lines 212-217
|
||||
textureNameBuffer.Clear();
|
||||
fs.ReadExactly(textureNameBuffer);
|
||||
stage.TextureName = Encoding.ASCII.GetString(textureNameBuffer).TrimEnd('\0');
|
||||
|
||||
file.Stages.Add(stage);
|
||||
}
|
||||
|
||||
// --- 3. Animations ---
|
||||
for (int i = 0; i < animCount; i++)
|
||||
{
|
||||
var anim = new MaterialAnimation();
|
||||
|
||||
uint typeAndParams = fs.ReadUInt32LittleEndian();
|
||||
anim.Target = (AnimationTarget)(typeAndParams >> 3);
|
||||
anim.LoopMode = (AnimationLoopMode)(typeAndParams & 7);
|
||||
|
||||
ushort keyCount = fs.ReadUInt16LittleEndian();
|
||||
|
||||
for (int k = 0; k < keyCount; k++)
|
||||
{
|
||||
var key = new AnimKey
|
||||
{
|
||||
StageIndex = fs.ReadUInt16LittleEndian(),
|
||||
DurationMs = fs.ReadUInt16LittleEndian(),
|
||||
InterpolationCurve = fs.ReadUInt16LittleEndian()
|
||||
};
|
||||
anim.Keys.Add(key);
|
||||
}
|
||||
|
||||
// Precompute description for UI to avoid per-frame allocations
|
||||
anim.TargetDescription = ComputeTargetDescription(anim.Target);
|
||||
|
||||
file.Animations.Add(anim);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private static string ComputeTargetDescription(AnimationTarget target)
|
||||
{
|
||||
// Precompute the description once during parsing
|
||||
if ((int)target == 0)
|
||||
return "No interpolation - entire stage is copied as-is (Flags: 0x0)";
|
||||
|
||||
var parts = new List<string>();
|
||||
|
||||
if ((target & AnimationTarget.Ambient) != 0)
|
||||
parts.Add("Ambient RGB");
|
||||
if ((target & AnimationTarget.Diffuse) != 0)
|
||||
parts.Add("Diffuse RGB");
|
||||
if ((target & AnimationTarget.Specular) != 0)
|
||||
parts.Add("Specular RGB");
|
||||
if ((target & AnimationTarget.Emissive) != 0)
|
||||
parts.Add("Emissive RGB");
|
||||
if ((target & AnimationTarget.Power) != 0)
|
||||
parts.Add("Ambient.A + Power");
|
||||
|
||||
return $"Interpolates: {string.Join(", ", parts)} | Other components copied (Flags: 0x{(int)target:X})";
|
||||
}
|
||||
}
|
||||
|
||||
// Helper extensions
|
||||
internal static class StreamExtensions
|
||||
{
|
||||
public static float ReadFloatLittleEndian(this Stream stream)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
stream.ReadExactly(buffer);
|
||||
return BinaryPrimitives.ReadSingleLittleEndian(buffer);
|
||||
}
|
||||
|
||||
public static ushort ReadUInt16LittleEndian(this Stream stream)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[2];
|
||||
stream.ReadExactly(buffer);
|
||||
return BinaryPrimitives.ReadUInt16LittleEndian(buffer);
|
||||
}
|
||||
|
||||
public static uint ReadUInt32LittleEndian(this Stream stream)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
stream.ReadExactly(buffer);
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(buffer);
|
||||
}
|
||||
}
|
||||
290
MaterialLib/decompiled.c
Normal file
290
MaterialLib/decompiled.c
Normal file
@@ -0,0 +1,290 @@
|
||||
|
||||
/* returns index into g_MaterialGlobalDescriptors */
|
||||
|
||||
int __fastcall LoadMaterialByName(char *itemname)
|
||||
|
||||
{
|
||||
char *pcVar1;
|
||||
undefined1 *puVar2;
|
||||
byte *pbVar3;
|
||||
uint uVar4;
|
||||
undefined4 *puVar5;
|
||||
int iVar6;
|
||||
int iVar7;
|
||||
MaterialGlobalDescriptor_ptr_4_undefined4 piVar8;
|
||||
nres_metadata_item *pnVar8;
|
||||
int iVar9;
|
||||
MaterialStageWorldDllInternal *pMVar10;
|
||||
int iVar11;
|
||||
ITexture **ppIVar12;
|
||||
MaterialAnimationKey *pMVar13;
|
||||
MaterialGlobalDescriptor *pMVar14;
|
||||
float unaff_EBX;
|
||||
MaterialStageExternal *pfVar16;
|
||||
ushort unaff_DI;
|
||||
MaterialAnimation *pMVar15;
|
||||
uint uVar16;
|
||||
int iStack_90;
|
||||
uint uStack_8c;
|
||||
char acStack_88 [8];
|
||||
int iStack_80;
|
||||
char acStack_58 [88];
|
||||
|
||||
iVar6 = (*(*g_material_resfile)->get_index_in_file_by_itemname)(g_material_resfile,itemname);
|
||||
if (iVar6 < 0) {
|
||||
sprintf(acStack_58,s_Material_%s_not_found._100246f8,itemname);
|
||||
/* WARNING: Subroutine does not return */
|
||||
write_error_to_file_and_msgbox(acStack_58,(void *)0x0);
|
||||
}
|
||||
iVar7 = 0;
|
||||
if (0 < g_MaterialGlobalDescriptor_count) {
|
||||
pMVar14 = g_MaterialGlobalDescriptors;
|
||||
do {
|
||||
if (pMVar14->index_in_file == iVar6) {
|
||||
if (-1 < iVar7) {
|
||||
g_MaterialGlobalDescriptors[iVar7].RefCount =
|
||||
g_MaterialGlobalDescriptors[iVar7].RefCount + 1;
|
||||
return iVar7;
|
||||
}
|
||||
break;
|
||||
}
|
||||
iVar7 += 1;
|
||||
pMVar14 = pMVar14 + 1;
|
||||
} while (iVar7 < g_MaterialGlobalDescriptor_count);
|
||||
}
|
||||
iVar7 = 0;
|
||||
if (0 < g_MaterialGlobalDescriptor_count) {
|
||||
piVar8 = &g_MaterialGlobalDescriptors[0].RefCount;
|
||||
do {
|
||||
if (ADJ(piVar8)->RefCount == 0) break;
|
||||
iVar7 += 1;
|
||||
piVar8 = piVar8 + 0x5c;
|
||||
} while (iVar7 < g_MaterialGlobalDescriptor_count);
|
||||
}
|
||||
if (iVar7 == g_MaterialGlobalDescriptor_count) {
|
||||
g_MaterialGlobalDescriptor_count += 1;
|
||||
}
|
||||
iStack_80 = iVar7;
|
||||
g_currentMaterialFileData_ptr =
|
||||
(*(*g_material_resfile)->get_item_data_ptr_by_index)(g_material_resfile,iVar6,1);
|
||||
pnVar8 = (*(*g_material_resfile)->get_metadata_ptr)(g_material_resfile);
|
||||
uVar16 = 0;
|
||||
uVar4 = pnVar8[iVar6].magic1;
|
||||
if ((pnVar8[iVar6].element_count_or_version & 1) != 0) {
|
||||
uVar16 = 0x200000;
|
||||
}
|
||||
g_MaterialGlobalDescriptors[iVar7].extra_meta.field0_0x0 = 0;
|
||||
g_MaterialGlobalDescriptors[iVar7].extra_meta.field2_0x8 = 0;
|
||||
g_MaterialGlobalDescriptors[iVar7].extra_meta.field1_0x4 =
|
||||
pnVar8[iVar6].element_count_or_version >> 2 & 0xf;
|
||||
iVar9 = g_bumpmapping_enabled_value;
|
||||
if (((pnVar8[iVar6].element_count_or_version & 2) != 0) &&
|
||||
((g_MaterialGlobalDescriptors[iVar7].extra_meta.field0_0x0 = 1, iVar9 == 0 ||
|
||||
(g_supports_texture_mode_6 == 0)))) {
|
||||
uVar16 |= 0x80000;
|
||||
}
|
||||
if ((pnVar8[iVar6].element_count_or_version & 0x40) != 0) {
|
||||
g_MaterialGlobalDescriptors[iVar7].extra_meta.field2_0x8 = 1;
|
||||
}
|
||||
iVar9 = 0;
|
||||
g_MaterialGlobalDescriptors[iVar7].RefCount = g_MaterialGlobalDescriptors[iVar7].RefCount + 1;
|
||||
puVar5 = g_currentMaterialFileData_ptr;
|
||||
g_MaterialGlobalDescriptors[iVar7].index_in_file = iVar6;
|
||||
iVar6 = 0;
|
||||
do {
|
||||
pcVar1 = (char *)(iVar9 + (int)puVar5);
|
||||
iVar9 += 1;
|
||||
acStack_88[iVar6 + -0x1c] = *pcVar1;
|
||||
iVar6 += 1;
|
||||
} while (iVar6 < 2);
|
||||
g_MaterialGlobalDescriptors[iVar7].stageCount = (uint)unaff_DI;
|
||||
iVar6 = 0;
|
||||
do {
|
||||
iVar11 = iVar9;
|
||||
iVar9 = iVar11 + 1;
|
||||
acStack_88[iVar6 + -0x1c] = *(char *)(iVar11 + (int)puVar5);
|
||||
iVar6 += 1;
|
||||
} while (iVar6 < 2);
|
||||
DAT_10128674 = iVar9;
|
||||
g_MaterialGlobalDescriptors[iVar7].animCount = (uint)unaff_DI;
|
||||
if (0x13 < unaff_DI) {
|
||||
/* WARNING: Subroutine does not return */
|
||||
write_error_to_file_and_msgbox(s_Too_many_animations_for_material_100246cc,(void *)0x0);
|
||||
}
|
||||
g_MaterialGlobalDescriptors[iVar7].field8_0x15c = 1.0;
|
||||
g_MaterialGlobalDescriptors[iVar7].field9_0x160 = 0;
|
||||
if (uVar4 < 2) {
|
||||
g_MaterialGlobalDescriptors[iVar7].field6_0x154 = 0xff;
|
||||
g_MaterialGlobalDescriptors[iVar7].field7_0x158 = 0xff;
|
||||
}
|
||||
else {
|
||||
g_MaterialGlobalDescriptors[iVar7].field6_0x154 = (uint)*(byte *)(iVar9 + (int)puVar5);
|
||||
iVar6 = iVar11 + 3;
|
||||
DAT_10128674 = iVar6;
|
||||
g_MaterialGlobalDescriptors[iVar7].field7_0x158 = (uint)*(byte *)(iVar11 + 2 + (int)puVar5);
|
||||
if (2 < uVar4) {
|
||||
iVar9 = 0;
|
||||
do {
|
||||
puVar2 = (undefined1 *)(iVar6 + (int)puVar5);
|
||||
iVar6 += 1;
|
||||
(&stack0xffffff68)[iVar9] = *puVar2;
|
||||
iVar9 += 1;
|
||||
} while (iVar9 < 4);
|
||||
DAT_10128674 = iVar6;
|
||||
g_MaterialGlobalDescriptors[iVar7].field8_0x15c = unaff_EBX;
|
||||
if (3 < uVar4) {
|
||||
iVar9 = 0;
|
||||
do {
|
||||
puVar2 = (undefined1 *)(iVar6 + (int)puVar5);
|
||||
iVar6 += 1;
|
||||
(&stack0xffffff68)[iVar9] = *puVar2;
|
||||
iVar9 += 1;
|
||||
} while (iVar9 < 4);
|
||||
DAT_10128674 = iVar6;
|
||||
g_MaterialGlobalDescriptors[iVar7].field9_0x160 = unaff_EBX;
|
||||
}
|
||||
}
|
||||
}
|
||||
pMVar10 = (MaterialStageWorldDllInternal *)
|
||||
_malloc(g_MaterialGlobalDescriptors[iVar7].stageCount * 0x4c);
|
||||
g_MaterialGlobalDescriptors[iVar7].stages = pMVar10;
|
||||
iVar6 = 0;
|
||||
if (0 < g_MaterialGlobalDescriptors[iVar7].stageCount) {
|
||||
iVar9 = 0;
|
||||
do {
|
||||
pfVar16 = (MaterialStageExternal *)
|
||||
((int)&((g_MaterialGlobalDescriptors[iVar7].stages)->diffuse).R + iVar9);
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->ambient).R = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->ambient).G = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->ambient).B = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->ambient).A = (float)*pbVar3 * 0.01;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->diffuse).R = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->diffuse).G = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->diffuse).B = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->diffuse).A = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->specular).R = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->specular).G = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->specular).B = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->specular).A = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->emissive).R = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->emissive).G = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->emissive).B = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
(pfVar16->emissive).A = (float)*pbVar3 * 1/255f;
|
||||
pbVar3 = (byte *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
pfVar16->Power = (float)(uint)*pbVar3;
|
||||
pcVar1 = (char *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 = DAT_10128674 + 1;
|
||||
pfVar16->current_m_LL_ITexture = (ITexture **)(int)*pcVar1;
|
||||
iVar11 = 0;
|
||||
do {
|
||||
pcVar1 = (char *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
acStack_88[iVar11] = *pcVar1;
|
||||
iVar11 += 1;
|
||||
} while (iVar11 < 0x10);
|
||||
if (acStack_88[0] == '\0') {
|
||||
pfVar16->m_LL_ITexture = (ITexture **)0xffffffff;
|
||||
pfVar16->current_m_LL_ITexture = (ITexture **)0xffffffff;
|
||||
}
|
||||
else {
|
||||
ppIVar12 = (ITexture **)LoadAndCacheTexture(acStack_88,uVar16);
|
||||
pfVar16->m_LL_ITexture = ppIVar12;
|
||||
}
|
||||
iVar6 += 1;
|
||||
iVar9 += 0x4c;
|
||||
} while (iVar6 < g_MaterialGlobalDescriptors[iVar7].stageCount);
|
||||
}
|
||||
iVar6 = 0;
|
||||
if (0 < g_MaterialGlobalDescriptors[iVar7].animCount) {
|
||||
pMVar15 = g_MaterialGlobalDescriptors[iVar7].animations;
|
||||
do {
|
||||
puVar5 = g_currentMaterialFileData_ptr;
|
||||
iVar9 = 0;
|
||||
do {
|
||||
pcVar1 = (char *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
acStack_88[iVar9 + -4] = *pcVar1;
|
||||
iVar9 += 1;
|
||||
} while (iVar9 < 4);
|
||||
pMVar15->FieldSelector = (int)uStack_8c >> 3;
|
||||
pMVar15->loop_mode = uStack_8c & 7;
|
||||
iVar9 = 0;
|
||||
do {
|
||||
pcVar1 = (char *)(DAT_10128674 + (int)puVar5);
|
||||
DAT_10128674 += 1;
|
||||
acStack_88[iVar9 + -0x1c] = *pcVar1;
|
||||
iVar9 += 1;
|
||||
} while (iVar9 < 2);
|
||||
pMVar15->keyCount = (uint)unaff_DI;
|
||||
pMVar13 = (MaterialAnimationKey *)_malloc((uint)unaff_DI << 3);
|
||||
pMVar15->keys = pMVar13;
|
||||
iVar9 = 0;
|
||||
if (0 < (int)pMVar15->keyCount) {
|
||||
do {
|
||||
iVar11 = 0;
|
||||
do {
|
||||
pcVar1 = (char *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
acStack_88[iVar11 + -0x1c] = *pcVar1;
|
||||
iVar11 += 1;
|
||||
} while (iVar11 < 2);
|
||||
pMVar15->keys[iVar9].stage_index = (uint)unaff_DI;
|
||||
iVar11 = 0;
|
||||
do {
|
||||
pcVar1 = (char *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
acStack_88[iVar11 + -0x1c] = *pcVar1;
|
||||
iVar11 += 1;
|
||||
} while (iVar11 < 2);
|
||||
pMVar15->keys[iVar9].duration_ms = unaff_DI;
|
||||
iVar11 = 0;
|
||||
do {
|
||||
pcVar1 = (char *)(DAT_10128674 + (int)g_currentMaterialFileData_ptr);
|
||||
DAT_10128674 += 1;
|
||||
acStack_88[iVar11 + -0x1c] = *pcVar1;
|
||||
iVar11 += 1;
|
||||
} while (iVar11 < 2);
|
||||
pMVar15->keys[iVar9].field2_0x6 = unaff_DI;
|
||||
iVar9 += 1;
|
||||
} while (iVar9 < (int)pMVar15->keyCount);
|
||||
}
|
||||
iVar6 += 1;
|
||||
pMVar15 = pMVar15 + 1;
|
||||
} while (iVar6 < g_MaterialGlobalDescriptors[iVar7].animCount);
|
||||
}
|
||||
return iStack_90;
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,14 +0,0 @@
|
||||
// See https://aka.ms/new-console-template for more information
|
||||
|
||||
Console.WriteLine("Hello, World!");
|
||||
|
||||
using var fs = new FileStream("C:\\ParkanUnpacked\\11_fr_e_brige.msh\\0_fr_e_brige.bin", FileMode.Open);
|
||||
|
||||
byte[] buffer = new byte[38];
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
fs.ReadExactly(buffer);
|
||||
|
||||
Console.WriteLine(string.Join(" ", buffer.Select(x => x.ToString("X2"))));
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,9 +0,0 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
// var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\Autodemo.00\\data.tma";
|
||||
// var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\Tutorial.01\\data.tma";
|
||||
var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\CAMPAIGN\\CAMPAIGN.01\\Mission.02\\data.tma";
|
||||
// var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\Single.01\\data.tma";
|
||||
@@ -1,3 +1,5 @@
|
||||
namespace MissionTmaLib;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
|
||||
public record ArealInfo(int Index, int CoordsCount, List<Vector3> Coords);
|
||||
@@ -20,7 +20,7 @@ public class ClanInfo
|
||||
/// Игра называет этот путь TreeName
|
||||
/// </summary>
|
||||
public string ResearchNResPath { get; set; }
|
||||
public int UnkInt3 { get; set; }
|
||||
public int Brains { get; set; }
|
||||
public int AlliesMapCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace MissionTmaLib;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
|
||||
public class GameObjectInfo
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
namespace MissionTmaLib;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
|
||||
public record GameObjectSetting(int SettingType, IntFloatValue Unk1, IntFloatValue Unk2, IntFloatValue Unk3, string Name);
|
||||
@@ -1,3 +1,5 @@
|
||||
namespace MissionTmaLib;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
|
||||
public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownFlags2, float UnknownFloat, int UnknownInt3);
|
||||
@@ -1,9 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace MissionTmaLib.Parsing;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static int ReadInt32LittleEndian(this FileStream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static float ReadFloatLittleEndian(this FileStream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
||||
}
|
||||
|
||||
public static string ReadLengthPrefixedString(this FileStream fs)
|
||||
{
|
||||
var len = fs.ReadInt32LittleEndian();
|
||||
|
||||
if (len == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var buffer = new byte[len];
|
||||
|
||||
fs.ReadExactly(buffer, 0, len);
|
||||
|
||||
return Encoding.ASCII.GetString(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
namespace MissionTmaLib.Parsing;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib.Parsing;
|
||||
|
||||
public class MissionTmaParser
|
||||
{
|
||||
public static MissionTmaParseResult ReadFile(string filePath)
|
||||
{
|
||||
var fs = new FileStream(filePath, FileMode.Open);
|
||||
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
return ReadFile(fs);
|
||||
}
|
||||
|
||||
public static MissionTmaParseResult ReadFile(Stream fs)
|
||||
{
|
||||
var arealData = LoadAreals(fs);
|
||||
|
||||
var clansData = LoadClans(fs);
|
||||
@@ -18,7 +25,7 @@ public class MissionTmaParser
|
||||
return new MissionTmaParseResult(missionDat, null);
|
||||
}
|
||||
|
||||
private static ArealsFileData LoadAreals(FileStream fileStream)
|
||||
private static ArealsFileData LoadAreals(Stream fileStream)
|
||||
{
|
||||
var unusedHeader = fileStream.ReadInt32LittleEndian();
|
||||
var arealCount = fileStream.ReadInt32LittleEndian();
|
||||
@@ -54,7 +61,7 @@ public class MissionTmaParser
|
||||
return new ArealsFileData(unusedHeader, arealCount, infos);
|
||||
}
|
||||
|
||||
private static ClansFileData? LoadClans(FileStream fileStream)
|
||||
private static ClansFileData? LoadClans(Stream fileStream)
|
||||
{
|
||||
var clanFeatureSet = fileStream.ReadInt32LittleEndian();
|
||||
|
||||
@@ -118,7 +125,7 @@ public class MissionTmaParser
|
||||
|
||||
if (4 < clanFeatureSet)
|
||||
{
|
||||
clanTreeInfo.UnkInt3 = fileStream.ReadInt32LittleEndian();
|
||||
clanTreeInfo.Brains = fileStream.ReadInt32LittleEndian();
|
||||
}
|
||||
|
||||
if (5 < clanFeatureSet)
|
||||
@@ -156,7 +163,7 @@ public class MissionTmaParser
|
||||
return clanInfo;
|
||||
}
|
||||
|
||||
private static GameObjectsFileData LoadGameObjects(FileStream fileStream)
|
||||
private static GameObjectsFileData LoadGameObjects(Stream fileStream)
|
||||
{
|
||||
var gameObjectsFeatureSet = fileStream.ReadInt32LittleEndian();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
namespace MissionTmaLib;
|
||||
using Common;
|
||||
|
||||
namespace MissionTmaLib;
|
||||
|
||||
public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3);
|
||||
@@ -1,11 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,7 +1,42 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
var fileBytes = File.ReadAllBytes("C:\\Program Files (x86)\\Nikita\\Iron Strategy\\gamefont.rlb");
|
||||
// Unfinished
|
||||
|
||||
var fileBytes = File.ReadAllBytes("C:\\Program Files (x86)\\Nikita\\Iron Strategy\\gamefont-1.rlb");
|
||||
|
||||
var header = fileBytes.AsSpan().Slice(0, 32);
|
||||
|
||||
var nlHeaderBytes = header.Slice(0, 2);
|
||||
var mustBeZero = header[2];
|
||||
var mustBeOne = header[3];
|
||||
var numberOfEntriesBytes = header.Slice(4, 2);
|
||||
var sortingFlagBytes = header.Slice(14, 2);
|
||||
var decryptionKeyBytes = header.Slice(20, 2);
|
||||
|
||||
var numberOfEntries = BinaryPrimitives.ReadInt16LittleEndian(numberOfEntriesBytes);
|
||||
var sortingFlag = BinaryPrimitives.ReadInt16LittleEndian(sortingFlagBytes);
|
||||
var decryptionKey = BinaryPrimitives.ReadInt16LittleEndian(decryptionKeyBytes);
|
||||
|
||||
var headerSize = numberOfEntries * 32;
|
||||
|
||||
var decryptedHeader = new byte[headerSize];
|
||||
|
||||
var keyLow = decryptionKeyBytes[0];
|
||||
var keyHigh = decryptionKeyBytes[1];
|
||||
for (var i = 0; i < headerSize; i++)
|
||||
{
|
||||
byte tmp = (byte)((keyLow << 1) ^ keyHigh);
|
||||
keyLow = tmp;
|
||||
keyHigh = (byte)((keyHigh >> 1) ^ tmp);
|
||||
decryptedHeader[i] = (byte)(fileBytes[32 + i] ^ tmp);
|
||||
}
|
||||
|
||||
var decryptedHeaderString = Encoding.ASCII.GetString(decryptedHeader, 0, headerSize);
|
||||
var entries = decryptedHeader.Chunk(32).ToArray();
|
||||
var entriesStrings = entries.Select(x => Encoding.ASCII.GetString(x, 0, x.Length)).ToArray();
|
||||
|
||||
File.WriteAllBytes("export.nl", decryptedHeader);
|
||||
var fileCount = BinaryPrimitives.ReadInt16LittleEndian(fileBytes.AsSpan().Slice(4, 2));
|
||||
|
||||
var decodedHeader = new byte[fileCount * 32];
|
||||
|
||||
@@ -19,10 +19,11 @@ public record NResArchiveHeader(string NRes, int Version, int FileCount, int Tot
|
||||
/// каждый элемент это 64 байта,
|
||||
/// найти начало можно как (Header.TotalFileLengthBytes - Header.FileCount * 64)
|
||||
/// </summary>
|
||||
/// <param name="FileType">[0..8] ASCII описание типа файла, например TEXM или MAT0</param>
|
||||
/// <param name="FileType">[0..4] ASCII описание типа файла, например TEXM или MAT0</param>
|
||||
/// <param name="ElementCount">[4..8] Количество элементов в файле (если файл составной, например .trf) или версия, например у материалов или флаги</param>
|
||||
/// <param name="Magic1">[8..12] Неизвестное число</param>
|
||||
/// <param name="FileLength">[12..16] Длина файла в байтах</param>
|
||||
/// <param name="Magic2">[16..20] Неизвестное число</param>
|
||||
/// <param name="ElementSize">[16..20] Размер элемента в файле (если файл составной, например .trf) </param>
|
||||
/// <param name="FileName">[20..40] ASCII имя файла</param>
|
||||
/// <param name="Magic3">[40..44] Неизвестное число</param>
|
||||
/// <param name="Magic4">[44..48] Неизвестное число</param>
|
||||
@@ -32,9 +33,10 @@ public record NResArchiveHeader(string NRes, int Version, int FileCount, int Tot
|
||||
/// <param name="Index">[60..64] Индекс в файле (от 0, не больше чем кол-во файлов)</param>
|
||||
public record ListMetadataItem(
|
||||
string FileType,
|
||||
uint ElementCount,
|
||||
int Magic1,
|
||||
int FileLength,
|
||||
int Magic2,
|
||||
int ElementSize,
|
||||
string FileName,
|
||||
int Magic3,
|
||||
int Magic4,
|
||||
|
||||
@@ -30,7 +30,7 @@ public class NResExporter
|
||||
extension = ".bin";
|
||||
}
|
||||
|
||||
var targetFilePath = Path.Combine(targetDirectoryPath, $"{archiveFile.Index}_{fileName}{extension}");
|
||||
var targetFilePath = Path.Combine(targetDirectoryPath, $"{archiveFile.Index}_{archiveFile.FileType}_{fileName}{extension}");
|
||||
|
||||
File.WriteAllBytes(targetFilePath, buffer);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -7,8 +7,13 @@ public static class NResParser
|
||||
{
|
||||
public static NResParseResult ReadFile(string path)
|
||||
{
|
||||
using FileStream nResFs = new FileStream(path, FileMode.Open);
|
||||
using FileStream nResFs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
return ReadFile(nResFs);
|
||||
}
|
||||
|
||||
public static NResParseResult ReadFile(Stream nResFs)
|
||||
{
|
||||
if (nResFs.Length < 16)
|
||||
{
|
||||
return new NResParseResult(null, "Файл не может быть NRes, менее 16 байт");
|
||||
@@ -48,13 +53,32 @@ public static class NResParser
|
||||
for (int i = 0; i < header.FileCount; i++)
|
||||
{
|
||||
nResFs.ReadExactly(metaDataBuffer);
|
||||
var type = "";
|
||||
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
if (!char.IsLetterOrDigit((char)metaDataBuffer[j]))
|
||||
{
|
||||
type += metaDataBuffer[j]
|
||||
.ToString("X2") + " ";
|
||||
}
|
||||
else
|
||||
{
|
||||
type += (char)metaDataBuffer[j];
|
||||
}
|
||||
}
|
||||
|
||||
var type2 = BinaryPrimitives.ReadUInt32LittleEndian(metaDataBuffer.Slice(4));
|
||||
|
||||
type = type.Trim();
|
||||
|
||||
elements.Add(
|
||||
new ListMetadataItem(
|
||||
FileType: Encoding.ASCII.GetString(metaDataBuffer[..8]).TrimEnd('\0'),
|
||||
FileType: type,
|
||||
ElementCount: type2,
|
||||
Magic1: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[8..12]),
|
||||
FileLength: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[12..16]),
|
||||
Magic2: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[16..20]),
|
||||
ElementSize: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[16..20]),
|
||||
FileName: Encoding.ASCII.GetString(metaDataBuffer[20..40]).TrimEnd('\0'),
|
||||
Magic3: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[40..44]),
|
||||
Magic4: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[44..48]),
|
||||
|
||||
@@ -32,10 +32,11 @@
|
||||
3. В конце файла есть метаданные.
|
||||
Поскольку NRes это по сути архив, длина метаданных у каждого файла разная и считается как `Количество файлов * 64`, каждый элемент метаданных - 64 байта.
|
||||
|
||||
+ [0..8] ASCII описание типа файла, например TEXM или MAT0
|
||||
+ [0..4] ASCII описание типа файла, например TEXM или MAT0
|
||||
+ [4..8] Количество элементов в файле (если файл составной, например .trf)
|
||||
+ [8..12] Неизвестное число
|
||||
+ [12..16] Длина файла в байтах
|
||||
+ [16..20] Неизвестное число
|
||||
+ [16..20] Размер элемента в файле (если файл составной, например .trf)
|
||||
+ [20..40] ASCII имя файла
|
||||
+ [40..44] Неизвестное число
|
||||
+ [44..48] Неизвестное число
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using ImGuiNET;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NResUI.Abstractions;
|
||||
@@ -33,6 +34,8 @@ public class App
|
||||
|
||||
public void Init(IWindow window, GL openGl, ImFontPtr openSansFont)
|
||||
{
|
||||
// Call this once at program startup
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
ImGui.StyleColorsLight();
|
||||
|
||||
IServiceCollection serviceCollection = new ServiceCollection();
|
||||
@@ -55,6 +58,9 @@ public class App
|
||||
serviceCollection.AddSingleton(new MissionTmaViewModel());
|
||||
serviceCollection.AddSingleton(new BinaryExplorerViewModel());
|
||||
serviceCollection.AddSingleton(new ScrViewModel());
|
||||
serviceCollection.AddSingleton(new VarsetViewModel());
|
||||
serviceCollection.AddSingleton(new CpDatSchemeViewModel());
|
||||
serviceCollection.AddSingleton(new MaterialViewModel());
|
||||
|
||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
|
||||
|
||||
150
NResUI/ImGuiUI/CpDatSchemeExplorer.cs
Normal file
150
NResUI/ImGuiUI/CpDatSchemeExplorer.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using CpDatLib;
|
||||
using ImGuiNET;
|
||||
using NResUI.Abstractions;
|
||||
using NResUI.Models;
|
||||
|
||||
namespace NResUI.ImGuiUI;
|
||||
|
||||
public class CpDatSchemeExplorer : IImGuiPanel
|
||||
{
|
||||
private readonly CpDatSchemeViewModel _viewModel;
|
||||
|
||||
public CpDatSchemeExplorer(CpDatSchemeViewModel viewModel)
|
||||
{
|
||||
_viewModel = viewModel;
|
||||
}
|
||||
|
||||
public void OnImGuiRender()
|
||||
{
|
||||
if (ImGui.Begin("cp .dat Scheme Explorer"))
|
||||
{
|
||||
ImGui.Text("cp .dat - это файл схема здания или робота. Их можно найти в папке UNITS");
|
||||
ImGui.Separator();
|
||||
|
||||
var cpDat = _viewModel.CpDatScheme;
|
||||
if (_viewModel.HasFile && cpDat is not null)
|
||||
{
|
||||
ImGui.Text("Тип объекта в схеме: ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(cpDat.Type.ToString("G"));
|
||||
|
||||
var root = cpDat.Root;
|
||||
|
||||
DrawEntry(root, 0);
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
if (ImGui.BeginTable("content", 8, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX | ImGuiTableFlags.Sortable))
|
||||
{
|
||||
ImGui.TableSetupColumn("Индекс");
|
||||
ImGui.TableSetupColumn("Уровень вложенности");
|
||||
ImGui.TableSetupColumn("Архив");
|
||||
ImGui.TableSetupColumn("Элемент");
|
||||
ImGui.TableSetupColumn("Magic1");
|
||||
ImGui.TableSetupColumn("Magic2");
|
||||
ImGui.TableSetupColumn("Описание");
|
||||
ImGui.TableSetupColumn("Тип");
|
||||
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
// Handle sorting
|
||||
ImGuiTableSortSpecsPtr sortSpecs = ImGui.TableGetSortSpecs();
|
||||
if (sortSpecs.SpecsDirty)
|
||||
{
|
||||
// Only handle the first sort spec for simplicity
|
||||
var sortSpec = sortSpecs.Specs;
|
||||
|
||||
if (sortSpec.ColumnIndex == 0)
|
||||
{
|
||||
_viewModel.RebuildFlatList();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
_viewModel.FlatList.Sort((a, b) =>
|
||||
{
|
||||
int result = 0;
|
||||
switch (sortSpec.ColumnIndex)
|
||||
{
|
||||
case 1: result = a.Level.CompareTo(b.Level); break;
|
||||
case 2: result = string.Compare(a.Entry.ArchiveFile, b.Entry.ArchiveFile, StringComparison.Ordinal); break;
|
||||
case 3: result = string.Compare(a.Entry.ArchiveEntryName, b.Entry.ArchiveEntryName, StringComparison.Ordinal); break;
|
||||
case 4: result = a.Entry.Magic1.CompareTo(b.Entry.Magic1); break;
|
||||
case 5: result = a.Entry.Magic2.CompareTo(b.Entry.Magic2); break;
|
||||
case 6: result = string.Compare(a.Entry.Description, b.Entry.Description, StringComparison.Ordinal); break;
|
||||
case 7: result = a.Entry.Type.CompareTo(b.Entry.Type); break;
|
||||
}
|
||||
|
||||
return sortSpec.SortDirection == ImGuiSortDirection.Descending ? -result : result;
|
||||
});
|
||||
}
|
||||
|
||||
sortSpecs.SpecsDirty = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _viewModel.FlatList.Count; i++)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(i.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.FlatList[i].Level.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.FlatList[i].Entry.ArchiveFile);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.FlatList[i].Entry.ArchiveEntryName);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.FlatList[i].Entry.Magic1.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.FlatList[i].Entry.Magic2.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.FlatList[i].Entry.Description);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.FlatList[i].Entry.Type.ToString("G"));
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
void DrawEntry(CpDatEntry entry, int index)
|
||||
{
|
||||
if (ImGui.TreeNodeEx($"Элемент: \"{entry.ArchiveFile}/{entry.ArchiveEntryName}\" - {entry.Description}##entry_{index}"))
|
||||
{
|
||||
ImGui.Text("Magic1: ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(entry.Magic1.ToString());
|
||||
|
||||
ImGui.Text("Magic2: ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(entry.Magic2.ToString());
|
||||
|
||||
ImGui.Text("Тип: ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(entry.Type.ToString());
|
||||
|
||||
ImGui.Text("Кол-во дочерних элементов: ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(entry.ChildCount.ToString());
|
||||
|
||||
foreach (var child in entry.Children)
|
||||
{
|
||||
DrawEntry(child, ++index);
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_viewModel.Error is not null)
|
||||
{
|
||||
ImGui.Text(_viewModel.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text("cp .dat не открыт");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.End();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using CpDatLib;
|
||||
using ImGuiNET;
|
||||
using MissionTmaLib;
|
||||
using MissionTmaLib.Parsing;
|
||||
@@ -8,6 +9,7 @@ using NResUI.Abstractions;
|
||||
using NResUI.Models;
|
||||
using ScrLib;
|
||||
using TexmLib;
|
||||
using VarsetLib;
|
||||
|
||||
namespace NResUI.ImGuiUI
|
||||
{
|
||||
@@ -16,6 +18,8 @@ namespace NResUI.ImGuiUI
|
||||
TexmExplorerViewModel texmExplorerViewModel,
|
||||
ScrViewModel scrViewModel,
|
||||
MissionTmaViewModel missionTmaViewModel,
|
||||
VarsetViewModel varsetViewModel,
|
||||
CpDatSchemeViewModel cpDatSchemeViewModel,
|
||||
MessageBoxModalPanel messageBox)
|
||||
: IImGuiPanel
|
||||
{
|
||||
@@ -104,6 +108,36 @@ namespace NResUI.ImGuiUI
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Open Varset File"))
|
||||
{
|
||||
var result = Dialog.FileOpen("var");
|
||||
|
||||
if (result.IsOk)
|
||||
{
|
||||
var path = result.Path;
|
||||
var parseResult = VarsetParser.Parse(path);
|
||||
|
||||
varsetViewModel.Items = parseResult;
|
||||
|
||||
Console.WriteLine("Read VARSET");
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Open cp .dat Scheme File"))
|
||||
{
|
||||
var result = Dialog.FileOpen("dat");
|
||||
|
||||
if (result.IsOk)
|
||||
{
|
||||
var path = result.Path;
|
||||
var parseResult = CpDatParser.Parse(path);
|
||||
|
||||
cpDatSchemeViewModel.SetParseResult(parseResult, path);
|
||||
|
||||
Console.WriteLine("Read cp .dat");
|
||||
}
|
||||
}
|
||||
|
||||
if (nResExplorerViewModel.HasFile)
|
||||
{
|
||||
if (ImGui.MenuItem("Экспортировать NRes"))
|
||||
|
||||
301
NResUI/ImGuiUI/MaterialExplorerPanel.cs
Normal file
301
NResUI/ImGuiUI/MaterialExplorerPanel.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
using MaterialLib;
|
||||
using NResUI.Abstractions;
|
||||
using NResUI.Models;
|
||||
|
||||
namespace NResUI.ImGuiUI;
|
||||
|
||||
public class MaterialExplorerPanel : IImGuiPanel
|
||||
{
|
||||
private readonly MaterialViewModel _viewModel;
|
||||
|
||||
public MaterialExplorerPanel(MaterialViewModel viewModel)
|
||||
{
|
||||
_viewModel = viewModel;
|
||||
}
|
||||
|
||||
public void OnImGuiRender()
|
||||
{
|
||||
if (ImGui.Begin("Material Explorer"))
|
||||
{
|
||||
ImGui.Text("Материал это бинарный файл, который можно найти в materials.lib.");
|
||||
ImGui.Text("Его можно открыть только из открытого NRes архива, т.к. чтение полагается на флаги из архива");
|
||||
|
||||
if (!_viewModel.HasFile)
|
||||
{
|
||||
ImGui.Text("No Material file opened");
|
||||
}
|
||||
else if (_viewModel.Error != null)
|
||||
{
|
||||
ImGui.TextColored(new Vector4(1, 0, 0, 1), $"Error: {_viewModel.Error}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var mat = _viewModel.MaterialFile!;
|
||||
|
||||
ImGui.Text($"File: {_viewModel.FilePath}");
|
||||
ImGui.Separator();
|
||||
|
||||
// === FILE READ SEQUENCE ===
|
||||
|
||||
if (ImGui.CollapsingHeader("1. Metadata & Derived Fields", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
{
|
||||
ImGui.TextDisabled("(Not from file content)");
|
||||
ImGui.Text($"File Name: {mat.FileName}");
|
||||
ImGui.Text($"Version (ElementCount): {mat.Version}");
|
||||
ImGui.Text($"Magic1: {mat.Magic1}");
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.TextDisabled("(Derived from Version)");
|
||||
ImGui.Text($"Material Rendering Type: {mat.MaterialRenderingType}");
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("0=Standard, 1=Special, 2=Particle/Effect");
|
||||
}
|
||||
ImGui.Text($"Supports Bump Mapping: {mat.SupportsBumpMapping}");
|
||||
ImGui.Text($"Is Particle Effect: {mat.IsParticleEffect}");
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("0=Normal material, 8=Particle/Effect (e.g., jet engines)");
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.CollapsingHeader("2. File Header (Read Order)", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
{
|
||||
ImGui.Text($"Stage Count: {mat.Stages.Count}");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(2 bytes, ushort)");
|
||||
|
||||
ImGui.Text($"Animation Count: {mat.Animations.Count}");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(2 bytes, ushort)");
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.TextDisabled($"(Read if Magic1 >= 2)");
|
||||
ImGui.Text($"Source Blend Mode: {mat.SourceBlendMode}");
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(GetBlendModeDescription(mat.SourceBlendMode));
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(1 byte)");
|
||||
|
||||
ImGui.Text($"Dest Blend Mode: {mat.DestBlendMode}");
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(GetBlendModeDescription(mat.DestBlendMode));
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(1 byte)");
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.TextDisabled("(Read if Magic1 > 2)");
|
||||
ImGui.Text($"Global Alpha Multiplier: {mat.GlobalAlphaMultiplier:F3}");
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("Always 1.0 in all 628 materials - global alpha multiplier");
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(4 bytes, float)");
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.TextDisabled("(Read if Magic1 > 3)");
|
||||
ImGui.Text($"Global Emissive Intensity: {mat.GlobalEmissiveIntensity:F3}");
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("0=no glow (99.4%), rare values: 1000, 10000");
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(4 bytes, float)");
|
||||
}
|
||||
|
||||
if (ImGui.CollapsingHeader($"3. Stages ({mat.Stages.Count}) - 34 bytes each", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
{
|
||||
for (int i = 0; i < mat.Stages.Count; i++)
|
||||
{
|
||||
var stage = mat.Stages[i];
|
||||
if (ImGui.TreeNode($"Stage {i}: {stage.TextureName}"))
|
||||
{
|
||||
ImGui.TextDisabled("=== File Read Order (34 bytes) ===");
|
||||
|
||||
// 1. Ambient (4 bytes)
|
||||
ImGui.Text("1. Ambient Color:");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(4 bytes, A scaled by 0.01)");
|
||||
var ambient = new Vector4(stage.AmbientR, stage.AmbientG, stage.AmbientB, stage.AmbientA);
|
||||
ImGui.ColorEdit4("##Ambient", ref ambient, ImGuiColorEditFlags.NoInputs);
|
||||
ImGui.SameLine();
|
||||
ImGui.Text($"RGBA: ({stage.AmbientR:F3}, {stage.AmbientG:F3}, {stage.AmbientB:F3}, {stage.AmbientA:F3})");
|
||||
|
||||
// 2. Diffuse (4 bytes)
|
||||
ImGui.Text("2. Diffuse Color:");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(4 bytes)");
|
||||
var diffuse = new Vector4(stage.DiffuseR, stage.DiffuseG, stage.DiffuseB, stage.DiffuseA);
|
||||
ImGui.ColorEdit4("##Diffuse", ref diffuse, ImGuiColorEditFlags.NoInputs);
|
||||
ImGui.SameLine();
|
||||
ImGui.Text($"RGBA: ({stage.DiffuseR:F3}, {stage.DiffuseG:F3}, {stage.DiffuseB:F3}, {stage.DiffuseA:F3})");
|
||||
|
||||
// 3. Specular (4 bytes)
|
||||
ImGui.Text("3. Specular Color:");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(4 bytes)");
|
||||
var specular = new Vector4(stage.SpecularR, stage.SpecularG, stage.SpecularB, stage.SpecularA);
|
||||
ImGui.ColorEdit4("##Specular", ref specular, ImGuiColorEditFlags.NoInputs);
|
||||
ImGui.SameLine();
|
||||
ImGui.Text($"RGBA: ({stage.SpecularR:F3}, {stage.SpecularG:F3}, {stage.SpecularB:F3}, {stage.SpecularA:F3})");
|
||||
|
||||
// 4. Emissive (4 bytes)
|
||||
ImGui.Text("4. Emissive Color:");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(4 bytes)");
|
||||
var emissive = new Vector4(stage.EmissiveR, stage.EmissiveG, stage.EmissiveB, stage.EmissiveA);
|
||||
ImGui.ColorEdit4("##Emissive", ref emissive, ImGuiColorEditFlags.NoInputs);
|
||||
ImGui.SameLine();
|
||||
ImGui.Text($"RGBA: ({stage.EmissiveR:F3}, {stage.EmissiveG:F3}, {stage.EmissiveB:F3}, {stage.EmissiveA:F3})");
|
||||
|
||||
// 5. Power (1 byte)
|
||||
ImGui.Text($"5. Power: {stage.Power:F3}");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(1 byte -> float)");
|
||||
|
||||
// 6. Texture Stage Index (1 byte)
|
||||
ImGui.Text($"6. Texture Stage Index: {stage.TextureStageIndex}");
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("255 = not set/default, 0-47 = reference to specific texture stage");
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(1 byte)");
|
||||
|
||||
// 7. Texture Name (16 bytes)
|
||||
ImGui.Text($"7. Texture Name: {stage.TextureName}");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(16 bytes, ASCII)");
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.CollapsingHeader($"4. Animations ({mat.Animations.Count})"))
|
||||
{
|
||||
for (int i = 0; i < mat.Animations.Count; i++)
|
||||
{
|
||||
var anim = mat.Animations[i];
|
||||
if (ImGui.TreeNode($"Anim {i}: {anim.Target} ({anim.LoopMode})"))
|
||||
{
|
||||
ImGui.TextDisabled("=== File Read Order ===");
|
||||
|
||||
// Combined field (4 bytes)
|
||||
ImGui.Text("1. Target & Loop Mode:");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(4 bytes combined)");
|
||||
|
||||
ImGui.Indent();
|
||||
ImGui.Text($"Target (bits 3-31): {anim.Target}");
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(anim.TargetDescription);
|
||||
}
|
||||
|
||||
ImGui.Text($"Loop Mode (bits 0-2): {anim.LoopMode}");
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(GetAnimationLoopModeDescription(anim.LoopMode));
|
||||
}
|
||||
ImGui.Unindent();
|
||||
|
||||
// Key count (2 bytes)
|
||||
ImGui.Text($"2. Key Count: {anim.Keys.Count}");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("(2 bytes, ushort)");
|
||||
|
||||
// Keys (6 bytes each)
|
||||
ImGui.Text($"3. Keys ({anim.Keys.Count} × 6 bytes):");
|
||||
if (ImGui.BeginTable($"keys_{i}", 4, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg))
|
||||
{
|
||||
ImGui.TableSetupColumn("#");
|
||||
ImGui.TableSetupColumn("Stage Index (2 bytes)");
|
||||
ImGui.TableSetupColumn("Duration ms (2 bytes)");
|
||||
ImGui.TableSetupColumn("Interpolation (2 bytes)");
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
int keyIdx = 0;
|
||||
foreach (var key in anim.Keys)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(keyIdx.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(key.StageIndex.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(key.DurationMs.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(key.InterpolationCurve.ToString());
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("Always 0 = linear interpolation");
|
||||
}
|
||||
keyIdx++;
|
||||
}
|
||||
ImGui.EndTable();
|
||||
}
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui.End();
|
||||
}
|
||||
|
||||
private static string GetBlendModeDescription(BlendMode mode)
|
||||
{
|
||||
return mode switch
|
||||
{
|
||||
BlendMode.Zero => "D3DBLEND_ZERO\nBlend factor is (0, 0, 0, 0)\nResults in black/transparent",
|
||||
|
||||
BlendMode.One => "D3DBLEND_ONE\nBlend factor is (1, 1, 1, 1)\nUses full color value (no blending)",
|
||||
|
||||
BlendMode.SrcColor => "D3DBLEND_SRCCOLOR\nBlend factor is (Rs, Gs, Bs, As)\nUses source color",
|
||||
|
||||
BlendMode.InvSrcColor => "D3DBLEND_INVSRCCOLOR\nBlend factor is (1-Rs, 1-Gs, 1-Bs, 1-As)\nUses inverted source color",
|
||||
|
||||
BlendMode.SrcAlpha => "D3DBLEND_SRCALPHA\nBlend factor is (As, As, As, As)\nUses source alpha for all channels\n Standard transparency (common with InvSrcAlpha for dest)",
|
||||
|
||||
BlendMode.InvSrcAlpha => "D3DBLEND_INVSRCALPHA\nBlend factor is (1-As, 1-As, 1-As, 1-As)\nUses inverted source alpha\n Standard transparency (common with SrcAlpha for source)",
|
||||
|
||||
BlendMode.DestAlpha => "D3DBLEND_DESTALPHA\nBlend factor is (Ad, Ad, Ad, Ad)\nUses destination alpha",
|
||||
|
||||
BlendMode.InvDestAlpha => "D3DBLEND_INVDESTALPHA\nBlend factor is (1-Ad, 1-Ad, 1-Ad, 1-Ad)\nUses inverted destination alpha",
|
||||
|
||||
BlendMode.DestColor => "D3DBLEND_DESTCOLOR\nBlend factor is (Rd, Gd, Bd, Ad)\nUses destination color",
|
||||
|
||||
BlendMode.InvDestColor => "D3DBLEND_INVDESTCOLOR\nBlend factor is (1-Rd, 1-Gd, 1-Bd, 1-Ad)\nUses inverted destination color",
|
||||
|
||||
BlendMode.SrcAlphaSat => "D3DBLEND_SRCALPHASAT\nBlend factor is (f, f, f, 1) where f = min(As, 1-Ad)\nSaturates source alpha",
|
||||
|
||||
BlendMode.BothSrcAlpha => "D3DBLEND_BOTHSRCALPHA (Obsolete in D3D9+)\nBlend factor is (As, As, As, As) for both source and dest\nSource: (As, As, As, As), Dest: (1-As, 1-As, 1-As, 1-As)",
|
||||
|
||||
BlendMode.BothInvSrcAlpha => "D3DBLEND_BOTHINVSRCALPHA (Obsolete in D3D9+)\nBlend factor is (1-As, 1-As, 1-As, 1-As) for both source and dest\nSource: (1-As, 1-As, 1-As, 1-As), Dest: (As, As, As, As)",
|
||||
|
||||
BlendMode.Unknown => "Unknown/Default (0xFF)\nUninitialized or opaque rendering",
|
||||
|
||||
_ => "Unknown blend mode"
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetAnimationLoopModeDescription(AnimationLoopMode mode)
|
||||
{
|
||||
return mode switch
|
||||
{
|
||||
AnimationLoopMode.Loop => "Loop: Animation repeats continuously from start to end",
|
||||
AnimationLoopMode.PingPong => "PingPong: Animation plays forward then backward repeatedly",
|
||||
AnimationLoopMode.Clamp => "Clamp: Animation plays once and holds at the final value",
|
||||
AnimationLoopMode.Random => "Random: Animation selects random keyframes",
|
||||
_ => "Unknown loop mode"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@ public class MissionTmaExplorer : IImGuiPanel
|
||||
{
|
||||
if (ImGui.Begin("Mission TMA Explorer"))
|
||||
{
|
||||
ImGui.Text("data.tma - это файл миссии. Его можно найти в папке MISSIONS");
|
||||
ImGui.Separator();
|
||||
|
||||
var mission = _viewModel.Mission;
|
||||
if (_viewModel.HasFile && mission is not null)
|
||||
{
|
||||
@@ -132,7 +135,7 @@ public class MissionTmaExplorer : IImGuiPanel
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(clanInfo.ClanType.ToReadableString());
|
||||
|
||||
ImGui.Text("Скрипты поведения: ");
|
||||
ImGui.Text("Скрипты поведения (Mission Scripts): ");
|
||||
Utils.ShowHint("Пути к файлам .scr и .fml описывающих настройку объектов и поведение AI");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(clanInfo.ScriptsString);
|
||||
@@ -176,14 +179,14 @@ public class MissionTmaExplorer : IImGuiPanel
|
||||
ImGui.Text("Отсутствует неизвестная часть");
|
||||
}
|
||||
|
||||
ImGui.Text("Путь к файлу .trf: ");
|
||||
Utils.ShowHint("Не до конца понятно, что означает, вероятно это NRes с деревом исследований");
|
||||
ImGui.Text("Дерево исследований: ");
|
||||
Utils.ShowHint("NRes с деревом исследований");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(clanInfo.ResearchNResPath);
|
||||
|
||||
ImGui.Text("Неизвестное число 3: ");
|
||||
ImGui.Text("Количество мозгов (Brains))): ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(clanInfo.UnkInt3.ToString());
|
||||
ImGui.Text(clanInfo.Brains.ToString());
|
||||
|
||||
ImGui.Text("Матрица союзников");
|
||||
Utils.ShowHint("Если 1, то кланы - союзники, и не нападают друг на друга");
|
||||
|
||||
@@ -1,22 +1,47 @@
|
||||
using ImGuiNET;
|
||||
using CpDatLib;
|
||||
using ImGuiNET;
|
||||
using MissionTmaLib.Parsing;
|
||||
using NResLib;
|
||||
using NResUI.Abstractions;
|
||||
using NResUI.Models;
|
||||
using ScrLib;
|
||||
using TexmLib;
|
||||
using VarsetLib;
|
||||
|
||||
namespace NResUI.ImGuiUI;
|
||||
|
||||
public class NResExplorerPanel : IImGuiPanel
|
||||
{
|
||||
private readonly NResExplorerViewModel _viewModel;
|
||||
private readonly TexmExplorerViewModel _texmExplorerViewModel;
|
||||
private readonly VarsetViewModel _varsetViewModel;
|
||||
private readonly CpDatSchemeViewModel _cpDatSchemeViewModel;
|
||||
private readonly MissionTmaViewModel _missionTmaViewModel;
|
||||
private readonly ScrViewModel _scrViewModel;
|
||||
private readonly MaterialViewModel _materialViewModel;
|
||||
|
||||
public NResExplorerPanel(NResExplorerViewModel viewModel)
|
||||
public NResExplorerPanel(NResExplorerViewModel viewModel, TexmExplorerViewModel texmExplorerViewModel,
|
||||
VarsetViewModel varsetViewModel, CpDatSchemeViewModel cpDatSchemeViewModel, MissionTmaViewModel missionTmaViewModel, ScrViewModel scrViewModel, MaterialViewModel materialViewModel)
|
||||
{
|
||||
_viewModel = viewModel;
|
||||
_texmExplorerViewModel = texmExplorerViewModel;
|
||||
_varsetViewModel = varsetViewModel;
|
||||
_cpDatSchemeViewModel = cpDatSchemeViewModel;
|
||||
_missionTmaViewModel = missionTmaViewModel;
|
||||
_scrViewModel = scrViewModel;
|
||||
_materialViewModel = materialViewModel;
|
||||
}
|
||||
|
||||
int contextMenuRow = -1;
|
||||
|
||||
public void OnImGuiRender()
|
||||
{
|
||||
if (ImGui.Begin("NRes Explorer"))
|
||||
{
|
||||
ImGui.Text(
|
||||
"NRes - это файл-архив. Они имеют разные расширения. Примеры - Textures.lib, weapon.rlb, object.dlb, behpsp.res");
|
||||
ImGui.Separator();
|
||||
|
||||
if (!_viewModel.HasFile)
|
||||
{
|
||||
ImGui.Text("No NRes is opened");
|
||||
@@ -31,7 +56,7 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
if (_viewModel.Archive is not null)
|
||||
{
|
||||
ImGui.Text(_viewModel.Path);
|
||||
|
||||
|
||||
ImGui.Text("Header: ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(_viewModel.Archive.Header.NRes);
|
||||
@@ -45,13 +70,14 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString());
|
||||
|
||||
|
||||
if (ImGui.BeginTable("content", 11))
|
||||
if (ImGui.BeginTable("content", 12,
|
||||
ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||
{
|
||||
ImGui.TableSetupColumn("Тип файла");
|
||||
ImGui.TableSetupColumn("Кол-во элементов");
|
||||
ImGui.TableSetupColumn("Magic1");
|
||||
ImGui.TableSetupColumn("Длина файла в байтах");
|
||||
ImGui.TableSetupColumn("Magic2");
|
||||
ImGui.TableSetupColumn("Размер элемента");
|
||||
ImGui.TableSetupColumn("Имя файла");
|
||||
ImGui.TableSetupColumn("Magic3");
|
||||
ImGui.TableSetupColumn("Magic4");
|
||||
@@ -66,8 +92,21 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
ImGui.Selectable("##row_select" + i, false, ImGuiSelectableFlags.SpanAllColumns);
|
||||
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Right))
|
||||
{
|
||||
Console.WriteLine("Context menu for row " + i);
|
||||
contextMenuRow = i;
|
||||
ImGui.OpenPopup("row_context_menu");
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.Text(_viewModel.Archive.Files[i].FileType);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.Archive.Files[i].ElementCount.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(
|
||||
_viewModel.Archive.Files[i]
|
||||
.Magic1.ToString()
|
||||
@@ -80,7 +119,7 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(
|
||||
_viewModel.Archive.Files[i]
|
||||
.Magic2.ToString()
|
||||
.ElementSize.ToString()
|
||||
);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(_viewModel.Archive.Files[i].FileName);
|
||||
@@ -116,6 +155,152 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
);
|
||||
}
|
||||
|
||||
if (ImGui.BeginPopup("row_context_menu"))
|
||||
{
|
||||
if (contextMenuRow == -1 || contextMenuRow > _viewModel.Archive.Files.Count)
|
||||
{
|
||||
ImGui.Text("Broken context menu :(. Reopen");
|
||||
}
|
||||
else
|
||||
{
|
||||
var file = _viewModel.Archive.Files[contextMenuRow];
|
||||
ImGui.Text("Actions for file " + file.FileName);
|
||||
ImGui.TextDisabled("Program has no understading of file format(");
|
||||
ImGui.Separator();
|
||||
if (ImGui.MenuItem("Open as Texture TEXM"))
|
||||
{
|
||||
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
|
||||
FileShare.Read);
|
||||
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
|
||||
|
||||
var buffer = new byte[file.FileLength];
|
||||
|
||||
fs.ReadExactly(buffer, 0, file.FileLength);
|
||||
|
||||
using var ms = new MemoryStream(buffer);
|
||||
|
||||
var parseResult = TexmParser.ReadFromStream(ms, file.FileName);
|
||||
|
||||
_texmExplorerViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName));
|
||||
Console.WriteLine("Read TEXM from context menu");
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Open as Archive NRes"))
|
||||
{
|
||||
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
|
||||
FileShare.Read);
|
||||
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
|
||||
|
||||
var buffer = new byte[file.FileLength];
|
||||
|
||||
fs.ReadExactly(buffer, 0, file.FileLength);
|
||||
|
||||
using var ms = new MemoryStream(buffer);
|
||||
|
||||
var parseResult = NResParser.ReadFile(ms);
|
||||
|
||||
_viewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName));
|
||||
Console.WriteLine("Read NRes from context menu");
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Open as Varset .var"))
|
||||
{
|
||||
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
|
||||
FileShare.Read);
|
||||
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
|
||||
|
||||
var buffer = new byte[file.FileLength];
|
||||
|
||||
fs.ReadExactly(buffer, 0, file.FileLength);
|
||||
|
||||
using var ms = new MemoryStream(buffer);
|
||||
|
||||
var parseResult = VarsetParser.Parse(ms);
|
||||
|
||||
_varsetViewModel.Items = parseResult;
|
||||
Console.WriteLine("Read Varset from context menu");
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Open as Scheme cp.dat"))
|
||||
{
|
||||
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
|
||||
FileShare.Read);
|
||||
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
|
||||
|
||||
var buffer = new byte[file.FileLength];
|
||||
|
||||
fs.ReadExactly(buffer, 0, file.FileLength);
|
||||
|
||||
using var ms = new MemoryStream(buffer);
|
||||
|
||||
var parseResult = CpDatParser.Parse(ms);
|
||||
|
||||
_cpDatSchemeViewModel.SetParseResult(parseResult, file.FileName);
|
||||
Console.WriteLine("Read cp.dat from context menu");
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Open as Mission .tma"))
|
||||
{
|
||||
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
|
||||
FileShare.Read);
|
||||
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
|
||||
|
||||
var buffer = new byte[file.FileLength];
|
||||
|
||||
fs.ReadExactly(buffer, 0, file.FileLength);
|
||||
|
||||
using var ms = new MemoryStream(buffer);
|
||||
|
||||
var parseResult = MissionTmaParser.ReadFile(ms);
|
||||
|
||||
_missionTmaViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName));
|
||||
Console.WriteLine("Read .tma from context menu");
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Open as Scripts .scr"))
|
||||
{
|
||||
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read,
|
||||
FileShare.Read);
|
||||
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
|
||||
|
||||
var buffer = new byte[file.FileLength];
|
||||
|
||||
fs.ReadExactly(buffer, 0, file.FileLength);
|
||||
|
||||
using var ms = new MemoryStream(buffer);
|
||||
|
||||
var parseResult = ScrParser.ReadFile(ms);
|
||||
|
||||
_scrViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName));
|
||||
Console.WriteLine("Read .scr from context menu");
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Open as Material"))
|
||||
{
|
||||
using var fs = new FileStream(_viewModel.Path!, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
fs.Seek(file.OffsetInFile, SeekOrigin.Begin);
|
||||
|
||||
var buffer = new byte[file.FileLength];
|
||||
fs.ReadExactly(buffer, 0, file.FileLength);
|
||||
using var ms = new MemoryStream(buffer);
|
||||
|
||||
try
|
||||
{
|
||||
var parseResult = MaterialLib.MaterialParser.ReadFromStream(ms, file.FileName, (int)file.ElementCount, file.Magic1);
|
||||
_materialViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName));
|
||||
Console.WriteLine("Read Material from context menu");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error reading material: {ex}");
|
||||
_materialViewModel.SetError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using ImGuiNET;
|
||||
using NResUI.Abstractions;
|
||||
using NResUI.Models;
|
||||
using ScrLib;
|
||||
|
||||
namespace NResUI.ImGuiUI;
|
||||
|
||||
@@ -17,6 +18,9 @@ public class ScrExplorer : IImGuiPanel
|
||||
{
|
||||
if (ImGui.Begin("SCR Explorer"))
|
||||
{
|
||||
ImGui.Text("scr - это файл AI скриптов. Их можно найти в папке MISSIONS/SCRIPTS");
|
||||
ImGui.Separator();
|
||||
|
||||
var scr = _viewModel.Scr;
|
||||
if (_viewModel.HasFile && scr is not null)
|
||||
{
|
||||
@@ -46,10 +50,10 @@ public class ScrExplorer : IImGuiPanel
|
||||
|
||||
if (ImGui.BeginTable($"Элементы##{i:0000}", 8, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||
{
|
||||
ImGui.TableSetupColumn("Индекс скрипта");
|
||||
ImGui.TableSetupColumn("Индекс встроенного скрипта");
|
||||
ImGui.TableSetupColumn("UnkInner2");
|
||||
ImGui.TableSetupColumn("UnkInner3");
|
||||
ImGui.TableSetupColumn("UnkInner4");
|
||||
ImGui.TableSetupColumn("Тип действия");
|
||||
ImGui.TableSetupColumn("UnkInner5");
|
||||
ImGui.TableSetupColumn("Кол-во аргументов");
|
||||
ImGui.TableSetupColumn("Аргументы");
|
||||
@@ -62,21 +66,66 @@ public class ScrExplorer : IImGuiPanel
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(inner.ScriptIndex.ToString());
|
||||
|
||||
if (inner.ScriptIndex == 2)
|
||||
{
|
||||
Utils.ShowHint("Первый аргумент - номер проблемы");
|
||||
}
|
||||
|
||||
if (inner.ScriptIndex == 4)
|
||||
{
|
||||
Utils.ShowHint("Первый аргумент - номер проблемы");
|
||||
}
|
||||
|
||||
if (inner.ScriptIndex == 8)
|
||||
{
|
||||
Utils.ShowHint("Установить dCurrentProblem стейт (VARSET:arg0)");
|
||||
}
|
||||
|
||||
if (inner.ScriptIndex == 20)
|
||||
{
|
||||
Utils.ShowHint("Первый аргумент - номер проблемы");
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(inner.UnkInner2.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(inner.UnkInner3.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(inner.UnkInner4.ToString());
|
||||
ImGui.Text($"{(int) inner.Type}: {inner.Type:G}");
|
||||
if (inner.Type == ScrEntryInnerType._0)
|
||||
{
|
||||
Utils.ShowHint("0 обязан иметь аргументы");
|
||||
}
|
||||
|
||||
if (inner.Type == ScrEntryInnerType.CheckInternalState)
|
||||
{
|
||||
Utils.ShowHint("Для 5 вообще не нужны данные, игра проверяет внутренний стейт");
|
||||
}
|
||||
|
||||
if (inner.Type == ScrEntryInnerType.SetVarsetValue)
|
||||
{
|
||||
Utils.ShowHint("В случае 6, игра берёт UnkInner2 (индекс в Varset) и устанавливает ему значение UnkInner3");
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(inner.UnkInner5.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(inner.ArgumentsCount.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(string.Join(", ", inner.Arguments));
|
||||
foreach (var argument in inner.Arguments)
|
||||
{
|
||||
if (ImGui.Button(argument.ToString()))
|
||||
{
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(inner.UnkInner7.ToString());
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ public class TexmExplorer : IImGuiPanel
|
||||
{
|
||||
if (ImGui.Begin("TEXM Explorer"))
|
||||
{
|
||||
ImGui.Text("TEXM - это файл текстуры. Их можно найти внутри NRes архивов, например Textures.lib");
|
||||
ImGui.Separator();
|
||||
|
||||
if (!_viewModel.HasFile)
|
||||
{
|
||||
ImGui.Text("No TEXM opened");
|
||||
@@ -59,7 +62,7 @@ public class TexmExplorer : IImGuiPanel
|
||||
|
||||
ImGui.Text("Magic2: ");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(_viewModel.TexmFile.Header.Magic2.ToString());
|
||||
ImGui.Text(_viewModel.TexmFile.Header.FormatOptionFlags.ToString());
|
||||
|
||||
ImGui.Text("Format: ");
|
||||
ImGui.SameLine();
|
||||
|
||||
60
NResUI/ImGuiUI/VarsetExplorerPanel.cs
Normal file
60
NResUI/ImGuiUI/VarsetExplorerPanel.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using ImGuiNET;
|
||||
using NResUI.Abstractions;
|
||||
using NResUI.Models;
|
||||
|
||||
namespace NResUI.ImGuiUI;
|
||||
|
||||
public class VarsetExplorerPanel : IImGuiPanel
|
||||
{
|
||||
private readonly VarsetViewModel _viewModel;
|
||||
|
||||
public VarsetExplorerPanel(VarsetViewModel viewModel)
|
||||
{
|
||||
_viewModel = viewModel;
|
||||
}
|
||||
|
||||
public void OnImGuiRender()
|
||||
{
|
||||
if (ImGui.Begin("VARSET Explorer"))
|
||||
{
|
||||
ImGui.Text(".var - это файл динамических настроек. Можно найти в MISSIONS/SCRIPTS/varset.var, а также внутри behpsp.res");
|
||||
ImGui.Separator();
|
||||
|
||||
if (_viewModel.Items.Count == 0)
|
||||
{
|
||||
ImGui.Text("VARSET не загружен");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui.BeginTable($"varset", 4, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||
{
|
||||
ImGui.TableSetupColumn("Индекс");
|
||||
ImGui.TableSetupColumn("Тип");
|
||||
ImGui.TableSetupColumn("Имя");
|
||||
ImGui.TableSetupColumn("Значение");
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
for (int j = 0; j < _viewModel.Items.Count; j++)
|
||||
{
|
||||
var item = _viewModel.Items[j];
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(j.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(item.Type);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(item.Name);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(item.Value);
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
ImGui.End();
|
||||
}
|
||||
}
|
||||
}
|
||||
50
NResUI/Models/CpDatSchemeViewModel.cs
Normal file
50
NResUI/Models/CpDatSchemeViewModel.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using CpDatLib;
|
||||
using ScrLib;
|
||||
|
||||
namespace NResUI.Models;
|
||||
|
||||
public class CpDatSchemeViewModel
|
||||
{
|
||||
public bool HasFile { get; set; }
|
||||
public string? Error { get; set; }
|
||||
|
||||
public CpDatScheme? CpDatScheme { get; set; }
|
||||
|
||||
public List<(int Level, CpDatEntry Entry)> FlatList { get; set; } = [];
|
||||
|
||||
public string? Path { get; set; }
|
||||
|
||||
public void SetParseResult(CpDatParseResult parseResult, string path)
|
||||
{
|
||||
CpDatScheme = parseResult.Scheme;
|
||||
Error = parseResult.Error;
|
||||
HasFile = true;
|
||||
Path = path;
|
||||
|
||||
if (CpDatScheme is not null)
|
||||
{
|
||||
RebuildFlatList();
|
||||
}
|
||||
}
|
||||
|
||||
public void RebuildFlatList()
|
||||
{
|
||||
FlatList = [];
|
||||
|
||||
if (CpDatScheme is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CollectEntries(CpDatScheme.Root, 0);
|
||||
|
||||
void CollectEntries(CpDatEntry entry, int level)
|
||||
{
|
||||
FlatList.Add((level, entry));
|
||||
foreach (var child in entry.Children)
|
||||
{
|
||||
CollectEntries(child, level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
NResUI/Models/MaterialViewModel.cs
Normal file
26
NResUI/Models/MaterialViewModel.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using MaterialLib;
|
||||
|
||||
namespace NResUI.Models;
|
||||
|
||||
public class MaterialViewModel
|
||||
{
|
||||
public MaterialFile? MaterialFile { get; private set; }
|
||||
public string? FilePath { get; private set; }
|
||||
public string? Error { get; private set; }
|
||||
|
||||
public bool HasFile => MaterialFile != null;
|
||||
|
||||
public void SetParseResult(MaterialFile materialFile, string filePath)
|
||||
{
|
||||
MaterialFile = materialFile;
|
||||
FilePath = filePath;
|
||||
Error = null;
|
||||
}
|
||||
|
||||
public void SetError(string error)
|
||||
{
|
||||
MaterialFile = null;
|
||||
FilePath = null;
|
||||
Error = error;
|
||||
}
|
||||
}
|
||||
8
NResUI/Models/VarsetViewModel.cs
Normal file
8
NResUI/Models/VarsetViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using VarsetLib;
|
||||
|
||||
namespace NResUI.Models;
|
||||
|
||||
public class VarsetViewModel
|
||||
{
|
||||
public List<VarsetItem> Items { get; set; } = [];
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType Condition="'$(OS)' == 'Windows_NT'">WinExe</OutputType>
|
||||
<OutputType Condition="'$(OS)' != 'Windows_NT'">Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -12,17 +10,20 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageReference Include="NativeFileDialogSharp" Version="0.5.0" />
|
||||
<PackageReference Include="Silk.NET" Version="2.22.0" />
|
||||
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="NativeFileDialogSharp" />
|
||||
<PackageReference Include="Silk.NET" />
|
||||
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CpDatLib\CpDatLib.csproj" />
|
||||
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
||||
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
||||
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
||||
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
||||
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
|
||||
<ProjectReference Include="..\MaterialLib\MaterialLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -137,9 +137,9 @@ namespace NResUI
|
||||
|
||||
public void SetLod(int @base, int min, int max)
|
||||
{
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, @base);
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, min);
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, max);
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, in @base);
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, in min);
|
||||
_gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, in max);
|
||||
}
|
||||
|
||||
public void SetWrap(TextureCoordinate coord, TextureWrapMode mode)
|
||||
|
||||
54
PalLib/PalFile.cs
Normal file
54
PalLib/PalFile.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace PalLib;
|
||||
|
||||
/// <summary>
|
||||
/// PAL файл по сути это indexed текстура (1024 байт - 256 цветов lookup + 4 байта "Ipol" и затем 256x256 индексов в lookup)
|
||||
/// </summary>
|
||||
public class PalFile
|
||||
{
|
||||
public required string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 256 цветов lookup (1024 байт)
|
||||
/// </summary>
|
||||
public required byte[] Palette { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 256x256 индексов в lookup
|
||||
/// </summary>
|
||||
public required byte[] Indices { get; set; }
|
||||
|
||||
public void SaveAsPng(string outputPath)
|
||||
{
|
||||
const int width = 256;
|
||||
const int height = 256;
|
||||
|
||||
var rgbaBytes = new byte[width * height * 4];
|
||||
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
{
|
||||
var index = Indices[i];
|
||||
|
||||
// Palette is 256 colors * 4 bytes (ARGB usually, based on TexmLib)
|
||||
// TexmLib: r = lookup[i*4+0], g = lookup[i*4+1], b = lookup[i*4+2], a = lookup[i*4+3]
|
||||
// Assuming same format here.
|
||||
|
||||
// since PAL is likely directx related, the format is is likely BGRA
|
||||
|
||||
var b = Palette[index * 4 + 0];
|
||||
var g = Palette[index * 4 + 1];
|
||||
var r = Palette[index * 4 + 2];
|
||||
var a = Palette[index * 4 + 3]; // Alpha? Or is it unused/padding? TexmLib sets alpha to 255 manually for indexed.
|
||||
|
||||
rgbaBytes[i * 4 + 0] = r;
|
||||
rgbaBytes[i * 4 + 1] = g;
|
||||
rgbaBytes[i * 4 + 2] = b;
|
||||
rgbaBytes[i * 4 + 3] = 255;
|
||||
}
|
||||
|
||||
using var image = Image.LoadPixelData<Rgba32>(rgbaBytes, width, height);
|
||||
image.SaveAsPng(outputPath);
|
||||
}
|
||||
}
|
||||
7
PalLib/PalLib.csproj
Normal file
7
PalLib/PalLib.csproj
Normal file
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
37
PalLib/PalParser.cs
Normal file
37
PalLib/PalParser.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Text;
|
||||
|
||||
namespace PalLib;
|
||||
|
||||
public class PalParser
|
||||
{
|
||||
public static PalFile ReadFromStream(Stream stream, string filename)
|
||||
{
|
||||
// Expected size: 1024 (palette) + 4 ("Ipol") + 65536 (indices) = 66564
|
||||
if (stream.Length != 66564)
|
||||
{
|
||||
throw new InvalidDataException($"Invalid PAL file size. Expected 66564 bytes, got {stream.Length}.");
|
||||
}
|
||||
|
||||
var palette = new byte[1024];
|
||||
stream.ReadExactly(palette, 0, 1024);
|
||||
|
||||
var signatureBytes = new byte[4];
|
||||
stream.ReadExactly(signatureBytes, 0, 4);
|
||||
var signature = Encoding.ASCII.GetString(signatureBytes);
|
||||
|
||||
if (signature != "Ipol")
|
||||
{
|
||||
throw new InvalidDataException($"Invalid PAL file signature. Expected 'Ipol', got '{signature}'.");
|
||||
}
|
||||
|
||||
var indices = new byte[65536];
|
||||
stream.ReadExactly(indices, 0, 65536);
|
||||
|
||||
return new PalFile
|
||||
{
|
||||
FileName = filename,
|
||||
Palette = palette,
|
||||
Indices = indices
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParkanPlayground", "ParkanPlayground\ParkanPlayground.csproj", "{7DB19000-6F41-4BAE-A904-D34EFCA065E9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextureDecoder", "TextureDecoder\TextureDecoder.csproj", "{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLUnpacker", "NLUnpacker\NLUnpacker.csproj", "{50C83E6C-23ED-4A8E-B948-89686A742CF0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NResUI", "NResUI\NResUI.csproj", "{7456A089-0701-416C-8668-1F740BF4B72C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NResLib", "NResLib\NResLib.csproj", "{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeshUnpacker", "MeshUnpacker\MeshUnpacker.csproj", "{F1465FFE-0D66-4A3C-90D7-153A14E226E6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TexmLib", "TexmLib\TexmLib.csproj", "{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionDataUnpacker", "MissionDataUnpacker\MissionDataUnpacker.csproj", "{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "meta", "meta", "{BAF212FE-A0FD-41A2-A1A9-B406FDDFBAF3}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionTmaLib", "MissionTmaLib\MissionTmaLib.csproj", "{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScrLib", "ScrLib\ScrLib.csproj", "{C445359B-97D4-4432-9331-708B5A14887A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7DB19000-6F41-4BAE-A904-D34EFCA065E9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{15D1C9ED-1080-417D-A4D1-CFF80BE6A218}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7456A089-0701-416C-8668-1F740BF4B72C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F1465FFE-0D66-4A3C-90D7-153A14E226E6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{40097CB1-B4B8-4D3E-A874-7D46F5C81DB3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C445359B-97D4-4432-9331-708B5A14887A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C445359B-97D4-4432-9331-708B5A14887A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C445359B-97D4-4432-9331-708B5A14887A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C445359B-97D4-4432-9331-708B5A14887A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
19
ParkanPlayground.slnx
Normal file
19
ParkanPlayground.slnx
Normal file
@@ -0,0 +1,19 @@
|
||||
<Solution>
|
||||
<Folder Name="/meta/">
|
||||
<File Path="Directory.Build.props" />
|
||||
<File Path="Directory.Packages.props" />
|
||||
<File Path="README.md" />
|
||||
</Folder>
|
||||
<Project Path="Common\Common.csproj" Type="Classic C#" />
|
||||
<Project Path="CpDatLib\CpDatLib.csproj" Type="Classic C#" />
|
||||
<Project Path="MaterialLib\MaterialLib.csproj" Type="Classic C#" />
|
||||
<Project Path="MissionTmaLib/MissionTmaLib.csproj" />
|
||||
<Project Path="NLUnpacker/NLUnpacker.csproj" />
|
||||
<Project Path="NResLib/NResLib.csproj" />
|
||||
<Project Path="NResUI/NResUI.csproj" />
|
||||
<Project Path="PalLib\PalLib.csproj" Type="Classic C#" />
|
||||
<Project Path="ParkanPlayground/ParkanPlayground.csproj" />
|
||||
<Project Path="ScrLib/ScrLib.csproj" />
|
||||
<Project Path="TexmLib/TexmLib.csproj" />
|
||||
<Project Path="VarsetLib/VarsetLib.csproj" />
|
||||
</Solution>
|
||||
255
ParkanPlayground/Effects/FxidReader.cs
Normal file
255
ParkanPlayground/Effects/FxidReader.cs
Normal file
@@ -0,0 +1,255 @@
|
||||
using Common;
|
||||
|
||||
namespace ParkanPlayground.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Static reader methods for parsing FXID effect definition structures from binary streams.
|
||||
/// </summary>
|
||||
public static class FxidReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a Vector3 (3 floats: X, Y, Z) from the binary stream.
|
||||
/// </summary>
|
||||
public static Vector3 ReadVector3(BinaryReader br)
|
||||
{
|
||||
float x = br.ReadSingle();
|
||||
float y = br.ReadSingle();
|
||||
float z = br.ReadSingle();
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the 60-byte effect header from the binary stream.
|
||||
/// </summary>
|
||||
public static EffectHeader ReadEffectHeader(BinaryReader br)
|
||||
{
|
||||
EffectHeader h;
|
||||
h.ComponentCount = br.ReadUInt32();
|
||||
h.Unknown1 = br.ReadUInt32();
|
||||
h.Duration = br.ReadSingle();
|
||||
h.Unknown2 = br.ReadSingle();
|
||||
h.Flags = br.ReadUInt32();
|
||||
h.Unknown3 = br.ReadUInt32();
|
||||
h.Reserved = br.ReadBytes(24);
|
||||
h.ScaleX = br.ReadSingle();
|
||||
h.ScaleY = br.ReadSingle();
|
||||
h.ScaleZ = br.ReadSingle();
|
||||
return h;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a BillboardComponentData (type 1) from the binary stream.
|
||||
/// </summary>
|
||||
public static BillboardComponentData ReadBillboardComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
BillboardComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.Unknown04 = br.ReadSingle();
|
||||
d.ScalarAMin = br.ReadSingle();
|
||||
d.ScalarAMax = br.ReadSingle();
|
||||
d.ScalarAExp = br.ReadSingle();
|
||||
d.ActiveTimeStart = br.ReadSingle();
|
||||
d.ActiveTimeEnd = br.ReadSingle();
|
||||
d.SampleSpreadParam = br.ReadSingle();
|
||||
d.Unknown20 = br.ReadUInt32();
|
||||
d.PrimarySampleCount = br.ReadUInt32();
|
||||
d.SecondarySampleCount = br.ReadUInt32();
|
||||
d.ExtentVec0 = ReadVector3(br);
|
||||
d.ExtentVec1 = ReadVector3(br);
|
||||
d.ExtentVec2 = ReadVector3(br);
|
||||
d.ExponentTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet1 = ReadVector3(br);
|
||||
d.RadiusTriplet2 = ReadVector3(br);
|
||||
d.ExponentTriplet1 = ReadVector3(br);
|
||||
d.NoiseAmplitude = br.ReadSingle();
|
||||
d.Reserved = br.ReadBytes(0x50);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a SoundComponentData (type 2) from the binary stream.
|
||||
/// </summary>
|
||||
public static SoundComponentData ReadSoundComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
SoundComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.PlayMode = br.ReadUInt32();
|
||||
d.StartTime = br.ReadSingle();
|
||||
d.EndTime = br.ReadSingle();
|
||||
d.Pos0 = ReadVector3(br);
|
||||
d.Pos1 = ReadVector3(br);
|
||||
d.Offset0 = ReadVector3(br);
|
||||
d.Offset1 = ReadVector3(br);
|
||||
d.Scalar0Min = br.ReadSingle();
|
||||
d.Scalar0Max = br.ReadSingle();
|
||||
d.Scalar1Min = br.ReadSingle();
|
||||
d.Scalar1Max = br.ReadSingle();
|
||||
d.SoundFlags = br.ReadUInt32();
|
||||
d.SoundNameAndReserved = br.ReadBytes(0x40);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an AnimParticleComponentData (type 3) from the binary stream.
|
||||
/// </summary>
|
||||
public static AnimParticleComponentData ReadAnimParticleComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
AnimParticleComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.Unknown04 = br.ReadSingle();
|
||||
d.ScalarAMin = br.ReadSingle();
|
||||
d.ScalarAMax = br.ReadSingle();
|
||||
d.ScalarAExp = br.ReadSingle();
|
||||
d.ActiveTimeStart = br.ReadSingle();
|
||||
d.ActiveTimeEnd = br.ReadSingle();
|
||||
d.SampleSpreadParam = br.ReadSingle();
|
||||
d.Unknown20 = br.ReadUInt32();
|
||||
d.PrimarySampleCount = br.ReadUInt32();
|
||||
d.SecondarySampleCount = br.ReadUInt32();
|
||||
d.ExtentVec0 = ReadVector3(br);
|
||||
d.ExtentVec1 = ReadVector3(br);
|
||||
d.ExtentVec2 = ReadVector3(br);
|
||||
d.ExponentTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet1 = ReadVector3(br);
|
||||
d.RadiusTriplet2 = ReadVector3(br);
|
||||
d.ExponentTriplet1 = ReadVector3(br);
|
||||
d.NoiseAmplitude = br.ReadSingle();
|
||||
d.Reserved = br.ReadBytes(0x38);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an AnimBillboardComponentData (type 4) from the binary stream.
|
||||
/// </summary>
|
||||
public static AnimBillboardComponentData ReadAnimBillboardComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
AnimBillboardComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.Unknown04 = br.ReadSingle();
|
||||
d.ScalarAMin = br.ReadSingle();
|
||||
d.ScalarAMax = br.ReadSingle();
|
||||
d.ScalarAExp = br.ReadSingle();
|
||||
d.ActiveTimeStart = br.ReadSingle();
|
||||
d.ActiveTimeEnd = br.ReadSingle();
|
||||
d.SampleSpreadParam = br.ReadSingle();
|
||||
d.Unknown20 = br.ReadUInt32();
|
||||
d.PrimarySampleCount = br.ReadUInt32();
|
||||
d.SecondarySampleCount = br.ReadUInt32();
|
||||
d.ExtentVec0 = ReadVector3(br);
|
||||
d.ExtentVec1 = ReadVector3(br);
|
||||
d.ExtentVec2 = ReadVector3(br);
|
||||
d.ExponentTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet1 = ReadVector3(br);
|
||||
d.RadiusTriplet2 = ReadVector3(br);
|
||||
d.ExponentTriplet1 = ReadVector3(br);
|
||||
d.NoiseAmplitude = br.ReadSingle();
|
||||
d.Reserved = br.ReadBytes(0x3C);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a TrailComponentData (type 5) from the binary stream.
|
||||
/// </summary>
|
||||
public static TrailComponentData ReadTrailComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
TrailComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.Unknown04To10 = br.ReadBytes(0x10);
|
||||
d.SegmentCount = br.ReadUInt32();
|
||||
d.Param0 = br.ReadSingle();
|
||||
d.Param1 = br.ReadSingle();
|
||||
d.Unknown20 = br.ReadUInt32();
|
||||
d.Unknown24 = br.ReadUInt32();
|
||||
d.ActiveTimeStart = br.ReadSingle();
|
||||
d.ActiveTimeEnd = br.ReadSingle();
|
||||
d.TextureNameAndReserved = br.ReadBytes(0x40);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a PointComponentData (type 6) from the binary stream.
|
||||
/// Note: Point components have no payload beyond the typeAndFlags header.
|
||||
/// </summary>
|
||||
public static PointComponentData ReadPointComponent(uint typeAndFlags)
|
||||
{
|
||||
PointComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a PlaneComponentData (type 7) from the binary stream.
|
||||
/// </summary>
|
||||
public static PlaneComponentData ReadPlaneComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
PlaneComponentData d;
|
||||
d.Base = ReadAnimParticleComponent(br, typeAndFlags);
|
||||
d.ExtraPlaneParam0 = br.ReadUInt32();
|
||||
d.ExtraPlaneParam1 = br.ReadUInt32();
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a ModelComponentData (type 8) from the binary stream.
|
||||
/// </summary>
|
||||
public static ModelComponentData ReadModelComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
ModelComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.Unk04 = br.ReadBytes(0x14);
|
||||
d.ActiveTimeStart = br.ReadSingle();
|
||||
d.ActiveTimeEnd = br.ReadSingle();
|
||||
d.Unknown20 = br.ReadUInt32();
|
||||
d.InstanceCount = br.ReadUInt32();
|
||||
d.BasePos = ReadVector3(br);
|
||||
d.OffsetPos = ReadVector3(br);
|
||||
d.ScatterExtent = ReadVector3(br);
|
||||
d.Axis0 = ReadVector3(br);
|
||||
d.Axis1 = ReadVector3(br);
|
||||
d.Axis2 = ReadVector3(br);
|
||||
d.Reserved70 = br.ReadBytes(0x18);
|
||||
d.RadiusTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet1 = ReadVector3(br);
|
||||
d.ReservedA0 = br.ReadBytes(0x18);
|
||||
d.TextureNameAndFlags = br.ReadBytes(0x40);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an AnimModelComponentData (type 9) from the binary stream.
|
||||
/// </summary>
|
||||
public static AnimModelComponentData ReadAnimModelComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
AnimModelComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.AnimSpeed = br.ReadSingle();
|
||||
d.MinTime = br.ReadSingle();
|
||||
d.MaxTime = br.ReadSingle();
|
||||
d.Exponent = br.ReadSingle();
|
||||
d.Reserved14 = br.ReadBytes(0x14);
|
||||
d.DirVec0 = ReadVector3(br);
|
||||
d.Reserved34 = br.ReadBytes(0x0C);
|
||||
d.RadiusTriplet0 = ReadVector3(br);
|
||||
d.DirVec1 = ReadVector3(br);
|
||||
d.RadiusTriplet1 = ReadVector3(br);
|
||||
d.ExtentVec0 = ReadVector3(br);
|
||||
d.ExtentVec1 = ReadVector3(br);
|
||||
d.Reserved7C = br.ReadBytes(0x0C);
|
||||
d.TextureNameAndFlags = br.ReadBytes(0x48);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a CubeComponentData (type 10) from the binary stream.
|
||||
/// </summary>
|
||||
public static CubeComponentData ReadCubeComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
CubeComponentData d;
|
||||
d.Base = ReadAnimBillboardComponent(br, typeAndFlags);
|
||||
d.ExtraCubeParam0 = br.ReadUInt32();
|
||||
return d;
|
||||
}
|
||||
}
|
||||
237
ParkanPlayground/Effects/FxidTypes.cs
Normal file
237
ParkanPlayground/Effects/FxidTypes.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using Common;
|
||||
|
||||
namespace ParkanPlayground.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Effect-level header at the start of each FXID file (60 bytes total).
|
||||
/// Parsed from CEffect_InitFromDef: defines component count, global duration/flags,
|
||||
/// some unknown control fields, and the uniform scale vector applied to the effect.
|
||||
/// </summary>
|
||||
public struct EffectHeader
|
||||
{
|
||||
public uint ComponentCount;
|
||||
public uint Unknown1;
|
||||
public float Duration;
|
||||
public float Unknown2;
|
||||
public uint Flags;
|
||||
public uint Unknown3;
|
||||
public byte[] Reserved; // 24 bytes
|
||||
public float ScaleX;
|
||||
public float ScaleY;
|
||||
public float ScaleZ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared on-disk definition layout for billboard-style components (type 1).
|
||||
/// Used by CBillboardComponent_Initialize/Update/Render to drive size/color/alpha
|
||||
/// curves and sample scattering within a 3D extent volume.
|
||||
/// </summary>
|
||||
public struct BillboardComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // type (low byte) and flags as seen in CEffect_InitFromDef
|
||||
public float Unknown04; // mode / flag-like float, semantics not fully clear
|
||||
public float ScalarAMin; // base scalar A (e.g. base radius)
|
||||
public float ScalarAMax; // max scalar A
|
||||
public float ScalarAExp; // exponent applied to scalar A curve
|
||||
public float ActiveTimeStart; // activation window start (seconds)
|
||||
public float ActiveTimeEnd; // activation window end (seconds)
|
||||
public float SampleSpreadParam; // extra param used when scattering samples
|
||||
public uint Unknown20; // used as integer param in billboard code, exact meaning unknown
|
||||
public uint PrimarySampleCount; // number of samples along primary axis
|
||||
public uint SecondarySampleCount; // number of samples along secondary axis
|
||||
public Vector3 ExtentVec0; // base extent/origin vector
|
||||
public Vector3 ExtentVec1; // extent center / offset
|
||||
public Vector3 ExtentVec2; // extent size used for random boxing
|
||||
public Vector3 ExponentTriplet0;// exponent triplet for size/color curve
|
||||
public Vector3 RadiusTriplet0; // radius curve key 0
|
||||
public Vector3 RadiusTriplet1; // radius curve key 1
|
||||
public Vector3 RadiusTriplet2; // radius curve key 2
|
||||
public Vector3 ExponentTriplet1;// second exponent triplet (e.g. alpha curve)
|
||||
public float NoiseAmplitude; // per-sample noise amplitude
|
||||
public byte[] Reserved; // 0x50-byte tail, currently not touched by billboard code
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3D sound component definition (type 2).
|
||||
/// Used by CSoundComponent_Initialize/Update to drive positional audio, playback
|
||||
/// window, and scalar ranges (e.g. volume / pitch), plus a 0x40-byte sound name tail.
|
||||
/// </summary>
|
||||
public struct SoundComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // component type and flags
|
||||
public uint PlayMode; // playback mode (looping, one-shot, etc.)
|
||||
public float StartTime; // playback window start (seconds)
|
||||
public float EndTime; // playback window end (seconds)
|
||||
public Vector3 Pos0; // base 3D position or path start
|
||||
public Vector3 Pos1; // secondary position / path end
|
||||
public Vector3 Offset0; // random offset range 0
|
||||
public Vector3 Offset1; // random offset range 1
|
||||
public float Scalar0Min; // scalar range 0 min (e.g. volume)
|
||||
public float Scalar0Max; // scalar range 0 max
|
||||
public float Scalar1Min; // scalar range 1 min (e.g. pitch)
|
||||
public float Scalar1Max; // scalar range 1 max
|
||||
public uint SoundFlags; // misc sound control flags
|
||||
public byte[] SoundNameAndReserved; // 0x40-byte tail; sound name plus padding/unused
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animated particle component definition (type 3).
|
||||
/// Prefix layout matches BillboardComponentData and is used to allocate a grid of
|
||||
/// particle objects; the 0x38-byte tail is passed into CFxManager_LoadTexture.
|
||||
/// </summary>
|
||||
public struct AnimParticleComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // type (low byte) and flags as seen in CEffect_InitFromDef
|
||||
public float Unknown04; // mode / flag-like float, semantics not fully clear
|
||||
public float ScalarAMin; // base scalar A (e.g. base radius)
|
||||
public float ScalarAMax; // max scalar A
|
||||
public float ScalarAExp; // exponent applied to scalar A curve
|
||||
public float ActiveTimeStart; // activation window start (seconds)
|
||||
public float ActiveTimeEnd; // activation window end (seconds)
|
||||
public float SampleSpreadParam; // extra param used when scattering particles
|
||||
public uint Unknown20; // used as integer param in anim particle code, exact meaning unknown
|
||||
public uint PrimarySampleCount; // number of particles along primary axis
|
||||
public uint SecondarySampleCount; // number of particles along secondary axis
|
||||
public Vector3 ExtentVec0; // base extent/origin vector
|
||||
public Vector3 ExtentVec1; // extent center / offset
|
||||
public Vector3 ExtentVec2; // extent size used for random boxing
|
||||
public Vector3 ExponentTriplet0;// exponent triplet for size/color curve
|
||||
public Vector3 RadiusTriplet0; // radius curve key 0
|
||||
public Vector3 RadiusTriplet1; // radius curve key 1
|
||||
public Vector3 RadiusTriplet2; // radius curve key 2
|
||||
public Vector3 ExponentTriplet1;// second exponent triplet (e.g. alpha curve)
|
||||
public float NoiseAmplitude; // per-particle noise amplitude
|
||||
public byte[] Reserved; // 0x38-byte tail; forwarded to CFxManager_LoadTexture unchanged
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animated billboard component definition (type 4).
|
||||
/// Shares the same prefix layout as BillboardComponentData, including extents and
|
||||
/// radius/exponent triplets, but uses a 0x3C-byte tail passed to CFxManager_LoadTexture.
|
||||
/// </summary>
|
||||
public struct AnimBillboardComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // type (low byte) and flags as seen in CEffect_InitFromDef
|
||||
public float Unknown04; // mode / flag-like float, semantics not fully clear
|
||||
public float ScalarAMin; // base scalar A (e.g. base radius)
|
||||
public float ScalarAMax; // max scalar A
|
||||
public float ScalarAExp; // exponent applied to scalar A curve
|
||||
public float ActiveTimeStart; // activation window start (seconds)
|
||||
public float ActiveTimeEnd; // activation window end (seconds)
|
||||
public float SampleSpreadParam; // extra param used when scattering animated billboards
|
||||
public uint Unknown20; // used as integer param in anim billboard code, exact meaning unknown
|
||||
public uint PrimarySampleCount; // number of samples along primary axis
|
||||
public uint SecondarySampleCount; // number of samples along secondary axis
|
||||
public Vector3 ExtentVec0; // base extent/origin vector
|
||||
public Vector3 ExtentVec1; // extent center / offset
|
||||
public Vector3 ExtentVec2; // extent size used for random boxing
|
||||
public Vector3 ExponentTriplet0;// exponent triplet for size/color curve
|
||||
public Vector3 RadiusTriplet0; // radius curve key 0
|
||||
public Vector3 RadiusTriplet1; // radius curve key 1
|
||||
public Vector3 RadiusTriplet2; // radius curve key 2
|
||||
public Vector3 ExponentTriplet1;// second exponent triplet (e.g. alpha curve)
|
||||
public float NoiseAmplitude; // per-sample noise amplitude
|
||||
public byte[] Reserved; // 0x3C-byte tail; forwarded to CFxManager_LoadTexture unchanged
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compact definition for trail / ribbon components (type 5).
|
||||
/// CTrailComponent_Initialize interprets this as segment count, width/alpha/UV
|
||||
/// ranges, timing, and a shared texture name at +0x30.
|
||||
/// </summary>
|
||||
public struct TrailComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // component type and flags
|
||||
public byte[] Unknown04To10; // 0x10 bytes at +4..+0x13, used only indirectly; types unknown
|
||||
public uint SegmentCount; // number of trail segments (particles)
|
||||
public float Param0; // first width/alpha/UV control value (start)
|
||||
public float Param1; // second width/alpha/UV control value (end)
|
||||
public uint Unknown20; // extra integer parameter, purpose unknown
|
||||
public uint Unknown24; // extra integer parameter, purpose unknown
|
||||
public float ActiveTimeStart; // trail activation start time (>= 0)
|
||||
public float ActiveTimeEnd; // trail activation end time
|
||||
public byte[] TextureNameAndReserved; // 0x40-byte tail containing texture name and padding/flags
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple point component definition (type 6).
|
||||
/// Definition block is just the 4-byte typeAndFlags header; no extra data on disk.
|
||||
/// </summary>
|
||||
public struct PointComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // component type and flags; definition block has no payload
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plane component definition (type 7).
|
||||
/// Shares the same 0xC8-byte prefix layout as AnimParticleComponentData (type 3),
|
||||
/// followed by two dwords of plane-specific data.
|
||||
/// </summary>
|
||||
public struct PlaneComponentData
|
||||
{
|
||||
public AnimParticleComponentData Base; // shared 0xC8-byte prefix: time window, sample counts, extents, curves
|
||||
public uint ExtraPlaneParam0; // plane-specific parameter, semantics not yet reversed
|
||||
public uint ExtraPlaneParam1; // plane-specific parameter, semantics not yet reversed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static model component definition (type 8).
|
||||
/// Layout fully matches the IDA typedef used by CModelComponent_Initialize:
|
||||
/// time window, instance count, spatial extents/axes, radius triplets, and a
|
||||
/// 0x40-byte texture name tail.
|
||||
/// </summary>
|
||||
public struct ModelComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // component type and flags
|
||||
public byte[] Unk04; // 0x14-byte blob at +0x04..+0x17, purpose unclear
|
||||
public float ActiveTimeStart; // activation window start (seconds), +0x18
|
||||
public float ActiveTimeEnd; // activation window end (seconds), +0x1C
|
||||
public uint Unknown20; // extra flags/int parameter at +0x20
|
||||
public uint InstanceCount; // number of model instances to spawn at +0x24
|
||||
public Vector3 BasePos; // base position of the emitter / origin, +0x28
|
||||
public Vector3 OffsetPos; // positional offset applied per-instance, +0x34
|
||||
public Vector3 ScatterExtent; // extent volume used for random scattering, +0x40
|
||||
public Vector3 Axis0; // local axis 0 (orientation / shape), +0x4C
|
||||
public Vector3 Axis1; // local axis 1 (orientation / shape), +0x58
|
||||
public Vector3 Axis2; // local axis 2 (orientation / shape), +0x64
|
||||
public byte[] Reserved70; // 0x18 bytes at +0x70..+0x87, not directly used
|
||||
public Vector3 RadiusTriplet0; // radius / extent triplet 0 at +0x88
|
||||
public Vector3 RadiusTriplet1; // radius / extent triplet 1 at +0x94
|
||||
public byte[] ReservedA0; // 0x18 bytes at +0xA0..+0xB7, not directly used
|
||||
public byte[] TextureNameAndFlags; // 0x40-byte tail at +0xB8: texture name + padding/flags
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animated model component definition (type 9).
|
||||
/// Layout derived from CAnimModelComponent_Initialize: time params, direction vectors,
|
||||
/// radius triplets, extent vectors, and a 0x48-byte texture name tail.
|
||||
/// </summary>
|
||||
public struct AnimModelComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // component type and flags
|
||||
public float AnimSpeed; // animation speed multiplier at +0x04
|
||||
public float MinTime; // activation window start (clamped >= 0) at +0x08
|
||||
public float MaxTime; // activation window end at +0x0C
|
||||
public float Exponent; // exponent for time interpolation at +0x10
|
||||
public byte[] Reserved14; // 0x14 bytes at +0x14..+0x27, padding
|
||||
public Vector3 DirVec0; // normalized direction vector 0 at +0x28
|
||||
public byte[] Reserved34; // 0x0C bytes at +0x34..+0x3F, padding
|
||||
public Vector3 RadiusTriplet0; // radius triplet 0 at +0x40
|
||||
public Vector3 DirVec1; // normalized direction vector 1 at +0x4C
|
||||
public Vector3 RadiusTriplet1; // radius triplet 1 at +0x58
|
||||
public Vector3 ExtentVec0; // extent vector 0 at +0x64
|
||||
public Vector3 ExtentVec1; // extent vector 1 at +0x70
|
||||
public byte[] Reserved7C; // 0x0C bytes at +0x7C..+0x87, padding
|
||||
public byte[] TextureNameAndFlags; // 0x48-byte tail at +0x88: texture/model name + padding
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cube component definition (type 10).
|
||||
/// Shares the same 0xCC-byte prefix layout as AnimBillboardComponentData (type 4),
|
||||
/// followed by one dword of cube-specific data.
|
||||
/// </summary>
|
||||
public struct CubeComponentData
|
||||
{
|
||||
public AnimBillboardComponentData Base; // shared 0xCC-byte prefix: billboard-style time window, extents, curves
|
||||
public uint ExtraCubeParam0; // cube-specific parameter, semantics not yet reversed
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static int ReadInt32LittleEndian(this FileStream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static float ReadFloatLittleEndian(this FileStream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
||||
}
|
||||
|
||||
public static string ReadLengthPrefixedString(this FileStream fs)
|
||||
{
|
||||
var len = fs.ReadInt32LittleEndian();
|
||||
|
||||
if (len == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var buffer = new byte[len];
|
||||
|
||||
fs.ReadExactly(buffer, 0, len);
|
||||
|
||||
return Encoding.ASCII.GetString(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
350
ParkanPlayground/MSH_FORMAT.md
Normal file
350
ParkanPlayground/MSH_FORMAT.md
Normal file
@@ -0,0 +1,350 @@
|
||||
# Документация формата MSH
|
||||
|
||||
Формат `.msh` используется игрой Parkan: Железная стратегия (1998) для хранения 3D-мешей.
|
||||
MSH файлы — это NRes архивы, содержащие несколько типизированных компонентов.
|
||||
|
||||
## Обзор
|
||||
|
||||
Существует **два варианта** формата MSH:
|
||||
|
||||
| Вариант | Применение | Ключевые компоненты | Хранение треугольников |
|
||||
|---------|------------|---------------------|------------------------|
|
||||
| **Модель** | Роботы, здания, объекты | 06, 0D, 07 | Индексированные треугольники |
|
||||
| **Ландшафт** | Террейн | 0B, 15 | Прямые треугольники |
|
||||
|
||||
### Автоопределение типа
|
||||
|
||||
```
|
||||
Модель: Есть компонент 06 (индексы) И 0D (батчи)
|
||||
Ландшафт: Есть компонент 0B (материалы) И НЕТ компонента 06
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Сводка компонентов
|
||||
|
||||
| Тип | Название | Размер элемента | Описание |
|
||||
|:---:|----------|:---------------:|----------|
|
||||
| 01 | Pieces | 38 (0x26) | Части меша / тайлы с LOD-ссылками |
|
||||
| 02 | Submeshes | 68 (0x44) | LOD части с баундинг-боксами |
|
||||
| 03 | Vertices | 12 (0x0C) | Позиции вершин (Vector3) |
|
||||
| 04 | неизвестно | 4 | (неизвестно) |
|
||||
| 05 | неизвестно | 4 | (неизвестно) |
|
||||
| 06 | Indices | 2 | Индексы вершин треугольников (только Модель) |
|
||||
| 07 | неизвестно | 16 | (только Модель) |
|
||||
| 08 | Animations | 4 | Кейфреймы анимации меша |
|
||||
| 0A | ExternalRefs | переменный | Внешние ссылки на меши (строки) |
|
||||
| 0B | неизвестно | 4 | неизвестно (только Ландшафт) |
|
||||
| 0D | неизвестно | 20 (0x14) | неизвестно (только Модель) |
|
||||
| 0E | неизвестно | 4 | неизвестно (только Ландшафт) |
|
||||
| 12 | MicrotextureMap | 4 | неизвестно |
|
||||
| 13 | ShortAnims | 2 | Короткие индексы анимаций |
|
||||
| 15 | неизвестно | 28 (0x1C) | неизвестно |
|
||||
|
||||
---
|
||||
|
||||
## Поток данных
|
||||
|
||||
### Модель (роботы, здания)
|
||||
|
||||
```
|
||||
Компонент 01 (Pieces - части)
|
||||
│
|
||||
└─► Lod[n] ──► Компонент 02 (индекс сабмеша)
|
||||
│
|
||||
├─► StartIndexIn07 ──► Компонент 07 (данные на треугольник)
|
||||
│
|
||||
└─► StartOffsetIn0d:ByteLengthIn0D ──► Компонент 0D (батчи)
|
||||
│
|
||||
├─► IndexInto06:CountOf06 ──► Компонент 06 (индексы)
|
||||
│ │
|
||||
│ └─► Компонент 03 (вершины)
|
||||
│
|
||||
└─► IndexInto03 (базовое смещение вершины)
|
||||
```
|
||||
|
||||
### Ландшафт (террейн)
|
||||
|
||||
```
|
||||
Компонент 01 (Тайлы, обычно 16×16 = 256)
|
||||
│
|
||||
└─► Lod[n] ──► Компонент 02 (индекс сабмеша)
|
||||
│
|
||||
└─► StartIndexIn07:CountIn07 ──► Компонент 15 (треугольники)
|
||||
│
|
||||
└─► Vertex1/2/3Index ──► Компонент 03 (вершины)
|
||||
|
||||
└─► StartIndexIn07:CountIn07 ──► Компонент 0B (материалы, параллельно 15)
|
||||
```
|
||||
|
||||
**Важно:** В ландшафтных мешах поля `StartIndexIn07` и `CountIn07` в Компоненте 02
|
||||
используются для индексации в Компонент 15 (треугольники), а не в Компонент 07.
|
||||
|
||||
---
|
||||
|
||||
## Структуры компонентов
|
||||
|
||||
### Компонент 01 - Pieces (0x26 = 38 байт)
|
||||
|
||||
Определяет части меша (для моделей) или тайлы террейна (для ландшафтов).
|
||||
|
||||
| Смещение | Размер | Тип | Поле | Описание |
|
||||
|:--------:|:------:|:---:|------|----------|
|
||||
| 0x00 | 1 | byte | Type1 | Флаги типа части |
|
||||
| 0x01 | 1 | byte | Type2 | Дополнительные флаги |
|
||||
| 0x02 | 2 | int16 | ParentIndex | Индекс родителя (-1 = корень) |
|
||||
| 0x04 | 2 | int16 | OffsetIntoFile13 | Смещение в короткие анимации |
|
||||
| 0x06 | 2 | int16 | IndexInFile08 | Индекс в анимации |
|
||||
| 0x08 | 30 | ushort[15] | Lod | Индексы сабмешей по LOD-уровням (0xFFFF = не используется) |
|
||||
|
||||
**Ландшафт:** 256 тайлов в сетке 16×16. Каждый тайл имеет 2 LOD (индексы 0-255 и 256-511).
|
||||
|
||||
---
|
||||
|
||||
### Компонент 02 - Submeshes (Заголовок: 0x8C = 140 байт, Элемент: 0x44 = 68 байт)
|
||||
|
||||
#### Заголовок (140 байт)
|
||||
|
||||
| Смещение | Размер | Тип | Поле | Описание |
|
||||
|:--------:|:------:|:---:|------|----------|
|
||||
| 0x00 | 96 | Vector3[8] | BoundingBox | 8-точечный баундинг-бокс |
|
||||
| 0x60 | 12 | Vector3 | Center | Центральная точка |
|
||||
| 0x6C | 4 | float | CenterW | W-компонента |
|
||||
| 0x70 | 12 | Vector3 | Bottom | Нижняя точка |
|
||||
| 0x7C | 12 | Vector3 | Top | Верхняя точка |
|
||||
| 0x88 | 4 | float | XYRadius | Радиус в плоскости XY |
|
||||
|
||||
#### Элемент (68 байт)
|
||||
|
||||
| Смещение | Размер | Тип | Поле | Описание |
|
||||
|:--------:|:------:|:---:|------|----------|
|
||||
| 0x00 | 2 | ushort | StartIndexIn07 | **Модель:** Начальный индекс в Компоненте 07<br>**Ландшафт:** Начальный индекс треугольника в Компоненте 15 |
|
||||
| 0x02 | 2 | ushort | CountIn07 | **Модель:** Количество в Компоненте 07<br>**Ландшафт:** Количество треугольников |
|
||||
| 0x04 | 2 | ushort | StartOffsetIn0d | Начальное смещение в Компоненте 0D (только Модель) |
|
||||
| 0x06 | 2 | ushort | ByteLengthIn0D | Количество батчей в Компоненте 0D (только Модель) |
|
||||
| 0x08 | 12 | Vector3 | LocalMinimum | Минимум локального баундинг-бокса |
|
||||
| 0x14 | 12 | Vector3 | LocalMaximum | Максимум локального баундинг-бокса |
|
||||
| 0x20 | 12 | Vector3 | Center | Центр сабмеша |
|
||||
| 0x2C | 12 | Vector3 | Vector4 | Неизвестно |
|
||||
| 0x38 | 12 | Vector3 | Vector5 | Неизвестно |
|
||||
|
||||
---
|
||||
|
||||
### Компонент 03 - Vertices (0x0C = 12 байт)
|
||||
|
||||
| Смещение | Размер | Тип | Поле | Описание |
|
||||
|:--------:|:------:|:---:|------|----------|
|
||||
| 0x00 | 4 | float | X | Координата X |
|
||||
| 0x04 | 4 | float | Y | Координата Y |
|
||||
| 0x08 | 4 | float | Z | Координата Z |
|
||||
|
||||
---
|
||||
|
||||
### Компонент 06 - Indices (2 байта) - Только Модель
|
||||
|
||||
Массив `ushort` значений — индексы вершин треугольников.
|
||||
Используются группами по 3 для каждого треугольника. Ссылки через батчи Компонента 0D.
|
||||
|
||||
---
|
||||
|
||||
### Компонент 07 - Triangle Data (0x10 = 16 байт) - Только Модель
|
||||
|
||||
Данные рендеринга на каждый треугольник.
|
||||
|
||||
| Смещение | Размер | Тип | Поле | Описание |
|
||||
|:--------:|:------:|:---:|------|----------|
|
||||
| 0x00 | 2 | ushort | Flags | Флаги рендера |
|
||||
| 0x02 | 2 | ushort | Magic02 | Неизвестно |
|
||||
| 0x04 | 2 | ushort | Magic04 | Неизвестно |
|
||||
| 0x06 | 2 | ushort | Magic06 | Неизвестно |
|
||||
| 0x08 | 2 | int16 | OffsetX | Нормализованный X (÷32767 для -1..1) |
|
||||
| 0x0A | 2 | int16 | OffsetY | Нормализованный Y (÷32767 для -1..1) |
|
||||
| 0x0C | 2 | int16 | OffsetZ | Нормализованный Z (÷32767 для -1..1) |
|
||||
| 0x0E | 2 | ushort | Magic14 | Неизвестно |
|
||||
|
||||
---
|
||||
|
||||
### Компонент 0B - Material Data (4 байта) - Только Ландшафт
|
||||
|
||||
Информация о материале/текстуре на каждый треугольник. Параллельный массив к Компоненту 15.
|
||||
|
||||
| Смещение | Размер | Тип | Поле | Описание |
|
||||
|:--------:|:------:|:---:|------|----------|
|
||||
| 0x00 | 2 | ushort | HighWord | Индекс материала/текстуры |
|
||||
| 0x02 | 2 | ushort | LowWord | Индекс треугольника (последовательный) |
|
||||
|
||||
---
|
||||
|
||||
### Компонент 0D - Draw Batches (0x14 = 20 байт) - Только Модель
|
||||
|
||||
Определяет батчи вызовов отрисовки.
|
||||
|
||||
| Смещение | Размер | Тип | Поле | Описание |
|
||||
|:--------:|:------:|:---:|------|----------|
|
||||
| 0x00 | 2 | ushort | Flags | Флаги батча |
|
||||
| 0x02 | 2 | - | Padding | - |
|
||||
| 0x04 | 1 | byte | Magic04 | Неизвестно |
|
||||
| 0x05 | 1 | byte | Magic05 | Неизвестно |
|
||||
| 0x06 | 2 | ushort | Magic06 | Неизвестно |
|
||||
| 0x08 | 2 | ushort | CountOf06 | Количество индексов для отрисовки |
|
||||
| 0x0A | 4 | int32 | IndexInto06 | Начальный индекс в Компоненте 06 |
|
||||
| 0x0E | 2 | ushort | CountOf03 | Количество вершин |
|
||||
| 0x10 | 4 | int32 | IndexInto03 | Базовое смещение вершины в Компоненте 03 |
|
||||
|
||||
---
|
||||
|
||||
### Компонент 15 - Triangles (0x1C = 28 байт)
|
||||
|
||||
Прямые определения треугольников. Используется и Моделью и Ландшафтом,
|
||||
но только Ландшафт использует их напрямую для рендеринга.
|
||||
|
||||
| Смещение | Размер | Тип | Поле | Описание |
|
||||
|:--------:|:------:|:---:|------|----------|
|
||||
| 0x00 | 4 | uint32 | Flags | Флаги треугольника (0x20000 = коллизия) |
|
||||
| 0x04 | 4 | uint32 | MaterialData | Данные материала (см. ниже) |
|
||||
| 0x08 | 2 | ushort | Vertex1Index | Индекс первой вершины |
|
||||
| 0x0A | 2 | ushort | Vertex2Index | Индекс второй вершины |
|
||||
| 0x0C | 2 | ushort | Vertex3Index | Индекс третьей вершины |
|
||||
| 0x0E | 4 | uint32 | Magic0E | Неизвестно |
|
||||
| 0x12 | 4 | uint32 | Magic12 | Неизвестно |
|
||||
| 0x16 | 4 | uint32 | Magic16 | Неизвестно |
|
||||
| 0x1A | 2 | ushort | Magic1A | Неизвестно |
|
||||
|
||||
#### MaterialData (0x04) - Структура материала
|
||||
|
||||
```
|
||||
MaterialData = 0xFFFF_SSPP
|
||||
│ │└─ PP: Основной материал (byte 0)
|
||||
│ └─── SS: Вторичный материал для блендинга (byte 1)
|
||||
└────── Всегда 0xFFFF (байты 2-3)
|
||||
```
|
||||
|
||||
| Значение SS | Описание |
|
||||
|:-----------:|----------|
|
||||
| 0xFF | Сплошной материал (без блендинга) |
|
||||
| 0x01-0xFE | Индекс вторичного материала для блендинга |
|
||||
|
||||
Примеры:
|
||||
- `0xFFFFFF01` = Сплошной материал 1
|
||||
- `0xFFFF0203` = Материал 3 с блендингом в материал 2
|
||||
|
||||
---
|
||||
|
||||
### Компонент 0A - External References (переменный размер)
|
||||
|
||||
Таблица строк для внешних ссылок на части меша. Формат:
|
||||
|
||||
```
|
||||
[4 байта: длина] [байты строки] [null-терминатор]
|
||||
...повтор...
|
||||
```
|
||||
|
||||
Длина 0 означает пустую запись. Строки типа `"central"` имеют особое значение (flag |= 1).
|
||||
|
||||
---
|
||||
|
||||
## Пример: Ландшафт SC_1
|
||||
|
||||
```
|
||||
Land.msh (SC_1):
|
||||
├── 01: 256 тайлов (сетка 16×16)
|
||||
├── 02: 512 сабмешей (256 LOD0 + 256 LOD1)
|
||||
├── 03: 10 530 вершин
|
||||
├── 04: 10 530 данных на вершину
|
||||
├── 05: 10 530 данных на вершину
|
||||
├── 0B: 7 882 записи материалов
|
||||
├── 0E: 10 530 данных на вершину
|
||||
├── 12: 10 530 микротекстурный маппинг
|
||||
└── 15: 7 882 треугольника
|
||||
├── LOD 0: 4 993 треугольника (тайлы 0-255 → сабмеши 0-255)
|
||||
└── LOD 1: 2 889 треугольников (тайлы 0-255 → сабмеши 256-511)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Использование
|
||||
|
||||
```csharp
|
||||
var converter = new MshConverter();
|
||||
|
||||
// Автоопределение типа и конвертация в OBJ
|
||||
converter.Convert("Land.msh", "terrain.obj", lodLevel: 0);
|
||||
converter.Convert("robot.msh", "robot.obj", lodLevel: 0);
|
||||
|
||||
// Ручное определение типа
|
||||
var archive = NResParser.ReadFile("mesh.msh").Archive;
|
||||
var type = MshConverter.DetectMeshType(archive);
|
||||
// Возвращает: MshType.Model или MshType.Landscape
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Формат WEA - Файлы материалов ландшафта
|
||||
|
||||
Файлы `.wea` — текстовые файлы, определяющие таблицу материалов для ландшафта.
|
||||
|
||||
### Формат
|
||||
|
||||
```
|
||||
{count}
|
||||
{index} {material_name}
|
||||
{index} {material_name}
|
||||
...
|
||||
```
|
||||
|
||||
### Связь с Land.msh
|
||||
|
||||
Каждая карта имеет два файла материалов:
|
||||
|
||||
| Файл | Используется для | Треугольники в Comp15 |
|
||||
|------|------------------|----------------------|
|
||||
| `Land1.wea` | LOD0 (высокая детализация) | Первые N (сумма CountIn07 для LOD0) |
|
||||
| `Land2.wea` | LOD1 (низкая детализация) | Остальные |
|
||||
|
||||
### Пример (SC_1)
|
||||
|
||||
**Land1.wea:**
|
||||
```
|
||||
4
|
||||
0 B_S0
|
||||
1 L04
|
||||
2 L02
|
||||
3 L00
|
||||
```
|
||||
|
||||
**Land2.wea:**
|
||||
```
|
||||
4
|
||||
0 DEFAULT
|
||||
1 L05
|
||||
2 L03
|
||||
3 L01
|
||||
```
|
||||
|
||||
### Маппинг материалов
|
||||
|
||||
Индекс материала в `Comp15.MaterialData & 0xFF` → строка в `.wea` файле.
|
||||
|
||||
```
|
||||
Треугольник с MaterialData = 0xFFFF0102
|
||||
└─ Основной материал = 02 → Land1.wea[2] = "L02"
|
||||
└─ Блендинг с материалом = 01 → Land1.wea[1] = "L04"
|
||||
```
|
||||
|
||||
### Типичные имена материалов
|
||||
|
||||
| Префикс | Назначение |
|
||||
|---------|------------|
|
||||
| L00-L05 | Текстуры ландшафта (grass, dirt, etc.) |
|
||||
| B_S0 | Базовая текстура |
|
||||
| DEFAULT | Фолбэк для LOD1 |
|
||||
| WATER | Вода (поверхность) |
|
||||
| WATER_BOT | Вода (дно) |
|
||||
| WATER_M | Вода LOD1 |
|
||||
|
||||
---
|
||||
|
||||
## Источники
|
||||
|
||||
- Реверс-инжиниринг `Terrain.dll` (класс CLandscape)
|
||||
- Декомпиляция Ghidra: `CLandscape::ctor` и `IMesh2_of_CLandscape::Render`
|
||||
77
ParkanPlayground/Msh01.cs
Normal file
77
ParkanPlayground/Msh01.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Buffers.Binary;
|
||||
using NResLib;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
public static class Msh01
|
||||
{
|
||||
public static Msh01Component ReadComponent(FileStream mshFs, NResArchive archive)
|
||||
{
|
||||
var headerFileEntry = archive.Files.FirstOrDefault(x => x.FileType == "01 00 00 00");
|
||||
|
||||
if (headerFileEntry is null)
|
||||
{
|
||||
throw new Exception("Archive doesn't contain header file (01)");
|
||||
}
|
||||
|
||||
var data = new byte[headerFileEntry.ElementCount * headerFileEntry.ElementSize];
|
||||
mshFs.Seek(headerFileEntry.OffsetInFile, SeekOrigin.Begin);
|
||||
mshFs.ReadExactly(data, 0, data.Length);
|
||||
|
||||
var dataSpan = data.AsSpan();
|
||||
|
||||
var elements = new List<SubMesh>((int)headerFileEntry.ElementCount);
|
||||
for (var i = 0; i < headerFileEntry.ElementCount; i++)
|
||||
{
|
||||
var element = new SubMesh()
|
||||
{
|
||||
Type1 = dataSpan[i * headerFileEntry.ElementSize + 0],
|
||||
Type2 = dataSpan[i * headerFileEntry.ElementSize + 1],
|
||||
ParentIndex =
|
||||
BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 2)),
|
||||
OffsetIntoFile13 =
|
||||
BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 4)),
|
||||
IndexInFile08 =
|
||||
BinaryPrimitives.ReadInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 6))
|
||||
};
|
||||
|
||||
element.Lod[0] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 8));
|
||||
element.Lod[1] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 10));
|
||||
element.Lod[2] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 12));
|
||||
element.Lod[3] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 14));
|
||||
element.Lod[4] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 16));
|
||||
element.Lod[5] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 18));
|
||||
element.Lod[6] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 20));
|
||||
element.Lod[7] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 22));
|
||||
element.Lod[8] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 24));
|
||||
element.Lod[9] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 26));
|
||||
element.Lod[10] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 28));
|
||||
element.Lod[11] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 30));
|
||||
element.Lod[12] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 32));
|
||||
element.Lod[13] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 34));
|
||||
element.Lod[14] = BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(i * headerFileEntry.ElementSize + 36));
|
||||
elements.Add(element);
|
||||
}
|
||||
|
||||
return new Msh01Component()
|
||||
{
|
||||
Elements = elements
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public class Msh01Component
|
||||
{
|
||||
public List<SubMesh> Elements { get; set; }
|
||||
}
|
||||
|
||||
public class SubMesh
|
||||
{
|
||||
public byte Type1 { get; set; }
|
||||
public byte Type2 { get; set; }
|
||||
public short ParentIndex { get; set; }
|
||||
public short OffsetIntoFile13 { get; set; }
|
||||
public short IndexInFile08 { get; set; }
|
||||
public ushort[] Lod { get; set; } = new ushort[15];
|
||||
}
|
||||
}
|
||||
193
ParkanPlayground/Msh02.cs
Normal file
193
ParkanPlayground/Msh02.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System.Buffers.Binary;
|
||||
using Common;
|
||||
using NResLib;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
public static class Msh02
|
||||
{
|
||||
public static Msh02Component ReadComponent(FileStream mshFs, NResArchive archive)
|
||||
{
|
||||
var fileEntry = archive.Files.FirstOrDefault(x => x.FileType == "02 00 00 00");
|
||||
|
||||
if (fileEntry is null)
|
||||
{
|
||||
throw new Exception("Archive doesn't contain 02 component");
|
||||
}
|
||||
|
||||
var data = new byte[fileEntry.FileLength];
|
||||
mshFs.Seek(fileEntry.OffsetInFile, SeekOrigin.Begin);
|
||||
mshFs.ReadExactly(data, 0, data.Length);
|
||||
|
||||
var header = data.AsSpan(0, 0x8c); // 140 bytes header
|
||||
|
||||
var center = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x60)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x64)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x68))
|
||||
);
|
||||
var centerW = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x6c));
|
||||
|
||||
var bb = new BoundingBox();
|
||||
bb.BottomFrontLeft = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(4)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(8))
|
||||
);
|
||||
bb.BottomFrontRight = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(12)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(16)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(20))
|
||||
);
|
||||
bb.BottomBackRight = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(24)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(28)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(32))
|
||||
);
|
||||
bb.BottomBackLeft = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(36)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(40)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(44))
|
||||
);
|
||||
bb.TopFrontLeft = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(48)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(52)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(56))
|
||||
);
|
||||
bb.TopFrontRight = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(60)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(64)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(68))
|
||||
);
|
||||
bb.TopBackRight = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(72)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(76)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(80))
|
||||
);
|
||||
bb.TopBackLeft = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(84)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(88)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(92))
|
||||
);
|
||||
|
||||
var bottom = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(112)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(116)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(120))
|
||||
);
|
||||
|
||||
var top = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(124)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(128)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(header.Slice(132))
|
||||
);
|
||||
|
||||
var xyRadius = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(136));
|
||||
|
||||
|
||||
List<Msh02Element> elements = new List<Msh02Element>();
|
||||
var skippedHeader = data.AsSpan(0x8c); // skip header
|
||||
for (var i = 0; i < fileEntry.ElementCount; i++)
|
||||
{
|
||||
var element = new Msh02Element();
|
||||
element.StartIndexIn07 =
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 0));
|
||||
element.CountIn07 =
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 2));
|
||||
element.StartOffsetIn0d =
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 4));
|
||||
element.ByteLengthIn0D =
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 6));
|
||||
element.LocalMinimum = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 8)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 12)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 16))
|
||||
);
|
||||
element.LocalMaximum = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 20)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 24)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 28))
|
||||
);
|
||||
element.Center = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 32)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 36)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 40))
|
||||
);
|
||||
element.Vector4 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 44)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 48)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 52))
|
||||
);
|
||||
element.Vector5 = new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 56)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 60)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(fileEntry.ElementSize * i + 64))
|
||||
);
|
||||
elements.Add(element);
|
||||
|
||||
_ = 5;
|
||||
}
|
||||
|
||||
return new Msh02Component()
|
||||
{
|
||||
Header = new Msh02Header()
|
||||
{
|
||||
BoundingBox = bb,
|
||||
Center = center,
|
||||
CenterW = centerW,
|
||||
Bottom = bottom,
|
||||
Top = top,
|
||||
XYRadius = xyRadius
|
||||
},
|
||||
Elements = elements
|
||||
};
|
||||
}
|
||||
|
||||
public class Msh02Component
|
||||
{
|
||||
public Msh02Header Header { get; set; }
|
||||
public List<Msh02Element> Elements { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 140 байт в начале файла
|
||||
/// </summary>
|
||||
public class Msh02Header
|
||||
{
|
||||
public BoundingBox BoundingBox { get; set; }
|
||||
public Vector3 Center { get; set; }
|
||||
public float CenterW { get; set; }
|
||||
public Vector3 Bottom { get; set; }
|
||||
public Vector3 Top { get; set; }
|
||||
public float XYRadius { get; set; }
|
||||
}
|
||||
|
||||
public class Msh02Element
|
||||
{
|
||||
public ushort StartIndexIn07 { get; set; }
|
||||
public ushort CountIn07 { get; set; }
|
||||
public ushort StartOffsetIn0d { get; set; }
|
||||
public ushort ByteLengthIn0D { get; set; }
|
||||
public Vector3 LocalMinimum { get; set; }
|
||||
public Vector3 LocalMaximum { get; set; }
|
||||
public Vector3 Center { get; set; }
|
||||
public Vector3 Vector4 { get; set; }
|
||||
public Vector3 Vector5 { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 96 bytes - bounding box (8 points each 3 float = 96 bytes)
|
||||
/// 0x60 bytes or 0x18 by 4 bytes
|
||||
/// </summary>
|
||||
public class BoundingBox
|
||||
{
|
||||
public Vector3 BottomFrontLeft { get; set; }
|
||||
public Vector3 BottomFrontRight { get; set; }
|
||||
public Vector3 BottomBackRight { get; set; }
|
||||
public Vector3 BottomBackLeft { get; set; }
|
||||
public Vector3 TopBackRight { get; set; }
|
||||
public Vector3 TopFrontRight { get; set; }
|
||||
public Vector3 TopBackLeft { get; set; }
|
||||
public Vector3 TopFrontLeft { get; set; }
|
||||
}
|
||||
}
|
||||
35
ParkanPlayground/Msh03.cs
Normal file
35
ParkanPlayground/Msh03.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Buffers.Binary;
|
||||
using Common;
|
||||
using NResLib;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
public class Msh03
|
||||
{
|
||||
public static List<Vector3> ReadComponent(FileStream mshFs, NResArchive mshNres)
|
||||
{
|
||||
var verticesFileEntry = mshNres.Files.FirstOrDefault(x => x.FileType == "03 00 00 00");
|
||||
|
||||
if (verticesFileEntry is null)
|
||||
{
|
||||
throw new Exception("Archive doesn't contain vertices file (03)");
|
||||
}
|
||||
|
||||
if (verticesFileEntry.ElementSize != 12)
|
||||
{
|
||||
throw new Exception("Vertices file (03) element size is not 12");
|
||||
}
|
||||
|
||||
var verticesFile = new byte[verticesFileEntry.ElementCount * verticesFileEntry.ElementSize];
|
||||
mshFs.Seek(verticesFileEntry.OffsetInFile, SeekOrigin.Begin);
|
||||
mshFs.ReadExactly(verticesFile, 0, verticesFile.Length);
|
||||
|
||||
var vertices = verticesFile.Chunk(12).Select(x => new Vector3(
|
||||
BinaryPrimitives.ReadSingleLittleEndian(x.AsSpan(0)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(x.AsSpan(4)),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(x.AsSpan(8))
|
||||
)
|
||||
).ToList();
|
||||
return vertices;
|
||||
}
|
||||
}
|
||||
32
ParkanPlayground/Msh06.cs
Normal file
32
ParkanPlayground/Msh06.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Buffers.Binary;
|
||||
using NResLib;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
public static class Msh06
|
||||
{
|
||||
public static List<ushort> ReadComponent(
|
||||
FileStream mshFs, NResArchive archive)
|
||||
{
|
||||
var entry = archive.Files.FirstOrDefault(x => x.FileType == "06 00 00 00");
|
||||
|
||||
if (entry is null)
|
||||
{
|
||||
throw new Exception("Archive doesn't contain file (06)");
|
||||
}
|
||||
|
||||
var data = new byte[entry.ElementCount * entry.ElementSize];
|
||||
mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
|
||||
mshFs.ReadExactly(data, 0, data.Length);
|
||||
|
||||
var elements = new List<ushort>((int)entry.ElementCount);
|
||||
for (var i = 0; i < entry.ElementCount; i++)
|
||||
{
|
||||
elements.Add(
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(i * 2))
|
||||
);
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
}
|
||||
53
ParkanPlayground/Msh07.cs
Normal file
53
ParkanPlayground/Msh07.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Buffers.Binary;
|
||||
using NResLib;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
public static class Msh07
|
||||
{
|
||||
public static List<Msh07Element> ReadComponent(
|
||||
FileStream mshFs, NResArchive archive)
|
||||
{
|
||||
var entry = archive.Files.FirstOrDefault(x => x.FileType == "07 00 00 00");
|
||||
|
||||
if (entry is null)
|
||||
{
|
||||
throw new Exception("Archive doesn't contain file (07)");
|
||||
}
|
||||
|
||||
var data = new byte[entry.ElementCount * entry.ElementSize];
|
||||
mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
|
||||
mshFs.ReadExactly(data, 0, data.Length);
|
||||
|
||||
var elementBytes = data.Chunk(16);
|
||||
|
||||
var elements = elementBytes.Select(x => new Msh07Element()
|
||||
{
|
||||
Flags = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0)),
|
||||
Magic02 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(2)),
|
||||
Magic04 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(4)),
|
||||
Magic06 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(6)),
|
||||
OffsetX = BinaryPrimitives.ReadInt16LittleEndian(x.AsSpan(8)),
|
||||
OffsetY = BinaryPrimitives.ReadInt16LittleEndian(x.AsSpan(10)),
|
||||
OffsetZ = BinaryPrimitives.ReadInt16LittleEndian(x.AsSpan(12)),
|
||||
Magic14 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(14)),
|
||||
}).ToList();
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
public class Msh07Element
|
||||
{
|
||||
public ushort Flags { get; set; }
|
||||
public ushort Magic02 { get; set; }
|
||||
public ushort Magic04 { get; set; }
|
||||
public ushort Magic06 { get; set; }
|
||||
// normalized vector X, need to divide by 32767 to get float in range -1..1
|
||||
public short OffsetX { get; set; }
|
||||
// normalized vector Y, need to divide by 32767 to get float in range -1..1
|
||||
public short OffsetY { get; set; }
|
||||
// normalized vector Z, need to divide by 32767 to get float in range -1..1
|
||||
public short OffsetZ { get; set; }
|
||||
public ushort Magic14 { get; set; }
|
||||
}
|
||||
}
|
||||
49
ParkanPlayground/Msh0A.cs
Normal file
49
ParkanPlayground/Msh0A.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using NResLib;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
public class Msh0A
|
||||
{
|
||||
public static List<string> ReadComponent(FileStream mshFs, NResArchive archive)
|
||||
{
|
||||
var aFileEntry = archive.Files.FirstOrDefault(x => x.FileType == "0A 00 00 00");
|
||||
|
||||
if (aFileEntry is null)
|
||||
{
|
||||
throw new Exception("Archive doesn't contain 0A component");
|
||||
}
|
||||
|
||||
var data = new byte[aFileEntry.FileLength];
|
||||
mshFs.Seek(aFileEntry.OffsetInFile, SeekOrigin.Begin);
|
||||
mshFs.ReadExactly(data, 0, data.Length);
|
||||
|
||||
int pos = 0;
|
||||
var strings = new List<string>();
|
||||
while (pos < data.Length)
|
||||
{
|
||||
var len = BinaryPrimitives.ReadInt32LittleEndian(data.AsSpan(pos));
|
||||
if (len == 0)
|
||||
{
|
||||
pos += 4; // empty entry, no string attached
|
||||
strings.Add(""); // add empty string
|
||||
}
|
||||
else
|
||||
{
|
||||
// len is not 0, we need to read it
|
||||
var strBytes = data.AsSpan(pos + 4, len);
|
||||
var str = Encoding.UTF8.GetString(strBytes);
|
||||
strings.Add(str);
|
||||
pos += len + 4 + 1; // skip length prefix and string itself, +1, because it's null-terminated
|
||||
}
|
||||
}
|
||||
|
||||
if (strings.Count != aFileEntry.ElementCount)
|
||||
{
|
||||
throw new Exception("String count mismatch in 0A component");
|
||||
}
|
||||
|
||||
return strings;
|
||||
}
|
||||
}
|
||||
55
ParkanPlayground/Msh0D.cs
Normal file
55
ParkanPlayground/Msh0D.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Buffers.Binary;
|
||||
using NResLib;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
public static class Msh0D
|
||||
{
|
||||
public const int ElementSize = 20;
|
||||
|
||||
public static List<Msh0DElement> ReadComponent(
|
||||
FileStream mshFs, NResArchive archive)
|
||||
{
|
||||
var entry = archive.Files.FirstOrDefault(x => x.FileType == "0D 00 00 00");
|
||||
|
||||
if (entry is null)
|
||||
{
|
||||
throw new Exception("Archive doesn't contain file (0D)");
|
||||
}
|
||||
|
||||
var data = new byte[entry.ElementCount * entry.ElementSize];
|
||||
mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
|
||||
mshFs.ReadExactly(data, 0, data.Length);
|
||||
|
||||
var elementBytes = data.Chunk(ElementSize);
|
||||
|
||||
var elements = elementBytes.Select(x => new Msh0DElement()
|
||||
{
|
||||
Flags = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0)),
|
||||
Magic04 = x.AsSpan(4)[0],
|
||||
Magic05 = x.AsSpan(5)[0],
|
||||
Magic06 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(6)),
|
||||
CountOf06 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(8)),
|
||||
IndexInto06 = BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0xA)),
|
||||
CountOf03 = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0xE)),
|
||||
IndexInto03 = BinaryPrimitives.ReadInt32LittleEndian(x.AsSpan(0x10)),
|
||||
}).ToList();
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
public class Msh0DElement
|
||||
{
|
||||
public uint Flags { get; set; }
|
||||
|
||||
// Magic04 и Magic06 обрабатываются вместе
|
||||
|
||||
public byte Magic04 { get; set; }
|
||||
public byte Magic05 { get; set; }
|
||||
public ushort Magic06 { get; set; }
|
||||
public ushort CountOf06 { get; set; }
|
||||
public int IndexInto06 { get; set; }
|
||||
public ushort CountOf03 { get; set; }
|
||||
public int IndexInto03 { get; set; }
|
||||
}
|
||||
}
|
||||
55
ParkanPlayground/Msh15.cs
Normal file
55
ParkanPlayground/Msh15.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Buffers.Binary;
|
||||
using NResLib;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
public static class Msh15
|
||||
{
|
||||
public static List<Msh15Element> ReadComponent(
|
||||
FileStream mshFs, NResArchive archive)
|
||||
{
|
||||
var entry = archive.Files.FirstOrDefault(x => x.FileType == "15 00 00 00");
|
||||
|
||||
if (entry is null)
|
||||
{
|
||||
throw new Exception("Archive doesn't contain file (15)");
|
||||
}
|
||||
|
||||
var data = new byte[entry.ElementCount * entry.ElementSize];
|
||||
mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
|
||||
mshFs.ReadExactly(data, 0, data.Length);
|
||||
|
||||
var elementBytes = data.Chunk(28);
|
||||
|
||||
var elements = elementBytes.Select(x => new Msh15Element()
|
||||
{
|
||||
Flags = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0)),
|
||||
Magic04 = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(4)),
|
||||
Vertex1Index = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(8)),
|
||||
Vertex2Index = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(10)),
|
||||
Vertex3Index = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(12)),
|
||||
Magic0E = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(14)),
|
||||
Magic12 = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(18)),
|
||||
Magic16 = BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(22)),
|
||||
Magic1A = BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(26)),
|
||||
|
||||
}).ToList();
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
public class Msh15Element
|
||||
{
|
||||
public uint Flags { get; set; }
|
||||
|
||||
public uint Magic04 { get; set; }
|
||||
public ushort Vertex1Index { get; set; }
|
||||
public ushort Vertex2Index { get; set; }
|
||||
public ushort Vertex3Index { get; set; }
|
||||
|
||||
public uint Magic0E { get; set; }
|
||||
public uint Magic12 { get; set; }
|
||||
public uint Magic16 { get; set; }
|
||||
public ushort Magic1A { get; set; }
|
||||
}
|
||||
}
|
||||
314
ParkanPlayground/MshConverter.cs
Normal file
314
ParkanPlayground/MshConverter.cs
Normal file
@@ -0,0 +1,314 @@
|
||||
using System.Text;
|
||||
using Common;
|
||||
using NResLib;
|
||||
|
||||
namespace ParkanPlayground;
|
||||
|
||||
public enum MshType
|
||||
{
|
||||
Unknown,
|
||||
Model, // Has component 06 (indices), 0D (batches), 07
|
||||
Landscape // Has component 0B (per-triangle material), uses 15 directly
|
||||
}
|
||||
|
||||
public class MshConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Detects mesh type based on which components are present in the archive.
|
||||
/// </summary>
|
||||
public static MshType DetectMeshType(NResArchive archive)
|
||||
{
|
||||
bool hasComponent06 = archive.Files.Any(f => f.FileType == "06 00 00 00");
|
||||
bool hasComponent0B = archive.Files.Any(f => f.FileType == "0B 00 00 00");
|
||||
bool hasComponent0D = archive.Files.Any(f => f.FileType == "0D 00 00 00");
|
||||
|
||||
// Model: Uses indexed triangles via component 06 and batches via 0D
|
||||
if (hasComponent06 && hasComponent0D)
|
||||
return MshType.Model;
|
||||
|
||||
// Landscape: Uses direct triangles in component 15, with material data in 0B
|
||||
if (hasComponent0B && !hasComponent06)
|
||||
return MshType.Landscape;
|
||||
|
||||
return MshType.Unknown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a .msh file to OBJ format, auto-detecting mesh type.
|
||||
/// </summary>
|
||||
/// <param name="mshPath">Path to the .msh file</param>
|
||||
/// <param name="outputPath">Output OBJ path (optional, defaults to input name + .obj)</param>
|
||||
/// <param name="lodLevel">LOD level to export (0 = highest detail)</param>
|
||||
public void Convert(string mshPath, string? outputPath = null, int lodLevel = 0)
|
||||
{
|
||||
var mshNresResult = NResParser.ReadFile(mshPath);
|
||||
if (mshNresResult.Archive is null)
|
||||
{
|
||||
Console.WriteLine($"ERROR: Failed to read NRes archive: {mshNresResult.Error}");
|
||||
return;
|
||||
}
|
||||
|
||||
var archive = mshNresResult.Archive;
|
||||
var meshType = DetectMeshType(archive);
|
||||
|
||||
outputPath ??= Path.ChangeExtension(mshPath, ".obj");
|
||||
|
||||
Console.WriteLine($"Converting: {Path.GetFileName(mshPath)}");
|
||||
Console.WriteLine($"Detected type: {meshType}");
|
||||
|
||||
using var fs = new FileStream(mshPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
switch (meshType)
|
||||
{
|
||||
case MshType.Model:
|
||||
ConvertModel(fs, archive, outputPath, lodLevel);
|
||||
break;
|
||||
case MshType.Landscape:
|
||||
ConvertLandscape(fs, archive, outputPath, lodLevel);
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine("ERROR: Unknown mesh type, cannot convert.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a model mesh (robots, buildings, etc.) to OBJ.
|
||||
/// Uses indexed triangles: 01 → 02 → 0D → 06 → 03
|
||||
/// </summary>
|
||||
private void ConvertModel(FileStream fs, NResArchive archive, string outputPath, int lodLevel)
|
||||
{
|
||||
var component01 = Msh01.ReadComponent(fs, archive);
|
||||
var component02 = Msh02.ReadComponent(fs, archive);
|
||||
var component03 = Msh03.ReadComponent(fs, archive);
|
||||
var component06 = Msh06.ReadComponent(fs, archive);
|
||||
var component07 = Msh07.ReadComponent(fs, archive);
|
||||
var component0D = Msh0D.ReadComponent(fs, archive);
|
||||
|
||||
Console.WriteLine($"Vertices: {component03.Count}");
|
||||
Console.WriteLine($"Pieces: {component01.Elements.Count}");
|
||||
Console.WriteLine($"Submeshes: {component02.Elements.Count}");
|
||||
|
||||
using var sw = new StreamWriter(outputPath, false, new UTF8Encoding(false));
|
||||
sw.WriteLine($"# Model mesh converted from {Path.GetFileName(outputPath)}");
|
||||
sw.WriteLine($"# LOD level: {lodLevel}");
|
||||
|
||||
// Write all vertices
|
||||
foreach (var v in component03)
|
||||
sw.WriteLine($"v {v.X:F6} {v.Y:F6} {v.Z:F6}");
|
||||
|
||||
int exportedFaces = 0;
|
||||
|
||||
for (var pieceIndex = 0; pieceIndex < component01.Elements.Count; pieceIndex++)
|
||||
{
|
||||
var piece = component01.Elements[pieceIndex];
|
||||
|
||||
// Get submesh index for requested LOD
|
||||
if (lodLevel >= piece.Lod.Length)
|
||||
continue;
|
||||
|
||||
var submeshIdx = piece.Lod[lodLevel];
|
||||
if (submeshIdx == 0xFFFF || submeshIdx >= component02.Elements.Count)
|
||||
continue;
|
||||
|
||||
sw.WriteLine($"g piece_{pieceIndex}");
|
||||
|
||||
var submesh = component02.Elements[submeshIdx];
|
||||
var batchStart = submesh.StartOffsetIn0d;
|
||||
var batchCount = submesh.ByteLengthIn0D;
|
||||
|
||||
for (var batchIdx = 0; batchIdx < batchCount; batchIdx++)
|
||||
{
|
||||
var batch = component0D[batchStart + batchIdx];
|
||||
var baseVertex = batch.IndexInto03;
|
||||
var indexStart = batch.IndexInto06;
|
||||
var indexCount = batch.CountOf06;
|
||||
|
||||
for (int i = 0; i < indexCount; i += 3)
|
||||
{
|
||||
var i1 = baseVertex + component06[indexStart + i];
|
||||
var i2 = baseVertex + component06[indexStart + i + 1];
|
||||
var i3 = baseVertex + component06[indexStart + i + 2];
|
||||
|
||||
sw.WriteLine($"f {i1 + 1} {i2 + 1} {i3 + 1}");
|
||||
exportedFaces++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"Exported: {component03.Count} vertices, {exportedFaces} faces");
|
||||
Console.WriteLine($"Output: {outputPath}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a landscape mesh (terrain) to OBJ.
|
||||
/// Uses direct triangles: 01 → 02 → 15 (via StartIndexIn07/CountIn07)
|
||||
/// </summary>
|
||||
private void ConvertLandscape(FileStream fs, NResArchive archive, string outputPath, int lodLevel)
|
||||
{
|
||||
var component01 = Msh01.ReadComponent(fs, archive);
|
||||
var component02 = Msh02.ReadComponent(fs, archive);
|
||||
var component03 = Msh03.ReadComponent(fs, archive);
|
||||
var component15 = Msh15.ReadComponent(fs, archive);
|
||||
|
||||
Console.WriteLine($"Vertices: {component03.Count}");
|
||||
Console.WriteLine($"Triangles: {component15.Count}");
|
||||
Console.WriteLine($"Tiles: {component01.Elements.Count}");
|
||||
Console.WriteLine($"Submeshes: {component02.Elements.Count}");
|
||||
|
||||
using var sw = new StreamWriter(outputPath, false, new UTF8Encoding(false));
|
||||
sw.WriteLine($"# Landscape mesh converted from {Path.GetFileName(outputPath)}");
|
||||
sw.WriteLine($"# LOD level: {lodLevel}");
|
||||
sw.WriteLine($"# Tile grid: {(int)Math.Sqrt(component01.Elements.Count)}x{(int)Math.Sqrt(component01.Elements.Count)}");
|
||||
|
||||
// Write all vertices
|
||||
foreach (var v in component03)
|
||||
sw.WriteLine($"v {v.X:F6} {v.Y:F6} {v.Z:F6}");
|
||||
|
||||
int exportedFaces = 0;
|
||||
|
||||
for (var tileIdx = 0; tileIdx < component01.Elements.Count; tileIdx++)
|
||||
{
|
||||
var tile = component01.Elements[tileIdx];
|
||||
|
||||
// Get submesh index for requested LOD
|
||||
if (lodLevel >= tile.Lod.Length)
|
||||
continue;
|
||||
|
||||
var submeshIdx = tile.Lod[lodLevel];
|
||||
if (submeshIdx == 0xFFFF || submeshIdx >= component02.Elements.Count)
|
||||
continue;
|
||||
|
||||
sw.WriteLine($"g tile_{tileIdx}");
|
||||
|
||||
var submesh = component02.Elements[submeshIdx];
|
||||
|
||||
// For landscape, StartIndexIn07 = triangle start index, CountIn07 = triangle count
|
||||
var triangleStart = submesh.StartIndexIn07;
|
||||
var triangleCount = submesh.CountIn07;
|
||||
|
||||
for (var triOffset = 0; triOffset < triangleCount; triOffset++)
|
||||
{
|
||||
var triIdx = triangleStart + triOffset;
|
||||
if (triIdx >= component15.Count)
|
||||
{
|
||||
Console.WriteLine($"WARNING: Triangle index {triIdx} out of range for tile {tileIdx}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var tri = component15[triIdx];
|
||||
sw.WriteLine($"f {tri.Vertex1Index + 1} {tri.Vertex2Index + 1} {tri.Vertex3Index + 1}");
|
||||
exportedFaces++;
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"Exported: {component03.Count} vertices, {exportedFaces} faces");
|
||||
Console.WriteLine($"Output: {outputPath}");
|
||||
}
|
||||
|
||||
public record Face(Vector3 P1, Vector3 P2, Vector3 P3);
|
||||
|
||||
public static void ExportCube(string filePath, Vector3[] points)
|
||||
{
|
||||
if (points.Length != 8)
|
||||
throw new ArgumentException("Cube must have exactly 8 points.");
|
||||
|
||||
using (StreamWriter writer = new StreamWriter(filePath))
|
||||
{
|
||||
// Write vertices
|
||||
foreach (var p in points)
|
||||
{
|
||||
writer.WriteLine($"v {p.X} {p.Y} {p.Z}");
|
||||
}
|
||||
|
||||
// Write faces (each face defined by 4 vertices, using 1-based indices)
|
||||
int[][] faces = new int[][]
|
||||
{
|
||||
new int[] { 1, 2, 3, 4 }, // bottom
|
||||
new int[] { 5, 6, 7, 8 }, // top
|
||||
new int[] { 1, 2, 6, 5 }, // front
|
||||
new int[] { 2, 3, 7, 6 }, // right
|
||||
new int[] { 3, 4, 8, 7 }, // back
|
||||
new int[] { 4, 1, 5, 8 } // left
|
||||
};
|
||||
|
||||
foreach (var f in faces)
|
||||
{
|
||||
writer.WriteLine($"f {f[0]} {f[1]} {f[2]} {f[3]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExportCubesAtPositions(string filePath, List<Vector3> centers, float size = 2f)
|
||||
{
|
||||
float half = size / 2f;
|
||||
using (StreamWriter writer = new StreamWriter(filePath))
|
||||
{
|
||||
int vertexOffset = 0;
|
||||
|
||||
foreach (var c in centers)
|
||||
{
|
||||
// Generate 8 vertices for this cube
|
||||
Vector3[] vertices = new Vector3[]
|
||||
{
|
||||
new Vector3(c.X - half, c.Y - half, c.Z - half),
|
||||
new Vector3(c.X + half, c.Y - half, c.Z - half),
|
||||
new Vector3(c.X + half, c.Y - half, c.Z + half),
|
||||
new Vector3(c.X - half, c.Y - half, c.Z + half),
|
||||
|
||||
new Vector3(c.X - half, c.Y + half, c.Z - half),
|
||||
new Vector3(c.X + half, c.Y + half, c.Z - half),
|
||||
new Vector3(c.X + half, c.Y + half, c.Z + half),
|
||||
new Vector3(c.X - half, c.Y + half, c.Z + half)
|
||||
};
|
||||
|
||||
// Write vertices
|
||||
foreach (var v in vertices)
|
||||
{
|
||||
writer.WriteLine($"v {v.X} {v.Y} {v.Z}");
|
||||
}
|
||||
|
||||
// Define faces (1-based indices, counter-clockwise)
|
||||
int[][] faces = new int[][]
|
||||
{
|
||||
new int[] { 1, 2, 3, 4 }, // bottom
|
||||
new int[] { 5, 6, 7, 8 }, // top
|
||||
new int[] { 1, 2, 6, 5 }, // front
|
||||
new int[] { 2, 3, 7, 6 }, // right
|
||||
new int[] { 3, 4, 8, 7 }, // back
|
||||
new int[] { 4, 1, 5, 8 } // left
|
||||
};
|
||||
|
||||
// Write faces with offset
|
||||
foreach (var f in faces)
|
||||
{
|
||||
writer.WriteLine(
|
||||
$"f {f[0] + vertexOffset} {f[1] + vertexOffset} {f[2] + vertexOffset} {f[3] + vertexOffset}");
|
||||
}
|
||||
|
||||
vertexOffset += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Export(string filePath, IEnumerable<Vector3> vertices, List<IndexedEdge> edges)
|
||||
{
|
||||
using (var writer = new StreamWriter(filePath))
|
||||
{
|
||||
writer.WriteLine("# Exported OBJ file");
|
||||
|
||||
// Write vertices
|
||||
foreach (var v in vertices)
|
||||
{
|
||||
writer.WriteLine($"v {v.X:F2} {v.Y:F2} {v.Z:F2}");
|
||||
}
|
||||
|
||||
// Write edges as lines ("l" elements in .obj format)
|
||||
foreach (var e in edges)
|
||||
{
|
||||
// OBJ uses 1-based indexing
|
||||
writer.WriteLine($"l {e.Index1 + 1} {e.Index2 + 1}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,16 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
||||
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
||||
<ProjectReference Include="..\PalLib\PalLib.csproj" />
|
||||
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
||||
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
|
||||
<ProjectReference Include="..\MaterialLib\MaterialLib.csproj" />
|
||||
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,64 +1,332 @@
|
||||
using System.Buffers.Binary;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using NResLib;
|
||||
using ParkanPlayground;
|
||||
using ParkanPlayground.Effects;
|
||||
using static ParkanPlayground.Effects.FxidReader;
|
||||
|
||||
// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\default.scr";
|
||||
// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\scr_pl_1.scr";
|
||||
// var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\scream.scr";
|
||||
var path = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\SCRIPTS\\scream1.scr";
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
|
||||
using var fs = new FileStream(path, FileMode.Open);
|
||||
|
||||
// тут всегда число 59 (0x3b) - это число известных игре скриптов
|
||||
var magic = fs.ReadInt32LittleEndian();
|
||||
|
||||
Console.WriteLine($"Count: {magic}");
|
||||
|
||||
var entryCount = fs.ReadInt32LittleEndian();
|
||||
|
||||
Console.WriteLine($"EntryCount: {entryCount}");
|
||||
|
||||
for (var i = 0; i < entryCount; i++)
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine($"Entry: {i}");
|
||||
var str = fs.ReadLengthPrefixedString();
|
||||
|
||||
Console.WriteLine($"\tStr: {str}");
|
||||
|
||||
// тут игра дополнительно вычитывает ещё 1 байт, видимо как \0 для char*
|
||||
fs.ReadByte();
|
||||
Console.WriteLine("Usage: ParkanPlayground <effects-directory-or-fxid-file>");
|
||||
return;
|
||||
}
|
||||
|
||||
var index = fs.ReadInt32LittleEndian();
|
||||
Console.WriteLine($"\tIndex: {index}");
|
||||
var innerCount = fs.ReadInt32LittleEndian();
|
||||
Console.WriteLine($"\tInnerCount: {innerCount}");
|
||||
for (var i1 = 0; i1 < innerCount; i1++)
|
||||
var path = args[0];
|
||||
bool anyError = false;
|
||||
var sizeByType = new Dictionary<byte, int>
|
||||
{
|
||||
[1] = 0xE0, // 1: Billboard
|
||||
[2] = 0x94, // 2: Sound
|
||||
[3] = 0xC8, // 3: AnimParticle
|
||||
[4] = 0xCC, // 4: AnimBillboard
|
||||
[5] = 0x70, // 5: Trail
|
||||
[6] = 0x04, // 6: Point
|
||||
[7] = 0xD0, // 7: Plane
|
||||
[8] = 0xF8, // 8: Model
|
||||
[9] = 0xD0, // 9: AnimModel
|
||||
[10] = 0xD0, // 10: Cube
|
||||
};
|
||||
|
||||
// Check for --dump-headers flag
|
||||
bool dumpHeaders = args.Length > 1 && args[1] == "--dump-headers";
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
var files = Directory.EnumerateFiles(path, "*.bin").ToList();
|
||||
|
||||
if (dumpHeaders)
|
||||
{
|
||||
var scriptIndex = fs.ReadInt32LittleEndian();
|
||||
var unkInner2 = fs.ReadInt32LittleEndian();
|
||||
var unkInner3 = fs.ReadInt32LittleEndian();
|
||||
var unkInner4 = fs.ReadInt32LittleEndian();
|
||||
var unkInner5 = fs.ReadInt32LittleEndian();
|
||||
|
||||
Console.WriteLine($"\t\tScriptIndex: {scriptIndex}");
|
||||
Console.WriteLine($"\t\tUnkInner2: {unkInner2}");
|
||||
Console.WriteLine($"\t\tUnkInner3: {unkInner3}");
|
||||
Console.WriteLine($"\t\tUnkInner4: {unkInner4}");
|
||||
Console.WriteLine($"\t\tUnkInner5: {unkInner5}");
|
||||
|
||||
var scriptArgumentsCount = fs.ReadInt32LittleEndian();
|
||||
Console.WriteLine($"\t\tScript Arguments Count: {scriptArgumentsCount}");
|
||||
|
||||
for (var i2 = 0; i2 < scriptArgumentsCount; i2++)
|
||||
// Collect all headers for analysis
|
||||
var headers = new List<(string name, EffectHeader h)>();
|
||||
foreach (var file in files)
|
||||
{
|
||||
var scriptArgument = fs.ReadInt32LittleEndian();
|
||||
Console.WriteLine($"\t\t\t{scriptArgument}");
|
||||
try
|
||||
{
|
||||
using var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var br = new BinaryReader(fs, Encoding.ASCII, leaveOpen: false);
|
||||
if (fs.Length >= 60)
|
||||
{
|
||||
headers.Add((Path.GetFileName(file), ReadEffectHeader(br)));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// Analyze unique values
|
||||
Console.WriteLine("=== UNIQUE VALUES ANALYSIS ===\n");
|
||||
|
||||
var uniqueUnk1 = headers.Select(x => x.h.Unknown1).Distinct().OrderBy(x => x).ToList();
|
||||
Console.WriteLine($"Unknown1 unique values ({uniqueUnk1.Count}): {string.Join(", ", uniqueUnk1)}");
|
||||
|
||||
var uniqueUnk2 = headers.Select(x => x.h.Unknown2).Distinct().OrderBy(x => x).ToList();
|
||||
Console.WriteLine($"Unknown2 unique values ({uniqueUnk2.Count}): {string.Join(", ", uniqueUnk2.Select(x => x.ToString("F2")))}");
|
||||
|
||||
var uniqueFlags = headers.Select(x => x.h.Flags).Distinct().OrderBy(x => x).ToList();
|
||||
Console.WriteLine($"Flags unique values ({uniqueFlags.Count}): {string.Join(", ", uniqueFlags.Select(x => $"0x{x:X4}"))}");
|
||||
|
||||
var uniqueUnk3 = headers.Select(x => x.h.Unknown3).Distinct().OrderBy(x => x).ToList();
|
||||
Console.WriteLine($"Unknown3 unique values ({uniqueUnk3.Count}): {string.Join(", ", uniqueUnk3)}");
|
||||
Console.WriteLine($"Unknown3 as hex: {string.Join(", ", uniqueUnk3.Select(x => $"0x{x:X3}"))}");
|
||||
Console.WriteLine($"Unknown3 decoded (hi.lo): {string.Join(", ", uniqueUnk3.Select(x => $"{x >> 8}.{x & 0xFF}"))}");
|
||||
|
||||
// Check reserved bytes
|
||||
var nonZeroReserved = headers.Where(x => x.h.Reserved.Any(b => b != 0)).ToList();
|
||||
Console.WriteLine($"\nFiles with non-zero Reserved bytes: {nonZeroReserved.Count} / {headers.Count}");
|
||||
|
||||
// Check scales
|
||||
var uniqueScales = headers.Select(x => (x.h.ScaleX, x.h.ScaleY, x.h.ScaleZ)).Distinct().ToList();
|
||||
Console.WriteLine($"Unique scale combinations: {string.Join(", ", uniqueScales.Select(s => $"({s.ScaleX:F2},{s.ScaleY:F2},{s.ScaleZ:F2})"))}");
|
||||
|
||||
Console.WriteLine("\n=== SAMPLE HEADERS (first 30) ===");
|
||||
Console.WriteLine($"{"File",-40} | {"Cnt",3} | {"U1",2} | {"Duration",8} | {"U2",6} | {"Flags",6} | {"U3",4} | Scale");
|
||||
Console.WriteLine(new string('-', 100));
|
||||
|
||||
foreach (var (name, h) in headers.Take(30))
|
||||
{
|
||||
Console.WriteLine($"{name,-40} | {h.ComponentCount,3} | {h.Unknown1,2} | {h.Duration,8:F2} | {h.Unknown2,6:F2} | 0x{h.Flags:X4} | {h.Unknown3,4} | ({h.ScaleX:F1},{h.ScaleY:F1},{h.ScaleZ:F1})");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!ValidateFxidFile(file))
|
||||
{
|
||||
anyError = true;
|
||||
}
|
||||
}
|
||||
|
||||
var unkInner7 = fs.ReadInt32LittleEndian();
|
||||
|
||||
Console.WriteLine($"\t\tUnkInner7 {unkInner7}");
|
||||
Console.WriteLine("---");
|
||||
Console.WriteLine(anyError
|
||||
? "Validation finished with errors."
|
||||
: "All FXID files parsed successfully.");
|
||||
}
|
||||
}
|
||||
else if (File.Exists(path))
|
||||
{
|
||||
anyError = !ValidateFxidFile(path);
|
||||
Console.WriteLine(anyError ? "Validation failed." : "Validation OK.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Path not found: {path}");
|
||||
}
|
||||
|
||||
void DumpEffectHeader(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var br = new BinaryReader(fs, Encoding.ASCII, leaveOpen: false);
|
||||
|
||||
if (fs.Length < 60)
|
||||
{
|
||||
Console.WriteLine($"{Path.GetFileName(path)}: file too small");
|
||||
return;
|
||||
}
|
||||
|
||||
var h = ReadEffectHeader(br);
|
||||
|
||||
// Format reserved bytes as hex (show first 8 bytes for brevity)
|
||||
var reservedHex = BitConverter.ToString(h.Reserved, 0, Math.Min(8, h.Reserved.Length)).Replace("-", " ");
|
||||
if (h.Reserved.Length > 8) reservedHex += "...";
|
||||
|
||||
// Check if reserved has any non-zero bytes
|
||||
bool reservedAllZero = h.Reserved.All(b => b == 0);
|
||||
|
||||
Console.WriteLine($"{Path.GetFileName(path),-40} | {h.ComponentCount,7} | {h.Unknown1,4} | {h.Duration,8:F2} | {h.Unknown2,8:F2} | 0x{h.Flags:X4} | {h.Unknown3,4} | {(reservedAllZero ? "(all zero)" : reservedHex),-20} | ({h.ScaleX:F2}, {h.ScaleY:F2}, {h.ScaleZ:F2})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"{Path.GetFileName(path)}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateFxidFile(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var br = new BinaryReader(fs, Encoding.ASCII, leaveOpen: false);
|
||||
|
||||
const int headerSize = 60; // sizeof(EffectHeader) on disk
|
||||
if (fs.Length < headerSize)
|
||||
{
|
||||
Console.WriteLine($"{path}: file too small ({fs.Length} bytes).");
|
||||
return false;
|
||||
}
|
||||
|
||||
var header = ReadEffectHeader(br);
|
||||
|
||||
var typeCounts = new Dictionary<byte, int>();
|
||||
|
||||
for (int i = 0; i < header.ComponentCount; i++)
|
||||
{
|
||||
long blockStart = fs.Position;
|
||||
if (fs.Position + 4 > fs.Length)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: unexpected EOF before type (offset 0x{fs.Position:X}, size 0x{fs.Length:X}).");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint typeAndFlags = br.ReadUInt32();
|
||||
byte type = (byte)(typeAndFlags & 0xFF);
|
||||
|
||||
if (!typeCounts.TryGetValue(type, out var count))
|
||||
{
|
||||
count = 0;
|
||||
}
|
||||
typeCounts[type] = count + 1;
|
||||
|
||||
if (!sizeByType.TryGetValue(type, out int blockSize))
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: unknown type {type} (typeAndFlags=0x{typeAndFlags:X8}).");
|
||||
return false;
|
||||
}
|
||||
|
||||
int remaining = blockSize - 4;
|
||||
if (fs.Position + remaining > fs.Length)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: block size 0x{blockSize:X} runs past EOF (blockStart=0x{blockStart:X}, fileSize=0x{fs.Length:X}).");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == 1)
|
||||
{
|
||||
var def = ReadBillboardComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Reserved.Length != 0x50)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 1 reserved length {def.Reserved.Length}, expected 0x50.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 2)
|
||||
{
|
||||
var def = ReadSoundComponent(br, typeAndFlags);
|
||||
|
||||
if (def.SoundNameAndReserved.Length != 0x40)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 2 reserved length {def.SoundNameAndReserved.Length}, expected 0x40.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 3)
|
||||
{
|
||||
var def = ReadAnimParticleComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Reserved.Length != 0x38)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 3 reserved length {def.Reserved.Length}, expected 0x38.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 4)
|
||||
{
|
||||
var def = ReadAnimBillboardComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Reserved.Length != 0x3C)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 4 reserved length {def.Reserved.Length}, expected 0x3C.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 5)
|
||||
{
|
||||
var def = ReadTrailComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Unknown04To10.Length != 0x10)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 5 prefix length {def.Unknown04To10.Length}, expected 0x10.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (def.TextureNameAndReserved.Length != 0x40)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 5 tail length {def.TextureNameAndReserved.Length}, expected 0x40.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 6)
|
||||
{
|
||||
// Point components have no extra bytes beyond the 4-byte typeAndFlags header.
|
||||
var def = ReadPointComponent(typeAndFlags);
|
||||
}
|
||||
else if (type == 7)
|
||||
{
|
||||
var def = ReadPlaneComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Base.Reserved.Length != 0x38)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 7 base reserved length {def.Base.Reserved.Length}, expected 0x38.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 8)
|
||||
{
|
||||
var def = ReadModelComponent(br, typeAndFlags);
|
||||
|
||||
if (def.TextureNameAndFlags.Length != 0x40)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 8 tail length {def.TextureNameAndFlags.Length}, expected 0x40.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 9)
|
||||
{
|
||||
var def = ReadAnimModelComponent(br, typeAndFlags);
|
||||
|
||||
if (def.TextureNameAndFlags.Length != 0x48)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 9 tail length {def.TextureNameAndFlags.Length}, expected 0x48.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 10)
|
||||
{
|
||||
var def = ReadCubeComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Base.Reserved.Length != 0x3C)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 10 base reserved length {def.Base.Reserved.Length}, expected 0x3C.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip the remaining bytes for other component types.
|
||||
fs.Position += remaining;
|
||||
}
|
||||
}
|
||||
|
||||
// Dump a compact per-file summary of component types and counts.
|
||||
var sb = new StringBuilder();
|
||||
bool first = true;
|
||||
foreach (var kv in typeCounts)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
sb.Append(kv.Key);
|
||||
sb.Append('x');
|
||||
sb.Append(kv.Value);
|
||||
first = false;
|
||||
}
|
||||
Console.WriteLine($"{path}: components={header.ComponentCount}, types=[{sb}]");
|
||||
|
||||
if (fs.Position != fs.Length)
|
||||
{
|
||||
Console.WriteLine($"{path}: parsed to 0x{fs.Position:X}, but file size is 0x{fs.Length:X} (leftover {fs.Length - fs.Position} bytes).");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"{path}: exception while parsing: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
10
ParkanPlayground/from_ghidra/BoundingBox_typedef.c
Normal file
10
ParkanPlayground/from_ghidra/BoundingBox_typedef.c
Normal file
@@ -0,0 +1,10 @@
|
||||
typedef struct BoundingBox {
|
||||
Vector3 BottomFrontLeft;
|
||||
Vector3 BottomFrontRight;
|
||||
Vector3 BottomBackRight;
|
||||
Vector3 BottomBackLeft;
|
||||
Vector3 TopBackRight;
|
||||
Vector3 TopFrontRight;
|
||||
Vector3 TopBackLeft;
|
||||
Vector3 TopFrontLeft;
|
||||
} BoundingBox;
|
||||
2481
ParkanPlayground/from_ghidra/CLandscape_ctor.c
Normal file
2481
ParkanPlayground/from_ghidra/CLandscape_ctor.c
Normal file
File diff suppressed because it is too large
Load Diff
60
ParkanPlayground/from_ghidra/CLandscape_typedef.c
Normal file
60
ParkanPlayground/from_ghidra/CLandscape_typedef.c
Normal file
@@ -0,0 +1,60 @@
|
||||
typedef struct CLandscape {
|
||||
IComponent * IComponent;
|
||||
CGameObject game_object;
|
||||
ITerrain * ITerrain;
|
||||
IMesh2 * IMesh2;
|
||||
I0x1 * I0x1;
|
||||
IControl * IControl;
|
||||
I0x22 * IUnknown_0x22;
|
||||
undefined4 01_data_ptr;
|
||||
MSH_02_file * msh_02_data_ptr;
|
||||
undefined4 0b_data_ptr;
|
||||
// collapse unknown fields
|
||||
int[3][5] field_532;
|
||||
// collapse unknown fields
|
||||
undefined4 IComponent_owner;
|
||||
KindaArray array_0x278;
|
||||
int _count_unk_obj_array_elemsize_212;
|
||||
undefined4 vertices_count;
|
||||
uint 15_element_count;
|
||||
// collapse unknown fields
|
||||
Vector3 * 03_vertices_data_ptr;
|
||||
int 03_vertices_stride;
|
||||
undefined4 04_data_ptr;
|
||||
undefined4 04_stride;
|
||||
// collapse unknown fields
|
||||
undefined4 05_data_ptr;
|
||||
undefined4 05_stride;
|
||||
undefined4 12_microtexture_mapping_data_ptr;
|
||||
undefined4 12_microtexture_mapping_stride;
|
||||
// collapse unknown fields
|
||||
MshMetaForLandscape * mshmeta_ptr;
|
||||
// collapse unknown fields
|
||||
KindaArray array_0x7a40;
|
||||
BoundingBox bounding_box_from_msh_02;
|
||||
IMatManager * * IMatManager;
|
||||
undefined4 wear_ptr;
|
||||
IEffectManager * * ResolvedIEffectManager;
|
||||
ISystemArealMap * * ResolvedISystemArealMap;
|
||||
ICollManager * * ResolvedICollManager;
|
||||
ISoundPool * * ISoundPool;
|
||||
I3DSound * * I3DSound;
|
||||
ILightManager * * ILightManager_owned;
|
||||
KindaArray array_0x7c00;
|
||||
KindaArray array_0x7c0c;
|
||||
CShade * Shade;
|
||||
undefined4 I3DRender??;
|
||||
undefined4 field_31776;
|
||||
undefined4 flags_of_mesh_part;
|
||||
INResFile * * INResFile;
|
||||
KindaArray array_0x7c2c;
|
||||
undefined4 current_visible_primitives_count;
|
||||
IMesh2 * * meshes;
|
||||
IGameObject * * game_objects;
|
||||
// collapse unknown fields
|
||||
KindaArray array_0x7c84;
|
||||
undefined4 * field_31888;
|
||||
undefined4 _CheckMaxBasementAngleStep;
|
||||
CollisionContext m_CollisionContext;
|
||||
BoundingBox bounding_box;
|
||||
} CLandscape;
|
||||
1232
ParkanPlayground/from_ghidra/IMesh2_of_CLandscape_Render.c
Normal file
1232
ParkanPlayground/from_ghidra/IMesh2_of_CLandscape_Render.c
Normal file
File diff suppressed because it is too large
Load Diff
512
README.md
512
README.md
@@ -2,11 +2,13 @@
|
||||
|
||||
<div align="center">
|
||||
<img width="300" height="300" src="https://github.com/user-attachments/assets/dcd9ac8f-7d30-491c-ae6c-537267beb7dc" alt="x86 Registers" />
|
||||
<img width="817" height="376" alt="Image" src="https://github.com/user-attachments/assets/c4959106-9da4-4c78-a2b7-6c94e360a89e" />
|
||||
</div>
|
||||
|
||||
|
||||
## Сборка проекта
|
||||
|
||||
Проект написан на C# под `.NET 8`
|
||||
Проект написан на C# под `.NET 9`
|
||||
|
||||
Вам должно хватить `dotnet build` для сборки всех проектов отдельно.
|
||||
|
||||
@@ -14,13 +16,13 @@
|
||||
|
||||
### Состояние проекта
|
||||
|
||||
- Распаковка всех `NRes` файлов
|
||||
- Распаковка всех `TEXM` текстур
|
||||
+ формат 565 работает некорректно
|
||||
+ не понятно назначение двух магических чисел в заголовке
|
||||
- Распаковка данных миссии `.tma`. Пока работает чтение ареалов и кланов.
|
||||
- Распаковка файла NL. Есть только декодирование заголовка. Формат кажется не используется игрой, а реверс бинарника игры то ещё занятие.
|
||||
- Распаковка текстуры шрифта формата TFNT. Встроен прямо в UI. По сути шрифт это 4116 байт заголовка и текстура TEXM сразу после.
|
||||
- Поддержка всех `NRes` файлов - звуки, музыка, текстуры, карты и другие файлы. Есть документация.
|
||||
- Поддержка всех `TEXM` текстур. Есть документация.
|
||||
- Поддержка файлов миссий `.tma`.
|
||||
- Поддержка шрифтов TFNT.
|
||||
- Поддержка файлов скриптов `.scr`.
|
||||
- Поддержка файлов параметров `.var`.
|
||||
- Поддержка файлов схем объектов `.dat`.
|
||||
|
||||
|
||||
### Структура проекта
|
||||
@@ -34,47 +36,6 @@
|
||||
|
||||
Я конечно стараюсь, но ничего не обещаю.
|
||||
|
||||
#### NResUI
|
||||
|
||||
UI приложение на OpenGL + ImGui.
|
||||
|
||||
Туда постепенно добавляю логику.
|
||||
|
||||
#### NResLib
|
||||
|
||||
Библиотека распаковки формата NRes и всех файлов, которые им запакованы.
|
||||
|
||||
Есть логика импорта и экспорта. Работа не завершена, но уже сейчас можно читать любые архивы такого формата.
|
||||
|
||||
#### TexmLib
|
||||
|
||||
Библиотека распаковки текстур TEXM.
|
||||
|
||||
Есть логика импорта и экспорта, хотя к UI последняя не подключена.
|
||||
|
||||
#### NLUnpacker
|
||||
|
||||
Приложение распаковки NL.
|
||||
|
||||
Работа приостановлена, т.к. кажется игра не использует эти файлы.
|
||||
|
||||
#### MissionDataUnpacker
|
||||
|
||||
Приложение распаковки миссий `.tma`.
|
||||
|
||||
Готово чтение ареалов и кланов. Пока в процессе.
|
||||
|
||||
#### ParkanPlayground
|
||||
|
||||
Пустой проект, использую для локальных тестов.
|
||||
|
||||
#### TextureDecoder
|
||||
|
||||
Приложение для экспорта текстур TEXM.
|
||||
|
||||
Изначально тут игрался с текстурами.
|
||||
|
||||
|
||||
## Для Reverse Engineering-а использую Ghidra
|
||||
|
||||
### Наблюдения
|
||||
@@ -86,6 +47,457 @@ UI приложение на OpenGL + ImGui.
|
||||
- Игра активно и обильно течёт по памяти, оставляя после чтения файлов их `MapViewOfFile` и подобные штуки.
|
||||
- Игра нормально не работает на Win10. Мне помог dgVoodoo. Хотя с ним не работает `MisEditor`.
|
||||
|
||||
## Как быстро найти текст среди всех файлов игры
|
||||
|
||||
```shell
|
||||
grep -rl --include="*" "s_tree_05" .
|
||||
```
|
||||
|
||||
## Как быстро найти байты среди всех файлов игры
|
||||
|
||||
```shell
|
||||
grep -rlU $'\x73\x5f\x74\x72\x65\x65\x5f\x30\x35' .
|
||||
```
|
||||
|
||||
## Как работает игра
|
||||
|
||||
Главное меню:
|
||||
|
||||
Игра сканирует хардкод папку `missions` на наличие файлов миссий. (буквально 01, 02, 03 и т.д.)
|
||||
|
||||
Сначала игра читает название миссии из файла `descr` - тут название для меню.
|
||||
|
||||
- Одиночные игры - `missions/single.{index}/descr`
|
||||
- Тренировочные миссии - `missions/tutorial.{index}/descr`
|
||||
- Кампания - `missions/campaign/campaign.{index1}/descr`
|
||||
* Далее используются подпапки - `missions/campaign/campaign.{index1}/mission.{index2}/descr`
|
||||
|
||||
Как только игра не находит файл `descr`, заканчивается итерация по папкам (понял, т.к. пробуется файл 05 - он не существует).
|
||||
|
||||
Загрузка миссии:
|
||||
|
||||
Читается файл `ui/game_resources.cfg`
|
||||
Из этого файла загружаются ресурсы
|
||||
- `library = "ui\\ui.lib"` - загружается файл `ui.lib`
|
||||
- `library = "ui\\font.lib"` - загружается файл `font.lib`
|
||||
- `library = "sounds.lib"` - загружается файл `sounds.lib`
|
||||
- `library = "voices.lib"` - загружается файл `voices.lib`
|
||||
|
||||
Затем игра читает `save/saveslots.cfg` - тут слоты сохранения
|
||||
|
||||
Затем `Comp.ini` - тут системные функции, которые используются для загрузки объектов.
|
||||
|
||||
```
|
||||
IComponent ** LoadSomething(undefined4, undefined4, undefined4, undefined4)
|
||||
```
|
||||
|
||||
- `Host.url` - этого файла нет
|
||||
- `palettes.lib` - тут палитры, но этот NRes пустой
|
||||
- `system.rlb` - не понятно что
|
||||
- `Textures.lib` - тут текстуры
|
||||
- `Material.lib` - тут какие-то материалы - не понятно
|
||||
- `LightMap.lib` - видимо это карты освещения - не понятно
|
||||
- `sys.lib` - не понятно
|
||||
|
||||
|
||||
- `ScanCode.dsc` - текстовый файл с мапом клавиш
|
||||
- `command.dsc` - текстовый файл с мапом клавиш
|
||||
|
||||
Тут видимо идёт конфигурация ввода
|
||||
|
||||
- `table_1.man` - текстовый файл
|
||||
- `table_2.man` - текстовый файл
|
||||
- `hero.man` - текстовый файл
|
||||
- `addition.man` - текстовый файл
|
||||
- Снова `table_1.man`
|
||||
- Снова `table_1.man`
|
||||
- `M1.tbl` - текстовый файл
|
||||
- Снова `table_2.man`
|
||||
- Снова `table_2.man`
|
||||
- `M2.tbl` - текстовый файл
|
||||
- Снова `hero.man`
|
||||
- Снова `hero.man`
|
||||
- `HERO.TBL`
|
||||
- Снова `addition.man`
|
||||
|
||||
|
||||
- `ui/hq.cfg`
|
||||
- Снова `ui/hq.cfg`
|
||||
|
||||
|
||||
Дальше непосредственно читается миссия
|
||||
|
||||
- `mission.cfg` - метадата миссии
|
||||
- `units\\units\\prebld\\scr_pre1.dat` из метаданных `object prebuild` - `cp` файл (грузятся подряд все)
|
||||
- Опять `ui/hq.cfg`
|
||||
- `mistips.mis` - описание для игрока (экран F1)
|
||||
- `scancode.dsc` - хз
|
||||
- `command.dsc` - хз
|
||||
- `ui_hero.man` - хз
|
||||
- `ui_bots.man` - хз
|
||||
- `ui_hq.man` - хз
|
||||
- `ui_other.man` - хз
|
||||
- Цикл чтения курсоров
|
||||
* `ui/cursor.cfg` - тут настройки курсора.
|
||||
* `ui/{name}` - курсор
|
||||
- Снова `mission.cfg` - метадата миссии
|
||||
- `descr` - название
|
||||
- `data/textres.cfg` - конфиг текстов
|
||||
- Снова `mission.cfg` - метадата миссии
|
||||
- Ещё раз `mission.cfg` - метадата миссии
|
||||
- `ui/minimap.lib` - NRes с текстурами миникарты.
|
||||
- `messages.cfg` - Tutorial messages
|
||||
|
||||
УРА НАКОНЕЦ-ТО `data.tma`
|
||||
|
||||
- Из `.tma` берётся LAND строка (я её так назвал)
|
||||
- `DATA\\MAPS\\SC_3\\land1.wea`
|
||||
- `DATA\\MAPS\\SC_3\\land2.wea`
|
||||
- `BuildDat.lst` - Behaviour will use these schemes to Build Fortification
|
||||
- `DATA\\MAPS\\SC_3\\land.map`
|
||||
- `DATA\\MAPS\\SC_3\\land.msh`
|
||||
|
||||
|
||||
- `effects.rlb`
|
||||
|
||||
Цикл по кланам из `.tma`
|
||||
- `MISSIONS\\SCRIPTS\\screampl.scr`
|
||||
- `varset.var`
|
||||
- `MISSIONS\\SCRIPTS\\varset.var`
|
||||
- `MISSIONS\\SCRIPTS\\screampl.fml`
|
||||
|
||||
|
||||
- `missions/single.01/sky.ske`
|
||||
- `missions/single.01/sky.wea`
|
||||
|
||||
Дальше начинаются объекты игры
|
||||
- `"UNITS\\BUILDS\\BUNKER\\mbunk01.dat"` - cp файл
|
||||
|
||||
## Загрузка `cp` файлов
|
||||
|
||||
`cp` файл - схема. Он содержит дерево частей объекта.
|
||||
|
||||
`cp` файл читается в `ArealMap.dll/CreateObjectFromScheme`
|
||||
|
||||
В зависимости от типа объекта внутри схемы (байты 4..8) выбирается функция, с помощью которой загружается схема.
|
||||
|
||||
Функция выбирается на основе файла `Comp.ini`.
|
||||
- Для ClassBuilding (0x80000000) - вызывается функция c классом 3 (по таблице ниже Building).
|
||||
- Для всех остальных - функция с классом 4 (по таблице ниже Agent).
|
||||
|
||||
На основе файла `Comp.ini` и первом вызове внутри функции `World3D.dll/CreateObject` ремаппинг id:
|
||||
|
||||
|
||||
|
||||
| Logic ID | ClassName | Function |
|
||||
|:--------:|:------------:|--------------------------------|
|
||||
| 1 | Landscape | `terrain.dll LoadLandscape` |
|
||||
| 2 | Agent | `animesh.dll LoadAgent` |
|
||||
| 3 | Building | `terrain.dll LoadBuilding` |
|
||||
| 4 | Agent | `animesh.dll LoadAgent` |
|
||||
| 5 | Camera | `terrain.dll LoadCamera` |
|
||||
| 7 | Atmosphere | `terrain.dll CreateAtmosphere` |
|
||||
| 9 | Agent | `animesh.dll LoadAgent` |
|
||||
| 10 | Agent | `animesh.dll LoadAgent` |
|
||||
| 11 | Research | `misload.dll LoadResearch` |
|
||||
| 12 | Agent | `animesh.dll LoadAgent` |
|
||||
|
||||
Будет дополняться по мере реверса.
|
||||
|
||||
Всем этим функциям передаётся `nres_file_name, nres_entry_name, 0, player_id`
|
||||
|
||||
## `fr FORT` файл
|
||||
|
||||
Всегда 0x80 байт
|
||||
Содержит 2 ссылки на файлы:
|
||||
- `.bas`
|
||||
- `.ctl` - вызывается `LoadAgent`
|
||||
|
||||
## `.msh`
|
||||
|
||||
### Описание ниже валидно только для моделей роботов и зданий.
|
||||
##### Land.msh использует другой формат, хотя 03 файл это всё ещё точки.
|
||||
|
||||
Загружается в `AniMesh.dll/LoadAniMesh`
|
||||
|
||||
- Тип 01 - заголовок. Он хранит список деталей (submesh) в разных LOD
|
||||
```
|
||||
нулевому элементу добавляется флаг 0x1000000
|
||||
Содержит 2 ссылки на файлы анимаций (короткие - файл 13, длинные - файл 08)
|
||||
Если интерполируется анимация -0.5s короче чем magic1 у файла 13
|
||||
И у файла есть OffsetIntoFile13
|
||||
И ushort значение в файле 13 по этому оффсету > IndexInFile08 (это по-моему выполняется всегда)
|
||||
Тогда вместо IndexInFile08 используется значение из файла 13 по этому оффсету (второй байт)
|
||||
```
|
||||
- Тип 02 - описание одного LOD Submesh
|
||||
```
|
||||
Вначале идёт заголовок 0x8C (140) байт
|
||||
В заголовке:
|
||||
8 Vector3 (x,y,z) - bounding box
|
||||
1 Vector4 - center
|
||||
1 Vector3 - bottom
|
||||
1 Vector3 - top
|
||||
1 float - xy_radius
|
||||
Далее инфа про куски меша
|
||||
```
|
||||
- Тип 03 - это вершины (vertex)
|
||||
- Тип 06 - индексы треугольников в файле 03
|
||||
- Тип 04 - скорее всего какие-то цвета RGBA или типа того
|
||||
- Тип 08 - меш-анимации (см файл 01)
|
||||
```
|
||||
Индексируется по IndexInFile08 из файла 01 либо по файлу 13 через OffsetIntoFile13
|
||||
Структура:
|
||||
Vector3 position;
|
||||
float time; // содержит только целые секунды
|
||||
short rotation_x; // делится на 32767
|
||||
short rotation_y; // делится на 32767
|
||||
short rotation_z; // делится на 32767
|
||||
short rotation_w; // делится на 32767
|
||||
---
|
||||
Игра интерполирует анимацию между текущим стейтом и следующим по time.
|
||||
Если время интерполяции совпадает с исходным time, жёстко берётся первый стейт из 0x13.
|
||||
Если время интерполяции совпадает с конечным time, жёстко берётся второй стейт из 0x13.
|
||||
Если ни то и ни другое, тогда t = (time - souce.time) / (dest.time - source.time)
|
||||
```
|
||||
- Тип 12 - microtexture mapping
|
||||
- Тип 13 - короткие меш-анимации (почему я это не дописал?)
|
||||
```
|
||||
Буквально (hex)
|
||||
00 01 01 02 ...
|
||||
```
|
||||
- Тип 0A - ссылка на части меша, не упакованные в текущий меш (например у бункера 4 и 5 части хранятся в parts.rlb)
|
||||
```
|
||||
Не имеет фиксированной длины. Хранит строки в следующем формате.
|
||||
Игра обращается по индексу, пропуская суммарную длину и пропуская 4 байта на каждую строку (длина).
|
||||
т.е. буквально файл выглядит так
|
||||
00 00 00 00 - пустая строка
|
||||
03 00 00 00 - длина строки 1
|
||||
73 74 72 00 - строка "str" + null terminator
|
||||
.. и повторяется до конца файла
|
||||
Кол-во элементов из файла 01 должно быть равно кол-ву строк в этом файле, хотя игра это не проверяет.
|
||||
Если у элемента эта строка равна "central", ему выставляется флаг (flag |= 1)
|
||||
```
|
||||
|
||||
## `.wea`
|
||||
|
||||
Загружается в `World3D.dll/LoadMatManager`
|
||||
|
||||
По сути это текстовый файл состоящий из 2 частей:
|
||||
- Материалы
|
||||
```
|
||||
{count}
|
||||
{id} {name}
|
||||
```
|
||||
- Карты освещения
|
||||
```
|
||||
LIGHTMAPS
|
||||
{count}
|
||||
{id} {name}
|
||||
```
|
||||
|
||||
Может как-то анимироваться. Как - пока не понятно.
|
||||
|
||||
## Lightmap (TEXM)
|
||||
|
||||
У текстур использующихся как карты освещения специальные имена.
|
||||
|
||||
`some_name.00` или `some_name.0`
|
||||
|
||||
Первая цифра после `.` это группа палет (всего 0x11), вторая цифра индекс в этой группе.
|
||||
|
||||
```
|
||||
All lightmaps named whatever.0 → they all end up with no DirectDraw palette attached.
|
||||
```
|
||||
|
||||
## Люблю разработчиков
|
||||
|
||||
У них получилось сделать LightSource и MaterialDescriptor чётко одинаковыми по размеру (0x5c) из-за чего я потерял 3 дня прыгая по указателям
|
||||
|
||||
## `FXID` - файл эффектов
|
||||
|
||||
По сути представляет собой последовательный список саб-эффектов идущих друг за другом.
|
||||
|
||||
Всего существует 9 (1..9) видов эффектов: !описать по мере реверса
|
||||
|
||||
Выглядит так, словно весь файл это тоже эффект сам по себе.
|
||||
|
||||
```
|
||||
0x00-0x04 Type (игра обрезает 1 байт, и поддерживает только значения 1-9)
|
||||
0x04-0x08 unknown
|
||||
0x08-0x0C unknown
|
||||
0x0C-0x10 unknown
|
||||
0x10-0x14 EffectTemplateFlags
|
||||
...
|
||||
0x30-0x34 ScaleX
|
||||
0x34-0x38 ScaleY
|
||||
0x38-0x3C ScaleZ
|
||||
|
||||
enum EffectTemplateFlags : uint32_t
|
||||
{
|
||||
EffectTemplateFlag_RandomizeStrength = 0x0001, // used in UpdateDust
|
||||
EffectTemplateFlag_RandomOffset = 0x0008, // random worldTransform offset
|
||||
EffectTemplateFlag_TriangularShape = 0x0020, // post-process strength as 0→1→0
|
||||
EffectTemplateFlag_OnlyWhenEnvBit0Off = 0x0080, // gating in ComputeEffectStrength
|
||||
EffectTemplateFlag_OnlyWhenEnvBit0On = 0x0100, // gating in ComputeEffectStrength
|
||||
EffectTemplateFlag_MultiplyByLife = 0x0200, // multiply strength by life progress
|
||||
EffectTemplateFlag_EnvFlag2_IfNotSet = 0x0800, // if NOT set → envFlags |= 0x02
|
||||
EffectTemplateFlag_EnvFlag10_IfSet = 0x1000, // if set → envFlags |= 0x10
|
||||
EffectTemplateFlag_AlwaysEmitDust = 0x0010, // ignore piece state / flags
|
||||
EffectTemplateFlag_IgnorePieceState = 0x8000, // treat piece default state as OK
|
||||
EffectTemplateFlag_AutoDeleteAtFull = 0x0002, // delete when strength >= 1
|
||||
EffectTemplateFlag_DetachAfterEmit = 0x0004, // detach from attachment after update
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
# ЕСЛИ ГДЕ-ТО ВИДИШЬ PTR_func_ret1_no_args, ТО ЭТО ShaderConfig
|
||||
|
||||
В рендеринге порядок дата-стримов такой
|
||||
|
||||
position
|
||||
normal
|
||||
color
|
||||
|
||||
# Внутренняя система ID
|
||||
|
||||
- `1` - unknown (implemented by CLandscape) видимо ILandscape
|
||||
- `3` - unknown (implemented by CAtmosphere) видимо IAtmosphere
|
||||
- `4` - IShader
|
||||
- `5` - ITerrain
|
||||
- `6` - IGameObject
|
||||
- `7` - ISettings
|
||||
- `8` - ICamera
|
||||
- `9` - IQueue
|
||||
- `10` - IControl
|
||||
- `0xb` - IAnimation
|
||||
- `0xc` - IShadeStatsBuilder (придумал сам implemented by CShade)
|
||||
- `0xd` - IMatManager
|
||||
- `0xe` - ILightManager
|
||||
- `0xf` - IShade
|
||||
- `0x10` - IBehaviour
|
||||
- `0x11` - IBasement
|
||||
- `0x12` - ICamera2 или IBufferingCamera
|
||||
- `0x13` - IEffectManager
|
||||
- `0x14` - IPosition
|
||||
- `0x15` - IAgent
|
||||
- `0x16` - ILifeSystem
|
||||
- `0x17` - IBuilding - точно он, т.к. ArealMap.CreateObject на него проверяет
|
||||
- `0x18` - IMesh2
|
||||
- `0x19` - IManManager
|
||||
- `0x20` - IJointMesh
|
||||
- `0x21` - IShadowProcessor (придумал сам implemented by CShade)
|
||||
- `0x22` - unknown (implement by CLandscape)
|
||||
- `0x23` - IGameSettingsRoot
|
||||
- `0x24` - IGameObject2
|
||||
- `0x25` - unknown (implemented by CAniMesh)
|
||||
- `0x26` - unknown (implemented by CAniMesh and CControl and CWizard)
|
||||
- `0x28` - ICollObject
|
||||
- `0x29` - IPhysicalModel
|
||||
- `0x101` - I3DRender
|
||||
- `0x102` - ITexture (writable)
|
||||
- `0x103` - IColorLookup
|
||||
- `0x104` - IBitmapFont
|
||||
- `0x105` - INResFile
|
||||
- `0x106` - NResFileMetadata
|
||||
- `0x107` - I3DSound
|
||||
- `0x108` - IListenerTransform
|
||||
- `0x109` - ISoundPool
|
||||
- `0x10a` - ISoundBuffer
|
||||
- `0x10c` - ICDPlayer
|
||||
- `0x10d` - IVertexBuffer
|
||||
- `0x201` - IWizard
|
||||
- `0x202` - IItemManager
|
||||
- `0x203` - ICollManager
|
||||
- `0x301` - IArealMap
|
||||
- `0x302` - ISystemArealMap
|
||||
- `0x303` - IHallway
|
||||
- `0x304` - IDistributor
|
||||
- `0x401` - ISuperAI
|
||||
- `0x501` - MissionData
|
||||
- `0x502` - ResTree
|
||||
- `0x700` - INetWatcher
|
||||
- `0x701` - INetworkInterface
|
||||
|
||||
## SuperAI = Clan с точки зрения индексации
|
||||
|
||||
Т.е. у каждого клана свой SuperAI
|
||||
|
||||
## Опции
|
||||
|
||||
World3D.dll содержит функцию CreateGameSettings.
|
||||
Она создаёт объект настроек и далее вызывает методы в соседних библиотеках.
|
||||
- Terrain.dll - InitializeSettings
|
||||
- Effect.dll - InitializeSettings
|
||||
- Control.dll - InitializeSettings
|
||||
|
||||
Остальные наверное не трогают настройки.
|
||||
|
||||
| Resource ID | wOptionID | Name | Default | Description |
|
||||
|:-----------:|:---------------:|:--------------------------:|:-------:|--------------------|
|
||||
| 1 | 100 (0x64) | "Texture detail" | | |
|
||||
| 2 | 101 (0x65) | "3D Sound" | | |
|
||||
| 3 | 102 (0x66) | "Mouse sensitivity" | | |
|
||||
| 4 | 103 (0x67) | "Joystick sensitivity" | | |
|
||||
| 5 | !not a setting! | "Illegal wOptionID" | | |
|
||||
| 6 | 104 (0x68) | "Wait for retrace" | | |
|
||||
| 7 | 105 (0x69) | "Inverse mouse X" | | |
|
||||
| 8 | 106 (0x6a) | "Inverse mouse Y" | | |
|
||||
| 9 | 107 (0x6b) | "Inverse joystick X" | | |
|
||||
| 10 | 108 (0x6c) | "Inverse joystick Y" | | |
|
||||
| 11 | 109 (0x6d) | "Use BumpMapping" | | |
|
||||
| 12 | 110 (0x6e) | "3D Sound quality" | | |
|
||||
| 13 | 90 (0x5a) | "Reverse sound" | | |
|
||||
| 14 | 91 (0x5b) | "Sound buffer frequency" | | |
|
||||
| 15 | 92 (0x5c) | "Play sound buffer always" | | |
|
||||
| 16 | 93 (0x5d) | "Select best sound device" | | |
|
||||
| ---- | 30 (0x1e) | ShadeConfig | | из файла shade.cfg |
|
||||
| ---- | (0x8001e) | | | добавляет AniMesh |
|
||||
|
||||
|
||||
## Goodies
|
||||
|
||||
```c++
|
||||
// Тип положения объекта в пространстве. Используется при расчёте эффектов,
|
||||
// расстановке объектов и для получения/выдачи их матриц трансформации.
|
||||
enum EPlacementType
|
||||
{
|
||||
// 0: Полная мировая матрица, ориентированная "смотрю на цель".
|
||||
// Входная matrix — это мировая матрица (look-at к какой-то цели).
|
||||
// Движок при необходимости переводит её в локальное/костное пространство
|
||||
// (через inverse(parent/joint) * world и т.п.).
|
||||
// Смысл: "Построй мне мировую матрицу так, чтобы я смотрел на цель;
|
||||
// если я привязан к кости — учти это."
|
||||
Placement_WorldLookAtTarget = 0,
|
||||
|
||||
// 1: Мировая матрица, где основная ось объекта (this->direction)
|
||||
// выровнена по направлению к цели.
|
||||
// Входная matrix — мировая; логика выравнивания использует внутренний
|
||||
// вектор direction и целевую точку/направление.
|
||||
// Смысл: "Используй мой внутренний direction как основную ось
|
||||
// и как можно лучше направь её в сторону цели."
|
||||
Placement_WorldAlignDirectionToTarget = 1,
|
||||
|
||||
// 2: Мировая матрица, полностью задаётся внутренним состоянием объекта.
|
||||
// Входная matrix трактуется как "standalone world" — готовая мировая
|
||||
// матрица объекта. Движок лишь переводит её во внутреннее локальное/
|
||||
// костное пространство (inverse(parent/joint) * world), без look-at
|
||||
// и без выравнивания по цели.
|
||||
// Смысл: "Вот моя конечная мировая матрица. Просто встрои её в иерархию,
|
||||
// не трогая ориентацию дополнительной логикой."
|
||||
Placement_WorldFromStoredTransform = 2,
|
||||
|
||||
// 3: Мировая матрица, ориентированная вдоль вектора движения, но с учётом цели.
|
||||
// Используется предыдущая мировая позиция + текущая + target, чтобы
|
||||
// построить базис: основная ось — направление движения, вспомогательная —
|
||||
// направление к цели.
|
||||
// Смысл: "Ориентируй меня по вектору моего движения, но также учитывай цель."
|
||||
Placement_WorldAlignMotionToTarget = 3
|
||||
};
|
||||
```
|
||||
|
||||
## Контакты
|
||||
|
||||
Вы можете связаться со мной в [Telegram](https://t.me/bird_egop).
|
||||
Вы можете связаться со мной в [Telegram](https://t.me/bird_egop).
|
||||
@@ -1,39 +0,0 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace ScrLib;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static int ReadInt32LittleEndian(this FileStream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
||||
}
|
||||
|
||||
public static float ReadFloatLittleEndian(this FileStream fs)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
fs.ReadExactly(buf);
|
||||
|
||||
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
||||
}
|
||||
|
||||
public static string ReadLengthPrefixedString(this FileStream fs)
|
||||
{
|
||||
var len = fs.ReadInt32LittleEndian();
|
||||
|
||||
if (len == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var buffer = new byte[len];
|
||||
|
||||
fs.ReadExactly(buffer, 0, len);
|
||||
|
||||
return Encoding.ASCII.GetString(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ public class ScrFile
|
||||
/// тут всегда число 59 (0x3b) - это число известных игре скриптов
|
||||
/// </summary>
|
||||
public int Magic { get; set; }
|
||||
|
||||
|
||||
public int EntryCount { get; set; }
|
||||
|
||||
|
||||
public List<ScrEntry> Entries { get; set; }
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class ScrEntry
|
||||
public string Title { get; set; }
|
||||
|
||||
public int Index { get; set; }
|
||||
|
||||
|
||||
public int InnerCount { get; set; }
|
||||
|
||||
public List<ScrEntryInner> Inners { get; set; }
|
||||
@@ -32,7 +32,9 @@ public class ScrEntryInner
|
||||
|
||||
public int UnkInner2 { get; set; }
|
||||
public int UnkInner3 { get; set; }
|
||||
public int UnkInner4 { get; set; }
|
||||
|
||||
public ScrEntryInnerType Type { get; set; }
|
||||
|
||||
public int UnkInner5 { get; set; }
|
||||
|
||||
public int ArgumentsCount { get; set; }
|
||||
@@ -40,4 +42,19 @@ public class ScrEntryInner
|
||||
public List<int> Arguments { get; set; }
|
||||
|
||||
public int UnkInner7 { get; set; }
|
||||
}
|
||||
|
||||
public enum ScrEntryInnerType
|
||||
{
|
||||
Unspecified = -1,
|
||||
_0 = 0,
|
||||
_1 = 1,
|
||||
_2 = 2,
|
||||
_3 = 3,
|
||||
_4 = 4,
|
||||
CheckInternalState = 5,
|
||||
/// <summary>
|
||||
/// В случае 6, игра берёт UnkInner2 (индекс в Varset) и устанавливает ему значение UnkInner3
|
||||
/// </summary>
|
||||
SetVarsetValue = 6,
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
namespace ScrLib;
|
||||
using Common;
|
||||
|
||||
namespace ScrLib;
|
||||
|
||||
public class ScrParser
|
||||
{
|
||||
public static ScrFile ReadFile(string filePath)
|
||||
{
|
||||
var fs = new FileStream(filePath, FileMode.Open);
|
||||
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
return ReadFile(fs);
|
||||
}
|
||||
|
||||
public static ScrFile ReadFile(Stream fs)
|
||||
{
|
||||
var scrFile = new ScrFile();
|
||||
|
||||
|
||||
scrFile.Magic = fs.ReadInt32LittleEndian();
|
||||
|
||||
scrFile.EntryCount = fs.ReadInt32LittleEndian();
|
||||
@@ -17,7 +24,7 @@ public class ScrParser
|
||||
{
|
||||
var entry = new ScrEntry();
|
||||
entry.Title = fs.ReadLengthPrefixedString();
|
||||
|
||||
|
||||
// тут игра дополнительно вычитывает ещё 1 байт, видимо как \0 для char*
|
||||
fs.ReadByte();
|
||||
|
||||
@@ -28,12 +35,12 @@ public class ScrParser
|
||||
{
|
||||
var entryInner = new ScrEntryInner();
|
||||
entryInner.ScriptIndex = fs.ReadInt32LittleEndian();
|
||||
|
||||
|
||||
entryInner.UnkInner2 = fs.ReadInt32LittleEndian();
|
||||
entryInner.UnkInner3 = fs.ReadInt32LittleEndian();
|
||||
entryInner.UnkInner4 = fs.ReadInt32LittleEndian();
|
||||
entryInner.Type = (ScrEntryInnerType)fs.ReadInt32LittleEndian();
|
||||
entryInner.UnkInner5 = fs.ReadInt32LittleEndian();
|
||||
|
||||
|
||||
entryInner.ArgumentsCount = fs.ReadInt32LittleEndian();
|
||||
|
||||
entryInner.Arguments = [];
|
||||
@@ -46,7 +53,7 @@ public class ScrParser
|
||||
entryInner.UnkInner7 = fs.ReadInt32LittleEndian();
|
||||
entry.Inners.Add(entryInner);
|
||||
}
|
||||
|
||||
|
||||
scrFile.Entries.Add(entry);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ public static class Extensions
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
8888 => 32,
|
||||
4444 => 16,
|
||||
565 => 16,
|
||||
888 => 32,
|
||||
0x22B8 => 32,
|
||||
0x115C => 16,
|
||||
0x235 => 16,
|
||||
0x378 => 32,
|
||||
0 => 32
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace TexmLib;
|
||||
/// <param name="MipmapCount">Кол-во мипмапов (уменьшенные копии текстуры)</param>
|
||||
/// <param name="Stride">Сколько БИТ занимает 1 пиксель</param>
|
||||
/// <param name="Magic1">Неизвестно</param>
|
||||
/// <param name="Magic2">Неизвестно</param>
|
||||
/// <param name="FormatOptionFlags">Дополнительные флаги для текстуры</param>
|
||||
/// <param name="Format">Формат пикселя(4444, 8888, 888)</param>
|
||||
public record TexmHeader(
|
||||
string TexmAscii,
|
||||
@@ -23,7 +23,7 @@ public record TexmHeader(
|
||||
int MipmapCount,
|
||||
int Stride,
|
||||
int Magic1,
|
||||
int Magic2,
|
||||
int FormatOptionFlags,
|
||||
int Format
|
||||
);
|
||||
|
||||
@@ -180,14 +180,20 @@ public class TexmFile
|
||||
{
|
||||
var rawPixel = span.Slice(i, 2);
|
||||
|
||||
var g = (byte)(((rawPixel[0] >> 3) & 0b11111) * 255 / 31);
|
||||
var b = (byte)((((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111)) * 255 / 63);
|
||||
var r = (byte)((rawPixel[1] & 0b11111) * 255 / 31);
|
||||
// swap endianess
|
||||
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||
|
||||
result[i / 2 * 4 + 0] = r;
|
||||
result[i / 2 * 4 + 1] = g;
|
||||
result[i / 2 * 4 + 2] = b;
|
||||
result[i / 2 * 4 + 3] = r;
|
||||
var r = (byte)(((rawPixel[0] >> 3) & 0b11111) * 255 / 31);
|
||||
var g = (byte)((((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111)) * 255 / 63);
|
||||
var b = (byte)((rawPixel[1] & 0b11111) * 255 / 31);
|
||||
|
||||
result[i / 2 * 4 + 0] = (byte)(0xff - r);
|
||||
result[i / 2 * 4 + 1] = (byte)(0xff - g);
|
||||
result[i / 2 * 4 + 2] = (byte)(0xff - b);
|
||||
result[i / 2 * 4 + 3] = 0xff;
|
||||
|
||||
// swap endianess back
|
||||
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -202,6 +208,9 @@ public class TexmFile
|
||||
{
|
||||
var rawPixel = span.Slice(i, 2);
|
||||
|
||||
// swap endianess
|
||||
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||
|
||||
var a = (byte)(((rawPixel[0] >> 4) & 0b1111) * 17);
|
||||
var b = (byte)(((rawPixel[0] >> 0) & 0b1111) * 17);
|
||||
var g = (byte)(((rawPixel[1] >> 4) & 0b1111) * 17);
|
||||
@@ -211,6 +220,9 @@ public class TexmFile
|
||||
result[i / 2 * 4 + 1] = g;
|
||||
result[i / 2 * 4 + 2] = b;
|
||||
result[i / 2 * 4 + 3] = a;
|
||||
|
||||
// swap endianess back
|
||||
(rawPixel[0], rawPixel[1]) = (rawPixel[1], rawPixel[0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -247,16 +259,21 @@ public class TexmFile
|
||||
for (var i = 0; i < span.Length; i += 4)
|
||||
{
|
||||
var rawPixel = span.Slice(i, 4);
|
||||
// swap endianess back
|
||||
// (rawPixel[0], rawPixel[1], rawPixel[2], rawPixel[3]) = (rawPixel[3], rawPixel[2], rawPixel[1], rawPixel[0]);
|
||||
|
||||
var b = rawPixel[0];
|
||||
var r = rawPixel[0];
|
||||
var g = rawPixel[1];
|
||||
var r = rawPixel[2];
|
||||
var b = rawPixel[2];
|
||||
var a = rawPixel[3];
|
||||
|
||||
result[i + 0] = r;
|
||||
result[i + 1] = g;
|
||||
result[i + 2] = b;
|
||||
result[i + 3] = a;
|
||||
|
||||
// swap endianess back
|
||||
// (rawPixel[0], rawPixel[1], rawPixel[2], rawPixel[3]) = (rawPixel[3], rawPixel[2], rawPixel[1], rawPixel[0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -22,7 +22,7 @@ public class TexmParser
|
||||
var mipmapCountBytes = headerBytes[12..16];
|
||||
var strideBytes = headerBytes[16..20];
|
||||
var magic1Bytes = headerBytes[20..24];
|
||||
var magic2Bytes = headerBytes[24..28];
|
||||
var formatOptionFlagsBytes = headerBytes[24..28];
|
||||
var formatBytes = headerBytes[28..32];
|
||||
|
||||
var texmAscii = Encoding.ASCII.GetString(texmHeader).Trim('\0');
|
||||
@@ -31,7 +31,7 @@ public class TexmParser
|
||||
var mipmapCount = BinaryPrimitives.ReadInt32LittleEndian(mipmapCountBytes);
|
||||
var stride = BinaryPrimitives.ReadInt32LittleEndian(strideBytes);
|
||||
var magic1 = BinaryPrimitives.ReadInt32LittleEndian(magic1Bytes);
|
||||
var magic2 = BinaryPrimitives.ReadInt32LittleEndian(magic2Bytes);
|
||||
var formatOptionFlags = BinaryPrimitives.ReadInt32LittleEndian(formatOptionFlagsBytes);
|
||||
var format = BinaryPrimitives.ReadInt32LittleEndian(formatBytes);
|
||||
|
||||
if (texmAscii != "Texm")
|
||||
@@ -51,7 +51,7 @@ public class TexmParser
|
||||
mipmapCount,
|
||||
stride,
|
||||
magic1,
|
||||
magic2,
|
||||
formatOptionFlags,
|
||||
format
|
||||
);
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Buffers.Binary;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using TexmLib;
|
||||
|
||||
var folder = "C:\\Projects\\CSharp\\ParkanPlayground\\ParkanPlayground\\bin\\Debug\\net8.0\\ui.lib";
|
||||
|
||||
var files = Directory.EnumerateFiles(folder);
|
||||
|
||||
List<TexmFile> textureFiles = [];
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fs = new FileStream(file, FileMode.Open);
|
||||
|
||||
var parseResult = TexmParser.ReadFromStream(fs, file);
|
||||
|
||||
textureFiles.Add(parseResult.TexmFile);
|
||||
|
||||
Console.WriteLine($"Successfully read: {file}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine($"Failed read: {file}");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var textureFile in textureFiles)
|
||||
{
|
||||
await textureFile.WriteToFolder("unpacked");
|
||||
Console.WriteLine($"Unpacked {Path.GetFileName(textureFile.FileName)} into folder");
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
VarsetLib/BinaryVarsetFile.cs
Normal file
6
VarsetLib/BinaryVarsetFile.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace VarsetLib;
|
||||
|
||||
/// <summary>
|
||||
/// Бинарный файл, который можно найти в behpsp.res
|
||||
/// </summary>
|
||||
public record BinaryVarsetFile(int Count, List<BinaryVarsetItem> Items);
|
||||
93
VarsetLib/BinaryVarsetFileParser.cs
Normal file
93
VarsetLib/BinaryVarsetFileParser.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using Common;
|
||||
|
||||
namespace VarsetLib;
|
||||
|
||||
public class BinaryVarsetFileParser
|
||||
{
|
||||
public static BinaryVarsetFile Parse(string path)
|
||||
{
|
||||
using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
return Parse(fs);
|
||||
}
|
||||
|
||||
public static BinaryVarsetFile Parse(Stream fs)
|
||||
{
|
||||
var count = fs.ReadInt32LittleEndian();
|
||||
|
||||
var items = new List<BinaryVarsetItem>(count);
|
||||
|
||||
Span<byte> buf4 = stackalloc byte[4];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var valueLength = fs.ReadInt32LittleEndian();
|
||||
|
||||
var valueType = (BinaryVarsetValueType)fs.ReadInt32LittleEndian();
|
||||
|
||||
fs.ReadExactly(buf4);
|
||||
var magic3 = new IntFloatValue(buf4);
|
||||
var name = fs.ReadLengthPrefixedString();
|
||||
var string2 = fs.ReadLengthPrefixedString();
|
||||
|
||||
fs.ReadExactly(buf4);
|
||||
var magic4 = new IntFloatValue(buf4);
|
||||
fs.ReadExactly(buf4);
|
||||
var magic5 = new IntFloatValue(buf4);
|
||||
fs.ReadExactly(buf4);
|
||||
var magic6 = new IntFloatValue(buf4);
|
||||
fs.ReadExactly(buf4);
|
||||
var magic7 = new IntFloatValue(buf4);
|
||||
|
||||
if(string2.Length != 0)
|
||||
{
|
||||
_ = 5;
|
||||
}
|
||||
|
||||
if (valueType is BinaryVarsetValueType.Bool)
|
||||
{
|
||||
items.Add(new BinaryVarsetItem<bool>(
|
||||
valueLength,
|
||||
valueType,
|
||||
magic3.AsInt != 0,
|
||||
name,
|
||||
string2,
|
||||
magic4.AsInt != 0,
|
||||
magic5.AsInt != 0,
|
||||
magic6.AsInt != 0,
|
||||
magic7.AsInt != 0));
|
||||
}
|
||||
else if (valueType is BinaryVarsetValueType.Dword)
|
||||
{
|
||||
items.Add(new BinaryVarsetItem<uint>(
|
||||
valueLength,
|
||||
valueType,
|
||||
(uint)magic3.AsInt,
|
||||
name,
|
||||
string2,
|
||||
(uint)magic4.AsInt,
|
||||
(uint)magic5.AsInt,
|
||||
(uint)magic6.AsInt,
|
||||
(uint)magic7.AsInt));
|
||||
}
|
||||
else if (valueType is BinaryVarsetValueType.Float)
|
||||
{
|
||||
items.Add(new BinaryVarsetItem<float>(
|
||||
valueLength,
|
||||
valueType,
|
||||
magic3.AsFloat,
|
||||
name,
|
||||
string2,
|
||||
magic4.AsFloat,
|
||||
magic5.AsFloat,
|
||||
magic6.AsFloat,
|
||||
magic7.AsFloat));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Unknown value type {valueType}");
|
||||
}
|
||||
}
|
||||
|
||||
return new BinaryVarsetFile(count, items);
|
||||
}
|
||||
}
|
||||
35
VarsetLib/BinaryVarsetItem.cs
Normal file
35
VarsetLib/BinaryVarsetItem.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Diagnostics;
|
||||
using Common;
|
||||
|
||||
namespace VarsetLib;
|
||||
|
||||
[DebuggerDisplay("{DebugDisplay}")]
|
||||
public abstract record BinaryVarsetItem()
|
||||
{
|
||||
protected abstract string DebugDisplay { get; }
|
||||
}
|
||||
|
||||
public record BinaryVarsetItem<TValue>(
|
||||
int ValueLength, // длина значения
|
||||
BinaryVarsetValueType ValueType, // тип значения
|
||||
TValue Magic3, // кажется 0 всегда
|
||||
string Name, // имя переменной
|
||||
string String2, // кажется всегда пусто
|
||||
TValue Magic4,
|
||||
TValue Magic5,
|
||||
TValue Magic6, // минимум
|
||||
TValue Magic7 // максимум
|
||||
) : BinaryVarsetItem
|
||||
{
|
||||
protected override string DebugDisplay => $"{typeof(TValue).Name}, {ValueLength} bytes. magic3: {Magic3}. {Name,-30} {String2} - {Magic4} {Magic5} {Magic6} {Magic7}";
|
||||
};
|
||||
|
||||
public enum BinaryVarsetValueType
|
||||
{
|
||||
Float0 = 0,
|
||||
Int = 1,
|
||||
Bool = 2,
|
||||
Float = 3,
|
||||
CharPtr = 4,
|
||||
Dword = 5
|
||||
}
|
||||
3
VarsetLib/VarsetItem.cs
Normal file
3
VarsetLib/VarsetItem.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace VarsetLib;
|
||||
|
||||
public record VarsetItem(string Type, string Name, string Value);
|
||||
5
VarsetLib/VarsetLib.csproj
Normal file
5
VarsetLib/VarsetLib.csproj
Normal file
@@ -0,0 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
82
VarsetLib/VarsetParser.cs
Normal file
82
VarsetLib/VarsetParser.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
namespace VarsetLib;
|
||||
|
||||
public class VarsetParser
|
||||
{
|
||||
public static List<VarsetItem> Parse(string path)
|
||||
{
|
||||
using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
return Parse(fs);
|
||||
}
|
||||
|
||||
public static List<VarsetItem> Parse(Stream fs)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reader = new StreamReader(fs);
|
||||
|
||||
List<VarsetItem> varsetItems = [];
|
||||
|
||||
var lineIndex = 1;
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
var line = reader.ReadLine()!;
|
||||
if (line.Length == 0)
|
||||
{
|
||||
lineIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.StartsWith("//") || line.Trim().StartsWith("//"))
|
||||
{
|
||||
lineIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!line.StartsWith("VAR"))
|
||||
{
|
||||
Console.WriteLine($"Error on line: {lineIndex}! Not starting with VAR");
|
||||
lineIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var openParenthesisIndex = line.IndexOf("(");
|
||||
var closeParenthesisIndex = line.IndexOf(")");
|
||||
|
||||
if (openParenthesisIndex == -1 || closeParenthesisIndex == -1 ||
|
||||
closeParenthesisIndex <= openParenthesisIndex)
|
||||
{
|
||||
Console.WriteLine($"Error on line: {lineIndex}! VAR() format invalid");
|
||||
lineIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var arguments = line.Substring(openParenthesisIndex + 1,
|
||||
closeParenthesisIndex - openParenthesisIndex - 1);
|
||||
|
||||
var parts = arguments.Trim()
|
||||
.Split(',');
|
||||
|
||||
var type = parts[0]
|
||||
.Trim();
|
||||
|
||||
var name = parts[1]
|
||||
.Trim();
|
||||
|
||||
var value = parts[2]
|
||||
.Trim();
|
||||
|
||||
var item = new VarsetItem(type, name, value);
|
||||
varsetItems.Add(item);
|
||||
|
||||
lineIndex++;
|
||||
}
|
||||
|
||||
return varsetItems;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
629
material_field_analysis.csv
Normal file
629
material_field_analysis.csv
Normal file
@@ -0,0 +1,629 @@
|
||||
FileName,MaterialTypeMaybe,IsTerrainMaybe,SupportsTextureMode6,GlobalTransparencyMaybe,GlobalSelfIlluminationMaybe,StageCount,AnimCount,HasField18NonZero
|
||||
B_PG3,0,0,True,1,000,0,000,1,1,True
|
||||
B_PG4,0,0,True,1,000,0,000,1,1,True
|
||||
B_PG6,0,0,True,1,000,0,000,1,1,True
|
||||
B_PG2,1,0,False,1,000,0,000,1,1,True
|
||||
B_PG5,0,0,True,1,000,0,000,1,1,True
|
||||
B_GEN_05,0,0,True,1,000,0,000,1,1,True
|
||||
B_GEN_06,0,0,True,1,000,0,000,1,1,True
|
||||
B_GEN_07,0,0,True,1,000,0,000,1,1,True
|
||||
B_GEN_08,0,0,True,1,000,0,000,1,1,True
|
||||
B_DD1CL,0,0,True,1,000,0,000,8,1,True
|
||||
B_DD1DK,0,0,True,1,000,0,000,1,1,True
|
||||
B_DD2,0,0,True,1,000,0,000,1,1,True
|
||||
B_RL_06,0,0,True,1,000,0,000,1,1,True
|
||||
B_RL_02,0,0,True,1,000,0,000,1,1,True
|
||||
B_RL_03,0,0,True,1,000,0,000,1,1,True
|
||||
B_RL_19,0,0,True,1,000,0,000,1,1,True
|
||||
B_RL_20,0,0,True,1,000,0,000,1,1,True
|
||||
B_RL_21,0,0,True,1,000,0,000,1,1,True
|
||||
B_S12,0,0,True,1,000,0,000,1,1,True
|
||||
B_S0,0,0,True,1,000,0,000,1,1,True
|
||||
B_S0A1,0,0,True,1,000,0,000,1,1,True
|
||||
B_S1,0,0,True,1,000,0,000,1,1,True
|
||||
B_S10,0,0,True,1,000,0,000,1,1,True
|
||||
B_S2,0,0,True,1,000,0,000,1,1,True
|
||||
B_S13,0,0,True,1,000,0,000,4,1,True
|
||||
B_S14,0,0,True,1,000,0,000,1,1,True
|
||||
B_S6,0,0,True,1,000,0,000,1,1,True
|
||||
B_S3,0,0,True,1,000,0,000,1,1,True
|
||||
B_S4,0,0,True,1,000,0,000,1,1,True
|
||||
B_S5,1,0,False,1,000,0,000,1,1,True
|
||||
B_S7,0,0,True,1,000,0,000,1,1,True
|
||||
B_S0A2,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG3,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG4,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG6,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG7,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG2,1,0,False,1,000,0,000,1,1,True
|
||||
R_PG5,0,0,True,1,000,0,000,1,1,True
|
||||
R_GEN_05,0,0,True,1,000,0,000,1,1,True
|
||||
R_GEN_06,0,0,True,1,000,0,000,1,1,True
|
||||
R_GEN_07,0,0,True,1,000,0,000,1,1,True
|
||||
R_GEN_08,0,0,True,1,000,0,000,1,1,True
|
||||
R_DD1,0,0,True,1,000,0,000,1,1,True
|
||||
R_DD1CL,0,0,True,1,000,0,000,1,1,True
|
||||
R_DD1DK,0,0,True,1,000,0,000,1,1,True
|
||||
R_DD2,0,0,True,1,000,0,000,1,1,True
|
||||
R_RL_06,0,0,True,1,000,0,000,1,1,True
|
||||
R_RL_02,0,0,True,1,000,0,000,1,1,True
|
||||
R_RL_03,0,0,True,1,000,0,000,1,1,True
|
||||
R_RL_20,0,0,True,1,000,0,000,1,1,True
|
||||
R_RL_21,0,0,True,1,000,0,000,1,1,True
|
||||
R_S12,0,0,True,1,000,0,000,1,1,True
|
||||
R_S0,0,0,True,1,000,0,000,1,1,True
|
||||
R_S0A1,0,0,True,1,000,0,000,1,1,True
|
||||
R_S1,0,0,True,1,000,0,000,1,1,True
|
||||
R_S10,0,0,True,1,000,0,000,1,1,True
|
||||
R_S2,0,0,True,1,000,0,000,1,1,True
|
||||
R_S6,0,0,True,1,000,0,000,1,1,True
|
||||
R_S3,0,0,True,1,000,0,000,1,1,True
|
||||
R_S4,0,0,True,1,000,0,000,1,1,True
|
||||
R_S5,1,0,False,1,000,0,000,1,1,True
|
||||
R_S7,0,0,True,1,000,0,000,1,1,True
|
||||
R_S0A2,0,0,True,1,000,0,000,1,1,True
|
||||
R_S13,0,0,True,1,000,0,000,4,1,True
|
||||
R_S14,0,0,True,1,000,0,000,1,1,True
|
||||
SKY1,1,0,False,1,000,0,000,1,1,True
|
||||
B_RLW_4,0,0,True,1,000,0,000,1,1,True
|
||||
R_RLW_4,0,0,True,1,000,0,000,1,1,True
|
||||
DEFAULT,1,0,False,1,000,0,000,1,1,True
|
||||
EXPL1,1,0,False,1,000,0,000,16,1,True
|
||||
JET1,2,8,False,1,000,0,000,6,1,True
|
||||
JET2,2,8,False,1,000,0,000,4,1,True
|
||||
SHOOT1,1,0,False,1,000,0,000,4,1,True
|
||||
JET3,2,8,False,1,000,0,000,1,1,True
|
||||
SUN1,1,0,False,1,000,0,000,1,1,True
|
||||
JET4,2,8,False,1,000,0,000,4,1,True
|
||||
CLOUD1,1,0,False,1,000,0,000,15,1,True
|
||||
SPLASH1,1,0,False,1,000,0,000,5,1,True
|
||||
TRACE1,1,0,False,1,000,0,000,4,1,True
|
||||
TRACE2,1,0,False,1,000,0,000,4,1,True
|
||||
B_COMP_2,0,0,True,1,000,0,000,1,1,True
|
||||
R_COMP_2,0,0,True,1,000,0,000,1,1,True
|
||||
EXPL2,1,0,False,1,000,0,000,16,1,True
|
||||
JET5,2,8,False,1,000,0,000,1,1,True
|
||||
B_COMP_1,0,0,True,1,000,0,000,4,1,True
|
||||
B_COMP_3,1,0,False,1,000,0,000,4,1,True
|
||||
LIGHT1,2,8,False,1,000,0,000,1,1,True
|
||||
BLACK,0,0,False,1,000,0,000,1,1,True
|
||||
R_RLW_5,0,0,True,1,000,0,000,2,1,True
|
||||
JET6,2,8,False,1,000,0,000,6,1,True
|
||||
SHOOT2,1,0,False,1,000,0,000,4,1,True
|
||||
JET7,2,8,False,1,000,0,000,4,1,True
|
||||
SPLASH2,1,0,False,1,000,0,000,5,1,True
|
||||
CLOUD2,1,0,False,1,000,0,000,15,1,True
|
||||
R_MN_01,0,0,True,1,000,0,000,1,1,True
|
||||
B_MN_01,0,0,True,1,000,0,000,1,1,True
|
||||
B_MN_02,0,0,True,1,000,0,000,1,1,True
|
||||
B_COMP_3G,1,0,False,1,000,0,000,4,1,True
|
||||
B_COMP_3R,1,0,False,1,000,0,000,4,1,True
|
||||
B_COMP_3B,1,0,False,1,000,0,000,4,1,True
|
||||
JET8,2,8,False,1,000,0,000,6,1,True
|
||||
JET9,2,8,False,1,000,0,000,4,1,True
|
||||
B_MN_01R,0,0,True,1,000,0,000,4,1,True
|
||||
B_MN_01G,0,0,True,1,000,0,000,4,1,True
|
||||
B_MN_01B,0,0,True,1,000,0,000,4,1,True
|
||||
STONE00,0,0,True,1,000,0,000,1,1,True
|
||||
STONE01,0,0,True,1,000,0,000,1,1,True
|
||||
TREE00,1,0,False,1,000,0,000,1,1,True
|
||||
TREE01,0,0,False,1,000,0,000,1,1,True
|
||||
TREE02,0,0,False,1,000,0,000,1,1,True
|
||||
TREE03,1,0,False,1,000,0,000,1,1,True
|
||||
TREE04,0,0,False,1,000,0,000,1,1,True
|
||||
TREE05,0,0,False,1,000,0,000,1,1,True
|
||||
R_RL_26,0,0,True,1,000,0,000,4,1,True
|
||||
R_RL_26REV,0,0,True,1,000,0,000,4,1,True
|
||||
R_RL_27,0,0,True,1,000,0,000,4,1,True
|
||||
R_RL_27REV,0,0,True,1,000,0,000,4,1,True
|
||||
R_RL_25,0,0,True,1,000,0,000,4,1,True
|
||||
R_RL_25REV,0,0,True,1,000,0,000,4,1,True
|
||||
R_RL_24,0,0,True,1,000,0,000,1,1,True
|
||||
TREE06,1,0,False,1,000,0,000,1,1,True
|
||||
TREE07,1,0,False,1,000,0,000,1,1,True
|
||||
B_HNG_01,0,0,True,1,000,0,000,1,1,True
|
||||
B_HNG_02,1,0,False,1,000,0,000,1,1,True
|
||||
R_PG11,0,0,True,1,000,0,000,1,1,True
|
||||
B_PG11,0,0,True,1,000,0,000,1,1,True
|
||||
B_LIGHT,0,0,False,1,000,0,000,1,1,True
|
||||
B_PG12,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG12,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_01,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_02,0,0,True,1,000,0,000,3,1,True
|
||||
R_RL_19,0,0,True,1,000,0,000,1,1,True
|
||||
B_RB_04,0,0,True,1,000,0,000,1,1,True
|
||||
R_RB_04,0,0,True,1,000,0,000,1,1,True
|
||||
R_RBW_03,0,0,True,1,000,0,000,1,1,True
|
||||
DUST,1,0,False,1,000,0,000,4,1,True
|
||||
SMOKE,1,0,False,1,000,0,000,4,1,True
|
||||
FIRE_SMOKE,1,0,False,1,000,0,000,26,1,True
|
||||
DARK_CLOUD,1,0,False,1,000,0,000,4,1,True
|
||||
LASER_G,2,8,False,1,000,0,000,4,1,True
|
||||
LASER_R,2,8,False,1,000,0,000,4,1,True
|
||||
GLOW,1,0,False,1,000,0,000,1,1,True
|
||||
LASER_Y,2,8,False,1,000,0,000,4,1,True
|
||||
B_EFFECT_001,1,0,False,1,000,0,000,1,1,True
|
||||
R_RMW_9,0,0,True,1,000,0,000,3,1,True
|
||||
R_PG14,0,0,True,1,000,0,000,1,1,True
|
||||
SHIELD1,2,8,False,1,000,0,000,7,1,True
|
||||
B_LT_01,0,0,True,1,000,0,000,1,1,False
|
||||
B_LT_03,0,0,True,1,000,0,000,1,1,True
|
||||
B_LBL_01,0,0,True,1,000,0,000,8,8,True
|
||||
CUTTER,1,0,False,1,000,0,000,4,1,True
|
||||
GLOW_B,1,0,False,1,000,0,000,1,1,True
|
||||
GLOW_G,1,0,False,1,000,0,000,1,1,True
|
||||
GLOW_R,2,8,False,1,000,0,000,1,1,True
|
||||
TREE08,1,0,False,1,000,0,000,1,1,True
|
||||
TREE09,1,0,False,1,000,0,000,1,1,True
|
||||
TREE10,1,0,False,1,000,0,000,1,1,True
|
||||
TREE11,1,0,False,1,000,0,000,1,1,False
|
||||
TREE12,1,0,False,1,000,0,000,1,1,True
|
||||
TREE13,1,0,False,1,000,0,000,1,1,True
|
||||
TREE14,1,0,False,1,000,0,000,1,1,True
|
||||
TREE15,1,0,False,1,000,0,000,1,1,True
|
||||
BALL_B,1,0,False,1,000,0,000,1,1,True
|
||||
BALL_G,1,0,False,1,000,0,000,1,1,True
|
||||
BALL_R,1,0,False,1,000,0,000,1,1,True
|
||||
BALL_Y,1,0,False,1,000,0,000,1,1,True
|
||||
BALL_W,1,0,False,1,000,0,000,1,1,True
|
||||
GLOW_Y,1,0,False,1,000,0,000,1,1,True
|
||||
GLOW_W,1,0,False,1,000,0,000,1,1,True
|
||||
SPLASH3,1,0,False,1,000,0,000,5,1,True
|
||||
LASER_B,2,8,False,1,000,0,000,4,1,True
|
||||
LIGHT_W,1,0,False,1,000,0,000,1,1,True
|
||||
SHOOT3,1,0,False,1,000,0,000,4,1,True
|
||||
CUTTER_HIT,1,0,False,1,000,0,000,3,1,True
|
||||
RAILGUN_HIT,2,8,False,1,000,0,000,3,1,True
|
||||
EXPL3,1,0,False,1,000,0,000,24,1,True
|
||||
R_PG9,0,0,True,1,000,0,000,1,1,True
|
||||
BIRD_B,0,0,True,1,000,0,000,1,1,True
|
||||
BIRD_T,0,0,True,1,000,0,000,1,1,True
|
||||
BIRD_W,0,0,True,1,000,0,000,1,1,True
|
||||
MATL,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_06,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_07,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_04,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_03,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_03R,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_03B,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_05,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_06B,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_06R,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_06G,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_06Y,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_07Y,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_07R,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_07G,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_07B,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG17,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG18,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG19,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG20,0,0,True,1,000,0,000,1,1,True
|
||||
R_SLD_POD,0,0,True,1,000,0,000,1,1,True
|
||||
R_SLD_GL,1,0,False,1,000,0,000,1,1,True
|
||||
BIRD_U,0,0,True,1,000,0,000,1,1,True
|
||||
SHIELD_R,2,8,False,1,000,0,000,10,1,True
|
||||
SHIELD_G,2,8,False,1,000,0,000,10,1,True
|
||||
SHIELD_B,2,8,False,1,000,0,000,10,1,True
|
||||
B_MTP_07E,0,0,True,1,000,0,000,1,1,True
|
||||
B_MTP_08,0,0,True,1,000,0,000,3,1,True
|
||||
LASER_R_HIT,2,8,False,1,000,0,000,6,1,True
|
||||
LASER_G_HIT,2,8,False,1,000,0,000,6,1,True
|
||||
LASER_B_HIT,2,8,False,1,000,0,000,6,1,True
|
||||
LASER_Y_HIT,2,8,False,1,000,0,000,6,1,True
|
||||
B_P00,0,0,True,1,000,0,000,1,1,True
|
||||
AIGRETTE_Y,1,0,False,1,000,0,000,3,1,True
|
||||
AIGRETTE_R,1,0,False,1,000,0,000,3,1,True
|
||||
AIGRETTE_G,1,0,False,1,000,0,000,3,1,True
|
||||
AIGRETTE_B,1,0,False,1,000,0,000,3,1,True
|
||||
B_LT_01R,0,0,True,1,000,0,000,1,1,False
|
||||
B_LT_01G,0,0,True,1,000,0,000,1,1,False
|
||||
B_LT_01B,0,0,True,1,000,0,000,1,1,False
|
||||
B_LT_01Y,0,0,True,1,000,0,000,1,1,False
|
||||
B_COMP_4,0,0,True,1,000,0,000,1,1,True
|
||||
B_COMP_4R,0,0,True,1,000,0,000,1,1,True
|
||||
B_COMP_4G,0,0,True,1,000,0,000,1,1,True
|
||||
B_COMP_4B,0,0,True,1,000,0,000,1,1,True
|
||||
B_COMP_4Y,0,0,True,1,000,0,000,1,1,True
|
||||
B_LT_03B,0,0,True,1,000,0,000,1,1,True
|
||||
B_LT_03R,0,0,True,1,000,0,000,1,1,True
|
||||
B_LT_03G,0,0,True,1,000,0,000,1,1,True
|
||||
B_LT_03Y,0,0,True,1,000,0,000,1,1,True
|
||||
B_MN_03,0,0,True,1,000,0,000,1,1,True
|
||||
B_MN_03R,0,0,True,1,000,0,000,1,1,True
|
||||
B_MN_03G,0,0,True,1,000,0,000,1,1,True
|
||||
B_MN_03B,0,0,True,1,000,0,000,1,1,True
|
||||
B_MN_03Y,0,0,True,1,000,0,000,1,1,True
|
||||
B_LT_04,0,0,True,1,000,0,000,1,1,True
|
||||
CLOUD3,1,0,False,1,000,0,000,15,1,True
|
||||
B_GEN_09,0,0,True,1,000,0,000,1,1,True
|
||||
R_GEN_09,0,0,True,1,000,0,000,1,1,True
|
||||
B_GEN_08L,0,0,True,1,000,0,000,1,1,True
|
||||
B_S3L,0,0,True,1,000,0,000,1,1,True
|
||||
B_S4L,0,0,True,1,000,0,000,1,1,True
|
||||
PORTAL_001,1,0,False,1,000,0,000,2,1,False
|
||||
PORTAL_004,1,0,False,1,000,0,000,2,1,True
|
||||
B_GEN_10,0,0,True,1,000,0,000,1,1,True
|
||||
B_GEN_11,0,0,True,1,000,0,000,1,1,True
|
||||
HWOY01,1,0,False,1,000,0,000,1,1,True
|
||||
FTREE1,1,0,False,1,000,0,000,1,1,True
|
||||
HTREE1,1,0,False,1,000,0,000,1,1,True
|
||||
KORA,0,0,True,1,000,0,000,1,1,True
|
||||
KORA2,0,0,True,1,000,0,000,1,1,True
|
||||
SKIN02,0,0,True,1,000,0,000,1,1,True
|
||||
B_LT_02,0,0,True,1,000,0,000,1,1,True
|
||||
EXPL4,1,0,False,1,000,0,000,29,1,True
|
||||
SHOOT4,1,0,False,1,000,0,000,4,1,True
|
||||
SPLASH4,1,0,False,1,000,0,000,6,1,True
|
||||
EXPL5,1,0,False,1,000,0,000,4,1,True
|
||||
EXPL6,1,0,False,1,000,0,000,8,1,True
|
||||
SPLASH5,1,0,False,1,000,0,000,6,1,True
|
||||
SPLASH6,1,0,False,1,000,0,000,6,1,True
|
||||
B_MTP_02L,0,0,True,1,000,0,000,3,1,True
|
||||
B_MTP_01L,0,0,True,1,000,0,000,1,1,True
|
||||
B_GEN_05L,0,0,True,1,000,0,000,1,1,True
|
||||
B_P08,1,0,False,1,000,0,000,2,1,True
|
||||
B_P10,1,0,False,1,000,0,000,2,1,True
|
||||
B_P11,1,0,False,1,000,0,000,2,1,True
|
||||
B_P13,1,0,False,1,000,0,000,2,1,True
|
||||
LAMP,1,0,False,1,000,0,000,1,1,True
|
||||
LASER_W_HIT,2,8,False,1,000,0,000,6,1,True
|
||||
SPITTLE_R,1,0,False,1,000,0,000,16,1,True
|
||||
SPITTLE_G,1,0,False,1,000,0,000,16,1,True
|
||||
SHOOT5,1,0,False,1,000,0,000,4,1,True
|
||||
ALIEN_BALL,1,0,False,1,000,0,000,1,1,True
|
||||
ALIENSKIN,0,0,True,1,000,0,000,1,1,True
|
||||
CLOUD_G,1,0,False,1,000,0,000,15,1,True
|
||||
CLOUD_R,1,0,False,1,000,0,000,15,1,True
|
||||
CLOUD_B,1,0,False,1,000,0,000,15,1,True
|
||||
BUILD_SPHERE,1,0,False,1,000,0,000,4,1,True
|
||||
BUILD_MIRACLE,1,0,False,1,000,0,000,4,1,True
|
||||
BUILD_GALO,1,0,False,1,000,0,000,4,1,True
|
||||
SHIELD_Y,2,8,False,1,000,0,000,10,1,True
|
||||
PLAS_BUL,2,8,False,1,000,0,000,4,1,True
|
||||
PLAS_SHOOT,2,8,False,1,000,0,000,6,1,True
|
||||
GLOW_V,1,0,False,1,000,0,000,1,1,True
|
||||
LASER_V,2,8,False,1,000,0,000,4,1,True
|
||||
LASER_V_HIT,2,8,False,1,000,0,000,6,1,True
|
||||
ENV_MOON,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_STARS,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_FLARE_00,2,8,False,1,000,0,000,1,1,True
|
||||
ENV_FLARE_01,2,8,False,1,000,0,000,1,1,True
|
||||
SHOOK,1,0,False,1,000,0,000,4,1,True
|
||||
SHOOK_B,1,0,False,1,000,0,000,4,1,True
|
||||
ENV_CLOUDS,1,0,False,1,000,0,000,1,1,True
|
||||
BUILD_SHOOT,1,0,False,1,000,0,000,1,1,True
|
||||
BUILD_FLY,1,0,False,1,000,0,000,8,1,True
|
||||
ENV_SUN,1,0,False,1,000,0,000,1,1,False
|
||||
FIRE_SMOKE_W,1,0,False,1,000,0,000,23,1,True
|
||||
B_MN_01Y,0,0,True,1,000,0,000,4,1,True
|
||||
R_LBL_01,0,0,True,1,000,0,000,8,8,True
|
||||
TREE16,1,0,False,1,000,0,000,1,1,True
|
||||
B_RBW_3,0,0,True,1,000,0,000,1,1,True
|
||||
SNOWFLAKE,1,0,False,1,000,0,000,1,1,True
|
||||
FIRESTORM,1,0,False,1,000,0,000,8,1,True
|
||||
RAIN_DROP,1,0,False,1,000,0,000,1,1,True
|
||||
B_TELEPORT,1,0,False,1,000,0,000,3,1,True
|
||||
B_TELEPORT2,1,0,False,1,000,0,000,2,1,True
|
||||
WATER,0,0,False,1,000,0,000,1,1,True
|
||||
WATER_DROP,1,0,False,1,000,0,000,6,1,True
|
||||
WATER_DROP_R,1,0,False,1,000,0,000,6,1,True
|
||||
WATER_DROP_G,1,0,False,1,000,0,000,6,1,True
|
||||
WATER_DROP_B,1,0,False,1,000,0,000,6,1,True
|
||||
WATER_M,0,0,False,1,000,0,000,10,1,True
|
||||
B_MTP_04G,0,0,True,1,000,0,000,1,1,True
|
||||
TASER_SHOOT,1,0,False,1,000,0,000,6,1,True
|
||||
SHOOK2,1,0,False,1,000,0,000,4,1,True
|
||||
SHOOK2_B,1,0,False,1,000,0,000,4,1,True
|
||||
TASER_HIT,1,0,False,1,000,0,000,6,1,True
|
||||
B_MTP_07BT,0,0,True,1,000,0,000,3,1,True
|
||||
B_LT_04_PG7,0,0,True,1,000,0,000,1,1,True
|
||||
B_LT_04R_PG7,0,0,True,1,000,0,000,1,1,True
|
||||
B_LT_04G_PG7,0,0,True,1,000,0,000,1,1,True
|
||||
B_LT_04B_PG7,0,0,True,1,000,0,000,1,1,True
|
||||
B_LT_04Y_PG7,0,0,True,1,000,0,000,1,1,True
|
||||
B_RB_04L,0,0,True,1,000,0,000,1,1,True
|
||||
B_PG4L,0,0,True,1,000,0,000,1,1,True
|
||||
B_S6L,0,0,True,1,000,0,000,1,1,True
|
||||
B_PG11L,0,0,True,1,000,0,000,1,1,True
|
||||
B_RBW_3L,0,0,True,1,000,0,000,1,1,True
|
||||
B_RL_06L,0,0,True,1,000,0,000,1,1,True
|
||||
B_DD1DK_DAM,0,0,True,1,000,1000,000,1,1,True
|
||||
B_S0_DAM,0,0,True,1,000,1000,000,1,1,True
|
||||
B_PLT_01,0,0,True,1,000,0,000,1,1,True
|
||||
B_PLT_02,0,0,True,1,000,0,000,1,1,True
|
||||
B_PLT_03,0,0,True,1,000,0,000,1,1,True
|
||||
B_PLT_05,0,0,True,1,000,0,000,1,1,False
|
||||
B_PLT_06,0,0,True,1,000,0,000,1,1,True
|
||||
B_PLT_04,0,0,True,1,000,0,000,2,1,True
|
||||
ENV_LAVA,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_LAVA_M,1,0,False,1,000,0,000,4,1,True
|
||||
DARK_SMOKE,1,0,False,1,000,0,000,17,1,True
|
||||
ENV_CLOUDS_2,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_SUN_2,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_FLARE_01_2,2,8,False,1,000,0,000,1,1,True
|
||||
ENV_FLARE_00_2,2,8,False,1,000,0,000,1,1,True
|
||||
ENV_NEBULA_2,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_MOON_2,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_CLOUDS_3,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_CLOUDS_4,1,0,False,1,000,0,000,1,1,True
|
||||
FIRE_SMOKE_Y,1,0,False,1,000,0,000,23,1,True
|
||||
APKORA,0,0,True,1,000,0,000,1,1,True
|
||||
NTREE1,1,0,False,1,000,0,000,1,1,True
|
||||
B_RU01,0,0,True,1,000,0,000,1,1,True
|
||||
B_RU02,0,0,True,1,000,0,000,1,1,True
|
||||
B_RU03,0,0,True,1,000,0,000,1,1,True
|
||||
FIRE,1,0,False,1,000,0,000,23,1,True
|
||||
PG24,1,0,False,1,000,0,000,1,1,True
|
||||
PG25,1,0,False,1,000,0,000,1,1,True
|
||||
PG26,0,0,True,1,000,0,000,1,1,True
|
||||
ICE2,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_MOON_3,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_MOON_4,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_MOON_5,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_SUN_1,1,0,False,1,000,0,000,1,1,False
|
||||
B_PLT_07,0,0,True,1,000,0,000,1,1,True
|
||||
ENV_CLOUDS_5,1,0,False,1,000,0,000,1,1,True
|
||||
MAT_DROP,1,0,False,1,000,0,000,1,1,True
|
||||
R_TPG02,0,0,True,1,000,0,000,1,1,True
|
||||
R_TPG01,0,0,True,1,000,0,000,1,1,True
|
||||
R_LAUSE2,0,0,True,1,000,0,000,1,1,True
|
||||
R_LAUSE,0,0,True,1,000,0,000,1,1,True
|
||||
R_LAULEG1,0,0,True,1,000,0,000,1,1,True
|
||||
ENV_CLOUDS_6,1,0,False,1,000,0,000,1,1,True
|
||||
R_AIM,0,0,True,1,000,0,000,1,1,True
|
||||
B_FOUND,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_NEBULA_0,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_NEBULA_1,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_NEBULA_3,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_SUN_6,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_SUN_3,1,0,False,1,000,0,000,1,1,False
|
||||
ENV_SUN_4,1,0,False,1,000,0,000,1,1,True
|
||||
ENV_SUN_5,1,0,False,1,000,0,000,1,1,True
|
||||
B_PLT_06_NS,0,0,True,1,000,0,000,1,1,True
|
||||
SNOWFLAKE_B,1,0,False,1,000,0,000,1,1,True
|
||||
B_A_BRIGE,1,0,False,1,000,0,000,4,1,True
|
||||
B_PLT_08,0,0,True,1,000,0,000,1,1,True
|
||||
R_NP01,0,0,True,1,000,0,000,1,1,True
|
||||
R_NP03,0,0,True,1,000,0,000,1,1,True
|
||||
R_NP04,0,0,True,1,000,0,000,1,1,True
|
||||
R_TB08,0,0,True,1,000,0,000,1,1,False
|
||||
R_TB09,0,0,True,1,000,0,000,1,1,True
|
||||
R_TB10,0,0,True,1,000,0,000,1,1,True
|
||||
R_TB12,0,0,True,1,000,0,000,1,1,True
|
||||
R_TB19,0,0,True,1,000,0,000,1,1,True
|
||||
R_NP05,0,0,True,1,000,0,000,1,1,True
|
||||
ENV_LIGHTNING,2,8,False,1,000,0,000,1,1,True
|
||||
MINERALS,1,0,False,1,000,0,000,4,1,True
|
||||
MINERAL_CIRCLE,1,0,False,1,000,0,000,1,1,True
|
||||
MINERAL_RAY,1,0,False,1,000,0,000,4,1,True
|
||||
R_NP06,0,0,True,1,000,0,000,1,1,True
|
||||
R_BG_15,0,0,True,1,000,0,000,1,1,True
|
||||
R_BG_10,0,0,True,1,000,0,000,1,1,False
|
||||
R_BG_11,0,0,True,1,000,0,000,1,1,True
|
||||
R_MG_01,0,0,True,1,000,0,000,1,1,True
|
||||
R_MG_02,0,0,True,1,000,0,000,1,1,True
|
||||
R_LG_01,0,0,True,1,000,0,000,1,1,True
|
||||
R_LG_02,0,0,True,1,000,0,000,1,1,True
|
||||
R_TG_01,0,0,True,1,000,0,000,1,1,True
|
||||
R_TG_02,0,0,True,1,000,0,000,1,1,True
|
||||
R_NP07,0,0,True,1,000,0,000,1,1,True
|
||||
R_NP08,0,0,True,1,000,0,000,1,1,True
|
||||
R_NP09,0,0,True,1,000,0,000,1,1,True
|
||||
R_TB32,0,0,True,1,000,0,000,1,1,True
|
||||
R_TB33,0,0,True,1,000,0,000,1,1,True
|
||||
R_TB34,0,0,True,1,000,0,000,1,1,True
|
||||
R_TB26,0,0,True,1,000,0,000,1,1,False
|
||||
R_GB05,0,0,True,1,000,0,000,1,1,False
|
||||
R_GB10,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB09,0,0,True,1,000,0,000,1,1,True
|
||||
R_FL_PG,0,0,True,1,000,0,000,1,1,True
|
||||
R_NP10,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB17,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB19,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB18,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB16,0,0,True,1,000,0,000,4,1,True
|
||||
R_GB24,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB27,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB33,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB34,0,0,True,1,000,0,000,1,1,False
|
||||
R_GB46,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB45,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB47,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB48,0,0,True,1,000,0,000,1,1,True
|
||||
R_GB50,0,0,True,1,000,0,000,4,1,True
|
||||
R_GB51,0,0,True,1,000,0,000,4,1,True
|
||||
R_GB52,0,0,True,1,000,0,000,4,1,True
|
||||
R_ENG_PG,0,0,True,1,000,0,000,1,1,True
|
||||
R_NM_PG,0,0,True,1,000,0,000,1,1,True
|
||||
R_NRS_PG,0,0,True,1,000,0,000,1,1,True
|
||||
R_NSS_PG,0,0,True,1,000,0,000,1,1,True
|
||||
L00,0,0,False,1,000,0,000,2,2,True
|
||||
L01,0,0,False,1,000,0,000,2,2,True
|
||||
L02,0,0,False,1,000,0,000,2,2,True
|
||||
L03,0,0,False,1,000,0,000,2,2,True
|
||||
L04,0,0,False,1,000,0,000,2,2,True
|
||||
L05,0,0,False,1,000,0,000,2,2,True
|
||||
L06,0,0,False,1,000,0,000,2,2,True
|
||||
L07,0,0,False,1,000,0,000,2,2,True
|
||||
L08,0,0,False,1,000,0,000,2,1,True
|
||||
L09,0,0,False,1,000,0,000,2,2,True
|
||||
L10,0,0,False,1,000,0,000,2,2,True
|
||||
L11,0,0,False,1,000,0,000,2,2,True
|
||||
L13,0,0,False,1,000,0,000,2,2,True
|
||||
L14,0,0,False,1,000,0,000,2,2,True
|
||||
L15,0,0,False,1,000,0,000,2,2,True
|
||||
L16,0,0,False,1,000,0,000,2,2,True
|
||||
L17,0,0,False,1,000,0,000,2,2,True
|
||||
L18,0,0,False,1,000,0,000,2,2,True
|
||||
L19,0,0,False,1,000,0,000,2,2,True
|
||||
L20,0,0,False,1,000,0,000,2,2,True
|
||||
L21,0,0,False,1,000,0,000,2,2,True
|
||||
L22,0,0,False,1,000,0,000,2,2,True
|
||||
L23,0,0,False,1,000,0,000,2,2,True
|
||||
L24,0,0,False,1,000,0,000,2,2,True
|
||||
L25,0,0,False,1,000,0,000,2,2,True
|
||||
L26,0,0,False,1,000,0,000,2,2,True
|
||||
L27,0,0,False,1,000,0,000,2,2,True
|
||||
L28,0,0,False,1,000,0,000,2,2,True
|
||||
L29,0,0,False,1,000,0,000,2,2,True
|
||||
L30,0,0,False,1,000,0,000,2,2,True
|
||||
L31,0,0,False,1,000,0,000,2,2,True
|
||||
L32,0,0,False,1,000,0,000,2,1,True
|
||||
L33,0,0,False,1,000,0,000,2,2,True
|
||||
L34,0,0,False,1,000,0,000,2,2,True
|
||||
L35,0,0,False,1,000,0,000,2,2,True
|
||||
L36,0,0,False,1,000,0,000,2,2,True
|
||||
L37,0,0,False,1,000,0,000,2,2,True
|
||||
L38,0,0,False,1,000,0,000,2,2,True
|
||||
L39,0,0,False,1,000,0,000,2,2,True
|
||||
L40,0,0,False,1,000,0,000,2,2,True
|
||||
L41,0,0,False,1,000,0,000,2,2,True
|
||||
L42,0,0,False,1,000,0,000,2,2,True
|
||||
L43,0,0,False,1,000,0,000,2,2,True
|
||||
R_NP12,0,0,True,1,000,0,000,1,1,True
|
||||
R_NP11,0,0,True,1,000,0,000,1,1,True
|
||||
R_TM05,0,0,True,1,000,0,000,1,1,False
|
||||
R_TM13,0,0,True,1,000,0,000,1,1,True
|
||||
R_TM12,0,0,True,1,000,0,000,1,1,True
|
||||
R_TT05,0,0,True,1,000,0,000,1,1,False
|
||||
R_NTT05,0,0,True,1,000,0,000,4,1,True
|
||||
R_NTT08,0,0,True,1,000,0,000,1,1,True
|
||||
R_NSM_PG,0,0,True,1,000,0,000,1,1,True
|
||||
R_NP13,0,0,True,1,000,0,000,1,1,True
|
||||
R_NTT22,0,0,True,1,000,0,000,1,1,False
|
||||
B_PG30,0,0,True,1,000,0,000,1,1,True
|
||||
B_PG31,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG30,0,0,True,1,000,0,000,1,1,True
|
||||
R_PG31,0,0,True,1,000,0,000,1,1,True
|
||||
ELKA,1,0,False,1,000,0,000,1,1,False
|
||||
TRCH02,0,0,True,1,000,0,000,1,1,False
|
||||
GRASS,1,0,False,1,000,0,000,1,1,True
|
||||
ICE01,1,0,False,1,000,0,000,1,1,True
|
||||
LEAF01,1,0,False,1,000,0,000,1,1,False
|
||||
SUNFLOWR,0,0,True,1,000,0,000,1,1,True
|
||||
BAO01,1,0,False,1,000,0,000,1,1,True
|
||||
DHORNO1,0,0,True,1,000,0,000,1,1,True
|
||||
DHORN01,0,0,True,1,000,0,000,1,1,True
|
||||
TRCH01,1,0,False,1,000,0,000,1,1,True
|
||||
TRCH03,0,0,True,1,000,0,000,1,1,True
|
||||
TRCH04,0,0,True,1,000,0,000,1,1,True
|
||||
TRCH05,0,0,True,1,000,0,000,1,1,True
|
||||
DEFL_B,1,0,False,1,000,0,000,4,1,True
|
||||
ENV_WAVE,2,8,False,1,000,0,000,1,1,True
|
||||
DEFL_G,1,0,False,1,000,0,000,4,1,True
|
||||
SHOOK_BC,1,0,False,1,000,0,000,1,1,True
|
||||
SMOKE_W,1,0,False,1,000,0,000,10,1,True
|
||||
ENV_LAVA_BOT,0,0,False,1,000,10000,000,2,2,True
|
||||
WATER_BOT,0,0,False,1,000,10000,000,2,2,True
|
||||
ENV_WAVE_B,2,8,False,1,000,0,000,1,1,True
|
||||
ENV_WAVE_G,2,8,False,1,000,0,000,1,1,True
|
||||
HLP_RED_ME,0,0,True,1,000,0,000,1,1,True
|
||||
B_COMP_1SI,0,0,True,1,000,0,000,4,1,True
|
||||
HLP_GLOW_B,1,0,False,1,000,0,000,3,1,True
|
||||
HLP_LAMP_B,0,0,True,1,000,0,000,3,1,True
|
||||
HLP_PLACE_B,0,0,True,1,000,0,000,3,1,True
|
||||
HLP_RAY_B,1,0,False,1,000,0,000,3,1,True
|
||||
HLP_SIGN_B,1,0,False,1,000,0,000,3,1,True
|
||||
ENV_WAVE_R,2,8,False,1,000,0,000,1,1,True
|
||||
R_NRS_PGL,0,0,True,1,000,0,000,3,1,True
|
||||
R_NM_RGL,0,0,True,1,000,0,000,3,1,True
|
||||
R_PG_RGL,0,0,True,1,000,0,000,3,1,True
|
||||
R_FL_RGL,0,0,True,1,000,0,000,3,1,True
|
||||
DEFL_BL,2,8,False,1,000,0,000,4,1,True
|
||||
CLOUD2L,2,8,False,1,000,0,000,15,1,True
|
||||
ENV_NLAVA,1,0,False,1,000,0,000,8,1,True
|
||||
ENV_NLAVA_M,1,0,False,1,000,0,000,8,1,True
|
||||
GLOW_ENG,2,8,False,1,000,0,000,1,1,True
|
||||
FIRE_ADD,2,8,False,1,000,0,000,23,1,True
|
||||
SMOKE_ADD,2,8,False,1,000,0,000,4,1,True
|
||||
OK_TEST1,0,0,True,1,000,0,000,1,1,True
|
||||
SMOKE_Y,1,0,False,1,000,0,000,16,1,True
|
||||
SMOKE_Y_ADD,2,8,False,1,000,0,000,16,1,True
|
||||
DUST_Y,1,0,False,1,000,0,000,4,1,True
|
||||
FIRE_SMOKE_G,1,0,False,1,000,0,000,23,1,True
|
||||
FIRE_SMOKE_GADD,2,8,False,1,000,0,000,23,1,True
|
||||
SMOKE_YG,1,0,False,1,000,0,000,16,1,True
|
||||
SMOKE_YG_ADD,2,8,False,1,000,0,000,16,1,True
|
||||
SMOKE_G,1,0,False,1,000,0,000,16,1,True
|
||||
SMOKE_G_ADD,2,8,False,1,000,0,000,16,1,True
|
||||
SPITTLE_G_ADD,2,8,False,1,000,0,000,16,1,True
|
||||
WD_G_ADD,2,8,False,1,000,0,000,6,1,True
|
||||
TOK41,1,0,False,1,000,0,000,1,1,True
|
||||
DUST_ADD,2,8,False,1,000,0,000,4,1,True
|
||||
TOK31,0,0,True,1,000,0,000,1,1,True
|
||||
TOK31DARK,0,0,True,1,000,0,000,1,1,True
|
||||
TOK32,1,0,False,1,000,0,000,4,1,True
|
||||
TOK42,0,0,True,1,000,0,000,1,1,True
|
||||
BUILD_SPHERE_A,1,0,False,1,000,0,000,4,1,True
|
||||
LASER_RN,1,0,False,1,000,0,000,4,1,True
|
||||
TOK43,0,0,True,1,000,0,000,1,1,True
|
||||
TOK43DARK,0,0,True,1,000,0,000,1,1,True
|
||||
TOK43_2,0,0,True,1,000,0,000,4,1,True
|
||||
TOK43_2D,0,0,True,1,000,0,000,1,1,False
|
||||
TOK43A,0,0,True,1,000,0,000,1,1,True
|
||||
SMOKE_R,1,0,False,1,000,0,000,16,1,True
|
||||
SMOKE_R_ADD,2,8,False,1,000,0,000,16,1,True
|
||||
TOK41_RAY,2,8,False,1,000,0,000,8,1,True
|
||||
SMOKE_GG,1,0,False,1,000,0,000,16,1,True
|
||||
SMOKE_GG_ADD,2,8,False,1,000,0,000,16,1,True
|
||||
SMOKE_W_ADD,2,8,False,1,000,0,000,10,1,True
|
||||
TOK41_PLC,1,0,False,1,000,0,000,4,1,True
|
||||
TOK33_BASE,2,8,False,1,000,0,000,4,1,True
|
||||
TOK44_BASE,2,8,False,1,000,0,000,4,1,True
|
||||
SMOKE_W1_ADD,2,8,False,1,000,0,000,10,1,True
|
||||
TOK42W,0,0,True,1,000,0,000,1,1,True
|
||||
TOK42_W4,0,0,True,1,000,0,000,3,1,True
|
||||
TOK42_W1E,0,0,True,1,000,0,000,2,1,True
|
||||
TOK42_W1M,0,0,True,1,000,0,000,2,1,True
|
||||
TOK42_W5,0,0,True,1,000,0,000,2,1,True
|
||||
SMOKE_R1_ADD,2,8,False,1,000,0,000,16,1,True
|
||||
DUST_WORM_ADD,2,8,False,1,000,0,000,4,1,True
|
||||
DUST_WORM,1,0,False,1,000,0,000,4,1,True
|
||||
TOK11,0,0,True,1,000,0,000,1,1,True
|
||||
TOK11_H,0,0,True,1,000,0,000,3,1,True
|
||||
TOK11_Y,0,0,True,1,000,0,000,3,1,True
|
||||
R_RLW_5L,0,0,True,1,000,0,000,1,1,True
|
||||
PMAP_5,0,0,True,1,000,0,000,1,1,True
|
||||
PMAP_42,0,0,True,1,000,0,000,1,1,False
|
||||
PMAP_1,0,0,True,1,000,0,000,1,1,True
|
||||
PMAP_2,0,0,True,1,000,0,000,1,1,True
|
||||
PMAP_3,0,0,True,1,000,0,000,1,1,True
|
||||
PMAP_4,0,0,True,1,000,0,000,1,1,True
|
||||
PMAP_18,0,0,True,1,000,0,000,1,1,True
|
||||
PMAP_31,0,0,True,1,000,0,000,1,1,False
|
||||
PMAP_14,0,0,True,1,000,0,000,1,1,False
|
||||
PMAP_15,0,0,True,1,000,0,000,1,1,True
|
||||
PMAP_16,0,0,True,1,000,0,000,1,1,True
|
||||
PMAP_17,0,0,True,1,000,0,000,1,1,True
|
||||
HLP_GLOW_G,1,0,False,1,000,0,000,3,1,True
|
||||
HLP_LAMP_G,0,0,True,1,000,0,000,3,1,True
|
||||
HLP_PLACE_G,0,0,True,1,000,0,000,3,1,True
|
||||
HLP_RAY_G,1,0,False,1,000,0,000,3,1,True
|
||||
HLP_SIGN_G,1,0,False,1,000,0,000,3,1,True
|
||||
HLP_GLOW_R,1,0,False,1,000,0,000,3,1,True
|
||||
HLP_LAMP_R,0,0,True,1,000,0,000,3,1,True
|
||||
HLP_PLACE_R,0,0,True,1,000,0,000,3,1,True
|
||||
HLP_RAY_R,1,0,False,1,000,0,000,3,1,True
|
||||
R_SRL_25,0,0,True,1,000,0,000,1,1,True
|
||||
R_SRL_25REV,0,0,True,1,000,0,000,1,1,True
|
||||
R_SRL_26,0,0,True,1,000,0,000,1,1,False
|
||||
R_SRL_26REV,0,0,True,1,000,0,000,1,1,True
|
||||
R_SRL_27,0,0,True,1,000,0,000,1,1,True
|
||||
R_SRL_27REV,0,0,True,1,000,0,000,1,1,True
|
||||
DEFL_Y_ADD,2,8,False,1,000,0,000,4,1,True
|
||||
DEFL_Y1_ADD,2,8,False,1,000,0,000,4,1,True
|
||||
TOK51,1,0,False,1,000,0,000,1,1,True
|
||||
TF2,1,0,False,1,000,0,000,1,1,True
|
||||
TF1,0,0,True,1,000,0,000,1,1,True
|
||||
TF1_05,0,0,True,1,000,0,000,1,1,False
|
||||
TF1_06A,0,0,True,1,000,0,000,4,1,True
|
||||
TF1_BLACK,0,0,True,1,000,0,000,1,1,True
|
||||
TF1_06,0,0,True,1,000,0,000,1,1,True
|
||||
LASER_R_ADD,2,8,False,1,000,0,000,6,1,True
|
||||
TOK76_UP,1,0,False,1,000,0,000,3,1,True
|
||||
TOK76_DN,1,0,False,1,000,0,000,1,1,True
|
||||
TOK41_RAYR,2,8,False,1,000,0,000,8,1,True
|
||||
SMOKE_R2_ADD,2,8,False,1,000,0,000,10,1,True
|
||||
JET_R,2,8,False,1,000,0,000,1,1,True
|
||||
JET_G,2,8,False,1,000,0,000,1,1,True
|
||||
|
Reference in New Issue
Block a user