Initial commit

This commit is contained in:
wincent
2019-11-13 19:45:48 +01:00
commit bbce3c5cac
84 changed files with 5331 additions and 0 deletions

571
.gitignore vendored Normal file
View File

@@ -0,0 +1,571 @@
# Created by https://www.gitignore.io/api/rider,csharp,visualstudio
# Edit at https://www.gitignore.io/?templates=rider,csharp,visualstudio
### Csharp ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
### Rider ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
# User-specific files (MonoDevelop/Xamarin Studio)
# Build results
# Visual Studio 2015/2017 cache/options directory
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
# MSTest test Results
# NUNIT
# Build Results of an ATL Project
# Benchmark Results
# .NET Core
# StyleCop
# Files built by Visual Studio
# Chutzpah Test files
# Visual C++ cache files
# Visual Studio profiler
# Visual Studio Trace Files
# TFS 2012 Local Workspace
# Guidance Automation Toolkit
# ReSharper is a .NET coding add-in
# JustCode is a .NET coding add-in
# TeamCity is a build add-in
# DotCover is a Code Coverage Tool
# AxoCover is a Code Coverage Tool
# Visual Studio code coverage results
# NCrunch
# MightyMoose
# Web workbench (sass)
# Installshield output folder
# DocProject is a documentation generator add-in
# Click-Once directory
# Publish Web Output
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
# NuGet Packages
# The packages folder can be ignored because of Package Restore
# except build/, which is used as an MSBuild target.
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
# Microsoft Azure Build Output
# Microsoft Azure Emulator
# Windows Store app package directories and files
# Visual Studio cache files
# files ending in .cache can be ignored
# but keep track of directories ending in .cache
# Others
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
# SQL Server files
# Business Intelligence projects
# Microsoft Fakes
# GhostDoc plugin setting file
# Node.js Tools for Visual Studio
# Visual Studio 6 build log
# Visual Studio 6 workspace options file
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
# Visual Studio LightSwitch build output
# Paket dependency manager
# FAKE - F# Make
# JetBrains Rider
# CodeRush personal settings
# Python Tools for Visual Studio (PTVS)
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
# Telerik's JustMock configuration file
# BizTalk build output
# OpenCover UI analysis results
# Azure Stream Analytics local run output
# MSBuild Binary and Structured Log
# NVidia Nsight GPU debugger configuration file
# MFractors (Xamarin productivity tool) working folder
# Local History for Visual Studio
# End of https://www.gitignore.io/api/rider,csharp,visualstudio
.DS_Store

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "RakDotNet.IO"]
path = "RakDotNet.IO"
url = https://github.com/yuwui/RakDotNet.IO.git

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/.idea.InfectedRose/.idea/workspace.xml

View File

@@ -0,0 +1,46 @@
using System;
using System.Numerics;
using RakDotNet.IO;
namespace InfectedRose.Core
{
public static class BitReaderExtensions
{
public static string ReadNiString(this BitReader @this, bool wide = false, bool small = false)
{
var len = small ? @this.Read<byte>() : @this.Read<uint>();
var str = new char[len];
for (var i = 0; i < len; i++)
{
str[i] = (char) (wide ? @this.Read<ushort>() : @this.Read<byte>());
}
return new string(str);
}
public static Quaternion ReadNiQuaternion(this BitReader @this)
{
return new Quaternion
{
W = @this.Read<float>(),
X = @this.Read<float>(),
Y = @this.Read<float>(),
Z = @this.Read<float>()
};
}
public static byte[] ReadBuffer(this BitReader @this, uint length)
{
var buffer = new byte[length];
for (var i = 0; i < length; i++)
{
buffer[i] = @this.Read<byte>();
}
return buffer;
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Numerics;
using RakDotNet.IO;
namespace InfectedRose.Core
{
public static class BitWriterExtensions
{
public static void WriteNiString(this BitWriter @this, string str, bool wide = false, bool small = false)
{
if (small) @this.Write((byte) str.Length);
else @this.Write((uint) str.Length);
foreach (var c in str)
{
if (wide) @this.Write((ushort) c);
else @this.Write((byte) c);
}
}
public static void WriteNiQuaternion(this BitWriter @this, Quaternion quaternion)
{
@this.Write(quaternion.W);
@this.Write(quaternion.X);
@this.Write(quaternion.Y);
@this.Write(quaternion.Z);
}
public static void Write(this BitWriter @this, byte[] buffer)
{
foreach (var value in buffer)
{
@this.Write(value);
}
}
}
}

View File

@@ -0,0 +1,8 @@
using RakDotNet.IO;
namespace InfectedRose.Core
{
public interface IConstruct : ISerializable, IDeserializable
{
}
}

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\RakDotNet.IO\RakDotNet.IO\RakDotNet.IO.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,122 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using InfectedRose.Database.Generic;
namespace InfectedRose.Database
{
public class AccessDatabase : IList<Table>
{
public DatabaseFile File { get; private set; }
public AccessDatabase(DatabaseFile file)
{
File = file;
}
public IEnumerator<Table> GetEnumerator()
{
return File.TableHeader.Tables.Select(t => new Table(t.info, t.data)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(Table item)
{
if (item == default) return;
var list = File.TableHeader.Tables.ToList();
list.Add((item.Info, item.Data));
File.TableHeader.Tables = list.ToArray();
}
public void Clear()
{
File.TableHeader.Tables = new (FdbColumnHeader info, FdbRowBucket data)[0];
}
public bool Contains(Table item)
{
return this.Any(value => value == item);
}
public void CopyTo(Table[] array, int arrayIndex)
{
var list = File.TableHeader.Tables.Select(t => new Table(t.info, t.data)).ToList();
list.CopyTo(array, arrayIndex);
}
public bool Remove(Table item)
{
if (item == default) return false;
var removal = File.TableHeader.Tables.FirstOrDefault(t => t.info == item.Info && t.data == item.Data);
if (removal == default) return false;
var list = File.TableHeader.Tables.ToList();
list.Remove(removal);
File.TableHeader.Tables = list.ToArray();
return true;
}
public int Count => File.TableHeader.Tables.Length;
public bool IsReadOnly => false;
public int IndexOf(Table item)
{
if (item == default) return -1;
return File.TableHeader.Tables.ToList().IndexOf(
File.TableHeader.Tables.First(t => t.info == item.Info && t.data == item.Data)
);
}
public void Insert(int index, Table item)
{
if (item == default) return;
var list = File.TableHeader.Tables.ToList();
list.Insert(index, (item.Info, item.Data));
File.TableHeader.Tables = list.ToArray();
}
public void RemoveAt(int index)
{
var list = File.TableHeader.Tables.ToList();
list.RemoveAt(index);
File.TableHeader.Tables = list.ToArray();
}
public Table this[int index]
{
get
{
var (info, data) = File.TableHeader.Tables[index];
return new Table(info, data);
}
set => File.TableHeader.Tables[index] = (value.Info, value.Data);
}
public Table this[string name]
{
get
{
foreach (var (info, data) in File.TableHeader.Tables)
{
if (info.TableName == name)
return new Table(info, data);
}
return default;
}
}
}
}

View File

@@ -0,0 +1,115 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace InfectedRose.Database
{
public class Column : IList<Field>
{
internal FdbRowInfo Data { get; private set; }
internal Table Table { get; private set; }
internal Column(FdbRowInfo data, Table table)
{
Data = data;
Table = table;
}
public IEnumerator<Field> GetEnumerator()
{
int index = default;
return Data.DataHeader.Data.Fields.Select(
field => new Field(Data.DataHeader.Data, index++, Table)
).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(Field item)
{
throw new NotSupportedException("Fields should fallow the table structure, and their signature should not be changed.");
}
public void Clear()
{
throw new NotSupportedException("Fields should fallow the table structure, and their signature should not be changed.");
}
public bool Contains(Field item)
{
if (item == default) return false;
int index = default;
var list = Data.DataHeader.Data.Fields.Select(
field => new Field(Data.DataHeader.Data, index++, Table)
).ToList();
return list.Any(c => c.Data == item.Data && c.Index == item.Index);
}
public void CopyTo(Field[] array, int arrayIndex)
{
int index = default;
var list = Data.DataHeader.Data.Fields.Select(
field => new Field(Data.DataHeader.Data, index++, Table)
).ToList();
list.CopyTo(array, arrayIndex);
}
public bool Remove(Field item)
{
throw new NotSupportedException("Fields should fallow the table structure, and their signature should not be changed.");
}
public int Count => Data.DataHeader.Data.Fields.Length;
public bool IsReadOnly => false;
public int IndexOf(Field item)
{
return item?.Index ?? -1;
}
public void Insert(int index, Field item)
{
throw new NotSupportedException("Fields should fallow the table structure, and their signature should not be changed.");
}
public void RemoveAt(int index)
{
throw new NotSupportedException("Fields should fallow the table structure, and their signature should not be changed.");
}
public Field this[int index]
{
get => new Field(Data.DataHeader.Data, index, Table);
set => throw new NotSupportedException("Fields should fallow the table structure, and their signature should not be changed.");
}
public Field this[string name]
{
get
{
int index = default;
for (var i = 0; i < Table.TableInfo.Count; i++)
{
var column = Table.TableInfo[i];
if (column.Name == name) index = i;
}
return new Field(Data.DataHeader.Data, index, Table);
}
}
}
}

View File

@@ -0,0 +1,44 @@
namespace InfectedRose.Database
{
public class ColumnInfo
{
internal Table Table { get; private set; }
internal int Index { get; private set; }
public DataType Type
{
get => Table.Info.Data.Fields[Index].type;
set
{
var dataField = Table.Info.Data.Fields[Index];
dataField.type = value;
Table.Info.Data.Fields[Index] = dataField;
}
}
public string Name
{
get => Table.Info.Data.Fields[Index].name;
set
{
var dataField = Table.Info.Data.Fields[Index];
dataField.name = new FdbString
{
Value = value
};
Table.Info.Data.Fields[Index] = dataField;
}
}
internal ColumnInfo(Table table, int index)
{
Table = table;
Index = index;
}
}
}

View File

@@ -0,0 +1,15 @@
namespace InfectedRose.Database
{
public enum DataType : uint
{
Nothing, // cant remember if those are just skipped/ignored or even showed up
Integer,
Unknown1, // never used?
Float,
Text, // called STRING in MSSQL?
Boolean,
Bigint, // or DATETIME?
Unknown2, // never used?
Varchar // called TEXT in MSSQL?
}
}

View File

@@ -0,0 +1,11 @@
using RakDotNet.IO;
namespace InfectedRose.Database
{
public abstract class DatabaseData : IDeserializable
{
internal abstract void Compile(DatabaseFile databaseFile);
public abstract void Deserialize(BitReader reader);
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using RakDotNet.IO;
namespace InfectedRose.Database
{
public class DatabaseFile : IDeserializable
{
public List<object> Structure { get; set; }
internal FdbTableHeader TableHeader { get; set; }
public void Deserialize(BitReader reader)
{
var tableCount = reader.Read<uint>();
using (new DatabaseScope(reader))
{
TableHeader = new FdbTableHeader(tableCount);
TableHeader.Deserialize(reader);
}
}
/// <summary>
/// Compile the database to a hash-map
/// </summary>
/// <remarks>
/// This is a really long process and should be run in a Task.
/// </remarks>
/// <returns>Compiled database</returns>
public byte[] Compile(Action<int> onData)
{
Structure = new List<object>
{
(uint) TableHeader.Tables.Length,
TableHeader
};
TableHeader.Compile(this);
var fdb = new List<byte>();
var pointers = new List<(DatabaseData, int)>();
int index = default;
foreach (var obj in Structure)
{
onData(++index);
switch (obj)
{
case null:
fdb.AddRange(ToBytes(-1));
break;
case DatabaseData data:
{
var pointer = pointers.Where(p => p.Item1 == data).ToArray();
if (pointer.Any())
{
var bytes = ToBytes(fdb.Count);
foreach (var tuple in pointer)
{
pointers.Remove(tuple);
for (var j = 0; j < 4; j++) fdb[tuple.Item2 + j] = bytes[j];
}
}
else
{
// Save pointers for last
pointers.Add((data, fdb.Count));
// Reserve space for pointer
fdb.AddRange(new byte[4]);
}
break;
}
default:
fdb.AddRange(ToBytes(obj));
break;
}
}
return fdb.ToArray();
}
private static byte[] ToBytes(object obj)
{
if (!obj.GetType().IsValueType)
{
Console.WriteLine($"{obj} is not a valid struct");
return new byte[0];
}
var size = Marshal.SizeOf(obj);
var buf = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(obj, ptr, false);
Marshal.Copy(ptr, buf, 0, size);
return buf;
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using RakDotNet.IO;
namespace InfectedRose.Database
{
internal class DatabaseScope : IDisposable
{
private readonly long _current;
private readonly int _pointer;
private readonly BitReader _reader;
public DatabaseScope(BitReader reader, bool signed = false)
{
_current = reader.BaseStream.Position + 4;
_pointer = signed ? reader.Read<int>() : (int) reader.Read<uint>();
if (_pointer != -1) reader.BaseStream.Position = _pointer;
_reader = reader;
}
public void Dispose()
{
_reader.BaseStream.Position = _current;
}
public static implicit operator bool(DatabaseScope s)
{
return s._pointer != -1;
}
}
}

View File

@@ -0,0 +1,22 @@
using RakDotNet.IO;
namespace InfectedRose.Database
{
internal class FdbBitInt : DatabaseData
{
public long Value { get; set; }
internal override void Compile(DatabaseFile databaseFile)
{
databaseFile.Structure.Add(this);
databaseFile.Structure.Add(Value);
}
public override void Deserialize(BitReader reader)
{
using var s = new DatabaseScope(reader, true);
if (s) Value = reader.Read<long>();
}
}
}

View File

@@ -0,0 +1,41 @@
using RakDotNet.IO;
namespace InfectedRose.Database
{
internal class FdbColumnData : DatabaseData
{
private readonly uint _columnCount;
public FdbColumnData(uint columnCount)
{
_columnCount = columnCount;
}
public (DataType type, FdbString name)[] Fields { get; set; }
internal override void Compile(DatabaseFile databaseFile)
{
databaseFile.Structure.Add(this);
for (var i = 0; i < Fields.Length; i++)
{
databaseFile.Structure.Add((uint) Fields[i].type);
databaseFile.Structure.Add(Fields[i].name);
}
foreach (var s in Fields) s.name.Compile(databaseFile);
}
public override void Deserialize(BitReader reader)
{
Fields = new (DataType type, FdbString name)[_columnCount];
for (var i = 0; i < _columnCount; i++)
{
Fields[i].type = (DataType) reader.Read<uint>();
Fields[i].name = new FdbString();
Fields[i].name.Deserialize(reader);
}
}
}
}

View File

@@ -0,0 +1,41 @@
using RakDotNet.IO;
namespace InfectedRose.Database
{
internal class FdbColumnHeader : DatabaseData
{
public FdbString TableName { get; set; }
public FdbColumnData Data { get; set; }
internal override void Compile(DatabaseFile databaseFile)
{
databaseFile.Structure.Add(this);
databaseFile.Structure.Add((uint) Data.Fields.Length);
databaseFile.Structure.Add(TableName);
databaseFile.Structure.Add(Data);
TableName.Compile(databaseFile);
Data.Compile(databaseFile);
}
public override void Deserialize(BitReader reader)
{
var columnCount = reader.Read<uint>();
TableName = new FdbString();
TableName.Deserialize(reader);
using (new DatabaseScope(reader))
{
Data = new FdbColumnData(columnCount);
Data.Deserialize(reader);
}
}
public override string ToString()
{
return TableName;
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using RakDotNet.IO;
namespace InfectedRose.Database
{
internal class FdbRowBucket : DatabaseData
{
public FdbRowHeader RowHeader { get; set; }
internal override void Compile(DatabaseFile databaseFile)
{
databaseFile.Structure.Add(this);
Console.WriteLine(NextPowerOf2(RowHeader.RowInfos.Length));
databaseFile.Structure.Add(NextPowerOf2(RowHeader.RowInfos.Length));
databaseFile.Structure.Add(RowHeader);
RowHeader?.Compile(databaseFile);
}
public override void Deserialize(BitReader reader)
{
var rowCount = reader.Read<uint>();
using var s = new DatabaseScope(reader, true);
if (!s) return;
RowHeader = new FdbRowHeader(rowCount);
RowHeader.Deserialize(reader);
}
internal static uint NextPowerOf2(int n)
{
var count = 0;
// First n in the below condition
// is for the case where n is 0
if (n > 0 && (n & (n - 1)) == 0)
return (uint) n;
while (n != 0)
{
n >>= 1;
count += 1;
}
return (uint) (1 << count);
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
using RakDotNet.IO;
namespace InfectedRose.Database
{
internal class FdbRowData : DatabaseData
{
public FdbRowData(uint columnCount)
{
Fields = new (DataType, object)[columnCount];
}
public (DataType type, object value)[] Fields { get; set; }
internal override void Compile(DatabaseFile databaseFile)
{
databaseFile.Structure.Add(this);
for (var i = 0; i < Fields.Length; i++)
{
databaseFile.Structure.Add((uint) Fields[i].type);
databaseFile.Structure.Add(Fields[i].value);
}
foreach (var o in Fields)
if (o.value is DatabaseData data)
data.Compile(databaseFile);
}
public override void Deserialize(BitReader reader)
{
for (var i = 0; i < Fields.Length; i++)
{
Fields[i].type = (DataType) reader.Read<uint>();
switch (Fields[i].type)
{
case DataType.Nothing:
Fields[i].value = reader.Read<int>();
break;
case DataType.Integer:
Fields[i].value = reader.Read<int>();
break;
case DataType.Unknown1:
Fields[i].value = reader.Read<int>();
break;
case DataType.Float:
Fields[i].value = reader.Read<float>();
break;
case DataType.Text:
var str = new FdbString();
str.Deserialize(reader);
Fields[i].value = str;
break;
case DataType.Boolean:
Fields[i].value = reader.Read<int>() != 0;
break;
case DataType.Bigint:
var bigInt = new FdbBitInt();
bigInt.Deserialize(reader);
Fields[i].value = bigInt;
break;
case DataType.Unknown2:
Fields[i].value = reader.Read<int>();
break;
case DataType.Varchar:
var str1 = new FdbString();
str1.Deserialize(reader);
Fields[i].value = str1;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
}

View File

@@ -0,0 +1,31 @@
using RakDotNet.IO;
namespace InfectedRose.Database
{
internal class FdbRowDataHeader : DatabaseData
{
public FdbRowData Data { get; set; }
internal override void Compile(DatabaseFile databaseFile)
{
databaseFile.Structure.Add(this);
databaseFile.Structure.Add((uint) Data.Fields.Length);
databaseFile.Structure.Add(Data);
Data?.Compile(databaseFile);
}
public override void Deserialize(BitReader reader)
{
var columnCount = reader.Read<uint>();
using var s = new DatabaseScope(reader, true);
if (!s) return;
Data = new FdbRowData(columnCount);
Data.Deserialize(reader);
}
}
}

View File

@@ -0,0 +1,45 @@
using RakDotNet.IO;
namespace InfectedRose.Database
{
internal class FdbRowHeader : DatabaseData
{
private readonly uint _rowCount;
public FdbRowHeader(uint rowCount)
{
_rowCount = rowCount;
}
public FdbRowInfo[] RowInfos;
internal override void Compile(DatabaseFile databaseFile)
{
databaseFile.Structure.Add(this);
foreach (var rowInfo in RowInfos) databaseFile.Structure.Add(rowInfo);
for (var i = 0; i < FdbRowBucket.NextPowerOf2(RowInfos.Length) - RowInfos.Length; i++)
{
databaseFile.Structure.Add(-1);
}
foreach (var rowInfo in RowInfos) rowInfo?.Compile(databaseFile);
}
public override void Deserialize(BitReader reader)
{
RowInfos = new FdbRowInfo[_rowCount];
for (var i = 0; i < _rowCount; i++)
{
using var s = new DatabaseScope(reader, true);
if (!s) continue;
RowInfos[i] = new FdbRowInfo();
RowInfos[i].Deserialize(reader);
}
}
}
}

View File

@@ -0,0 +1,47 @@
using RakDotNet.IO;
namespace InfectedRose.Database
{
internal class FdbRowInfo : DatabaseData
{
public FdbRowDataHeader DataHeader { get; set; }
public FdbRowInfo Linked { get; set; }
internal override void Compile(DatabaseFile databaseFile)
{
databaseFile.Structure.Add(this);
databaseFile.Structure.Add(DataHeader);
if (Linked == default)
databaseFile.Structure.Add(-1);
else
databaseFile.Structure.Add(Linked);
DataHeader?.Compile(databaseFile);
Linked?.Compile(databaseFile);
}
public override void Deserialize(BitReader reader)
{
using (var s = new DatabaseScope(reader, true))
{
if (s)
{
DataHeader = new FdbRowDataHeader();
DataHeader.Deserialize(reader);
}
}
using (var s = new DatabaseScope(reader, true))
{
if (!s) return;
Linked = new FdbRowInfo();
Linked.Deserialize(reader);
}
}
}
}

View File

@@ -0,0 +1,45 @@
using System.Text;
using RakDotNet.IO;
namespace InfectedRose.Database
{
internal class FdbString : DatabaseData
{
public string Value { get; set; }
public static implicit operator string(FdbString s)
{
return s.Value;
}
public override string ToString()
{
return Value;
}
internal override void Compile(DatabaseFile databaseFile)
{
databaseFile.Structure.Add(this);
foreach (var c in Value) databaseFile.Structure.Add((byte) c);
databaseFile.Structure.Add((byte) 0);
}
public override void Deserialize(BitReader reader)
{
var builder = new StringBuilder();
using (new DatabaseScope(reader))
{
while (true)
{
var c = reader.Read<byte>();
if (c == 0) break;
builder.Append((char) c);
}
}
Value = builder.ToString();
}
}
}

View File

@@ -0,0 +1,53 @@
using RakDotNet.IO;
namespace InfectedRose.Database
{
internal class FdbTableHeader : DatabaseData
{
private readonly uint _tableCount;
public FdbTableHeader(uint tableCount)
{
_tableCount = tableCount;
}
public (FdbColumnHeader info, FdbRowBucket data)[] Tables { get; set; }
internal override void Compile(DatabaseFile databaseFile)
{
databaseFile.Structure.Add(this);
for (var i = 0; i < Tables.Length; i++)
{
databaseFile.Structure.Add(Tables[i].info);
databaseFile.Structure.Add(Tables[i].data);
}
for (var i = 0; i < Tables.Length; i++)
{
Tables[i].data.Compile(databaseFile);
Tables[i].info.Compile(databaseFile);
}
}
public override void Deserialize(BitReader reader)
{
Tables = new (FdbColumnHeader info, FdbRowBucket data)[_tableCount];
for (var i = 0; i < _tableCount; i++)
{
using (new DatabaseScope(reader))
{
Tables[i].info = new FdbColumnHeader();
Tables[i].info.Deserialize(reader);
}
using (new DatabaseScope(reader))
{
Tables[i].data = new FdbRowBucket();
Tables[i].data.Deserialize(reader);
}
}
}
}
}

View File

@@ -0,0 +1,48 @@
namespace InfectedRose.Database
{
public class Field
{
internal FdbRowData Data { get; private set; }
internal int Index { get; private set; }
public DataType Type
{
get => Data.Fields[Index].type;
set
{
var dataField = Data.Fields[Index];
dataField.type = value;
Data.Fields[Index] = dataField;
}
}
public object Value
{
get => Data.Fields[Index].value;
set
{
var dataField = Data.Fields[Index];
value = value switch
{
string str => new FdbString {Value = str},
long lon => new FdbBitInt {Value = lon},
_ => value
};
dataField.value = value;
Data.Fields[Index] = dataField;
}
}
internal Field(FdbRowData data, int index, Table table)
{
Data = data;
Index = index;
}
}
}

View File

@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
namespace InfectedRose.Database.Generic
{
public static class TableExtensions
{
public static TypedTable<T> Typed<T>(this AccessDatabase @this) where T : class
{
var type = typeof(T);
var attribute = type.GetCustomAttribute<TableAttribute>();
var id = attribute?.Name ?? type.Name;
var table = @this[id];
return new TypedTable<T>(table.Info, table.Data);
}
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;
namespace InfectedRose.Database.Generic
{
public class TypedTable<T> : Table where T : class
{
private readonly Dictionary<T, int> _managed;
internal TypedTable(FdbColumnHeader info, FdbRowBucket data) : base(info, data)
{
_managed = new Dictionary<T, int>();
}
public new T this[int index]
{
get
{
if (_managed.ContainsValue(index))
{
return _managed.First(m => m.Value == index).Key;
}
var type = typeof(T);
var instance = (T) Activator.CreateInstance(type, true);
var baseColumn = base[index];
foreach (var property in type.GetProperties())
{
var attribute = property.GetCustomAttribute<ColumnAttribute>();
var id = attribute?.Name ?? property.Name;
var data = baseColumn[id].Value;
property.SetValue(instance, data);
}
_managed[instance] = index;
return instance;
}
}
public new T Create()
{
base.Create(out var index);
return this[index];
}
public void Save()
{
var type = typeof(T);
var properties = type.GetProperties();
foreach (var (column, index) in _managed)
{
var baseColumn = base[index];
foreach (var property in properties)
{
var attribute = property.GetCustomAttribute<ColumnAttribute>();
var id = attribute?.Name ?? property.Name;
var value = property.GetValue(column);
var data = value switch
{
long lon => new FdbBitInt {Value = lon},
string str => new FdbString {Value = str},
_ => value
};
baseColumn[id].Value = data;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\InfectedRose.Core\InfectedRose.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,207 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace InfectedRose.Database
{
public class Table : IList<Column>
{
internal FdbColumnHeader Info { get; private set; }
internal FdbRowBucket Data { get; private set; }
public string Name
{
get => Info.TableName;
set => Info.TableName = new FdbString
{
Value = value
};
}
public TableInfo TableInfo => new TableInfo(this);
internal Table(FdbColumnHeader info, FdbRowBucket data)
{
Info = info;
Data = data;
}
private List<Column> Fields
{
get
{
var columns = new List<Column>();
foreach (var info in Data.RowHeader.RowInfos)
{
var linked = info;
while (linked != default)
{
columns.Add(new Column(linked, this));
linked = linked.Linked;
}
}
return columns;
}
}
public IEnumerator<Column> GetEnumerator()
{
return Fields.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(Column item)
{
if (item == default) return;
var list = Data.RowHeader.RowInfos.ToList();
list.Add(item.Data);
Data.RowHeader.RowInfos = list.ToArray();
}
public void Clear()
{
Data.RowHeader.RowInfos = new FdbRowInfo[0];
}
public bool Contains(Column item)
{
return Fields.Contains(item);
}
public void CopyTo(Column[] array, int arrayIndex)
{
Fields.CopyTo(array, arrayIndex);
}
public bool Remove(Column item)
{
if (item == default) return false;
foreach (var info in Data.RowHeader.RowInfos)
{
var linked = info;
if (linked == item.Data)
{
var fields = Data.RowHeader.RowInfos.ToList();
fields.Remove(linked);
Data.RowHeader.RowInfos = fields.ToArray();
return true;
}
while (linked != default)
{
if (linked.Linked == item.Data)
{
linked.Linked = default;
return true;
}
linked = linked.Linked;
}
}
return false;
}
public int Count => Fields.Count;
public bool IsReadOnly => false;
public int IndexOf(Column item)
{
return Fields.IndexOf(item);
}
public void Insert(int index, Column item)
{
if (item == default) return;
var list = Data.RowHeader.RowInfos.ToList();
list.Insert(index, item.Data);
Data.RowHeader.RowInfos = list.ToArray();
}
public void RemoveAt(int index)
{
Remove(this[index]);
}
public Column this[int index]
{
get => Fields[index];
set => throw new NotSupportedException();
}
public Column Create() => Create(out _);
public Column Create(out int index)
{
var list = Data.RowHeader.RowInfos.ToList();
var column = new FdbRowInfo
{
DataHeader = new FdbRowDataHeader
{
Data = new FdbRowData((uint) Info.Data.Fields.Length)
}
};
for (var i = 0; i < Info.Data.Fields.Length; i++)
{
var type = Info.Data.Fields[i].type;
column.DataHeader.Data.Fields[i] = (type, GetDefault(type));
}
index = list.Count;
list.Add(column);
Data.RowHeader.RowInfos = list.ToArray();
return new Column(column, this);
}
private object GetDefault(DataType type)
{
return type switch
{
DataType.Nothing => (object) 0,
DataType.Integer => 0,
DataType.Unknown1 => 0,
DataType.Float => 0,
DataType.Boolean => 0,
DataType.Unknown2 => 0,
DataType.Varchar => new FdbString(),
DataType.Text => new FdbString(),
DataType.Bigint => new FdbBitInt(),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
public override string ToString()
{
return Name;
}
}
}

View File

@@ -0,0 +1,120 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace InfectedRose.Database
{
public class TableInfo : IList<ColumnInfo>
{
internal Table Table { get; private set; }
internal TableInfo(Table table)
{
Table = table;
}
public IEnumerator<ColumnInfo> GetEnumerator()
{
int index = default;
return Table.Info.Data.Fields.Select(
field => new ColumnInfo(Table, index++)
).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(ColumnInfo item)
{
if (item == default) return;
var list = Table.Info.Data.Fields.ToList();
list.Add((item.Type, new FdbString
{
Value = item.Name
}));
Table.Info.Data.Fields = list.ToArray();
}
public void Clear()
{
Table.Info.Data.Fields = new (DataType type, FdbString name)[0];
}
public bool Contains(ColumnInfo item)
{
return item != default && Table.Info.Data.Fields.Any(f => f.name == item.Name);
}
public void CopyTo(ColumnInfo[] array, int arrayIndex)
{
int index = default;
var list = Table.Info.Data.Fields.Select(field => new ColumnInfo(Table, index++)).ToList();
list.CopyTo(array, arrayIndex);
}
public bool Remove(ColumnInfo item)
{
if (item == default) return false;
var list = Table.Info.Data.Fields.ToList();
list.Remove(list.First(l => l.name == item.Name));
Table.Info.Data.Fields = list.ToArray();
return true;
}
public int Count => Table.Info.Data.Fields.Length;
public bool IsReadOnly => false;
public int IndexOf(ColumnInfo item)
{
return item?.Index ?? -1;
}
public void Insert(int index, ColumnInfo item)
{
if (item == default) return;
var list = Table.Info.Data.Fields.ToList();
list.Insert(index, (item.Type, new FdbString
{
Value = item.Name
}));
Table.Info.Data.Fields = list.ToArray();
}
public void RemoveAt(int index)
{
Remove(this[index]);
}
public ColumnInfo this[int index]
{
get => new ColumnInfo(Table, index);
set
{
var list = Table.Info.Data.Fields.ToList();
list[index] = (value.Type, new FdbString
{
Value = value.Name
});
Table.Info.Data.Fields = list.ToArray();
}
}
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\InfectedRose.Database\InfectedRose.Database.csproj" />
<ProjectReference Include="..\InfectedRose.Luz\InfectedRose.Luz.csproj" />
<ProjectReference Include="..\InfectedRose.Lvl\InfectedRose.Lvl.csproj" />
<ProjectReference Include="..\InfectedRose.Terrain\InfectedRose.Terrain.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,251 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using InfectedRose.Database;
using InfectedRose.Database.Generic;
using InfectedRose.Luz;
using InfectedRose.Lvl;
using InfectedRose.Terrain;
using RakDotNet.IO;
namespace InfectedRose.Examples
{
internal static class Program
{
private static async Task Main(string[] args)
{
Console.Write("Type: ");
if (!int.TryParse(Console.ReadLine(), out var i)) return;
switch (i)
{
case 0:
await Terrain(args);
return;
case 1:
await Luz(args);
return;
case 2:
await Lvl(args);
return;
case 3:
await LvlTest(args);
return;
case 4:
await Database(args);
return;
}
}
private static async Task Database(IReadOnlyList<string> args)
{
string file;
if (args.Count > 0)
{
file = args[0];
}
else
{
Console.Write("File: ");
file = Console.ReadLine();
}
var databaseFile = new DatabaseFile();
await using (var fileStream = File.OpenRead(file))
{
using var reader = new BitReader(fileStream);
databaseFile.Deserialize(reader);
}
var database = new AccessDatabase(databaseFile);
var table = database.Typed<ZoneSummary>();
foreach (var col in table)
{
Console.WriteLine(col["ZoneId"].Value);
}
var newColumn = table.Create();
newColumn.ZoneId = 42;
table.Save();
var margin = 0;
var bytes = databaseFile.Compile(i =>
{
margin++;
if (margin != 100000) return;
margin = default;
Console.WriteLine($"{Math.Round(i / (double) databaseFile.Structure.Count * 100, 2)}%");
});
File.WriteAllBytes($"{file}.new.fdb", bytes);
}
private static async Task Terrain(IReadOnlyList<string> args)
{
string file;
if (args.Count > 0)
{
file = args[0];
}
else
{
Console.Write("File: ");
file = Console.ReadLine();
}
var terrain = new TerrainFile();
await using (var fileStream = File.OpenRead(file))
{
using var reader = new BitReader(fileStream);
terrain.Deserialize(reader);
foreach (var chunk in terrain.Chunks)
{
var data = chunk.HeightMap.Data;
var min = data.Min();
var max = data.Max();
uint randomIndex = default;
for (var i = 0; i < data.Length; i++)
{
if (randomIndex++ == 100)
{
data[i] = max;
}
else if (randomIndex == 200)
{
data[i] = min;
randomIndex = default;
}
}
chunk.HeightMap.Data = data;
}
}
await using var writeStream = File.OpenWrite(file);
using var writer = new BitWriter(writeStream);
terrain.Serialize(writer);
Console.Write("Done");
}
private static async Task Luz(IReadOnlyList<string> args)
{
string file;
if (args.Count > 0)
{
file = args[0];
}
else
{
Console.Write("File: ");
file = Console.ReadLine();
}
var luz = new LuzFile();
await using (var fileStream = File.OpenRead(file))
{
using var reader = new BitReader(fileStream);
luz.Deserialize(reader);
}
await using var writeStream = File.OpenWrite(file);
using var writer = new BitWriter(writeStream);
luz.Serialize(writer);
Console.Write("Done");
}
private static async Task Lvl(IReadOnlyList<string> args)
{
string file;
if (args.Count > 0)
{
file = args[0];
}
else
{
Console.Write("File: ");
file = Console.ReadLine();
}
var lvl = new LvlFile();
await using (var fileStream = File.OpenRead(file))
{
using var reader = new BitReader(fileStream);
lvl.Deserialize(reader);
}
await using var writeStream = File.OpenWrite(file);
using var writer = new BitWriter(writeStream);
lvl.Serialize(writer);
Console.Write("Done");
}
private static async Task LvlTest(IReadOnlyList<string> args)
{
string dir;
if (args.Count > 0)
{
dir = args[0];
}
else
{
Console.Write("Dir: ");
dir = Console.ReadLine();
}
foreach (var file in Directory.GetFiles(dir, "*.lvl", SearchOption.AllDirectories))
{
var lvl = new LvlFile();
await using var fileStream = File.OpenRead(file);
using var reader = new BitReader(fileStream);
Console.Write($"{file} -> ");
lvl.Deserialize(reader);
Console.Write('\n');
}
Console.Write("Done");
}
}
}

View File

@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace InfectedRose.Examples
{
[Table("ZoneSummary")]
public class ZoneSummary
{
[Column("zoneId")] public int ZoneId { get; set; }
[Column("type")] public int Type { get; set; }
[Column("value")] public int Value { get; set; }
[Column("_uniqueID")] public int UniqueId { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
namespace InfectedRose.Luz
{
public enum AchievementRequired
{
None,
Builder,
Craftsman,
SeniorBuilder,
Journeyman,
MasterBuilder,
Architect,
SeniorArchitect,
MasterArchitect,
Visionary,
Exemplar
}
}

View File

@@ -0,0 +1,9 @@
namespace InfectedRose.Luz
{
public enum PathBehavior : uint
{
Loop,
Bounce,
Once
}
}

View File

@@ -0,0 +1,14 @@
namespace InfectedRose.Luz
{
public enum PathType : uint
{
Movement,
MovingPlatform,
Property,
Camera,
Spawner,
Showcase,
Race,
Rail
}
}

View File

@@ -0,0 +1,14 @@
namespace InfectedRose.Luz
{
public enum RentalTimeUnit
{
Forever,
Seconds,
Minutes,
Hours,
Days,
Weeks,
Months,
Years
}
}

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\InfectedRose.Core\InfectedRose.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,39 @@
using System;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzCameraPath : LuzPathData
{
public string NextPath { get; set; }
public bool UnknownBool { get; set; }
public LuzCameraPath(uint version) : base(version)
{
}
public override void Serialize(BitWriter writer)
{
base.Serialize(writer);
writer.WriteNiString(NextPath, true, true);
if (Version >= 14)
writer.Write((byte) (UnknownBool ? 1 : 0));
}
public override void Deserialize(BitReader reader)
{
base.Deserialize(reader);
NextPath = reader.ReadNiString(true, true);
Console.WriteLine(NextPath);
if (Version >= 14)
UnknownBool = reader.Read<byte>() != 0;
}
}
}

View File

@@ -0,0 +1,60 @@
using System.IO;
using System.Numerics;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzCameraWaypoint : LuzPathWaypoint
{
public Quaternion Rotation { get; set; }
public float Time { get; set; }
public float Tension { get; set; }
public float Continuity { get; set; }
public float Bias { get; set; }
public float FieldOfView { get; set; }
public LuzCameraWaypoint(uint version) : base(version)
{
}
public override void Serialize(BitWriter writer)
{
base.Serialize(writer);
writer.WriteNiQuaternion(Rotation);
writer.Write(Time);
writer.Write(FieldOfView);
writer.Write(Tension);
writer.Write(Continuity);
writer.Write(Bias);
}
public override void Deserialize(BitReader reader)
{
base.Deserialize(reader);
Rotation = reader.ReadNiQuaternion();
Time = reader.Read<float>();
FieldOfView = reader.Read<float>();
Tension = reader.Read<float>();
Continuity = reader.Read<float>();
Bias = reader.Read<float>();
}
}
}

239
InfectedRose.Luz/LuzFile.cs Normal file
View File

@@ -0,0 +1,239 @@
using System;
using System.IO;
using System.Numerics;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzFile : IConstruct
{
public uint Version { get; set; }
public uint RevisionNumber { get; set; }
public uint WorldId { get; set; }
public Vector3 SpawnPoint { get; set; }
public Quaternion SpawnRotation { get; set; }
public LuzScene[] Scenes { get; set; }
public byte UnknownByte { get; set; }
public string TerrainFileName { get; set; }
public string TerrainFile { get; set; }
public string TerrainDescription { get; set; }
public LuzSceneTransition[] Transitions { get; set; }
public LuzPathData[] PathData { get; set; }
//public LvlFile[] LvlFiles { get; set; }
public void Serialize(BitWriter writer)
{
writer.Write(Version);
if (Version >= 0x24)
{
writer.Write(RevisionNumber);
}
writer.Write(WorldId);
if (Version >= 0x26)
{
writer.Write(SpawnPoint);
writer.Write(SpawnRotation);
}
if (Version < 0x25)
{
writer.Write((byte) Scenes.Length);
}
else
{
writer.Write((uint) Scenes.Length);
}
foreach (var scene in Scenes)
{
scene.Serialize(writer);
}
writer.Write(UnknownByte);
writer.WriteNiString(TerrainFileName, false, true);
writer.WriteNiString(TerrainFile, false, true);
writer.WriteNiString(TerrainDescription, false, true);
if (Version >= 20)
{
writer.Write((uint) Transitions.Length);
foreach (var transition in Transitions)
{
transition.Serialize(writer);
}
}
if (Version < 0x23) return;
var position = writer.BaseStream.Position;
WritePaths(writer);
var finishPosition = writer.BaseStream.Position;
writer.BaseStream.Position = position;
writer.Write(finishPosition - position - 4);
}
private void WritePaths(BitWriter writer)
{
writer.BaseStream.Position += 4;
writer.Write((uint) 1);
writer.Write((uint) PathData.Length);
foreach (var pathData in PathData)
{
writer.Write(pathData.Version);
writer.WriteNiString(pathData.PathName, true, true);
writer.Write((uint) pathData.Type);
writer.Write(pathData.UnknownInt);
pathData.Serialize(writer);
writer.Write((uint) pathData.Waypoints.Length);
foreach (var waypoint in pathData.Waypoints)
{
waypoint.Serialize(writer);
}
}
}
public void Deserialize(BitReader reader)
{
Version = reader.Read<uint>();
if (Version >= 0x24)
{
RevisionNumber = reader.Read<uint>();
}
WorldId = reader.Read<uint>();
if (Version >= 0x26)
{
SpawnPoint = reader.Read<Vector3>();
SpawnRotation = reader.Read<Quaternion>();
}
var sceneCount = Version < 0x25 ? reader.Read<byte>() : reader.Read<uint>();
Scenes = new LuzScene[sceneCount];
//LvlFiles = new LvlFile[sceneCount];
for (var i = 0; i < sceneCount; i++)
{
Scenes[i] = new LuzScene();
Scenes[i].Deserialize(reader);
//LvlFiles[i] = new LvlFile($"{Path.GetDirectoryName(path)}/{Scenes[i].FileName}");
}
UnknownByte = reader.Read<byte>();
TerrainFileName = reader.ReadNiString(false, true);
TerrainFile = reader.ReadNiString(false, true);
TerrainDescription = reader.ReadNiString(false, true);
if (Version >= 0x20)
{
var sceneTransitionCount = reader.Read<uint>();
Transitions = new LuzSceneTransition[sceneTransitionCount];
for (var i = 0; i < sceneTransitionCount; i++)
{
Transitions[i] = new LuzSceneTransition(Version);
Transitions[i].Deserialize(reader);
}
}
if (Version < 0x23) return;
{
Console.WriteLine(reader.Read<uint>());
reader.Read<uint>();
var pathDataCount = reader.Read<uint>();
PathData = new LuzPathData[pathDataCount];
for (var i = 0; i < pathDataCount; i++)
{
var version = reader.Read<uint>();
var name = reader.ReadNiString(true, true);
var type = (PathType) reader.Read<uint>();
Console.WriteLine($"[{i}:{name}] -> [{type}]");
PathData[i] = type switch
{
PathType.Movement => new LuzPathData(version),
PathType.MovingPlatform => new LuzMovingPlatformPath(version),
PathType.Property => new LuzPropertyPath(version),
PathType.Camera => new LuzCameraPath(version),
PathType.Spawner => new LuzSpawnerPath(version),
PathType.Showcase => new LuzPathData(version),
PathType.Race => new LuzPathData(version),
PathType.Rail => new LuzPathData(version),
_ => throw new ArgumentOutOfRangeException()
};
PathData[i].UnknownInt = reader.Read<uint>();
PathData[i].PathName = name;
PathData[i].Type = type;
PathData[i].Deserialize(reader);
var count = reader.Read<uint>();
PathData[i].Waypoints = new LuzPathWaypoint[count];
for (var j = 0; j < count; j++)
{
PathData[i].Waypoints[j] = type switch
{
PathType.Movement => new LuzMovementWaypoint(version),
PathType.MovingPlatform => new LuzMovingPlatformWaypoint(version),
PathType.Property => new LuzPathWaypoint(version),
PathType.Camera => new LuzCameraWaypoint(version),
PathType.Spawner => new LuzSpawnerWaypoint(version),
PathType.Showcase => new LuzPathWaypoint(version),
PathType.Race => new LuzRaceWaypoint(version),
PathType.Rail => new LuzRailWaypoint(version),
_ => throw new ArgumentOutOfRangeException()
};
PathData[i].Waypoints[j].Deserialize(reader);
}
}
}
}
}
}

View File

@@ -0,0 +1,40 @@
using System.IO;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzMovementWaypoint : LuzPathWaypoint
{
public LuzPathConfig[] Configs { get; set; }
public LuzMovementWaypoint(uint version) : base(version)
{
}
public override void Serialize(BitWriter writer)
{
base.Serialize(writer);
writer.Write((uint) Configs.Length);
foreach (var config in Configs)
{
config.Serialize(writer);
}
}
public override void Deserialize(BitReader reader)
{
base.Deserialize(reader);
var configCount = reader.Read<uint>();
Configs = new LuzPathConfig[configCount];
for (var i = 0; i < configCount; i++)
{
Configs[i] = new LuzPathConfig();
Configs[i].Deserialize(reader);
}
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.IO;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzMovingPlatformPath : LuzPathData
{
public string MovingPlatformSound { get; set; }
public bool TimeBased { get; set; }
public LuzMovingPlatformPath(uint version) : base(version)
{
}
public override void Serialize(BitWriter writer)
{
base.Serialize(writer);
if (Version >= 18)
writer.Write((byte) (TimeBased ? 1 : 0));
else if (Version >= 13)
writer.WriteNiString(MovingPlatformSound, true, true);
}
public override void Deserialize(BitReader reader)
{
base.Deserialize(reader);
if (Version >= 18)
TimeBased = reader.Read<byte>() != 0;
else if (Version >= 13)
MovingPlatformSound = reader.ReadNiString(true, true);
}
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.IO;
using System.Numerics;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzMovingPlatformWaypoint : LuzPathWaypoint
{
public Quaternion Rotation { get; set; }
public bool LockPlayer { get; set; }
public float Speed { get; set; }
public float Wait { get; set; }
public string DepartSound { get; set; }
public string ArriveSound { get; set; }
public LuzMovingPlatformWaypoint(uint version) : base(version)
{
}
public override void Serialize(BitWriter writer)
{
base.Serialize(writer);
writer.WriteNiQuaternion(Rotation);
writer.Write((byte) (LockPlayer ? 1 : 0));
writer.Write(Speed);
writer.Write(Wait);
if (Version < 13) return;
writer.WriteNiString(DepartSound, true, true);
writer.WriteNiString(ArriveSound, true, true);
}
public override void Deserialize(BitReader reader)
{
base.Deserialize(reader);
Rotation = reader.ReadNiQuaternion();
LockPlayer = reader.Read<byte>() != 0;
Speed = reader.Read<float>();
Wait = reader.Read<float>();
if (Version < 13) return;
DepartSound = reader.ReadNiString(true, true);
ArriveSound = reader.ReadNiString(true, true);
Console.WriteLine($"{DepartSound} -> {ArriveSound}");
}
}
}

View File

@@ -0,0 +1,25 @@
using System.IO;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzPathConfig : IConstruct
{
public string ConfigName { get; set; }
public string ConfigTypeAndValue { get; set; }
public void Serialize(BitWriter writer)
{
writer.WriteNiString(ConfigName, true, true);
writer.WriteNiString(ConfigTypeAndValue, true, true);
}
public void Deserialize(BitReader reader)
{
ConfigName = reader.ReadNiString(true, true);
ConfigTypeAndValue = reader.ReadNiString(true, true);
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.IO;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
[Serializable]
public class LuzPathData : IConstruct
{
public uint Version { get; set; }
public string PathName { get; set; }
public uint UnknownInt { get; set; }
public PathType Type { get; set; }
public PathBehavior Behavior { get; set; }
public LuzPathWaypoint[] Waypoints { get; set; }
public LuzPathData(uint version)
{
Version = version;
}
public virtual void Serialize(BitWriter writer)
{
writer.Write((uint) Behavior);
}
public virtual void Deserialize(BitReader reader)
{
Behavior = (PathBehavior) reader.Read<uint>();
}
}
}

View File

@@ -0,0 +1,29 @@
using System.IO;
using System.Numerics;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzPathWaypoint : IConstruct
{
public Vector3 Position { get; set; }
public uint Version { get; set; }
public LuzPathWaypoint(uint version)
{
Version = version;
}
public virtual void Serialize(BitWriter writer)
{
writer.Write(Position);
}
public virtual void Deserialize(BitReader reader)
{
Position = reader.Read<Vector3>();
}
}
}

View File

@@ -0,0 +1,102 @@
using System.IO;
using System.Numerics;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzPropertyPath : LuzPathData
{
public int UnknownInt0 { get; set; }
public int UnknownInt1 { get; set; }
public int Price { get; set; }
public int RentalTime { get; set; }
public ulong AssociatedZone { get; set; }
public string DisplayName { get; set; }
public string DisplayDescription { get; set; }
public int CloneLimit { get; set; }
public float ReputationMultiplier { get; set; }
public RentalTimeUnit TimeUnit { get; set; }
public AchievementRequired Achievement { get; set; }
public Vector3 PlayerZonePoint { get; set; }
public float MaxBuildHeight { get; set; }
public LuzPropertyPath(uint version) : base(version)
{
}
public override void Serialize(BitWriter writer)
{
base.Serialize(writer);
writer.Write(UnknownInt0);
writer.Write(Price);
writer.Write(RentalTime);
writer.Write(AssociatedZone);
writer.WriteNiString(DisplayName, true, true);
writer.WriteNiString(DisplayDescription, true);
writer.Write(UnknownInt1);
writer.Write(CloneLimit);
writer.Write(ReputationMultiplier);
writer.Write((int) TimeUnit);
writer.Write((int) Achievement);
writer.Write(PlayerZonePoint);
writer.Write(MaxBuildHeight);
}
public override void Deserialize(BitReader reader)
{
base.Deserialize(reader);
UnknownInt0 = reader.Read<int>();
Price = reader.Read<int>();
RentalTime = reader.Read<int>();
AssociatedZone = reader.Read<ulong>();
DisplayName = reader.ReadNiString(true, true);
DisplayDescription = reader.ReadNiString(true);
UnknownInt1 = reader.Read<int>();
CloneLimit = reader.Read<int>();
ReputationMultiplier = reader.Read<float>();
TimeUnit = (RentalTimeUnit) reader.Read<int>();
Achievement = (AchievementRequired) reader.Read<int>();
PlayerZonePoint = reader.Read<Vector3>();
MaxBuildHeight = reader.Read<float>();
}
}
}

View File

@@ -0,0 +1,42 @@
using System.IO;
using System.Numerics;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzRaceWaypoint : LuzPathWaypoint
{
public Quaternion Rotation { get; set; }
public byte[] UnknownBytes { get; set; }
public Vector3 UnknownVector { get; set; }
public LuzRaceWaypoint(uint version) : base(version)
{
}
public override void Serialize(BitWriter writer)
{
base.Serialize(writer);
writer.WriteNiQuaternion(Rotation);
writer.Write(UnknownBytes);
writer.Write(UnknownVector);
}
public override void Deserialize(BitReader reader)
{
base.Deserialize(reader);
Rotation = reader.ReadNiQuaternion();
UnknownBytes = reader.ReadBuffer(2);
UnknownVector = reader.Read<Vector3>();
}
}
}

View File

@@ -0,0 +1,60 @@
using System.IO;
using System.Numerics;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzRailWaypoint : LuzPathWaypoint
{
public LuzPathConfig[] Configs { get; set; }
public Quaternion UnknownQuaternion { get; set; }
public float Speed { get; set; }
public LuzRailWaypoint(uint version) : base(version)
{
}
public override void Serialize(BitWriter writer)
{
base.Serialize(writer);
writer.WriteNiQuaternion(UnknownQuaternion);
if (Version >= 17)
{
writer.Write(Speed);
}
writer.Write((uint) Configs.Length);
foreach (var config in Configs)
{
config.Serialize(writer);
}
}
public override void Deserialize(BitReader reader)
{
base.Deserialize(reader);
UnknownQuaternion = reader.ReadNiQuaternion();
if (Version >= 17)
{
Speed = reader.Read<float>();
}
var configCount = reader.Read<uint>();
Configs = new LuzPathConfig[configCount];
for (var i = 0; i < configCount; i++)
{
Configs[i] = new LuzPathConfig();
Configs[i].Deserialize(reader);
}
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.IO;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
[Serializable]
public class LuzScene : IConstruct
{
public string FileName { get; set; }
public byte SceneId { get; set; }
public bool IsAudioScene { get; set; }
public string SceneName { get; set; }
public byte[] UnknownByteArray0 { get; set; } = new byte[3];
public byte[] UnknownByteArray1 { get; set; } = new byte[3];
public byte[] UnknownByteArray2 { get; set; } = new byte[3];
public void Serialize(BitWriter writer)
{
writer.WriteNiString(FileName, false, true);
writer.Write(SceneId);
writer.Write(UnknownByteArray0);
writer.Write((byte) (IsAudioScene ? 1 : 0));
writer.Write(UnknownByteArray1);
writer.WriteNiString(SceneName, false, true);
writer.Write(UnknownByteArray2);
}
public void Deserialize(BitReader reader)
{
FileName = reader.ReadNiString(false, true);
SceneId = reader.Read<byte>();
UnknownByteArray0 = reader.ReadBuffer(3);
IsAudioScene = reader.Read<byte>() != 0;
UnknownByteArray1 = reader.ReadBuffer(3);
SceneName = reader.ReadNiString(false, true);
UnknownByteArray2 = reader.ReadBuffer(3);
}
public override string ToString()
{
return FileName;
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.IO;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
[Serializable]
public class LuzSceneTransition : IConstruct
{
public string SceneTransitionName { get; set; }
public LuzSceneTransitionPoint[] TransitionPoints { get; set; }
public uint Version { get; set; }
public LuzSceneTransition(uint version)
{
Version = version;
}
public void Serialize(BitWriter writer)
{
if (Version < 0x25)
{
writer.WriteNiString(SceneTransitionName, false, true);
}
foreach (var transitionPoint in TransitionPoints)
{
transitionPoint.Serialize(writer);
}
}
public void Deserialize(BitReader reader)
{
if (Version < 0x25)
{
SceneTransitionName = reader.ReadNiString(false, true);
}
var pointCount = Version <= 21 || Version >= 0x27 ? 2 : 5;
TransitionPoints = new LuzSceneTransitionPoint[pointCount];
for (var i = 0; i < pointCount; i++)
{
TransitionPoints[i] = new LuzSceneTransitionPoint();
TransitionPoints[i].Deserialize(reader);
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.IO;
using System.Numerics;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
[Serializable]
public class LuzSceneTransitionPoint : IConstruct
{
public ulong SceneId { get; set; }
public Vector3 Point { get; set; }
public void Serialize(BitWriter writer)
{
writer.Write(SceneId);
writer.Write(Point);
}
public void Deserialize(BitReader reader)
{
SceneId = reader.Read<ulong>();
Point = reader.Read<Vector3>();
}
}
}

View File

@@ -0,0 +1,60 @@
using System.IO;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzSpawnerPath : LuzPathData
{
public uint SpawnedLot { get; set; }
public uint RespawnTime { get; set; }
public int MaxSpawnCount { get; set; }
public uint NumberToMaintain { get; set; }
public long SpawnerObjectId { get; set; }
public bool ActivateSpawnerNetworkOnLoad { get; set; }
public LuzSpawnerPath(uint version) : base(version)
{
}
public override void Serialize(BitWriter writer)
{
base.Serialize(writer);
writer.Write(SpawnedLot);
writer.Write(RespawnTime);
writer.Write(MaxSpawnCount);
writer.Write(NumberToMaintain);
writer.Write(SpawnerObjectId);
if (Version > 8)
writer.Write((byte) (ActivateSpawnerNetworkOnLoad ? 1 : 0));
}
public override void Deserialize(BitReader reader)
{
base.Deserialize(reader);
SpawnedLot = reader.Read<uint>();
RespawnTime = reader.Read<uint>();
MaxSpawnCount = reader.Read<int>();
NumberToMaintain = reader.Read<uint>();
SpawnerObjectId = reader.Read<long>();
if (Version > 8)
ActivateSpawnerNetworkOnLoad = reader.Read<byte>() != 0;
}
}
}

View File

@@ -0,0 +1,48 @@
using System.Numerics;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Luz
{
public class LuzSpawnerWaypoint : LuzPathWaypoint
{
public Quaternion Rotation { get; set; }
public LuzPathConfig[] Configs { get; set; }
public LuzSpawnerWaypoint(uint version) : base(version)
{
}
public override void Serialize(BitWriter writer)
{
base.Serialize(writer);
writer.WriteNiQuaternion(Quaternion.Identity);
writer.Write((uint) Configs.Length);
foreach (var config in Configs)
{
config.Serialize(writer);
}
}
public override void Deserialize(BitReader reader)
{
base.Deserialize(reader);
Rotation = reader.ReadNiQuaternion();
var configCount = reader.Read<uint>();
Configs = new LuzPathConfig[configCount];
for (var i = 0; i < configCount; i++)
{
Configs[i] = new LuzPathConfig();
Configs[i].Deserialize(reader);
}
}
}
}

View File

@@ -0,0 +1,16 @@
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Lvl
{
public abstract class ChunkBase : IConstruct
{
public ushort Index { get; set; }
public abstract uint ChunkType { get; }
public abstract void Serialize(BitWriter writer);
public abstract void Deserialize(BitReader reader);
}
}

View File

@@ -0,0 +1,33 @@
using System.IO;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Lvl
{
public class IdStruct : IConstruct
{
public uint Id { get; set; }
public float UnknownFloat0 { get; set; }
public float UnknownFloat1 { get; set; }
public void Serialize(BitWriter writer)
{
writer.Write(Id);
writer.Write(UnknownFloat0);
writer.Write(UnknownFloat1);
}
public void Deserialize(BitReader reader)
{
Id = reader.Read<uint>();
UnknownFloat0 = reader.Read<float>();
UnknownFloat1 = reader.Read<float>();
}
}
}

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\InfectedRose.Core\InfectedRose.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,234 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Text;
namespace Lvl
{
public class LegoDataDictionary : IDictionary<string, object>
{
public const char InfoSeparator = '\u001F';
private readonly Dictionary<string, (byte, object)> _map;
public int Count => _map.Count;
public bool IsReadOnly => false;
public ICollection<string> Keys => _map.Keys;
public ICollection<object> Values => _map.Values.Select(v => v.Item2).ToArray();
public object this[string key]
{
get => _map[key].Item2;
set => Add(key, value);
}
public object this[string key, byte type]
{
get => _map[key].Item2;
set => Add(key, value, type);
}
public LegoDataDictionary()
{
_map = new Dictionary<string, (byte, object)>();
}
public void Add(string key, object value, byte type)
=> _map[key] = (type, value);
public void Add(string key, object value)
{
var type =
value is int ? 1 :
value is float ? 3 :
value is double ? 4 :
value is uint ? 5 :
value is bool ? 7 :
value is long ? 8 :
value is byte[] ? 13 : 0;
Add(key, value, (byte) type);
}
public void Add(KeyValuePair<string, object> item)
=> Add(item.Key, item.Value);
public void Clear()
=> _map.Clear();
public bool ContainsKey(string key)
=> _map.ContainsKey(key);
public bool Contains(KeyValuePair<string, object> item)
=> _map.ContainsKey(item.Key) && _map[item.Key].Item2 == item.Value;
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public bool Remove(string key)
=> _map.Remove(key);
public bool Remove(KeyValuePair<string, object> item)
=> _map.Remove(item.Key);
public bool TryGetValue(string key, out object value)
{
if (_map.TryGetValue(key, out var temp))
{
value = temp.Item2;
return true;
}
value = null;
return false;
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
foreach (var k in _map)
{
yield return new KeyValuePair<string, object>(k.Key, k.Value.Item2);
}
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public override string ToString()
=> ToString("\r\n");
public string ToString(string separator)
{
var str = new StringBuilder();
foreach (var k in _map)
{
string val;
switch (k.Value.Item2)
{
case Vector2 vec2:
val = $"{vec2.X}{InfoSeparator}{vec2.Y}";
break;
case Vector3 vec3:
val = $"{vec3.X}{InfoSeparator}{vec3.Z}{InfoSeparator}{vec3.Y}";
break;
case Vector4 vec4:
val = $"{vec4.X}{InfoSeparator}{vec4.Z}{InfoSeparator}{vec4.Y}{InfoSeparator}{vec4.W}";
break;
case LegoDataList list:
val = list.ToString();
break;
default:
val = k.Value.Item2.ToString();
break;
}
str.Append($"{k}={k.Value.Item1}:{val}");
var i = _map.Keys.ToList().IndexOf(k.Key);
if (i + 1 < Count)
str.Append(separator);
}
return str.ToString();
}
public static LegoDataDictionary FromDictionary<T>(Dictionary<string, T> dict)
{
var ldd = new LegoDataDictionary();
foreach (var k in dict)
{
ldd[k.Key] = k.Value;
}
return ldd;
}
public static LegoDataDictionary FromString(string text, char separator = '\n')
{
var dict = new LegoDataDictionary();
var lines = text.Replace("\r", "").Split(separator);
foreach (var line in lines)
{
var firstEqual = line.IndexOf('=');
var firstColon = line.IndexOf(':');
var key = line.Substring(0, firstEqual);
var type = int.Parse(line.Substring(firstEqual + 1, firstColon - firstEqual - 1));
var val = line.Substring(firstColon + 1);
object v;
switch (type)
{
case 1:
case 2:
v = int.Parse(val);
break;
case 3:
v = float.Parse(val, CultureInfo.InvariantCulture);
break;
case 4:
v = double.Parse(val);
break;
case 5:
case 6:
v = uint.Parse(val);
break;
case 7:
v = int.Parse(val) == 1;
break;
case 8:
case 9:
v = long.Parse(val);
break;
default:
if (val.Contains('+'))
{
v = LegoDataList.FromString(val);
}
else if (val.Contains('\u001F'))
{
var floats = val.Split('\u001F').Select(s => float.Parse(s, CultureInfo.InvariantCulture)).ToArray();
v =
floats.Length == 1 ? floats[0] :
floats.Length == 2 ? new Vector2(floats[0], floats[1]) :
floats.Length == 3 ? new Vector3(floats[0], floats[1], floats[2]) :
floats.Length == 4 ? new Vector4(floats[0], floats[1], floats[2], floats[3]) :
(object) val;
}
else
{
v = val;
}
break;
}
dict[key, (byte) type] = v;
}
return dict;
}
}
}

View File

@@ -0,0 +1,222 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Text;
namespace Lvl
{
public class LegoDataList : IList<object>
{
public const char InfoSeparator = '\u001F';
private readonly List<(byte, object)> _list;
public int Count => _list.Count;
public bool IsReadOnly => false;
public object this[int index]
{
get => _list[index].Item2;
set => Insert(index, value);
}
public object this[int index, byte type]
{
get => _list[index].Item2;
set => Insert(index, value, type);
}
public LegoDataList()
{
_list = new List<(byte, object)>();
}
public void Add(object item, byte type)
=> _list.Add((type, item));
public void Add(object item)
{
var type =
item is int ? 1 :
item is float ? 3 :
item is double ? 4 :
item is uint ? 5 :
item is bool ? 7 :
item is long ? 8 :
item is byte[] ? 13 : 0;
Add(item, (byte) type);
}
public void Insert(int index, object item, byte type)
=> _list.Insert(index, (type, item));
public void Insert(int index, object item)
{
var type =
item is int ? 1 :
item is float ? 3 :
item is double ? 4 :
item is uint ? 5 :
item is bool ? 7 :
item is long ? 8 :
item is byte[] ? 13 : 0;
Insert(index, item, (byte) type);
}
public void Clear()
=> _list.Clear();
public bool Contains(object item)
=> _list.Exists(i => i.Item2 == item);
public void RemoveAt(int index)
=> _list.RemoveAt(index);
public bool Remove(object item)
=> _list.Remove(_list.Find(i => i.Item2 == item));
public int IndexOf(object item)
=> _list.FindIndex(i => i.Item2 == item);
public void CopyTo(object[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public IEnumerator<object> GetEnumerator()
{
foreach (var (_, v) in _list) yield return v;
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public override string ToString()
=> ToString("+");
public string ToString(string separator)
{
var str = new StringBuilder();
foreach (var (t, v) in _list)
{
string val;
switch (v)
{
case Vector2 vec2:
val = $"{vec2.X}{InfoSeparator}{vec2.Y}";
break;
case Vector3 vec3:
val = $"{vec3.X}{InfoSeparator}{vec3.Z}{InfoSeparator}{vec3.Y}";
break;
case Vector4 vec4:
val = $"{vec4.X}{InfoSeparator}{vec4.Z}{InfoSeparator}{vec4.Y}{InfoSeparator}{vec4.W}";
break;
case LegoDataList list:
val = list.ToString();
break;
default:
val = v.ToString();
break;
}
str.Append($"{t}:{val}");
if (IndexOf(v) + 1 < Count)
str.Append(separator);
}
return str.ToString();
}
public static LegoDataList FromEnumerable<T>(IEnumerable<T> list)
{
var ldl = new LegoDataList();
foreach (var item in list) ldl.Add(item);
return ldl;
}
public static LegoDataList FromString(string text)
{
var list = new LegoDataList();
var entries = text.Split('+');
for (var i = 0; i < entries.Length; i++)
{
var entry = entries[i];
var firstColon = entry.IndexOf(':');
var type = int.Parse(entry.Substring(0, firstColon));
var val = entry.Substring(firstColon + 1);
object v = null;
switch (type)
{
case 1:
case 2:
v = int.Parse(val);
break;
case 3:
v = float.Parse(val, CultureInfo.InvariantCulture);
break;
case 4:
v = double.Parse(val);
break;
case 5:
case 6:
v = uint.Parse(val);
break;
case 7:
v = int.Parse(val) == 1;
break;
case 8:
case 9:
v = long.Parse(val);
break;
default:
if (val.Contains('+'))
{
v = FromString(val);
}
else if (val.Contains('\u001F'))
{
var floats = val.Split('\u001F').Select(s => float.Parse(s, CultureInfo.InvariantCulture)).ToArray();
v =
floats.Length == 1 ? floats[0] :
floats.Length == 2 ? new Vector2(floats[0], floats[1]) :
floats.Length == 3 ? new Vector3(floats[0], floats[1], floats[2]) :
floats.Length == 4 ? new Vector4(floats[0], floats[1], floats[2], floats[3]) :
(object) val;
}
else
{
v = val;
}
break;
}
list[i, (byte) type] = v;
}
return list;
}
}
}

View File

@@ -0,0 +1,35 @@
using System.IO;
using RakDotNet.IO;
namespace InfectedRose.Lvl
{
public class LevelEnvironmentConfig : ChunkBase
{
public ParticleStruct[] ParticleStructs { get; set; }
public override uint ChunkType => 2002;
public override void Serialize(BitWriter writer)
{
writer.Write((uint) ParticleStructs.Length);
foreach (var particleStruct in ParticleStructs)
{
particleStruct.Serialize(writer);
}
}
public override void Deserialize(BitReader reader)
{
ParticleStructs = new ParticleStruct[reader.Read<uint>()];
for (var i = 0; i < ParticleStructs.Length; i++)
{
var particle = new ParticleStruct();
particle.Deserialize(reader);
ParticleStructs[i] = particle;
}
}
}
}

View File

@@ -0,0 +1,46 @@
using System.IO;
using RakDotNet.IO;
namespace InfectedRose.Lvl
{
public class LevelInfo : ChunkBase
{
public uint LvlVersion { get; set; }
public uint RevisionNumber { get; set; }
public uint AddressChunk2000 { get; set; }
public uint AddressChunk2001 { get; set; }
public uint AddressChunk2002 { get; set; }
public override uint ChunkType => 1000;
public override void Serialize(BitWriter writer)
{
writer.Write(LvlVersion);
writer.Write(RevisionNumber);
writer.Write(AddressChunk2000);
writer.Write(AddressChunk2001);
writer.Write(AddressChunk2002);
}
public override void Deserialize(BitReader reader)
{
LvlVersion = reader.Read<uint>();
RevisionNumber = reader.Read<uint>();
AddressChunk2000 = reader.Read<uint>();
AddressChunk2001 = reader.Read<uint>();
AddressChunk2002 = reader.Read<uint>();
}
}
}

View File

@@ -0,0 +1,84 @@
using System.IO;
using System.Numerics;
using InfectedRose.Core;
using Lvl;
using RakDotNet.IO;
namespace InfectedRose.Lvl
{
public class LevelObjectTemplate : IConstruct
{
public ulong ObjectId { get; set; }
public int Lot { get; set; }
public uint AssetType { get; set; }
public uint UnknownInt { get; set; }
public Vector3 Position { get; set; }
public Quaternion Rotation { get; set; }
public float Scale { get; set; }
public LegoDataDictionary LegoInfo { get; set; }
public uint LvlVersion { get; set; }
public LevelObjectTemplate(uint lvlVersion)
{
LvlVersion = lvlVersion;
}
public void Serialize(BitWriter writer)
{
writer.Write(ObjectId);
writer.Write(Lot);
if (LvlVersion >= 0x26)
writer.Write(AssetType);
if (LvlVersion >= 0x20)
writer.Write(UnknownInt);
writer.Write(Position);
writer.WriteNiQuaternion(Rotation);
writer.Write(Scale);
writer.WriteNiString(LegoInfo.ToString(), true);
if (LvlVersion >= 0x7)
writer.Write(0u);
}
public void Deserialize(BitReader reader)
{
ObjectId = reader.Read<ulong>();
Lot = reader.Read<int>();
if (LvlVersion >= 0x26)
AssetType = reader.Read<uint>();
if (LvlVersion >= 0x20)
UnknownInt = reader.Read<uint>();
Position = reader.Read<Vector3>();
Rotation = reader.ReadNiQuaternion();
Scale = reader.Read<float>();
var legoInfo = reader.ReadNiString(true);
LegoInfo = LegoDataDictionary.FromString(legoInfo);
if (LvlVersion >= 0x7)
reader.Read<uint>();
}
}
}

View File

@@ -0,0 +1,42 @@
using System.IO;
using RakDotNet.IO;
namespace InfectedRose.Lvl
{
public class LevelObjects : ChunkBase
{
public LevelObjectTemplate[] Templates { get; set; }
public uint LvlVersion { get; set; }
public override uint ChunkType => 2001;
public LevelObjects(uint lvlVersion)
{
LvlVersion = lvlVersion;
}
public override void Serialize(BitWriter writer)
{
writer.Write((uint) Templates.Length);
foreach (var template in Templates)
{
template.Serialize(writer);
}
}
public override void Deserialize(BitReader reader)
{
Templates = new LevelObjectTemplate[reader.Read<uint>()];
for (var i = 0; i < Templates.Length; i++)
{
var template = new LevelObjectTemplate(LvlVersion);
template.Deserialize(reader);
Templates[i] = template;
}
}
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.IO;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Lvl
{
public class LevelSkyConfig : ChunkBase
{
public IdStruct[] Identifiers { get; set; }
public string[] SkyFilesPaths { get; set; } = new string[6];
public float[] UnknownFloatArray0 { get; set; }
public float[] UnknownFloatArray1 { get; set; } = new float[3];
public float[] UnknownFloatArray2 { get; set; } = new float[3];
public byte[] UnknownSectionData { get; set; }
public override uint ChunkType => 2000;
public override void Serialize(BitWriter writer)
{
writer.Write(8);
// TODO: Add UnknownFloatArray0?
var addressPosition = writer.BaseStream.Position;
writer.BaseStream.Position += 8;
writer.Write((uint) Identifiers.Length);
foreach (var identifier in Identifiers)
{
identifier.Serialize(writer);
}
for (var i = 0; i < 3; i++)
{
writer.Write(UnknownFloatArray1[i]);
}
for (var i = 0; i < 3; i++)
{
writer.Write(UnknownFloatArray2[i]);
}
var skySectionAddress = writer.BaseStream.Position;
writer.Write(WriteSkySection());
var otherSectionAddress = writer.BaseStream.Position;
writer.Write(WriteOtherSection());
writer.BaseStream.Position = addressPosition;
writer.Write((uint) skySectionAddress);
writer.Write((uint) otherSectionAddress);
writer.BaseStream.Position = otherSectionAddress + 4;
}
private byte[] WriteSkySection()
{
using var stream = new MemoryStream();
using var writer = new BitWriter(stream);
for (var i = 0; i < 6; i++)
{
writer.WriteNiString(SkyFilesPaths[i]);
}
return stream.ToArray();
}
private byte[] WriteOtherSection()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream);
writer.Write(0u);
return stream.ToArray();
}
public override void Deserialize(BitReader reader)
{
var sizeOfData = reader.Read<uint>();
var skySectionAddress = reader.Read<uint>();
var otherSectionAddress = reader.Read<uint>();
UnknownFloatArray0 = new float[(sizeOfData - 8) / 4];
for (var i = 0; i < UnknownFloatArray0.Length; i++)
{
UnknownFloatArray0[i] = reader.Read<float>();
}
Identifiers = new IdStruct[reader.Read<uint>()];
for (var i = 0; i < Identifiers.Length; i++)
{
var idStruct = new IdStruct();
idStruct.Deserialize(reader);
Identifiers[i] = idStruct;
}
for (var i = 0; i < 3; i++)
{
UnknownFloatArray1[i] = reader.Read<float>();
}
for (var i = 0; i < 3; i++)
{
UnknownFloatArray2[i] = reader.Read<float>();
}
reader.BaseStream.Position = skySectionAddress;
for (var i = 0; i < 6; i++)
{
SkyFilesPaths[i] = reader.ReadNiString();
}
reader.BaseStream.Position = otherSectionAddress;
UnknownSectionData = reader.ReadBuffer(reader.Read<uint>());
}
}
}

141
InfectedRose.Lvl/LvlFile.cs Normal file
View File

@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Lvl
{
public class LvlFile : IConstruct
{
public uint LvlVersion { get; set; }
public LevelInfo LevelInfo;
public LevelSkyConfig LevelSkyConfig;
public LevelObjects LevelObjects;
public LevelEnvironmentConfig LevelEnvironmentConfig;
private static readonly byte[] ChunkHeader = "CHNK".Select(c => (byte) c).ToArray();
public void Serialize(BitWriter writer)
{
var levelInfoData = WriteChunk(LevelInfo);
var levelSkyData = WriteChunk(LevelSkyConfig);
var levelObjectsData = WriteChunk(LevelObjects);
var levelEnvData = WriteChunk(LevelEnvironmentConfig);
Console.WriteLine($"{levelInfoData.Length} - {levelSkyData.Length} - {levelObjectsData.Length} - {levelEnvData.Length}");
}
private static byte[] WriteChunk(IConstruct chunk)
{
using var stream = new MemoryStream();
using var writer = new BitWriter(stream);
chunk.Serialize(writer);
return stream.ToArray();
}
public void Deserialize(BitReader reader)
{
var magic = new string(reader.ReadBuffer(4).Select(s => (char) s).ToArray());
if (magic != "CHNK")
{
Console.WriteLine(new NotImplementedException("Older LVL files not yet supported."));
return;
}
reader.BaseStream.Position = 0;
while (reader.BaseStream.Position != reader.BaseStream.Length)
{
var startPosition = reader.BaseStream.Position;
magic = new string(reader.ReadBuffer(4).Select(s => (char) s).ToArray());
if (startPosition % 16 != 0 || magic != "CHNK")
{
throw new Exception($"{startPosition} % 16 = {startPosition % 16} | {magic}");
}
var chunkType = reader.Read<uint>();
reader.Read<ushort>();
var index = reader.Read<ushort>();
var chunkLength = reader.Read<uint>();
var chunkAddress = reader.Read<uint>();
reader.BaseStream.Position = chunkAddress;
if (reader.BaseStream.Position % 16 != 0) break;
switch (chunkType)
{
case 1000:
var chunk1000 = new LevelInfo
{
Index = index
};
chunk1000.Deserialize(reader);
LvlVersion = chunk1000.LvlVersion;
LevelInfo = chunk1000;
break;
case 2000:
var chunk2000 = new LevelSkyConfig
{
Index = index
};
chunk2000.Deserialize(reader);
LevelSkyConfig = chunk2000;
break;
case 2001:
var chunk2001 = new LevelObjects(LvlVersion)
{
Index = index
};
chunk2001.Deserialize(reader);
LevelObjects = chunk2001;
break;
case 2002:
var chunk2002 = new LevelEnvironmentConfig
{
Index = index
};
chunk2002.Deserialize(reader);
LevelEnvironmentConfig = chunk2002;
break;
default:
throw new ArgumentOutOfRangeException($"{chunkType} is not a valid chunk type.");
}
while (reader.BaseStream.Position != startPosition + chunkLength)
{
reader.Read<byte>();
}
reader.BaseStream.Position = startPosition + chunkLength;
Console.WriteLine($"[END] -> {reader.BaseStream.Position}");
}
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Numerics;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Lvl
{
public class ParticleStruct : IConstruct
{
public byte[] UnknownByteArray0 { get; set; } = new byte[2];
public Vector3 Position { get; set; }
public Quaternion Rotation { get; set; }
public string ParticleName { get; set; }
public byte[] UnknownByteArray1 { get; set; } = new byte[4];
public void Serialize(BitWriter writer)
{
writer.Write(UnknownByteArray0);
writer.Write(Position);
writer.WriteNiQuaternion(Rotation);
writer.WriteNiString(ParticleName, true);
writer.Write(UnknownByteArray1);
}
public void Deserialize(BitReader reader)
{
UnknownByteArray0 = reader.ReadBuffer(2);
Position = reader.Read<Vector3>();
Rotation = reader.ReadNiQuaternion();
var position = reader.BaseStream.Position;
try
{
ParticleName = reader.ReadNiString(true);
Console.WriteLine(ParticleName);
}
catch
{
reader.BaseStream.Position = position + 4;
}
UnknownByteArray1 = reader.ReadBuffer(4);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace InfectedRose.Nif
{
public class BlockInfo
{
public ushort TypeIndex { get; set; }
public uint Size { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace InfectedRose.Nif
{
public enum Endian : byte
{
EndianBig,
EndianLittle
}
}

View File

@@ -0,0 +1,170 @@
namespace InfectedRose.Nif
{
public enum NifVersion : uint
{
/// <summary>
/// The ve R_2_3
/// </summary>
Ver23 = 33751040u,
/// <summary>
/// The ve R_3_0
/// </summary>
Ver30 = 50331648u,
/// <summary>
/// The ve R_3_03
/// </summary>
Ver303 = 50332416u,
/// <summary>
/// The ve R_3_1
/// </summary>
Ver31 = 50397184u,
/// <summary>
/// The ve R_3_3_0_13
/// </summary>
Ver33013 = 50528269u,
/// <summary>
/// The ve R_4_0_0_0
/// </summary>
Ver4000 = 67108864u,
/// <summary>
/// The ve R_4_0_0_2
/// </summary>
Ver4002 = 67108866u,
/// <summary>
/// The ve R_4_1_0_12
/// </summary>
Ver4101 = 0x04010001,
/// <summary>
/// The ve R_4_1_0_12
/// </summary>
Ver41012 = 67174412u,
/// <summary>
/// The ve R_4_2_0_2
/// </summary>
Ver4202 = 67239938u,
/// <summary>
/// The ve R_4_2_1_0
/// </summary>
Ver4210 = 67240192u,
/// <summary>
/// The ve R_4_2_2_0
/// </summary>
Ver4220 = 67240448u,
/// <summary>
/// The ve R_5_0_0_1
/// </summary>
Ver5001 = 83886081u,
/// <summary>
/// The ve R_10_0_1_0
/// </summary>
Ver10010 = 167772416u,
/// <summary>
/// The ve R_10_0_1_2
/// </summary>
Ver10012 = 167772418u,
/// <summary>
/// The ve R_10_0_1_3
/// </summary>
Ver10013,
/// <summary>
/// The ve R_10_1_0_0
/// </summary>
Ver10100 = 167837696u,
/// <summary>
/// The ve R_10_1_0_101
/// </summary>
Ver1010101 = 167837797u,
/// <summary>
/// The ve R_10_1_0_106
/// </summary>
Ver1010106 = 167837802u,
/// <summary>
/// The ve R_10_2_0_0
/// </summary>
Ver10200 = 167903232u,
/// <summary>
/// The ve R_10_4_0_1
/// </summary>
Ver10401 = 168034305u,
/// <summary>
/// The ve R_20_0_0_4
/// </summary>
Ver20004 = 335544324u,
/// <summary>
/// The ve R_20_0_0_5
/// </summary>
Ver20005,
/// <summary>
/// The ve R_20_1_0_3
/// </summary>
Ver20103 = 335609859u,
/// <summary>
/// The ve R_20_2_0_7
/// </summary>
Ver20207 = 335675399u,
/// <summary>
/// The ve R_20_2_0_8
/// </summary>
Ver20208,
/// <summary>
/// The ve R_20_3_0_1
/// </summary>
Ver20301 = 335740929u,
/// <summary>
/// The ve R_20_3_0_2
/// </summary>
Ver20302,
/// <summary>
/// The ve R_20_3_0_3
/// </summary>
Ver20303,
/// <summary>
/// The ve R_20_3_0_6
/// </summary>
Ver20306 = 335740934u,
/// <summary>
/// The ve R_20_3_0_9
/// </summary>
Ver20309 = 335740937u, // LEGO Universe
/// <summary>
/// The ve r_ unsupported
/// </summary>
VerUnsupported = 4294967295u,
/// <summary>
/// The ve r_ invalid
/// </summary>
VerInvalid = 4294967294u
}
}

View File

@@ -0,0 +1,82 @@
using System.Collections.Generic;
using System.Text;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Nif
{
public class Header : IConstruct
{
public string HeaderString { get; set; }
public NifVersion Version { get; set; }
public Endian Endian { get; set; }
public string VersionString { get; set; }
public uint UserVersion { get; set; }
public BlockInfo[] NodeInfo { get; set; }
public NifString[] NodeTypes { get; set; }
public uint MaxStringLength { get; set; }
public uint[] Groups { get; set; }
public void Serialize(BitWriter writer)
{
throw new System.NotImplementedException();
}
public void Deserialize(BitReader reader)
{
var versionStringBuilder = new StringBuilder();
var character = reader.Read<byte>();
while (character != 0xA)
{
versionStringBuilder.Append((char) character);
character = reader.Read<byte>();
}
VersionString = versionStringBuilder.ToString();
reader.Read<byte>();
Version = (NifVersion) reader.Read<uint>();
Endian = (Endian) reader.Read<byte>();
UserVersion = reader.Read<uint>();
NodeInfo = new BlockInfo[reader.Read<uint>()];
NodeTypes = new NifString[reader.Read<uint>()];
for (var i = 0; i < NodeTypes.Length; i++)
{
var str = new NifString();
str.Deserialize(reader);
NodeTypes[i] = str;
}
for (var i = 0; i < NodeInfo.Length; i++)
{
NodeInfo[i] = new BlockInfo
{
TypeIndex = reader.Read<ushort>()
};
}
foreach (var info in NodeInfo)
{
info.Size = reader.Read<uint>();
}
}
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\InfectedRose.Core\InfectedRose.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Nodes" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Nif
{
public class NifFile : IConstruct
{
public void Serialize(BitWriter writer)
{
throw new System.NotImplementedException();
}
public void Deserialize(BitReader reader)
{
throw new System.NotImplementedException();
}
}
}

View File

@@ -0,0 +1,40 @@
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Nif
{
public class NifString : IConstruct
{
public string Value { get; set; }
public bool Wide { get; set; }
public bool Small { get; set; }
public void Serialize(BitWriter writer)
{
if (Small) writer.Write((byte) Value.Length);
else writer.Write((uint) Value.Length);
foreach (var character in Value)
{
if (Wide) writer.Write((ushort) character);
else writer.Write((byte) character);
}
}
public void Deserialize(BitReader reader)
{
var length = Small ? reader.Read<byte>() : reader.Read<uint>();
var chars = new char[length];
for (var i = 0; i < length; i++)
{
chars[i] = (char) (Wide ? reader.Read<ushort>() : reader.Read<byte>());
}
Value = new string(chars);
}
}
}

View File

@@ -0,0 +1,134 @@
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Terrain
{
public class Chunk : IConstruct
{
public int ChunkIndex { get; set; }
public HeightMap HeightMap { get; set; }
public ColorMap Colormap0 { get; set; }
public TerrainDirectDraw Lightmap { get; set; }
public ColorMap Colormap1 { get; set; }
public byte UnknownByte { get; set; }
public TerrainDirectDraw Blendmap { get; set; }
public WeirdStruct[] WeirdStructs { get; set; }
public byte[] UnknownByteArray0 { get; set; }
public ShortMap ShortMap { get; set; }
public short[][] UnknownShortArray { get; set; }
public byte[] UnknownByteArray1 { get; set; }
public void Serialize(BitWriter writer)
{
writer.Write(ChunkIndex);
HeightMap.Serialize(writer);
Colormap0.Serialize(writer);
Lightmap.Serialize(writer);
Colormap1.Serialize(writer);
writer.Write(UnknownByte);
Blendmap.Serialize(writer);
writer.Write(WeirdStructs.Length);
foreach (var weirdStruct in WeirdStructs)
{
weirdStruct.Serialize(writer);
}
foreach (var unknownByte in UnknownByteArray0)
{
writer.Write(unknownByte);
}
ShortMap.Serialize(writer);
if (ShortMap.Data.Length == default) return;
for (var i = 0; i < 32; i++)
{
writer.Write(UnknownByteArray1[i]);
}
for (var i = 0; i < 16; i++)
{
writer.Write((short) UnknownShortArray[i].Length);
for (var j = 0; j < UnknownShortArray[i].Length; j++)
{
writer.Write(UnknownShortArray[i][j]);
}
}
}
public void Deserialize(BitReader reader)
{
ChunkIndex = reader.Read<int>();
HeightMap = new HeightMap();
HeightMap.Deserialize(reader);
Colormap0 = new ColorMap();
Colormap0.Deserialize(reader);
Lightmap = new TerrainDirectDraw();
Lightmap.Deserialize(reader);
Colormap1 = new ColorMap();
Colormap1.Deserialize(reader);
UnknownByte = reader.Read<byte>();
Blendmap = new TerrainDirectDraw();
Blendmap.Deserialize(reader);
WeirdStructs = new WeirdStruct[reader.Read<int>()];
for (var i = 0; i < WeirdStructs.Length; i++)
{
var weirdStruct = new WeirdStruct();
weirdStruct.Deserialize(reader);
WeirdStructs[i] = weirdStruct;
}
UnknownByteArray0 = reader.ReadBuffer((uint) (Colormap0.Width * Colormap0.Height));
ShortMap = new ShortMap();
ShortMap.Deserialize(reader);
if (ShortMap.Data.Length == default) return;
UnknownByteArray1 = reader.ReadBuffer(32);
UnknownShortArray = new short[16][];
for (var i = 0; i < 16; i++)
{
var length = reader.Read<short>();
UnknownShortArray[i] = new short[length];
for (var j = 0; j < length; j++)
{
UnknownShortArray[i][j] = reader.Read<short>();
}
}
}
}
}

View File

@@ -0,0 +1,41 @@
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Terrain
{
public class ColorMap : IConstruct
{
public int Width { get; set; }
public int Height { get; set; }
public uint[] Data { get; set; }
public uint GetValue(int x, int y)
{
return Data[y * Width + x];
}
public void Serialize(BitWriter writer)
{
writer.Write(Width);
for (var i = 0; i < Width * Height; i++)
{
writer.Write(Data[i]);
}
}
public void Deserialize(BitReader reader)
{
Width = Height = reader.Read<int>();
Data = new uint[Width * Height];
for (var i = 0; i < Data.Length; i++)
{
Data[i] = reader.Read<uint>();
}
}
}
}

View File

@@ -0,0 +1,66 @@
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Terrain
{
public class HeightMap : IConstruct
{
public int Width { get; set; }
public int Height { get; set; }
public float UnknownFloat0 { get; set; }
public float UnknownFloat1 { get; set; }
public float UnknownFloat2 { get; set; }
public int[] UnknownIntArray { get; set; }
public float[] Data { get; set; }
public void Serialize(BitWriter writer)
{
writer.Write(Width);
writer.Write(Height);
writer.Write(UnknownFloat0);
writer.Write(UnknownFloat1);
for (var i = 0; i < 4; i++)
{
writer.Write(UnknownIntArray[i]);
}
writer.Write(UnknownFloat2);
for (var i = 0; i < Width * Height; i++)
{
writer.Write(Data[i]);
}
}
public void Deserialize(BitReader reader)
{
Width = reader.Read<int>();
Height = reader.Read<int>();
UnknownFloat0 = reader.Read<float>();
UnknownFloat1 = reader.Read<float>();
UnknownIntArray = new int[4];
for (var i = 0; i < 4; i++)
{
UnknownIntArray[i] = reader.Read<int>();
}
UnknownFloat2 = reader.Read<float>();
Data = new float[Width * Height];
for (var i = 0; i < Data.Length; i++)
{
Data[i] = reader.Read<float>();
}
}
}
}

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\InfectedRose.Core\InfectedRose.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,30 @@
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Terrain
{
public class ShortMap : IConstruct
{
public short[] Data { get; set; }
public void Serialize(BitWriter writer)
{
writer.Write(Data.Length);
foreach (var data in Data)
{
writer.Write(data);
}
}
public void Deserialize(BitReader reader)
{
Data = new short[reader.Read<int>()];
for (var i = 0; i < Data.Length; i++)
{
Data[i] = reader.Read<short>();
}
}
}
}

View File

@@ -0,0 +1,25 @@
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Terrain
{
public class TerrainDirectDraw : IConstruct
{
public byte[] Data { get; set; }
public void Serialize(BitWriter writer)
{
writer.Write(Data.Length);
foreach (var data in Data)
{
writer.Write(data);
}
}
public void Deserialize(BitReader reader)
{
Data = reader.ReadBuffer((uint) reader.Read<int>());
}
}
}

View File

@@ -0,0 +1,7 @@
namespace InfectedRose.Terrain
{
public class TerrainEditor
{
}
}

View File

@@ -0,0 +1,52 @@
using System.Collections.Generic;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Terrain
{
public class TerrainFile : IConstruct
{
public List<Chunk> Chunks;
public int ChunkTotalCount { get; set; }
public int ChunkCountX { get; set; }
public int ChunkCountY { get; set; }
public byte[] UnknownHeader { get; set; }
public void Serialize(BitWriter writer)
{
writer.Write(UnknownHeader);
writer.Write(ChunkTotalCount);
writer.Write(ChunkCountX);
writer.Write(ChunkCountY);
foreach (var chunk in Chunks)
{
chunk.Serialize(writer);
}
}
public void Deserialize(BitReader reader)
{
Chunks = new List<Chunk>();
UnknownHeader = reader.ReadBuffer(3);
ChunkTotalCount = reader.Read<int>();
ChunkCountX = reader.Read<int>();
ChunkCountY = reader.Read<int>();
for (var i = 0; i < ChunkTotalCount; i++)
{
var chunk = new Chunk();
chunk.Deserialize(reader);
Chunks.Add(chunk);
}
}
}
}

View File

@@ -0,0 +1,51 @@
using System.Numerics;
using InfectedRose.Core;
using RakDotNet.IO;
namespace InfectedRose.Terrain
{
public class WeirdStruct : IConstruct
{
public int Type { get; set; }
public Vector3 Position { get; set; }
public Quaternion Rotation { get; set; }
public uint UnknownInt { get; set; }
public void Serialize(BitWriter writer)
{
writer.Write(Type);
writer.Write(Rotation.W);
writer.Write(Position);
writer.Write(Rotation.X);
writer.Write(Rotation.Y);
writer.Write(Rotation.Z);
writer.Write(UnknownInt);
}
public void Deserialize(BitReader reader)
{
Type = reader.Read<int>();
var rotW = reader.Read<float>();
Position = reader.Read<Vector3>();
Rotation = new Quaternion
{
X = reader.Read<float>(),
Y = reader.Read<float>(),
Z = reader.Read<float>(),
W = rotW
};
UnknownInt = reader.Read<uint>();
}
}
}

58
InfectedRose.sln Normal file
View File

@@ -0,0 +1,58 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Terrain", "InfectedRose.Terrain\InfectedRose.Terrain.csproj", "{C5948668-31F0-457F-A1D7-A32DB85A6A5D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Examples", "InfectedRose.Examples\InfectedRose.Examples.csproj", "{135A3A1D-3C81-45AB-9195-DEB44D2581F4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Core", "InfectedRose.Core\InfectedRose.Core.csproj", "{2D0A3B97-ACED-487E-AF18-F6E81627D0F6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Luz", "InfectedRose.Luz\InfectedRose.Luz.csproj", "{0C38A6FC-7EF9-4B97-B767-6A2F40962D29}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Lvl", "InfectedRose.Lvl\InfectedRose.Lvl.csproj", "{8BC6E972-A4B6-4D79-9948-A3265FAAF2FC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Database", "InfectedRose.Database\InfectedRose.Database.csproj", "{94A2F951-BA5B-4C51-9EC5-C909D97D30A3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RakDotNet.IO", "RakDotNet.IO\RakDotNet.IO\RakDotNet.IO.csproj", "{C5BEDCE5-382D-4963-B68F-F098A9EB3CA8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InfectedRose.Nif", "InfectedRose.Nif\InfectedRose.Nif.csproj", "{BDCD4970-7BFF-4C69-B866-F022583D9E18}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C5948668-31F0-457F-A1D7-A32DB85A6A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5948668-31F0-457F-A1D7-A32DB85A6A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5948668-31F0-457F-A1D7-A32DB85A6A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5948668-31F0-457F-A1D7-A32DB85A6A5D}.Release|Any CPU.Build.0 = Release|Any CPU
{135A3A1D-3C81-45AB-9195-DEB44D2581F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{135A3A1D-3C81-45AB-9195-DEB44D2581F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{135A3A1D-3C81-45AB-9195-DEB44D2581F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{135A3A1D-3C81-45AB-9195-DEB44D2581F4}.Release|Any CPU.Build.0 = Release|Any CPU
{2D0A3B97-ACED-487E-AF18-F6E81627D0F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D0A3B97-ACED-487E-AF18-F6E81627D0F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D0A3B97-ACED-487E-AF18-F6E81627D0F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D0A3B97-ACED-487E-AF18-F6E81627D0F6}.Release|Any CPU.Build.0 = Release|Any CPU
{0C38A6FC-7EF9-4B97-B767-6A2F40962D29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C38A6FC-7EF9-4B97-B767-6A2F40962D29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C38A6FC-7EF9-4B97-B767-6A2F40962D29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C38A6FC-7EF9-4B97-B767-6A2F40962D29}.Release|Any CPU.Build.0 = Release|Any CPU
{8BC6E972-A4B6-4D79-9948-A3265FAAF2FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8BC6E972-A4B6-4D79-9948-A3265FAAF2FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BC6E972-A4B6-4D79-9948-A3265FAAF2FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BC6E972-A4B6-4D79-9948-A3265FAAF2FC}.Release|Any CPU.Build.0 = Release|Any CPU
{94A2F951-BA5B-4C51-9EC5-C909D97D30A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94A2F951-BA5B-4C51-9EC5-C909D97D30A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94A2F951-BA5B-4C51-9EC5-C909D97D30A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94A2F951-BA5B-4C51-9EC5-C909D97D30A3}.Release|Any CPU.Build.0 = Release|Any CPU
{C5BEDCE5-382D-4963-B68F-F098A9EB3CA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5BEDCE5-382D-4963-B68F-F098A9EB3CA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5BEDCE5-382D-4963-B68F-F098A9EB3CA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5BEDCE5-382D-4963-B68F-F098A9EB3CA8}.Release|Any CPU.Build.0 = Release|Any CPU
{BDCD4970-7BFF-4C69-B866-F022583D9E18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BDCD4970-7BFF-4C69-B866-F022583D9E18}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDCD4970-7BFF-4C69-B866-F022583D9E18}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BDCD4970-7BFF-4C69-B866-F022583D9E18}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal