mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-04-30 16:31:46 -04:00
Added FSO.Files for use with the API server
Don't ask me. FreeSO is the prime example of dependency hell.
This commit is contained in:
parent
4b5e584eeb
commit
8fec258215
104 changed files with 14653 additions and 163 deletions
158
CMakeLists.txt
158
CMakeLists.txt
|
@ -1,158 +0,0 @@
|
||||||
#########################################
|
|
||||||
#### CMake generator file for Niotso ####
|
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 2.6)
|
|
||||||
|
|
||||||
enable_language(ASM)
|
|
||||||
set(CMAKE_C_COMPILER "gcc")
|
|
||||||
set(CMAKE_CXX_COMPILER "gcc")
|
|
||||||
set(CMAKE_ASM_COMPILER "gcc")
|
|
||||||
|
|
||||||
project(Niotso)
|
|
||||||
|
|
||||||
# Installation directory
|
|
||||||
if(WIN32)
|
|
||||||
set(CMAKE_INSTALL_PREFIX "c:/Program Files (x86)/Maxis/The Sims Online/Niotso" CACHE FILEPATH "Installation directory")
|
|
||||||
else()
|
|
||||||
set(CMAKE_INSTALL_PREFIX "/usr/bin" CACHE FILEPATH "Installation directory")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Build type
|
|
||||||
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
|
||||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build configurations: Release Debug Release-MakeProfile Release-UseProfile")
|
|
||||||
else()
|
|
||||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build configurations: Release Debug")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
set(64BIT 0)
|
|
||||||
else()
|
|
||||||
set(64BIT 1)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
|
||||||
# Base options
|
|
||||||
set(CFLAGS "-Wall -Wextra -Wabi -pedantic -mmmx -msse -msse2 -msse3 -fvisibility=hidden")
|
|
||||||
set(LDFLAGS "-static-libgcc")
|
|
||||||
set(RCFLAGS "")
|
|
||||||
|
|
||||||
set(CFLAGS_LANG_C "-ansi")
|
|
||||||
set(CFLAGS_LANG_CPP "-fvisibility-inlines-hidden -fno-exceptions -fno-rtti -fno-threadsafe-statics -D__STDC_LIMIT_MACROS")
|
|
||||||
|
|
||||||
if(64BIT)
|
|
||||||
set(CFLAGS "-m64 ${CFLAGS}")
|
|
||||||
set(LDFLAGS "-m64 ${LDFLAGS}")
|
|
||||||
set(RCFLAGS "${RCFLAGS} -F pe-x86-64")
|
|
||||||
else()
|
|
||||||
set(CFLAGS "-m32 ${CFLAGS}")
|
|
||||||
set(LDFLAGS "-m32 ${LDFLAGS}")
|
|
||||||
set(RCFLAGS "${RCFLAGS} -F pe-i386")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
####
|
|
||||||
## [Profiles]
|
|
||||||
|
|
||||||
if(NOT (CMAKE_BUILD_TYPE MATCHES "Debug"))
|
|
||||||
if(CMAKE_BUILD_TYPE MATCHES "Release-MakeProfile")
|
|
||||||
set(CFLAGS "${CFLAGS} -fprofile-generate")
|
|
||||||
set(LDFLAGS "${LDFLAGS} -lgcov")
|
|
||||||
elseif(CMAKE_BUILD_TYPE MATCHES "Release-UseProfile")
|
|
||||||
set(CFLAGS "${CFLAGS} -fprofile-use")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Size
|
|
||||||
set(CFLAGS_SIZE "${CFLAGS} -Os -g0 -fomit-frame-pointer -mfpmath=both -msahf -malign-double -mpc32 -ffast-math -fmerge-all-constants -funsafe-loop-optimizations -fsched-pressure -mstringop-strategy=rep_byte -fno-stack-protector")
|
|
||||||
set(LDFLAGS_SIZE "${LDFLAGS} -s -fwhole-program -flto -fno-stack-protector")
|
|
||||||
|
|
||||||
# Speed
|
|
||||||
set(CFLAGS_SPEED "${CFLAGS} -O3 -g0 -fomit-frame-pointer -mfpmath=both -msahf -malign-double -mpc32 -ffast-math -fmerge-all-constants -funsafe-loop-optimizations -fsched-pressure -fno-stack-protector -fmodulo-sched -fmodulo-sched-allow-regmoves -fgcse-sm -fgcse-las -fsched-spec-load -fsched-spec-load-dangerous -fsched-stalled-insns=0 -fsched-stalled-insns-dep -fsched2-use-superblocks -fipa-pta -fipa-matrix-reorg -ftree-loop-linear -floop-interchange -floop-strip-mine -floop-block -fgraphite-identity -floop-parallelize-all -ftree-loop-distribution -ftree-loop-im -ftree-loop-ivcanon -fivopts -fvect-cost-model -fvariable-expansion-in-unroller -fbranch-target-load-optimize -maccumulate-outgoing-args -flto")
|
|
||||||
set(LDFLAGS_SPEED "${LDFLAGS} -s -fwhole-program -flto -fno-stack-protector")
|
|
||||||
else()
|
|
||||||
# Debug
|
|
||||||
set(CFLAGS_DEBUG "${CFLAGS} -O0 -g3 -fstack-protector-all -D_FORTIFY_SOURCE=2 -DDEBUG")
|
|
||||||
set(LDFLAGS_DEBUG "${LDFLAGS} -fstack-protector-all")
|
|
||||||
set(CFLAGS_SIZE "${CFLAGS_DEBUG}")
|
|
||||||
set(LDFLAGS_SIZE "${LDFLAGS_DEBUG}")
|
|
||||||
set(CFLAGS_SPEED "${CFLAGS_DEBUG}")
|
|
||||||
set(LDFLAGS_SPEED "${LDFLAGS_DEBUG}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(CMAKE_C_FLAGS "${CFLAGS_LANG_C} ${CFLAGS_SIZE}")
|
|
||||||
set(CMAKE_CXX_FLAGS "${CFLAGS_LANG_CPP} ${CFLAGS_SIZE}")
|
|
||||||
if(64BIT)
|
|
||||||
set(CMAKE_SHARED_LIBRARY_C_FLAGS "-fpic")
|
|
||||||
set(CMAKE_SHARED_LIBRARY_CXX_FLAGS "-fpic")
|
|
||||||
set(CMAKE_SHARED_LIBRARY_ASM_FLAGS "-fpic")
|
|
||||||
else()
|
|
||||||
set(CMAKE_SHARED_LIBRARY_C_FLAGS "")
|
|
||||||
set(CMAKE_SHARED_LIBRARY_CXX_FLAGS "")
|
|
||||||
set(CMAKE_SHARED_LIBRARY_ASM_FLAGS "")
|
|
||||||
endif()
|
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS "-shared ${LDFLAGS} ${LDFLAGS_SIZE}")
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${LDFLAGS} ${LDFLAGS_SIZE}")
|
|
||||||
set(CMAKE_RC_FLAGS "${RCFLAGS}")
|
|
||||||
set(CMAKE_ASM_FLAGS "${CFLAGS}")
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
set(DIST_NAME "windows" CACHE STRING "Output folder name for the _dist folder (no start or end slash)")
|
|
||||||
elseif(APPLE)
|
|
||||||
set(DIST_NAME "mac" CACHE STRING "Output folder name for the _dist folder (no start or end slash)")
|
|
||||||
elseif(UNIX)
|
|
||||||
set(DIST_NAME "linux" CACHE STRING "Output folder name for the _dist folder (no start or end slash)")
|
|
||||||
else()
|
|
||||||
set(DIST_NAME "unknown" CACHE STRING "Output folder name for the _dist folder (no start or end slash)")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
endif()
|
|
||||||
|
|
||||||
#set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/_dist/${DIST_NAME}") (-flto means our archive files should not be redistributed)
|
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/_dist/${DIST_NAME}")
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/_dist/${DIST_NAME}")
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
set(FREETYPE_INCLUDE ${CMAKE_SOURCE_DIR}/_deps/freetype/include ${CMAKE_SOURCE_DIR}/_deps/freetype/include/freetype/config)
|
|
||||||
set(LIBJPEGTURBO_INCLUDE ${CMAKE_SOURCE_DIR}/_deps/libjpeg-turbo)
|
|
||||||
set(LIBMPG123_INCLUDE ${CMAKE_SOURCE_DIR}/_deps/libmpg123)
|
|
||||||
set(LIBPNG_INCLUDE ${CMAKE_SOURCE_DIR}/_deps/libpng)
|
|
||||||
set(LIBPQ_INCLUDE ${CMAKE_SOURCE_DIR}/_deps/libpq ${CMAKE_SOURCE_DIR}/_deps/libpq/include ${CMAKE_SOURCE_DIR}/_deps/libpq/include/port/win32 ${CMAKE_SOURCE_DIR}/_deps/libpq/include/port)
|
|
||||||
set(ZLIB_INCLUDE ${CMAKE_SOURCE_DIR}/_deps/zlib)
|
|
||||||
|
|
||||||
add_subdirectory(_deps/freetype)
|
|
||||||
add_subdirectory(_deps/libjpeg-turbo)
|
|
||||||
add_subdirectory(_deps/libmpg123)
|
|
||||||
add_subdirectory(_deps/libpng)
|
|
||||||
add_subdirectory(_deps/libpq)
|
|
||||||
add_subdirectory(_deps/zlib)
|
|
||||||
|
|
||||||
set(FREETYPE_LINK freetype_shared)
|
|
||||||
set(LIBJPEG_LINK jpegturbo_static)
|
|
||||||
set(LIBMPG123_LINK libmpg123_static)
|
|
||||||
set(LIBPNG_LINK libpng_static)
|
|
||||||
set(LIBPQ_LINK libpq_shared)
|
|
||||||
set(ZLIB_LINK zlib_static)
|
|
||||||
else()
|
|
||||||
set(FREETYPE_LINK freetype)
|
|
||||||
set(LIBJPEG_LINK jpeg)
|
|
||||||
set(LIBMPG123_LINK mpg123)
|
|
||||||
set(LIBPNG_LINK png)
|
|
||||||
set(LIBPQ_LINK pq)
|
|
||||||
set(ZLIB_LINK z)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(FILEHANDLER_INCLUDE ${CMAKE_SOURCE_DIR}/library/FileHandler)
|
|
||||||
set(LIBGLDEMO_INCLUDE ${CMAKE_SOURCE_DIR}/library/libgldemo)
|
|
||||||
set(LIBVITABOY_INCLUDE ${CMAKE_SOURCE_DIR}/library/libvitaboy)
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
set(GLDEMO_EXE WIN32)
|
|
||||||
set(GLDEMO_LINK mingw32 libgldemo_static opengl32 glu32)
|
|
||||||
else()
|
|
||||||
set(GLDEMO_EXE "")
|
|
||||||
set(GLDEMO_LINK libgldemo_static Xxf86vm rt Xext X11 GL GLU)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_subdirectory(Client)
|
|
||||||
add_subdirectory(library)
|
|
||||||
add_subdirectory(Server)
|
|
||||||
add_subdirectory(Tools)
|
|
|
@ -18,6 +18,8 @@ Will this succeed? *I have no idea*. I'm not much of a game developer, but that
|
||||||
|
|
||||||
- [ ] Rewrite header files
|
- [ ] Rewrite header files
|
||||||
|
|
||||||
|
- [ ] Communicate with FreeSO-based API server
|
||||||
|
|
||||||
- [ ] Write Vitaboy renderer in Zig
|
- [ ] Write Vitaboy renderer in Zig
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
zTSO uses FreeSO's API architecture for the backend. However, zTSO is not 100% compatible with FreeSO due to differences in how the simulation is managed. Instructions for setting up an API server are available in the appropriate directories. zTSO's API is based on FreeSO `beta-update-88b`.
|
zTSO uses FreeSO's API architecture for the backend. However, zTSO is not 100% compatible with FreeSO due to differences in how the simulation is managed. Instructions for setting up an API server are available in the appropriate directories. zTSO's API is based on FreeSO `beta-update-88b`.
|
||||||
|
|
||||||
To maintain compatibility with legacy servers, zTSO follows a slightly altered version scheme that adheres to SemVer guidelines. Instead of `beta/update-88b`, it becomes `beta/update-0.88.101`. Prefixed letters are replaced with a three-digit incremental patch number, akin to .NET's SDK versions. This approach aims to ensure that zTSO servers cannot access FreeSO's servers while remaining familiar to existing users and server operators.
|
To maintain compatibility with legacy servers, zTSO follows a slightly altered version scheme that adheres to SemVer guidelines. Instead of `beta/update-88b`, it becomes `beta/update-0.88.101`. Prefixed letters are replaced with a three-digit incremental patch number, akin to .NET's SDK versions. This approach aims to ensure that zTSO clients cannot access FreeSO's servers while remaining familiar to existing users and server operators.
|
||||||
|
|
||||||
As the API server is already complete, zTSO's client and server will have separate release cycles. The API server will have a Long-Term Support (LTS) release to support existing server operators, while the client will follow a rapid rolling release. To prevent conflicts between the two clients, the API server will be modified to check for zTSO-based clients as part of an additional handshake during login. This check will be ignored by FreeSO-based clients as it constitutes a separate API call.
|
As the API server is already complete, zTSO's client and server will have separate release cycles. The API server will have a Long-Term Support (LTS) release to support existing server operators, while the client will follow a rapid rolling release. To prevent conflicts between the two clients, the API server will be modified to check for zTSO-based clients as part of an additional handshake during login. This check will be ignored by FreeSO-based clients as it constitutes a separate API call.
|
||||||
|
|
||||||
|
|
64
server/tso.files/Endian.cs
Executable file
64
server/tso.files/Endian.cs
Executable file
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FSO.Files
|
||||||
|
{
|
||||||
|
public class Endian
|
||||||
|
{
|
||||||
|
static Endian()
|
||||||
|
{
|
||||||
|
_LittleEndian = BitConverter.IsLittleEndian;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static short SwapInt16(short v)
|
||||||
|
{
|
||||||
|
return (short)(((v & 0xff) << 8) | ((v >> 8) & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ushort SwapUInt16(ushort v)
|
||||||
|
{
|
||||||
|
return (ushort)(((v & 0xff) << 8) | ((v >> 8) & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int SwapInt32(int v)
|
||||||
|
{
|
||||||
|
return (int)(((SwapInt16((short)v) & 0xffff) << 0x10) |
|
||||||
|
(SwapInt16((short)(v >> 0x10)) & 0xffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static uint SwapUInt32(uint v)
|
||||||
|
{
|
||||||
|
return (uint)(((SwapUInt16((ushort)v) & 0xffff) << 0x10) |
|
||||||
|
(SwapUInt16((ushort)(v >> 0x10)) & 0xffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long SwapInt64(long v)
|
||||||
|
{
|
||||||
|
return (long)(((SwapInt32((int)v) & 0xffffffffL) << 0x20) |
|
||||||
|
(SwapInt32((int)(v >> 0x20)) & 0xffffffffL));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ulong SwapUInt64(ulong v)
|
||||||
|
{
|
||||||
|
return (ulong)(((SwapUInt32((uint)v) & 0xffffffffL) << 0x20) |
|
||||||
|
(SwapUInt32((uint)(v >> 0x20)) & 0xffffffffL));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsBigEndian
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return !_LittleEndian;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsLittleEndian
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _LittleEndian;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly bool _LittleEndian;
|
||||||
|
}
|
||||||
|
}
|
150
server/tso.files/FAR1/FAR1Archive.cs
Executable file
150
server/tso.files/FAR1/FAR1Archive.cs
Executable file
|
@ -0,0 +1,150 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.FAR1
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A FAR1 (File Archive v1) archive.
|
||||||
|
/// </summary>
|
||||||
|
public class FAR1Archive
|
||||||
|
{
|
||||||
|
private string m_Path;
|
||||||
|
private BinaryReader m_Reader;
|
||||||
|
|
||||||
|
private uint m_ManifestOffset;
|
||||||
|
private uint m_NumFiles;
|
||||||
|
private List<FarEntry> m_Entries = new List<FarEntry>();
|
||||||
|
private bool V1b = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The offset into the archive of the manifest.
|
||||||
|
/// </summary>
|
||||||
|
public uint ManifestOffset
|
||||||
|
{
|
||||||
|
get { return m_ManifestOffset; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of files/entries in the archive.
|
||||||
|
/// </summary>
|
||||||
|
public uint NumFiles
|
||||||
|
{
|
||||||
|
get { return m_NumFiles; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new FAR1Archive instance from a path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Path">The path to the archive.</param>
|
||||||
|
public FAR1Archive(string Path, bool v1b)
|
||||||
|
{
|
||||||
|
m_Path = Path;
|
||||||
|
m_Reader = new BinaryReader(File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||||
|
|
||||||
|
//Magic number - An 8-byte string (not null-terminated), consisting of the ASCII characters "FAR!byAZ"
|
||||||
|
string Header = Encoding.ASCII.GetString(m_Reader.ReadBytes(8));
|
||||||
|
//Version - A 4-byte unsigned integer specifying the version; 1a and 1b each specify 1.
|
||||||
|
uint Version = m_Reader.ReadUInt32();
|
||||||
|
|
||||||
|
if ((Header != "FAR!byAZ") || (Version != 1))
|
||||||
|
{
|
||||||
|
throw (new Exception("Archive wasn't a valid FAR V.1 archive!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//File table offset - A 4-byte unsigned integer specifying the offset to the file table
|
||||||
|
//from the beginning of the archive.
|
||||||
|
m_ManifestOffset = m_Reader.ReadUInt32();
|
||||||
|
m_Reader.BaseStream.Seek(m_ManifestOffset, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
m_NumFiles = m_Reader.ReadUInt32();
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < m_NumFiles; i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
FarEntry Entry = new FarEntry();
|
||||||
|
Entry.DataLength = m_Reader.ReadInt32();
|
||||||
|
Entry.DataLength2 = m_Reader.ReadInt32();
|
||||||
|
Entry.DataOffset = m_Reader.ReadInt32();
|
||||||
|
Entry.FilenameLength = (v1b) ? m_Reader.ReadInt16() : (short)m_Reader.ReadInt32();
|
||||||
|
Entry.Filename = Encoding.ASCII.GetString(m_Reader.ReadBytes(Entry.FilenameLength));
|
||||||
|
|
||||||
|
m_Entries.Add(Entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an entry based on a KeyValuePair.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Entry">A KeyValuePair (string, byte[]) representing the entry. The byte array can be null.</param>
|
||||||
|
/// <returns>A FarEntry or null if the entry wasn't found.</returns>
|
||||||
|
public byte[] GetEntry(KeyValuePair<string, byte[]> Entry)
|
||||||
|
{
|
||||||
|
foreach (FarEntry Ent in m_Entries)
|
||||||
|
{
|
||||||
|
if (Ent.Filename == Entry.Key)
|
||||||
|
{
|
||||||
|
m_Reader.BaseStream.Seek(Ent.DataOffset, SeekOrigin.Begin);
|
||||||
|
return m_Reader.ReadBytes(Ent.DataLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an entry's data from a FarEntry instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Entry">A FarEntry instance.</param>
|
||||||
|
/// <returns>The entry's data.</returns>
|
||||||
|
public byte[] GetEntry(FarEntry Entry)
|
||||||
|
{
|
||||||
|
foreach (FarEntry Ent in m_Entries)
|
||||||
|
{
|
||||||
|
if (Ent.Filename == Entry.Filename)
|
||||||
|
{
|
||||||
|
m_Reader.BaseStream.Seek(Ent.DataOffset, SeekOrigin.Begin);
|
||||||
|
return m_Reader.ReadBytes(Ent.DataLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of all FarEntry instances in this archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<FarEntry> GetAllFarEntries()
|
||||||
|
{
|
||||||
|
return m_Entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all entries in the archive.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A List of KeyValuePair instances.</returns>
|
||||||
|
public List<KeyValuePair<string, byte[]>> GetAllEntries()
|
||||||
|
{
|
||||||
|
List<KeyValuePair<string, byte[]>> Entries = new List<KeyValuePair<string,byte[]>>();
|
||||||
|
|
||||||
|
foreach (FarEntry Entry in m_Entries)
|
||||||
|
{
|
||||||
|
m_Reader.BaseStream.Seek(Entry.DataOffset, SeekOrigin.Begin);
|
||||||
|
byte[] Data = m_Reader.ReadBytes(Entry.DataLength);
|
||||||
|
|
||||||
|
KeyValuePair<string, byte[]> KvP = new KeyValuePair<string, byte[]>(Entry.Filename, Data);
|
||||||
|
Entries.Add(KvP);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
m_Reader.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
server/tso.files/FAR1/FarEntry.cs
Executable file
22
server/tso.files/FAR1/FarEntry.cs
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
namespace FSO.Files.FAR1
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an entry in a FAR1 archive.
|
||||||
|
/// </summary>
|
||||||
|
public class FarEntry
|
||||||
|
{
|
||||||
|
//Decompressed data size - A 4-byte unsigned integer specifying the uncompressed size of the file.
|
||||||
|
public int DataLength;
|
||||||
|
//A 4-byte unsigned integer specifying the compressed size of the file; if this and the previous field are the same,
|
||||||
|
//the file is considered uncompressed. (It is the responsibility of the archiver to only store data compressed when
|
||||||
|
//its size is less than the size of the original data.) Note that The Sims 1 does not actually support any form
|
||||||
|
//of compression.
|
||||||
|
public int DataLength2;
|
||||||
|
//A 4-byte unsigned integer specifying the offset of the file from the beginning of the archive.
|
||||||
|
public int DataOffset;
|
||||||
|
//A 4-byte unsigned integer specifying the length of the filename field that follows.
|
||||||
|
public short FilenameLength;
|
||||||
|
//Filename - The name of the archived file; size depends on the previous field.
|
||||||
|
public string Filename;
|
||||||
|
}
|
||||||
|
}
|
419
server/tso.files/FAR3/Decompresser.cs
Executable file
419
server/tso.files/FAR3/Decompresser.cs
Executable file
|
@ -0,0 +1,419 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.FAR3
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a decompresser that can decompress files in a FAR3
|
||||||
|
/// archive. If you have some kind of need to understand this code, go to:
|
||||||
|
/// http://wiki.niotso.org/RefPack
|
||||||
|
/// The code in this class was ported from DBPF4J:
|
||||||
|
/// http://sc4dbpf4j.cvs.sourceforge.net/viewvc/sc4dbpf4j/DBPF4J/
|
||||||
|
/// </summary>
|
||||||
|
public class Decompresser
|
||||||
|
{
|
||||||
|
private long m_CompressedSize = 0;
|
||||||
|
private long m_DecompressedSize = 0;
|
||||||
|
|
||||||
|
public long DecompressedSize
|
||||||
|
{
|
||||||
|
get { return m_DecompressedSize; }
|
||||||
|
set { m_DecompressedSize = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public long CompressedSize
|
||||||
|
{
|
||||||
|
get { return m_CompressedSize; }
|
||||||
|
set { m_CompressedSize = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies data from source to destination array.<br>
|
||||||
|
/// The copy is byte by byte from srcPos to destPos and given length.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Src">The source array.</param>
|
||||||
|
/// <param name="SrcPos">The source Position.</param>
|
||||||
|
/// <param name="Dest">The destination array.</param>
|
||||||
|
/// <param name="DestPos">The destination Position.</param>
|
||||||
|
/// <param name="Length">The length.</param>
|
||||||
|
private void ArrayCopy2(byte[] Src, int SrcPos, ref byte[] Dest, int DestPos, long Length)
|
||||||
|
{
|
||||||
|
if (Dest.Length < DestPos + Length)
|
||||||
|
{
|
||||||
|
byte[] DestExt = new byte[(int)(DestPos + Length)];
|
||||||
|
Array.Copy(Dest, 0, DestExt, 0, Dest.Length);
|
||||||
|
Dest = DestExt;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < Length/* - 1*/; i++)
|
||||||
|
Dest[DestPos + i] = Src[SrcPos + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies data from array at destPos-srcPos to array at destPos.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">The array.</param>
|
||||||
|
/// <param name="srcPos">The Position to copy from (reverse from end of array!)</param>
|
||||||
|
/// <param name="destPos">The Position to copy to.</param>
|
||||||
|
/// <param name="length">The length of data to copy.</param>
|
||||||
|
private void OffsetCopy(ref byte[] array, int srcPos, int destPos, long length)
|
||||||
|
{
|
||||||
|
srcPos = destPos - srcPos;
|
||||||
|
|
||||||
|
if (array.Length < destPos + length)
|
||||||
|
{
|
||||||
|
byte[] NewArray = new byte[(int)(destPos + length)];
|
||||||
|
Array.Copy(array, 0, NewArray, 0, array.Length);
|
||||||
|
array = NewArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < length /*- 1*/; i++)
|
||||||
|
{
|
||||||
|
array[destPos + i] = array[srcPos + i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compresses data and returns it as an array of bytes.
|
||||||
|
/// Assumes that the array of bytes passed contains
|
||||||
|
/// uncompressed data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Data">The data to be compressed.</param>
|
||||||
|
/// <returns>An array of bytes with compressed data.</returns>
|
||||||
|
public byte[] Compress(byte[] Data)
|
||||||
|
{
|
||||||
|
// if data is big enough for compress
|
||||||
|
if (Data.Length > 6)
|
||||||
|
{
|
||||||
|
// some Compression Data
|
||||||
|
const int MAX_OFFSET = 0x20000;
|
||||||
|
const int MAX_COPY_COUNT = 0x404;
|
||||||
|
// used to finetune the lookup (small values increase the
|
||||||
|
// compression for Big Files)
|
||||||
|
const int QFS_MAXITER = 0x80;
|
||||||
|
|
||||||
|
// contains the latest offset for a combination of two
|
||||||
|
// characters
|
||||||
|
Dictionary<int, ArrayList> cmpmap2 = new Dictionary<int, ArrayList>();
|
||||||
|
|
||||||
|
// will contain the compressed data (maximal size =
|
||||||
|
// uncompressedSize+MAX_COPY_COUNT)
|
||||||
|
byte[] cData = new byte[Data.Length + MAX_COPY_COUNT];
|
||||||
|
|
||||||
|
// init some vars
|
||||||
|
int writeIndex = 9; // leave 9 bytes for the header
|
||||||
|
int lastReadIndex = 0;
|
||||||
|
ArrayList indexList = null;
|
||||||
|
int copyOffset = 0;
|
||||||
|
int copyCount = 0;
|
||||||
|
int index = -1;
|
||||||
|
bool end = false;
|
||||||
|
|
||||||
|
// begin main compression loop
|
||||||
|
while (index < Data.Length - 3)
|
||||||
|
{
|
||||||
|
// get all Compression Candidates (list of offsets for all
|
||||||
|
// occurances of the current 3 bytes)
|
||||||
|
do
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
if (index >= Data.Length - 2)
|
||||||
|
{
|
||||||
|
end = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int mapindex = Data[index] + (Data[index + 1] << 8)
|
||||||
|
+ (Data[index + 2] << 16);
|
||||||
|
|
||||||
|
indexList = cmpmap2[mapindex];
|
||||||
|
if (indexList == null)
|
||||||
|
{
|
||||||
|
indexList = new ArrayList();
|
||||||
|
cmpmap2.Add(mapindex, indexList);
|
||||||
|
}
|
||||||
|
indexList.Add(index);
|
||||||
|
} while (index < lastReadIndex);
|
||||||
|
if (end)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// find the longest repeating byte sequence in the index
|
||||||
|
// List (for offset copy)
|
||||||
|
int offsetCopyCount = 0;
|
||||||
|
int loopcount = 1;
|
||||||
|
while ((loopcount < indexList.Count) && (loopcount < QFS_MAXITER))
|
||||||
|
{
|
||||||
|
int foundindex = (int) indexList[(indexList.Count - 1) - loopcount];
|
||||||
|
if ((index - foundindex) >= MAX_OFFSET)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
loopcount++;
|
||||||
|
copyCount = 3;
|
||||||
|
|
||||||
|
while ((Data.Length > index + copyCount)&& (Data[index + copyCount] == Data[foundindex + copyCount]) && (copyCount < MAX_COPY_COUNT))
|
||||||
|
{
|
||||||
|
copyCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copyCount > offsetCopyCount)
|
||||||
|
{
|
||||||
|
offsetCopyCount = copyCount;
|
||||||
|
copyOffset = index - foundindex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we can compress this
|
||||||
|
// In FSH Tool stand additionally this:
|
||||||
|
if (offsetCopyCount > Data.Length - index)
|
||||||
|
{
|
||||||
|
offsetCopyCount = index - Data.Length;
|
||||||
|
}
|
||||||
|
if (offsetCopyCount <= 2)
|
||||||
|
{
|
||||||
|
offsetCopyCount = 0;
|
||||||
|
}
|
||||||
|
else if ((offsetCopyCount == 3) && (copyOffset > 0x400))
|
||||||
|
{ // 1024
|
||||||
|
offsetCopyCount = 0;
|
||||||
|
}
|
||||||
|
else if ((offsetCopyCount == 4) && (copyOffset > 0x4000))
|
||||||
|
{ // 16384
|
||||||
|
offsetCopyCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is offset-compressable? so do the compression
|
||||||
|
if (offsetCopyCount > 0)
|
||||||
|
{
|
||||||
|
// plaincopy
|
||||||
|
|
||||||
|
// In FSH Tool stand this (A):
|
||||||
|
while (index - lastReadIndex >= 4)
|
||||||
|
{
|
||||||
|
copyCount = (index - lastReadIndex) / 4 - 1;
|
||||||
|
if (copyCount > 0x1B)
|
||||||
|
{
|
||||||
|
copyCount = 0x1B;
|
||||||
|
}
|
||||||
|
cData[writeIndex++] = (byte)(0xE0 + copyCount);
|
||||||
|
copyCount = 4 * copyCount + 4;
|
||||||
|
|
||||||
|
ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount);
|
||||||
|
lastReadIndex += copyCount;
|
||||||
|
writeIndex += copyCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// offsetcopy
|
||||||
|
copyCount = index - lastReadIndex;
|
||||||
|
copyOffset--;
|
||||||
|
if ((offsetCopyCount <= 0x0A) && (copyOffset < 0x400))
|
||||||
|
{
|
||||||
|
cData[writeIndex++] = (byte) (((copyOffset >> 8) << 5)
|
||||||
|
+ ((offsetCopyCount - 3) << 2) + copyCount);
|
||||||
|
cData[writeIndex++] = (byte)(copyOffset & 0xff);
|
||||||
|
}
|
||||||
|
else if ((offsetCopyCount <= 0x43) && (copyOffset < 0x4000))
|
||||||
|
{
|
||||||
|
cData[writeIndex++] = (byte)(0x80 + (offsetCopyCount - 4));
|
||||||
|
cData[writeIndex++] = (byte) ((copyCount << 6) + (copyOffset >> 8));
|
||||||
|
cData[writeIndex++] = (byte) (copyOffset & 0xff);
|
||||||
|
}
|
||||||
|
else if ((offsetCopyCount <= MAX_COPY_COUNT) && (copyOffset < MAX_OFFSET))
|
||||||
|
{
|
||||||
|
cData[writeIndex++] = (byte)(0xc0
|
||||||
|
+ ((copyOffset >> 16) << 4)
|
||||||
|
+ (((offsetCopyCount - 5) >> 8) << 2) + copyCount);
|
||||||
|
cData[writeIndex++] = (byte)((copyOffset >> 8) & 0xff);
|
||||||
|
cData[writeIndex++] = (byte)(copyOffset & 0xff);
|
||||||
|
cData[writeIndex++] = (byte)((offsetCopyCount - 5) & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// do the offset copy
|
||||||
|
ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount);
|
||||||
|
writeIndex += copyCount;
|
||||||
|
lastReadIndex += copyCount;
|
||||||
|
lastReadIndex += offsetCopyCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the End Record
|
||||||
|
index = Data.Length;
|
||||||
|
// in FSH Tool stand the same as above (A)
|
||||||
|
while (index - lastReadIndex >= 4)
|
||||||
|
{
|
||||||
|
copyCount = (index - lastReadIndex) / 4 - 1;
|
||||||
|
|
||||||
|
if (copyCount > 0x1B)
|
||||||
|
copyCount = 0x1B;
|
||||||
|
|
||||||
|
cData[writeIndex++] = (byte)(0xE0 + copyCount);
|
||||||
|
copyCount = 4 * copyCount + 4;
|
||||||
|
|
||||||
|
ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount);
|
||||||
|
lastReadIndex += copyCount;
|
||||||
|
writeIndex += copyCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
copyCount = index - lastReadIndex;
|
||||||
|
cData[writeIndex++] = (byte) (0xfc + copyCount);
|
||||||
|
ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount);
|
||||||
|
writeIndex += copyCount;
|
||||||
|
lastReadIndex += copyCount;
|
||||||
|
|
||||||
|
MemoryStream DataStream = new MemoryStream();
|
||||||
|
BinaryWriter Writer = new BinaryWriter(DataStream);
|
||||||
|
|
||||||
|
// write the header for the compressed data
|
||||||
|
// set the compressed size
|
||||||
|
Writer.Write((uint)writeIndex);
|
||||||
|
m_CompressedSize = writeIndex;
|
||||||
|
// set the MAGICNUMBER
|
||||||
|
Writer.Write((ushort)0xFB10);
|
||||||
|
// set the decompressed size
|
||||||
|
byte[] revData = BitConverter.GetBytes(Data.Length);
|
||||||
|
Writer.Write((revData[2] << 16) | (revData[1] << 8) | revData[0]);
|
||||||
|
Writer.Write(cData);
|
||||||
|
|
||||||
|
//Avoid nasty swearing here!
|
||||||
|
Writer.Flush();
|
||||||
|
|
||||||
|
m_DecompressedSize = Data.Length;
|
||||||
|
|
||||||
|
return DataStream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decompresses data and returns it as an
|
||||||
|
/// uncompressed array of bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Data">The data to decompress.</param>
|
||||||
|
/// <returns>An uncompressed array of bytes.</returns>
|
||||||
|
public byte[] Decompress(byte[] Data)
|
||||||
|
{
|
||||||
|
|
||||||
|
MemoryStream MemData = new MemoryStream(Data);
|
||||||
|
BinaryReader Reader = new BinaryReader(MemData);
|
||||||
|
|
||||||
|
if (Data.Length > 6)
|
||||||
|
{
|
||||||
|
byte[] DecompressedData = new byte[(int)m_DecompressedSize];
|
||||||
|
int DataPos = 0;
|
||||||
|
|
||||||
|
int Pos = 0;
|
||||||
|
long Control1 = 0;
|
||||||
|
|
||||||
|
while (Control1 != 0xFC && Pos < Data.Length)
|
||||||
|
{
|
||||||
|
Control1 = Data[Pos];
|
||||||
|
Pos++;
|
||||||
|
|
||||||
|
if (Pos == Data.Length)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (Control1 >= 0 && Control1 <= 127)
|
||||||
|
{
|
||||||
|
// 0x00 - 0x7F
|
||||||
|
long control2 = Data[Pos];
|
||||||
|
Pos++;
|
||||||
|
long numberOfPlainText = (Control1 & 0x03);
|
||||||
|
ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText);
|
||||||
|
DataPos += (int)numberOfPlainText;
|
||||||
|
Pos += (int)numberOfPlainText;
|
||||||
|
|
||||||
|
if (DataPos == (DecompressedData.Length))
|
||||||
|
break;
|
||||||
|
|
||||||
|
int offset = (int)(((Control1 & 0x60) << 3) + (control2) + 1);
|
||||||
|
long numberToCopyFromOffset = ((Control1 & 0x1C) >> 2) + 3;
|
||||||
|
OffsetCopy(ref DecompressedData, offset, DataPos, numberToCopyFromOffset);
|
||||||
|
DataPos += (int)numberToCopyFromOffset;
|
||||||
|
|
||||||
|
if (DataPos == (DecompressedData.Length))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if ((Control1 >= 128 && Control1 <= 191))
|
||||||
|
{
|
||||||
|
// 0x80 - 0xBF
|
||||||
|
long control2 = Data[Pos];
|
||||||
|
Pos++;
|
||||||
|
long control3 = Data[Pos];
|
||||||
|
Pos++;
|
||||||
|
|
||||||
|
long numberOfPlainText = (control2 >> 6) & 0x03;
|
||||||
|
ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText);
|
||||||
|
DataPos += (int)numberOfPlainText;
|
||||||
|
Pos += (int)numberOfPlainText;
|
||||||
|
|
||||||
|
if (DataPos == (DecompressedData.Length))
|
||||||
|
break;
|
||||||
|
|
||||||
|
int offset = (int)(((control2 & 0x3F) << 8) + (control3) + 1);
|
||||||
|
long numberToCopyFromOffset = (Control1 & 0x3F) + 4;
|
||||||
|
OffsetCopy(ref DecompressedData, offset, DataPos, numberToCopyFromOffset);
|
||||||
|
DataPos += (int)numberToCopyFromOffset;
|
||||||
|
|
||||||
|
if (DataPos == (DecompressedData.Length))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (Control1 >= 192 && Control1 <= 223)
|
||||||
|
{
|
||||||
|
// 0xC0 - 0xDF
|
||||||
|
long numberOfPlainText = (Control1 & 0x03);
|
||||||
|
long control2 = Data[Pos];
|
||||||
|
Pos++;
|
||||||
|
long control3 = Data[Pos];
|
||||||
|
Pos++;
|
||||||
|
long control4 = Data[Pos];
|
||||||
|
Pos++;
|
||||||
|
ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText);
|
||||||
|
DataPos += (int)numberOfPlainText;
|
||||||
|
Pos += (int)numberOfPlainText;
|
||||||
|
|
||||||
|
if (DataPos == (DecompressedData.Length))
|
||||||
|
break;
|
||||||
|
|
||||||
|
int offset = (int)(((Control1 & 0x10) << 12) + (control2 << 8) + (control3) + 1);
|
||||||
|
long numberToCopyFromOffset = ((Control1 & 0x0C) << 6) + (control4) + 5;
|
||||||
|
OffsetCopy(ref DecompressedData, offset, DataPos, numberToCopyFromOffset);
|
||||||
|
DataPos += (int)numberToCopyFromOffset;
|
||||||
|
|
||||||
|
if (DataPos == (DecompressedData.Length))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (Control1 >= 224 && Control1 <= 251)
|
||||||
|
{
|
||||||
|
// 0xE0 - 0xFB
|
||||||
|
long numberOfPlainText = ((Control1 & 0x1F) << 2) + 4;
|
||||||
|
ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText);
|
||||||
|
DataPos += (int)numberOfPlainText;
|
||||||
|
Pos += (int)numberOfPlainText;
|
||||||
|
|
||||||
|
if (DataPos == (DecompressedData.Length))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
long numberOfPlainText = (Control1 & 0x03);
|
||||||
|
ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText);
|
||||||
|
|
||||||
|
DataPos += (int)numberOfPlainText;
|
||||||
|
Pos += (int)numberOfPlainText;
|
||||||
|
|
||||||
|
if (DataPos == (DecompressedData.Length))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecompressedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
//No data to decompress
|
||||||
|
return Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
243
server/tso.files/FAR3/FAR3Archive.cs
Executable file
243
server/tso.files/FAR3/FAR3Archive.cs
Executable file
|
@ -0,0 +1,243 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.FAR3
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a single FAR3 archive.
|
||||||
|
/// </summary>
|
||||||
|
public class FAR3Archive : IDisposable
|
||||||
|
{
|
||||||
|
private BinaryReader m_Reader;
|
||||||
|
public static bool isReadingSomething = false;
|
||||||
|
|
||||||
|
private string m_ArchivePath;
|
||||||
|
private Dictionary<string, Far3Entry> m_Entries = new Dictionary<string, Far3Entry>();
|
||||||
|
private List<Far3Entry> m_EntriesList = new List<Far3Entry>();
|
||||||
|
private Dictionary<uint, Far3Entry> m_EntryByID = new Dictionary<uint, Far3Entry>();
|
||||||
|
private uint m_ManifestOffset;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new FAR3Archive instance from a path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Path">The path to the archive.</param>
|
||||||
|
public FAR3Archive(string Path)
|
||||||
|
{
|
||||||
|
m_ArchivePath = Path;
|
||||||
|
|
||||||
|
if (isReadingSomething == false)
|
||||||
|
{
|
||||||
|
isReadingSomething = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Reader = new BinaryReader(File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new FAR3Exception("Could not open the specified archive - " + Path + "! (FAR3Archive())");
|
||||||
|
}
|
||||||
|
|
||||||
|
string Header = Encoding.ASCII.GetString(m_Reader.ReadBytes(8));
|
||||||
|
uint Version = m_Reader.ReadUInt32();
|
||||||
|
|
||||||
|
if ((Header != "FAR!byAZ") || (Version != 3))
|
||||||
|
{
|
||||||
|
throw new FAR3Exception("Archive wasn't a valid FAR V.3 archive! (FAR3Archive())");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint ManifestOffset = m_Reader.ReadUInt32();
|
||||||
|
m_ManifestOffset = ManifestOffset;
|
||||||
|
|
||||||
|
m_Reader.BaseStream.Seek(ManifestOffset, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
uint NumFiles = m_Reader.ReadUInt32();
|
||||||
|
|
||||||
|
for (int i = 0; i < NumFiles; i++)
|
||||||
|
{
|
||||||
|
Far3Entry Entry = new Far3Entry();
|
||||||
|
Entry.DecompressedFileSize = m_Reader.ReadUInt32();
|
||||||
|
byte dummy0 = m_Reader.ReadByte();
|
||||||
|
byte dummy1 = m_Reader.ReadByte();
|
||||||
|
byte dummy2 = m_Reader.ReadByte();
|
||||||
|
Entry.CompressedFileSize = (uint)((dummy0 << 0) | (dummy1 << 8) | (dummy2) << 16);
|
||||||
|
Entry.DataType = m_Reader.ReadByte();
|
||||||
|
Entry.DataOffset = m_Reader.ReadUInt32();
|
||||||
|
//Entry.HasFilename = m_Reader.ReadUInt16();
|
||||||
|
Entry.IsCompressed = m_Reader.ReadByte();
|
||||||
|
Entry.AccessNumber = m_Reader.ReadByte();
|
||||||
|
Entry.FilenameLength = m_Reader.ReadUInt16();
|
||||||
|
Entry.TypeID = m_Reader.ReadUInt32();
|
||||||
|
Entry.FileID = m_Reader.ReadUInt32();
|
||||||
|
Entry.Filename = Encoding.ASCII.GetString(m_Reader.ReadBytes(Entry.FilenameLength));
|
||||||
|
|
||||||
|
if (!m_Entries.ContainsKey(Entry.Filename))
|
||||||
|
m_Entries.Add(Entry.Filename, Entry);
|
||||||
|
m_EntriesList.Add(Entry);
|
||||||
|
|
||||||
|
m_EntryByID.Add(Entry.FileID, Entry); //isn't this a bad idea? i have a feeling this is a bad idea...
|
||||||
|
}
|
||||||
|
|
||||||
|
//Keep the stream open, it helps peformance.
|
||||||
|
//m_Reader.Close();
|
||||||
|
isReadingSomething = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an entry's data from a Far3Entry instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Entry">The Far3Entry instance.</param>
|
||||||
|
/// <returns>The entry's data.</returns>
|
||||||
|
public byte[] GetEntry(Far3Entry Entry)
|
||||||
|
{
|
||||||
|
lock (m_Reader)
|
||||||
|
{
|
||||||
|
m_Reader.BaseStream.Seek((long)Entry.DataOffset, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
isReadingSomething = true;
|
||||||
|
|
||||||
|
if (Entry.IsCompressed == 0x01)
|
||||||
|
{
|
||||||
|
m_Reader.BaseStream.Seek(9, SeekOrigin.Current);
|
||||||
|
uint Filesize = m_Reader.ReadUInt32();
|
||||||
|
ushort CompressionID = m_Reader.ReadUInt16();
|
||||||
|
|
||||||
|
if (CompressionID == 0xFB10)
|
||||||
|
{
|
||||||
|
byte dummy0 = m_Reader.ReadByte();
|
||||||
|
byte dummy1 = m_Reader.ReadByte();
|
||||||
|
byte dummy2 = m_Reader.ReadByte();
|
||||||
|
uint DecompressedSize = (uint)((dummy0 << 0x10) | (dummy1 << 0x08) | +dummy2);
|
||||||
|
|
||||||
|
Decompresser Dec = new Decompresser();
|
||||||
|
Dec.CompressedSize = Filesize;
|
||||||
|
Dec.DecompressedSize = DecompressedSize;
|
||||||
|
|
||||||
|
byte[] DecompressedData = Dec.Decompress(m_Reader.ReadBytes((int)Filesize));
|
||||||
|
//m_Reader.Close();
|
||||||
|
|
||||||
|
isReadingSomething = false;
|
||||||
|
|
||||||
|
return DecompressedData;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_Reader.BaseStream.Seek((m_Reader.BaseStream.Position - 15), SeekOrigin.Begin);
|
||||||
|
|
||||||
|
byte[] Data = m_Reader.ReadBytes((int)Entry.DecompressedFileSize);
|
||||||
|
//m_Reader.Close();
|
||||||
|
|
||||||
|
isReadingSomething = false;
|
||||||
|
|
||||||
|
return Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
byte[] Data = m_Reader.ReadBytes((int)Entry.DecompressedFileSize);
|
||||||
|
//m_Reader.Close();
|
||||||
|
|
||||||
|
isReadingSomething = false;
|
||||||
|
|
||||||
|
return Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FAR3Exception("FAR3Entry didn't exist in archive - FAR3Archive.GetEntry()");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the entries of this FAR3Archive as byte arrays together with their corresponding FileIDs.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A List of KeyValuePair instances.</returns>
|
||||||
|
public List<KeyValuePair<uint, byte[]>> GetAllEntries()
|
||||||
|
{
|
||||||
|
List<KeyValuePair<uint, byte[]>> toReturn = new List<KeyValuePair<uint, byte[]>>();
|
||||||
|
foreach (Far3Entry Entry in m_EntriesList)
|
||||||
|
{
|
||||||
|
toReturn.Add(new KeyValuePair<uint, byte[]>(Entry.FileID, GetEntry(Entry)));
|
||||||
|
}
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the entries of this FAR3Archive as FAR3Entry instances in a List.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns the entries of this FAR3Archive as FAR3Entry instances in a List.</returns>
|
||||||
|
public List<Far3Entry> GetAllFAR3Entries()
|
||||||
|
{
|
||||||
|
List<Far3Entry> Entries = new List<Far3Entry>();
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, Far3Entry> KVP in m_Entries)
|
||||||
|
Entries.Add(KVP.Value);
|
||||||
|
|
||||||
|
return Entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an entry from a FileID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="FileID">The entry's FileID.</param>
|
||||||
|
/// <returns>The entry's data.</returns>
|
||||||
|
public byte[] GetItemByID(uint FileID)
|
||||||
|
{
|
||||||
|
var item = m_EntryByID[FileID];
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
throw new FAR3Exception("Didn't find entry!");
|
||||||
|
}
|
||||||
|
return GetEntry(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an entry from its ID (TypeID + FileID).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ID">The ID of the entry.</param>
|
||||||
|
/// <returns>The entry's data.</returns>
|
||||||
|
public byte[] GetItemByID(ulong ID)
|
||||||
|
{
|
||||||
|
byte[] Bytes = BitConverter.GetBytes(ID);
|
||||||
|
uint FileID = BitConverter.ToUInt32(Bytes, 4);
|
||||||
|
uint TypeID = BitConverter.ToUInt32(Bytes, 0);
|
||||||
|
|
||||||
|
var item = m_EntryByID[FileID];
|
||||||
|
if (item == null || item.TypeID != TypeID)
|
||||||
|
{
|
||||||
|
throw new FAR3Exception("Didn't find entry!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetEntry(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an entry's data from a filename.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filename">The filename of the entry.</param>
|
||||||
|
/// <returns>The entry's data.</returns>
|
||||||
|
public byte[] this[string Filename]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return GetEntry(m_Entries[Filename]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes this FAR3Archive instance.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (m_Reader != null)
|
||||||
|
{
|
||||||
|
m_Reader.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
15
server/tso.files/FAR3/FAR3Exception.cs
Executable file
15
server/tso.files/FAR3/FAR3Exception.cs
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FSO.Files.FAR3
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an exception thrown by a FAR3Archive instance.
|
||||||
|
/// </summary>
|
||||||
|
public class FAR3Exception : Exception
|
||||||
|
{
|
||||||
|
public FAR3Exception(string Message)
|
||||||
|
: base(Message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
server/tso.files/FAR3/Far3Entry.cs
Executable file
32
server/tso.files/FAR3/Far3Entry.cs
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
namespace FSO.Files.FAR3
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an entry in a FAR3 archive.
|
||||||
|
/// </summary>
|
||||||
|
public class Far3Entry
|
||||||
|
{
|
||||||
|
//A 4-byte unsigned integer specifying the uncompressed size of the file.
|
||||||
|
public uint DecompressedFileSize;
|
||||||
|
// A 3-byte unsigned integer specifying the compressed size of the file (including
|
||||||
|
//the Persist header); if the data is raw, this field is ignored (though TSO's game
|
||||||
|
//files have this set to the same first three bytes as the previous field).
|
||||||
|
public uint CompressedFileSize;
|
||||||
|
//Data type - A single byte used to describe what type of data is pointed to by the Data offset field.
|
||||||
|
//The value can be 0x80 to denote that the data is a Persist container or 0x00 to denote that it is raw data.
|
||||||
|
public byte DataType;
|
||||||
|
//A 4-byte unsigned integer specifying the offset of the file from the beginning of the archive.
|
||||||
|
public uint DataOffset;
|
||||||
|
//A byte (can be either 0 or 1) specifying if this file is compressed.
|
||||||
|
public byte IsCompressed;
|
||||||
|
//A byte specifying the number of files this time has been accessed?
|
||||||
|
public byte AccessNumber;
|
||||||
|
//A 2-byte unsigned integer specifying the length of the filename field.
|
||||||
|
public ushort FilenameLength;
|
||||||
|
//A 4-byte integer describing what type of file is held.
|
||||||
|
public uint TypeID;
|
||||||
|
//A 4-byte ID assigned to the file which, together with the Type ID, is assumed to be unique all throughout the game.
|
||||||
|
public uint FileID;
|
||||||
|
//The name of the archived file; size depends on the filename length field.
|
||||||
|
public string Filename;
|
||||||
|
}
|
||||||
|
}
|
265
server/tso.files/FSO.Files.csproj
Executable file
265
server/tso.files/FSO.Files.csproj
Executable file
|
@ -0,0 +1,265 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProductVersion>9.0.30729</ProductVersion>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<ProjectGuid>{18583453-A970-4AC5-83B1-2D6BFDF94C24}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>FSO.Files</RootNamespace>
|
||||||
|
<AssemblyName>FSO.Files</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<FileUpgradeFlags>
|
||||||
|
</FileUpgradeFlags>
|
||||||
|
<UpgradeBackupLocation>
|
||||||
|
</UpgradeBackupLocation>
|
||||||
|
<OldToolsVersion>3.5</OldToolsVersion>
|
||||||
|
<PublishUrl>publish\</PublishUrl>
|
||||||
|
<Install>true</Install>
|
||||||
|
<InstallFrom>Disk</InstallFrom>
|
||||||
|
<UpdateEnabled>false</UpdateEnabled>
|
||||||
|
<UpdateMode>Foreground</UpdateMode>
|
||||||
|
<UpdateInterval>7</UpdateInterval>
|
||||||
|
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||||
|
<UpdatePeriodically>false</UpdatePeriodically>
|
||||||
|
<UpdateRequired>false</UpdateRequired>
|
||||||
|
<MapFileExtensions>true</MapFileExtensions>
|
||||||
|
<ApplicationRevision>0</ApplicationRevision>
|
||||||
|
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||||
|
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||||
|
<UseApplicationTrust>false</UseApplicationTrust>
|
||||||
|
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<UseVSHostingProcess>true</UseVSHostingProcess>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||||
|
<OutputPath>bin\x86\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<UseVSHostingProcess>true</UseVSHostingProcess>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ServerRelease|x86'">
|
||||||
|
<OutputPath>bin\x86\ServerRelease\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ServerRelease|AnyCPU'">
|
||||||
|
<OutputPath>bin\ServerRelease\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="bz2portable, Version=0.86.0.518, Culture=neutral, PublicKeyToken=43072770b20d93eb, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\bz2portable.1.0.1\lib\bz2portable.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="deltaq, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4ec66318c45104d7, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\deltaq.1.0.1\lib\deltaq.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="MonoGame.Framework, Version=3.6.0.1625, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core">
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
<Reference Include="xxHashSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\xxHashSharp.1.0.0\lib\net45\xxHashSharp.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Formats\IFF\Chunks\ARRY.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\FCNS.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\HOUS.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\PART.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\PNG.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\CARR.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\FAMI.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\FAMs.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\FSOM.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\FSOR.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\FSOV.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\IffFieldEncode.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\MTEX.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\NBRS.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\NGBH.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\OBJM.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\OBJT.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\SIMI.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\THMB.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\TRCN.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\TREE.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\TTAT.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\WALm.cs" />
|
||||||
|
<Compile Include="Formats\tsodata\BulletinItem.cs" />
|
||||||
|
<Compile Include="Formats\tsodata\TSODataDefinition.cs" />
|
||||||
|
<Compile Include="Formats\IFF\ChunkRuntimeInfo.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\PIFF.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\SPR2FrameEncoder.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\TPRP.cs" />
|
||||||
|
<Compile Include="Formats\IFF\IffRuntimeInfo.cs" />
|
||||||
|
<Compile Include="Formats\IFF\PIFFRegistry.cs" />
|
||||||
|
<Compile Include="Formats\PiffEncoder.cs" />
|
||||||
|
<Compile Include="Formats\tsodata\MessageItem.cs" />
|
||||||
|
<Compile Include="Formats\tsodata\TSOp.cs" />
|
||||||
|
<Compile Include="HIT\Patch.cs" />
|
||||||
|
<Compile Include="ImageLoader.cs" />
|
||||||
|
<Compile Include="FAR1\FAR1Archive.cs" />
|
||||||
|
<Compile Include="Formats\DBPF\DBPFEntry.cs" />
|
||||||
|
<Compile Include="Formats\DBPF\DBPFFile.cs" />
|
||||||
|
<Compile Include="Formats\IFF\AbstractIffChunk.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\BCON.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\BHAV.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\BMP.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\FWAV.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\GLOB.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\TTAs.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\OBJf.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\CTSS.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\DGRP.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\OBJD.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\PALT.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\SLOT.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\SPR.cs">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Formats\IFF\Chunks\SPR2.cs">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Formats\IFF\Chunks\STR.cs" />
|
||||||
|
<Compile Include="Formats\IFF\Chunks\TTAB.cs" />
|
||||||
|
<Compile Include="Formats\IFF\IffFile.cs" />
|
||||||
|
<Compile Include="Formats\OTF\OTFFile.cs" />
|
||||||
|
<Compile Include="HIT\EVT.cs" />
|
||||||
|
<Compile Include="HIT\FSC.cs" />
|
||||||
|
<Compile Include="HIT\HITConstants.cs" />
|
||||||
|
<Compile Include="HIT\HITFile.cs" />
|
||||||
|
<Compile Include="HIT\Hitlist.cs" />
|
||||||
|
<Compile Include="HIT\Hot.cs" />
|
||||||
|
<Compile Include="HIT\HSM.cs" />
|
||||||
|
<Compile Include="HIT\TLO.cs" />
|
||||||
|
<Compile Include="HIT\Track.cs" />
|
||||||
|
<Compile Include="Endian.cs" />
|
||||||
|
<Compile Include="FAR1\FarEntry.cs" />
|
||||||
|
<Compile Include="FAR3\Decompresser.cs" />
|
||||||
|
<Compile Include="FAR3\FAR3Archive.cs" />
|
||||||
|
<Compile Include="FAR3\Far3Entry.cs" />
|
||||||
|
<Compile Include="FAR3\FAR3Exception.cs" />
|
||||||
|
<Compile Include="ImageLoaderHelpers.cs" />
|
||||||
|
<Compile Include="IniFile.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="RC\DGRP3DGeometry.cs" />
|
||||||
|
<Compile Include="RC\DGRP3DMesh.cs" />
|
||||||
|
<Compile Include="RC\DGRP3DVert.cs" />
|
||||||
|
<Compile Include="RC\DGRPRCParams.cs" />
|
||||||
|
<Compile Include="RC\FSOF.cs" />
|
||||||
|
<Compile Include="RC\NBHm.cs" />
|
||||||
|
<Compile Include="RC\OBJReader.cs" />
|
||||||
|
<Compile Include="RC\Utils\DepthTreatment.cs" />
|
||||||
|
<Compile Include="Tuning.cs" />
|
||||||
|
<Compile Include="Utils\BCFReadProxy.cs" />
|
||||||
|
<Compile Include="Utils\CurLoader.cs" />
|
||||||
|
<Compile Include="Utils\DiffGenerator.cs" />
|
||||||
|
<Compile Include="Utils\IFileInfoUtilizer.cs" />
|
||||||
|
<Compile Include="Utils\IoWriter.cs" />
|
||||||
|
<Compile Include="Utils\IoBuffer.cs" />
|
||||||
|
<Compile Include="Utils\ITextureProvider.cs" />
|
||||||
|
<Compile Include="Utils\IWorldTextureProvider.cs" />
|
||||||
|
<Compile Include="Utils\WorldTexture.cs" />
|
||||||
|
<Compile Include="UTK\UTKFile2.cs" />
|
||||||
|
<Compile Include="XA\XAFile.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="app.config" />
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<BootstrapperPackage Include=".NETFramework,Version=v4.0">
|
||||||
|
<Visible>False</Visible>
|
||||||
|
<ProductName>Microsoft .NET Framework 4 %28x86 and x64%29</ProductName>
|
||||||
|
<Install>true</Install>
|
||||||
|
</BootstrapperPackage>
|
||||||
|
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||||
|
<Visible>False</Visible>
|
||||||
|
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
|
||||||
|
<Install>false</Install>
|
||||||
|
</BootstrapperPackage>
|
||||||
|
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||||
|
<Visible>False</Visible>
|
||||||
|
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||||
|
<Install>false</Install>
|
||||||
|
</BootstrapperPackage>
|
||||||
|
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
|
||||||
|
<Visible>False</Visible>
|
||||||
|
<ProductName>Windows Installer 3.1</ProductName>
|
||||||
|
<Install>true</Install>
|
||||||
|
</BootstrapperPackage>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Other\libs\TargaImagePCL\TargaImagePCL.csproj">
|
||||||
|
<Project>{d8232422-9d79-4200-a981-eb70ed82ccf3}</Project>
|
||||||
|
<Name>TargaImagePCL</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\tso.common\FSO.Common.csproj">
|
||||||
|
<Project>{c42962a1-8796-4f47-9dcd-79ed5904d8ca}</Project>
|
||||||
|
<Name>FSO.Common</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
</Project>
|
60
server/tso.files/Formats/DBPF/DBPFEntry.cs
Executable file
60
server/tso.files/Formats/DBPF/DBPFEntry.cs
Executable file
|
@ -0,0 +1,60 @@
|
||||||
|
namespace FSO.Files.Formats.DBPF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// GroupIDs for DBPF archives, defined in sys\\tsosounddata.ini
|
||||||
|
/// </summary>
|
||||||
|
public enum DBPFGroupID : uint
|
||||||
|
{
|
||||||
|
Multiplayer = 0x29dd0888,
|
||||||
|
Custom = 0x29daa4a6,
|
||||||
|
CustomTrks = 0x29d9359d,
|
||||||
|
Tracks = 0xa9c6c89a,
|
||||||
|
TrackDefs = 0xfdbdbf87,
|
||||||
|
tsov2 = 0x69c6c943,
|
||||||
|
Samples = 0x9dbdbf89,
|
||||||
|
HitLists = 0x9dbdbf74,
|
||||||
|
HitListsTemp = 0xc9c6c9b3,
|
||||||
|
Stings = 0xddbdbf8c,
|
||||||
|
HitLabUI = 0x1d6962cf,
|
||||||
|
HitLabTestSamples = 0x1d8a8b4f,
|
||||||
|
HitLabTest = 0xbd6e5937,
|
||||||
|
EP2 = 0xdde8f5c6,
|
||||||
|
EP5Samps = 0x8a6fcc30
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TypeIDs for DBPF archives, defined in sys\\tsoaudio.ini
|
||||||
|
/// </summary>
|
||||||
|
public enum DBPFTypeID : uint
|
||||||
|
{
|
||||||
|
XA = 0x1d07eb4b,
|
||||||
|
UTK = 0x1b6b9806,
|
||||||
|
WAV = 0xbb7051f5,
|
||||||
|
MP3 = 0x3cec2b47,
|
||||||
|
TRK = 0x5D73A611,
|
||||||
|
HIT = 0x7b1acfcd,
|
||||||
|
SoundFX = 0x2026960b,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an entry in a DBPF archive.
|
||||||
|
/// </summary>
|
||||||
|
public class DBPFEntry
|
||||||
|
{
|
||||||
|
//A 4-byte integer describing what type of file is held
|
||||||
|
public DBPFTypeID TypeID;
|
||||||
|
|
||||||
|
//A 4-byte integer identifying what resource group the file belongs to
|
||||||
|
public DBPFGroupID GroupID;
|
||||||
|
|
||||||
|
//A 4-byte ID assigned to the file which, together with the Type ID and the second instance ID (if applicable), is assumed to be unique all throughout the game
|
||||||
|
public uint InstanceID;
|
||||||
|
//too bad we're not using a version with a second instance id!!
|
||||||
|
|
||||||
|
//A 4-byte unsigned integer specifying the offset to the entry's data from the beginning of the archive
|
||||||
|
public uint FileOffset;
|
||||||
|
|
||||||
|
//A 4-byte unsigned integer specifying the size of the entry's data
|
||||||
|
public uint FileSize;
|
||||||
|
}
|
||||||
|
}
|
184
server/tso.files/Formats/DBPF/DBPFFile.cs
Executable file
184
server/tso.files/Formats/DBPF/DBPFFile.cs
Executable file
|
@ -0,0 +1,184 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.DBPF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The database-packed file (DBPF) is a format used to store data for pretty much all Maxis games after The Sims,
|
||||||
|
/// including The Sims Online (the first appearance of this format), SimCity 4, The Sims 2, Spore, The Sims 3, and
|
||||||
|
/// SimCity 2013.
|
||||||
|
/// </summary>
|
||||||
|
public class DBPFFile : IDisposable
|
||||||
|
{
|
||||||
|
public int DateCreated;
|
||||||
|
public int DateModified;
|
||||||
|
|
||||||
|
private uint IndexMajorVersion;
|
||||||
|
private uint NumEntries;
|
||||||
|
private IoBuffer m_Reader;
|
||||||
|
|
||||||
|
private List<DBPFEntry> m_EntriesList = new List<DBPFEntry>();
|
||||||
|
private Dictionary<ulong, DBPFEntry> m_EntryByID = new Dictionary<ulong, DBPFEntry>();
|
||||||
|
private Dictionary<DBPFTypeID, List<DBPFEntry>> m_EntriesByType = new Dictionary<DBPFTypeID, List<DBPFEntry>>();
|
||||||
|
|
||||||
|
private IoBuffer Io;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new DBPF instance.
|
||||||
|
/// </summary>
|
||||||
|
public DBPFFile()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a DBPF instance from a path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The path to an DBPF archive.</param>
|
||||||
|
public DBPFFile(string file)
|
||||||
|
{
|
||||||
|
var stream = File.OpenRead(file);
|
||||||
|
Read(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a DBPF archive from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to read from.</param>
|
||||||
|
public void Read(Stream stream)
|
||||||
|
{
|
||||||
|
m_EntryByID = new Dictionary<ulong,DBPFEntry>();
|
||||||
|
m_EntriesList = new List<DBPFEntry>();
|
||||||
|
|
||||||
|
var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN);
|
||||||
|
m_Reader = io;
|
||||||
|
this.Io = io;
|
||||||
|
|
||||||
|
var magic = io.ReadCString(4);
|
||||||
|
if (magic != "DBPF")
|
||||||
|
{
|
||||||
|
throw new Exception("Not a DBPF file");
|
||||||
|
}
|
||||||
|
|
||||||
|
var majorVersion = io.ReadUInt32();
|
||||||
|
var minorVersion = io.ReadUInt32();
|
||||||
|
var version = majorVersion + (((double)minorVersion)/10.0);
|
||||||
|
|
||||||
|
/** Unknown, set to 0 **/
|
||||||
|
io.Skip(12);
|
||||||
|
|
||||||
|
if (version == 1.0)
|
||||||
|
{
|
||||||
|
this.DateCreated = io.ReadInt32();
|
||||||
|
this.DateModified = io.ReadInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 2.0)
|
||||||
|
{
|
||||||
|
IndexMajorVersion = io.ReadUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
NumEntries = io.ReadUInt32();
|
||||||
|
uint indexOffset = 0;
|
||||||
|
if (version < 2.0)
|
||||||
|
{
|
||||||
|
indexOffset = io.ReadUInt32();
|
||||||
|
}
|
||||||
|
var indexSize = io.ReadUInt32();
|
||||||
|
|
||||||
|
if (version < 2.0)
|
||||||
|
{
|
||||||
|
var trashEntryCount = io.ReadUInt32();
|
||||||
|
var trashIndexOffset = io.ReadUInt32();
|
||||||
|
var trashIndexSize = io.ReadUInt32();
|
||||||
|
var indexMinor = io.ReadUInt32();
|
||||||
|
}
|
||||||
|
else if (version == 2.0)
|
||||||
|
{
|
||||||
|
var indexMinor = io.ReadUInt32();
|
||||||
|
indexOffset = io.ReadUInt32();
|
||||||
|
io.Skip(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Padding **/
|
||||||
|
io.Skip(32);
|
||||||
|
|
||||||
|
io.Seek(SeekOrigin.Begin, indexOffset);
|
||||||
|
for (int i = 0; i < NumEntries; i++)
|
||||||
|
{
|
||||||
|
var entry = new DBPFEntry();
|
||||||
|
entry.TypeID = (DBPFTypeID)io.ReadUInt32();
|
||||||
|
entry.GroupID = (DBPFGroupID)io.ReadUInt32();
|
||||||
|
entry.InstanceID = io.ReadUInt32();
|
||||||
|
entry.FileOffset = io.ReadUInt32();
|
||||||
|
entry.FileSize = io.ReadUInt32();
|
||||||
|
|
||||||
|
m_EntriesList.Add(entry);
|
||||||
|
ulong id = (((ulong)entry.InstanceID) << 32) + (ulong)entry.TypeID;
|
||||||
|
if (!m_EntryByID.ContainsKey(id))
|
||||||
|
m_EntryByID.Add(id, entry);
|
||||||
|
|
||||||
|
if (!m_EntriesByType.ContainsKey(entry.TypeID))
|
||||||
|
m_EntriesByType.Add(entry.TypeID, new List<DBPFEntry>());
|
||||||
|
|
||||||
|
m_EntriesByType[entry.TypeID].Add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a DBPFEntry's data from this DBPF instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entry">Entry to retrieve data for.</param>
|
||||||
|
/// <returns>Data for entry.</returns>
|
||||||
|
public byte[] GetEntry(DBPFEntry entry)
|
||||||
|
{
|
||||||
|
m_Reader.Seek(SeekOrigin.Begin, entry.FileOffset);
|
||||||
|
|
||||||
|
return m_Reader.ReadBytes((int)entry.FileSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an entry from its ID (TypeID + FileID).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ID">The ID of the entry.</param>
|
||||||
|
/// <returns>The entry's data.</returns>
|
||||||
|
public byte[] GetItemByID(ulong ID)
|
||||||
|
{
|
||||||
|
if (m_EntryByID.ContainsKey(ID))
|
||||||
|
return GetEntry(m_EntryByID[ID]);
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all entries of a specific type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Type">The Type of the entry.</param>
|
||||||
|
/// <returns>The entry data, paired with its instance id.</returns>
|
||||||
|
public List<KeyValuePair<uint, byte[]>> GetItemsByType(DBPFTypeID Type)
|
||||||
|
{
|
||||||
|
|
||||||
|
var result = new List<KeyValuePair<uint, byte[]>>();
|
||||||
|
|
||||||
|
var entries = m_EntriesByType[Type];
|
||||||
|
for (int i = 0; i < entries.Count; i++)
|
||||||
|
{
|
||||||
|
result.Add(new KeyValuePair<uint, byte[]>(entries[i].InstanceID, GetEntry(entries[i])));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes this DBPF instance.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Io.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
51
server/tso.files/Formats/IFF/AbstractIffChunk.cs
Executable file
51
server/tso.files/Formats/IFF/AbstractIffChunk.cs
Executable file
|
@ -0,0 +1,51 @@
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An IFF is made up of chunks.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class IffChunk
|
||||||
|
{
|
||||||
|
public ushort ChunkID;
|
||||||
|
public ushort ChunkFlags;
|
||||||
|
public string ChunkType; //just making it easier to access
|
||||||
|
public string ChunkLabel;
|
||||||
|
public bool ChunkProcessed;
|
||||||
|
public byte[] OriginalData; //IDE ONLY: always contains base data for any chunk.
|
||||||
|
public ushort OriginalID;
|
||||||
|
public bool AddedByPatch;
|
||||||
|
public string OriginalLabel;
|
||||||
|
public byte[] ChunkData;
|
||||||
|
public IffFile ChunkParent;
|
||||||
|
|
||||||
|
public ChunkRuntimeState RuntimeInfo = ChunkRuntimeState.Normal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads this chunk from an IFF.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">The IFF to read from.</param>
|
||||||
|
/// <param name="stream">The stream to read from.</param>
|
||||||
|
public abstract void Read(IffFile iff, Stream stream);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of this chunk.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The name of this chunk as a string.</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "#" + ChunkID.ToString() + " " + ChunkLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to write this chunk to a stream (presumably an IFF or PIFF)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff"></param>
|
||||||
|
/// <param name="stream"></param>
|
||||||
|
/// <returns>True if data has been written, false if not. </returns>
|
||||||
|
public virtual bool Write(IffFile iff, Stream stream) { return false; }
|
||||||
|
|
||||||
|
public virtual void Dispose() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
10
server/tso.files/Formats/IFF/ChunkRuntimeInfo.cs
Executable file
10
server/tso.files/Formats/IFF/ChunkRuntimeInfo.cs
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FSO.Files.Formats.IFF
|
||||||
|
{
|
||||||
|
public enum ChunkRuntimeState
|
||||||
|
{
|
||||||
|
Normal,
|
||||||
|
Patched, //unmodified, but still save when outputting PIFF
|
||||||
|
Modified, //modified. save when outputting PIFF
|
||||||
|
Delete //this chunk should not be saved, or should be saved as a deletion.
|
||||||
|
}
|
||||||
|
}
|
142
server/tso.files/Formats/IFF/Chunks/ARRY.cs
Executable file
142
server/tso.files/Formats/IFF/Chunks/ARRY.cs
Executable file
|
@ -0,0 +1,142 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class ARRY : IffChunk
|
||||||
|
{
|
||||||
|
public int Width;
|
||||||
|
public int Height;
|
||||||
|
public ARRYType Type;
|
||||||
|
public byte[] Data;
|
||||||
|
public byte[] TransposeData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var stride = (int)Type;
|
||||||
|
byte[] result = new byte[Data.Length];
|
||||||
|
for (int i = 0; i < Data.Length; i += stride)
|
||||||
|
{
|
||||||
|
var divI = i / stride;
|
||||||
|
var x = divI % Width;
|
||||||
|
var y = divI / Width;
|
||||||
|
int targetIndex = y * stride + x * stride * Width;
|
||||||
|
for (int j = 0; j < stride; j++)
|
||||||
|
{
|
||||||
|
result[targetIndex++] = Data[i + j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var zero = io.ReadInt32();
|
||||||
|
Width = io.ReadInt32();
|
||||||
|
Height = io.ReadInt32();
|
||||||
|
Type = (ARRYType)io.ReadInt32();
|
||||||
|
var dataByteSize = ByteSize();
|
||||||
|
Data = new byte[Width * Height * dataByteSize];
|
||||||
|
var unknown = io.ReadInt32();
|
||||||
|
|
||||||
|
|
||||||
|
int currentPosition = 0;
|
||||||
|
while (io.HasMore)
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
//bit format:
|
||||||
|
//1000 yyyy yyxx xxxx
|
||||||
|
|
||||||
|
var flre = io.ReadUInt16();
|
||||||
|
if (flre == 0) break;
|
||||||
|
bool rawFill = (flre & 0x8000) == 0;
|
||||||
|
if (rawFill)
|
||||||
|
{
|
||||||
|
for (int i=0; i<flre; i++)
|
||||||
|
{
|
||||||
|
if (!io.HasMore) return;
|
||||||
|
Data[currentPosition++] = io.ReadByte();
|
||||||
|
if (currentPosition > Data.Length) return;
|
||||||
|
}
|
||||||
|
if ((flre & 1) == 1) io.ReadByte();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastPosition = currentPosition;
|
||||||
|
currentPosition += flre & 0x7FFF;
|
||||||
|
currentPosition %= Data.Length; //wrap to data size
|
||||||
|
if (currentPosition == 0) return;
|
||||||
|
|
||||||
|
var pad = io.ReadByte();//ReadElement(io); //pad the previous entries with this data
|
||||||
|
//if ((dataByteSize & 1) == 1)
|
||||||
|
io.ReadByte(); //padded to 16 bits
|
||||||
|
|
||||||
|
while (lastPosition < currentPosition)
|
||||||
|
{
|
||||||
|
Data[lastPosition++] = pad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!io.HasMore) return;
|
||||||
|
|
||||||
|
var size = io.ReadInt16();
|
||||||
|
if ((size & 0x8000) != 0)
|
||||||
|
{
|
||||||
|
io.Seek(SeekOrigin.Current, -2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
Data[currentPosition++] = io.ReadByte();//ReadElement(io);
|
||||||
|
currentPosition %= Data.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((size & 1) == 1) io.ReadByte(); //16-bit pad
|
||||||
|
|
||||||
|
currentPosition %= Data.Length; //12 bit wrap
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ByteSize()
|
||||||
|
{
|
||||||
|
return (int)Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string DebugPrint()
|
||||||
|
{
|
||||||
|
var dataByteSize = ByteSize();
|
||||||
|
var result = new StringBuilder();
|
||||||
|
int index = 0;
|
||||||
|
for (int y = 0; y < Height; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < Width; x++)
|
||||||
|
{
|
||||||
|
var item = Data[index];
|
||||||
|
var str = item.ToString().PadLeft(3, ' ');
|
||||||
|
result.Append((str == " 0") ? " ." : str);
|
||||||
|
index += dataByteSize;
|
||||||
|
}
|
||||||
|
result.AppendLine();
|
||||||
|
}
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ARRYType : int
|
||||||
|
{
|
||||||
|
RLEAlt = 4, //altitude
|
||||||
|
RLEFloor = 1, //floors, "ground", grass, flags, pool "ARRY(9)", water
|
||||||
|
RLEWalls = 8, //walls
|
||||||
|
Objects = 2, //objects "ARRY(3)"
|
||||||
|
}
|
||||||
|
}
|
50
server/tso.files/Formats/IFF/Chunks/BCON.cs
Executable file
50
server/tso.files/Formats/IFF/Chunks/BCON.cs
Executable file
|
@ -0,0 +1,50 @@
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type holds a number of constants that behavior code can refer to.
|
||||||
|
/// Labels may be provided for them in a TRCN chunk with the same ID.
|
||||||
|
/// </summary>
|
||||||
|
public class BCON : IffChunk
|
||||||
|
{
|
||||||
|
public byte Flags;
|
||||||
|
public ushort[] Constants = new ushort[0];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a BCON chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream instance holding a BCON.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var num = io.ReadByte();
|
||||||
|
Flags = io.ReadByte();
|
||||||
|
|
||||||
|
Constants = new ushort[num];
|
||||||
|
for (var i = 0; i < num; i++)
|
||||||
|
{
|
||||||
|
Constants[i] = io.ReadUInt16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteByte((byte)Constants.Length);
|
||||||
|
io.WriteByte(Flags);
|
||||||
|
foreach (var c in Constants)
|
||||||
|
{
|
||||||
|
io.WriteUInt16(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
server/tso.files/Formats/IFF/Chunks/BHAV.cs
Executable file
128
server/tso.files/Formats/IFF/Chunks/BHAV.cs
Executable file
|
@ -0,0 +1,128 @@
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type holds Behavior code in SimAntics.
|
||||||
|
/// </summary>
|
||||||
|
public class BHAV : IffChunk
|
||||||
|
{
|
||||||
|
public TREE RuntimeTree;
|
||||||
|
public BHAVInstruction[] Instructions = new BHAVInstruction[0];
|
||||||
|
public byte Type;
|
||||||
|
public byte Args;
|
||||||
|
public ushort Locals;
|
||||||
|
public ushort Flags;
|
||||||
|
|
||||||
|
public uint RuntimeVer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a BHAV from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream instance holding a BHAV chunk.</param>
|
||||||
|
public override void Read(IffFile iff, System.IO.Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var version = io.ReadUInt16();
|
||||||
|
uint count = 0;
|
||||||
|
|
||||||
|
if (version == 0x8000)
|
||||||
|
{
|
||||||
|
count = io.ReadUInt16();
|
||||||
|
io.Skip(8);
|
||||||
|
}
|
||||||
|
else if (version == 0x8001)
|
||||||
|
{
|
||||||
|
count = io.ReadUInt16();
|
||||||
|
var unknown = io.ReadBytes(8);
|
||||||
|
}
|
||||||
|
else if (version == 0x8002)
|
||||||
|
{
|
||||||
|
count = io.ReadUInt16();
|
||||||
|
this.Type = io.ReadByte();
|
||||||
|
this.Args = io.ReadByte();
|
||||||
|
this.Locals = io.ReadUInt16();
|
||||||
|
this.Flags = io.ReadUInt16();
|
||||||
|
io.Skip(2);
|
||||||
|
}
|
||||||
|
else if (version == 0x8003)
|
||||||
|
{
|
||||||
|
this.Type = io.ReadByte();
|
||||||
|
this.Args = io.ReadByte();
|
||||||
|
this.Locals = io.ReadByte();
|
||||||
|
io.Skip(2);
|
||||||
|
this.Flags = io.ReadUInt16();
|
||||||
|
count = io.ReadUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
Instructions = new BHAVInstruction[count];
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var instruction = new BHAVInstruction();
|
||||||
|
instruction.Opcode = io.ReadUInt16();
|
||||||
|
instruction.TruePointer = io.ReadByte();
|
||||||
|
instruction.FalsePointer = io.ReadByte();
|
||||||
|
instruction.Operand = io.ReadBytes(8);
|
||||||
|
Instructions[i] = instruction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
if (IffFile.TargetTS1)
|
||||||
|
{ //version 0x8002
|
||||||
|
io.WriteUInt16(0x8002);
|
||||||
|
io.WriteUInt16((ushort)Instructions.Length);
|
||||||
|
io.WriteByte(Type);
|
||||||
|
io.WriteByte(Args);
|
||||||
|
io.WriteUInt16(Locals);
|
||||||
|
io.WriteUInt16(Flags);
|
||||||
|
io.WriteBytes(new byte[] { 0, 0 });
|
||||||
|
|
||||||
|
foreach (var inst in Instructions)
|
||||||
|
{
|
||||||
|
io.WriteUInt16(inst.Opcode);
|
||||||
|
io.WriteByte(inst.TruePointer);
|
||||||
|
io.WriteByte(inst.FalsePointer);
|
||||||
|
io.WriteBytes(inst.Operand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
io.WriteUInt16(0x8003);
|
||||||
|
io.WriteByte(Type);
|
||||||
|
io.WriteByte(Args);
|
||||||
|
io.WriteByte((byte)Locals);
|
||||||
|
io.WriteBytes(new byte[] { 0, 0 });
|
||||||
|
io.WriteUInt16(Flags);
|
||||||
|
io.WriteUInt32((ushort)Instructions.Length);
|
||||||
|
|
||||||
|
foreach (var inst in Instructions)
|
||||||
|
{
|
||||||
|
io.WriteUInt16(inst.Opcode);
|
||||||
|
io.WriteByte(inst.TruePointer);
|
||||||
|
io.WriteByte(inst.FalsePointer);
|
||||||
|
io.WriteBytes(inst.Operand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BHAVInstruction
|
||||||
|
{
|
||||||
|
public ushort Opcode;
|
||||||
|
public byte TruePointer;
|
||||||
|
public byte FalsePointer;
|
||||||
|
public byte[] Operand;
|
||||||
|
public bool Breakpoint; //only used at runtime
|
||||||
|
}
|
||||||
|
}
|
39
server/tso.files/Formats/IFF/Chunks/BMP.cs
Executable file
39
server/tso.files/Formats/IFF/Chunks/BMP.cs
Executable file
|
@ -0,0 +1,39 @@
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type holds an image in BMP format.
|
||||||
|
/// </summary>
|
||||||
|
public class BMP : IffChunk
|
||||||
|
{
|
||||||
|
public byte[] data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a BMP chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a BMP chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
data = new byte[stream.Length];
|
||||||
|
stream.Read(data, 0, (int)stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Texture2D GetTexture(GraphicsDevice device)
|
||||||
|
{
|
||||||
|
var tex = ImageLoader.FromStream(device, new MemoryStream(data));
|
||||||
|
return tex;
|
||||||
|
//return Texture2D.FromStream(device, new MemoryStream(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
stream.Write(data, 0, data.Length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
98
server/tso.files/Formats/IFF/Chunks/CARR.cs
Executable file
98
server/tso.files/Formats/IFF/Chunks/CARR.cs
Executable file
|
@ -0,0 +1,98 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class CARR : IffChunk
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public JobLevel[] JobLevels;
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.ReadUInt32(); //pad
|
||||||
|
var version = io.ReadUInt32();
|
||||||
|
|
||||||
|
var MjbO = io.ReadUInt32();
|
||||||
|
|
||||||
|
var compressionCode = io.ReadByte();
|
||||||
|
if (compressionCode != 1) throw new Exception("hey what!!");
|
||||||
|
|
||||||
|
Name = io.ReadNullTerminatedString();
|
||||||
|
if (Name.Length % 2 == 1) io.ReadByte();
|
||||||
|
var iop = new IffFieldEncode(io);
|
||||||
|
|
||||||
|
|
||||||
|
var numLevels = iop.ReadInt32();
|
||||||
|
|
||||||
|
JobLevels = new JobLevel[numLevels];
|
||||||
|
for (int i=0; i<numLevels; i++)
|
||||||
|
{
|
||||||
|
JobLevels[i] = new JobLevel(iop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetJobData(int level, int data)
|
||||||
|
{
|
||||||
|
var entry = JobLevels[level];
|
||||||
|
switch (data)
|
||||||
|
{
|
||||||
|
case 0: //number of levels
|
||||||
|
return JobLevels.Length;
|
||||||
|
case 1: //salary
|
||||||
|
return entry.Salary;
|
||||||
|
case 12: //start hour
|
||||||
|
return entry.StartTime;
|
||||||
|
case 13:
|
||||||
|
return entry.EndTime;
|
||||||
|
case 21:
|
||||||
|
return entry.CarType;
|
||||||
|
case 22:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
if (data < 12)
|
||||||
|
return entry.MinRequired[data-2];
|
||||||
|
else
|
||||||
|
return entry.MotiveDelta[data-14];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JobLevel
|
||||||
|
{
|
||||||
|
public int[] MinRequired = new int[10]; //friends, then skills.
|
||||||
|
public int[] MotiveDelta = new int[7];
|
||||||
|
public int Salary;
|
||||||
|
public int StartTime;
|
||||||
|
public int EndTime;
|
||||||
|
public int CarType;
|
||||||
|
|
||||||
|
public string JobName;
|
||||||
|
public string MaleUniformMesh;
|
||||||
|
public string FemaleUniformMesh;
|
||||||
|
public string UniformSkin;
|
||||||
|
public string unknown;
|
||||||
|
|
||||||
|
public JobLevel(IffFieldEncode iop)
|
||||||
|
{
|
||||||
|
for (int i=0; i<MinRequired.Length; i++)
|
||||||
|
MinRequired[i] = iop.ReadInt32();
|
||||||
|
for (int i = 0; i < MotiveDelta.Length; i++)
|
||||||
|
MotiveDelta[i] = iop.ReadInt32();
|
||||||
|
Salary = iop.ReadInt32();
|
||||||
|
StartTime = iop.ReadInt32();
|
||||||
|
EndTime = iop.ReadInt32();
|
||||||
|
CarType = iop.ReadInt32();
|
||||||
|
|
||||||
|
JobName = iop.ReadString(false);
|
||||||
|
MaleUniformMesh = iop.ReadString(false);
|
||||||
|
FemaleUniformMesh = iop.ReadString(false);
|
||||||
|
UniformSkin = iop.ReadString(false);
|
||||||
|
unknown = iop.ReadString(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
server/tso.files/Formats/IFF/Chunks/CTSS.cs
Executable file
9
server/tso.files/Formats/IFF/Chunks/CTSS.cs
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Catalog text strings; equivalent in format to STR#.
|
||||||
|
/// </summary>
|
||||||
|
public class CTSS : STR
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
324
server/tso.files/Formats/IFF/Chunks/DGRP.cs
Executable file
324
server/tso.files/Formats/IFF/Chunks/DGRP.cs
Executable file
|
@ -0,0 +1,324 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type collects SPR# and SPR2 resources into a "drawing group" which
|
||||||
|
/// can be used to display one tile of an object from all directions and zoom levels.
|
||||||
|
/// Objects which span across multiple tiles have a separate DGRP chunk for each tile.
|
||||||
|
/// A DGRP chunk always consists of 12 images (one for every direction/zoom level combination),
|
||||||
|
/// which in turn contain info about one or more sprites.
|
||||||
|
/// </summary>
|
||||||
|
public class DGRP : IffChunk
|
||||||
|
{
|
||||||
|
public DGRPImage[] Images { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a DGRPImage instance from this DGRP instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="direction">The direction the DGRP is facing.</param>
|
||||||
|
/// <param name="zoom">Zoom level DGRP is drawn at.</param>
|
||||||
|
/// <param name="worldRotation">Current rotation of world.</param>
|
||||||
|
/// <returns>A DGRPImage instance.</returns>
|
||||||
|
public DGRPImage GetImage(uint direction, uint zoom, uint worldRotation){
|
||||||
|
|
||||||
|
uint rotatedDirection = 0;
|
||||||
|
|
||||||
|
/**LeftFront = 0x10,
|
||||||
|
LeftBack = 0x40,
|
||||||
|
RightFront = 0x04,
|
||||||
|
RightBack = 0x01**/
|
||||||
|
int rotateBits = (int)direction << ((int)worldRotation * 2);
|
||||||
|
rotatedDirection = (uint)((rotateBits & 255) | (rotateBits >> 8));
|
||||||
|
|
||||||
|
foreach(DGRPImage image in Images)
|
||||||
|
{
|
||||||
|
if (image.Direction == rotatedDirection && image.Zoom == zoom)
|
||||||
|
{
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a DGRP from a stream instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream instance holding a DGRP chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var version = io.ReadUInt16();
|
||||||
|
uint imageCount = version < 20003 ? io.ReadUInt16() : io.ReadUInt32();
|
||||||
|
Images = new DGRPImage[imageCount];
|
||||||
|
|
||||||
|
for (var i = 0; i < imageCount; i++)
|
||||||
|
{
|
||||||
|
var image = new DGRPImage(this);
|
||||||
|
image.Read(version, io);
|
||||||
|
Images[i] = image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteUInt16(20004);
|
||||||
|
io.WriteUInt32((uint)Images.Length);
|
||||||
|
|
||||||
|
foreach (var img in Images)
|
||||||
|
{
|
||||||
|
img.Write(io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A DGRP is made up of multiple DGRPImages,
|
||||||
|
/// which are made up of multiple DGRPSprites.
|
||||||
|
/// </summary>
|
||||||
|
public class DGRPImage
|
||||||
|
{
|
||||||
|
private DGRP Parent;
|
||||||
|
public uint Direction;
|
||||||
|
public uint Zoom;
|
||||||
|
public DGRPSprite[] Sprites;
|
||||||
|
|
||||||
|
public DGRPImage(DGRP parent)
|
||||||
|
{
|
||||||
|
this.Parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a DGRPImage from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a DGRPImage.</param>
|
||||||
|
public void Read(uint version, IoBuffer io)
|
||||||
|
{
|
||||||
|
uint spriteCount = 0;
|
||||||
|
if (version < 20003){
|
||||||
|
spriteCount = io.ReadUInt16();
|
||||||
|
Direction = io.ReadByte();
|
||||||
|
Zoom = io.ReadByte();
|
||||||
|
}else{
|
||||||
|
Direction = io.ReadUInt32();
|
||||||
|
Zoom = io.ReadUInt32();
|
||||||
|
spriteCount = io.ReadUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Sprites = new DGRPSprite[spriteCount];
|
||||||
|
for (var i = 0; i < spriteCount; i++){
|
||||||
|
var sprite = new DGRPSprite(Parent);
|
||||||
|
sprite.Read(version, io);
|
||||||
|
this.Sprites[i] = sprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(IoWriter io)
|
||||||
|
{
|
||||||
|
io.WriteUInt32(Direction);
|
||||||
|
io.WriteUInt32(Zoom);
|
||||||
|
io.WriteUInt32((uint)Sprites.Length);
|
||||||
|
foreach (var spr in Sprites)
|
||||||
|
{
|
||||||
|
spr.Write(io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum DGRPSpriteFlags
|
||||||
|
{
|
||||||
|
Flip = 0x1,
|
||||||
|
Unknown = 0x2, //set for end table
|
||||||
|
Luminous = 0x4,
|
||||||
|
Unknown2 = 0x8,
|
||||||
|
Unknown3 = 0x10 //set for end table
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes up a DGRPImage.
|
||||||
|
/// </summary>
|
||||||
|
public class DGRPSprite : ITextureProvider, IWorldTextureProvider
|
||||||
|
{
|
||||||
|
private DGRP Parent;
|
||||||
|
public uint SpriteID;
|
||||||
|
public uint SpriteFrameIndex;
|
||||||
|
public DGRPSpriteFlags Flags;
|
||||||
|
|
||||||
|
public Vector2 SpriteOffset;
|
||||||
|
public Vector3 ObjectOffset;
|
||||||
|
|
||||||
|
public bool Flip {
|
||||||
|
get { return (Flags & DGRPSpriteFlags.Flip) > 0; }
|
||||||
|
set {
|
||||||
|
Flags = Flags & (~DGRPSpriteFlags.Flip);
|
||||||
|
if (value) Flags |= DGRPSpriteFlags.Flip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Luminous
|
||||||
|
{
|
||||||
|
get { return (Flags & DGRPSpriteFlags.Luminous) > 0; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Flags = Flags & (~DGRPSpriteFlags.Luminous);
|
||||||
|
if (value) Flags |= DGRPSpriteFlags.Luminous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DGRPSprite(DGRP parent)
|
||||||
|
{
|
||||||
|
this.Parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a DGRPSprite from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a DGRPSprite.</param>
|
||||||
|
public void Read(uint version, IoBuffer io)
|
||||||
|
{
|
||||||
|
if (version < 20003)
|
||||||
|
{
|
||||||
|
//Unknown ignored "Type" field
|
||||||
|
var type = io.ReadUInt16();
|
||||||
|
SpriteID = io.ReadUInt16();
|
||||||
|
SpriteFrameIndex = io.ReadUInt16();
|
||||||
|
|
||||||
|
var flagsRaw = io.ReadUInt16();
|
||||||
|
Flags = (DGRPSpriteFlags)flagsRaw;
|
||||||
|
|
||||||
|
SpriteOffset.X = io.ReadInt16();
|
||||||
|
SpriteOffset.Y = io.ReadInt16();
|
||||||
|
|
||||||
|
if (version == 20001)
|
||||||
|
{
|
||||||
|
ObjectOffset.Z = io.ReadFloat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SpriteID = io.ReadUInt32();
|
||||||
|
SpriteFrameIndex = io.ReadUInt32();
|
||||||
|
SpriteOffset.X = io.ReadInt32();
|
||||||
|
SpriteOffset.Y = io.ReadInt32();
|
||||||
|
ObjectOffset.Z = io.ReadFloat();
|
||||||
|
Flags = (DGRPSpriteFlags)io.ReadUInt32();
|
||||||
|
if (version == 20004)
|
||||||
|
{
|
||||||
|
ObjectOffset.X = io.ReadFloat();
|
||||||
|
ObjectOffset.Y = io.ReadFloat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(IoWriter io)
|
||||||
|
{
|
||||||
|
io.WriteUInt32(SpriteID);
|
||||||
|
io.WriteUInt32(SpriteFrameIndex);
|
||||||
|
io.WriteInt32((int)SpriteOffset.X);
|
||||||
|
io.WriteInt32((int)SpriteOffset.Y);
|
||||||
|
io.WriteFloat(ObjectOffset.Z);
|
||||||
|
io.WriteUInt32((uint)Flags);
|
||||||
|
io.WriteFloat(ObjectOffset.X);
|
||||||
|
io.WriteFloat(ObjectOffset.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets position of this sprite.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A Vector2 instance holding position of this sprite.</returns>
|
||||||
|
public Vector2 GetPosition()
|
||||||
|
{
|
||||||
|
var iff = Parent.ChunkParent;
|
||||||
|
var spr2 = iff.Get<SPR2>((ushort)this.SpriteID);
|
||||||
|
if (spr2 != null)
|
||||||
|
{
|
||||||
|
return spr2.Frames[this.SpriteFrameIndex].Position;
|
||||||
|
}
|
||||||
|
return new Vector2(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ITextureProvider Members
|
||||||
|
|
||||||
|
public Microsoft.Xna.Framework.Graphics.Texture2D GetTexture(Microsoft.Xna.Framework.Graphics.GraphicsDevice device){
|
||||||
|
var iff = Parent.ChunkParent;
|
||||||
|
var spr2 = iff.Get<SPR2>((ushort)this.SpriteID);
|
||||||
|
if (spr2 != null){
|
||||||
|
return spr2.Frames[this.SpriteFrameIndex].GetTexture(device);
|
||||||
|
}
|
||||||
|
var spr1 = iff.Get<SPR>((ushort)this.SpriteID);
|
||||||
|
if (spr1 != null){
|
||||||
|
return spr1.Frames[(int)this.SpriteFrameIndex].GetTexture(device);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IWorldTextureProvider Members
|
||||||
|
|
||||||
|
public byte[] GetDepth()
|
||||||
|
{
|
||||||
|
var iff = Parent.ChunkParent;
|
||||||
|
var spr2 = iff.Get<SPR2>((ushort)this.SpriteID);
|
||||||
|
if (spr2 != null)
|
||||||
|
{
|
||||||
|
spr2.Frames[this.SpriteFrameIndex].DecodeIfRequired(true);
|
||||||
|
var buf = spr2.Frames[this.SpriteFrameIndex].ZBufferData;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldTexture GetWorldTexture(Microsoft.Xna.Framework.Graphics.GraphicsDevice device)
|
||||||
|
{
|
||||||
|
var iff = Parent.ChunkParent;
|
||||||
|
var spr2 = iff.Get<SPR2>((ushort)this.SpriteID);
|
||||||
|
if (spr2 != null)
|
||||||
|
{
|
||||||
|
return spr2.Frames[this.SpriteFrameIndex].GetWorldTexture(device);
|
||||||
|
}
|
||||||
|
var spr1 = iff.Get<SPR>((ushort)this.SpriteID);
|
||||||
|
if (spr1 != null)
|
||||||
|
{
|
||||||
|
var result = new WorldTexture();
|
||||||
|
result.Pixel = spr1.Frames[(int)this.SpriteFrameIndex].GetTexture(device);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point GetDimensions()
|
||||||
|
{
|
||||||
|
var iff = Parent.ChunkParent;
|
||||||
|
var spr2 = iff.Get<SPR2>((ushort)this.SpriteID);
|
||||||
|
if (spr2 != null)
|
||||||
|
{
|
||||||
|
var frame = spr2.Frames[this.SpriteFrameIndex];
|
||||||
|
return new Point(frame.Width, frame.Height);
|
||||||
|
}
|
||||||
|
var spr1 = iff.Get<SPR>((ushort)this.SpriteID);
|
||||||
|
if (spr1 != null)
|
||||||
|
{
|
||||||
|
var result = new WorldTexture();
|
||||||
|
var frame = spr1.Frames[(int)this.SpriteFrameIndex];
|
||||||
|
return new Point(frame.Width, frame.Height);
|
||||||
|
}
|
||||||
|
return new Point(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
102
server/tso.files/Formats/IFF/Chunks/FAMI.cs
Executable file
102
server/tso.files/Formats/IFF/Chunks/FAMI.cs
Executable file
|
@ -0,0 +1,102 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class defines a single family in the neighbourhood, and various properties such as their
|
||||||
|
/// budget and house assignment. These can be modified using GenericTS1Calls, but are mainly
|
||||||
|
/// defined within CAS.
|
||||||
|
/// </summary>
|
||||||
|
public class FAMI : IffChunk
|
||||||
|
{
|
||||||
|
public uint Version = 0x9;
|
||||||
|
|
||||||
|
public int HouseNumber;
|
||||||
|
//this is not a typical family number - it is unique between user created families, but -1 for townies.
|
||||||
|
//i believe it is an alternate family UID that basically runs on an auto increment to obtain its value.
|
||||||
|
//(in comparison with the ChunkID as family that is used ingame, which appears to fill spaces as they are left)
|
||||||
|
public int FamilyNumber;
|
||||||
|
public int Budget;
|
||||||
|
public int ValueInArch;
|
||||||
|
public int FamilyFriends;
|
||||||
|
public int Unknown; //19, 17 or 1? could be flags, (1, 16, 2) ... 0 for townies. 24 for CAS created (new 16+8?)
|
||||||
|
//1: in house
|
||||||
|
//2: unknown, but is set sometimes
|
||||||
|
//4: unknown
|
||||||
|
//8: user created?
|
||||||
|
//16: in cas
|
||||||
|
|
||||||
|
public uint[] FamilyGUIDs = new uint[] { };
|
||||||
|
|
||||||
|
public uint[] RuntimeSubset = new uint[] { }; //the members of this family currently active. don't save!
|
||||||
|
|
||||||
|
public void SelectWholeFamily()
|
||||||
|
{
|
||||||
|
RuntimeSubset = FamilyGUIDs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectOneMember(uint guid)
|
||||||
|
{
|
||||||
|
RuntimeSubset = new uint[] { guid };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a FAMI chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a OBJf chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.ReadUInt32(); //pad
|
||||||
|
Version = io.ReadUInt32(); //0x9 for latest game
|
||||||
|
string magic = io.ReadCString(4); //IMAF
|
||||||
|
|
||||||
|
HouseNumber = io.ReadInt32();
|
||||||
|
FamilyNumber = io.ReadInt32();
|
||||||
|
Budget = io.ReadInt32();
|
||||||
|
ValueInArch = io.ReadInt32();
|
||||||
|
FamilyFriends = io.ReadInt32();
|
||||||
|
Unknown = io.ReadInt32();
|
||||||
|
FamilyGUIDs = new uint[io.ReadInt32()];
|
||||||
|
for (int i=0; i<FamilyGUIDs.Length; i++)
|
||||||
|
{
|
||||||
|
FamilyGUIDs[i] = io.ReadUInt32();
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
io.ReadInt32();
|
||||||
|
} catch
|
||||||
|
{
|
||||||
|
//for some reason FAMI "Default" only has 3 zeroes after it, but only if saved by base game.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteInt32(0);
|
||||||
|
io.WriteUInt32(9);
|
||||||
|
io.WriteCString("IMAF", 4);
|
||||||
|
io.WriteInt32(HouseNumber);
|
||||||
|
io.WriteInt32(FamilyNumber);
|
||||||
|
io.WriteInt32(Budget);
|
||||||
|
io.WriteInt32(ValueInArch);
|
||||||
|
io.WriteInt32(FamilyFriends);
|
||||||
|
io.WriteInt32(Unknown);
|
||||||
|
io.WriteInt32(FamilyGUIDs.Length);
|
||||||
|
foreach (var guid in FamilyGUIDs)
|
||||||
|
io.WriteUInt32(guid);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
io.WriteInt32(0);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
server/tso.files/Formats/IFF/Chunks/FAMs.cs
Executable file
6
server/tso.files/Formats/IFF/Chunks/FAMs.cs
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class FAMs : STR
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
56
server/tso.files/Formats/IFF/Chunks/FCNS.cs
Executable file
56
server/tso.files/Formats/IFF/Chunks/FCNS.cs
Executable file
|
@ -0,0 +1,56 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Duplicate of STR chunk, instead used for simulator constants.
|
||||||
|
/// </summary>
|
||||||
|
public class FCNS : STR
|
||||||
|
{
|
||||||
|
//no difference!
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var zero = io.ReadInt32();
|
||||||
|
var version = io.ReadInt32(); //2 in tso
|
||||||
|
string magic = io.ReadCString(4); //NSCF
|
||||||
|
var count = io.ReadInt32();
|
||||||
|
|
||||||
|
LanguageSets[0].Strings = new STRItem[count];
|
||||||
|
for (int i=0; i<count; i++)
|
||||||
|
{
|
||||||
|
string name, desc;
|
||||||
|
float value;
|
||||||
|
if (version == 2)
|
||||||
|
{
|
||||||
|
name = io.ReadVariableLengthPascalString();
|
||||||
|
value = io.ReadFloat();
|
||||||
|
desc = io.ReadVariableLengthPascalString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
name = io.ReadNullTerminatedString();
|
||||||
|
if (name.Length % 2 == 0) io.ReadByte(); //padding to 2 byte align
|
||||||
|
value = io.ReadFloat();
|
||||||
|
desc = io.ReadNullTerminatedString();
|
||||||
|
if (desc.Length % 2 == 0) io.ReadByte(); //padding to 2 byte align
|
||||||
|
}
|
||||||
|
|
||||||
|
LanguageSets[0].Strings[i] = new STRItem()
|
||||||
|
{
|
||||||
|
Value = name + ": " + value,
|
||||||
|
Comment = desc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
server/tso.files/Formats/IFF/Chunks/FSOM.cs
Executable file
57
server/tso.files/Formats/IFF/Chunks/FSOM.cs
Executable file
|
@ -0,0 +1,57 @@
|
||||||
|
using FSO.Files.RC;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Iff chunk wrapper for an FSOM file.
|
||||||
|
/// </summary>
|
||||||
|
public class FSOM : IffChunk
|
||||||
|
{
|
||||||
|
private byte[] data;
|
||||||
|
private DGRP3DMesh Cached;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a BMP chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a BMP chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
data = new byte[stream.Length];
|
||||||
|
stream.Read(data, 0, (int)stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
using (var cstream = new GZipStream(stream, CompressionMode.Compress))
|
||||||
|
Cached.Save(cstream);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
stream.Write(data, 0, data.Length);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DGRP3DMesh Get(DGRP dgrp, GraphicsDevice device)
|
||||||
|
{
|
||||||
|
if (Cached == null) {
|
||||||
|
using (var stream = new MemoryStream(data)) {
|
||||||
|
Cached = new DGRP3DMesh(dgrp, stream, device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = null;
|
||||||
|
return Cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMesh(DGRP3DMesh mesh)
|
||||||
|
{
|
||||||
|
Cached = mesh;
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
server/tso.files/Formats/IFF/Chunks/FSOR.cs
Executable file
35
server/tso.files/Formats/IFF/Chunks/FSOR.cs
Executable file
|
@ -0,0 +1,35 @@
|
||||||
|
using FSO.Files.RC;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Metadata for an object's mesh reconstruction. Currently only supports file-wise parameters.
|
||||||
|
/// </summary>
|
||||||
|
public class FSOR : IffChunk
|
||||||
|
{
|
||||||
|
public static int CURRENT_VERSION = 1;
|
||||||
|
public int Version = CURRENT_VERSION;
|
||||||
|
public DGRPRCParams Params = new DGRPRCParams();
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
Version = io.ReadInt32();
|
||||||
|
Params = new DGRPRCParams(io, Version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteInt32(Version);
|
||||||
|
Params.Save(io);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
server/tso.files/Formats/IFF/Chunks/FSOV.cs
Executable file
36
server/tso.files/Formats/IFF/Chunks/FSOV.cs
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A simple container for FSOV data within an iff. If this exists, normal TS1 iff loading is subverted.
|
||||||
|
/// </summary>
|
||||||
|
public class FSOV : IffChunk
|
||||||
|
{
|
||||||
|
public static int CURRENT_VERSION = 1;
|
||||||
|
public int Version = CURRENT_VERSION;
|
||||||
|
public byte[] Data;
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
Version = io.ReadInt32();
|
||||||
|
var length = io.ReadInt32();
|
||||||
|
Data = io.ReadBytes(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteInt32(Version);
|
||||||
|
io.WriteInt32(Data.Length);
|
||||||
|
io.WriteBytes(Data);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
server/tso.files/Formats/IFF/Chunks/FWAV.cs
Executable file
35
server/tso.files/Formats/IFF/Chunks/FWAV.cs
Executable file
|
@ -0,0 +1,35 @@
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type holds the name of a sound event that this object uses.
|
||||||
|
/// </summary>
|
||||||
|
public class FWAV : IffChunk
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a FWAV chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a FWAV chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
Name = io.ReadNullTerminatedString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteNullTerminatedString(Name);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
server/tso.files/Formats/IFF/Chunks/GLOB.cs
Executable file
52
server/tso.files/Formats/IFF/Chunks/GLOB.cs
Executable file
|
@ -0,0 +1,52 @@
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type holds the filename of a semi-global iff file used by this object.
|
||||||
|
/// </summary>
|
||||||
|
public class GLOB : IffChunk
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a GLOB chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a GLOB chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
StringBuilder temp = new StringBuilder();
|
||||||
|
var num = io.ReadByte();
|
||||||
|
if (num < 48)
|
||||||
|
{ //less than smallest ASCII value for valid filename character, so assume this is a pascal string
|
||||||
|
temp.Append(io.ReadCString(num));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ //we're actually a null terminated string!
|
||||||
|
temp.Append((char)num);
|
||||||
|
while (stream.Position < stream.Length)
|
||||||
|
{
|
||||||
|
char read = (char)io.ReadByte();
|
||||||
|
if (read == 0) break;
|
||||||
|
else temp.Append(read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Name = temp.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteNullTerminatedString(Name);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
server/tso.files/Formats/IFF/Chunks/HOUS.cs
Executable file
38
server/tso.files/Formats/IFF/Chunks/HOUS.cs
Executable file
|
@ -0,0 +1,38 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class HOUS : IffChunk
|
||||||
|
{
|
||||||
|
public int Version;
|
||||||
|
public int UnknownFlag;
|
||||||
|
public int UnknownOne;
|
||||||
|
public int UnknownNumber;
|
||||||
|
public int UnknownNegative;
|
||||||
|
public short CameraDir;
|
||||||
|
public short UnknownOne2;
|
||||||
|
public short UnknownFlag2;
|
||||||
|
public uint GUID;
|
||||||
|
public string RoofName;
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var zero = io.ReadInt32();
|
||||||
|
Version = io.ReadInt32();
|
||||||
|
var suoh = io.ReadCString(4);
|
||||||
|
UnknownFlag = io.ReadInt32();
|
||||||
|
UnknownOne = io.ReadInt32();
|
||||||
|
UnknownNumber = io.ReadInt32();
|
||||||
|
UnknownNegative = io.ReadInt32();
|
||||||
|
CameraDir = io.ReadInt16();
|
||||||
|
UnknownOne2 = io.ReadInt16();
|
||||||
|
UnknownFlag2 = io.ReadInt16();
|
||||||
|
GUID = io.ReadUInt32();
|
||||||
|
RoofName = io.ReadNullTerminatedString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
server/tso.files/Formats/IFF/Chunks/ISPR.cs
Executable file
18
server/tso.files/Formats/IFF/Chunks/ISPR.cs
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
||||||
|
* If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
* http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace tso.files.formats.iff.chunks
|
||||||
|
{
|
||||||
|
public interface ISPR
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
152
server/tso.files/Formats/IFF/Chunks/IffFieldEncode.cs
Executable file
152
server/tso.files/Formats/IFF/Chunks/IffFieldEncode.cs
Executable file
|
@ -0,0 +1,152 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to read values from field encoded stream.
|
||||||
|
/// </summary>
|
||||||
|
public class IffFieldEncode : IOProxy
|
||||||
|
{
|
||||||
|
private byte bitPos = 0;
|
||||||
|
private byte curByte = 0;
|
||||||
|
private bool odd = false;
|
||||||
|
public byte[] widths = { 5, 8, 13, 16 };
|
||||||
|
public byte[] widths2 = { 6, 11, 21, 32 };
|
||||||
|
public bool StreamEnd;
|
||||||
|
|
||||||
|
public void setBytePos(int n)
|
||||||
|
{
|
||||||
|
io.Seek(SeekOrigin.Begin, n);
|
||||||
|
curByte = io.ReadByte();
|
||||||
|
bitPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ushort ReadUInt16()
|
||||||
|
{
|
||||||
|
return (ushort)ReadField(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override short ReadInt16()
|
||||||
|
{
|
||||||
|
return (short)ReadField(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int ReadInt32()
|
||||||
|
{
|
||||||
|
return (int)ReadField(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override uint ReadUInt32()
|
||||||
|
{
|
||||||
|
return (uint)ReadField(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float ReadFloat()
|
||||||
|
{
|
||||||
|
return (float)ReadField(true);
|
||||||
|
//this is incredibly wrong
|
||||||
|
}
|
||||||
|
|
||||||
|
private long ReadField(bool big)
|
||||||
|
{
|
||||||
|
if (ReadBit() == 0) return 0;
|
||||||
|
|
||||||
|
uint code = ReadBits(2);
|
||||||
|
byte width = (big) ? widths2[code] : widths[code];
|
||||||
|
long value = ReadBits(width);
|
||||||
|
value |= -(value & (1 << (width - 1)));
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tuple<long, int> DebugReadField(bool big)
|
||||||
|
{
|
||||||
|
if (ReadBit() == 0) return new Tuple<long, int>(0, 0);
|
||||||
|
|
||||||
|
uint code = ReadBits(2);
|
||||||
|
byte width = (big) ? widths2[code] : widths[code];
|
||||||
|
long value = ReadBits(width);
|
||||||
|
value |= -(value & (1 << (width - 1)));
|
||||||
|
|
||||||
|
return new Tuple<long, int>(value, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tuple<byte, byte, bool, long> MarkStream()
|
||||||
|
{
|
||||||
|
return new Tuple<byte, byte, bool, long>(bitPos, curByte, odd, io.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RevertToMark(Tuple<byte, byte, bool, long> mark)
|
||||||
|
{
|
||||||
|
StreamEnd = false;
|
||||||
|
bitPos = mark.Item1;
|
||||||
|
curByte = mark.Item2;
|
||||||
|
odd = mark.Item3;
|
||||||
|
io.Seek(SeekOrigin.Begin, mark.Item4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint ReadBits(int n)
|
||||||
|
{
|
||||||
|
uint total = 0;
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
total += (uint)(ReadBit() << ((n - i) - 1));
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte ReadBit()
|
||||||
|
{
|
||||||
|
byte result = (byte)((curByte & (1 << (7 - bitPos))) >> (7 - bitPos));
|
||||||
|
if (++bitPos > 7)
|
||||||
|
{
|
||||||
|
bitPos = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
curByte = io.ReadByte();
|
||||||
|
odd = !odd;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
curByte = 0; //no more data, read 0
|
||||||
|
odd = !odd;
|
||||||
|
StreamEnd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadString(bool nextField)
|
||||||
|
{
|
||||||
|
if (bitPos == 0)
|
||||||
|
{
|
||||||
|
io.Seek(SeekOrigin.Current, -1);
|
||||||
|
odd = !odd;
|
||||||
|
}
|
||||||
|
var str = io.ReadNullTerminatedString();
|
||||||
|
if ((str.Length % 2 == 0) == !odd) io.ReadByte(); //2 byte pad
|
||||||
|
|
||||||
|
bitPos = 8;
|
||||||
|
if (nextField && io.HasMore)
|
||||||
|
{
|
||||||
|
curByte = io.ReadByte();
|
||||||
|
odd = true;
|
||||||
|
bitPos = 0;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
odd = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IffFieldEncode(IoBuffer io) : base(io)
|
||||||
|
{
|
||||||
|
curByte = io.ReadByte();
|
||||||
|
odd = !odd;
|
||||||
|
bitPos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
server/tso.files/Formats/IFF/Chunks/MTEX.cs
Executable file
59
server/tso.files/Formats/IFF/Chunks/MTEX.cs
Executable file
|
@ -0,0 +1,59 @@
|
||||||
|
using FSO.Common;
|
||||||
|
using FSO.Common.Utils;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Texture for a 3D Mesh. Can be jpg, png or bmp.
|
||||||
|
/// </summary>
|
||||||
|
public class MTEX : IffChunk
|
||||||
|
{
|
||||||
|
private byte[] data;
|
||||||
|
private Texture2D Cached;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a BMP chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a BMP chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
data = new byte[stream.Length];
|
||||||
|
stream.Read(data, 0, (int)stream.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
stream.Write(data, 0, data.Length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Texture2D GetTexture(GraphicsDevice device)
|
||||||
|
{
|
||||||
|
if (Cached == null)
|
||||||
|
{
|
||||||
|
Cached = ImageLoader.FromStream(device, new MemoryStream(data));
|
||||||
|
if (FSOEnvironment.EnableNPOTMip)
|
||||||
|
{
|
||||||
|
var data = new Color[Cached.Width * Cached.Height];
|
||||||
|
Cached.GetData(data);
|
||||||
|
var n = new Texture2D(device, Cached.Width, Cached.Height, true, SurfaceFormat.Color);
|
||||||
|
TextureUtils.UploadWithMips(n, device, data);
|
||||||
|
Cached.Dispose();
|
||||||
|
Cached = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!IffFile.RETAIN_CHUNK_DATA) data = null;
|
||||||
|
return Cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetData(byte[] data)
|
||||||
|
{
|
||||||
|
this.data = data;
|
||||||
|
Cached = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
224
server/tso.files/Formats/IFF/Chunks/NBRS.cs
Executable file
224
server/tso.files/Formats/IFF/Chunks/NBRS.cs
Executable file
|
@ -0,0 +1,224 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk defines all neighbours in a neighbourhood.
|
||||||
|
/// A neighbour is a specific version of a sim object with associated relationships and person data. (skills, person type)
|
||||||
|
///
|
||||||
|
/// These can be read within SimAntics without the avatar actually present. This is used to find and spawn suitable sims on
|
||||||
|
/// ped portals as visitors, and also drive phone calls to other sims in the neighbourhood.
|
||||||
|
/// When neighbours are spawned, they assume the attributes saved here. A TS1 global call allows the game to save these attributes.
|
||||||
|
/// </summary>
|
||||||
|
public class NBRS : IffChunk
|
||||||
|
{
|
||||||
|
public List<Neighbour> Entries = new List<Neighbour>();
|
||||||
|
public Dictionary<short, Neighbour> NeighbourByID = new Dictionary<short, Neighbour>();
|
||||||
|
public Dictionary<uint, short> DefaultNeighbourByGUID = new Dictionary<uint, short>();
|
||||||
|
|
||||||
|
public uint Version;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a NBRS chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a NBRS chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.ReadUInt32(); //pad
|
||||||
|
Version = io.ReadUInt32(); //0x49 for latest game
|
||||||
|
string magic = io.ReadCString(4); //SRBN
|
||||||
|
var count = io.ReadUInt32();
|
||||||
|
|
||||||
|
for (int i=0; i<count; i++)
|
||||||
|
{
|
||||||
|
if (!io.HasMore) break;
|
||||||
|
var neigh = new Neighbour(io);
|
||||||
|
Entries.Add(neigh);
|
||||||
|
if (neigh.Unknown1 > 0)
|
||||||
|
{
|
||||||
|
NeighbourByID.Add(neigh.NeighbourID, neigh);
|
||||||
|
DefaultNeighbourByGUID[neigh.GUID] = neigh.NeighbourID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entries = Entries.OrderBy(x => x.NeighbourID).ToList();
|
||||||
|
foreach (var entry in Entries)
|
||||||
|
entry.RuntimeIndex = Entries.IndexOf(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a NBRS chunk to a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A destination stream.</param>
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteUInt32(0);
|
||||||
|
io.WriteUInt32(0x49);
|
||||||
|
io.WriteCString("SRBN", 4);
|
||||||
|
io.WriteInt32(Entries.Count);
|
||||||
|
foreach (var n in NeighbourByID.Values)
|
||||||
|
{
|
||||||
|
n.Save(io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddNeighbor(Neighbour nb) {
|
||||||
|
Entries.Add(nb);
|
||||||
|
Entries = Entries.OrderBy(x => x.NeighbourID).ToList();
|
||||||
|
foreach (var entry in Entries)
|
||||||
|
entry.RuntimeIndex = Entries.IndexOf(entry);
|
||||||
|
|
||||||
|
NeighbourByID.Add(nb.NeighbourID, nb);
|
||||||
|
DefaultNeighbourByGUID[nb.GUID] = nb.NeighbourID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short GetFreeID()
|
||||||
|
{
|
||||||
|
//find the lowest id that is free
|
||||||
|
short newID = 1;
|
||||||
|
for (int i = 0; i < Entries.Count; i++)
|
||||||
|
{
|
||||||
|
if (Entries[i].NeighbourID == newID) newID++;
|
||||||
|
else if (Entries[i].NeighbourID < newID) continue;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
return newID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Neighbour
|
||||||
|
{
|
||||||
|
public int Unknown1 = 1; //1
|
||||||
|
public int Version = 0xA; //0x4, 0xA
|
||||||
|
//if 0xA, unknown3 follows
|
||||||
|
//0x4 indicates person data size of 0xa0.. (160 bytes, or 80 entries)
|
||||||
|
public int Unknown3 = 9; //9
|
||||||
|
public string Name;
|
||||||
|
public int MysteryZero = 0;
|
||||||
|
public int PersonMode; //0/5/9
|
||||||
|
public short[] PersonData; //can be null
|
||||||
|
|
||||||
|
public short NeighbourID;
|
||||||
|
public uint GUID;
|
||||||
|
public int UnknownNegOne = -1; //negative 1 usually
|
||||||
|
|
||||||
|
public Dictionary<int, List<short>> Relationships;
|
||||||
|
|
||||||
|
public int RuntimeIndex; //used for fast continuation of Set to Next
|
||||||
|
|
||||||
|
public Neighbour() { }
|
||||||
|
|
||||||
|
public Neighbour(IoBuffer io)
|
||||||
|
{
|
||||||
|
Unknown1 = io.ReadInt32();
|
||||||
|
if (Unknown1 != 1) { return; }
|
||||||
|
Version = io.ReadInt32();
|
||||||
|
if (Version == 0xA)
|
||||||
|
{
|
||||||
|
//TODO: what version does this truly start?
|
||||||
|
Unknown3 = io.ReadInt32();
|
||||||
|
if (Unknown3 != 9) { }
|
||||||
|
}
|
||||||
|
Name = io.ReadNullTerminatedString();
|
||||||
|
if (Name.Length % 2 == 0) io.ReadByte();
|
||||||
|
MysteryZero = io.ReadInt32();
|
||||||
|
if (MysteryZero != 0) { }
|
||||||
|
PersonMode = io.ReadInt32();
|
||||||
|
if (PersonMode > 0)
|
||||||
|
{
|
||||||
|
var size = (Version == 0x4) ? 0xa0 : 0x200;
|
||||||
|
PersonData = new short[88];
|
||||||
|
int pdi = 0;
|
||||||
|
for (int i=0; i<size; i+=2)
|
||||||
|
{
|
||||||
|
if (pdi >= 88)
|
||||||
|
{
|
||||||
|
io.ReadBytes(size - i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
PersonData[pdi++] = io.ReadInt16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NeighbourID = io.ReadInt16();
|
||||||
|
GUID = io.ReadUInt32();
|
||||||
|
UnknownNegOne = io.ReadInt32();
|
||||||
|
if (UnknownNegOne != -1) { }
|
||||||
|
|
||||||
|
var entries = io.ReadInt32();
|
||||||
|
Relationships = new Dictionary<int, List<short>>();
|
||||||
|
for (int i=0; i<entries; i++)
|
||||||
|
{
|
||||||
|
var keyCount = io.ReadInt32();
|
||||||
|
if (keyCount != 1) { }
|
||||||
|
var key = io.ReadInt32();
|
||||||
|
var values = new List<short>();
|
||||||
|
var valueCount = io.ReadInt32();
|
||||||
|
for (int j=0; j<valueCount; j++)
|
||||||
|
{
|
||||||
|
values.Add((short)io.ReadInt32());
|
||||||
|
}
|
||||||
|
Relationships.Add(key, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(IoWriter io)
|
||||||
|
{
|
||||||
|
io.WriteInt32(Unknown1);
|
||||||
|
io.WriteInt32(Version);
|
||||||
|
if (Version == 0xA) io.WriteInt32(Unknown3);
|
||||||
|
io.WriteNullTerminatedString(Name);
|
||||||
|
if (Name.Length % 2 == 0) io.WriteByte(0);
|
||||||
|
io.WriteInt32(MysteryZero);
|
||||||
|
io.WriteInt32(PersonMode);
|
||||||
|
if (PersonMode > 0)
|
||||||
|
{
|
||||||
|
var size = (Version == 0x4) ? 0xa0 : 0x200;
|
||||||
|
int pdi = 0;
|
||||||
|
for (int i = 0; i < size; i += 2)
|
||||||
|
{
|
||||||
|
if (pdi >= 88)
|
||||||
|
{
|
||||||
|
io.WriteInt16(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
io.WriteInt16(PersonData[pdi++]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteInt16(NeighbourID);
|
||||||
|
io.WriteUInt32(GUID);
|
||||||
|
io.WriteInt32(UnknownNegOne);
|
||||||
|
|
||||||
|
io.WriteInt32(Relationships.Count);
|
||||||
|
foreach (var rel in Relationships)
|
||||||
|
{
|
||||||
|
io.WriteInt32(1); //keycount (1)
|
||||||
|
io.WriteInt32(rel.Key);
|
||||||
|
io.WriteInt32(rel.Value.Count);
|
||||||
|
foreach (var val in rel.Value)
|
||||||
|
{
|
||||||
|
io.WriteInt32(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
117
server/tso.files/Formats/IFF/Chunks/NGBH.cs
Executable file
117
server/tso.files/Formats/IFF/Chunks/NGBH.cs
Executable file
|
@ -0,0 +1,117 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type contains general neighbourhood data within a neighbourhood.iff file.
|
||||||
|
/// The only thing this was used for initially was tracking the tutorial.
|
||||||
|
///
|
||||||
|
/// As of hot date, it also includes inventory data, which was added as something of an afterthought.
|
||||||
|
/// </summary>
|
||||||
|
public class NGBH : IffChunk
|
||||||
|
{
|
||||||
|
public short[] NeighborhoodData = new short[16];
|
||||||
|
public Dictionary<short, List<InventoryItem>> InventoryByID = new Dictionary<short, List<InventoryItem>>();
|
||||||
|
|
||||||
|
public uint Version;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a NGBH chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a OBJf chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.ReadUInt32(); //pad
|
||||||
|
Version = io.ReadUInt32(); //0x49 for latest game
|
||||||
|
string magic = io.ReadCString(4); //HBGN
|
||||||
|
|
||||||
|
for (int i=0; i<16; i++)
|
||||||
|
{
|
||||||
|
NeighborhoodData[i] = io.ReadInt16();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!io.HasMore) return; //no inventory present (yet)
|
||||||
|
var count = io.ReadInt32();
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if (io.ReadInt32() != 1) { }
|
||||||
|
var neighID = io.ReadInt16();
|
||||||
|
var inventoryCount = io.ReadInt32();
|
||||||
|
var inventory = new List<InventoryItem>();
|
||||||
|
|
||||||
|
for (int j=0; j<inventoryCount; j++)
|
||||||
|
{
|
||||||
|
inventory.Add(new InventoryItem(io));
|
||||||
|
}
|
||||||
|
InventoryByID[neighID] = inventory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteInt32(0);
|
||||||
|
io.WriteInt32(0x49);
|
||||||
|
io.WriteCString("HBGN", 4);
|
||||||
|
|
||||||
|
for (int i=0; i<NeighborhoodData.Length; i++)
|
||||||
|
{
|
||||||
|
io.WriteInt16(NeighborhoodData[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteInt32(InventoryByID.Count);
|
||||||
|
foreach (var item in InventoryByID)
|
||||||
|
{
|
||||||
|
io.WriteInt32(1);
|
||||||
|
io.WriteInt16(item.Key);
|
||||||
|
io.WriteInt32(item.Value.Count);
|
||||||
|
foreach (var invent in item.Value)
|
||||||
|
{
|
||||||
|
invent.SerializeInto(io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InventoryItem
|
||||||
|
{
|
||||||
|
public int Type;
|
||||||
|
public uint GUID;
|
||||||
|
public ushort Count;
|
||||||
|
|
||||||
|
public InventoryItem() { }
|
||||||
|
|
||||||
|
public InventoryItem(IoBuffer io)
|
||||||
|
{
|
||||||
|
Type = io.ReadInt32();
|
||||||
|
GUID = io.ReadUInt32();
|
||||||
|
Count = io.ReadUInt16();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SerializeInto(IoWriter io)
|
||||||
|
{
|
||||||
|
io.WriteInt32(Type);
|
||||||
|
io.WriteUInt32(GUID);
|
||||||
|
io.WriteUInt16(Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InventoryItem Clone()
|
||||||
|
{
|
||||||
|
return new InventoryItem() { Type = Type, GUID = GUID, Count = Count };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "Type: "+Type+", GUID: "+GUID+", Count: "+Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
585
server/tso.files/Formats/IFF/Chunks/OBJD.cs
Executable file
585
server/tso.files/Formats/IFF/Chunks/OBJD.cs
Executable file
|
@ -0,0 +1,585 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Type of OBJD.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable()]
|
||||||
|
public enum OBJDType
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
//Character or NPC
|
||||||
|
Person = 2,
|
||||||
|
//Buyable objects
|
||||||
|
Normal = 4,
|
||||||
|
//Roaches, Stoves2, TrClownGen, AnimTester, HelpSystem, JobFinder, NPCController, Stoves,
|
||||||
|
//Tutorial, VisitGenerator, phonecall, unsnacker, CCPhonePlugin, EStove
|
||||||
|
SimType = 7,
|
||||||
|
//Stairs, doors, pool diving board & ladder, windows(?)
|
||||||
|
Portal = 8,
|
||||||
|
Cursor = 9,
|
||||||
|
PrizeToken = 10,
|
||||||
|
//Temporary location for drop or shoo
|
||||||
|
Internal = 11,
|
||||||
|
//these are mysteriously set as global sometimes.
|
||||||
|
GiftToken = 12,
|
||||||
|
Food = 34
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is an object definition, the main chunk for an object and the first loaded by the VM.
|
||||||
|
/// There can be multiple master OBJDs in an IFF, meaning that one IFF file can define multiple objects.
|
||||||
|
/// </summary>
|
||||||
|
public class OBJD : IffChunk
|
||||||
|
{
|
||||||
|
public uint Version;
|
||||||
|
|
||||||
|
public static string[] VERSION_142_Fields = new string[]
|
||||||
|
{
|
||||||
|
"StackSize",
|
||||||
|
"BaseGraphicID",
|
||||||
|
"NumGraphics",
|
||||||
|
"BHAV_MainID",
|
||||||
|
"BHAV_GardeningID",
|
||||||
|
"TreeTableID",
|
||||||
|
"InteractionGroupID",
|
||||||
|
"ObjectType",
|
||||||
|
"MasterID",
|
||||||
|
"SubIndex",
|
||||||
|
"BHAV_WashHandsID",
|
||||||
|
"AnimationTableID",
|
||||||
|
"GUID1",
|
||||||
|
"GUID2",
|
||||||
|
"Disabled",
|
||||||
|
"BHAV_Portal",
|
||||||
|
"Price",
|
||||||
|
"BodyStringID",
|
||||||
|
"SlotID",
|
||||||
|
"BHAV_AllowIntersectionID",
|
||||||
|
"UsesFnTable",
|
||||||
|
"BitField1",
|
||||||
|
"BHAV_PrepareFoodID",
|
||||||
|
"BHAV_CookFoodID",
|
||||||
|
"BHAV_PlaceSurfaceID",
|
||||||
|
"BHAV_DisposeID",
|
||||||
|
"BHAV_EatID",
|
||||||
|
"BHAV_PickupFromSlotID",
|
||||||
|
"BHAV_WashDishID",
|
||||||
|
"BHAV_EatSurfaceID",
|
||||||
|
"BHAV_SitID",
|
||||||
|
"BHAV_StandID",
|
||||||
|
|
||||||
|
"SalePrice",
|
||||||
|
"InitialDepreciation",
|
||||||
|
"DailyDepreciation",
|
||||||
|
"SelfDepreciating",
|
||||||
|
"DepreciationLimit",
|
||||||
|
"RoomFlags",
|
||||||
|
"FunctionFlags",
|
||||||
|
"CatalogStringsID",
|
||||||
|
|
||||||
|
"Global",
|
||||||
|
"BHAV_Init",
|
||||||
|
"BHAV_Place",
|
||||||
|
"BHAV_UserPickup",
|
||||||
|
"WallStyle",
|
||||||
|
"BHAV_Load",
|
||||||
|
"BHAV_UserPlace",
|
||||||
|
"ObjectVersion",
|
||||||
|
"BHAV_RoomChange",
|
||||||
|
"MotiveEffectsID",
|
||||||
|
"BHAV_Cleanup",
|
||||||
|
"BHAV_LevelInfo",
|
||||||
|
"CatalogID",
|
||||||
|
"BHAV_ServingSurface",
|
||||||
|
"LevelOffset",
|
||||||
|
"Shadow",
|
||||||
|
"NumAttributes",
|
||||||
|
|
||||||
|
"BHAV_Clean",
|
||||||
|
"BHAV_QueueSkipped",
|
||||||
|
"FrontDirection",
|
||||||
|
"BHAV_WallAdjacencyChanged",
|
||||||
|
"MyLeadObject",
|
||||||
|
"DynamicSpriteBaseId",
|
||||||
|
"NumDynamicSprites",
|
||||||
|
|
||||||
|
"ChairEntryFlags",
|
||||||
|
"TileWidth",
|
||||||
|
"LotCategories",
|
||||||
|
"BuildModeType",
|
||||||
|
"OriginalGUID1",
|
||||||
|
"OriginalGUID2",
|
||||||
|
"SuitGUID1",
|
||||||
|
"SuitGUID2",
|
||||||
|
"BHAV_Pickup",
|
||||||
|
"ThumbnailGraphic",
|
||||||
|
"ShadowFlags",
|
||||||
|
"FootprintMask",
|
||||||
|
"BHAV_DynamicMultiTileUpdate",
|
||||||
|
"ShadowBrightness",
|
||||||
|
"BHAV_Repair",
|
||||||
|
|
||||||
|
"WallStyleSpriteID",
|
||||||
|
"RatingHunger",
|
||||||
|
"RatingComfort",
|
||||||
|
"RatingHygiene",
|
||||||
|
"RatingBladder",
|
||||||
|
"RatingEnergy",
|
||||||
|
"RatingFun",
|
||||||
|
"RatingRoom",
|
||||||
|
"RatingSkillFlags",
|
||||||
|
|
||||||
|
"NumTypeAttributes",
|
||||||
|
"MiscFlags",
|
||||||
|
"TypeAttrGUID1",
|
||||||
|
"TypeAttrGUID2"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string[] VERSION_138b_Extra_Fields = new string[]
|
||||||
|
{
|
||||||
|
"FunctionSubsort",
|
||||||
|
"DTSubsort",
|
||||||
|
"KeepBuying",
|
||||||
|
"VacationSubsort",
|
||||||
|
"ResetLotAction",
|
||||||
|
"CommunitySubsort",
|
||||||
|
"DreamFlags",
|
||||||
|
"RenderFlags",
|
||||||
|
"VitaboyFlags",
|
||||||
|
"STSubsort",
|
||||||
|
"MTSubsort"
|
||||||
|
};
|
||||||
|
|
||||||
|
public ushort GUID1
|
||||||
|
{
|
||||||
|
get { return (ushort)(GUID); }
|
||||||
|
set { GUID = (GUID & 0xFFFF0000) | value; }
|
||||||
|
}
|
||||||
|
public ushort GUID2
|
||||||
|
{
|
||||||
|
get { return (ushort)(GUID>>16); }
|
||||||
|
set { GUID = (GUID & 0x0000FFFF) | ((uint)value<<16); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort StackSize { get; set; }
|
||||||
|
public ushort BaseGraphicID { get; set; }
|
||||||
|
public ushort NumGraphics { get; set; }
|
||||||
|
public ushort TreeTableID { get; set; }
|
||||||
|
public short InteractionGroupID { get; set; }
|
||||||
|
public OBJDType ObjectType { get; set; }
|
||||||
|
public ushort MasterID { get; set; }
|
||||||
|
public short SubIndex { get; set; }
|
||||||
|
public ushort AnimationTableID { get; set; }
|
||||||
|
public uint GUID { get; set; }
|
||||||
|
public ushort Disabled { get; set; }
|
||||||
|
public ushort BHAV_Portal { get; set; }
|
||||||
|
public ushort Price { get; set; }
|
||||||
|
public ushort BodyStringID { get; set; }
|
||||||
|
public ushort SlotID { get; set; }
|
||||||
|
public ushort SalePrice { get; set; }
|
||||||
|
public ushort InitialDepreciation { get; set; }
|
||||||
|
public ushort DailyDepreciation { get; set; }
|
||||||
|
public ushort SelfDepreciating { get; set; }
|
||||||
|
public ushort DepreciationLimit { get; set; }
|
||||||
|
public ushort RoomFlags { get; set; }
|
||||||
|
public ushort FunctionFlags { get; set; }
|
||||||
|
public ushort CatalogStringsID { get; set; }
|
||||||
|
|
||||||
|
public ushort BHAV_MainID { get; set; }
|
||||||
|
public ushort BHAV_GardeningID { get; set; }
|
||||||
|
public ushort BHAV_WashHandsID { get; set; }
|
||||||
|
public ushort BHAV_AllowIntersectionID { get; set; }
|
||||||
|
public ushort UsesFnTable { get; set; }
|
||||||
|
public ushort BitField1 { get; set; }
|
||||||
|
|
||||||
|
public ushort BHAV_PrepareFoodID { get; set; }
|
||||||
|
public ushort BHAV_CookFoodID { get; set; }
|
||||||
|
public ushort BHAV_PlaceSurfaceID { get; set; }
|
||||||
|
public ushort BHAV_DisposeID { get; set; }
|
||||||
|
public ushort BHAV_EatID { get; set; }
|
||||||
|
public ushort BHAV_PickupFromSlotID { get; set; }
|
||||||
|
public ushort BHAV_WashDishID { get; set; }
|
||||||
|
public ushort BHAV_EatSurfaceID { get; set; }
|
||||||
|
public ushort BHAV_SitID { get; set; }
|
||||||
|
public ushort BHAV_StandID { get; set; }
|
||||||
|
|
||||||
|
public ushort Global { get; set; }
|
||||||
|
public ushort BHAV_Init { get; set; }
|
||||||
|
public ushort BHAV_Place { get; set; }
|
||||||
|
public ushort BHAV_UserPickup { get; set; }
|
||||||
|
public ushort WallStyle { get; set; }
|
||||||
|
public ushort BHAV_Load { get; set; }
|
||||||
|
public ushort BHAV_UserPlace { get; set; }
|
||||||
|
public ushort ObjectVersion { get; set; }
|
||||||
|
public ushort BHAV_RoomChange { get; set; }
|
||||||
|
public ushort MotiveEffectsID { get; set; }
|
||||||
|
public ushort BHAV_Cleanup { get; set; }
|
||||||
|
public ushort BHAV_LevelInfo { get; set; }
|
||||||
|
public ushort CatalogID { get; set; }
|
||||||
|
|
||||||
|
public ushort BHAV_ServingSurface { get; set; }
|
||||||
|
public ushort LevelOffset { get; set; }
|
||||||
|
public ushort Shadow { get; set; }
|
||||||
|
public ushort NumAttributes { get; set; }
|
||||||
|
|
||||||
|
public ushort BHAV_Clean { get; set; }
|
||||||
|
public ushort BHAV_QueueSkipped { get; set; }
|
||||||
|
public ushort FrontDirection { get; set; }
|
||||||
|
public ushort BHAV_WallAdjacencyChanged { get; set; }
|
||||||
|
public ushort MyLeadObject { get; set; }
|
||||||
|
public ushort DynamicSpriteBaseId { get; set; }
|
||||||
|
public ushort NumDynamicSprites { get; set; }
|
||||||
|
|
||||||
|
public ushort ChairEntryFlags { get; set; }
|
||||||
|
public ushort TileWidth { get; set; }
|
||||||
|
public ushort LotCategories { get; set; }
|
||||||
|
public ushort BuildModeType { get; set; }
|
||||||
|
public ushort OriginalGUID1 { get; set; }
|
||||||
|
public ushort OriginalGUID2 { get; set; }
|
||||||
|
public ushort SuitGUID1 { get; set; }
|
||||||
|
public ushort SuitGUID2 { get; set; }
|
||||||
|
public ushort BHAV_Pickup { get; set; }
|
||||||
|
public ushort ThumbnailGraphic { get; set; }
|
||||||
|
public ushort ShadowFlags { get; set; }
|
||||||
|
public ushort FootprintMask { get; set; }
|
||||||
|
public ushort BHAV_DynamicMultiTileUpdate { get; set; }
|
||||||
|
public ushort ShadowBrightness { get; set; }
|
||||||
|
public ushort BHAV_Repair { get; set; }
|
||||||
|
|
||||||
|
public ushort WallStyleSpriteID { get; set; }
|
||||||
|
public short RatingHunger { get; set; }
|
||||||
|
public short RatingComfort { get; set; }
|
||||||
|
public short RatingHygiene { get; set; }
|
||||||
|
public short RatingBladder { get; set; }
|
||||||
|
public short RatingEnergy { get; set; }
|
||||||
|
public short RatingFun { get; set; }
|
||||||
|
public short RatingRoom { get; set; }
|
||||||
|
public ushort RatingSkillFlags { get; set; }
|
||||||
|
|
||||||
|
public ushort[] RawData;
|
||||||
|
public ushort NumTypeAttributes { get; set; }
|
||||||
|
public ushort MiscFlags { get; set; }
|
||||||
|
public uint TypeAttrGUID;
|
||||||
|
|
||||||
|
public ushort FunctionSubsort { get; set; }
|
||||||
|
public ushort DTSubsort { get; set; }
|
||||||
|
public ushort KeepBuying { get; set; }
|
||||||
|
public ushort VacationSubsort { get; set; }
|
||||||
|
public ushort ResetLotAction { get; set; }
|
||||||
|
public ushort CommunitySubsort { get; set; }
|
||||||
|
public ushort DreamFlags { get; set; }
|
||||||
|
public ushort RenderFlags { get; set; }
|
||||||
|
public ushort VitaboyFlags { get; set; }
|
||||||
|
public ushort STSubsort { get; set; }
|
||||||
|
public ushort MTSubsort { get; set; }
|
||||||
|
|
||||||
|
public ushort FootprintNorth
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (ushort)(FootprintMask & 0xF);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
FootprintMask &= 0xFFF0;
|
||||||
|
FootprintMask |= (ushort)(value & 0xF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ushort FootprintEast
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (ushort)((FootprintMask >> 4) & 0xF);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
FootprintMask &= 0xFF0F;
|
||||||
|
FootprintMask |= (ushort)((value & 0xF) << 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ushort FootprintSouth
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (ushort)((FootprintMask >> 8) & 0xF);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
FootprintMask &= 0xF0FF;
|
||||||
|
FootprintMask |= (ushort)((value & 0xF) << 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ushort FootprintWest
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (ushort)((FootprintMask >> 12) & 0xF);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
FootprintMask &= 0x0FFF;
|
||||||
|
FootprintMask |= (ushort)((value & 0xF) << 12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort TypeAttrGUID1
|
||||||
|
{
|
||||||
|
get { return (ushort)(TypeAttrGUID); }
|
||||||
|
set { TypeAttrGUID = (TypeAttrGUID & 0xFFFF0000) | value; }
|
||||||
|
}
|
||||||
|
public ushort TypeAttrGUID2
|
||||||
|
{
|
||||||
|
get { return (ushort)(TypeAttrGUID >> 16); }
|
||||||
|
set { TypeAttrGUID = (TypeAttrGUID & 0x0000FFFF) | ((uint)value << 16); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsMaster
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return SubIndex == -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsMultiTile
|
||||||
|
{
|
||||||
|
get {
|
||||||
|
return MasterID != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetPropertyByName<T>(string name)
|
||||||
|
{
|
||||||
|
Type me = typeof(OBJD);
|
||||||
|
var prop = me.GetProperty(name);
|
||||||
|
return (T)Convert.ChangeType(prop.GetValue(this, null), typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPropertyByName(string name, object value)
|
||||||
|
{
|
||||||
|
Type me = typeof(OBJD);
|
||||||
|
var prop = me.GetProperty(name);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = Convert.ChangeType(value, prop.PropertyType);
|
||||||
|
} catch {
|
||||||
|
value = Enum.Parse(prop.PropertyType, value.ToString());
|
||||||
|
}
|
||||||
|
prop.SetValue(this, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
this.Version = io.ReadUInt32();
|
||||||
|
|
||||||
|
/**136 (80 fields)
|
||||||
|
138a (95 fields) - Used for The Sims 1 base game objects?
|
||||||
|
138b (108 fields) - Used for The Sims 1 expansion objects?
|
||||||
|
139 (96 fields)
|
||||||
|
140 (97 fields)
|
||||||
|
141 (97 fields)
|
||||||
|
142 (105 fields)**/
|
||||||
|
var numFields = 80;
|
||||||
|
if (Version == 138)
|
||||||
|
{
|
||||||
|
numFields = 95;
|
||||||
|
}
|
||||||
|
else if (Version == 139)
|
||||||
|
{
|
||||||
|
numFields = 96;
|
||||||
|
}
|
||||||
|
else if (Version == 140)
|
||||||
|
{
|
||||||
|
numFields = 97;
|
||||||
|
}
|
||||||
|
else if (Version == 141)
|
||||||
|
{
|
||||||
|
numFields = 97;
|
||||||
|
}
|
||||||
|
else if (Version == 142)
|
||||||
|
{
|
||||||
|
numFields = 105;
|
||||||
|
}
|
||||||
|
|
||||||
|
numFields -= 2;
|
||||||
|
RawData = new ushort[numFields];
|
||||||
|
io.Mark();
|
||||||
|
|
||||||
|
for (var i = 0; i < numFields; i++)
|
||||||
|
{
|
||||||
|
RawData[i] = io.ReadUInt16();
|
||||||
|
}
|
||||||
|
|
||||||
|
io.SeekFromMark(0);
|
||||||
|
|
||||||
|
this.StackSize = io.ReadUInt16();
|
||||||
|
this.BaseGraphicID = io.ReadUInt16();
|
||||||
|
this.NumGraphics = io.ReadUInt16();
|
||||||
|
this.BHAV_MainID = io.ReadUInt16();
|
||||||
|
this.BHAV_GardeningID = io.ReadUInt16();
|
||||||
|
this.TreeTableID = io.ReadUInt16();
|
||||||
|
this.InteractionGroupID = io.ReadInt16();
|
||||||
|
this.ObjectType = (OBJDType)io.ReadUInt16();
|
||||||
|
this.MasterID = io.ReadUInt16();
|
||||||
|
this.SubIndex = io.ReadInt16();
|
||||||
|
this.BHAV_WashHandsID = io.ReadUInt16();
|
||||||
|
this.AnimationTableID = io.ReadUInt16();
|
||||||
|
this.GUID = io.ReadUInt32();
|
||||||
|
this.Disabled = io.ReadUInt16();
|
||||||
|
this.BHAV_Portal = io.ReadUInt16();
|
||||||
|
this.Price = io.ReadUInt16();
|
||||||
|
this.BodyStringID = io.ReadUInt16();
|
||||||
|
this.SlotID = io.ReadUInt16();
|
||||||
|
this.BHAV_AllowIntersectionID = io.ReadUInt16();
|
||||||
|
this.UsesFnTable = io.ReadUInt16();
|
||||||
|
this.BitField1 = io.ReadUInt16();
|
||||||
|
this.BHAV_PrepareFoodID = io.ReadUInt16();
|
||||||
|
this.BHAV_CookFoodID = io.ReadUInt16();
|
||||||
|
this.BHAV_PlaceSurfaceID = io.ReadUInt16();
|
||||||
|
this.BHAV_DisposeID = io.ReadUInt16();
|
||||||
|
this.BHAV_EatID = io.ReadUInt16();
|
||||||
|
this.BHAV_PickupFromSlotID = io.ReadUInt16();
|
||||||
|
this.BHAV_WashDishID = io.ReadUInt16();
|
||||||
|
this.BHAV_EatSurfaceID = io.ReadUInt16();
|
||||||
|
this.BHAV_SitID = io.ReadUInt16();
|
||||||
|
this.BHAV_StandID = io.ReadUInt16();
|
||||||
|
|
||||||
|
this.SalePrice = io.ReadUInt16();
|
||||||
|
this.InitialDepreciation = io.ReadUInt16();
|
||||||
|
this.DailyDepreciation = io.ReadUInt16();
|
||||||
|
this.SelfDepreciating = io.ReadUInt16();
|
||||||
|
this.DepreciationLimit = io.ReadUInt16();
|
||||||
|
this.RoomFlags = io.ReadUInt16();
|
||||||
|
this.FunctionFlags = io.ReadUInt16();
|
||||||
|
this.CatalogStringsID = io.ReadUInt16();
|
||||||
|
|
||||||
|
this.Global = io.ReadUInt16();
|
||||||
|
this.BHAV_Init = io.ReadUInt16();
|
||||||
|
this.BHAV_Place = io.ReadUInt16();
|
||||||
|
this.BHAV_UserPickup = io.ReadUInt16();
|
||||||
|
this.WallStyle = io.ReadUInt16();
|
||||||
|
this.BHAV_Load = io.ReadUInt16();
|
||||||
|
this.BHAV_UserPlace = io.ReadUInt16();
|
||||||
|
this.ObjectVersion = io.ReadUInt16();
|
||||||
|
this.BHAV_RoomChange = io.ReadUInt16();
|
||||||
|
this.MotiveEffectsID = io.ReadUInt16();
|
||||||
|
this.BHAV_Cleanup = io.ReadUInt16();
|
||||||
|
this.BHAV_LevelInfo = io.ReadUInt16();
|
||||||
|
this.CatalogID = io.ReadUInt16();
|
||||||
|
this.BHAV_ServingSurface = io.ReadUInt16();
|
||||||
|
this.LevelOffset = io.ReadUInt16();
|
||||||
|
this.Shadow = io.ReadUInt16();
|
||||||
|
this.NumAttributes = io.ReadUInt16();
|
||||||
|
|
||||||
|
this.BHAV_Clean = io.ReadUInt16();
|
||||||
|
this.BHAV_QueueSkipped = io.ReadUInt16();
|
||||||
|
this.FrontDirection = io.ReadUInt16();
|
||||||
|
this.BHAV_WallAdjacencyChanged = io.ReadUInt16();
|
||||||
|
this.MyLeadObject = io.ReadUInt16();
|
||||||
|
this.DynamicSpriteBaseId = io.ReadUInt16();
|
||||||
|
this.NumDynamicSprites = io.ReadUInt16();
|
||||||
|
|
||||||
|
this.ChairEntryFlags = io.ReadUInt16();
|
||||||
|
this.TileWidth = io.ReadUInt16();
|
||||||
|
this.LotCategories = io.ReadUInt16();
|
||||||
|
this.BuildModeType = io.ReadUInt16();
|
||||||
|
this.OriginalGUID1 = io.ReadUInt16();
|
||||||
|
this.OriginalGUID2 = io.ReadUInt16();
|
||||||
|
this.SuitGUID1 = io.ReadUInt16();
|
||||||
|
this.SuitGUID2 = io.ReadUInt16();
|
||||||
|
this.BHAV_Pickup = io.ReadUInt16();
|
||||||
|
this.ThumbnailGraphic = io.ReadUInt16();
|
||||||
|
this.ShadowFlags = io.ReadUInt16();
|
||||||
|
this.FootprintMask = io.ReadUInt16();
|
||||||
|
this.BHAV_DynamicMultiTileUpdate = io.ReadUInt16();
|
||||||
|
this.ShadowBrightness = io.ReadUInt16();
|
||||||
|
|
||||||
|
if (numFields > 78)
|
||||||
|
{
|
||||||
|
this.BHAV_Repair = io.ReadUInt16();
|
||||||
|
this.WallStyleSpriteID = io.ReadUInt16();
|
||||||
|
this.RatingHunger = io.ReadInt16();
|
||||||
|
this.RatingComfort = io.ReadInt16();
|
||||||
|
this.RatingHygiene = io.ReadInt16();
|
||||||
|
this.RatingBladder = io.ReadInt16();
|
||||||
|
this.RatingEnergy = io.ReadInt16();
|
||||||
|
this.RatingFun = io.ReadInt16();
|
||||||
|
this.RatingRoom = io.ReadInt16();
|
||||||
|
this.RatingSkillFlags = io.ReadUInt16();
|
||||||
|
if (numFields > 90)
|
||||||
|
{
|
||||||
|
this.NumTypeAttributes = io.ReadUInt16();
|
||||||
|
this.MiscFlags = io.ReadUInt16();
|
||||||
|
this.TypeAttrGUID = io.ReadUInt32();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.FunctionSubsort = io.ReadUInt16();
|
||||||
|
this.DTSubsort = io.ReadUInt16();
|
||||||
|
this.KeepBuying = io.ReadUInt16();
|
||||||
|
this.VacationSubsort = io.ReadUInt16();
|
||||||
|
this.ResetLotAction = io.ReadUInt16();
|
||||||
|
this.CommunitySubsort = io.ReadUInt16();
|
||||||
|
this.DreamFlags = io.ReadUInt16();
|
||||||
|
this.RenderFlags = io.ReadUInt16();
|
||||||
|
this.VitaboyFlags = io.ReadUInt16();
|
||||||
|
this.STSubsort = io.ReadUInt16();
|
||||||
|
this.MTSubsort = io.ReadUInt16();
|
||||||
|
} catch (Exception)
|
||||||
|
{
|
||||||
|
//past this point if these fields are here is really a mystery
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.TypeAttrGUID == 0) this.TypeAttrGUID = GUID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
if (IffFile.TargetTS1)
|
||||||
|
{
|
||||||
|
//version 138
|
||||||
|
io.WriteUInt32(138);
|
||||||
|
var fields = VERSION_142_Fields.Concat(VERSION_138b_Extra_Fields);
|
||||||
|
foreach (var prop in fields)
|
||||||
|
{
|
||||||
|
io.WriteUInt16((ushort)GetPropertyByName<int>(prop));
|
||||||
|
}
|
||||||
|
for (int i = fields.Count(); i < 108; i++)
|
||||||
|
{
|
||||||
|
io.WriteUInt16(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//tso version 142
|
||||||
|
io.WriteUInt32(142);
|
||||||
|
foreach (var prop in VERSION_142_Fields)
|
||||||
|
{
|
||||||
|
io.WriteUInt16((ushort)GetPropertyByName<int>(prop));
|
||||||
|
}
|
||||||
|
for (int i = VERSION_142_Fields.Length; i < 105; i++)
|
||||||
|
{
|
||||||
|
io.WriteUInt16(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
server/tso.files/Formats/IFF/Chunks/OBJM.cs
Executable file
138
server/tso.files/Formats/IFF/Chunks/OBJM.cs
Executable file
|
@ -0,0 +1,138 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class OBJM : IffChunk
|
||||||
|
{
|
||||||
|
//work in progress
|
||||||
|
|
||||||
|
//data body starts with 0x01, but what is after that is unknown.
|
||||||
|
|
||||||
|
//empty body from house 0:
|
||||||
|
// 01 00 00 00 | 00 00 00
|
||||||
|
//
|
||||||
|
|
||||||
|
public ushort[] IDToOBJT;
|
||||||
|
|
||||||
|
public Dictionary<int, MappedObject> ObjectData;
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.ReadUInt32(); //pad
|
||||||
|
var version = io.ReadUInt32();
|
||||||
|
|
||||||
|
//house 00: 33 00 00 00
|
||||||
|
//house 03: 3E 00 00 00
|
||||||
|
//house 79: 45 00 00 00
|
||||||
|
//completec:49 00 00 00
|
||||||
|
//corresponds to house version?
|
||||||
|
|
||||||
|
var MjbO = io.ReadUInt32();
|
||||||
|
|
||||||
|
var compressionCode = io.ReadByte();
|
||||||
|
if (compressionCode != 1) throw new Exception("hey what!!");
|
||||||
|
|
||||||
|
var iop = new IffFieldEncode(io);
|
||||||
|
|
||||||
|
/*
|
||||||
|
var test1 = iop.ReadInt16();
|
||||||
|
var testas = new ushort[test1*2];
|
||||||
|
for (int i=0; i<test1*2; i++)
|
||||||
|
{
|
||||||
|
testas[i] = iop.ReadUInt16();
|
||||||
|
}*/
|
||||||
|
|
||||||
|
var table = new List<ushort>();
|
||||||
|
while (io.HasMore)
|
||||||
|
{
|
||||||
|
var value = iop.ReadUInt16();
|
||||||
|
if (value == 0) break;
|
||||||
|
table.Add(value);
|
||||||
|
}
|
||||||
|
IDToOBJT = table.ToArray();
|
||||||
|
|
||||||
|
var list = new List<short>();
|
||||||
|
while (io.HasMore)
|
||||||
|
{
|
||||||
|
list.Add(iop.ReadInt16());
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsets = SearchForObjectData(list);
|
||||||
|
for (int i=1; i<offsets.Count; i++)
|
||||||
|
{
|
||||||
|
Console.WriteLine(offsets[i] - offsets[i-1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectData = new Dictionary<int, MappedObject>();
|
||||||
|
int lastOff = 0;
|
||||||
|
foreach (var off in offsets)
|
||||||
|
{
|
||||||
|
// 58 behind the object data...
|
||||||
|
// [-12, 0, -12, 0, -4, 0, -4, 0, -8, 0, -8, 0, 0, 210, 0, 0, 0, 0, 146, 0, -1, -1, 0, 0, 164, 13, 0, 1, 0, 0, 0, 1, 1, 0, 99, 0, 0, 0, 9, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
// [-12, 0, -12, 0, -4, 0, -4, 0, -8, 0, -8, 0, 0, 210, 0, 0, 0, 0, 146, 0, -1, -1, 0, 0, 197, 13, 0, 1, 0, 0, 0, 1, 1, 0, 79, 0, 4, 2, 9, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
// [-12, 0, -12, 0, -4, 0, -4, 0, -8, 0, -8, 0, 0, 210, 0, 0, 0, 0, 146, 0, -1, -1, 0, 0, 197, 13, 0, 1, 0, 0, 0, 1, 1, 0, 71, 0, 3, 2, 9, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
// [ 1, 0, 1, 0, 0, 0,256, 0, 48, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 19493, 0, 0, -122, -8, 83, 0, 0, 0, 0, -23174, 0, 0, 0, 0, 0, 0, 196, 0, 2, 0, 0, 0, 0, 0,
|
||||||
|
var endOff = off + 72;
|
||||||
|
var size = endOff - lastOff;
|
||||||
|
var data = list.Skip(lastOff).Take(size).ToArray();
|
||||||
|
|
||||||
|
var bas = size - 72;
|
||||||
|
var objID = data[bas+11]; //object id
|
||||||
|
var dir = data[bas + 1];
|
||||||
|
var parent = data[bas + 26];
|
||||||
|
var containerid = data[bas + 2];
|
||||||
|
var containerslot = data[bas + 2];
|
||||||
|
|
||||||
|
ObjectData[objID] = new MappedObject() { ObjectID = objID, Direction = dir, Data = data, ParentID = parent, ContainerID = containerid, ContainerSlot = containerslot };
|
||||||
|
|
||||||
|
lastOff = endOff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MappedObject {
|
||||||
|
public string Name;
|
||||||
|
public uint GUID;
|
||||||
|
public int ObjectID;
|
||||||
|
public int Direction;
|
||||||
|
public int ParentID;
|
||||||
|
|
||||||
|
public int ContainerID;
|
||||||
|
public int ContainerSlot;
|
||||||
|
|
||||||
|
public short[] Data;
|
||||||
|
|
||||||
|
public int ArryX;
|
||||||
|
public int ArryY;
|
||||||
|
public int ArryLevel;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name ?? "(unreferenced)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<int> SearchForObjectData(List<short> data)
|
||||||
|
{
|
||||||
|
//we don't know exactly where the object data is in the format...
|
||||||
|
//but we know objects should have a birth date, basically always 1997 or (1997-36) for npcs.
|
||||||
|
//this should let us extract some important attributes like the structure of the data and object directions.
|
||||||
|
|
||||||
|
var offsets = new List<int>();
|
||||||
|
for (int i=0; i<data.Count-3; i++)
|
||||||
|
{
|
||||||
|
if ((data[i] == 1997 || data[i] == 1998 || data[i] == (1997-36)) && (data[i + 1] > 0 && data[i+1] < 13) && (data[i + 2] > 0 && data[i + 2] < 32)) {
|
||||||
|
offsets.Add(i - 45);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return offsets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
server/tso.files/Formats/IFF/Chunks/OBJT.cs
Executable file
64
server/tso.files/Formats/IFF/Chunks/OBJT.cs
Executable file
|
@ -0,0 +1,64 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class OBJT : IffChunk
|
||||||
|
{
|
||||||
|
//another sims 1 masterpiece. A list of object info.
|
||||||
|
public List<OBJTEntry> Entries;
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var zero = io.ReadInt32();
|
||||||
|
var version = io.ReadInt32(); //should be 2/3
|
||||||
|
var objt = io.ReadInt32(); //tjbo
|
||||||
|
|
||||||
|
Entries = new List<OBJTEntry>();
|
||||||
|
//single tile objects are named. multitile objects arent.
|
||||||
|
|
||||||
|
while (io.HasMore)
|
||||||
|
{
|
||||||
|
Entries.Add(new OBJTEntry(io, version));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OBJTEntry
|
||||||
|
{
|
||||||
|
public uint GUID;
|
||||||
|
public ushort Unknown1a;
|
||||||
|
public ushort Unknown1b;
|
||||||
|
public ushort Unknown2a;
|
||||||
|
public ushort Unknown2b;
|
||||||
|
public ushort TypeID;
|
||||||
|
public OBJDType OBJDType;
|
||||||
|
public string Name;
|
||||||
|
public OBJTEntry(IoBuffer io, int version)
|
||||||
|
{
|
||||||
|
//16 bytes of data
|
||||||
|
GUID = io.ReadUInt32();
|
||||||
|
if (GUID == 0) return;
|
||||||
|
Unknown1a = io.ReadUInt16();
|
||||||
|
Unknown1b = io.ReadUInt16();
|
||||||
|
Unknown2a = io.ReadUInt16();
|
||||||
|
Unknown2b = io.ReadUInt16();
|
||||||
|
//increases by one each time, one based, essentially an ID for this loaded type. Mostly matches index in array, but I guess it can possibly be different.
|
||||||
|
TypeID = io.ReadUInt16();
|
||||||
|
OBJDType = (OBJDType)io.ReadUInt16();
|
||||||
|
//then the name, null terminated
|
||||||
|
Name = io.ReadNullTerminatedString();
|
||||||
|
if (Name.Length%2 == 0) io.ReadByte(); //pad to short width
|
||||||
|
if (version > 2) io.ReadInt32(); //not sure what this is
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{TypeID}: {Name} ({GUID.ToString("x8")}): [{Unknown1a}, {Unknown1b}, {Unknown2a}, {Unknown2b}, {OBJDType}]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
server/tso.files/Formats/IFF/Chunks/OBJf.cs
Executable file
59
server/tso.files/Formats/IFF/Chunks/OBJf.cs
Executable file
|
@ -0,0 +1,59 @@
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type assigns BHAV subroutines to a number of events that occur in
|
||||||
|
/// (or outside of?) the object, which are described in behavior.iff chunk 00F5.
|
||||||
|
/// </summary>
|
||||||
|
public class OBJf : IffChunk
|
||||||
|
{
|
||||||
|
public OBJfFunctionEntry[] functions;
|
||||||
|
public uint Version;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a OBJf chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a OBJf chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.ReadUInt32(); //pad
|
||||||
|
Version = io.ReadUInt32();
|
||||||
|
string magic = io.ReadCString(4);
|
||||||
|
functions = new OBJfFunctionEntry[io.ReadUInt32()];
|
||||||
|
for (int i=0; i<functions.Length; i++) {
|
||||||
|
var result = new OBJfFunctionEntry();
|
||||||
|
result.ConditionFunction = io.ReadUInt16();
|
||||||
|
result.ActionFunction = io.ReadUInt16();
|
||||||
|
functions[i] = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteUInt32(0);
|
||||||
|
io.WriteUInt32(Version);
|
||||||
|
io.WriteCString("fJBO", 4);
|
||||||
|
io.WriteInt32(functions.Length);
|
||||||
|
foreach(var func in functions)
|
||||||
|
{
|
||||||
|
io.WriteUInt16(func.ConditionFunction);
|
||||||
|
io.WriteUInt16(func.ActionFunction);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct OBJfFunctionEntry {
|
||||||
|
public ushort ConditionFunction;
|
||||||
|
public ushort ActionFunction;
|
||||||
|
}
|
||||||
|
}
|
79
server/tso.files/Formats/IFF/Chunks/PALT.cs
Executable file
79
server/tso.files/Formats/IFF/Chunks/PALT.cs
Executable file
|
@ -0,0 +1,79 @@
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type holds a color palette.
|
||||||
|
/// </summary>
|
||||||
|
public class PALT : IffChunk
|
||||||
|
{
|
||||||
|
public PALT()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PALT(Color color)
|
||||||
|
{
|
||||||
|
Colors = new Color[256];
|
||||||
|
for (int i = 0; i < 256; i++)
|
||||||
|
{
|
||||||
|
Colors[i] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color[] Colors;
|
||||||
|
public int References = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a PALT chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a PALT chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var version = io.ReadUInt32();
|
||||||
|
var numEntries = io.ReadUInt32();
|
||||||
|
var reserved = io.ReadBytes(8);
|
||||||
|
|
||||||
|
Colors = new Color[numEntries];
|
||||||
|
for (var i = 0; i < numEntries; i++)
|
||||||
|
{
|
||||||
|
var r = io.ReadByte();
|
||||||
|
var g = io.ReadByte();
|
||||||
|
var b = io.ReadByte();
|
||||||
|
Colors[i] = new Color(r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteUInt32(0);
|
||||||
|
io.WriteUInt32((uint)Colors.Length);
|
||||||
|
io.WriteBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 });
|
||||||
|
foreach (var col in Colors)
|
||||||
|
{
|
||||||
|
io.WriteByte(col.R);
|
||||||
|
io.WriteByte(col.G);
|
||||||
|
io.WriteByte(col.B);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool PalMatch(Color[] data)
|
||||||
|
{
|
||||||
|
for (var i=0; i<Colors.Length; i++)
|
||||||
|
{
|
||||||
|
if (i >= data.Length) return true;
|
||||||
|
if (data[i].A != 0 && data[i] != Colors[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
140
server/tso.files/Formats/IFF/Chunks/PART.cs
Executable file
140
server/tso.files/Formats/IFF/Chunks/PART.cs
Executable file
|
@ -0,0 +1,140 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class PART : IffChunk
|
||||||
|
{
|
||||||
|
public static PART BROKEN = new PART()
|
||||||
|
{
|
||||||
|
Gravity = 0.15f,
|
||||||
|
RandomVel = 0.15f,
|
||||||
|
RandomRotVel = 1f,
|
||||||
|
Size = 0.75f,
|
||||||
|
SizeVel = 2.5f,
|
||||||
|
Duration = 3f,
|
||||||
|
FadeIn = 0.15f,
|
||||||
|
FadeOut = 0.6f,
|
||||||
|
SizeVariation = 0.4f,
|
||||||
|
|
||||||
|
TargetColor = Color.Gray,
|
||||||
|
TargetColorVar = 0.5f,
|
||||||
|
|
||||||
|
Frequency = 6f,
|
||||||
|
|
||||||
|
ChunkID = 256
|
||||||
|
};
|
||||||
|
|
||||||
|
public static int CURRENT_VERSION = 1;
|
||||||
|
public int Version = CURRENT_VERSION;
|
||||||
|
public int Type = 0; // default/manualbounds
|
||||||
|
|
||||||
|
public float Frequency;
|
||||||
|
public ushort TexID; //id for MTEX resource
|
||||||
|
public BoundingBox Bounds;
|
||||||
|
|
||||||
|
public Vector3 Velocity;
|
||||||
|
public float Gravity = -0.8f;
|
||||||
|
public float RandomVel;
|
||||||
|
public float RandomRotVel;
|
||||||
|
public float Size = 1;
|
||||||
|
public float SizeVel;
|
||||||
|
public float Duration = 1;
|
||||||
|
public float FadeIn;
|
||||||
|
public float FadeOut;
|
||||||
|
public float SizeVariation;
|
||||||
|
public Color TargetColor;
|
||||||
|
public float TargetColorVar;
|
||||||
|
public int Particles = 15;
|
||||||
|
|
||||||
|
public Vector4[] Parameters = null;
|
||||||
|
|
||||||
|
//(deltax, deltay, deltaz, gravity)
|
||||||
|
//(deltavar, rotdeltavar, size, sizevel)
|
||||||
|
//(duration, fadein, fadeout, sizevar)
|
||||||
|
//(targetColor.rgb, variation)
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
Version = io.ReadInt32();
|
||||||
|
Type = io.ReadInt32();
|
||||||
|
|
||||||
|
Frequency = io.ReadFloat();
|
||||||
|
TexID = io.ReadUInt16();
|
||||||
|
Particles = io.ReadInt32();
|
||||||
|
if (Type == 1)
|
||||||
|
{
|
||||||
|
Bounds = new BoundingBox(
|
||||||
|
new Vector3(io.ReadFloat(), io.ReadFloat(), io.ReadFloat()),
|
||||||
|
new Vector3(io.ReadFloat(), io.ReadFloat(), io.ReadFloat()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Velocity = new Vector3(io.ReadFloat(), io.ReadFloat(), io.ReadFloat());
|
||||||
|
Gravity = io.ReadFloat();
|
||||||
|
RandomVel = io.ReadFloat();
|
||||||
|
RandomRotVel = io.ReadFloat();
|
||||||
|
Size = io.ReadFloat();
|
||||||
|
SizeVel = io.ReadFloat();
|
||||||
|
Duration = io.ReadFloat();
|
||||||
|
FadeIn = io.ReadFloat();
|
||||||
|
FadeOut = io.ReadFloat();
|
||||||
|
SizeVariation = io.ReadFloat();
|
||||||
|
TargetColor.PackedValue = io.ReadUInt32();
|
||||||
|
TargetColorVar = io.ReadFloat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteInt32(Version);
|
||||||
|
io.WriteInt32(Type);
|
||||||
|
|
||||||
|
io.WriteFloat(Frequency);
|
||||||
|
io.WriteUInt16(TexID);
|
||||||
|
io.WriteInt32(Particles);
|
||||||
|
if (Type == 1)
|
||||||
|
{
|
||||||
|
io.WriteFloat(Bounds.Min.X);
|
||||||
|
io.WriteFloat(Bounds.Min.Y);
|
||||||
|
io.WriteFloat(Bounds.Min.Z);
|
||||||
|
|
||||||
|
io.WriteFloat(Bounds.Max.X);
|
||||||
|
io.WriteFloat(Bounds.Max.Y);
|
||||||
|
io.WriteFloat(Bounds.Max.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteFloat(Velocity.X);
|
||||||
|
io.WriteFloat(Velocity.Y);
|
||||||
|
io.WriteFloat(Velocity.Z);
|
||||||
|
|
||||||
|
io.WriteFloat(Gravity);
|
||||||
|
io.WriteFloat(RandomVel);
|
||||||
|
io.WriteFloat(RandomRotVel);
|
||||||
|
io.WriteFloat(Size);
|
||||||
|
io.WriteFloat(SizeVel);
|
||||||
|
io.WriteFloat(Duration);
|
||||||
|
io.WriteFloat(FadeIn);
|
||||||
|
io.WriteFloat(FadeOut);
|
||||||
|
io.WriteFloat(SizeVariation);
|
||||||
|
io.WriteUInt32(TargetColor.PackedValue);
|
||||||
|
io.WriteFloat(TargetColorVar);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BakeParameters()
|
||||||
|
{
|
||||||
|
Parameters = new Vector4[4];
|
||||||
|
Parameters[0] = new Vector4(Velocity, Gravity);
|
||||||
|
Parameters[1] = new Vector4(RandomVel, RandomRotVel, Size, SizeVel);
|
||||||
|
Parameters[2] = new Vector4(Duration, FadeIn, FadeOut, SizeVariation);
|
||||||
|
Parameters[3] = TargetColor.ToVector4();
|
||||||
|
Parameters[3].W = TargetColorVar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
187
server/tso.files/Formats/IFF/Chunks/PIFF.cs
Executable file
187
server/tso.files/Formats/IFF/Chunks/PIFF.cs
Executable file
|
@ -0,0 +1,187 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class PIFF : IffChunk
|
||||||
|
{
|
||||||
|
public static ushort CURRENT_VERSION = 2;
|
||||||
|
public ushort Version = CURRENT_VERSION;
|
||||||
|
public string SourceIff;
|
||||||
|
public string Comment = "";
|
||||||
|
public PIFFEntry[] Entries;
|
||||||
|
|
||||||
|
public PIFF()
|
||||||
|
{
|
||||||
|
ChunkType = "PIFF";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendAddedChunks(IffFile file)
|
||||||
|
{
|
||||||
|
foreach (var chunk in file.SilentListAll())
|
||||||
|
{
|
||||||
|
if (chunk == this) continue;
|
||||||
|
var entries = Entries.ToList();
|
||||||
|
entries.Add(new PIFFEntry()
|
||||||
|
{
|
||||||
|
ChunkID = chunk.ChunkID,
|
||||||
|
ChunkLabel = chunk.ChunkLabel,
|
||||||
|
ChunkFlags = chunk.ChunkFlags,
|
||||||
|
EntryType = PIFFEntryType.Add,
|
||||||
|
NewDataSize = (uint)(chunk.ChunkData?.Length ?? 0),
|
||||||
|
Type = chunk.ChunkType
|
||||||
|
});
|
||||||
|
Entries = entries.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
Version = io.ReadUInt16();
|
||||||
|
SourceIff = io.ReadVariableLengthPascalString();
|
||||||
|
if (Version > 1) Comment = io.ReadVariableLengthPascalString();
|
||||||
|
Entries = new PIFFEntry[io.ReadUInt16()];
|
||||||
|
for (int i=0; i<Entries.Length; i++)
|
||||||
|
{
|
||||||
|
var e = new PIFFEntry();
|
||||||
|
e.Type = io.ReadCString(4);
|
||||||
|
e.ChunkID = io.ReadUInt16();
|
||||||
|
if (Version > 1) e.Comment = io.ReadVariableLengthPascalString();
|
||||||
|
e.EntryType = (PIFFEntryType)io.ReadByte();
|
||||||
|
|
||||||
|
if (e.EntryType == PIFFEntryType.Patch)
|
||||||
|
{
|
||||||
|
e.ChunkLabel = io.ReadVariableLengthPascalString();
|
||||||
|
e.ChunkFlags = io.ReadUInt16();
|
||||||
|
if (Version > 0) e.NewChunkID = io.ReadUInt16();
|
||||||
|
else e.NewChunkID = e.ChunkID;
|
||||||
|
e.NewDataSize = io.ReadUInt32();
|
||||||
|
|
||||||
|
var size = io.ReadUInt32();
|
||||||
|
e.Patches = new PIFFPatch[size];
|
||||||
|
uint lastOff = 0;
|
||||||
|
|
||||||
|
for (int j=0; j<e.Patches.Length; j++)
|
||||||
|
{
|
||||||
|
var p = new PIFFPatch();
|
||||||
|
|
||||||
|
p.Offset = lastOff + io.ReadVarLen();
|
||||||
|
lastOff = p.Offset;
|
||||||
|
p.Size = io.ReadVarLen();
|
||||||
|
p.Mode = (PIFFPatchMode)io.ReadByte();
|
||||||
|
|
||||||
|
if (p.Mode == PIFFPatchMode.Add) p.Data = io.ReadBytes(p.Size);
|
||||||
|
e.Patches[j] = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entries[i] = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteUInt16(CURRENT_VERSION);
|
||||||
|
io.WriteVariableLengthPascalString(SourceIff);
|
||||||
|
io.WriteVariableLengthPascalString(Comment);
|
||||||
|
io.WriteUInt16((ushort)Entries.Length);
|
||||||
|
foreach (var ent in Entries)
|
||||||
|
{
|
||||||
|
io.WriteCString(ent.Type, 4);
|
||||||
|
io.WriteUInt16(ent.ChunkID);
|
||||||
|
io.WriteVariableLengthPascalString(ent.Comment);
|
||||||
|
io.WriteByte((byte)(ent.EntryType));
|
||||||
|
|
||||||
|
if (ent.EntryType == PIFFEntryType.Patch)
|
||||||
|
{
|
||||||
|
io.WriteVariableLengthPascalString(ent.ChunkLabel); //0 length means no replacement
|
||||||
|
io.WriteUInt16(ent.ChunkFlags);
|
||||||
|
io.WriteUInt16(ent.NewChunkID);
|
||||||
|
io.WriteUInt32(ent.NewDataSize);
|
||||||
|
io.WriteUInt32((uint)ent.Patches.Length);
|
||||||
|
|
||||||
|
uint lastOff = 0;
|
||||||
|
foreach (var p in ent.Patches)
|
||||||
|
{
|
||||||
|
io.WriteVarLen(p.Offset-lastOff);
|
||||||
|
lastOff = p.Offset;
|
||||||
|
io.WriteVarLen(p.Size);
|
||||||
|
io.WriteByte((byte)p.Mode);
|
||||||
|
if (p.Mode == PIFFPatchMode.Add) io.WriteBytes(p.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PIFFEntry
|
||||||
|
{
|
||||||
|
public string Type;
|
||||||
|
public ushort ChunkID;
|
||||||
|
public ushort NewChunkID;
|
||||||
|
public PIFFEntryType EntryType;
|
||||||
|
public string Comment = "";
|
||||||
|
|
||||||
|
public string ChunkLabel;
|
||||||
|
public ushort ChunkFlags;
|
||||||
|
public uint NewDataSize;
|
||||||
|
|
||||||
|
public PIFFPatch[] Patches;
|
||||||
|
|
||||||
|
public byte[] Apply(byte[] src)
|
||||||
|
{
|
||||||
|
var result = new byte[NewDataSize];
|
||||||
|
uint srcPtr = 0;
|
||||||
|
uint destPtr = 0;
|
||||||
|
int i = 0;
|
||||||
|
foreach (var p in Patches)
|
||||||
|
{
|
||||||
|
var copyCount = p.Offset - destPtr;
|
||||||
|
Array.Copy(src, srcPtr, result, destPtr, copyCount);
|
||||||
|
srcPtr += copyCount; destPtr += copyCount;
|
||||||
|
if (p.Mode == PIFFPatchMode.Add)
|
||||||
|
{
|
||||||
|
Array.Copy(p.Data, 0, result, destPtr, p.Size);
|
||||||
|
destPtr += p.Size;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
srcPtr += p.Size;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
var remainder = NewDataSize - destPtr;
|
||||||
|
if (remainder != 0) Array.Copy(src, srcPtr, result, destPtr, remainder);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PIFFEntryType : byte
|
||||||
|
{
|
||||||
|
Patch,
|
||||||
|
Remove,
|
||||||
|
Add
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PIFFPatch
|
||||||
|
{
|
||||||
|
public uint Offset;
|
||||||
|
public uint Size;
|
||||||
|
public PIFFPatchMode Mode;
|
||||||
|
public byte[] Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PIFFPatchMode : byte
|
||||||
|
{
|
||||||
|
Remove = 0,
|
||||||
|
Add = 1
|
||||||
|
}
|
||||||
|
}
|
10
server/tso.files/Formats/IFF/Chunks/PNG.cs
Executable file
10
server/tso.files/Formats/IFF/Chunks/PNG.cs
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type holds an image in PNG format.
|
||||||
|
/// </summary>
|
||||||
|
public class PNG : BMP
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
165
server/tso.files/Formats/IFF/Chunks/SIMI.cs
Executable file
165
server/tso.files/Formats/IFF/Chunks/SIMI.cs
Executable file
|
@ -0,0 +1,165 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class SIMI : IffChunk
|
||||||
|
{
|
||||||
|
public uint Version;
|
||||||
|
public short[] GlobalData;
|
||||||
|
|
||||||
|
public short Unknown1;
|
||||||
|
public int Unknown2;
|
||||||
|
public int Unknown3;
|
||||||
|
public int GUID1;
|
||||||
|
public int GUID2;
|
||||||
|
public int Unknown4;
|
||||||
|
public int LotValue;
|
||||||
|
public int ObjectsValue;
|
||||||
|
public int ArchitectureValue;
|
||||||
|
|
||||||
|
public SIMIBudgetDay[] BudgetDays;
|
||||||
|
|
||||||
|
public int PurchaseValue
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return LotValue + ObjectsValue + (ArchitectureValue * 7) / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.ReadUInt32(); //pad
|
||||||
|
Version = io.ReadUInt32();
|
||||||
|
string magic = io.ReadCString(4);
|
||||||
|
var items = (Version > 0x3F) ? 0x40 : 0x20;
|
||||||
|
|
||||||
|
GlobalData = new short[38];
|
||||||
|
|
||||||
|
for (int i=0; i<items; i++)
|
||||||
|
{
|
||||||
|
var dat = io.ReadInt16();
|
||||||
|
if (i < GlobalData.Length)
|
||||||
|
GlobalData[i] = dat;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unknown1 = io.ReadInt16();
|
||||||
|
Unknown2 = io.ReadInt32();
|
||||||
|
Unknown3 = io.ReadInt32();
|
||||||
|
GUID1 = io.ReadInt32();
|
||||||
|
GUID2 = io.ReadInt32();
|
||||||
|
Unknown4 = io.ReadInt32();
|
||||||
|
LotValue = io.ReadInt32();
|
||||||
|
ObjectsValue = io.ReadInt32();
|
||||||
|
ArchitectureValue = io.ReadInt32();
|
||||||
|
|
||||||
|
//short Unknown1 (0x7E1E, 0x702B)
|
||||||
|
//int Unknown2 (2 on house 1, 1 on house 66)
|
||||||
|
//int Unknown3 (0)
|
||||||
|
//int GUID1
|
||||||
|
//int GUID2 (changes on bulldoze)
|
||||||
|
//int Unknown4 (0)
|
||||||
|
//int LotValue
|
||||||
|
//int ObjectsValue
|
||||||
|
//int ArchitectureValue
|
||||||
|
|
||||||
|
//the sims tracked a sim's budget over the past few days of gameplay.
|
||||||
|
//this drove the budget window, which never actually came of much use to anyone ever.
|
||||||
|
|
||||||
|
BudgetDays = new SIMIBudgetDay[6];
|
||||||
|
for (int i=0; i<6; i++)
|
||||||
|
{
|
||||||
|
BudgetDays[i] = new SIMIBudgetDay(io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteInt32(0);
|
||||||
|
io.WriteInt32(0x3E);
|
||||||
|
io.WriteCString("IMIS", 4);
|
||||||
|
var items = (Version > 0x3E) ? 0x40 : 0x20;
|
||||||
|
|
||||||
|
GlobalData = new short[38];
|
||||||
|
|
||||||
|
for (int i = 0; i < items; i++)
|
||||||
|
{
|
||||||
|
if (i < GlobalData.Length)
|
||||||
|
io.WriteInt16(GlobalData[i]);
|
||||||
|
else
|
||||||
|
io.WriteInt16(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteInt16(Unknown1);
|
||||||
|
io.WriteInt32(Unknown2);
|
||||||
|
io.WriteInt32(Unknown3);
|
||||||
|
io.WriteInt32(GUID1);
|
||||||
|
io.WriteInt32(GUID2);
|
||||||
|
io.WriteInt32(Unknown4);
|
||||||
|
io.WriteInt32(LotValue);
|
||||||
|
io.WriteInt32(ObjectsValue);
|
||||||
|
io.WriteInt32(ArchitectureValue);
|
||||||
|
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
BudgetDays[i].Write(io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SIMIBudgetDay
|
||||||
|
{
|
||||||
|
public int Valid;
|
||||||
|
public int MiscIncome;
|
||||||
|
public int JobIncome;
|
||||||
|
|
||||||
|
public int ServiceExpense;
|
||||||
|
public int FoodExpense;
|
||||||
|
public int BillsExpense;
|
||||||
|
|
||||||
|
public int MiscExpense;
|
||||||
|
public int HouseholdExpense;
|
||||||
|
public int ArchitectureExpense;
|
||||||
|
|
||||||
|
public SIMIBudgetDay()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SIMIBudgetDay(IoBuffer io)
|
||||||
|
{
|
||||||
|
Valid = io.ReadInt32();
|
||||||
|
if (Valid == 0) return;
|
||||||
|
MiscIncome = io.ReadInt32();
|
||||||
|
JobIncome = io.ReadInt32();
|
||||||
|
ServiceExpense = io.ReadInt32();
|
||||||
|
FoodExpense = io.ReadInt32();
|
||||||
|
BillsExpense = io.ReadInt32();
|
||||||
|
MiscExpense = io.ReadInt32();
|
||||||
|
HouseholdExpense = io.ReadInt32();
|
||||||
|
ArchitectureExpense = io.ReadInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(IoWriter io)
|
||||||
|
{
|
||||||
|
io.WriteInt32(Valid);
|
||||||
|
if (Valid == 0) return;
|
||||||
|
io.WriteInt32(MiscIncome);
|
||||||
|
io.WriteInt32(JobIncome);
|
||||||
|
io.WriteInt32(ServiceExpense);
|
||||||
|
io.WriteInt32(FoodExpense);
|
||||||
|
io.WriteInt32(BillsExpense);
|
||||||
|
io.WriteInt32(MiscIncome);
|
||||||
|
io.WriteInt32(HouseholdExpense);
|
||||||
|
io.WriteInt32(ArchitectureExpense);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
218
server/tso.files/Formats/IFF/Chunks/SLOT.cs
Executable file
218
server/tso.files/Formats/IFF/Chunks/SLOT.cs
Executable file
|
@ -0,0 +1,218 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This format isn't documented on the wiki! Thanks, Darren!
|
||||||
|
/// </summary>
|
||||||
|
public class SLOT : IffChunk
|
||||||
|
{
|
||||||
|
|
||||||
|
public static float[] HeightOffsets = {
|
||||||
|
//NOTE: 1 indexed! to get offset for a height, lookup (SLOT.Height-1)
|
||||||
|
0, //floor
|
||||||
|
2.5f, //low table
|
||||||
|
4, //table
|
||||||
|
4, //counter
|
||||||
|
0, //non-standard (appears to use offset height)
|
||||||
|
0, //in hand (unused probably. we handle avatar hands as a special case.)
|
||||||
|
7, //sitting (used for chairs)
|
||||||
|
4, //end table
|
||||||
|
0 //TODO: unknown
|
||||||
|
};
|
||||||
|
|
||||||
|
public Dictionary<ushort, List<SLOTItem>> Slots = new Dictionary<ushort, List<SLOTItem>>();
|
||||||
|
public List<SLOTItem> Chronological = new List<SLOTItem>();
|
||||||
|
|
||||||
|
public uint Version;
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, System.IO.Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN)){
|
||||||
|
var zero = io.ReadUInt32();
|
||||||
|
var version = io.ReadUInt32();
|
||||||
|
Version = version;
|
||||||
|
var slotMagic = io.ReadBytes(4);
|
||||||
|
var numSlots = io.ReadUInt32();
|
||||||
|
|
||||||
|
/** The span for version 4 is 34.
|
||||||
|
* The span for version 6 is 54.
|
||||||
|
* The span for version 7 is 58.
|
||||||
|
* The span for version 8 is 62.
|
||||||
|
* The span for version 9 is 66.
|
||||||
|
* The span for version 10 is 70. **/
|
||||||
|
for (var i = 0; i < numSlots; i++){
|
||||||
|
var item = new SLOTItem();
|
||||||
|
item.Type = io.ReadUInt16();
|
||||||
|
item.Offset = new Vector3(
|
||||||
|
io.ReadFloat(),
|
||||||
|
io.ReadFloat(),
|
||||||
|
io.ReadFloat()
|
||||||
|
);
|
||||||
|
|
||||||
|
var standing = io.ReadInt32();
|
||||||
|
var sitting = io.ReadInt32();
|
||||||
|
var ground = io.ReadInt32();
|
||||||
|
var rsflags = io.ReadInt32();
|
||||||
|
var snaptargetslot = io.ReadInt32();
|
||||||
|
|
||||||
|
//bonuses (0 means never)
|
||||||
|
item.Standing = standing; //score bonus for standing destinations
|
||||||
|
item.Sitting = sitting; //score bonus for sitting destinations
|
||||||
|
item.Ground = ground; //score bonus for sitting on ground
|
||||||
|
|
||||||
|
item.Rsflags = (SLOTFlags)rsflags;
|
||||||
|
item.SnapTargetSlot = snaptargetslot;
|
||||||
|
|
||||||
|
if (version >= 6)
|
||||||
|
{
|
||||||
|
var minproximity = io.ReadInt32();
|
||||||
|
var maxproximity = io.ReadInt32();
|
||||||
|
var optimalproximity = io.ReadInt32();
|
||||||
|
var i9 = io.ReadInt32();
|
||||||
|
var i10 = io.ReadInt32();
|
||||||
|
|
||||||
|
item.MinProximity = minproximity;
|
||||||
|
item.MaxProximity = maxproximity;
|
||||||
|
item.OptimalProximity = optimalproximity;
|
||||||
|
item.MaxSize = i9;
|
||||||
|
item.I10 = i10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version <= 9) {
|
||||||
|
item.MinProximity *= 16;
|
||||||
|
item.MaxProximity *= 16;
|
||||||
|
item.OptimalProximity *= 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version >= 7) item.Gradient = io.ReadFloat();
|
||||||
|
|
||||||
|
if (version >= 8) item.Height = io.ReadInt32();
|
||||||
|
|
||||||
|
if (item.Height == 0) item.Height = 5; //use offset height, nonstandard.
|
||||||
|
|
||||||
|
if (version >= 9)
|
||||||
|
{
|
||||||
|
item.Facing = (SLOTFacing)io.ReadInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version >= 10) item.Resolution = io.ReadInt32();
|
||||||
|
|
||||||
|
if (!Slots.ContainsKey(item.Type)) Slots.Add(item.Type, new List<SLOTItem>());
|
||||||
|
Slots[item.Type].Add(item);
|
||||||
|
Chronological.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteInt32(0);
|
||||||
|
io.WriteInt32(10); //version
|
||||||
|
io.WriteCString("TOLS", 4);
|
||||||
|
io.WriteUInt32((uint)Chronological.Count);
|
||||||
|
foreach (var slot in Chronological)
|
||||||
|
{
|
||||||
|
io.WriteUInt16(slot.Type);
|
||||||
|
io.WriteFloat(slot.Offset.X);
|
||||||
|
io.WriteFloat(slot.Offset.Y);
|
||||||
|
io.WriteFloat(slot.Offset.Z);
|
||||||
|
|
||||||
|
io.WriteInt32(slot.Standing);
|
||||||
|
io.WriteInt32(slot.Sitting);
|
||||||
|
io.WriteInt32(slot.Ground);
|
||||||
|
io.WriteInt32((int)slot.Rsflags);
|
||||||
|
io.WriteInt32(slot.SnapTargetSlot);
|
||||||
|
|
||||||
|
io.WriteInt32(slot.MinProximity);
|
||||||
|
io.WriteInt32(slot.MaxProximity);
|
||||||
|
io.WriteInt32(slot.OptimalProximity);
|
||||||
|
io.WriteInt32(slot.MaxSize);
|
||||||
|
io.WriteInt32(slot.I10);
|
||||||
|
|
||||||
|
io.WriteFloat(slot.Gradient);
|
||||||
|
io.WriteInt32(slot.Height);
|
||||||
|
io.WriteInt32((int)slot.Facing);
|
||||||
|
io.WriteInt32(slot.Resolution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum SLOTFlags : int
|
||||||
|
{
|
||||||
|
NORTH = 1,
|
||||||
|
NORTH_EAST = 2,
|
||||||
|
EAST = 4,
|
||||||
|
SOUTH_EAST = 8,
|
||||||
|
SOUTH = 16,
|
||||||
|
SOUTH_WEST = 32,
|
||||||
|
WEST = 64,
|
||||||
|
NORTH_WEST = 128,
|
||||||
|
AllowAnyRotation = 256, //unknown - used for snap to offset? (but not all the time?)
|
||||||
|
Absolute = 512, //do not rotate goal around object
|
||||||
|
FacingAwayFromObject = 1024, //deprecated. does not appear - replaced by Facing field
|
||||||
|
IgnoreRooms = 2048,
|
||||||
|
SnapToDirection = 4096,
|
||||||
|
RandomScoring = 8192,
|
||||||
|
AllowFailureTrees = 16385,
|
||||||
|
AllowDifferentAlts = 32768,
|
||||||
|
UseAverageObjectLocation = 65536,
|
||||||
|
|
||||||
|
FSOEqualProximityScore = 1 << 29,
|
||||||
|
FSOSquare = 1 << 30
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SLOTFacing : int
|
||||||
|
{
|
||||||
|
FaceAnywhere = -3,
|
||||||
|
FaceTowardsObject = -2,
|
||||||
|
FaceAwayFromObject = -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SLOTItem
|
||||||
|
{
|
||||||
|
public ushort Type { get; set; }
|
||||||
|
public Vector3 Offset;
|
||||||
|
public int Standing { get; set; } = 1;
|
||||||
|
public int Sitting { get; set; } = 0;
|
||||||
|
public int Ground { get; set; } = 0;
|
||||||
|
public SLOTFlags Rsflags { get; set; }
|
||||||
|
public int SnapTargetSlot { get; set; } = -1;
|
||||||
|
public int MinProximity { get; set; }
|
||||||
|
public int MaxProximity { get; set; } = 0;
|
||||||
|
public int OptimalProximity { get; set; } = 0;
|
||||||
|
public int MaxSize { get; set; } = 100;
|
||||||
|
public int I10;
|
||||||
|
public float Gradient { get; set; }
|
||||||
|
public SLOTFacing Facing { get; set; } = SLOTFacing.FaceTowardsObject;
|
||||||
|
public int Resolution { get; set; } = 16;
|
||||||
|
public int Height { get; set; }
|
||||||
|
|
||||||
|
public float OffsetX
|
||||||
|
{
|
||||||
|
get => Offset.X;
|
||||||
|
set => Offset.X = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float OffsetY
|
||||||
|
{
|
||||||
|
get => Offset.Y;
|
||||||
|
set => Offset.Y = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float OffsetZ
|
||||||
|
{
|
||||||
|
get => Offset.Z;
|
||||||
|
set => Offset.Z = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
299
server/tso.files/Formats/IFF/Chunks/SPR.cs
Executable file
299
server/tso.files/Formats/IFF/Chunks/SPR.cs
Executable file
|
@ -0,0 +1,299 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using FSO.Common.Utils;
|
||||||
|
using FSO.Common;
|
||||||
|
using FSO.Common.Rendering;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type holds a number of paletted sprites that share a common color palette and lack z-buffers and
|
||||||
|
/// alpha buffers. SPR# chunks can be either big-endian or little-endian, which must be determined by comparing
|
||||||
|
/// the first two bytes to zero (since no version number uses more than two bytes).
|
||||||
|
/// </summary>
|
||||||
|
public class SPR : IffChunk
|
||||||
|
{
|
||||||
|
public List<SPRFrame> Frames { get; internal set; }
|
||||||
|
public ushort PaletteID;
|
||||||
|
private List<uint> Offsets;
|
||||||
|
public ByteOrder ByteOrd;
|
||||||
|
public bool WallStyle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a SPR chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a SPR chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var version1 = io.ReadUInt16();
|
||||||
|
var version2 = io.ReadUInt16();
|
||||||
|
uint version = 0;
|
||||||
|
|
||||||
|
if (version1 == 0)
|
||||||
|
{
|
||||||
|
io.ByteOrder = ByteOrder.BIG_ENDIAN;
|
||||||
|
version = (uint)(((version2|0xFF00)>>8) | ((version2&0xFF)<<8));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
version = version1;
|
||||||
|
}
|
||||||
|
ByteOrd = io.ByteOrder;
|
||||||
|
|
||||||
|
var spriteCount = io.ReadUInt32();
|
||||||
|
PaletteID = (ushort)io.ReadUInt32();
|
||||||
|
|
||||||
|
Frames = new List<SPRFrame>();
|
||||||
|
if (version != 1001)
|
||||||
|
{
|
||||||
|
var offsetTable = new List<uint>();
|
||||||
|
for (var i = 0; i < spriteCount; i++)
|
||||||
|
{
|
||||||
|
offsetTable.Add(io.ReadUInt32());
|
||||||
|
}
|
||||||
|
Offsets = offsetTable;
|
||||||
|
for (var i = 0; i < spriteCount; i++)
|
||||||
|
{
|
||||||
|
var frame = new SPRFrame(this);
|
||||||
|
io.Seek(SeekOrigin.Begin, offsetTable[i]);
|
||||||
|
var guessedSize = ((i + 1 < offsetTable.Count) ? offsetTable[i + 1] : (uint)stream.Length) - offsetTable[i];
|
||||||
|
frame.Read(version, io, guessedSize);
|
||||||
|
Frames.Add(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (io.HasMore)
|
||||||
|
{
|
||||||
|
var frame = new SPRFrame(this);
|
||||||
|
frame.Read(version, io, 0);
|
||||||
|
Frames.Add(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The frame (I.E sprite) of a SPR chunk.
|
||||||
|
/// </summary>
|
||||||
|
public class SPRFrame : ITextureProvider
|
||||||
|
{
|
||||||
|
public static PALT DEFAULT_PALT = new PALT(Color.Black);
|
||||||
|
|
||||||
|
public uint Version;
|
||||||
|
private SPR Parent;
|
||||||
|
private Texture2D PixelCache;
|
||||||
|
private byte[] ToDecode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new SPRFrame instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent">A SPR parent.</param>
|
||||||
|
public SPRFrame(SPR parent)
|
||||||
|
{
|
||||||
|
this.Parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a SPRFrame from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a SPRFrame.</param>
|
||||||
|
public void Read(uint version, IoBuffer io, uint guessedSize)
|
||||||
|
{
|
||||||
|
if (version == 1001)
|
||||||
|
{
|
||||||
|
var spriteFersion = io.ReadUInt32();
|
||||||
|
|
||||||
|
var size = io.ReadUInt32();
|
||||||
|
this.Version = spriteFersion;
|
||||||
|
|
||||||
|
if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1001, io);
|
||||||
|
else ToDecode = io.ReadBytes(size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.Version = version;
|
||||||
|
if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1000, io);
|
||||||
|
else ToDecode = io.ReadBytes(guessedSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReadDeferred(uint version, IoBuffer io)
|
||||||
|
{
|
||||||
|
var reserved = io.ReadUInt32();
|
||||||
|
var height = io.ReadUInt16();
|
||||||
|
var width = io.ReadUInt16();
|
||||||
|
this.Init(width, height);
|
||||||
|
this.Decode(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DecodeIfRequired()
|
||||||
|
{
|
||||||
|
if (ToDecode != null)
|
||||||
|
{
|
||||||
|
using (IoBuffer buf = IoBuffer.FromStream(new MemoryStream(ToDecode), Parent.ByteOrd))
|
||||||
|
{
|
||||||
|
ReadDeferred(Version, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
ToDecode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes this SPRFrame.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="io">IOBuffer used to read a SPRFrame.</param>
|
||||||
|
private void Decode(IoBuffer io)
|
||||||
|
{
|
||||||
|
var palette = Parent.ChunkParent.Get<PALT>(Parent.PaletteID);
|
||||||
|
if (palette == null)
|
||||||
|
{
|
||||||
|
palette = DEFAULT_PALT;
|
||||||
|
}
|
||||||
|
|
||||||
|
var y = 0;
|
||||||
|
var endmarker = false;
|
||||||
|
|
||||||
|
while (!endmarker){
|
||||||
|
var command = io.ReadByte();
|
||||||
|
var count = io.ReadByte();
|
||||||
|
|
||||||
|
switch (command){
|
||||||
|
/** Start marker **/
|
||||||
|
case 0x00:
|
||||||
|
case 0x10:
|
||||||
|
break;
|
||||||
|
/** Fill row with pixel data **/
|
||||||
|
case 0x04:
|
||||||
|
var bytes = count - 2;
|
||||||
|
var x = 0;
|
||||||
|
|
||||||
|
while (bytes > 0){
|
||||||
|
var pxCommand = io.ReadByte();
|
||||||
|
var pxCount = io.ReadByte();
|
||||||
|
bytes -= 2;
|
||||||
|
|
||||||
|
switch (pxCommand){
|
||||||
|
/** Next {n} pixels are transparent **/
|
||||||
|
case 0x01:
|
||||||
|
x += pxCount;
|
||||||
|
break;
|
||||||
|
/** Next {n} pixels are the same palette color **/
|
||||||
|
case 0x02:
|
||||||
|
var index = io.ReadByte();
|
||||||
|
var padding = io.ReadByte();
|
||||||
|
bytes -= 2;
|
||||||
|
|
||||||
|
var color = palette.Colors[index];
|
||||||
|
for (var j=0; j < pxCount; j++){
|
||||||
|
this.SetPixel(x, y, color);
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
/** Next {n} pixels are specific palette colours **/
|
||||||
|
case 0x03:
|
||||||
|
for (var j=0; j < pxCount; j++){
|
||||||
|
var index2 = io.ReadByte();
|
||||||
|
var color2 = palette.Colors[index2];
|
||||||
|
this.SetPixel(x, y, color2);
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
bytes -= pxCount;
|
||||||
|
if (pxCount % 2 != 0){
|
||||||
|
//Padding
|
||||||
|
io.ReadByte();
|
||||||
|
bytes--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
y++;
|
||||||
|
break;
|
||||||
|
/** End marker **/
|
||||||
|
case 0x05:
|
||||||
|
endmarker = true;
|
||||||
|
break;
|
||||||
|
/** Leave next rows transparent **/
|
||||||
|
case 0x09:
|
||||||
|
y += count;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color[] Data;
|
||||||
|
public int Width { get; internal set; }
|
||||||
|
public int Height { get; internal set; }
|
||||||
|
|
||||||
|
protected void Init(int width, int height)
|
||||||
|
{
|
||||||
|
this.Width = width;
|
||||||
|
this.Height = height;
|
||||||
|
Data = new Color[Width * Height];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color GetPixel(int x, int y)
|
||||||
|
{
|
||||||
|
return Data[(y * Width) + x];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPixel(int x, int y, Color color)
|
||||||
|
{
|
||||||
|
Data[(y * Width) + x] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Texture2D GetTexture(GraphicsDevice device)
|
||||||
|
{
|
||||||
|
DecodeIfRequired();
|
||||||
|
if (PixelCache == null)
|
||||||
|
{
|
||||||
|
var mip = !Parent.WallStyle && FSOEnvironment.Enable3D && FSOEnvironment.EnableNPOTMip;
|
||||||
|
var tc = FSOEnvironment.TexCompress;
|
||||||
|
|
||||||
|
if (Width * Height > 0)
|
||||||
|
{
|
||||||
|
var w = Math.Max(1, Width);
|
||||||
|
var h = Math.Max(1, Height);
|
||||||
|
if (mip && TextureUtils.OverrideCompression(w, h)) tc = false;
|
||||||
|
if (tc)
|
||||||
|
{
|
||||||
|
PixelCache = new Texture2D(device, ((w+3)/4)*4, ((h+3)/4)*4, mip, SurfaceFormat.Dxt5);
|
||||||
|
if (mip)
|
||||||
|
TextureUtils.UploadDXT5WithMips(PixelCache, w, h, device, Data);
|
||||||
|
else
|
||||||
|
PixelCache.SetData<byte>(TextureUtils.DXT5Compress(Data, w, h).Item1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PixelCache = new Texture2D(device, w, h, mip, SurfaceFormat.Color);
|
||||||
|
if (mip)
|
||||||
|
TextureUtils.UploadWithMips(PixelCache, device, Data);
|
||||||
|
else
|
||||||
|
PixelCache.SetData<Color>(this.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PixelCache = new Texture2D(device, Math.Max(1, Width), Math.Max(1, Height), mip, SurfaceFormat.Color);
|
||||||
|
PixelCache.SetData<Color>(new Color[] { Color.Transparent });
|
||||||
|
}
|
||||||
|
|
||||||
|
PixelCache.Tag = new TextureInfo(PixelCache, Width, Height);
|
||||||
|
if (!IffFile.RETAIN_CHUNK_DATA) Data = null;
|
||||||
|
}
|
||||||
|
return PixelCache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
787
server/tso.files/Formats/IFF/Chunks/SPR2.cs
Executable file
787
server/tso.files/Formats/IFF/Chunks/SPR2.cs
Executable file
|
@ -0,0 +1,787 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using FSO.Common.Utils;
|
||||||
|
using FSO.Common.Rendering;
|
||||||
|
using FSO.Common;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type holds a number of paletted sprites that may have z-buffer and/or alpha channels.
|
||||||
|
/// </summary>
|
||||||
|
public class SPR2 : IffChunk
|
||||||
|
{
|
||||||
|
public SPR2Frame[] Frames = new SPR2Frame[0];
|
||||||
|
public uint DefaultPaletteID;
|
||||||
|
public bool SpritePreprocessed;
|
||||||
|
|
||||||
|
private bool _ZAsAlpha;
|
||||||
|
public bool ZAsAlpha
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _ZAsAlpha;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value && !_ZAsAlpha)
|
||||||
|
{
|
||||||
|
foreach (var frame in Frames)
|
||||||
|
{
|
||||||
|
if (frame.Decoded && frame.PixelData != null) frame.CopyZToAlpha();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ZAsAlpha = value;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _FloorCopy;
|
||||||
|
public int FloorCopy
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _FloorCopy;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value > 0 && _FloorCopy == 0)
|
||||||
|
{
|
||||||
|
foreach (var frame in Frames)
|
||||||
|
{
|
||||||
|
if (frame.Decoded && frame.PixelData != null)
|
||||||
|
{
|
||||||
|
if (value == 1) frame.FloorCopy();
|
||||||
|
if (value == 2) frame.FloorCopyWater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_FloorCopy = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a SPR2 chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a SPR2 chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var version = io.ReadUInt32();
|
||||||
|
uint spriteCount = 0;
|
||||||
|
|
||||||
|
if (version == 1000)
|
||||||
|
{
|
||||||
|
spriteCount = io.ReadUInt32();
|
||||||
|
DefaultPaletteID = io.ReadUInt32();
|
||||||
|
var offsetTable = new uint[spriteCount];
|
||||||
|
for (var i = 0; i < spriteCount; i++)
|
||||||
|
{
|
||||||
|
offsetTable[i] = io.ReadUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
Frames = new SPR2Frame[spriteCount];
|
||||||
|
for (var i = 0; i < spriteCount; i++)
|
||||||
|
{
|
||||||
|
var frame = new SPR2Frame(this);
|
||||||
|
io.Seek(SeekOrigin.Begin, offsetTable[i]);
|
||||||
|
|
||||||
|
var guessedSize = ((i + 1 < offsetTable.Length) ? offsetTable[i + 1] : (uint)stream.Length) - offsetTable[i];
|
||||||
|
|
||||||
|
frame.Read(version, io, guessedSize);
|
||||||
|
Frames[i] = frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (version == 1001)
|
||||||
|
{
|
||||||
|
DefaultPaletteID = io.ReadUInt32();
|
||||||
|
spriteCount = io.ReadUInt32();
|
||||||
|
|
||||||
|
Frames = new SPR2Frame[spriteCount];
|
||||||
|
for (var i = 0; i < spriteCount; i++)
|
||||||
|
{
|
||||||
|
var frame = new SPR2Frame(this);
|
||||||
|
frame.Read(version, io, 0);
|
||||||
|
Frames[i] = frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
if (IffFile.TargetTS1)
|
||||||
|
{
|
||||||
|
io.WriteUInt32(1000);
|
||||||
|
uint length = 0;
|
||||||
|
if (Frames != null) length = (uint)Frames.Length;
|
||||||
|
io.WriteUInt32(length);
|
||||||
|
DefaultPaletteID = Frames?.FirstOrDefault()?.PaletteID ?? DefaultPaletteID;
|
||||||
|
io.WriteUInt32(DefaultPaletteID);
|
||||||
|
// begin offset table
|
||||||
|
var offTableStart = stream.Position;
|
||||||
|
for (int i = 0; i < length; i++) io.WriteUInt32(0); //filled in later
|
||||||
|
var offsets = new uint[length];
|
||||||
|
int offInd = 0;
|
||||||
|
if (Frames != null)
|
||||||
|
{
|
||||||
|
foreach (var frame in Frames)
|
||||||
|
{
|
||||||
|
offsets[offInd++] = (uint)stream.Position;
|
||||||
|
frame.Write(io, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
io.Seek(SeekOrigin.Begin, offTableStart);
|
||||||
|
foreach (var off in offsets) io.WriteUInt32(off);
|
||||||
|
io.Seek(SeekOrigin.End, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
io.WriteUInt32(1001);
|
||||||
|
io.WriteUInt32(DefaultPaletteID);
|
||||||
|
if (Frames == null) io.WriteUInt32(0);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
io.WriteUInt32((uint)Frames.Length);
|
||||||
|
foreach (var frame in Frames)
|
||||||
|
{
|
||||||
|
frame.Write(io, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyZToAlpha()
|
||||||
|
{
|
||||||
|
foreach (var frame in Frames)
|
||||||
|
{
|
||||||
|
frame.CopyZToAlpha();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
if (Frames == null) return;
|
||||||
|
foreach (var frame in Frames)
|
||||||
|
{
|
||||||
|
var palette = ChunkParent.Get<PALT>(frame.PaletteID);
|
||||||
|
if (palette != null) palette.References--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The frame (I.E sprite) of a SPR2 chunk.
|
||||||
|
/// </summary>
|
||||||
|
public class SPR2Frame : ITextureProvider, IWorldTextureProvider
|
||||||
|
{
|
||||||
|
public Color[] PixelData;
|
||||||
|
public byte[] ZBufferData;
|
||||||
|
public byte[] PalData;
|
||||||
|
|
||||||
|
private WeakReference<Texture2D> ZCache = new WeakReference<Texture2D>(null);
|
||||||
|
private WeakReference<Texture2D> PixelCache = new WeakReference<Texture2D>(null);
|
||||||
|
private Texture2D PermaRefZ;
|
||||||
|
private Texture2D PermaRefP;
|
||||||
|
|
||||||
|
public int Width { get; internal set; }
|
||||||
|
public int Height { get; internal set; }
|
||||||
|
public uint Flags { get; internal set; }
|
||||||
|
public ushort PaletteID { get; set; }
|
||||||
|
public ushort TransparentColorIndex { get; internal set; }
|
||||||
|
public Vector2 Position { get; internal set; }
|
||||||
|
|
||||||
|
private SPR2 Parent;
|
||||||
|
private uint Version;
|
||||||
|
private byte[] ToDecode;
|
||||||
|
public bool Decoded
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ToDecode == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool ContainsNothing = false;
|
||||||
|
public bool ContainsNoZ = false;
|
||||||
|
|
||||||
|
public SPR2Frame(SPR2 parent)
|
||||||
|
{
|
||||||
|
this.Parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a SPR2 chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="version">Version of the SPR2 that this frame belongs to.</param>
|
||||||
|
/// <param name="stream">A IOBuffer object used to read a SPR2 chunk.</param>
|
||||||
|
public void Read(uint version, IoBuffer io, uint guessedSize)
|
||||||
|
{
|
||||||
|
Version = version;
|
||||||
|
if (version == 1001)
|
||||||
|
{
|
||||||
|
var spriteVersion = io.ReadUInt32();
|
||||||
|
var spriteSize = io.ReadUInt32();
|
||||||
|
if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1001, io);
|
||||||
|
else ToDecode = io.ReadBytes(spriteSize);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1000, io);
|
||||||
|
else ToDecode = io.ReadBytes(guessedSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReadDeferred(uint version, IoBuffer io)
|
||||||
|
{
|
||||||
|
this.Width = io.ReadUInt16();
|
||||||
|
this.Height = io.ReadUInt16();
|
||||||
|
this.Flags = io.ReadUInt32();
|
||||||
|
this.PaletteID = io.ReadUInt16();
|
||||||
|
|
||||||
|
if (version == 1000 || this.PaletteID == 0 || this.PaletteID == 0xA3A3)
|
||||||
|
{
|
||||||
|
this.PaletteID = (ushort)Parent.DefaultPaletteID;
|
||||||
|
}
|
||||||
|
|
||||||
|
TransparentColorIndex = io.ReadUInt16();
|
||||||
|
|
||||||
|
var y = io.ReadInt16();
|
||||||
|
var x = io.ReadInt16();
|
||||||
|
this.Position = new Vector2(x, y);
|
||||||
|
|
||||||
|
this.Decode(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DecodeIfRequired(bool z)
|
||||||
|
{
|
||||||
|
if (ToDecode != null && (((this.Flags & 0x02) == 0x02 && z && ZBufferData == null) || (!z && PixelData == null)))
|
||||||
|
{
|
||||||
|
using (IoBuffer buf = IoBuffer.FromStream(new MemoryStream(ToDecode), ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
ReadDeferred(Version, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TimedReferenceController.CurrentType == CacheType.PERMANENT) ToDecode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(IoWriter io, bool ts1)
|
||||||
|
{
|
||||||
|
using (var sprStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
var sprIO = IoWriter.FromStream(sprStream, ByteOrder.LITTLE_ENDIAN);
|
||||||
|
sprIO.WriteUInt16((ushort)Width);
|
||||||
|
sprIO.WriteUInt16((ushort)Height);
|
||||||
|
sprIO.WriteUInt32(Flags);
|
||||||
|
sprIO.WriteUInt16(PaletteID);
|
||||||
|
sprIO.WriteUInt16(TransparentColorIndex);
|
||||||
|
sprIO.WriteUInt16((ushort)Position.Y);
|
||||||
|
sprIO.WriteUInt16((ushort)Position.X);
|
||||||
|
SPR2FrameEncoder.WriteFrame(this, sprIO);
|
||||||
|
|
||||||
|
var data = sprStream.ToArray();
|
||||||
|
if (!ts1)
|
||||||
|
{
|
||||||
|
io.WriteUInt32(1001);
|
||||||
|
io.WriteUInt32((uint)data.Length);
|
||||||
|
}
|
||||||
|
io.WriteBytes(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes this SPR2Frame.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="io">An IOBuffer instance used to read a SPR2Frame.</param>
|
||||||
|
private void Decode(IoBuffer io)
|
||||||
|
{
|
||||||
|
var y = 0;
|
||||||
|
var endmarker = false;
|
||||||
|
|
||||||
|
var hasPixels = (this.Flags & 0x01) == 0x01;
|
||||||
|
var hasZBuffer = (this.Flags & 0x02) == 0x02;
|
||||||
|
var hasAlpha = (this.Flags & 0x04) == 0x04;
|
||||||
|
|
||||||
|
var numPixels = this.Width * this.Height;
|
||||||
|
var ow = Width;
|
||||||
|
var fc = Parent.FloorCopy;
|
||||||
|
if (fc > 0)
|
||||||
|
{
|
||||||
|
numPixels += Height;
|
||||||
|
Width++;
|
||||||
|
}
|
||||||
|
if (hasPixels){
|
||||||
|
this.PixelData = new Color[numPixels];
|
||||||
|
this.PalData = new byte[numPixels];
|
||||||
|
}
|
||||||
|
if (hasZBuffer){
|
||||||
|
this.ZBufferData = new byte[numPixels];
|
||||||
|
}
|
||||||
|
|
||||||
|
var palette = Parent.ChunkParent.Get<PALT>(this.PaletteID);
|
||||||
|
if (palette == null) palette = new PALT() { Colors = new Color[256] };
|
||||||
|
palette.References++;
|
||||||
|
var transparentPixel = palette.Colors[TransparentColorIndex];
|
||||||
|
transparentPixel.A = 0;
|
||||||
|
|
||||||
|
while (!endmarker && io.HasMore)
|
||||||
|
{
|
||||||
|
var marker = io.ReadUInt16();
|
||||||
|
var command = marker >> 13;
|
||||||
|
var count = marker & 0x1FFF;
|
||||||
|
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
/** Fill with pixel data **/
|
||||||
|
case 0x00:
|
||||||
|
var bytes = count;
|
||||||
|
bytes -= 2;
|
||||||
|
|
||||||
|
var x = 0;
|
||||||
|
|
||||||
|
while (bytes > 0)
|
||||||
|
{
|
||||||
|
var pxMarker = io.ReadUInt16();
|
||||||
|
var pxCommand = pxMarker >> 13;
|
||||||
|
var pxCount = pxMarker & 0x1FFF;
|
||||||
|
bytes -= 2;
|
||||||
|
|
||||||
|
switch (pxCommand)
|
||||||
|
{
|
||||||
|
case 0x01:
|
||||||
|
case 0x02:
|
||||||
|
var pxWithAlpha = pxCommand == 0x02;
|
||||||
|
for (var col = 0; col < pxCount; col++)
|
||||||
|
{
|
||||||
|
var zValue = io.ReadByte();
|
||||||
|
var pxValue = io.ReadByte();
|
||||||
|
bytes -= 2;
|
||||||
|
|
||||||
|
var pxColor = palette.Colors[pxValue];
|
||||||
|
if (pxWithAlpha)
|
||||||
|
{
|
||||||
|
var alpha = io.ReadByte();
|
||||||
|
pxColor.A = (byte)(alpha * 8.2258064516129032258064516129032);
|
||||||
|
bytes--;
|
||||||
|
}
|
||||||
|
//this mode draws the transparent colour as solid for some reason.
|
||||||
|
//fixes backdrop theater
|
||||||
|
var offset = (y * Width) + x;
|
||||||
|
this.PixelData[offset] = pxColor;
|
||||||
|
this.PalData[offset] = pxValue;
|
||||||
|
this.ZBufferData[offset] = zValue;
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
if (pxWithAlpha)
|
||||||
|
{
|
||||||
|
/** Padding? **/
|
||||||
|
if ((pxCount * 3) % 2 != 0){
|
||||||
|
bytes--;
|
||||||
|
io.ReadByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x03:
|
||||||
|
for (var col = 0; col < pxCount; col++)
|
||||||
|
{
|
||||||
|
var offset = (y * Width) + x;
|
||||||
|
this.PixelData[offset] = transparentPixel;
|
||||||
|
this.PalData[offset] = (byte)TransparentColorIndex;
|
||||||
|
this.PixelData[offset].A = 0;
|
||||||
|
if (hasZBuffer){
|
||||||
|
this.ZBufferData[offset] = 255;
|
||||||
|
}
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x06:
|
||||||
|
for (var col = 0; col < pxCount; col++)
|
||||||
|
{
|
||||||
|
var pxIndex = io.ReadByte();
|
||||||
|
bytes--;
|
||||||
|
var offset = (y * Width) + x;
|
||||||
|
var pxColor = palette.Colors[pxIndex];
|
||||||
|
byte z = 0;
|
||||||
|
|
||||||
|
//not sure if this should happen
|
||||||
|
/*if (pxIndex == TransparentColorIndex)
|
||||||
|
{
|
||||||
|
pxColor.A = 0;
|
||||||
|
z = 255;
|
||||||
|
}*/
|
||||||
|
this.PixelData[offset] = pxColor;
|
||||||
|
this.PalData[offset] = pxIndex;
|
||||||
|
if (hasZBuffer)
|
||||||
|
{
|
||||||
|
this.ZBufferData[offset] = z;
|
||||||
|
}
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
if (pxCount % 2 != 0)
|
||||||
|
{
|
||||||
|
bytes--;
|
||||||
|
io.ReadByte();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** If row isnt filled in, the rest is transparent **/
|
||||||
|
while (x < ow)
|
||||||
|
{
|
||||||
|
var offset = (y * Width) + x;
|
||||||
|
if (hasZBuffer)
|
||||||
|
{
|
||||||
|
this.ZBufferData[offset] = 255;
|
||||||
|
}
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
/** Leave the next count rows in the color channel filled with the transparent color,
|
||||||
|
* in the z-buffer channel filled with 255, and in the alpha channel filled with 0. **/
|
||||||
|
case 0x04:
|
||||||
|
for (var row = 0; row < count; row++)
|
||||||
|
{
|
||||||
|
for (var col = 0; col < Width; col++)
|
||||||
|
{
|
||||||
|
var offset = ((y+row) * Width) + col;
|
||||||
|
if (hasPixels)
|
||||||
|
{
|
||||||
|
this.PixelData[offset] = transparentPixel;
|
||||||
|
this.PalData[offset] = (byte)TransparentColorIndex;
|
||||||
|
}
|
||||||
|
if (hasAlpha)
|
||||||
|
{
|
||||||
|
this.PixelData[offset].A = 0;
|
||||||
|
}
|
||||||
|
if (hasZBuffer)
|
||||||
|
{
|
||||||
|
ZBufferData[offset] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
y += count - 1;
|
||||||
|
break;
|
||||||
|
case 0x05:
|
||||||
|
endmarker = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
if (!IffFile.RETAIN_CHUNK_DATA) PalData = null;
|
||||||
|
if (Parent.ZAsAlpha) CopyZToAlpha();
|
||||||
|
if (Parent.FloorCopy == 1) FloorCopy();
|
||||||
|
if (Parent.FloorCopy == 2) FloorCopyWater();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a pixel from this SPR2Frame.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">X position of pixel.</param>
|
||||||
|
/// <param name="y">Y position of pixel.</param>
|
||||||
|
/// <returns>A Color instance with color of pixel.</returns>
|
||||||
|
public Color GetPixel(int x, int y)
|
||||||
|
{
|
||||||
|
return PixelData[(y * Width) + x];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a pixel from this SPR2Frame.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">X position of pixel.</param>
|
||||||
|
/// <param name="y">Y position of pixel.</param>
|
||||||
|
public void SetPixel(int x, int y, Color color)
|
||||||
|
{
|
||||||
|
PixelData[(y * Width) + x] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the Z buffer into the current sprite's alpha channel. Used by water tile.
|
||||||
|
/// </summary>
|
||||||
|
public void CopyZToAlpha()
|
||||||
|
{
|
||||||
|
for (int i=0; i<PixelData.Length; i++)
|
||||||
|
{
|
||||||
|
PixelData[i].A = (ZBufferData[i] < 32)?(byte)0:ZBufferData[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FloorCopy()
|
||||||
|
{
|
||||||
|
if (Width%2 != 0)
|
||||||
|
{
|
||||||
|
var target = new Color[(Width + 1) * Height];
|
||||||
|
for (int y=0; y<Height; y++)
|
||||||
|
{
|
||||||
|
Array.Copy(PixelData, y * Width, target, y * (Width + 1), Width);
|
||||||
|
}
|
||||||
|
PixelData = target;
|
||||||
|
Width += 1;
|
||||||
|
}
|
||||||
|
var ndat = new Color[PixelData.Length];
|
||||||
|
int hw = (Width) / 2;
|
||||||
|
int hh = (Height) / 2;
|
||||||
|
int idx = 0;
|
||||||
|
for (int y = 0; y < Height; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < Width; x++)
|
||||||
|
{
|
||||||
|
var xp = (x + hw) % Width;
|
||||||
|
var yp = (y + hh) % Height;
|
||||||
|
var rep = PixelData[xp + yp * Width];
|
||||||
|
if (rep.A >= 254) ndat[idx] = rep;
|
||||||
|
else ndat[idx] = PixelData[idx];
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PixelData = ndat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FloorCopyWater()
|
||||||
|
{
|
||||||
|
if (Width % 2 != 0)
|
||||||
|
{
|
||||||
|
var target = new Color[(Width + 1) * Height];
|
||||||
|
for (int y = 0; y < Height; y++)
|
||||||
|
{
|
||||||
|
Array.Copy(PixelData, y * Width, target, y * (Width + 1), Width);
|
||||||
|
}
|
||||||
|
PixelData = target;
|
||||||
|
Width += 1;
|
||||||
|
}
|
||||||
|
var ndat = new Color[PixelData.Length];
|
||||||
|
int hw = (Width) / 2;
|
||||||
|
int hh = (Height) / 2;
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
var palette = Parent.ChunkParent.Get<PALT>(this.PaletteID);
|
||||||
|
var transparentPixel = palette.Colors[TransparentColorIndex];
|
||||||
|
transparentPixel.A = 0;
|
||||||
|
|
||||||
|
for (int y = 0; y < Height; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < Width; x++)
|
||||||
|
{
|
||||||
|
var dat = PixelData[x + y * Width];
|
||||||
|
if (dat.PackedValue == 0 || dat.PackedValue == transparentPixel.PackedValue)
|
||||||
|
{
|
||||||
|
if (x < hw)
|
||||||
|
{
|
||||||
|
for (int j = x; j < Width; j++)
|
||||||
|
{
|
||||||
|
var rep = PixelData[j + y * Width];
|
||||||
|
if (!(rep.PackedValue == 0 || rep.PackedValue == transparentPixel.PackedValue))
|
||||||
|
{
|
||||||
|
ndat[idx] = rep;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int j = x; j >= 0; j--)
|
||||||
|
{
|
||||||
|
var rep = PixelData[j + y * Width];
|
||||||
|
if (!(rep.PackedValue == 0 || rep.PackedValue == transparentPixel.PackedValue))
|
||||||
|
{
|
||||||
|
ndat[idx] = rep;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
ndat[idx] = PixelData[idx];
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PixelData = ndat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a texture representing this SPR2Frame.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="device">GraphicsDevice instance used for drawing.</param>
|
||||||
|
/// <returns>A Texture2D instance holding the texture data.</returns>
|
||||||
|
public Texture2D GetTexture(GraphicsDevice device)
|
||||||
|
{
|
||||||
|
return GetTexture(device, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Texture2D GetTexture(GraphicsDevice device, bool onlyThis)
|
||||||
|
{
|
||||||
|
if (ContainsNothing) return null;
|
||||||
|
Texture2D result = null;
|
||||||
|
if (!PixelCache.TryGetTarget(out result) || ((CachableTexture2D)result).BeingDisposed || result.IsDisposed)
|
||||||
|
{
|
||||||
|
DecodeIfRequired(false);
|
||||||
|
if (this.Width == 0 || this.Height == 0)
|
||||||
|
{
|
||||||
|
ContainsNothing = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var tc = FSOEnvironment.TexCompress;
|
||||||
|
var mip = FSOEnvironment.Enable3D && (FSOEnvironment.EnableNPOTMip || (Width == 128 && Height == 64));
|
||||||
|
if (mip && TextureUtils.OverrideCompression(Width, Height)) tc = false;
|
||||||
|
if (tc)
|
||||||
|
{
|
||||||
|
|
||||||
|
result = new CachableTexture2D(device, ((Width+3)/4)*4, ((Height + 3) / 4) * 4, mip, SurfaceFormat.Dxt5);
|
||||||
|
if (mip) TextureUtils.UploadDXT5WithMips(result, Width, Height, device, this.PixelData);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var dxt = TextureUtils.DXT5Compress(this.PixelData, this.Width, this.Height);
|
||||||
|
result.SetData<byte>(dxt.Item1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = new CachableTexture2D(device, this.Width, this.Height, mip, SurfaceFormat.Color);
|
||||||
|
if (mip) TextureUtils.UploadWithMips(result, device, this.PixelData);
|
||||||
|
else result.SetData<Color>(this.PixelData);
|
||||||
|
}
|
||||||
|
result.Tag = new TextureInfo(result, Width, Height);
|
||||||
|
PixelCache = new WeakReference<Texture2D>(result);
|
||||||
|
if (TimedReferenceController.CurrentType == CacheType.PERMANENT) PermaRefP = result;
|
||||||
|
if (!IffFile.RETAIN_CHUNK_DATA)
|
||||||
|
{
|
||||||
|
PixelData = null;
|
||||||
|
//if (onlyThis && !FSOEnvironment.Enable3D) ZBufferData = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (TimedReferenceController.CurrentType != CacheType.PERMANENT) TimedReferenceController.KeepAlive(result, KeepAliveType.ACCESS);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Texture2D TryGetCachedZ()
|
||||||
|
{
|
||||||
|
Texture2D result = null;
|
||||||
|
if (ContainsNothing || ContainsNoZ) return null;
|
||||||
|
if (!ZCache.TryGetTarget(out result) || ((CachableTexture2D)result).BeingDisposed || result.IsDisposed)
|
||||||
|
return null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a z-texture representing this SPR2Frame.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="device">GraphicsDevice instance used for drawing.</param>
|
||||||
|
/// <returns>A Texture2D instance holding the texture data.</returns>
|
||||||
|
public Texture2D GetZTexture(GraphicsDevice device)
|
||||||
|
{
|
||||||
|
return GetZTexture(device, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Texture2D GetZTexture(GraphicsDevice device, bool onlyThis)
|
||||||
|
{
|
||||||
|
Texture2D result = null;
|
||||||
|
if (ContainsNothing || ContainsNoZ) return null;
|
||||||
|
if (!ZCache.TryGetTarget(out result) || ((CachableTexture2D)result).BeingDisposed || result.IsDisposed)
|
||||||
|
{
|
||||||
|
DecodeIfRequired(true);
|
||||||
|
if (this.Width == 0 || this.Height == 0)
|
||||||
|
{
|
||||||
|
ContainsNothing = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (ZBufferData == null)
|
||||||
|
{
|
||||||
|
ContainsNoZ = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (FSOEnvironment.TexCompress)
|
||||||
|
{
|
||||||
|
result = new CachableTexture2D(device, ((Width+3)/4)*4, ((Height+3)/4)*4, false, SurfaceFormat.Alpha8);
|
||||||
|
var tempZ = new byte[result.Width * result.Height];
|
||||||
|
var dind = 0;
|
||||||
|
var sind = 0;
|
||||||
|
for (int i=0; i<Height; i++)
|
||||||
|
{
|
||||||
|
Array.Copy(ZBufferData, sind, tempZ, dind, Width);
|
||||||
|
sind += Width;
|
||||||
|
dind += result.Width;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.SetData<byte>(tempZ);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = new CachableTexture2D(device, this.Width, this.Height, false, SurfaceFormat.Alpha8);
|
||||||
|
result.SetData<byte>(this.ZBufferData);
|
||||||
|
}
|
||||||
|
ZCache = new WeakReference<Texture2D>(result);
|
||||||
|
if (TimedReferenceController.CurrentType == CacheType.PERMANENT) PermaRefZ = result;
|
||||||
|
if (!IffFile.RETAIN_CHUNK_DATA)
|
||||||
|
{
|
||||||
|
//if (!FSOEnvironment.Enable3D) ZBufferData = null; disabled right now til we get a clean way of getting this post-world-texture for ultra lighting
|
||||||
|
if (onlyThis) PixelData = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (TimedReferenceController.CurrentType != CacheType.PERMANENT) TimedReferenceController.KeepAlive(result, KeepAliveType.ACCESS);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IWorldTextureProvider Members
|
||||||
|
|
||||||
|
public WorldTexture GetWorldTexture(GraphicsDevice device)
|
||||||
|
{
|
||||||
|
var result = new WorldTexture
|
||||||
|
{
|
||||||
|
Pixel = this.GetTexture(device, false)
|
||||||
|
};
|
||||||
|
result.ZBuffer = this.GetZTexture(device, false);
|
||||||
|
if (!IffFile.RETAIN_CHUNK_DATA)
|
||||||
|
{
|
||||||
|
PixelData = null;
|
||||||
|
if (!FSOEnvironment.Enable3D) ZBufferData = null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public Color[] SetData(Color[] px, byte[] zpx, Rectangle rect)
|
||||||
|
{
|
||||||
|
PixelCache = null; //can't exactly dispose this.. it's likely still in use!
|
||||||
|
ZCache = null;
|
||||||
|
PixelData = px;
|
||||||
|
ZBufferData = zpx;
|
||||||
|
Position = new Vector2(rect.X, rect.Y);
|
||||||
|
|
||||||
|
Width = rect.Width;
|
||||||
|
Height = rect.Height;
|
||||||
|
Flags = 7;
|
||||||
|
TransparentColorIndex = 255;
|
||||||
|
|
||||||
|
var colors = SPR2FrameEncoder.QuantizeFrame(this, out PalData);
|
||||||
|
|
||||||
|
var palt = new Color[256];
|
||||||
|
int i = 0;
|
||||||
|
foreach (var c in colors)
|
||||||
|
palt[i++] = new Color(c.R, c.G, c.B, (byte)255);
|
||||||
|
|
||||||
|
return palt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPalt(PALT p)
|
||||||
|
{
|
||||||
|
if (this.PaletteID != 0)
|
||||||
|
{
|
||||||
|
var old = Parent.ChunkParent.Get<PALT>(this.PaletteID);
|
||||||
|
if (old != null) old.References--;
|
||||||
|
}
|
||||||
|
PaletteID = p.ChunkID;
|
||||||
|
p.References++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
124
server/tso.files/Formats/IFF/Chunks/SPR2FrameEncoder.cs
Executable file
124
server/tso.files/Formats/IFF/Chunks/SPR2FrameEncoder.cs
Executable file
|
@ -0,0 +1,124 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public static class SPR2FrameEncoder
|
||||||
|
{
|
||||||
|
|
||||||
|
public delegate Color[] QuantizerFunction(SPR2Frame frame, out byte[] bytes);
|
||||||
|
|
||||||
|
public static QuantizerFunction QuantizeFrame;
|
||||||
|
|
||||||
|
public static void WriteFrame(SPR2Frame frame, IoWriter output)
|
||||||
|
{
|
||||||
|
var bytes = frame.PalData;
|
||||||
|
var col = frame.PixelData;
|
||||||
|
var zs = frame.ZBufferData;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
int blankLines = 0;
|
||||||
|
for (int y=0; y<frame.Height; y++)
|
||||||
|
{
|
||||||
|
byte lastCmd = 0;
|
||||||
|
List<byte> dataBuf = new List<byte>();
|
||||||
|
int rlecount = 0;
|
||||||
|
bool anySolid = false;
|
||||||
|
|
||||||
|
var scanStream = new MemoryStream();
|
||||||
|
var scanOut = IoWriter.FromStream(scanStream, ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
for (int x=0; x<frame.Width; x++)
|
||||||
|
{
|
||||||
|
byte plt = bytes[index];
|
||||||
|
byte a = col[index].A;
|
||||||
|
byte z = zs[index];
|
||||||
|
|
||||||
|
var cmd = getCmd(plt, a, z);
|
||||||
|
|
||||||
|
if (x == 0 || cmd != lastCmd)
|
||||||
|
{
|
||||||
|
if (x != 0)
|
||||||
|
{
|
||||||
|
//write a command to write the last sequence of pixels
|
||||||
|
scanOut.WriteUInt16((ushort)(((int)lastCmd<<13)|rlecount));
|
||||||
|
if ((dataBuf.Count % 2) != 0) dataBuf.Add(0);
|
||||||
|
scanOut.WriteBytes(dataBuf.ToArray());
|
||||||
|
dataBuf.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCmd = cmd;
|
||||||
|
rlecount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case 0x1:
|
||||||
|
case 0x2:
|
||||||
|
dataBuf.Add(z);
|
||||||
|
dataBuf.Add(plt);
|
||||||
|
if (cmd == 0x2) dataBuf.Add((byte)Math.Ceiling(a/ 8.2258064516129032258064516129032));
|
||||||
|
anySolid = true;
|
||||||
|
break;
|
||||||
|
case 0x6:
|
||||||
|
dataBuf.Add(plt);
|
||||||
|
anySolid = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rlecount++;
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anySolid)
|
||||||
|
{
|
||||||
|
//write a command to write the last sequence of pixels
|
||||||
|
scanOut.WriteUInt16((ushort)(((int)lastCmd << 13) | rlecount));
|
||||||
|
if ((dataBuf.Count % 2) != 0) dataBuf.Add(0);
|
||||||
|
scanOut.WriteBytes(dataBuf.ToArray());
|
||||||
|
dataBuf.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
var scanData = scanStream.ToArray();
|
||||||
|
|
||||||
|
if (scanData.Length == 0)
|
||||||
|
blankLines++; //line is transparent
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (blankLines > 0)
|
||||||
|
{
|
||||||
|
//add transparent lines before our new command
|
||||||
|
output.WriteUInt16((ushort)((0x4<<13) | blankLines));
|
||||||
|
blankLines = 0;
|
||||||
|
}
|
||||||
|
output.WriteUInt16((ushort)(scanData.Length+2));
|
||||||
|
output.WriteBytes(scanData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blankLines > 0)
|
||||||
|
{
|
||||||
|
//add transparent lines before our new command
|
||||||
|
output.WriteUInt16((ushort)((0x4 << 13) | blankLines));
|
||||||
|
blankLines = 0;
|
||||||
|
}
|
||||||
|
output.WriteUInt16((ushort)(0x5<<13));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte getCmd(byte col, byte a, byte z)
|
||||||
|
{
|
||||||
|
if (a == 0) return 0x03; //transparent fill
|
||||||
|
else if (a < 255) return 0x02; // col,a,z
|
||||||
|
else if (z > 0) return 0x01; // col,z
|
||||||
|
else return 0x06; // col
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
438
server/tso.files/Formats/IFF/Chunks/STR.cs
Executable file
438
server/tso.files/Formats/IFF/Chunks/STR.cs
Executable file
|
@ -0,0 +1,438 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type holds text strings.
|
||||||
|
/// The first two bytes correspond to the format code, of which there are four types.
|
||||||
|
/// Some chunks in the game do not specify any data after the version number, so be sure to implement bounds checking.
|
||||||
|
/// </summary>
|
||||||
|
public class STR : IffChunk
|
||||||
|
{
|
||||||
|
public static string[] LanguageSetNames =
|
||||||
|
{
|
||||||
|
"English (US)",
|
||||||
|
"English (UK)",
|
||||||
|
"French",
|
||||||
|
"German",
|
||||||
|
"Italian",
|
||||||
|
"Spanish",
|
||||||
|
"Dutch",
|
||||||
|
"Danish",
|
||||||
|
"Swedish",
|
||||||
|
"Norwegian",
|
||||||
|
"Finish",
|
||||||
|
"Hebrew",
|
||||||
|
"Russian",
|
||||||
|
"Portuguese",
|
||||||
|
"Japanese",
|
||||||
|
"Polish",
|
||||||
|
"Simplified Chinese",
|
||||||
|
"Traditional Chinese",
|
||||||
|
"Thai",
|
||||||
|
"Korean",
|
||||||
|
"Slovak"
|
||||||
|
};
|
||||||
|
|
||||||
|
public STRLanguageSet[] LanguageSets = new STRLanguageSet[20];
|
||||||
|
public static STRLangCode DefaultLangCode = STRLangCode.EnglishUS;
|
||||||
|
|
||||||
|
public STR()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many strings are in this chunk?
|
||||||
|
/// </summary>
|
||||||
|
public int Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return LanguageSets[0]?.Strings.Length ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public STRLanguageSet GetLanguageSet(STRLangCode set)
|
||||||
|
{
|
||||||
|
if (set == STRLangCode.Default) set = DefaultLangCode;
|
||||||
|
int code = (int)set;
|
||||||
|
if ((LanguageSets[code-1]?.Strings.Length ?? 0) == 0) return LanguageSets[0]; //if undefined, fallback to English US
|
||||||
|
else return LanguageSets[code-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSetInit(STRLangCode set)
|
||||||
|
{
|
||||||
|
if (set == STRLangCode.Default) set = DefaultLangCode;
|
||||||
|
if (set == STRLangCode.EnglishUS) return true;
|
||||||
|
int code = (int)set;
|
||||||
|
return (LanguageSets[code - 1].Strings.Length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitLanguageSet(STRLangCode set)
|
||||||
|
{
|
||||||
|
if (set == STRLangCode.Default) set = DefaultLangCode;
|
||||||
|
int code = (int)set;
|
||||||
|
var length = LanguageSets[0].Strings.Length;
|
||||||
|
LanguageSets[code - 1].Strings = new STRItem[length];
|
||||||
|
for (int i=0; i< length; i++)
|
||||||
|
{
|
||||||
|
var src = LanguageSets[0].Strings[i];
|
||||||
|
LanguageSets[code - 1].Strings[i] = new STRItem()
|
||||||
|
{
|
||||||
|
LanguageCode = (byte)code,
|
||||||
|
Value = src.Value,
|
||||||
|
Comment = src.Comment
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a string from this chunk.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of string.</param>
|
||||||
|
/// <returns>A string at specific index, null if not found.</returns>
|
||||||
|
///
|
||||||
|
public string GetString(int index)
|
||||||
|
{
|
||||||
|
return GetString(index, STRLangCode.Default);
|
||||||
|
}
|
||||||
|
public string GetString(int index, STRLangCode language)
|
||||||
|
{
|
||||||
|
var item = GetStringEntry(index, language);
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
return item.Value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetComment(int index)
|
||||||
|
{
|
||||||
|
return GetComment(index, STRLangCode.Default);
|
||||||
|
}
|
||||||
|
public string GetComment(int index, STRLangCode language)
|
||||||
|
{
|
||||||
|
var item = GetStringEntry(index, language);
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
return item.Comment;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetString(int index, string value)
|
||||||
|
{
|
||||||
|
SetString(index, value, STRLangCode.Default);
|
||||||
|
}
|
||||||
|
public void SetString(int index, string value, STRLangCode language)
|
||||||
|
{
|
||||||
|
var languageSet = GetLanguageSet(language);
|
||||||
|
if (index < languageSet.Strings.Length)
|
||||||
|
{
|
||||||
|
languageSet.Strings[index].Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SwapString(int srcindex, int dstindex)
|
||||||
|
{
|
||||||
|
foreach (var languageSet in LanguageSets)
|
||||||
|
{
|
||||||
|
if (languageSet.Strings.Length == 0) continue; //language not initialized
|
||||||
|
var temp = languageSet.Strings[srcindex];
|
||||||
|
languageSet.Strings[srcindex] = languageSet.Strings[dstindex];
|
||||||
|
languageSet.Strings[dstindex] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertString(int index, STRItem item)
|
||||||
|
{
|
||||||
|
byte i = 1;
|
||||||
|
foreach (var languageSet in LanguageSets) {
|
||||||
|
if (languageSet.Strings.Length == 0 && i > 1)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
continue; //language not initialized
|
||||||
|
}
|
||||||
|
var newStr = new STRItem[languageSet.Strings.Length + 1];
|
||||||
|
Array.Copy(languageSet.Strings, newStr, index); //copy before strings
|
||||||
|
newStr[index] = new STRItem()
|
||||||
|
{
|
||||||
|
LanguageCode = i,
|
||||||
|
Value = item.Value,
|
||||||
|
Comment = item.Comment
|
||||||
|
};
|
||||||
|
Array.Copy(languageSet.Strings, index, newStr, index + 1, (languageSet.Strings.Length - index));
|
||||||
|
languageSet.Strings = newStr;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveString(int index)
|
||||||
|
{
|
||||||
|
foreach (var languageSet in LanguageSets)
|
||||||
|
{
|
||||||
|
if (languageSet.Strings.Length == 0) continue; //language not initialized
|
||||||
|
var newStr = new STRItem[languageSet.Strings.Length - 1];
|
||||||
|
Array.Copy(languageSet.Strings, newStr, index); //copy before strings
|
||||||
|
Array.Copy(languageSet.Strings, index + 1, newStr, index, (languageSet.Strings.Length - (index + 1)));
|
||||||
|
languageSet.Strings = newStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a STRItem instance from this STR chunk.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of STRItem.</param>
|
||||||
|
/// <returns>STRItem at index, null if not found.</returns>
|
||||||
|
public STRItem GetStringEntry(int index)
|
||||||
|
{
|
||||||
|
return GetStringEntry(index, STRLangCode.Default);
|
||||||
|
}
|
||||||
|
public STRItem GetStringEntry(int index, STRLangCode language)
|
||||||
|
{
|
||||||
|
var languageSet = GetLanguageSet(language);
|
||||||
|
if (index < (languageSet?.Strings.Length ?? 0) && index > -1)
|
||||||
|
{
|
||||||
|
return languageSet.Strings[index];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a STR chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a STR chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var formatCode = io.ReadInt16();
|
||||||
|
LanguageSets = new STRLanguageSet[20];
|
||||||
|
if (!io.HasMore){
|
||||||
|
for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formatCode == 0)
|
||||||
|
{
|
||||||
|
var numStrings = io.ReadUInt16();
|
||||||
|
for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] };
|
||||||
|
LanguageSets[0].Strings = new STRItem[numStrings];
|
||||||
|
for (var i = 0; i < numStrings; i++)
|
||||||
|
{
|
||||||
|
LanguageSets[0].Strings[i] = new STRItem
|
||||||
|
{
|
||||||
|
Value = io.ReadPascalString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//This format changed 00 00 to use C strings rather than Pascal strings.
|
||||||
|
else if (formatCode == -1)
|
||||||
|
{
|
||||||
|
var numStrings = io.ReadUInt16();
|
||||||
|
for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] };
|
||||||
|
LanguageSets[0].Strings = new STRItem[numStrings];
|
||||||
|
for (var i = 0; i < numStrings; i++)
|
||||||
|
{
|
||||||
|
LanguageSets[0].Strings[i] = new STRItem
|
||||||
|
{
|
||||||
|
Value = io.ReadNullTerminatedUTF8()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//This format changed FF FF to use string pairs rather than single strings.
|
||||||
|
else if (formatCode == -2)
|
||||||
|
{
|
||||||
|
var numStrings = io.ReadUInt16();
|
||||||
|
for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] };
|
||||||
|
LanguageSets[0].Strings = new STRItem[numStrings];
|
||||||
|
for (var i = 0; i < numStrings; i++)
|
||||||
|
{
|
||||||
|
LanguageSets[0].Strings[i] = new STRItem
|
||||||
|
{
|
||||||
|
Value = io.ReadNullTerminatedString(),
|
||||||
|
Comment = io.ReadNullTerminatedString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//This format changed FD FF to use a language code.
|
||||||
|
else if (formatCode == -3)
|
||||||
|
{
|
||||||
|
var numStrings = io.ReadUInt16();
|
||||||
|
for (int i = 0; i < 20; i++) LanguageSets[i] = new STRLanguageSet { Strings = new STRItem[0] };
|
||||||
|
List<STRItem>[] LangSort = new List<STRItem>[20];
|
||||||
|
for (var i = 0; i < numStrings; i++)
|
||||||
|
{
|
||||||
|
var item = new STRItem
|
||||||
|
{
|
||||||
|
LanguageCode = io.ReadByte(),
|
||||||
|
Value = io.ReadNullTerminatedString(),
|
||||||
|
Comment = io.ReadNullTerminatedString()
|
||||||
|
};
|
||||||
|
|
||||||
|
var lang = item.LanguageCode;
|
||||||
|
if (lang == 0) lang = 1;
|
||||||
|
else if (lang < 0 || lang > 20) continue; //???
|
||||||
|
if (LangSort[lang - 1] == null)
|
||||||
|
{
|
||||||
|
LangSort[lang-1] = new List<STRItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
LangSort[lang - 1].Add(item);
|
||||||
|
}
|
||||||
|
for (int i=0; i<LanguageSets.Length; i++)
|
||||||
|
{
|
||||||
|
if (LangSort[i] != null) LanguageSets[i].Strings = LangSort[i].ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//This format is only used in The Sims Online. The format is essentially a performance improvement:
|
||||||
|
//it counteracts both the short string limit of 255 characters found in 00 00 and the inherent slowness
|
||||||
|
//of null-terminated strings in the other formats (which requires two passes over each string), and it
|
||||||
|
//also provides a string pair count for each language set which eliminates the need for two passes over
|
||||||
|
//each language set.
|
||||||
|
else if (formatCode == -4)
|
||||||
|
{
|
||||||
|
var numLanguageSets = io.ReadByte();
|
||||||
|
this.LanguageSets = new STRLanguageSet[numLanguageSets];
|
||||||
|
|
||||||
|
for(var i=0; i < numLanguageSets; i++)
|
||||||
|
{
|
||||||
|
var item = new STRLanguageSet();
|
||||||
|
var numStringPairs = io.ReadUInt16();
|
||||||
|
item.Strings = new STRItem[numStringPairs];
|
||||||
|
for (var x = 0; x < numStringPairs; x++)
|
||||||
|
{
|
||||||
|
item.Strings[x] = new STRItem
|
||||||
|
{
|
||||||
|
LanguageCode = (byte)(io.ReadByte() + 1),
|
||||||
|
Value = io.ReadVariableLengthPascalString(),
|
||||||
|
Comment = io.ReadVariableLengthPascalString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.LanguageSets[i] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
if (IffFile.TargetTS1)
|
||||||
|
{
|
||||||
|
// TS1 format - null terminated string
|
||||||
|
io.WriteInt16(-3);
|
||||||
|
var total = (short)LanguageSets.Sum(x => x?.Strings?.Length ?? 0);
|
||||||
|
io.WriteInt16(total);
|
||||||
|
foreach (var set in LanguageSets)
|
||||||
|
{
|
||||||
|
if (set?.Strings != null)
|
||||||
|
{
|
||||||
|
foreach (var str in set.Strings)
|
||||||
|
{
|
||||||
|
io.WriteByte((byte)(str.LanguageCode));
|
||||||
|
io.WriteNullTerminatedString(str.Value);
|
||||||
|
io.WriteNullTerminatedString(str.Comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i=0; i<total; i++)
|
||||||
|
{
|
||||||
|
io.WriteByte(0xA3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TSO format - variable length pascal
|
||||||
|
io.WriteInt16(-4);
|
||||||
|
io.WriteByte(20);
|
||||||
|
|
||||||
|
foreach (var set in LanguageSets)
|
||||||
|
{
|
||||||
|
if (set?.Strings == null)
|
||||||
|
{
|
||||||
|
io.WriteInt16(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
io.WriteUInt16((ushort)set.Strings.Length);
|
||||||
|
|
||||||
|
foreach (var str in set.Strings)
|
||||||
|
{
|
||||||
|
io.WriteByte((byte)(str.LanguageCode - 1));
|
||||||
|
io.WriteVariableLengthPascalString(str.Value);
|
||||||
|
io.WriteVariableLengthPascalString(str.Comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Item in a STR chunk.
|
||||||
|
/// </summary>
|
||||||
|
public class STRItem
|
||||||
|
{
|
||||||
|
public byte LanguageCode;
|
||||||
|
public string Value;
|
||||||
|
public string Comment;
|
||||||
|
|
||||||
|
public STRItem()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public STRItem(string value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Comment = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum STRLangCode : byte
|
||||||
|
{
|
||||||
|
Default = 0,
|
||||||
|
EnglishUS = 1,
|
||||||
|
EnglishUK = 2,
|
||||||
|
French = 3,
|
||||||
|
German = 4,
|
||||||
|
Italian = 5,
|
||||||
|
Spanish = 6,
|
||||||
|
Dutch = 7,
|
||||||
|
Danish = 8,
|
||||||
|
Swedish = 9,
|
||||||
|
Norwegian = 10,
|
||||||
|
Finish = 11,
|
||||||
|
Hebrew = 12,
|
||||||
|
Russian = 13,
|
||||||
|
Portuguese = 14,
|
||||||
|
Japanese = 15,
|
||||||
|
Polish = 16,
|
||||||
|
SimplifiedChinese = 17,
|
||||||
|
TraditionalChinese = 18,
|
||||||
|
Thai = 19,
|
||||||
|
Korean = 20,
|
||||||
|
|
||||||
|
//begin freeso
|
||||||
|
Slovak = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set of STRItems for a language.
|
||||||
|
/// </summary>
|
||||||
|
public class STRLanguageSet
|
||||||
|
{
|
||||||
|
public STRItem[] Strings = new STRItem[0];
|
||||||
|
}
|
||||||
|
}
|
26
server/tso.files/Formats/IFF/Chunks/THMB.cs
Executable file
26
server/tso.files/Formats/IFF/Chunks/THMB.cs
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class THMB : IffChunk
|
||||||
|
{
|
||||||
|
public int Width;
|
||||||
|
public int Height;
|
||||||
|
public int BaseYOff;
|
||||||
|
public int XOff;
|
||||||
|
public int AddYOff; //accounts for difference between roofed and unroofed. relative to the base.
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
Width = io.ReadInt32();
|
||||||
|
Height = io.ReadInt32();
|
||||||
|
BaseYOff = io.ReadInt32();
|
||||||
|
XOff = io.ReadInt32(); //0 in all cases i've found, pretty much?
|
||||||
|
AddYOff = io.ReadInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
server/tso.files/Formats/IFF/Chunks/TPRP.cs
Executable file
79
server/tso.files/Formats/IFF/Chunks/TPRP.cs
Executable file
|
@ -0,0 +1,79 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Labels for BHAV local variables and parameters.
|
||||||
|
/// </summary>
|
||||||
|
public class TPRP : IffChunk
|
||||||
|
{
|
||||||
|
public string[] ParamNames;
|
||||||
|
public string[] LocalNames;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a TPRP from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream instance holding a TPRP chunk.</param>
|
||||||
|
public override void Read(IffFile iff, System.IO.Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var zero = io.ReadInt32();
|
||||||
|
var version = io.ReadInt32();
|
||||||
|
var name = io.ReadCString(4); //"PRPT", or randomly 4 null characters for no good reason
|
||||||
|
|
||||||
|
var pCount = io.ReadInt32();
|
||||||
|
var lCount = io.ReadInt32();
|
||||||
|
ParamNames = new string[pCount];
|
||||||
|
LocalNames = new string[lCount];
|
||||||
|
for (int i = 0; i < pCount; i++)
|
||||||
|
{
|
||||||
|
ParamNames[i] = (version == 5) ? io.ReadPascalString() : io.ReadNullTerminatedString();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < lCount; i++)
|
||||||
|
{
|
||||||
|
LocalNames[i] = (version == 5) ? io.ReadPascalString() : io.ReadNullTerminatedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < pCount; i++)
|
||||||
|
{
|
||||||
|
//flags for parameters. probably disabled, unused, etc.
|
||||||
|
var flag = io.ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
//what are these?
|
||||||
|
if (version >= 3)
|
||||||
|
io.ReadInt32();
|
||||||
|
if (version >= 4)
|
||||||
|
io.ReadInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteInt32(0);
|
||||||
|
io.WriteInt32(5); //version
|
||||||
|
io.WriteCString("PRPT", 4);
|
||||||
|
io.WriteInt32(ParamNames.Length);
|
||||||
|
io.WriteInt32(LocalNames.Length);
|
||||||
|
foreach (var param in ParamNames)
|
||||||
|
io.WritePascalString(param);
|
||||||
|
foreach (var local in LocalNames)
|
||||||
|
io.WritePascalString(local);
|
||||||
|
|
||||||
|
for (int i=0; i<ParamNames.Length; i++)
|
||||||
|
{
|
||||||
|
io.WriteByte(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteInt32(0);
|
||||||
|
io.WriteInt32(0);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
96
server/tso.files/Formats/IFF/Chunks/TRCN.cs
Executable file
96
server/tso.files/Formats/IFF/Chunks/TRCN.cs
Executable file
|
@ -0,0 +1,96 @@
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides labels for BCON constants with the same resource ID.
|
||||||
|
/// </summary>
|
||||||
|
public class TRCN : IffChunk
|
||||||
|
{
|
||||||
|
public int Version;
|
||||||
|
public TRCNEntry[] Entries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a BCON chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream instance holding a BCON.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var zero = io.ReadInt32();
|
||||||
|
Version = io.ReadInt32();
|
||||||
|
var magic = io.ReadInt32();
|
||||||
|
var count = io.ReadInt32();
|
||||||
|
Entries = new TRCNEntry[count];
|
||||||
|
for (int i=0; i<count; i++)
|
||||||
|
{
|
||||||
|
var entry = new TRCNEntry();
|
||||||
|
entry.Read(io, Version, i > 0 && Version > 0);
|
||||||
|
Entries[i] = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteInt32(0);
|
||||||
|
io.WriteInt32(2); //we write out version 2
|
||||||
|
io.WriteInt32(0); //todo: NCRT ascii
|
||||||
|
io.WriteInt32(Entries.Length);
|
||||||
|
foreach (var entry in Entries)
|
||||||
|
{
|
||||||
|
entry.Write(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TRCNEntry
|
||||||
|
{
|
||||||
|
public int Flags;
|
||||||
|
public int Unknown;
|
||||||
|
public string Label = "";
|
||||||
|
public string Comment = "";
|
||||||
|
|
||||||
|
public byte RangeEnabled; //v1+ only
|
||||||
|
public short LowRange;
|
||||||
|
public short HighRange = 100;
|
||||||
|
|
||||||
|
public void Read(IoBuffer io, int version, bool odd)
|
||||||
|
{
|
||||||
|
Flags = io.ReadInt32();
|
||||||
|
Unknown = io.ReadInt32();
|
||||||
|
Label = (version > 1) ? io.ReadVariableLengthPascalString() : io.ReadNullTerminatedString();
|
||||||
|
if (version < 2 && ((Label.Length % 2 == 0) ^ odd)) io.ReadByte();
|
||||||
|
Comment = (version > 1) ? io.ReadVariableLengthPascalString() : io.ReadNullTerminatedString();
|
||||||
|
if (version < 2 && (Comment.Length % 2 == 0)) io.ReadByte();
|
||||||
|
|
||||||
|
if (version > 0)
|
||||||
|
{
|
||||||
|
RangeEnabled = io.ReadByte();
|
||||||
|
LowRange = io.ReadInt16();
|
||||||
|
HighRange = io.ReadInt16();
|
||||||
|
//io.ReadByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(IoWriter io)
|
||||||
|
{
|
||||||
|
io.WriteInt32(Flags);
|
||||||
|
io.WriteInt32(Unknown);
|
||||||
|
io.WriteVariableLengthPascalString(Label);
|
||||||
|
io.WriteVariableLengthPascalString(Comment);
|
||||||
|
|
||||||
|
io.WriteByte(RangeEnabled);
|
||||||
|
io.WriteInt16(LowRange);
|
||||||
|
io.WriteInt16(HighRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
345
server/tso.files/Formats/IFF/Chunks/TREE.cs
Executable file
345
server/tso.files/Formats/IFF/Chunks/TREE.cs
Executable file
|
@ -0,0 +1,345 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class TREE : IffChunk
|
||||||
|
{
|
||||||
|
public static TREE GenerateEmpty(BHAV bhav)
|
||||||
|
{
|
||||||
|
var result = new TREE();
|
||||||
|
result.ChunkLabel = "";
|
||||||
|
result.ChunkID = bhav.ChunkID;
|
||||||
|
result.AddedByPatch = true;
|
||||||
|
result.ChunkProcessed = true;
|
||||||
|
result.RuntimeInfo = ChunkRuntimeState.Modified;
|
||||||
|
result.ChunkType = "TREE";
|
||||||
|
|
||||||
|
result.CorrectConnections(bhav);
|
||||||
|
return result;
|
||||||
|
/*
|
||||||
|
var additionID = bhav.Instructions.Length;
|
||||||
|
|
||||||
|
Func<byte, short> resolveTrueFalse = (byte pointer) =>
|
||||||
|
{
|
||||||
|
switch (pointer)
|
||||||
|
{
|
||||||
|
case 253:
|
||||||
|
return -1;
|
||||||
|
case 255:
|
||||||
|
//generate false
|
||||||
|
case 254:
|
||||||
|
//generate true
|
||||||
|
}
|
||||||
|
if (pointer == 255) return -1;
|
||||||
|
else if (pointer == 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
//make an entry for each instruction. positions and sizes don't matter - we have a runtime flag to indicate they are not valid
|
||||||
|
for (int i=0; i<bhav.Instructions.Length; i++)
|
||||||
|
{
|
||||||
|
var inst = bhav.Instructions[i];
|
||||||
|
var box = new TREEBox(result);
|
||||||
|
box.InternalID = i;
|
||||||
|
box.PosisionInvalid = true;
|
||||||
|
box.Type = TREEBoxType.Primitive;
|
||||||
|
box.TruePointer =
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TREEBox> Entries = new List<TREEBox>();
|
||||||
|
public int PrimitiveCount => Entries.FindLastIndex(x => x.Type == TREEBoxType.Primitive) + 1;
|
||||||
|
|
||||||
|
//runtime
|
||||||
|
public uint TreeVersion = 0;
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var zero = io.ReadInt32();
|
||||||
|
var version = io.ReadInt32();
|
||||||
|
if (version > 1) throw new Exception("Unexpected TREE version: " + version);
|
||||||
|
string magic = io.ReadCString(4); //HBGN
|
||||||
|
if (magic != "EERT") throw new Exception("Magic number should be 'EERT', got " + magic);
|
||||||
|
var entryCount = io.ReadInt32();
|
||||||
|
Entries.Clear();
|
||||||
|
for (int i=0; i<entryCount; i++)
|
||||||
|
{
|
||||||
|
var box = new TREEBox(this);
|
||||||
|
box.Read(io, version);
|
||||||
|
box.InternalID = (short)i;
|
||||||
|
Entries.Add(box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteInt32(0);
|
||||||
|
io.WriteInt32(1);
|
||||||
|
io.WriteCString("EERT", 4);
|
||||||
|
io.WriteInt32(Entries.Count);
|
||||||
|
foreach (var entry in Entries)
|
||||||
|
{
|
||||||
|
entry.Write(io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyPointerDelta(int delta, int after)
|
||||||
|
{
|
||||||
|
foreach (var box in Entries)
|
||||||
|
{
|
||||||
|
if (box.InternalID >= after) box.InternalID += (short)delta;
|
||||||
|
if (box.TruePointer >= after) box.TruePointer += (short)delta;
|
||||||
|
if (box.FalsePointer >= after) box.FalsePointer += (short)delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CorrectConnections(BHAV bhav)
|
||||||
|
{
|
||||||
|
//make sure there are enough primitives for the bhav
|
||||||
|
var realPrimCount = bhav.Instructions.Length;
|
||||||
|
var treePrimCount = Entries.FindLastIndex(x => x.Type == TREEBoxType.Primitive) + 1;
|
||||||
|
|
||||||
|
ApplyPointerDelta(realPrimCount-treePrimCount, treePrimCount);
|
||||||
|
if (realPrimCount > treePrimCount)
|
||||||
|
{
|
||||||
|
//add new treeboxes
|
||||||
|
for (int i=treePrimCount; i<realPrimCount; i++)
|
||||||
|
{
|
||||||
|
var box = new TREEBox(this);
|
||||||
|
box.InternalID = (short)i;
|
||||||
|
box.PosisionInvalid = true;
|
||||||
|
box.Type = TREEBoxType.Primitive;
|
||||||
|
Entries.Insert(i, box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (treePrimCount > realPrimCount)
|
||||||
|
{
|
||||||
|
//remove treeboxes
|
||||||
|
for (int i=treePrimCount; i>realPrimCount; i--)
|
||||||
|
{
|
||||||
|
Entries.RemoveAt(i-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//make sure connections for each of the primitives match the BHAV
|
||||||
|
//if they don't, reconnect them or generate new boxes (true/false endpoints, maybe gotos in future)
|
||||||
|
|
||||||
|
for (int i=0; i<realPrimCount; i++)
|
||||||
|
{
|
||||||
|
var prim = bhav.Instructions[i];
|
||||||
|
var box = Entries[i];
|
||||||
|
|
||||||
|
if (prim.TruePointer != GetTrueID(box.TruePointer))
|
||||||
|
{
|
||||||
|
box.TruePointer = GetCorrectBox(prim.TruePointer);
|
||||||
|
}
|
||||||
|
if (prim.FalsePointer != GetTrueID((short)box.FalsePointer))
|
||||||
|
{
|
||||||
|
box.FalsePointer = GetCorrectBox(prim.FalsePointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteBox(TREEBox box)
|
||||||
|
{
|
||||||
|
//remove box. apply delta
|
||||||
|
var id = box.InternalID;
|
||||||
|
foreach (var box2 in Entries)
|
||||||
|
{
|
||||||
|
if (box2.TruePointer == id) box2.TruePointer = -1;
|
||||||
|
if (box2.FalsePointer == id) box2.FalsePointer = -1;
|
||||||
|
}
|
||||||
|
Entries.RemoveAt(id);
|
||||||
|
ApplyPointerDelta(-1, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertPrimitiveBox(TREEBox box)
|
||||||
|
{
|
||||||
|
var primEnd = PrimitiveCount;
|
||||||
|
ApplyPointerDelta(1, primEnd);
|
||||||
|
|
||||||
|
box.InternalID = (short)primEnd;
|
||||||
|
Entries.Insert(primEnd, box);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertRemovedBox(TREEBox box)
|
||||||
|
{
|
||||||
|
var oldIndex = box.InternalID;
|
||||||
|
ApplyPointerDelta(1, oldIndex);
|
||||||
|
Entries.Insert(oldIndex, box);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TREEBox MakeNewPrimitiveBox(TREEBoxType type)
|
||||||
|
{
|
||||||
|
var primEnd = PrimitiveCount;
|
||||||
|
ApplyPointerDelta(1, primEnd);
|
||||||
|
//find end of primitives and add box there. apply delta
|
||||||
|
var box = new TREEBox(this);
|
||||||
|
box.InternalID = (short)primEnd;
|
||||||
|
box.PosisionInvalid = true;
|
||||||
|
box.Type = type;
|
||||||
|
Entries.Insert(primEnd, box);
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TREEBox MakeNewSpecialBox(TREEBoxType type)
|
||||||
|
{
|
||||||
|
//add box at end. no delta needs to be applied.
|
||||||
|
var box = new TREEBox(this);
|
||||||
|
box.InternalID = (short)Entries.Count;
|
||||||
|
box.PosisionInvalid = true;
|
||||||
|
box.Type = type;
|
||||||
|
Entries.Add(box);
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
private short GetCorrectBox(byte realID)
|
||||||
|
{
|
||||||
|
switch (realID)
|
||||||
|
{
|
||||||
|
case 255:
|
||||||
|
//create false box
|
||||||
|
var f = MakeNewSpecialBox(TREEBoxType.False);
|
||||||
|
return f.InternalID;
|
||||||
|
case 254:
|
||||||
|
//create true box
|
||||||
|
var t = MakeNewSpecialBox(TREEBoxType.True);
|
||||||
|
return t.InternalID;
|
||||||
|
case 253:
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
return realID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte GetTrueID(short boxID)
|
||||||
|
{
|
||||||
|
return GetBox(boxID)?.TrueID ?? 253;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TREEBox GetBox(short pointer)
|
||||||
|
{
|
||||||
|
if (pointer < 0 || pointer >= Entries.Count) return null;
|
||||||
|
return Entries[pointer];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TREEBox
|
||||||
|
{
|
||||||
|
//runtime
|
||||||
|
public short InternalID = -1;
|
||||||
|
public bool PosisionInvalid; //forces a regeneration of position using the default tree algorithm
|
||||||
|
public TREE Parent;
|
||||||
|
public byte TrueID
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case TREEBoxType.Primitive:
|
||||||
|
return (byte)InternalID;
|
||||||
|
case TREEBoxType.Goto:
|
||||||
|
return LabelTrueID(new HashSet<short>());
|
||||||
|
case TREEBoxType.Label:
|
||||||
|
return 253; //arrows cannot point to a label
|
||||||
|
case TREEBoxType.True:
|
||||||
|
return 254;
|
||||||
|
case TREEBoxType.False:
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
return 253;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte LabelTrueID(HashSet<short> visited)
|
||||||
|
{
|
||||||
|
if (Type != TREEBoxType.Goto) return TrueID;
|
||||||
|
if (visited.Contains(InternalID)) return 253; //error
|
||||||
|
visited.Add(InternalID);
|
||||||
|
return Parent?.GetBox(Parent.GetBox(TruePointer)?.TruePointer ?? -1)?.LabelTrueID(visited) ?? 253;
|
||||||
|
}
|
||||||
|
|
||||||
|
//data
|
||||||
|
public TREEBoxType Type;
|
||||||
|
public ushort Unknown;
|
||||||
|
public short Width;
|
||||||
|
public short Height;
|
||||||
|
public short X;
|
||||||
|
public short Y;
|
||||||
|
public short CommentSize = 0x10;
|
||||||
|
public short TruePointer = -1;
|
||||||
|
public short Special; //0 or -1... unknown.
|
||||||
|
public int FalsePointer = -1;
|
||||||
|
public string Comment = "";
|
||||||
|
public int TrailingZero = 0;
|
||||||
|
|
||||||
|
public TREEBox(TREE parent)
|
||||||
|
{
|
||||||
|
Parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Read(IoBuffer io, int version)
|
||||||
|
{
|
||||||
|
Type = (TREEBoxType)io.ReadUInt16();
|
||||||
|
Unknown = io.ReadUInt16();
|
||||||
|
Width = io.ReadInt16();
|
||||||
|
Height = io.ReadInt16();
|
||||||
|
X = io.ReadInt16();
|
||||||
|
Y = io.ReadInt16();
|
||||||
|
CommentSize = io.ReadInt16();
|
||||||
|
TruePointer = io.ReadInt16();
|
||||||
|
Special = io.ReadInt16();
|
||||||
|
FalsePointer = io.ReadInt32();
|
||||||
|
Comment = io.ReadNullTerminatedString();
|
||||||
|
if (Comment.Length % 2 == 0) io.ReadByte(); //padding to 2 byte align
|
||||||
|
if (version > 0) TrailingZero = io.ReadInt32();
|
||||||
|
|
||||||
|
if (!Enum.IsDefined(typeof(TREEBoxType), Type)) throw new Exception("Unexpected TREE box type: " + Type.ToString());
|
||||||
|
if (Special < -1 || Special > 0) throw new Exception("Unexpected TREE special: " + Special);
|
||||||
|
if (Unknown != 0) throw new Exception("Unexpected Unknown: " + Unknown);
|
||||||
|
if (TrailingZero != 0) Console.WriteLine("Unexpected TrailingZero: " + TrailingZero);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(IoWriter io)
|
||||||
|
{
|
||||||
|
io.WriteUInt16((ushort)Type);
|
||||||
|
io.WriteUInt16(Unknown);
|
||||||
|
io.WriteInt16(Width);
|
||||||
|
io.WriteInt16(Height);
|
||||||
|
io.WriteInt16(X);
|
||||||
|
io.WriteInt16(Y);
|
||||||
|
io.WriteInt16(CommentSize);
|
||||||
|
io.WriteInt16(TruePointer);
|
||||||
|
io.WriteInt16(Special);
|
||||||
|
io.WriteInt32(FalsePointer);
|
||||||
|
io.WriteCString(Comment);
|
||||||
|
if (Comment.Length % 2 == 0) io.WriteByte(0xCD); //padding to 2 byte align
|
||||||
|
io.WriteInt32(TrailingZero);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Type.ToString() + " (" + TruePointer + ((FalsePointer == -1) ? "" : ("/"+FalsePointer)) + "): " + Comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TREEBoxType : ushort
|
||||||
|
{
|
||||||
|
Primitive = 0,
|
||||||
|
True = 1,
|
||||||
|
False = 2,
|
||||||
|
Comment = 3,
|
||||||
|
Label = 4,
|
||||||
|
Goto = 5 //no comment size, roughly primitive sized (180, 48), pointer goes to Label
|
||||||
|
}
|
||||||
|
}
|
433
server/tso.files/Formats/IFF/Chunks/TTAB.cs
Executable file
433
server/tso.files/Formats/IFF/Chunks/TTAB.cs
Executable file
|
@ -0,0 +1,433 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This chunk type defines a list of interactions for an object and assigns a BHAV subroutine
|
||||||
|
/// for each interaction. The pie menu labels shown to the user are stored in a TTAs chunk with
|
||||||
|
/// the same ID.
|
||||||
|
/// </summary>
|
||||||
|
public class TTAB : IffChunk
|
||||||
|
{
|
||||||
|
public TTABInteraction[] Interactions = new TTABInteraction[0];
|
||||||
|
public Dictionary<uint, TTABInteraction> InteractionByIndex = new Dictionary<uint, TTABInteraction>();
|
||||||
|
public TTABInteraction[] AutoInteractions = new TTABInteraction[0];
|
||||||
|
|
||||||
|
public static float[] AttenuationValues = {
|
||||||
|
0, //custom
|
||||||
|
0, //none
|
||||||
|
0.1f, //low
|
||||||
|
0.3f, //medium
|
||||||
|
0.6f, //high
|
||||||
|
};
|
||||||
|
|
||||||
|
public static float[] VisitorAttenuationValues = {
|
||||||
|
0, //custom
|
||||||
|
0, //none
|
||||||
|
0.01f, //low
|
||||||
|
0.02f, //medium
|
||||||
|
0.03f, //high
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a TTAB chunk from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iff">An Iff instance.</param>
|
||||||
|
/// <param name="stream">A Stream object holding a TTAB chunk.</param>
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
InteractionByIndex.Clear();
|
||||||
|
Interactions = new TTABInteraction[io.ReadUInt16()];
|
||||||
|
if (Interactions.Length == 0) return; //no interactions, don't bother reading remainder.
|
||||||
|
var version = io.ReadUInt16();
|
||||||
|
IOProxy iop;
|
||||||
|
if (version <= 3)
|
||||||
|
{
|
||||||
|
// DO NOT LOAD THIS TTAB TYPE
|
||||||
|
Interactions = new TTABInteraction[0];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (version < 9 || (version > 10 && !iff.TSBO)) iop = new TTABNormal(io);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var compressionCode = io.ReadByte();
|
||||||
|
if (compressionCode != 1) iop = new TTABNormal(io);
|
||||||
|
else iop = new IffFieldEncode(io);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < Interactions.Length; i++)
|
||||||
|
{
|
||||||
|
var result = new TTABInteraction();
|
||||||
|
result.ActionFunction = iop.ReadUInt16();
|
||||||
|
result.TestFunction = iop.ReadUInt16();
|
||||||
|
result.MotiveEntries = new TTABMotiveEntry[iop.ReadUInt32()];
|
||||||
|
result.Flags = (TTABFlags)iop.ReadUInt32();
|
||||||
|
result.TTAIndex = iop.ReadUInt32();
|
||||||
|
if (version > 6) result.AttenuationCode = iop.ReadUInt32();
|
||||||
|
result.AttenuationValue = iop.ReadFloat();
|
||||||
|
result.AutonomyThreshold = iop.ReadUInt32();
|
||||||
|
result.JoiningIndex = iop.ReadInt32();
|
||||||
|
for (int j = 0; j < result.MotiveEntries.Length; j++)
|
||||||
|
{
|
||||||
|
var motive = new TTABMotiveEntry();
|
||||||
|
motive.MotiveIndex = j;
|
||||||
|
if (version > 6) motive.EffectRangeMinimum = iop.ReadInt16();
|
||||||
|
motive.EffectRangeDelta = iop.ReadInt16();
|
||||||
|
if (version > 6) motive.PersonalityModifier = iop.ReadUInt16();
|
||||||
|
result.MotiveEntries[j] = motive;
|
||||||
|
}
|
||||||
|
if (version > 9 && !iff.TSBO)
|
||||||
|
{
|
||||||
|
result.Flags2 = (TSOFlags)iop.ReadUInt32();
|
||||||
|
}
|
||||||
|
Interactions[i] = result;
|
||||||
|
InteractionByIndex.Add(result.TTAIndex, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InitAutoInteractions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteUInt16((ushort)Interactions.Length);
|
||||||
|
io.WriteUInt16((ushort)((IffFile.TargetTS1) ? 8 : 10)); //version. we save version 10 which uses the IO proxy
|
||||||
|
//...but we can't write out to that yet so write with compression code 0
|
||||||
|
if (!IffFile.TargetTS1) io.WriteByte(0);
|
||||||
|
for (int i = 0; i < Interactions.Length; i++)
|
||||||
|
{
|
||||||
|
var action = Interactions[i];
|
||||||
|
io.WriteUInt16(action.ActionFunction);
|
||||||
|
io.WriteUInt16(action.TestFunction);
|
||||||
|
io.WriteUInt32((uint)action.MotiveEntries.Length);
|
||||||
|
io.WriteUInt32((uint)action.Flags);
|
||||||
|
io.WriteUInt32(action.TTAIndex);
|
||||||
|
io.WriteUInt32(action.AttenuationCode);
|
||||||
|
io.WriteFloat(action.AttenuationValue);
|
||||||
|
io.WriteUInt32(action.AutonomyThreshold);
|
||||||
|
io.WriteInt32(action.JoiningIndex);
|
||||||
|
for (int j=0; j < action.MotiveEntries.Length; j++)
|
||||||
|
{
|
||||||
|
var mot = action.MotiveEntries[j];
|
||||||
|
io.WriteInt16(mot.EffectRangeMinimum);
|
||||||
|
io.WriteInt16(mot.EffectRangeDelta);
|
||||||
|
io.WriteUInt16(mot.PersonalityModifier);
|
||||||
|
}
|
||||||
|
if (!IffFile.TargetTS1) io.WriteUInt32((uint)action.Flags2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertInteraction(TTABInteraction action, int index)
|
||||||
|
{
|
||||||
|
var newInt = new TTABInteraction[Interactions.Length + 1];
|
||||||
|
if (index == -1) index = 0;
|
||||||
|
Array.Copy(Interactions, newInt, index); //copy before strings
|
||||||
|
newInt[index] = action;
|
||||||
|
Array.Copy(Interactions, index, newInt, index + 1, (Interactions.Length - index));
|
||||||
|
Interactions = newInt;
|
||||||
|
|
||||||
|
if (!InteractionByIndex.ContainsKey(action.TTAIndex)) InteractionByIndex.Add(action.TTAIndex, action);
|
||||||
|
InitAutoInteractions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteInteraction(int index)
|
||||||
|
{
|
||||||
|
var action = Interactions[index];
|
||||||
|
var newInt = new TTABInteraction[Interactions.Length - 1];
|
||||||
|
if (index == -1) index = 0;
|
||||||
|
Array.Copy(Interactions, newInt, index); //copy before strings
|
||||||
|
Array.Copy(Interactions, index + 1, newInt, index, (Interactions.Length - (index + 1)));
|
||||||
|
Interactions = newInt;
|
||||||
|
|
||||||
|
if (InteractionByIndex.ContainsKey(action.TTAIndex)) InteractionByIndex.Remove(action.TTAIndex);
|
||||||
|
InitAutoInteractions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitAutoInteractions()
|
||||||
|
{
|
||||||
|
foreach (var interaction in Interactions)
|
||||||
|
{
|
||||||
|
interaction.ActiveMotiveEntries = interaction.MotiveEntries.Where(x => x.EffectRangeDelta != 0).ToArray();
|
||||||
|
}
|
||||||
|
AutoInteractions = Interactions.Where(interaction => interaction.ActiveMotiveEntries.Length > 0).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class IOProxy
|
||||||
|
{
|
||||||
|
public abstract ushort ReadUInt16();
|
||||||
|
public abstract short ReadInt16();
|
||||||
|
public abstract int ReadInt32();
|
||||||
|
public abstract uint ReadUInt32();
|
||||||
|
public abstract float ReadFloat();
|
||||||
|
|
||||||
|
public IoBuffer io;
|
||||||
|
public IOProxy(IoBuffer io)
|
||||||
|
{
|
||||||
|
this.io = io;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TTABNormal : IOProxy
|
||||||
|
{
|
||||||
|
public override ushort ReadUInt16() { return io.ReadUInt16(); }
|
||||||
|
public override short ReadInt16() { return io.ReadInt16(); }
|
||||||
|
public override int ReadInt32() { return io.ReadInt32(); }
|
||||||
|
public override uint ReadUInt32() { return io.ReadUInt32(); }
|
||||||
|
public override float ReadFloat() { return io.ReadFloat(); }
|
||||||
|
|
||||||
|
public TTABNormal(IoBuffer io) : base(io) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an interaction in a TTAB chunk.
|
||||||
|
/// </summary>
|
||||||
|
public class TTABInteraction
|
||||||
|
{
|
||||||
|
public ushort ActionFunction;
|
||||||
|
public ushort TestFunction;
|
||||||
|
public TTABMotiveEntry[] MotiveEntries;
|
||||||
|
public TTABFlags Flags;
|
||||||
|
public uint TTAIndex;
|
||||||
|
public uint AttenuationCode;
|
||||||
|
public float AttenuationValue;
|
||||||
|
public uint AutonomyThreshold;
|
||||||
|
public int JoiningIndex;
|
||||||
|
public TSOFlags Flags2 = (TSOFlags)0x1e; //allow a lot of things
|
||||||
|
|
||||||
|
public TTABMotiveEntry[] ActiveMotiveEntries; //populated when asking for auto interactions.
|
||||||
|
|
||||||
|
public InteractionMaskFlags MaskFlags {
|
||||||
|
get {
|
||||||
|
return (InteractionMaskFlags)(((int)Flags >> 16) & 0xF);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Flags = (TTABFlags)(((int)Flags & 0xFFFF) | ((int)value << 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//ALLOW
|
||||||
|
public bool AllowVisitors
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.AllowVisitors) > 0 || (Flags2 & TSOFlags.AllowVisitors) > 0; }
|
||||||
|
set {
|
||||||
|
Flags &= ~(TTABFlags.AllowVisitors); if (value) Flags |= TTABFlags.AllowVisitors;
|
||||||
|
Flags2 &= ~(TSOFlags.AllowVisitors); if (value) Flags2 |= TSOFlags.AllowVisitors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool AllowFriends
|
||||||
|
{
|
||||||
|
get { return (Flags2 & TSOFlags.AllowFriends) > 0; }
|
||||||
|
set { Flags2 &= ~(TSOFlags.AllowFriends); if (value) Flags2 |= TSOFlags.AllowFriends; }
|
||||||
|
}
|
||||||
|
public bool AllowRoommates
|
||||||
|
{
|
||||||
|
get { return (Flags2 & TSOFlags.AllowRoommates) > 0; }
|
||||||
|
set { Flags2 &= ~(TSOFlags.AllowRoommates); if (value) Flags2 |= TSOFlags.AllowRoommates; }
|
||||||
|
}
|
||||||
|
public bool AllowObjectOwner
|
||||||
|
{
|
||||||
|
get { return (Flags2 & TSOFlags.AllowObjectOwner) > 0; }
|
||||||
|
set { Flags2 &= ~(TSOFlags.AllowObjectOwner); if (value) Flags2 |= TSOFlags.AllowObjectOwner; }
|
||||||
|
}
|
||||||
|
public bool UnderParentalControl
|
||||||
|
{
|
||||||
|
get { return (Flags2 & TSOFlags.UnderParentalControl) > 0; }
|
||||||
|
set { Flags2 &= ~(TSOFlags.UnderParentalControl); if (value) Flags2 |= TSOFlags.UnderParentalControl; }
|
||||||
|
}
|
||||||
|
public bool AllowCSRs
|
||||||
|
{
|
||||||
|
get { return (Flags2 & TSOFlags.AllowCSRs) > 0; }
|
||||||
|
set { Flags2 &= ~(TSOFlags.AllowCSRs); if (value) Flags2 |= TSOFlags.AllowCSRs; }
|
||||||
|
}
|
||||||
|
public bool AllowGhosts
|
||||||
|
{
|
||||||
|
get { return (Flags2 & TSOFlags.AllowGhost) > 0; }
|
||||||
|
set { Flags2 &= ~(TSOFlags.AllowGhost); if (value) Flags2 |= TSOFlags.AllowGhost; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowCats
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.AllowCats) > 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.AllowCats); if (value) Flags |= TTABFlags.AllowCats; }
|
||||||
|
}
|
||||||
|
public bool AllowDogs
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.AllowDogs) > 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.AllowDogs); if (value) Flags |= TTABFlags.AllowDogs; }
|
||||||
|
}
|
||||||
|
|
||||||
|
//TS1
|
||||||
|
|
||||||
|
public bool TS1AllowCats
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.TS1AllowCats) > 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.TS1AllowCats); if (value) Flags |= TTABFlags.TS1AllowCats; }
|
||||||
|
}
|
||||||
|
public bool TS1AllowDogs
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.TS1AllowDogs) > 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.TS1AllowDogs); if (value) Flags |= TTABFlags.TS1AllowDogs; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TS1AllowAdults
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.TS1NoAdult) == 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.TS1NoAdult); if (!value) Flags |= TTABFlags.TS1NoAdult; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TS1AllowChild
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.TS1NoChild) == 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.TS1NoChild); if (!value) Flags |= TTABFlags.TS1NoChild; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TS1AllowDemoChild
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.TS1NoDemoChild) == 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.TS1NoDemoChild); if (!value) Flags |= TTABFlags.TS1NoDemoChild; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Joinable
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.Joinable) > 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.Joinable); if (value) Flags |= TTABFlags.Joinable; }
|
||||||
|
}
|
||||||
|
|
||||||
|
//FLAGS
|
||||||
|
public bool Debug
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.Debug) > 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.Debug); if (value) Flags |= TTABFlags.Debug; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Leapfrog {
|
||||||
|
get { return (Flags & TTABFlags.Leapfrog) > 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.Leapfrog); if (value) Flags |= TTABFlags.Leapfrog; }
|
||||||
|
}
|
||||||
|
public bool MustRun
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.MustRun) > 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.MustRun); if (value) Flags |= TTABFlags.MustRun; }
|
||||||
|
}
|
||||||
|
public bool AutoFirst
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.AutoFirstSelect) > 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.AutoFirstSelect); if (value) Flags |= TTABFlags.AutoFirstSelect; }
|
||||||
|
}
|
||||||
|
public bool RunImmediately
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.RunImmediately) > 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.RunImmediately); if (value) Flags |= TTABFlags.RunImmediately; }
|
||||||
|
}
|
||||||
|
public bool AllowConsecutive
|
||||||
|
{
|
||||||
|
get { return (Flags & TTABFlags.AllowConsecutive) > 0; }
|
||||||
|
set { Flags &= ~(TTABFlags.AllowConsecutive); if (value) Flags |= TTABFlags.AllowConsecutive; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool Carrying
|
||||||
|
{
|
||||||
|
get { return (MaskFlags & InteractionMaskFlags.AvailableWhenCarrying) > 0; }
|
||||||
|
set { MaskFlags &= ~(InteractionMaskFlags.AvailableWhenCarrying); if (value) MaskFlags |= InteractionMaskFlags.AvailableWhenCarrying; }
|
||||||
|
}
|
||||||
|
public bool Repair
|
||||||
|
{
|
||||||
|
get { return (MaskFlags & InteractionMaskFlags.IsRepair) > 0; }
|
||||||
|
set { MaskFlags &= ~(InteractionMaskFlags.IsRepair); if (value) MaskFlags |= InteractionMaskFlags.IsRepair; }
|
||||||
|
}
|
||||||
|
public bool AlwaysCheck
|
||||||
|
{
|
||||||
|
get { return (MaskFlags & InteractionMaskFlags.RunCheckAlways) > 0; }
|
||||||
|
set { MaskFlags &= ~(InteractionMaskFlags.RunCheckAlways); if (value) MaskFlags |= InteractionMaskFlags.RunCheckAlways; }
|
||||||
|
}
|
||||||
|
public bool WhenDead
|
||||||
|
{
|
||||||
|
get { return (MaskFlags & InteractionMaskFlags.AvailableWhenDead) > 0; }
|
||||||
|
set { MaskFlags &= ~(InteractionMaskFlags.AvailableWhenDead); if (value) MaskFlags |= InteractionMaskFlags.AvailableWhenDead; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitMotiveEntries()
|
||||||
|
{
|
||||||
|
for (int i=0; i<MotiveEntries.Length; i++)
|
||||||
|
{
|
||||||
|
MotiveEntries[i].MotiveIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a motive entry in a TTAB chunk.
|
||||||
|
/// </summary>
|
||||||
|
public struct TTABMotiveEntry
|
||||||
|
{
|
||||||
|
public int MotiveIndex; // don't save this
|
||||||
|
|
||||||
|
public short EffectRangeMinimum;
|
||||||
|
public short EffectRangeDelta;
|
||||||
|
public ushort PersonalityModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TTABFlags
|
||||||
|
{
|
||||||
|
AllowVisitors = 1, //COVERED, TODO for no TSOFlags? (default to only roomies, unless this flag set)
|
||||||
|
Joinable = 1 << 1, //TODO
|
||||||
|
RunImmediately = 1 << 2, //COVERED
|
||||||
|
AllowConsecutive = 1 << 3, //TODO
|
||||||
|
|
||||||
|
TS1NoChild = 1 << 4,
|
||||||
|
TS1NoDemoChild = 1 << 5,
|
||||||
|
TS1NoAdult = 1 << 6,
|
||||||
|
|
||||||
|
Debug = 1 << 7, //COVERED: only available to roomies for now
|
||||||
|
AutoFirstSelect = 1 << 8, //COVERED
|
||||||
|
|
||||||
|
TS1AllowCats = 1 << 9,
|
||||||
|
TS1AllowDogs = 1 << 10,
|
||||||
|
|
||||||
|
Leapfrog = 1 << 9, //COVERED
|
||||||
|
MustRun = 1 << 10, //COVERED
|
||||||
|
AllowDogs = 1 << 11, //COVERED
|
||||||
|
AllowCats = 1 << 12, //COVERED
|
||||||
|
|
||||||
|
TSOAvailableCarrying = 1 << 16, //COVERED
|
||||||
|
TSOIsRepair = 1 << 17, //TODO (only available when wear = 0)
|
||||||
|
TSORunCheckAlways = 1 << 18, //TODO
|
||||||
|
TSOAvailableWhenDead = 1<<19, //COVERED
|
||||||
|
|
||||||
|
FSOPushTail = 1<<30,
|
||||||
|
FSOPushHead = 1<<29,
|
||||||
|
FSOSkipPermissions = 1<<28,
|
||||||
|
FSODirectControl = 1<<27
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TSOFlags
|
||||||
|
{
|
||||||
|
NonEmpty = 1, //if this is the only flag set, flags aren't empty intentionally. force Owner, Roommates, Friends to on
|
||||||
|
AllowObjectOwner = 1 << 1, //COVERED
|
||||||
|
AllowRoommates = 1 << 2, //COVERED
|
||||||
|
AllowFriends = 1 << 3, //TODO
|
||||||
|
AllowVisitors = 1 << 4, //COVERED
|
||||||
|
AllowGhost = 1 << 5, //COVERED
|
||||||
|
UnderParentalControl = 1 << 6, //TODO: interactions always available
|
||||||
|
AllowCSRs = 1 << 7 //COVERED: only available to admins
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum InteractionMaskFlags
|
||||||
|
{
|
||||||
|
AvailableWhenCarrying = 1,
|
||||||
|
IsRepair = 1<<1,
|
||||||
|
RunCheckAlways = 1 << 2,
|
||||||
|
AvailableWhenDead = 1 << 3,
|
||||||
|
}
|
||||||
|
}
|
65
server/tso.files/Formats/IFF/Chunks/TTAT.cs
Executable file
65
server/tso.files/Formats/IFF/Chunks/TTAT.cs
Executable file
|
@ -0,0 +1,65 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
public class TATT : IffChunk
|
||||||
|
{
|
||||||
|
public Dictionary<uint, short[]> TypeAttributesByGUID = new Dictionary<uint, short[]>();
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.ReadUInt32(); //pad
|
||||||
|
var version = io.ReadUInt32(); //zero
|
||||||
|
|
||||||
|
var TTAT = io.ReadCString(4);
|
||||||
|
|
||||||
|
IOProxy iop;
|
||||||
|
var compressionCode = io.ReadByte();
|
||||||
|
//HACK: for freeso we don't run the field encoding coompression
|
||||||
|
//since fso neighbourhoods are not compatible with ts1, it does not matter too much
|
||||||
|
if (compressionCode != 1) iop = new TTABNormal(io);
|
||||||
|
else iop = new IffFieldEncode(io);
|
||||||
|
|
||||||
|
var total = iop.ReadInt32();
|
||||||
|
for (int i=0; i<total; i++)
|
||||||
|
{
|
||||||
|
var guid = (uint)iop.ReadInt32();
|
||||||
|
var count = iop.ReadInt32();
|
||||||
|
var tatts = new short[count];
|
||||||
|
for (int j=0; j<count; j++)
|
||||||
|
{
|
||||||
|
tatts[j] = iop.ReadInt16();
|
||||||
|
}
|
||||||
|
TypeAttributesByGUID[guid] = tatts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Write(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteInt32(0);
|
||||||
|
io.WriteInt32(0); //version
|
||||||
|
|
||||||
|
io.WriteCString("TTAT", 4);
|
||||||
|
|
||||||
|
io.WriteByte(0); //compression code
|
||||||
|
io.WriteInt32(TypeAttributesByGUID.Count);
|
||||||
|
foreach (var tatt in TypeAttributesByGUID)
|
||||||
|
{
|
||||||
|
io.WriteUInt32(tatt.Key);
|
||||||
|
io.WriteInt32(tatt.Value.Length);
|
||||||
|
foreach (var value in tatt.Value)
|
||||||
|
io.WriteInt16(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
server/tso.files/Formats/IFF/Chunks/TTAs.cs
Executable file
10
server/tso.files/Formats/IFF/Chunks/TTAs.cs
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Duplicate of STR chunk, instead used for pie menu strings.
|
||||||
|
/// </summary>
|
||||||
|
public class TTAs : STR
|
||||||
|
{
|
||||||
|
//no difference!
|
||||||
|
}
|
||||||
|
}
|
61
server/tso.files/Formats/IFF/Chunks/WALm.cs
Executable file
61
server/tso.files/Formats/IFF/Chunks/WALm.cs
Executable file
|
@ -0,0 +1,61 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF.Chunks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// WALm and FLRm chunks, used for mapping walls and floors in ARRY chunks to walls and floors in resource files (outwith floors.iff)
|
||||||
|
/// </summary>
|
||||||
|
public class WALm : IffChunk
|
||||||
|
{
|
||||||
|
public List<WALmEntry> Entries;
|
||||||
|
|
||||||
|
public override void Read(IffFile iff, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var zero = io.ReadInt32();
|
||||||
|
var version = io.ReadInt32(); //should be 0
|
||||||
|
var walm = io.ReadInt32(); //mLAW/mRLF
|
||||||
|
|
||||||
|
var count = io.ReadInt32();
|
||||||
|
Entries = new List<WALmEntry>();
|
||||||
|
|
||||||
|
for (int i=0; i<count; i++)
|
||||||
|
{
|
||||||
|
//size of fields depends on chunk id.
|
||||||
|
Entries.Add(new WALmEntry(io, ChunkID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FLRm : WALm
|
||||||
|
{
|
||||||
|
//literally no difference
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WALmEntry
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public int Unknown; //usually 1
|
||||||
|
public byte ID;
|
||||||
|
public byte[] Unknown2;
|
||||||
|
public WALmEntry(IoBuffer io, int id)
|
||||||
|
{
|
||||||
|
Name = io.ReadNullTerminatedString();
|
||||||
|
if (Name.Length % 2 == 0) io.ReadByte(); //pad to short width
|
||||||
|
Unknown = io.ReadInt32(); //index in iff?
|
||||||
|
ID = io.ReadByte();
|
||||||
|
Unknown2 = io.ReadBytes(5 + id * 2);
|
||||||
|
|
||||||
|
//id 0 seems to be an older format
|
||||||
|
//unknown2 is 01 00 00 00 00 00
|
||||||
|
//id 1 adds more fields
|
||||||
|
//unknown2 is 01 01 00 00 00 00 00 00
|
||||||
|
|
||||||
|
//related to number of walls or floors in the file?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
587
server/tso.files/Formats/IFF/IffFile.cs
Executable file
587
server/tso.files/Formats/IFF/IffFile.cs
Executable file
|
@ -0,0 +1,587 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
using FSO.Files.Formats.IFF.Chunks;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using FSO.Common.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interchange File Format (IFF) is a chunk-based file format for binary resource data
|
||||||
|
/// intended to promote a common model for store and use by an executable.
|
||||||
|
/// </summary>
|
||||||
|
public class IffFile : IFileInfoUtilizer, ITimedCachable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true to force the game to retain a copy of all chunk data at time of loading (used to generate piffs)
|
||||||
|
/// Should really only be set when the user wants to use the IDE, as it uses a lot more memory.
|
||||||
|
/// </summary>
|
||||||
|
public static bool RETAIN_CHUNK_DATA = false;
|
||||||
|
public static bool TargetTS1 = false;
|
||||||
|
public bool TSBO = false;
|
||||||
|
public bool RetainChunkData = RETAIN_CHUNK_DATA;
|
||||||
|
public object CachedJITModule; //for JIT and AOT modes
|
||||||
|
public uint ExecutableHash; //hash of BHAV and BCON chunks
|
||||||
|
|
||||||
|
public string Filename;
|
||||||
|
|
||||||
|
public static Dictionary<string, Type> CHUNK_TYPES = new Dictionary<string, Type>()
|
||||||
|
{
|
||||||
|
{"STR#", typeof(STR)},
|
||||||
|
{"CTSS", typeof(CTSS)},
|
||||||
|
{"PALT", typeof(PALT)},
|
||||||
|
{"OBJD", typeof(OBJD)},
|
||||||
|
{"DGRP", typeof(DGRP)},
|
||||||
|
{"SPR#", typeof(SPR)},
|
||||||
|
{"SPR2", typeof(SPR2)},
|
||||||
|
{"BHAV", typeof(BHAV)},
|
||||||
|
{"TPRP", typeof(TPRP)},
|
||||||
|
{"SLOT", typeof(SLOT)},
|
||||||
|
{"GLOB", typeof(GLOB)},
|
||||||
|
{"BCON", typeof(BCON)},
|
||||||
|
{"TTAB", typeof(TTAB)},
|
||||||
|
{"OBJf", typeof(OBJf)},
|
||||||
|
{"TTAs", typeof(TTAs)},
|
||||||
|
{"FWAV", typeof(FWAV)},
|
||||||
|
{"BMP_", typeof(BMP)},
|
||||||
|
{"PIFF", typeof(PIFF) },
|
||||||
|
{"TRCN", typeof(TRCN) },
|
||||||
|
|
||||||
|
{"objt", typeof(OBJT) },
|
||||||
|
{"Arry", typeof(ARRY) },
|
||||||
|
{"ObjM", typeof(OBJM) },
|
||||||
|
{"WALm", typeof(WALm) },
|
||||||
|
{"FLRm", typeof(FLRm) },
|
||||||
|
{"CARR", typeof(CARR) },
|
||||||
|
|
||||||
|
{"NBRS", typeof(NBRS) },
|
||||||
|
{"FAMI", typeof(FAMI) },
|
||||||
|
{"NGBH", typeof(NGBH) },
|
||||||
|
{"FAMs", typeof(FAMs) },
|
||||||
|
{"THMB", typeof(THMB) },
|
||||||
|
{"SIMI", typeof(SIMI) },
|
||||||
|
{"TATT", typeof(TATT) },
|
||||||
|
{"HOUS", typeof(HOUS) },
|
||||||
|
//todo: FAMh (family motives ("family house"?)) field encoded.
|
||||||
|
|
||||||
|
{"TREE", typeof(TREE) },
|
||||||
|
{"FCNS", typeof(FCNS) },
|
||||||
|
|
||||||
|
{"FSOR", typeof(FSOR) },
|
||||||
|
{"FSOM", typeof(FSOM) },
|
||||||
|
{"MTEX", typeof(MTEX) },
|
||||||
|
{"FSOV", typeof(FSOV) },
|
||||||
|
{"PNG_", typeof(PNG) }
|
||||||
|
};
|
||||||
|
|
||||||
|
public IffRuntimeInfo RuntimeInfo = new IffRuntimeInfo();
|
||||||
|
private Dictionary<Type, Dictionary<ushort, object>> ByChunkId;
|
||||||
|
private Dictionary<Type, List<object>> ByChunkType;
|
||||||
|
public List<IffChunk> RemovedOriginal = new List<IffChunk>();
|
||||||
|
public PIFF CurrentPIFF;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new IFF instance.
|
||||||
|
/// </summary>
|
||||||
|
public IffFile()
|
||||||
|
{
|
||||||
|
ByChunkId = new Dictionary<Type, Dictionary<ushort, object>>();
|
||||||
|
ByChunkType = new Dictionary<Type, List<object>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an IFF instance from a filepath.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filepath">Path to the IFF.</param>
|
||||||
|
public IffFile(string filepath) : this()
|
||||||
|
{
|
||||||
|
using (var stream = File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
|
{
|
||||||
|
this.Read(stream);
|
||||||
|
SetFilename(Path.GetFileName(filepath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an IFF instance from a filepath.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filepath">Path to the IFF.</param>
|
||||||
|
public IffFile(string filepath, bool retainData) : this()
|
||||||
|
{
|
||||||
|
RetainChunkData = retainData;
|
||||||
|
using (var stream = File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
|
{
|
||||||
|
this.Read(stream);
|
||||||
|
SetFilename(Path.GetFileName(filepath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private bool WasReferenced = true;
|
||||||
|
~IffFile()
|
||||||
|
{
|
||||||
|
if (WasReferenced)
|
||||||
|
{
|
||||||
|
TimedReferenceController.KeepAlive(this, KeepAliveType.DEREFERENCED);
|
||||||
|
WasReferenced = false;
|
||||||
|
GC.ReRegisterForFinalize(this);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
var all = SilentListAll();
|
||||||
|
foreach (var chunk in all)
|
||||||
|
{
|
||||||
|
chunk.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void Rereferenced(bool saved)
|
||||||
|
{
|
||||||
|
WasReferenced = saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkThrowaway()
|
||||||
|
{
|
||||||
|
WasReferenced = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads an IFF from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to read from.</param>
|
||||||
|
public void Read(Stream stream)
|
||||||
|
{
|
||||||
|
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.BIG_ENDIAN))
|
||||||
|
{
|
||||||
|
var identifier = io.ReadCString(60, false).Replace("\0", "");
|
||||||
|
if (identifier != "IFF FILE 2.5:TYPE FOLLOWED BY SIZE JAMIE DOORNBOS & MAXIS 1")
|
||||||
|
{
|
||||||
|
if (identifier != "IFF FILE 2.0:TYPE FOLLOWED BY SIZE JAMIE DOORNBOS & MAXIS 1") //house11.iff, seems to read fine
|
||||||
|
throw new Exception("Invalid iff file!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var rsmpOffset = io.ReadUInt32();
|
||||||
|
|
||||||
|
while (io.HasMore)
|
||||||
|
{
|
||||||
|
var newChunk = AddChunk(stream, io, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitHash()
|
||||||
|
{
|
||||||
|
if (ExecutableHash != 0) return;
|
||||||
|
if (ByChunkType.ContainsKey(typeof(BHAV)))
|
||||||
|
{
|
||||||
|
IEnumerable<object> executableTypes = ByChunkType[typeof(BHAV)];
|
||||||
|
if (ByChunkType.ContainsKey(typeof(BCON))) executableTypes = executableTypes.Concat(ByChunkType[typeof(BCON)]);
|
||||||
|
var hash = new xxHashSharp.xxHash();
|
||||||
|
hash.Init();
|
||||||
|
foreach (IffChunk chunk in executableTypes)
|
||||||
|
{
|
||||||
|
hash.Update(chunk.ChunkData ?? chunk.OriginalData, chunk.ChunkData.Length);
|
||||||
|
}
|
||||||
|
ExecutableHash = hash.Digest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IffChunk AddChunk(Stream stream, IoBuffer io, bool add)
|
||||||
|
{
|
||||||
|
var chunkType = io.ReadCString(4);
|
||||||
|
var chunkSize = io.ReadUInt32();
|
||||||
|
var chunkID = io.ReadUInt16();
|
||||||
|
var chunkFlags = io.ReadUInt16();
|
||||||
|
var chunkLabel = io.ReadCString(64).TrimEnd('\0');
|
||||||
|
var chunkDataSize = chunkSize - 76;
|
||||||
|
|
||||||
|
/** Do we understand this chunk type? **/
|
||||||
|
if (!CHUNK_TYPES.ContainsKey(chunkType))
|
||||||
|
{
|
||||||
|
/** Skip it! **/
|
||||||
|
io.Skip(Math.Min(chunkDataSize, stream.Length - stream.Position - 1)); //if the chunk is invalid, it will likely provide a chunk size beyond the limits of the file. (walls2.iff)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Type chunkClass = CHUNK_TYPES[chunkType];
|
||||||
|
IffChunk newChunk = (IffChunk)Activator.CreateInstance(chunkClass);
|
||||||
|
newChunk.ChunkID = chunkID;
|
||||||
|
newChunk.OriginalID = chunkID;
|
||||||
|
newChunk.ChunkFlags = chunkFlags;
|
||||||
|
newChunk.ChunkLabel = chunkLabel;
|
||||||
|
newChunk.ChunkType = chunkType;
|
||||||
|
newChunk.ChunkData = io.ReadBytes(chunkDataSize);
|
||||||
|
|
||||||
|
if (RetainChunkData)
|
||||||
|
{
|
||||||
|
newChunk.OriginalLabel = chunkLabel;
|
||||||
|
newChunk.OriginalData = newChunk.ChunkData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add)
|
||||||
|
{
|
||||||
|
newChunk.ChunkParent = this;
|
||||||
|
|
||||||
|
if (!ByChunkType.ContainsKey(chunkClass))
|
||||||
|
{
|
||||||
|
ByChunkType.Add(chunkClass, new List<object>());
|
||||||
|
}
|
||||||
|
if (!ByChunkId.ContainsKey(chunkClass))
|
||||||
|
{
|
||||||
|
ByChunkId.Add(chunkClass, new Dictionary<ushort, object>());
|
||||||
|
}
|
||||||
|
|
||||||
|
ByChunkType[chunkClass].Add(newChunk);
|
||||||
|
if (!ByChunkId[chunkClass].ContainsKey(chunkID)) ByChunkId[chunkClass].Add(chunkID, newChunk);
|
||||||
|
}
|
||||||
|
return newChunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.BIG_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteCString("IFF FILE 2.5:TYPE FOLLOWED BY SIZE\0 JAMIE DOORNBOS & MAXIS 1", 60);
|
||||||
|
io.WriteUInt32(0); //todo: resource map offset
|
||||||
|
|
||||||
|
var chunks = ListAll();
|
||||||
|
foreach (var c in chunks)
|
||||||
|
{
|
||||||
|
WriteChunk(io, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteChunk(IoWriter io, IffChunk c)
|
||||||
|
{
|
||||||
|
var typeString = CHUNK_TYPES.FirstOrDefault(x => x.Value == c.GetType()).Key;
|
||||||
|
|
||||||
|
io.WriteCString((typeString == null) ? c.ChunkType : typeString, 4);
|
||||||
|
|
||||||
|
byte[] data;
|
||||||
|
using (var cstr = new MemoryStream())
|
||||||
|
{
|
||||||
|
if (c.Write(this, cstr)) data = cstr.ToArray();
|
||||||
|
else data = c.OriginalData;
|
||||||
|
}
|
||||||
|
|
||||||
|
//todo: exporting PIFF as IFF SHOULD NOT DO THIS
|
||||||
|
c.OriginalData = data; //if we revert, it is to the last save.
|
||||||
|
c.RuntimeInfo = ChunkRuntimeState.Normal;
|
||||||
|
|
||||||
|
io.WriteUInt32((uint)data.Length + 76);
|
||||||
|
io.WriteUInt16(c.ChunkID);
|
||||||
|
if (c.ChunkFlags == 0) c.ChunkFlags = 0x10;
|
||||||
|
io.WriteUInt16(c.ChunkFlags);
|
||||||
|
io.WriteCString(c.ChunkLabel, 64);
|
||||||
|
io.WriteBytes(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private T prepare<T>(object input)
|
||||||
|
{
|
||||||
|
IffChunk chunk = (IffChunk)input;
|
||||||
|
if (chunk.ChunkProcessed != true)
|
||||||
|
{
|
||||||
|
lock (chunk)
|
||||||
|
{
|
||||||
|
if (chunk.ChunkProcessed != true)
|
||||||
|
{
|
||||||
|
using (var stream = new MemoryStream(chunk.ChunkData))
|
||||||
|
{
|
||||||
|
chunk.Read(this, stream);
|
||||||
|
chunk.ChunkData = null;
|
||||||
|
chunk.ChunkProcessed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T)input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a chunk by its type and ID
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the chunk.</typeparam>
|
||||||
|
/// <param name="id">ID of the chunk.</param>
|
||||||
|
/// <returns>A chunk.</returns>
|
||||||
|
public T Get<T>(ushort id){
|
||||||
|
Type typeofT = typeof(T);
|
||||||
|
if (ByChunkId.ContainsKey(typeofT))
|
||||||
|
{
|
||||||
|
var lookup = ByChunkId[typeofT];
|
||||||
|
if (lookup.ContainsKey(id))
|
||||||
|
{
|
||||||
|
return prepare<T>(lookup[id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IffChunk> ListAll()
|
||||||
|
{
|
||||||
|
var result = new List<IffChunk>();
|
||||||
|
foreach (var type in ByChunkType.Values)
|
||||||
|
{
|
||||||
|
foreach (var chunk in type)
|
||||||
|
{
|
||||||
|
result.Add(this.prepare<IffChunk>(chunk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IffChunk> SilentListAll()
|
||||||
|
{
|
||||||
|
var result = new List<IffChunk>();
|
||||||
|
foreach (var type in ByChunkType.Values)
|
||||||
|
{
|
||||||
|
foreach (var chunk in type)
|
||||||
|
{
|
||||||
|
result.Add((IffChunk)chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List all chunks of a certain type
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the chunks to list.</typeparam>
|
||||||
|
/// <returns>A list of chunks of the type.</returns>
|
||||||
|
public List<T> List<T>()
|
||||||
|
{
|
||||||
|
Type typeofT = typeof(T);
|
||||||
|
|
||||||
|
if (ByChunkType.ContainsKey(typeofT))
|
||||||
|
{
|
||||||
|
var result = new List<T>();
|
||||||
|
foreach (var item in ByChunkType[typeofT])
|
||||||
|
{
|
||||||
|
result.Add(this.prepare<T>(item));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveChunk(IffChunk chunk)
|
||||||
|
{
|
||||||
|
var type = chunk.GetType();
|
||||||
|
ByChunkId[type].Remove(chunk.ChunkID);
|
||||||
|
ByChunkType[type].Remove(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FullRemoveChunk(IffChunk chunk)
|
||||||
|
{
|
||||||
|
//register this chunk as one that has been hard removed
|
||||||
|
if (!chunk.AddedByPatch) RemovedOriginal.Add(chunk);
|
||||||
|
RemoveChunk(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddChunk(IffChunk chunk)
|
||||||
|
{
|
||||||
|
var type = chunk.GetType();
|
||||||
|
chunk.ChunkParent = this;
|
||||||
|
|
||||||
|
if (!ByChunkType.ContainsKey(type))
|
||||||
|
{
|
||||||
|
ByChunkType.Add(type, new List<object>());
|
||||||
|
}
|
||||||
|
if (!ByChunkId.ContainsKey(type))
|
||||||
|
{
|
||||||
|
ByChunkId.Add(type, new Dictionary<ushort, object>());
|
||||||
|
}
|
||||||
|
|
||||||
|
ByChunkId[type].Add(chunk.ChunkID, chunk);
|
||||||
|
ByChunkType[type].Add(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveAndSwap(IffChunk chunk, ushort targID)
|
||||||
|
{
|
||||||
|
if (chunk.ChunkID == targID) return;
|
||||||
|
var type = chunk.GetType();
|
||||||
|
object targ = null;
|
||||||
|
if (ByChunkId.ContainsKey(type))
|
||||||
|
{
|
||||||
|
ByChunkId[type].TryGetValue(targID, out targ);
|
||||||
|
}
|
||||||
|
|
||||||
|
IffChunk tChunk = (IffChunk)targ;
|
||||||
|
|
||||||
|
if (tChunk != null) RemoveChunk(tChunk);
|
||||||
|
var oldID = chunk.ChunkID;
|
||||||
|
RemoveChunk(chunk);
|
||||||
|
chunk.ChunkID = targID;
|
||||||
|
AddChunk(chunk);
|
||||||
|
if (tChunk != null)
|
||||||
|
{
|
||||||
|
tChunk.ChunkID = oldID;
|
||||||
|
AddChunk(tChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Revert()
|
||||||
|
{
|
||||||
|
//revert all iffs and rerun patches
|
||||||
|
|
||||||
|
var toRemove = new List<IffChunk>();
|
||||||
|
foreach (var chunk in SilentListAll())
|
||||||
|
{
|
||||||
|
if (chunk.AddedByPatch) chunk.ChunkParent.RemoveChunk(chunk);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (chunk.OriginalData != null)
|
||||||
|
{
|
||||||
|
//revert if we have a state to revert to. for new chunks this is not really possible.
|
||||||
|
chunk.ChunkData = chunk.OriginalData;
|
||||||
|
chunk.ChunkParent.MoveAndSwap(chunk, chunk.OriginalID);
|
||||||
|
chunk.Dispose();
|
||||||
|
chunk.ChunkProcessed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//add back removed chunks
|
||||||
|
foreach (var chunk in RemovedOriginal)
|
||||||
|
{
|
||||||
|
chunk.ChunkParent.AddChunk(chunk);
|
||||||
|
}
|
||||||
|
RemovedOriginal.Clear();
|
||||||
|
|
||||||
|
//rerun patches
|
||||||
|
foreach (var piff in RuntimeInfo.Patches)
|
||||||
|
{
|
||||||
|
Patch(piff);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var type in ByChunkType.Values)
|
||||||
|
{
|
||||||
|
foreach (IffChunk chunk in type)
|
||||||
|
{
|
||||||
|
prepare<IffChunk>(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Revert<T>(T chunk) where T : IffChunk
|
||||||
|
{
|
||||||
|
chunk.RuntimeInfo = ChunkRuntimeState.Normal;
|
||||||
|
if (RuntimeInfo.State != IffRuntimeState.Standalone && chunk.AddedByPatch)
|
||||||
|
{
|
||||||
|
//added by piff.
|
||||||
|
foreach (var piff in RuntimeInfo.Patches)
|
||||||
|
{
|
||||||
|
var oldC = piff.Get<T>(chunk.ChunkID);
|
||||||
|
if (oldC != null)
|
||||||
|
{
|
||||||
|
chunk.ChunkData = oldC.OriginalData;
|
||||||
|
chunk.Dispose();
|
||||||
|
chunk.ChunkProcessed = false;
|
||||||
|
chunk.RuntimeInfo = ChunkRuntimeState.Patched;
|
||||||
|
prepare<T>(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
chunk.ChunkData = chunk.OriginalData;
|
||||||
|
foreach (var piffFile in RuntimeInfo.Patches)
|
||||||
|
{
|
||||||
|
var piff = piffFile.List<PIFF>()[0];
|
||||||
|
foreach (var e in piff.Entries)
|
||||||
|
{
|
||||||
|
var type = CHUNK_TYPES[e.Type];
|
||||||
|
if (!(type.IsAssignableFrom(chunk.GetType())) || e.ChunkID != chunk.ChunkID) continue;
|
||||||
|
if (chunk.RuntimeInfo == ChunkRuntimeState.Patched) continue;
|
||||||
|
|
||||||
|
chunk.ChunkData = e.Apply(chunk.ChunkData);
|
||||||
|
chunk.RuntimeInfo = ChunkRuntimeState.Patched;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunk.ChunkProcessed = false;
|
||||||
|
chunk.Dispose();
|
||||||
|
prepare<T>(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Patch(IffFile piffFile)
|
||||||
|
{
|
||||||
|
if (RuntimeInfo.State == IffRuntimeState.ReadOnly) RuntimeInfo.State = IffRuntimeState.PIFFPatch;
|
||||||
|
var piff = piffFile.List<PIFF>()[0];
|
||||||
|
CurrentPIFF = piff;
|
||||||
|
|
||||||
|
//patch existing chunks using the PIFF chunk
|
||||||
|
//also delete chunks marked for deletion
|
||||||
|
|
||||||
|
var moveChunks = new List<IffChunk>();
|
||||||
|
var newIDs = new List<ushort>();
|
||||||
|
|
||||||
|
foreach (var e in piff.Entries)
|
||||||
|
{
|
||||||
|
var type = CHUNK_TYPES[e.Type];
|
||||||
|
|
||||||
|
Dictionary<ushort, object> chunks = null;
|
||||||
|
ByChunkId.TryGetValue(type, out chunks);
|
||||||
|
if (chunks == null) continue;
|
||||||
|
object objC = null;
|
||||||
|
chunks.TryGetValue(e.ChunkID, out objC);
|
||||||
|
if (objC == null) continue;
|
||||||
|
|
||||||
|
var chunk = (IffChunk)objC;
|
||||||
|
if (e.EntryType == PIFFEntryType.Remove)
|
||||||
|
{
|
||||||
|
FullRemoveChunk(chunk); //removed by PIFF
|
||||||
|
}
|
||||||
|
else if(e.EntryType == PIFFEntryType.Patch)
|
||||||
|
{
|
||||||
|
chunk.ChunkData = e.Apply(chunk.ChunkData ?? chunk.OriginalData);
|
||||||
|
chunk.ChunkProcessed = false;
|
||||||
|
if (e.ChunkLabel != "") chunk.ChunkLabel = e.ChunkLabel;
|
||||||
|
chunk.RuntimeInfo = ChunkRuntimeState.Patched;
|
||||||
|
|
||||||
|
if (e.ChunkID != e.NewChunkID)
|
||||||
|
{
|
||||||
|
moveChunks.Add(chunk);
|
||||||
|
newIDs.Add(e.NewChunkID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0; i<moveChunks.Count; i++)
|
||||||
|
MoveAndSwap(moveChunks[i], newIDs[i]);
|
||||||
|
|
||||||
|
//add chunks present in the piff to the original file
|
||||||
|
foreach (var typeG in piffFile.ByChunkType)
|
||||||
|
{
|
||||||
|
if (typeG.Key == typeof(PIFF)) continue;
|
||||||
|
foreach (var res in typeG.Value)
|
||||||
|
{
|
||||||
|
var chunk = (IffChunk)res;
|
||||||
|
chunk.AddedByPatch = true;
|
||||||
|
if (!ByChunkId.ContainsKey(chunk.GetType()) || !ByChunkId[chunk.GetType()].ContainsKey(chunk.ChunkID)) this.AddChunk(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFilename(string filename)
|
||||||
|
{
|
||||||
|
Filename = filename;
|
||||||
|
var piffs = PIFFRegistry.GetPIFFs(filename);
|
||||||
|
RuntimeInfo.Patches.Clear();
|
||||||
|
if (piffs != null)
|
||||||
|
{
|
||||||
|
//apply patches
|
||||||
|
foreach (var piff in piffs)
|
||||||
|
{
|
||||||
|
Patch(piff);
|
||||||
|
if (RetainChunkData) RuntimeInfo.Patches.Add(piff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
server/tso.files/Formats/IFF/IffRuntimeInfo.cs
Executable file
29
server/tso.files/Formats/IFF/IffRuntimeInfo.cs
Executable file
|
@ -0,0 +1,29 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF
|
||||||
|
{
|
||||||
|
public class IffRuntimeInfo
|
||||||
|
{
|
||||||
|
public IffRuntimeState State;
|
||||||
|
public IffUseCase UseCase;
|
||||||
|
public string Filename;
|
||||||
|
public string Path;
|
||||||
|
public bool Dirty;
|
||||||
|
public List<IffFile> Patches = new List<IffFile>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum IffRuntimeState
|
||||||
|
{
|
||||||
|
ReadOnly, //orignal game iff
|
||||||
|
PIFFPatch, //replacement patch
|
||||||
|
PIFFClone, //clone of original object
|
||||||
|
Standalone //standalone, mutable iff
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum IffUseCase
|
||||||
|
{
|
||||||
|
Global,
|
||||||
|
Object,
|
||||||
|
ObjectSprites,
|
||||||
|
}
|
||||||
|
}
|
111
server/tso.files/Formats/IFF/PIFFRegistry.cs
Executable file
111
server/tso.files/Formats/IFF/PIFFRegistry.cs
Executable file
|
@ -0,0 +1,111 @@
|
||||||
|
using FSO.Files.Formats.IFF.Chunks;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.IFF
|
||||||
|
{
|
||||||
|
public static class PIFFRegistry
|
||||||
|
{
|
||||||
|
private static Dictionary<string, List<IffFile>> PIFFsByName = new Dictionary<string, List<IffFile>>();
|
||||||
|
private static Dictionary<string, string> OtfRewrite = new Dictionary<string, string>();
|
||||||
|
private static Dictionary<string, bool> IsPIFFUser = new Dictionary<string, bool>(); //if a piff is User, all other piffs for that file are ignored.
|
||||||
|
private static HashSet<string> OBJDAdded = new HashSet<string>();
|
||||||
|
|
||||||
|
public static void Init(string basePath)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(basePath)) return;
|
||||||
|
string[] paths = Directory.GetFiles(basePath, "*.piff", SearchOption.AllDirectories);
|
||||||
|
for (int i = 0; i < paths.Length; i++)
|
||||||
|
{
|
||||||
|
string entry = paths[i].Replace('\\', '/');
|
||||||
|
bool user = entry.Contains("User/");
|
||||||
|
string filename = Path.GetFileName(entry);
|
||||||
|
|
||||||
|
PIFF piff;
|
||||||
|
IffFile piffFile;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
piffFile = new IffFile(entry);
|
||||||
|
piff = piffFile.List<PIFF>()[0];
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (piff.Version < 2)
|
||||||
|
{
|
||||||
|
piff.AppendAddedChunks(piffFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsPIFFUser.ContainsKey(piff.SourceIff))
|
||||||
|
{
|
||||||
|
var old = IsPIFFUser[piff.SourceIff];
|
||||||
|
if (old != user)
|
||||||
|
{
|
||||||
|
if (user)
|
||||||
|
{
|
||||||
|
//remove old piffs, as they have been overwritten by this user piff.
|
||||||
|
PIFFsByName[piff.SourceIff].Clear();
|
||||||
|
IsPIFFUser[piff.SourceIff] = true;
|
||||||
|
}
|
||||||
|
else continue; //a user piff exists. ignore these ones.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else IsPIFFUser.Add(piff.SourceIff, user);
|
||||||
|
|
||||||
|
if (!PIFFsByName.ContainsKey(piff.SourceIff)) PIFFsByName.Add(piff.SourceIff, new List<IffFile>());
|
||||||
|
PIFFsByName[piff.SourceIff].Add(piffFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] otfs = Directory.GetFiles(basePath, "*.otf", SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
foreach (var otf in otfs)
|
||||||
|
{
|
||||||
|
string entry = otf.Replace('\\', '/');
|
||||||
|
OtfRewrite[Path.GetFileName(entry)] = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var piffs in PIFFsByName)
|
||||||
|
{
|
||||||
|
foreach (var piff in piffs.Value)
|
||||||
|
{
|
||||||
|
var addedOBJD = piff.List<OBJD>();
|
||||||
|
if (addedOBJD != null)
|
||||||
|
{
|
||||||
|
OBJDAdded.Add(piffs.Key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pChunk = piff.List<PIFF>()?.FirstOrDefault();
|
||||||
|
if (pChunk != null && pChunk.Entries.Any(x => x.Type == "OBJD"))
|
||||||
|
{
|
||||||
|
OBJDAdded.Add(piffs.Key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HashSet<string> GetOBJDRewriteNames()
|
||||||
|
{
|
||||||
|
return OBJDAdded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetOTFRewrite(string srcFile)
|
||||||
|
{
|
||||||
|
string result = null;
|
||||||
|
OtfRewrite.TryGetValue(srcFile, out result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<IffFile> GetPIFFs(string srcFile)
|
||||||
|
{
|
||||||
|
List<IffFile> result = null;
|
||||||
|
PIFFsByName.TryGetValue(srcFile, out result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
server/tso.files/Formats/OTF/OTFFile.cs
Executable file
105
server/tso.files/Formats/OTF/OTFFile.cs
Executable file
|
@ -0,0 +1,105 @@
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
using System.Xml;
|
||||||
|
using FSO.Files.Formats.IFF;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.OTF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Object Tuning File (OTF) is an SGML format which defines tuning constants.
|
||||||
|
/// </summary>
|
||||||
|
public class OTFFile
|
||||||
|
{
|
||||||
|
public XmlDocument Document;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an OTF instance from a filepath.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filepath">Path to the OTF.</param>
|
||||||
|
public OTFFile(string filepath)
|
||||||
|
{
|
||||||
|
using (var stream = File.OpenRead(filepath))
|
||||||
|
{
|
||||||
|
this.Read(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public OTFFile()
|
||||||
|
{
|
||||||
|
//you can also create empty OTFs!
|
||||||
|
}
|
||||||
|
|
||||||
|
public OTFTable[] Tables;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an OTFTable instance from an ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ID">The ID of the table.</param>
|
||||||
|
/// <returns>An OTFTable instance.</returns>
|
||||||
|
public OTFTable GetTable(int ID)
|
||||||
|
{
|
||||||
|
return Tables.FirstOrDefault(x => x?.ID == ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads an OTF from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to read from.</param>
|
||||||
|
public void Read(Stream stream)
|
||||||
|
{
|
||||||
|
var doc = new XmlDocument();
|
||||||
|
doc.Load(stream);
|
||||||
|
|
||||||
|
if (IffFile.RETAIN_CHUNK_DATA) Document = doc;
|
||||||
|
|
||||||
|
var tables = doc.GetElementsByTagName("T");
|
||||||
|
Tables = new OTFTable[tables.Count];
|
||||||
|
|
||||||
|
for (var i = 0; i < tables.Count; i++)
|
||||||
|
{
|
||||||
|
var table = tables.Item(i);
|
||||||
|
if (table.NodeType == XmlNodeType.Comment) continue;
|
||||||
|
var tableEntry = new OTFTable();
|
||||||
|
tableEntry.ID = int.Parse(table.Attributes["i"].Value);
|
||||||
|
tableEntry.Name = table.Attributes["n"].Value;
|
||||||
|
|
||||||
|
var numKeys = table.ChildNodes.Count;
|
||||||
|
tableEntry.Keys = new OTFTableKey[numKeys];
|
||||||
|
|
||||||
|
for (var x = 0; x < numKeys; x++)
|
||||||
|
{
|
||||||
|
var key = table.ChildNodes[x];
|
||||||
|
|
||||||
|
if (key.NodeType == XmlNodeType.Comment) continue;
|
||||||
|
|
||||||
|
var keyEntry = new OTFTableKey();
|
||||||
|
keyEntry.ID = int.Parse(key.Attributes["i"].Value);
|
||||||
|
keyEntry.Label = key.Attributes["l"].Value;
|
||||||
|
keyEntry.Value = int.Parse(key.Attributes["v"].Value);
|
||||||
|
tableEntry.Keys[x] = keyEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tables[i] = tableEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OTFTable
|
||||||
|
{
|
||||||
|
public int ID;
|
||||||
|
public string Name;
|
||||||
|
public OTFTableKey[] Keys;
|
||||||
|
|
||||||
|
public OTFTableKey GetKey(int id)
|
||||||
|
{
|
||||||
|
return Keys.FirstOrDefault(x => x?.ID == id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OTFTableKey
|
||||||
|
{
|
||||||
|
public int ID;
|
||||||
|
public string Label;
|
||||||
|
public int Value;
|
||||||
|
}
|
||||||
|
}
|
267
server/tso.files/Formats/PiffEncoder.cs
Executable file
267
server/tso.files/Formats/PiffEncoder.cs
Executable file
|
@ -0,0 +1,267 @@
|
||||||
|
using FSO.Files.Formats.IFF;
|
||||||
|
using FSO.Files.Formats.IFF.Chunks;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats
|
||||||
|
{
|
||||||
|
public static class PiffEncoder
|
||||||
|
{
|
||||||
|
private static string FindComment(IffChunk chunk, PIFF oldPIFF)
|
||||||
|
{
|
||||||
|
var oldEntry = oldPIFF?.Entries?.FirstOrDefault(entry =>
|
||||||
|
entry.Type == chunk.ChunkType &&
|
||||||
|
((chunk.AddedByPatch) ? chunk.ChunkID : chunk.OriginalID) == entry.ChunkID);
|
||||||
|
return oldEntry?.Comment ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IffFile GeneratePiff(IffFile iff, HashSet<Type> allowedTypes, HashSet<Type> disallowedTypes, PIFF oldPIFF)
|
||||||
|
{
|
||||||
|
var piffFile = new IffFile();
|
||||||
|
|
||||||
|
var piff = new PIFF();
|
||||||
|
piff.SourceIff = iff.Filename;
|
||||||
|
if (oldPIFF != null) piff.Comment = oldPIFF.Comment;
|
||||||
|
var entries = new List<PIFFEntry>();
|
||||||
|
var chunks = iff.ListAll();
|
||||||
|
|
||||||
|
//write removals first
|
||||||
|
foreach (var c in iff.RemovedOriginal)
|
||||||
|
{
|
||||||
|
lock (c)
|
||||||
|
{
|
||||||
|
if ((allowedTypes == null || allowedTypes.Contains(c.GetType()))
|
||||||
|
&& (disallowedTypes == null || !disallowedTypes.Contains(c.GetType())))
|
||||||
|
{
|
||||||
|
entries.Add(new PIFFEntry {
|
||||||
|
Type = c.ChunkType, ChunkID = c.OriginalID, EntryType = PIFFEntryType.Remove,
|
||||||
|
ChunkLabel = c.ChunkLabel, ChunkFlags = c.ChunkFlags, Comment = FindComment(c, oldPIFF)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool anyAdded = false;
|
||||||
|
foreach (var c in chunks)
|
||||||
|
{
|
||||||
|
//find a comment for this chunk
|
||||||
|
lock (c)
|
||||||
|
{
|
||||||
|
if ((allowedTypes == null || allowedTypes.Contains(c.GetType()))
|
||||||
|
&& (disallowedTypes == null || !disallowedTypes.Contains(c.GetType())))
|
||||||
|
{
|
||||||
|
if (c.AddedByPatch)
|
||||||
|
{
|
||||||
|
//this chunk has been newly added.
|
||||||
|
var oldParent = c.ChunkParent;
|
||||||
|
anyAdded = true;
|
||||||
|
piffFile.AddChunk(c);
|
||||||
|
c.ChunkParent = oldParent;
|
||||||
|
|
||||||
|
//make an entry for it!
|
||||||
|
entries.Add(new PIFFEntry()
|
||||||
|
{
|
||||||
|
ChunkID = c.ChunkID,
|
||||||
|
ChunkLabel = c.ChunkLabel,
|
||||||
|
ChunkFlags = c.ChunkFlags,
|
||||||
|
EntryType = PIFFEntryType.Add,
|
||||||
|
NewDataSize = (uint)(c.ChunkData?.Length ?? 0),
|
||||||
|
Type = c.ChunkType,
|
||||||
|
Comment = FindComment(c, oldPIFF)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if ((c.RuntimeInfo == ChunkRuntimeState.Modified || c.RuntimeInfo == ChunkRuntimeState.Patched))
|
||||||
|
{
|
||||||
|
var chunkD = MakeChunkDiff(c);
|
||||||
|
if (chunkD != null && (chunkD.Patches.Length > 0 || c.OriginalLabel != c.ChunkLabel || c.OriginalID != c.ChunkID))
|
||||||
|
{
|
||||||
|
chunkD.Comment = FindComment(c, oldPIFF);
|
||||||
|
entries.Add(chunkD);
|
||||||
|
}
|
||||||
|
c.RuntimeInfo = ChunkRuntimeState.Patched;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entries.Count == 0 && !anyAdded) return null; //no patch data...
|
||||||
|
piff.Entries = entries.ToArray();
|
||||||
|
piff.ChunkID = 256;
|
||||||
|
piff.ChunkLabel = (piff.SourceIff + " patch");
|
||||||
|
piff.ChunkProcessed = true;
|
||||||
|
|
||||||
|
piffFile.AddChunk(piff);
|
||||||
|
piffFile.Filename = (oldPIFF != null) ? oldPIFF.ChunkParent.Filename : null; // (piff.SourceIff.Substring(0, piff.SourceIff.Length - 4)+".piff")
|
||||||
|
return piffFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PIFFEntry MakeChunkDiff(IffChunk chk)
|
||||||
|
{
|
||||||
|
var e = new PIFFEntry { Type = chk.ChunkType, ChunkID = chk.OriginalID, NewChunkID = chk.ChunkID };
|
||||||
|
if (chk == null)
|
||||||
|
{
|
||||||
|
e.EntryType = PIFFEntryType.Remove;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] newData = null;
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
if (!chk.Write(chk.ChunkParent, stream))
|
||||||
|
{
|
||||||
|
return null; //use original
|
||||||
|
}
|
||||||
|
newData = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
e.ChunkLabel = (chk.OriginalLabel==chk.ChunkLabel)?"":chk.ChunkLabel;
|
||||||
|
e.ChunkFlags = chk.ChunkFlags;
|
||||||
|
e.NewDataSize = (uint)newData.Length;
|
||||||
|
|
||||||
|
//encode difference as sequence of changes
|
||||||
|
var oldData = chk.OriginalData;
|
||||||
|
var patches = new List<PIFFPatch>();
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for (i=0; i<newData.Length; i += 1000)
|
||||||
|
{
|
||||||
|
if (i >= oldData.Length)
|
||||||
|
{
|
||||||
|
//no more comparisons, just add the remainder
|
||||||
|
var remain = new byte[newData.Length-i];
|
||||||
|
Array.Copy(newData, i, remain, 0, remain.Length);
|
||||||
|
patches.Add(new PIFFPatch
|
||||||
|
{
|
||||||
|
Mode = PIFFPatchMode.Add,
|
||||||
|
Data = remain,
|
||||||
|
Offset = (uint)i,
|
||||||
|
Size = (uint)remain.Length
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//dynamic programming matrix.
|
||||||
|
int m = Math.Min(1000, Math.Max(0, newData.Length - i))+1;
|
||||||
|
int n = Math.Min(1000, Math.Max(0, oldData.Length - i))+1;
|
||||||
|
ushort[,] comp = new ushort[m, n];
|
||||||
|
for (int x=1; x<m; x++)
|
||||||
|
{
|
||||||
|
for (int y=1; y<n; y++)
|
||||||
|
{
|
||||||
|
if (newData[i+x-1] == oldData[i + y -1]) comp[x, y] = (ushort)(comp[x - 1, y - 1] + 1);
|
||||||
|
else comp[x, y] = Math.Max(comp[x, y - 1], comp[x - 1, y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var changes = new Stack<byte>();
|
||||||
|
//backtrack through compare
|
||||||
|
{
|
||||||
|
int x = m-1, y = n-1;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (x>0 && y>0 && newData[i + x -1] == oldData[i + y -1])
|
||||||
|
{
|
||||||
|
x--; y--; changes.Push(0); //no change
|
||||||
|
} else if (y>0 && (x==0 || comp[x,y-1] >= comp[x-1,y]))
|
||||||
|
{
|
||||||
|
y--; changes.Push(2); //remove
|
||||||
|
} else if (x>0 && (y==0 || comp[x,y-1] < comp[x-1,y]))
|
||||||
|
{
|
||||||
|
x--; changes.Push(1); //add
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte lastC = 0;
|
||||||
|
PIFFPatch? curr = null;
|
||||||
|
List<byte> addArray = null;
|
||||||
|
int ptr = 0;
|
||||||
|
foreach (var c in changes)
|
||||||
|
{
|
||||||
|
if (c != lastC && curr != null)
|
||||||
|
{
|
||||||
|
var patch = curr.Value;
|
||||||
|
if (lastC == 1) patch.Data = addArray.ToArray();
|
||||||
|
patches.Add(patch);
|
||||||
|
curr = null;
|
||||||
|
}
|
||||||
|
if (c == 0) ptr++;
|
||||||
|
else if (c == 1)
|
||||||
|
{
|
||||||
|
if (lastC != 1)
|
||||||
|
{
|
||||||
|
curr = new PIFFPatch { Mode = PIFFPatchMode.Add, Offset = (uint)(i + ptr), Size = 1 };
|
||||||
|
addArray = new List<byte>();
|
||||||
|
addArray.Add(newData[i + ptr]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var patch = curr.Value;
|
||||||
|
patch.Size++;
|
||||||
|
curr = patch;
|
||||||
|
addArray.Add(newData[i + ptr]);
|
||||||
|
}
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (lastC != 2)
|
||||||
|
curr = new PIFFPatch { Mode = PIFFPatchMode.Remove, Offset = (uint)(i + ptr), Size = 1 };
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var patch = curr.Value;
|
||||||
|
patch.Size++;
|
||||||
|
curr = patch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastC = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr != null)
|
||||||
|
{
|
||||||
|
var patch = curr.Value;
|
||||||
|
if (lastC == 1) patch.Data = addArray.ToArray();
|
||||||
|
patches.Add(patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m < n)
|
||||||
|
{
|
||||||
|
//remainder on src to be removed
|
||||||
|
patches.Add(new PIFFPatch { Mode = PIFFPatchMode.Remove, Offset = (uint)(i+ptr), Size = (uint)(n-m) });
|
||||||
|
}
|
||||||
|
/*else if (m != n)
|
||||||
|
{
|
||||||
|
//remainder on dest to be added
|
||||||
|
var remain = new byte[m-n];
|
||||||
|
Array.Copy(newData, i+ptr, remain, 0, remain.Length);
|
||||||
|
patches.Add(new PIFFPatch
|
||||||
|
{
|
||||||
|
Mode = PIFFPatchMode.Add,
|
||||||
|
Data = remain,
|
||||||
|
Offset = (uint)(i+ ptr),
|
||||||
|
Size = (uint)remain.Length
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldData.Length > i)
|
||||||
|
{
|
||||||
|
//ran out of new data, but old is still going. Remove the remainder.
|
||||||
|
patches.Add(new PIFFPatch
|
||||||
|
{
|
||||||
|
Mode = PIFFPatchMode.Remove,
|
||||||
|
Offset = (uint)newData.Length,
|
||||||
|
Size = (uint)(oldData.Length - i)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Patches = patches.ToArray();
|
||||||
|
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
server/tso.files/Formats/tsodata/BulletinItem.cs
Executable file
91
server/tso.files/Formats/tsodata/BulletinItem.cs
Executable file
|
@ -0,0 +1,91 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.tsodata
|
||||||
|
{
|
||||||
|
public class BulletinItem
|
||||||
|
{
|
||||||
|
public static int CURRENT_VERSION = 1;
|
||||||
|
public int Version = CURRENT_VERSION;
|
||||||
|
public uint ID;
|
||||||
|
public uint NhoodID;
|
||||||
|
public uint SenderID;
|
||||||
|
|
||||||
|
public string Subject;
|
||||||
|
public string Body;
|
||||||
|
public string SenderName;
|
||||||
|
|
||||||
|
public long Time;
|
||||||
|
|
||||||
|
public BulletinType Type;
|
||||||
|
public BulletinFlags Flags;
|
||||||
|
|
||||||
|
public uint LotID; //optional: for lot advertisements.
|
||||||
|
|
||||||
|
public BulletinItem()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulletinItem(Stream stream)
|
||||||
|
{
|
||||||
|
Read(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(Stream stream)
|
||||||
|
{
|
||||||
|
using (var writer = IoWriter.FromStream(stream))
|
||||||
|
{
|
||||||
|
writer.WriteCString("FSOB", 4);
|
||||||
|
writer.WriteInt32(Version);
|
||||||
|
writer.WriteUInt32(ID);
|
||||||
|
writer.WriteUInt32(NhoodID);
|
||||||
|
writer.WriteUInt32(SenderID);
|
||||||
|
|
||||||
|
writer.WriteLongPascalString(Subject);
|
||||||
|
writer.WriteLongPascalString(Body);
|
||||||
|
writer.WriteLongPascalString(SenderName);
|
||||||
|
writer.WriteInt64(Time);
|
||||||
|
writer.WriteInt32((int)Type);
|
||||||
|
writer.WriteInt32((int)Flags);
|
||||||
|
|
||||||
|
writer.WriteUInt32(LotID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Read(Stream stream)
|
||||||
|
{
|
||||||
|
using (var reader = IoBuffer.FromStream(stream))
|
||||||
|
{
|
||||||
|
var magic = reader.ReadCString(4);
|
||||||
|
Version = reader.ReadInt32();
|
||||||
|
ID = reader.ReadUInt32();
|
||||||
|
NhoodID = reader.ReadUInt32();
|
||||||
|
SenderID = reader.ReadUInt32();
|
||||||
|
|
||||||
|
Subject = reader.ReadLongPascalString();
|
||||||
|
Body = reader.ReadLongPascalString();
|
||||||
|
SenderName = reader.ReadLongPascalString();
|
||||||
|
Time = reader.ReadInt64();
|
||||||
|
Type = (BulletinType)reader.ReadInt32();
|
||||||
|
Flags = (BulletinFlags)reader.ReadInt32();
|
||||||
|
|
||||||
|
LotID = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BulletinType
|
||||||
|
{
|
||||||
|
Mayor = 0,
|
||||||
|
System = 1,
|
||||||
|
Community = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum BulletinFlags
|
||||||
|
{
|
||||||
|
PromotedByMayor = 1
|
||||||
|
}
|
||||||
|
}
|
87
server/tso.files/Formats/tsodata/MessageItem.cs
Executable file
87
server/tso.files/Formats/tsodata/MessageItem.cs
Executable file
|
@ -0,0 +1,87 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.tsodata
|
||||||
|
{
|
||||||
|
public class MessageItem
|
||||||
|
{
|
||||||
|
public static int CURRENT_VERSION = 1;
|
||||||
|
public int Version = CURRENT_VERSION;
|
||||||
|
public int ID;
|
||||||
|
public uint SenderID;
|
||||||
|
public uint TargetID;
|
||||||
|
public string Subject;
|
||||||
|
public string Body;
|
||||||
|
public string SenderName;
|
||||||
|
public long Time;
|
||||||
|
public int Type; //(message/vote/club/maxis/tso/house/roommate/call)
|
||||||
|
public int Subtype; //(urgent?)
|
||||||
|
public int ReadState;
|
||||||
|
public int? ReplyID;
|
||||||
|
|
||||||
|
public MessageItem()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageItem(Stream stream)
|
||||||
|
{
|
||||||
|
Read(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(Stream stream) {
|
||||||
|
using (var writer = IoWriter.FromStream(stream))
|
||||||
|
{
|
||||||
|
writer.WriteCString("FSOI", 4);
|
||||||
|
writer.WriteInt32(Version);
|
||||||
|
writer.WriteInt32(ID);
|
||||||
|
writer.WriteUInt32(SenderID);
|
||||||
|
writer.WriteUInt32(TargetID);
|
||||||
|
writer.WriteLongPascalString(Subject);
|
||||||
|
writer.WriteLongPascalString(Body);
|
||||||
|
writer.WriteLongPascalString(SenderName);
|
||||||
|
writer.WriteInt64(Time);
|
||||||
|
writer.WriteInt32(Type);
|
||||||
|
writer.WriteInt32(Subtype);
|
||||||
|
writer.WriteInt32(ReadState);
|
||||||
|
writer.WriteByte((byte)((ReplyID == null) ? 0 : 1));
|
||||||
|
if (ReplyID != null) writer.WriteInt32(ReplyID.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Read(Stream stream)
|
||||||
|
{
|
||||||
|
using (var reader = IoBuffer.FromStream(stream))
|
||||||
|
{
|
||||||
|
var magic = reader.ReadCString(4);
|
||||||
|
Version = reader.ReadInt32();
|
||||||
|
ID = reader.ReadInt32();
|
||||||
|
SenderID = reader.ReadUInt32();
|
||||||
|
TargetID = reader.ReadUInt32();
|
||||||
|
Subject = reader.ReadLongPascalString();
|
||||||
|
Body = reader.ReadLongPascalString();
|
||||||
|
SenderName = reader.ReadLongPascalString();
|
||||||
|
Time = reader.ReadInt64();
|
||||||
|
Type = reader.ReadInt32();
|
||||||
|
Subtype = reader.ReadInt32();
|
||||||
|
ReadState = reader.ReadInt32();
|
||||||
|
if (reader.ReadByte() > 0)
|
||||||
|
{
|
||||||
|
ReplyID = reader.ReadInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MessageSpecialType
|
||||||
|
{
|
||||||
|
Normal = 0,
|
||||||
|
|
||||||
|
//neighbourhoods
|
||||||
|
Nominate = 1,
|
||||||
|
Vote = 2,
|
||||||
|
|
||||||
|
AcceptNomination = 3,
|
||||||
|
FreeVote = 4
|
||||||
|
}
|
||||||
|
}
|
517
server/tso.files/Formats/tsodata/TSODataDefinition.cs
Executable file
517
server/tso.files/Formats/tsodata/TSODataDefinition.cs
Executable file
|
@ -0,0 +1,517 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace FSO.Files.Formats.tsodata
|
||||||
|
{
|
||||||
|
public class TSODataDefinition
|
||||||
|
{
|
||||||
|
public List<ListEntry> List1;
|
||||||
|
public List<ListEntry> List2;
|
||||||
|
public List<ListEntry> List3;
|
||||||
|
public List<StringTableEntry> Strings;
|
||||||
|
|
||||||
|
public Struct[] Structs;
|
||||||
|
public DerivedStruct[] DerivedStructs;
|
||||||
|
public uint FileID;
|
||||||
|
|
||||||
|
public static TSODataDefinition Active;
|
||||||
|
|
||||||
|
private Dictionary<string, Struct> StructsByName = new Dictionary<string, Struct>();
|
||||||
|
|
||||||
|
public void Read(Stream stream)
|
||||||
|
{
|
||||||
|
using (var reader = new BinaryReader(stream))
|
||||||
|
{
|
||||||
|
FileID = reader.ReadUInt32();
|
||||||
|
this.List1 = ReadList(reader, false);
|
||||||
|
this.List2 = ReadList(reader, false);
|
||||||
|
this.List3 = ReadList(reader, true);
|
||||||
|
|
||||||
|
var numStrings = reader.ReadUInt32();
|
||||||
|
this.Strings = new List<StringTableEntry>();
|
||||||
|
for (var i = 0; i < numStrings; i++)
|
||||||
|
{
|
||||||
|
var stringEntry = new StringTableEntry();
|
||||||
|
stringEntry.ID = reader.ReadUInt32();
|
||||||
|
stringEntry.Value = ReadNullTerminatedString(reader);
|
||||||
|
stringEntry.Category = (StringTableType)reader.ReadByte();
|
||||||
|
this.Strings.Add(stringEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(Stream stream)
|
||||||
|
{
|
||||||
|
using (var writer = new BinaryWriter(stream))
|
||||||
|
{
|
||||||
|
writer.Write(FileID);
|
||||||
|
WriteList(List1, writer, false);
|
||||||
|
WriteList(List2, writer, false);
|
||||||
|
WriteList(List3, writer, true);
|
||||||
|
|
||||||
|
writer.Write(Strings.Count);
|
||||||
|
foreach (var str in Strings)
|
||||||
|
{
|
||||||
|
writer.Write(str.ID);
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes(str.Value));
|
||||||
|
writer.Write((byte)0);
|
||||||
|
writer.Write((byte)str.Category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Activate()
|
||||||
|
{
|
||||||
|
var Structs = new List<Struct>();
|
||||||
|
var DerivedStructs = new List<DerivedStruct>();
|
||||||
|
|
||||||
|
//1st level structs. all fields are primitive types
|
||||||
|
foreach (var item in List1)
|
||||||
|
{
|
||||||
|
var fields = new List<StructField>();
|
||||||
|
|
||||||
|
foreach (var field in item.Entries)
|
||||||
|
{
|
||||||
|
if (field.TypeStringID == 0xA99AF3AC) Console.WriteLine("unknown value: " + GetString(item.NameStringID) + "::" + GetString(field.NameStringID));
|
||||||
|
fields.Add(new StructField
|
||||||
|
{
|
||||||
|
ID = field.NameStringID,
|
||||||
|
Name = GetString(field.NameStringID),
|
||||||
|
TypeID = field.TypeStringID,
|
||||||
|
Classification = (StructFieldClassification)field.TypeClass,
|
||||||
|
ParentID = item.NameStringID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Structs.Add(new Struct
|
||||||
|
{
|
||||||
|
ID = item.NameStringID,
|
||||||
|
Name = GetString(item.NameStringID),
|
||||||
|
Fields = fields.ToList()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//2nd level structs. fields can be first level structs
|
||||||
|
//note: this might be a hard limit in tso, but it is not particularly important in freeso
|
||||||
|
// the game will even behave correctly if a 2nd level references another 2nd level,
|
||||||
|
// though a circular reference will likely still break everything.
|
||||||
|
foreach (var item in List2)
|
||||||
|
{
|
||||||
|
var fields = new List<StructField>();
|
||||||
|
|
||||||
|
foreach (var field in item.Entries)
|
||||||
|
{
|
||||||
|
if (field.TypeStringID == 0xA99AF3AC) Console.WriteLine("unknown value: " + GetString(item.NameStringID) + "::" + GetString(field.NameStringID));
|
||||||
|
fields.Add(new StructField
|
||||||
|
{
|
||||||
|
ID = field.NameStringID,
|
||||||
|
Name = GetString(field.NameStringID),
|
||||||
|
TypeID = field.TypeStringID,
|
||||||
|
Classification = (StructFieldClassification)field.TypeClass,
|
||||||
|
ParentID = item.NameStringID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Structs.Add(new Struct
|
||||||
|
{
|
||||||
|
ID = item.NameStringID,
|
||||||
|
Name = GetString(item.NameStringID),
|
||||||
|
Fields = fields.ToList()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//derived structs. serve as valid subsets of fields of an entity that the client can request.
|
||||||
|
foreach (var item in List3)
|
||||||
|
{
|
||||||
|
var fields = new List<DerivedStructFieldMask>();
|
||||||
|
|
||||||
|
foreach (var field in item.Entries)
|
||||||
|
{
|
||||||
|
if (field.TypeStringID == 0xA99AF3AC) Console.WriteLine("unknown value: " + GetString(item.NameStringID) + "::" + GetString(field.NameStringID));
|
||||||
|
fields.Add(new DerivedStructFieldMask
|
||||||
|
{
|
||||||
|
ID = field.NameStringID,
|
||||||
|
Name = GetString(field.NameStringID),
|
||||||
|
Type = (DerivedStructFieldMaskType)field.TypeClass
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
DerivedStructs.Add(new DerivedStruct
|
||||||
|
{
|
||||||
|
ID = item.NameStringID,
|
||||||
|
Parent = item.ParentStringID,
|
||||||
|
Name = GetString(item.NameStringID),
|
||||||
|
FieldMasks = fields.ToArray()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Structs = Structs.ToArray();
|
||||||
|
this.DerivedStructs = DerivedStructs.ToArray();
|
||||||
|
|
||||||
|
foreach (var _struct in Structs)
|
||||||
|
{
|
||||||
|
StructsByName.Add(_struct.Name, _struct);
|
||||||
|
}
|
||||||
|
|
||||||
|
//InjectStructs();
|
||||||
|
Active = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InjectStructs()
|
||||||
|
{
|
||||||
|
//this is just an example of how to do this.
|
||||||
|
//todo: a format we can easily create and read from to provide these new fields
|
||||||
|
|
||||||
|
StructsByName["Lot"].Fields.Add(new StructField()
|
||||||
|
{
|
||||||
|
Name = "Lot_SkillGamemode",
|
||||||
|
ID = 0xaabbccdd,
|
||||||
|
Classification = StructFieldClassification.SingleField,
|
||||||
|
ParentID = StructsByName["Lot"].ID,
|
||||||
|
TypeID = 1768755593 //uint32
|
||||||
|
});
|
||||||
|
|
||||||
|
var fields = DerivedStructs[17].FieldMasks.ToList();
|
||||||
|
fields.Add(new DerivedStructFieldMask()
|
||||||
|
{
|
||||||
|
ID = 0xaabbccdd,
|
||||||
|
Name = "Lot_SkillGamemode",
|
||||||
|
Type = DerivedStructFieldMaskType.KEEP
|
||||||
|
});
|
||||||
|
DerivedStructs[17].FieldMasks = fields.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Struct GetStructFromValue(object value)
|
||||||
|
{
|
||||||
|
if (value == null) { return null; }
|
||||||
|
return GetStruct(value.GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Struct GetStruct(Type type)
|
||||||
|
{
|
||||||
|
return GetStruct(type.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Struct GetStruct(string name)
|
||||||
|
{
|
||||||
|
if (StructsByName.ContainsKey(name))
|
||||||
|
{
|
||||||
|
return StructsByName[name];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Struct GetStruct(uint id)
|
||||||
|
{
|
||||||
|
return Structs.FirstOrDefault(x => x.ID == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringTableType GetStringType(uint id)
|
||||||
|
{
|
||||||
|
var item = Strings.FirstOrDefault(x => x.ID == id);
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return StringTableType.Field;
|
||||||
|
}
|
||||||
|
return item.Category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetString(uint id)
|
||||||
|
{
|
||||||
|
var item = Strings.FirstOrDefault(x => x.ID == id);
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return item.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void SetString(uint id, string value)
|
||||||
|
{
|
||||||
|
var item = Strings.FirstOrDefault(x => x.ID == id);
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveString(uint id)
|
||||||
|
{
|
||||||
|
Strings.RemoveAll(x => x.ID == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetString(List<StringTableEntry> strings, uint id)
|
||||||
|
{
|
||||||
|
var item = strings.FirstOrDefault(x => x.ID == id);
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return item.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ReadNullTerminatedString(BinaryReader reader)
|
||||||
|
{
|
||||||
|
var result = "";
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var ch = (char)reader.ReadByte();
|
||||||
|
if (ch == '\0')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result += ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ListEntry> ReadList(BinaryReader reader, bool parentID)
|
||||||
|
{
|
||||||
|
var list1Count = reader.ReadUInt32();
|
||||||
|
|
||||||
|
var list1 = new List<ListEntry>();
|
||||||
|
for (int i = 0; i < list1Count; i++)
|
||||||
|
{
|
||||||
|
var entry = new ListEntry();
|
||||||
|
entry.Parent = this;
|
||||||
|
entry.NameStringID = reader.ReadUInt32();
|
||||||
|
if (parentID)
|
||||||
|
{
|
||||||
|
entry.ParentStringID = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
entry.Entries = new List<ListEntryEntry>();
|
||||||
|
|
||||||
|
var subEntryCount = reader.ReadUInt32();
|
||||||
|
for (int y = 0; y < subEntryCount; y++)
|
||||||
|
{
|
||||||
|
var subEntry = new ListEntryEntry();
|
||||||
|
subEntry.Parent = entry;
|
||||||
|
subEntry.NameStringID = reader.ReadUInt32();
|
||||||
|
subEntry.TypeClass = reader.ReadByte();
|
||||||
|
if (!parentID)
|
||||||
|
{
|
||||||
|
subEntry.TypeStringID = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
entry.Entries.Add(subEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
list1.Add(entry);
|
||||||
|
}
|
||||||
|
return list1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteList(List<ListEntry> list, BinaryWriter writer, bool parentID)
|
||||||
|
{
|
||||||
|
writer.Write(list.Count);
|
||||||
|
foreach (var entry in list)
|
||||||
|
{
|
||||||
|
writer.Write(entry.NameStringID);
|
||||||
|
if (parentID) writer.Write(entry.ParentStringID);
|
||||||
|
writer.Write(entry.Entries.Count);
|
||||||
|
foreach (var subEntry in entry.Entries)
|
||||||
|
{
|
||||||
|
writer.Write(subEntry.NameStringID);
|
||||||
|
writer.Write(subEntry.TypeClass);
|
||||||
|
if (!parentID) writer.Write(subEntry.TypeStringID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StringTableType : byte
|
||||||
|
{
|
||||||
|
Field = 1,
|
||||||
|
Primitive = 2,
|
||||||
|
Level1 = 3,
|
||||||
|
Level2 = 4,
|
||||||
|
Derived = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StringTableEntry
|
||||||
|
{
|
||||||
|
public uint ID;
|
||||||
|
public string Value;
|
||||||
|
public StringTableType Category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ListEntry
|
||||||
|
{
|
||||||
|
public TSODataDefinition Parent;
|
||||||
|
|
||||||
|
public uint NameStringID { get; set; }
|
||||||
|
public uint ParentStringID { get; set; }
|
||||||
|
public List<ListEntryEntry> Entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ListEntryEntry
|
||||||
|
{
|
||||||
|
public ListEntry Parent;
|
||||||
|
|
||||||
|
public uint NameStringID;
|
||||||
|
public byte TypeClass;
|
||||||
|
public uint TypeStringID;
|
||||||
|
|
||||||
|
[Category("Struct Properties")]
|
||||||
|
[Description("The name for this field/struct. (ONLY FOR STRUCTS)")]
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Parent.Parent.GetString(NameStringID);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Parent.Parent.SetString(NameStringID, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Category("Struct Properties")]
|
||||||
|
[Description("The type this field should have. (ONLY FOR STRUCTS)")]
|
||||||
|
[TypeConverter(typeof(TypeSelector))]
|
||||||
|
public string FieldType {
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Parent.Parent.GetString(TypeStringID);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
TypeStringID = Parent.Parent.Strings.First(x => x.Value == value).ID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Category("Struct Properties")]
|
||||||
|
[Description("What kind of collection this field is. (ONLY FOR STRUCTS)")]
|
||||||
|
public StructFieldClassification FieldClass {
|
||||||
|
get {
|
||||||
|
return (StructFieldClassification)TypeClass;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
TypeClass = (byte)value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TypeSelector : TypeConverter
|
||||||
|
{
|
||||||
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
|
||||||
|
{
|
||||||
|
return new StandardValuesCollection(
|
||||||
|
TSODataDefinition.Active.Strings
|
||||||
|
.Where(x => x.Category == StringTableType.Level1 || x.Category == StringTableType.Primitive)
|
||||||
|
.OrderBy(x => x.Category)
|
||||||
|
.ThenBy(x => x.Value)
|
||||||
|
.Select(x => x.Value)
|
||||||
|
.ToList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Category("Mask Properties")]
|
||||||
|
[TypeConverter(typeof(NameSelector))]
|
||||||
|
[Description("The field to mask. (ONLY FOR MASKS)")]
|
||||||
|
public string MaskField
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Parent.Parent.GetString(NameStringID);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
NameStringID = Parent.Parent.Strings.First(x => x.Value == value).ID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Category("Mask Properties")]
|
||||||
|
[Description("If this field should be kept or removed for this request. (ONLY FOR MASKS)")]
|
||||||
|
public DerivedStructFieldMaskType MaskMode
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (DerivedStructFieldMaskType)TypeClass;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
TypeClass = (byte)value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NameSelector : TypeConverter
|
||||||
|
{
|
||||||
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
|
||||||
|
{
|
||||||
|
return new StandardValuesCollection(
|
||||||
|
TSODataDefinition.Active.Strings
|
||||||
|
.Where(x => x.Category == StringTableType.Field)
|
||||||
|
.OrderBy(x => x.Category)
|
||||||
|
.ThenBy(x => x.Value)
|
||||||
|
.Select(x => x.Value)
|
||||||
|
.ToList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Struct {
|
||||||
|
public uint ID;
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
public List<StructField> Fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StructField {
|
||||||
|
public uint ID;
|
||||||
|
public string Name;
|
||||||
|
public StructFieldClassification Classification;
|
||||||
|
public uint TypeID;
|
||||||
|
public uint ParentID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StructFieldClassification
|
||||||
|
{
|
||||||
|
SingleField = 0,
|
||||||
|
Map = 1,
|
||||||
|
List = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DerivedStruct
|
||||||
|
{
|
||||||
|
public uint ID;
|
||||||
|
public string Name;
|
||||||
|
public uint Parent;
|
||||||
|
|
||||||
|
public DerivedStructFieldMask[] FieldMasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DerivedStructFieldMask
|
||||||
|
{
|
||||||
|
public uint ID;
|
||||||
|
public string Name;
|
||||||
|
public DerivedStructFieldMaskType Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DerivedStructFieldMaskType
|
||||||
|
{
|
||||||
|
KEEP = 0x01,
|
||||||
|
REMOVE = 0x02
|
||||||
|
}
|
||||||
|
}
|
183
server/tso.files/Formats/tsodata/TSOp.cs
Executable file
183
server/tso.files/Formats/tsodata/TSOp.cs
Executable file
|
@ -0,0 +1,183 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using deltaq;
|
||||||
|
|
||||||
|
namespace TSOVersionPatcher
|
||||||
|
{
|
||||||
|
public class TSOp
|
||||||
|
{
|
||||||
|
public List<FileEntry> Patches;
|
||||||
|
public List<FileEntry> Additions;
|
||||||
|
public List<string> Deletions;
|
||||||
|
|
||||||
|
private Stream Str;
|
||||||
|
|
||||||
|
public TSOp(Stream str)
|
||||||
|
{
|
||||||
|
Str = str;
|
||||||
|
using (var io = IoBuffer.FromStream(str, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var magic = io.ReadCString(4);
|
||||||
|
if (magic != "TSOp")
|
||||||
|
throw new Exception("Not a TSO patch file!");
|
||||||
|
var version = io.ReadInt32();
|
||||||
|
|
||||||
|
var ips = io.ReadCString(4);
|
||||||
|
if (ips != "IPS_")
|
||||||
|
throw new Exception("Invalid Patch Chunk!");
|
||||||
|
|
||||||
|
var patchCount = io.ReadInt32();
|
||||||
|
Patches = new List<FileEntry>();
|
||||||
|
for (int i = 0; i < patchCount; i++)
|
||||||
|
{
|
||||||
|
Patches.Add(new FileEntry()
|
||||||
|
{
|
||||||
|
FileTarget = io.ReadVariableLengthPascalString().Replace('\\', '/'),
|
||||||
|
Length = io.ReadInt32(),
|
||||||
|
Offset = str.Position
|
||||||
|
});
|
||||||
|
str.Seek(Patches.Last().Length, SeekOrigin.Current);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var add = io.ReadCString(4);
|
||||||
|
if (add != "ADD_")
|
||||||
|
throw new Exception("Invalid Addition Chunk!");
|
||||||
|
|
||||||
|
var addCount = io.ReadInt32();
|
||||||
|
Additions = new List<FileEntry>();
|
||||||
|
for (int i = 0; i < addCount; i++)
|
||||||
|
{
|
||||||
|
Additions.Add(new FileEntry()
|
||||||
|
{
|
||||||
|
FileTarget = io.ReadVariableLengthPascalString().Replace('\\', '/'),
|
||||||
|
Length = io.ReadInt32(),
|
||||||
|
Offset = str.Position
|
||||||
|
});
|
||||||
|
str.Seek(Additions.Last().Length, SeekOrigin.Current);
|
||||||
|
}
|
||||||
|
|
||||||
|
var del = io.ReadCString(4);
|
||||||
|
if (del != "DEL_")
|
||||||
|
throw new Exception("Invalid Deletion Chunk!");
|
||||||
|
|
||||||
|
var delCount = io.ReadInt32();
|
||||||
|
Deletions = new List<string>();
|
||||||
|
for (int i = 0; i < delCount; i++)
|
||||||
|
{
|
||||||
|
Deletions.Add(io.ReadVariableLengthPascalString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecursiveDirectoryScan(string folder, HashSet<string> fileNames, string basePath)
|
||||||
|
{
|
||||||
|
var files = Directory.GetFiles(folder);
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
fileNames.Add(GetRelativePath(basePath, file));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirs = Directory.GetDirectories(folder);
|
||||||
|
foreach (var dir in dirs)
|
||||||
|
{
|
||||||
|
RecursiveDirectoryScan(dir, fileNames, basePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetRelativePath(string relativeTo, string path)
|
||||||
|
{
|
||||||
|
if (relativeTo.EndsWith("/") || relativeTo.EndsWith("\\")) relativeTo += "/";
|
||||||
|
var uri = new Uri(relativeTo);
|
||||||
|
var rel = Uri.UnescapeDataString(uri.MakeRelativeUri(new Uri(path)).ToString()).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||||
|
if (rel.Contains(Path.DirectorySeparatorChar.ToString()) == false)
|
||||||
|
{
|
||||||
|
rel = $".{ Path.DirectorySeparatorChar }{ rel }";
|
||||||
|
}
|
||||||
|
return rel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply(string source, string dest, Action<string, float> progress)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (source != dest)
|
||||||
|
{
|
||||||
|
progress("Copying Unchanged Files...", 0);
|
||||||
|
//if our destination folder is different,
|
||||||
|
//copy unchanged files first.
|
||||||
|
var sourceFiles = new HashSet<string>();
|
||||||
|
RecursiveDirectoryScan(source, sourceFiles, source);
|
||||||
|
|
||||||
|
sourceFiles.ExceptWith(new HashSet<string>(Patches.Select(x => x.FileTarget)));
|
||||||
|
sourceFiles.ExceptWith(new HashSet<string>(Deletions));
|
||||||
|
|
||||||
|
foreach (var file in sourceFiles)
|
||||||
|
{
|
||||||
|
var destP = Path.Combine(dest, file);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(destP));
|
||||||
|
|
||||||
|
File.Copy(Path.Combine(source, file), destP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var reader = new BinaryReader(Str);
|
||||||
|
int total = Patches.Count + Additions.Count + Deletions.Count;
|
||||||
|
int fileNum = 0;
|
||||||
|
foreach (var patch in Patches)
|
||||||
|
{
|
||||||
|
progress($"Patching {patch.FileTarget}...", fileNum / (float)total);
|
||||||
|
var path = Path.Combine(source, patch.FileTarget);
|
||||||
|
var dpath = Path.Combine(dest, patch.FileTarget);
|
||||||
|
var data = File.ReadAllBytes(path);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(dpath));
|
||||||
|
|
||||||
|
Str.Seek(patch.Offset, SeekOrigin.Begin);
|
||||||
|
var patchd = reader.ReadBytes(patch.Length);
|
||||||
|
BsPatch.Apply(data, patchd, File.Open(dpath, FileMode.Create, FileAccess.Write, FileShare.None));
|
||||||
|
fileNum++;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var add in Additions)
|
||||||
|
{
|
||||||
|
progress($"Adding {add.FileTarget}...", fileNum / (float)total);
|
||||||
|
var dpath = Path.Combine(dest, add.FileTarget);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(dpath));
|
||||||
|
|
||||||
|
Str.Seek(add.Offset, SeekOrigin.Begin);
|
||||||
|
var addData = reader.ReadBytes(add.Length);
|
||||||
|
File.WriteAllBytes(dpath, addData);
|
||||||
|
fileNum++;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var del in Deletions)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
progress($"Deleting {del}...", fileNum / (float)total);
|
||||||
|
File.Delete(Path.Combine(dest, del));
|
||||||
|
fileNum++;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//file not found. not important - we wanted it deleted anyways.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
progress(e.ToString(), -1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileEntry
|
||||||
|
{
|
||||||
|
public string FileTarget;
|
||||||
|
public long Offset;
|
||||||
|
public int Length;
|
||||||
|
}
|
||||||
|
}
|
96
server/tso.files/HIT/EVT.cs
Executable file
96
server/tso.files/HIT/EVT.cs
Executable file
|
@ -0,0 +1,96 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.HIT
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// EVT is a CSV format that defines a list of events for HIT to listen for.
|
||||||
|
/// </summary>
|
||||||
|
public class EVT
|
||||||
|
{
|
||||||
|
public List<EVTEntry> Entries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new evt file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filedata">The data to create the evt from.</param>
|
||||||
|
public EVT(byte[] Filedata)
|
||||||
|
{
|
||||||
|
ReadFile(new MemoryStream(Filedata));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new evt file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filedata">The path to the data to create the evt from.</param>
|
||||||
|
public EVT(string Filepath)
|
||||||
|
{
|
||||||
|
ReadFile(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadFile(Stream data)
|
||||||
|
{
|
||||||
|
Entries = new List<EVTEntry>();
|
||||||
|
BinaryReader Reader = new BinaryReader(data);
|
||||||
|
|
||||||
|
string CommaSeparatedValues = new string(Reader.ReadChars((int)data.Length));
|
||||||
|
string[] Lines = CommaSeparatedValues.Split(new string[] { "\r\n" }, StringSplitOptions.None);
|
||||||
|
|
||||||
|
for (int i = 0; i < Lines.Length; i++)
|
||||||
|
{
|
||||||
|
if (Lines[i] == "") continue;
|
||||||
|
string[] Values = Lines[i].Split(',');
|
||||||
|
|
||||||
|
var Entry = new EVTEntry();
|
||||||
|
Entry.Name = Values[0].ToLowerInvariant();
|
||||||
|
Entry.EventType = ParseHexString(Values[1]);
|
||||||
|
Entry.TrackID = ParseHexString(Values[2]);
|
||||||
|
Entry.Unknown = ParseHexString(Values[3]);
|
||||||
|
Entry.Unknown2 = ParseHexString(Values[4]);
|
||||||
|
Entry.Unknown3 = ParseHexString(Values[5]);
|
||||||
|
Entry.Unknown4 = ParseHexString(Values[6]);
|
||||||
|
Entries.Add(Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
Reader.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint ParseHexString(string input)
|
||||||
|
{
|
||||||
|
bool IsHex = false;
|
||||||
|
input = input.ToLowerInvariant();
|
||||||
|
|
||||||
|
if (input == "") return 0;
|
||||||
|
if (input.StartsWith("0x"))
|
||||||
|
{
|
||||||
|
input = input.Substring(2);
|
||||||
|
IsHex = true;
|
||||||
|
}
|
||||||
|
else if (input.Contains("a") || input.Contains("b") || input.Contains("c") || input.Contains("d") || input.Contains("e") || input.Contains("f"))
|
||||||
|
{
|
||||||
|
IsHex = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsHex)
|
||||||
|
{
|
||||||
|
return Convert.ToUInt32(input, 16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Convert.ToUInt32(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EVTEntry
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public uint EventType;
|
||||||
|
public uint TrackID;
|
||||||
|
public uint Unknown;
|
||||||
|
public uint Unknown2;
|
||||||
|
public uint Unknown3;
|
||||||
|
public uint Unknown4;
|
||||||
|
}
|
||||||
|
}
|
153
server/tso.files/HIT/FSC.cs
Executable file
153
server/tso.files/HIT/FSC.cs
Executable file
|
@ -0,0 +1,153 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.HIT
|
||||||
|
{
|
||||||
|
public class FSC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// FSC is a tabulated plaintext format that describes a sequence of notes to be played. In this game it is used to sequence the ambient sounds.
|
||||||
|
/// The conditions in which the sequence is randomized are not entirely apparent, and have been mostly guessed.
|
||||||
|
/// </summary>
|
||||||
|
///
|
||||||
|
|
||||||
|
public List<FSCNote> Notes;
|
||||||
|
|
||||||
|
public string VersionCode;
|
||||||
|
|
||||||
|
public ushort MasterVolume;
|
||||||
|
public ushort Priority;
|
||||||
|
public ushort Min;
|
||||||
|
public ushort Max;
|
||||||
|
public ushort Rows; //these seem to be outright lies, but let's leave them in
|
||||||
|
public ushort Columns;
|
||||||
|
public ushort Tempo;
|
||||||
|
public ushort BPB; //beats per bar
|
||||||
|
public ushort SelX;
|
||||||
|
public ushort SelY;
|
||||||
|
public ushort QuanX;
|
||||||
|
public ushort QuanY;
|
||||||
|
public ushort DiffX;
|
||||||
|
public ushort DiffY;
|
||||||
|
|
||||||
|
public List<int> RandomJumpPoints;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new hsm file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filedata">The data to create the hsm from.</param>
|
||||||
|
public FSC(byte[] Filedata)
|
||||||
|
{
|
||||||
|
ReadFile(new MemoryStream(Filedata));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new hsm file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filedata">The path to the data to create the hsm from.</param>
|
||||||
|
public FSC(string Filepath)
|
||||||
|
{
|
||||||
|
ReadFile(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadFile(Stream stream)
|
||||||
|
{
|
||||||
|
var io = new StreamReader(stream);
|
||||||
|
|
||||||
|
Notes = new List<FSCNote>();
|
||||||
|
RandomJumpPoints = new List<int>();
|
||||||
|
VersionCode = io.ReadLine();
|
||||||
|
var line = io.ReadLine();
|
||||||
|
|
||||||
|
while (line.StartsWith("#"))
|
||||||
|
line = io.ReadLine();
|
||||||
|
|
||||||
|
//Header
|
||||||
|
string[] Head = line.Split('\t');
|
||||||
|
MasterVolume = Convert.ToUInt16(Head[1]);
|
||||||
|
Priority = Convert.ToUInt16(Head[2]);
|
||||||
|
Min = Convert.ToUInt16(Head[3]);
|
||||||
|
Max = Convert.ToUInt16(Head[4]);
|
||||||
|
Rows = Convert.ToUInt16(Head[5]);
|
||||||
|
Columns = Convert.ToUInt16(Head[6]);
|
||||||
|
Tempo = Convert.ToUInt16(Head[7]);
|
||||||
|
BPB = Convert.ToUInt16(Head[8]);
|
||||||
|
SelX = Convert.ToUInt16(Head[9]);
|
||||||
|
SelY = Convert.ToUInt16(Head[10]);
|
||||||
|
if(Head[11][0] != '-') QuanX = Convert.ToUInt16(Head[11]);
|
||||||
|
if (Head[12][0] != '-') QuanY = Convert.ToUInt16(Head[12]);
|
||||||
|
DiffX = Convert.ToUInt16(Head[13]);
|
||||||
|
DiffY = Convert.ToUInt16(Head[14]);
|
||||||
|
|
||||||
|
line = io.ReadLine();
|
||||||
|
|
||||||
|
while (line.StartsWith("#") || line.StartsWith("cells"))
|
||||||
|
line = io.ReadLine();
|
||||||
|
|
||||||
|
while (!io.EndOfStream) //read notes
|
||||||
|
{
|
||||||
|
string line2 = io.ReadLine();
|
||||||
|
string[] Values = line2.Split('\t');
|
||||||
|
if (!line.StartsWith("#") && Values.Length == 20)
|
||||||
|
{
|
||||||
|
var note = new FSCNote()
|
||||||
|
{
|
||||||
|
Volume = Convert.ToUInt16(Values[1]),
|
||||||
|
Rand = Values[2] != "0",
|
||||||
|
LRPan = Convert.ToUInt16(Values[3]),
|
||||||
|
FBPan = Convert.ToUInt16(Values[4]),
|
||||||
|
Rand2 = Values[5] != "0",
|
||||||
|
|
||||||
|
Fin = Convert.ToUInt16(Values[6]),
|
||||||
|
FOut = Convert.ToUInt16(Values[7]),
|
||||||
|
dly = Convert.ToUInt16(Values[8]),
|
||||||
|
Rand3 = Values[9] != "0",
|
||||||
|
Loop = Convert.ToUInt16(Values[10]),
|
||||||
|
|
||||||
|
Loop2 = Values[11] != "0",
|
||||||
|
Quant = Convert.ToUInt16(Values[12]),
|
||||||
|
Prob = Convert.ToUInt16(Values[13]),
|
||||||
|
pitchL = Convert.ToInt16(Values[14]),
|
||||||
|
pitchR = Convert.ToInt16(Values[15]),
|
||||||
|
|
||||||
|
Fast = Values[16] != "0",
|
||||||
|
GroupID = Convert.ToUInt16(Values[17]),
|
||||||
|
Stereo = Values[18] != "0",
|
||||||
|
Filename = Values[19]
|
||||||
|
};
|
||||||
|
if (note.Rand) RandomJumpPoints.Add(Notes.Count);
|
||||||
|
Notes.Add(note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
io.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct FSCNote
|
||||||
|
{
|
||||||
|
public ushort Volume; //0-1024
|
||||||
|
public bool Rand;
|
||||||
|
public ushort LRPan; //0-1024
|
||||||
|
public ushort FBPan; //0-1024, front back
|
||||||
|
public bool Rand2;
|
||||||
|
|
||||||
|
public ushort Fin;
|
||||||
|
public ushort FOut;
|
||||||
|
public ushort dly;
|
||||||
|
public bool Rand3; //what
|
||||||
|
public ushort Loop;
|
||||||
|
|
||||||
|
public bool Loop2; //might be count then decider here
|
||||||
|
public ushort Quant; //but then what is this?
|
||||||
|
public ushort Prob; //probably random probability, not sure of range (0-16?)
|
||||||
|
public short pitchL; //pitch offsets
|
||||||
|
public short pitchR;
|
||||||
|
|
||||||
|
public bool Fast;
|
||||||
|
public ushort GroupID;
|
||||||
|
public bool Stereo;
|
||||||
|
public string Filename;
|
||||||
|
}
|
||||||
|
}
|
72
server/tso.files/HIT/HITConstants.cs
Executable file
72
server/tso.files/HIT/HITConstants.cs
Executable file
|
@ -0,0 +1,72 @@
|
||||||
|
namespace FSO.Files.HIT
|
||||||
|
{
|
||||||
|
public enum HITArgs
|
||||||
|
{
|
||||||
|
kArgsNormal = 0,
|
||||||
|
kArgsVolPan = 1,
|
||||||
|
kArgsIdVolPan = 2,
|
||||||
|
kArgsXYZ = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HITControlGroups
|
||||||
|
{
|
||||||
|
kGroupSFX = 1,
|
||||||
|
kGroupMusic = 2,
|
||||||
|
kGroupVox = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HITDuckingPriorities
|
||||||
|
{
|
||||||
|
duckpri_unknown1 = 32,
|
||||||
|
duckpri_unknown2 = 5000,
|
||||||
|
duckpri_always = 0x0,
|
||||||
|
duckpri_low = 0x1,
|
||||||
|
duckpri_normal = 0x14,
|
||||||
|
duckpri_high = 0x1e,
|
||||||
|
duckpri_higher = 0x28,
|
||||||
|
duckpri_evenhigher = 0x32,
|
||||||
|
duckpri_never = 0x64
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HITEvents
|
||||||
|
{
|
||||||
|
kSoundobPlay = 1,
|
||||||
|
kSoundobStop = 2,
|
||||||
|
kSoundobKill = 3,
|
||||||
|
kSoundobUpdate = 4,
|
||||||
|
kSoundobSetVolume = 5,
|
||||||
|
kSoundobSetPitch = 6,
|
||||||
|
kSoundobSetPan = 7,
|
||||||
|
kSoundobSetPosition = 8,
|
||||||
|
kSoundobSetFxType = 9,
|
||||||
|
kSoundobSetFxLevel = 10,
|
||||||
|
kSoundobPause = 11,
|
||||||
|
kSoundobUnpause = 12,
|
||||||
|
kSoundobLoad = 13,
|
||||||
|
kSoundobUnload = 14,
|
||||||
|
kSoundobCache = 15,
|
||||||
|
kSoundobUncache = 16,
|
||||||
|
kSoundobCancelNote = 19,
|
||||||
|
kKillAll = 20,
|
||||||
|
kPause = 21,
|
||||||
|
kUnpause = 22,
|
||||||
|
kKillInstance = 23,
|
||||||
|
kTurnOnTV = 30,
|
||||||
|
kTurnOffTV = 31,
|
||||||
|
kUpdateSourceVolPan = 32,
|
||||||
|
kSetMusicMode = 36,
|
||||||
|
kPlayPiano = 43,
|
||||||
|
debugeventson = 44,
|
||||||
|
debugeventsoff = 45,
|
||||||
|
debugsampleson = 46,
|
||||||
|
debugsamplesoff = 47,
|
||||||
|
debugtrackson = 48,
|
||||||
|
debugtracksoff = 49
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HITPerson
|
||||||
|
{
|
||||||
|
Instance = 0x0,
|
||||||
|
Gender = 0x1
|
||||||
|
}
|
||||||
|
}
|
101
server/tso.files/HIT/HITFile.cs
Executable file
101
server/tso.files/HIT/HITFile.cs
Executable file
|
@ -0,0 +1,101 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.HIT
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HIT files contain the bytecode to be executed when plaing track events.
|
||||||
|
/// </summary>
|
||||||
|
public class HITFile
|
||||||
|
{
|
||||||
|
public string MagicNumber;
|
||||||
|
public uint MajorVersion;
|
||||||
|
public uint MinorVersion;
|
||||||
|
public byte[] Data;
|
||||||
|
public Dictionary<uint, uint> EntryPointByTrackID;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new track.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filedata">The data to create the track from.</param>
|
||||||
|
public HITFile(byte[] Filedata)
|
||||||
|
{
|
||||||
|
ReadFile(new MemoryStream(Filedata));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new track.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filedata">The path to the data to create the track from.</param>
|
||||||
|
public HITFile(string Filepath)
|
||||||
|
{
|
||||||
|
ReadFile(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadFile(Stream data)
|
||||||
|
{
|
||||||
|
BinaryReader Reader = new BinaryReader(data);
|
||||||
|
|
||||||
|
MagicNumber = new string(Reader.ReadChars(4));
|
||||||
|
MajorVersion = Reader.ReadUInt32();
|
||||||
|
MinorVersion = Reader.ReadUInt32();
|
||||||
|
var signature = new string(Reader.ReadChars(4));
|
||||||
|
|
||||||
|
var tableLoc = FindBytePattern(Reader.BaseStream, new byte[] { (byte)'E', (byte)'N', (byte)'T', (byte)'P' });
|
||||||
|
if (tableLoc != -1)
|
||||||
|
{
|
||||||
|
Reader.BaseStream.Seek(tableLoc, SeekOrigin.Begin);
|
||||||
|
EntryPointByTrackID = new Dictionary<uint, uint>();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
|
||||||
|
var EndTest = ASCIIEncoding.ASCII.GetString(Reader.ReadBytes(4)); //can be invalid chars
|
||||||
|
if (EndTest.Equals("EENT", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Reader.BaseStream.Position -= 4; //go back to read it as a table entry
|
||||||
|
var track = Reader.ReadUInt32();
|
||||||
|
var address = Reader.ReadUInt32();
|
||||||
|
EntryPointByTrackID.Add(track, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Reader.BaseStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
this.Data = Reader.ReadBytes((int)Reader.BaseStream.Length);
|
||||||
|
|
||||||
|
Reader.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int FindBytePattern(Stream stream, byte[] pattern)
|
||||||
|
{ //a simple pattern matcher
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < stream.Length; i++)
|
||||||
|
{
|
||||||
|
var b = stream.ReadByte();
|
||||||
|
if (b == pattern[0])
|
||||||
|
{
|
||||||
|
bool match = true;
|
||||||
|
for (int j = 1; j < pattern.Length; j++)
|
||||||
|
{
|
||||||
|
var b2 = stream.ReadByte();
|
||||||
|
if (b2 != pattern[j])
|
||||||
|
{
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match) return (int)stream.Position;
|
||||||
|
else stream.Seek(i+1, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1; //no match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
server/tso.files/HIT/HSM.cs
Executable file
50
server/tso.files/HIT/HSM.cs
Executable file
|
@ -0,0 +1,50 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.HIT
|
||||||
|
{
|
||||||
|
public class HSM
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HSM is a plaintext format that names various HIT constants including subroutine locations.
|
||||||
|
/// </summary>
|
||||||
|
///
|
||||||
|
public Dictionary<string, int> Constants;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new hsm file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filedata">The data to create the hsm from.</param>
|
||||||
|
public HSM(byte[] Filedata)
|
||||||
|
{
|
||||||
|
ReadFile(new MemoryStream(Filedata));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new hsm file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filedata">The path to the data to create the hsm from.</param>
|
||||||
|
public HSM(string Filepath)
|
||||||
|
{
|
||||||
|
ReadFile(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadFile(Stream stream)
|
||||||
|
{
|
||||||
|
var io = new StreamReader(stream);
|
||||||
|
Constants = new Dictionary<string, int>();
|
||||||
|
|
||||||
|
while (!io.EndOfStream)
|
||||||
|
{
|
||||||
|
string line = io.ReadLine();
|
||||||
|
string[] Values = line.Split(' ');
|
||||||
|
|
||||||
|
var name = Values[0].ToLowerInvariant();
|
||||||
|
if (!Constants.ContainsKey(name)) Constants.Add(name, Convert.ToInt32(Values[1])); //the repeats are just labels for locations (usually called gotit)
|
||||||
|
}
|
||||||
|
|
||||||
|
io.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
server/tso.files/HIT/Hitlist.cs
Executable file
103
server/tso.files/HIT/Hitlist.cs
Executable file
|
@ -0,0 +1,103 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.HIT
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HLS refers to two binary formats that both define a list of IDs, known as a hitlist.
|
||||||
|
/// One format is a Pascal string with a 4-byte, little-endian length, representing a
|
||||||
|
/// comma-seperated list of decimal values, or decimal ranges (e.g. "1025-1035"), succeeded
|
||||||
|
/// by a single LF newline.
|
||||||
|
/// </summary>
|
||||||
|
public class Hitlist
|
||||||
|
{
|
||||||
|
private uint m_IDCount;
|
||||||
|
public List<uint> IDs; //variable length so it's easier to fill with ranges
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new hitlist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filedata">The data to create the hitlist from.</param>
|
||||||
|
public Hitlist(byte[] Filedata)
|
||||||
|
{
|
||||||
|
Read(new MemoryStream(Filedata));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Read(Stream data)
|
||||||
|
{
|
||||||
|
BinaryReader Reader = new BinaryReader(data);
|
||||||
|
|
||||||
|
IDs = new List<uint>();
|
||||||
|
var VerOrCount = Reader.ReadUInt32();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (VerOrCount == 1) //binary format, no hitlist is ever going to have length 1... (i hope)
|
||||||
|
{
|
||||||
|
m_IDCount = Reader.ReadUInt32();
|
||||||
|
|
||||||
|
for (int i = 0; i < m_IDCount; i++)
|
||||||
|
IDs.Add(Reader.ReadUInt32());
|
||||||
|
|
||||||
|
Reader.Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var str = new string(Reader.ReadChars((int)VerOrCount));
|
||||||
|
Populate(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Reader.BaseStream.Seek(4, SeekOrigin.Begin); //attempt 3rd mystery format, count+int32
|
||||||
|
for (int i = 0; i < VerOrCount; i++)
|
||||||
|
IDs.Add(Reader.ReadUInt32());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Hitlist()
|
||||||
|
{
|
||||||
|
IDs = new List<uint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Populate(string str)
|
||||||
|
{
|
||||||
|
var commaSplit = str.Split(',');
|
||||||
|
for (int i = 0; i < commaSplit.Length; i++)
|
||||||
|
{
|
||||||
|
var dashSplit = commaSplit[i].Split('-');
|
||||||
|
if (dashSplit.Length > 1)
|
||||||
|
{ //range, parse two values and fill in the gap
|
||||||
|
var min = Convert.ToUInt32(dashSplit[0]);
|
||||||
|
var max = Convert.ToUInt32(dashSplit[1]);
|
||||||
|
for (uint j = min; j <= max; j++)
|
||||||
|
{
|
||||||
|
IDs.Add(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ //literal entry, add to list
|
||||||
|
IDs.Add(Convert.ToUInt32(commaSplit[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Hitlist HitlistFromString(string str)
|
||||||
|
{
|
||||||
|
var result = new Hitlist();
|
||||||
|
result.Populate(str);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new hitlist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filepath">The path to the hitlist to read.</param>
|
||||||
|
public Hitlist(string Filepath)
|
||||||
|
{
|
||||||
|
Read(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
236
server/tso.files/HIT/Hot.cs
Executable file
236
server/tso.files/HIT/Hot.cs
Executable file
|
@ -0,0 +1,236 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.HIT
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration constants.
|
||||||
|
/// </summary>
|
||||||
|
public struct EventMappingEquate
|
||||||
|
{
|
||||||
|
public string Label;
|
||||||
|
public int Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This section assigns a sound ID to a number of subroutines
|
||||||
|
/// (exported or not) in the corresponding HIT file.
|
||||||
|
/// </summary>
|
||||||
|
public struct TrackData
|
||||||
|
{
|
||||||
|
//The syntax for TrackData is A = B, where A is the sound's File ID
|
||||||
|
//and B is the offset to the subroutine in the accompanying HIT file.
|
||||||
|
public long FileID;
|
||||||
|
public int SubRoutineOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HOT (short for HIT Options Table) is an ini format that defines
|
||||||
|
/// enumeration constants and track data for HIT binary files.
|
||||||
|
/// </summary>
|
||||||
|
public class Hot
|
||||||
|
{
|
||||||
|
private int m_Version;
|
||||||
|
private int m_LoadPriority;
|
||||||
|
private List<EventMappingEquate> m_EventMappingEquations = new List<EventMappingEquate>();
|
||||||
|
private List<TrackData> m_TrackDataList = new List<TrackData>();
|
||||||
|
public Dictionary<uint, Track> Tracks = new Dictionary<uint, Track>();
|
||||||
|
public Dictionary<uint, Patch> Patches = new Dictionary<uint, Patch>();
|
||||||
|
public Dictionary<uint, Hitlist> Hitlists = new Dictionary<uint, Hitlist>();
|
||||||
|
public Dictionary<uint, uint> TrackData = new Dictionary<uint, uint>();
|
||||||
|
public Dictionary<string, EVTEntry> Events = new Dictionary<string, EVTEntry>();
|
||||||
|
private Dictionary<string, int> EventMappingEquate = new Dictionary<string, int>();
|
||||||
|
public HSM AsmNames;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets this Hot instance's list of EventMappingEquate instances.
|
||||||
|
/// </summary>
|
||||||
|
public List<EventMappingEquate> EventMappingEquations
|
||||||
|
{
|
||||||
|
get { return m_EventMappingEquations; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets this Hot instance's list of TrackData instances.
|
||||||
|
/// </summary>
|
||||||
|
public List<TrackData> TrackDataList
|
||||||
|
{
|
||||||
|
get { return m_TrackDataList; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Hot(byte[] FileData)
|
||||||
|
{
|
||||||
|
LoadFrom(FileData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadFrom(byte[] FileData)
|
||||||
|
{
|
||||||
|
StreamReader Reader = new StreamReader(new MemoryStream(FileData));
|
||||||
|
HotReadMode ActiveState = HotReadMode.None;
|
||||||
|
|
||||||
|
while (!Reader.EndOfStream)
|
||||||
|
{
|
||||||
|
string CurrentLine = Reader.ReadLine().Trim().Replace("\r\n", "");
|
||||||
|
|
||||||
|
switch (CurrentLine)
|
||||||
|
{
|
||||||
|
case "[EventMappingEquate]":
|
||||||
|
ActiveState = HotReadMode.EventMappingEquate;
|
||||||
|
break;
|
||||||
|
case "[Options]":
|
||||||
|
ActiveState = HotReadMode.Options;
|
||||||
|
break;
|
||||||
|
case "[TrackData]":
|
||||||
|
ActiveState = HotReadMode.TrackData;
|
||||||
|
break;
|
||||||
|
case "[EventMapping]":
|
||||||
|
//equivalent to .evt file
|
||||||
|
//(name)=(eventMappingEquate as event type),(trackid),0,0,0,0
|
||||||
|
ActiveState = HotReadMode.EventMapping;
|
||||||
|
break;
|
||||||
|
case "[Track]":
|
||||||
|
//equivalent to a lot of track files
|
||||||
|
//(trackid)=0,(subroutine),(volume),(arguments),(duckingPriority),(controlGroup),(soundPressureLevel),@(hitlistID),(patchID)
|
||||||
|
//patch id is usually 0, in favor of single item hitlists
|
||||||
|
ActiveState = HotReadMode.Track;
|
||||||
|
break;
|
||||||
|
case "[Patch]":
|
||||||
|
//(patchid)=(name),(filenameInQuotes),(looped),(piano),0,0,0
|
||||||
|
ActiveState = HotReadMode.Patch;
|
||||||
|
break;
|
||||||
|
case "[GlobalHitList]":
|
||||||
|
//(hitlistid)=(hitlistString)
|
||||||
|
//note: many hitlists contain just one patch
|
||||||
|
ActiveState = HotReadMode.GlobalHitList;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CurrentLine.Contains("["))
|
||||||
|
{
|
||||||
|
if (!CurrentLine.Contains("]"))
|
||||||
|
{
|
||||||
|
if (CurrentLine != "")
|
||||||
|
{
|
||||||
|
string[] Params = CurrentLine.Split("=".ToCharArray());
|
||||||
|
//EventMappingEquate fields look like: kSndobPlay=1
|
||||||
|
switch (ActiveState)
|
||||||
|
{
|
||||||
|
case HotReadMode.EventMappingEquate:
|
||||||
|
EventMappingEquate EMappingEquate = new EventMappingEquate();
|
||||||
|
EMappingEquate.Label = Params[0];
|
||||||
|
EMappingEquate.Value = int.Parse(Params[1]);
|
||||||
|
EventMappingEquate[EMappingEquate.Label] = EMappingEquate.Value;
|
||||||
|
break;
|
||||||
|
//Options fields look like: Version=1
|
||||||
|
case HotReadMode.Options:
|
||||||
|
switch (Params[0])
|
||||||
|
{
|
||||||
|
case "Version":
|
||||||
|
m_Version = int.Parse(Params[1]);
|
||||||
|
break;
|
||||||
|
case "LoadPriority":
|
||||||
|
m_LoadPriority = int.Parse(Params[1]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
//TrackData fields look like: 0xb0f4=0x10
|
||||||
|
case HotReadMode.TrackData:
|
||||||
|
TrackData.Add(Convert.ToUInt32(Params[0], 16), Convert.ToUInt32(Params[1], 16));
|
||||||
|
break;
|
||||||
|
case HotReadMode.EventMapping:
|
||||||
|
var commaSplit = Params[1].Split(',');
|
||||||
|
Events[Params[0].ToLowerInvariant()] = new EVTEntry
|
||||||
|
{
|
||||||
|
Name = Params[0].ToLowerInvariant(),
|
||||||
|
EventType = (uint)ParseEME(commaSplit[0]),
|
||||||
|
TrackID = (commaSplit.Length>1)?(uint)ParseEME(commaSplit[1]):0
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case HotReadMode.Track:
|
||||||
|
var tid = uint.Parse(Params[0]);
|
||||||
|
var tcSplit = Params[1].Split(',');
|
||||||
|
|
||||||
|
var trk = new Track()
|
||||||
|
{
|
||||||
|
SubroutineID = 0,//(uint)HSMConst(tcSplit[1]),
|
||||||
|
Volume = (uint)ParseEME(tcSplit[2]),
|
||||||
|
ArgType = (HITArgs)ParseEME(tcSplit[3]),
|
||||||
|
DuckingPriority = (HITDuckingPriorities)ParseEME(tcSplit[4]),
|
||||||
|
ControlGroup = (HITControlGroups)ParseEME(tcSplit[5]),
|
||||||
|
HitlistID = (uint)HSMConst(tcSplit[7].Substring(1)), //cut out @
|
||||||
|
SoundID = (uint)ParseEME(tcSplit[8])
|
||||||
|
};
|
||||||
|
|
||||||
|
if (trk.HitlistID != 0 && TrackData.ContainsKey(trk.HitlistID)) trk.SubroutineID = TrackData[trk.HitlistID];
|
||||||
|
if (trk.SoundID != 0 && TrackData.ContainsKey(trk.SoundID)) trk.SubroutineID = TrackData[trk.SoundID];
|
||||||
|
|
||||||
|
Tracks[tid] = trk;
|
||||||
|
break;
|
||||||
|
case HotReadMode.Patch:
|
||||||
|
var pid = uint.Parse(Params[0]);
|
||||||
|
var patch = new Patch(Params[1]);
|
||||||
|
Patches[pid] = patch;
|
||||||
|
break;
|
||||||
|
case HotReadMode.GlobalHitList:
|
||||||
|
var hid = uint.Parse(Params[0]);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var hitlist = Hitlist.HitlistFromString(Params[1]);
|
||||||
|
Hitlists[hid] = hitlist;
|
||||||
|
} catch (Exception)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* todo: saxophone seems to reference an hsm.
|
||||||
|
* 20016=sulsaxj_way_aa
|
||||||
|
* 20017=sulsaxk_way_solo
|
||||||
|
* 20018=sulsaxl_way_fin
|
||||||
|
* these labels are in the hsm but they have a different case...
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int HSMConst(string input)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
AsmNames?.Constants?.TryGetValue(input, out result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ParseEME(string eme)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
if (int.TryParse(eme, out result)) return result;
|
||||||
|
EventMappingEquate.TryGetValue(eme, out result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Hot(string Filepath) : this(File.ReadAllBytes(Filepath))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Hot(string Filepath, HSM myAsm)
|
||||||
|
{
|
||||||
|
AsmNames = myAsm;
|
||||||
|
LoadFrom(File.ReadAllBytes(Filepath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HotReadMode
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
EventMappingEquate,
|
||||||
|
Options,
|
||||||
|
TrackData,
|
||||||
|
EventMapping,
|
||||||
|
Track,
|
||||||
|
Patch,
|
||||||
|
GlobalHitList
|
||||||
|
}
|
||||||
|
}
|
28
server/tso.files/HIT/Patch.cs
Executable file
28
server/tso.files/HIT/Patch.cs
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
namespace FSO.Files.HIT
|
||||||
|
{
|
||||||
|
public class Patch
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public string Filename;
|
||||||
|
public bool Looped;
|
||||||
|
public bool Piano;
|
||||||
|
|
||||||
|
public uint FileID; //patches are stubbed out in TSO.
|
||||||
|
public bool TSO;
|
||||||
|
|
||||||
|
public Patch(uint id)
|
||||||
|
{
|
||||||
|
FileID = id;
|
||||||
|
TSO = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Patch(string patchString)
|
||||||
|
{
|
||||||
|
var elems = patchString.Split(',');
|
||||||
|
if (elems.Length > 1) Name = elems[1];
|
||||||
|
if (elems.Length > 2) Filename = elems[2].Substring(1, elems[2].Length-2).Replace('\\', '/');
|
||||||
|
if (elems.Length > 3) Looped = elems[3] != "0";
|
||||||
|
if (elems.Length > 4) Piano = elems[4] != "0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
server/tso.files/HIT/TLO.cs
Executable file
91
server/tso.files/HIT/TLO.cs
Executable file
|
@ -0,0 +1,91 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
//ok, why do we have support for this? it's only used by hitlab and only the hitlab test files have them...
|
||||||
|
|
||||||
|
namespace FSO.Files.HIT
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a TLO file.
|
||||||
|
/// TLO (short for Track Logic according to hitlab.ini) is a format
|
||||||
|
/// used solely for Hitlab. Integers are little-endian.
|
||||||
|
/// </summary>
|
||||||
|
public class TLO
|
||||||
|
{
|
||||||
|
private uint m_Count;
|
||||||
|
public List<TLOSection> Sections = new List<TLOSection>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new tracklogic instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filedata">The data to create the tracklogic instance from.</param>
|
||||||
|
public TLO(byte[] Filedata)
|
||||||
|
{
|
||||||
|
BinaryReader Reader = new BinaryReader(new MemoryStream(Filedata));
|
||||||
|
|
||||||
|
Reader.ReadBytes(4); //Reserved.
|
||||||
|
m_Count = Reader.ReadUInt32();
|
||||||
|
|
||||||
|
for (int i = 0; i < m_Count; i++)
|
||||||
|
{
|
||||||
|
TLOSection Section = new TLOSection();
|
||||||
|
Section.Name = new string(Reader.ReadChars(Reader.ReadInt32()));
|
||||||
|
Section.GroupID1 = Reader.ReadUInt32();
|
||||||
|
Section.FileID1 = Reader.ReadUInt32();
|
||||||
|
Section.GroupID2 = Reader.ReadUInt32();
|
||||||
|
Section.FileID1 = Reader.ReadUInt32();
|
||||||
|
Section.TypeID = Reader.ReadUInt32();
|
||||||
|
Section.GroupID3 = Reader.ReadUInt32();
|
||||||
|
Section.FileID3 = Reader.ReadUInt32();
|
||||||
|
|
||||||
|
Sections.Add(Section);
|
||||||
|
}
|
||||||
|
|
||||||
|
Reader.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new tracklogic instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filepath">The path to the tracklogic file to read.</param>
|
||||||
|
public TLO(string Filepath)
|
||||||
|
{
|
||||||
|
BinaryReader Reader = new BinaryReader(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||||
|
|
||||||
|
Reader.ReadBytes(4); //Reserved.
|
||||||
|
m_Count = Reader.ReadUInt32();
|
||||||
|
|
||||||
|
for (int i = 0; i < m_Count; i++)
|
||||||
|
{
|
||||||
|
TLOSection Section = new TLOSection();
|
||||||
|
Section.Name = new string(Reader.ReadChars(Reader.ReadInt32()));
|
||||||
|
Section.GroupID1 = Reader.ReadUInt32();
|
||||||
|
Section.FileID1 = Reader.ReadUInt32();
|
||||||
|
Section.GroupID2 = Reader.ReadUInt32();
|
||||||
|
Section.FileID1 = Reader.ReadUInt32();
|
||||||
|
Section.TypeID = Reader.ReadUInt32();
|
||||||
|
Section.GroupID3 = Reader.ReadUInt32();
|
||||||
|
Section.FileID3 = Reader.ReadUInt32();
|
||||||
|
|
||||||
|
Sections.Add(Section);
|
||||||
|
}
|
||||||
|
|
||||||
|
Reader.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a section in a tracklogic file.
|
||||||
|
/// </summary>
|
||||||
|
public class TLOSection
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public uint GroupID1;
|
||||||
|
public uint FileID1;
|
||||||
|
public uint GroupID2;
|
||||||
|
public uint FileID2;
|
||||||
|
public uint TypeID;
|
||||||
|
public uint GroupID3;
|
||||||
|
public uint FileID3;
|
||||||
|
}
|
||||||
|
}
|
111
server/tso.files/HIT/Track.cs
Executable file
111
server/tso.files/HIT/Track.cs
Executable file
|
@ -0,0 +1,111 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.HIT
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// TRK is a CSV format that defines a HIT track.
|
||||||
|
/// </summary>
|
||||||
|
public class Track
|
||||||
|
{
|
||||||
|
private bool TWODKT = false; //Optional encoding as Pascal string, typical Maxis...
|
||||||
|
public string MagicNumber;
|
||||||
|
public uint Version;
|
||||||
|
public string TrackName;
|
||||||
|
public uint SoundID;
|
||||||
|
public uint TrackID;
|
||||||
|
public HITArgs ArgType;
|
||||||
|
public HITControlGroups ControlGroup;
|
||||||
|
public HITDuckingPriorities DuckingPriority;
|
||||||
|
public uint Looped;
|
||||||
|
public uint Volume;
|
||||||
|
|
||||||
|
public bool LoopDefined = false;
|
||||||
|
|
||||||
|
//ts1
|
||||||
|
public uint SubroutineID;
|
||||||
|
public uint HitlistID;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new track.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filedata">The data to create the track from.</param>
|
||||||
|
public Track(byte[] Filedata)
|
||||||
|
{
|
||||||
|
BinaryReader Reader = new BinaryReader(new MemoryStream(Filedata));
|
||||||
|
|
||||||
|
MagicNumber = new string(Reader.ReadChars(4));
|
||||||
|
|
||||||
|
if(MagicNumber == "2DKT")
|
||||||
|
TWODKT = true;
|
||||||
|
|
||||||
|
int CurrentVal = 8;
|
||||||
|
string data;
|
||||||
|
|
||||||
|
if(!TWODKT)
|
||||||
|
data = new string(Reader.ReadChars(Filedata.Length));
|
||||||
|
else
|
||||||
|
data = new string(Reader.ReadChars(Reader.ReadInt32()));
|
||||||
|
string[] Values = data.Split(',');
|
||||||
|
|
||||||
|
//MagicNumber = Values[0];
|
||||||
|
Version = ParseHexString(Values[1]);
|
||||||
|
TrackName = Values[2];
|
||||||
|
SoundID = ParseHexString(Values[3]);
|
||||||
|
TrackID = ParseHexString(Values[4]);
|
||||||
|
if (Values[5] != "\r\n" && Values[5] != "ETKD" && Values[5] != "") //some tracks terminate here...
|
||||||
|
{
|
||||||
|
ArgType = (HITArgs)ParseHexString(Values[5]);
|
||||||
|
ControlGroup = (HITControlGroups)ParseHexString(Values[7]);
|
||||||
|
|
||||||
|
if (Version == 2)
|
||||||
|
CurrentVal++;
|
||||||
|
|
||||||
|
CurrentVal += 3; //skip two unknowns and clsid
|
||||||
|
|
||||||
|
DuckingPriority = (HITDuckingPriorities)ParseHexString(Values[CurrentVal]);
|
||||||
|
CurrentVal++;
|
||||||
|
Looped = ParseHexString(Values[CurrentVal]);
|
||||||
|
LoopDefined = true;
|
||||||
|
CurrentVal++;
|
||||||
|
Volume = ParseHexString(Values[CurrentVal]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Reader.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Track() { }
|
||||||
|
|
||||||
|
private uint ParseHexString(string input)
|
||||||
|
{
|
||||||
|
bool IsHex = false;
|
||||||
|
|
||||||
|
if (input == "") return 0;
|
||||||
|
if (input.StartsWith("0x"))
|
||||||
|
{
|
||||||
|
input = input.Substring(2);
|
||||||
|
IsHex = true;
|
||||||
|
}
|
||||||
|
else if (input.Contains("a") || input.Contains("b") || input.Contains("c") || input.Contains("d") || input.Contains("e") || input.Contains("f"))
|
||||||
|
{
|
||||||
|
IsHex = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsHex)
|
||||||
|
{
|
||||||
|
return Convert.ToUInt32(input, 16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Convert.ToUInt32(input);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
181
server/tso.files/ImageLoader.cs
Executable file
181
server/tso.files/ImageLoader.cs
Executable file
|
@ -0,0 +1,181 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace FSO.Files
|
||||||
|
{
|
||||||
|
public class ImageLoader
|
||||||
|
{
|
||||||
|
public static bool UseSoftLoad = true;
|
||||||
|
public static int PremultiplyPNG = 0;
|
||||||
|
|
||||||
|
public static HashSet<uint> MASK_COLORS = new HashSet<uint>{
|
||||||
|
new Microsoft.Xna.Framework.Color(0xFF, 0x00, 0xFF, 0xFF).PackedValue,
|
||||||
|
new Microsoft.Xna.Framework.Color(0xFE, 0x02, 0xFE, 0xFF).PackedValue,
|
||||||
|
new Microsoft.Xna.Framework.Color(0xFF, 0x01, 0xFF, 0xFF).PackedValue
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Func<GraphicsDevice, Stream, Texture2D> BaseFunction = WinFromStream;
|
||||||
|
|
||||||
|
|
||||||
|
public static Texture2D FromStream(GraphicsDevice gd, Stream str)
|
||||||
|
{
|
||||||
|
return BaseFunction(gd, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Texture2D WinFromStream(GraphicsDevice gd, Stream str)
|
||||||
|
{
|
||||||
|
return WinFromStreamP(gd, str, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Texture2D WinFromStreamP(GraphicsDevice gd, Stream str, int premult)
|
||||||
|
{
|
||||||
|
//if (!UseSoftLoad)
|
||||||
|
//{
|
||||||
|
//attempt monogame load of image
|
||||||
|
|
||||||
|
var magic = (str.ReadByte() | (str.ReadByte() << 8));
|
||||||
|
str.Seek(0, SeekOrigin.Begin);
|
||||||
|
magic += 0;
|
||||||
|
if (magic == 0x4D42)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//it's a bitmap.
|
||||||
|
Texture2D tex;
|
||||||
|
if (ImageLoaderHelpers.BitmapFunction != null)
|
||||||
|
{
|
||||||
|
var bmp = ImageLoaderHelpers.BitmapFunction(str);
|
||||||
|
if (bmp == null) return null;
|
||||||
|
tex = new Texture2D(gd, bmp.Item2, bmp.Item3);
|
||||||
|
tex.SetData(bmp.Item1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tex = Texture2D.FromStream(gd, str);
|
||||||
|
}
|
||||||
|
ManualTextureMaskSingleThreaded(ref tex, MASK_COLORS.ToArray());
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null; //bad bitmap :(
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//test for targa
|
||||||
|
str.Seek(-18, SeekOrigin.End);
|
||||||
|
byte[] sig = new byte[16];
|
||||||
|
str.Read(sig, 0, 16);
|
||||||
|
str.Seek(0, SeekOrigin.Begin);
|
||||||
|
if (ASCIIEncoding.Default.GetString(sig) == "TRUEVISION-XFILE")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tga = new TargaImagePCL.TargaImage(str);
|
||||||
|
var tex = new Texture2D(gd, tga.Image.Width, tga.Image.Height);
|
||||||
|
tex.SetData(tga.Image.ToBGRA(true));
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null; //bad tga
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//anything else
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Texture2D tex;
|
||||||
|
Color[] buffer = null;
|
||||||
|
if (ImageLoaderHelpers.BitmapFunction != null)
|
||||||
|
{
|
||||||
|
var bmp = ImageLoaderHelpers.BitmapFunction(str);
|
||||||
|
if (bmp == null) return null;
|
||||||
|
tex = new Texture2D(gd, bmp.Item2, bmp.Item3);
|
||||||
|
tex.SetData(bmp.Item1);
|
||||||
|
|
||||||
|
//buffer = bmp.Item1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tex = Texture2D.FromStream(gd, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
premult += PremultiplyPNG;
|
||||||
|
if (premult == 1)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
buffer = new Color[tex.Width * tex.Height];
|
||||||
|
tex.GetData<Color>(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.Length; i++)
|
||||||
|
{
|
||||||
|
var a = buffer[i].A;
|
||||||
|
buffer[i] = new Color((byte)((buffer[i].R * a) / 255), (byte)((buffer[i].G * a) / 255), (byte)((buffer[i].B * a) / 255), a);
|
||||||
|
}
|
||||||
|
tex.SetData(buffer);
|
||||||
|
}
|
||||||
|
else if (premult == -1) //divide out a premultiply... currently needed for dx since it premultiplies pngs without reason
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
buffer = new Color[tex.Width * tex.Height];
|
||||||
|
tex.GetData<Color>(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.Length; i++)
|
||||||
|
{
|
||||||
|
var a = buffer[i].A / 255f;
|
||||||
|
buffer[i] = new Color((byte)(buffer[i].R / a), (byte)(buffer[i].G / a), (byte)(buffer[i].B / a), buffer[i].A);
|
||||||
|
}
|
||||||
|
tex.SetData(buffer);
|
||||||
|
}
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine("error: " + e.ToString());
|
||||||
|
return new Texture2D(gd, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ManualTextureMaskSingleThreaded(ref Texture2D Texture, uint[] ColorsFrom)
|
||||||
|
{
|
||||||
|
var ColorTo = Microsoft.Xna.Framework.Color.Transparent.PackedValue;
|
||||||
|
|
||||||
|
var size = Texture.Width * Texture.Height * 4;
|
||||||
|
byte[] buffer = new byte[size];
|
||||||
|
|
||||||
|
Texture.GetData<byte>(buffer);
|
||||||
|
|
||||||
|
var didChange = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i += 4)
|
||||||
|
{
|
||||||
|
if (buffer[i] >= 248 && buffer[i + 2] >= 248 && buffer[i + 1] <= 4)
|
||||||
|
{
|
||||||
|
buffer[i] = buffer[i + 1] = buffer[i + 2] = buffer[i + 3] = 0;
|
||||||
|
didChange = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didChange)
|
||||||
|
{
|
||||||
|
Texture.SetData(buffer);
|
||||||
|
}
|
||||||
|
else return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
11
server/tso.files/ImageLoaderHelpers.cs
Executable file
11
server/tso.files/ImageLoaderHelpers.cs
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files
|
||||||
|
{
|
||||||
|
public static class ImageLoaderHelpers
|
||||||
|
{
|
||||||
|
public static Func<Stream, Tuple<byte[], int, int>> BitmapFunction = null;
|
||||||
|
public static Action<byte[], int, int, Stream> SavePNGFunc = null;
|
||||||
|
}
|
||||||
|
}
|
59
server/tso.files/IniFile.cs
Executable file
59
server/tso.files/IniFile.cs
Executable file
|
@ -0,0 +1,59 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files
|
||||||
|
{
|
||||||
|
public class IniFile : Dictionary<string, IniSection>
|
||||||
|
{
|
||||||
|
public static IniFile Read(string path){
|
||||||
|
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read)){
|
||||||
|
var result = new IniFile();
|
||||||
|
result.Decode(stream);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Decode(Stream stream)
|
||||||
|
{
|
||||||
|
IniSection currentSection = null;
|
||||||
|
|
||||||
|
using (var reader = new StreamReader(stream)){
|
||||||
|
string line = null;
|
||||||
|
|
||||||
|
while((line = reader.ReadLine()) != null){
|
||||||
|
line = line.TrimStart();
|
||||||
|
|
||||||
|
if (line.StartsWith(";")){
|
||||||
|
//Comment
|
||||||
|
}else if (line.StartsWith("[")){
|
||||||
|
//Section
|
||||||
|
currentSection = new IniSection();
|
||||||
|
currentSection.Name = line.Substring(1);
|
||||||
|
currentSection.Name = currentSection.Name.Substring(0, currentSection.Name.LastIndexOf("]"));
|
||||||
|
this.Add(currentSection.Name, currentSection);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Could be a key / value
|
||||||
|
var split = line.IndexOf("=");
|
||||||
|
if(split != -1 && currentSection != null && split+1 < line.Length){
|
||||||
|
var key = line.Substring(0, split).Trim();
|
||||||
|
var value = line.Substring(split + 1).TrimStart();
|
||||||
|
|
||||||
|
if(key.Length > 0)
|
||||||
|
{
|
||||||
|
currentSection[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IniSection : Dictionary<string, string>
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
36
server/tso.files/Properties/AssemblyInfo.cs
Executable file
36
server/tso.files/Properties/AssemblyInfo.cs
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("SimsLib")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("SimsLib")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2010")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("97db5061-8f10-4ca0-8470-f42601031d4e")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
315
server/tso.files/RC/DGRP3DGeometry.cs
Executable file
315
server/tso.files/RC/DGRP3DGeometry.cs
Executable file
|
@ -0,0 +1,315 @@
|
||||||
|
using FSO.Files.Formats.IFF;
|
||||||
|
using FSO.Files.Formats.IFF.Chunks;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace FSO.Files.RC
|
||||||
|
{
|
||||||
|
public class DGRP3DGeometry
|
||||||
|
{
|
||||||
|
public bool Rendered = false;
|
||||||
|
public Texture2D Pixel;
|
||||||
|
public ushort PixelSPR;
|
||||||
|
public ushort PixelDir;
|
||||||
|
|
||||||
|
public ushort CustomTexture;
|
||||||
|
public static Func<string, Texture2D> ReplTextureProvider;
|
||||||
|
|
||||||
|
public List<DGRP3DVert> SVerts; //simplified vertices
|
||||||
|
public List<int> SIndices; //simplified indices
|
||||||
|
|
||||||
|
public VertexBuffer Verts;
|
||||||
|
public IndexBuffer Indices;
|
||||||
|
public int PrimCount;
|
||||||
|
|
||||||
|
public void SComplete(GraphicsDevice gd)
|
||||||
|
{
|
||||||
|
Rendered = true;
|
||||||
|
Verts?.Dispose();
|
||||||
|
Indices?.Dispose();
|
||||||
|
|
||||||
|
PrimCount = SIndices.Count / 3;
|
||||||
|
if (PrimCount > 0)
|
||||||
|
{
|
||||||
|
Verts = new VertexBuffer(gd, typeof(DGRP3DVert), SVerts.Count, BufferUsage.None);
|
||||||
|
Verts.SetData(SVerts.ToArray());
|
||||||
|
Indices = new IndexBuffer(gd, IndexElementSize.ThirtyTwoBits, SIndices.Count, BufferUsage.None);
|
||||||
|
Indices.SetData(SIndices.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IffFile.RETAIN_CHUNK_DATA)
|
||||||
|
{
|
||||||
|
SVerts = null;
|
||||||
|
SIndices = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DGRP3DGeometry() { }
|
||||||
|
public DGRP3DGeometry(IoBuffer io, DGRP source, GraphicsDevice gd, int Version)
|
||||||
|
{
|
||||||
|
PixelSPR = io.ReadUInt16();
|
||||||
|
PixelDir = io.ReadUInt16();
|
||||||
|
if (PixelDir == 65535)
|
||||||
|
{
|
||||||
|
CustomTexture = 1;
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
//temporary system for models without DGRP
|
||||||
|
Pixel = ReplTextureProvider("FSO_TEX_" + PixelSPR + ".png");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var name = source.ChunkParent.Filename.Replace('.', '_').Replace("spf", "iff");
|
||||||
|
name += "_TEX_" + PixelSPR + ".png";
|
||||||
|
Pixel = ReplTextureProvider(name);
|
||||||
|
if (Pixel == null)
|
||||||
|
{
|
||||||
|
Pixel = source.ChunkParent.Get<MTEX>(PixelSPR)?.GetTexture(gd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Pixel = source.GetImage(1, 3, PixelDir).Sprites[PixelSPR].GetTexture(gd);
|
||||||
|
}
|
||||||
|
|
||||||
|
var vertCount = io.ReadInt32();
|
||||||
|
SVerts = new List<DGRP3DVert>();
|
||||||
|
|
||||||
|
if (Version > 1)
|
||||||
|
{
|
||||||
|
var bytes = io.ReadBytes(vertCount * Marshal.SizeOf(typeof(DGRP3DVert)));
|
||||||
|
var readVerts = new DGRP3DVert[vertCount];
|
||||||
|
var pinnedHandle = GCHandle.Alloc(readVerts, GCHandleType.Pinned);
|
||||||
|
Marshal.Copy(bytes, 0, pinnedHandle.AddrOfPinnedObject(), bytes.Length);
|
||||||
|
pinnedHandle.Free();
|
||||||
|
SVerts = readVerts.ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < vertCount; i++)
|
||||||
|
{
|
||||||
|
var x = io.ReadFloat();
|
||||||
|
var y = io.ReadFloat();
|
||||||
|
var z = io.ReadFloat();
|
||||||
|
var u = io.ReadFloat();
|
||||||
|
var v = io.ReadFloat();
|
||||||
|
var normal = new Vector3();
|
||||||
|
SVerts.Add(new DGRP3DVert(new Vector3(x, y, z), normal, new Vector2(u, v)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var indexCount = io.ReadInt32();
|
||||||
|
SIndices = ToTArray<int>(io.ReadBytes(indexCount * 4)).ToList();
|
||||||
|
|
||||||
|
// bottom up triangle ordering. useful for trees.
|
||||||
|
/*
|
||||||
|
var triBase = new int[SIndices.Count / 3][];
|
||||||
|
for (int i = 0; i < triBase.Length; i++) triBase[i] = new int[] { SIndices[i * 3], SIndices[i*3 + 1], SIndices[i * 3 + 2] };
|
||||||
|
|
||||||
|
var ordered = triBase.OrderBy(x => SVerts[x[0]].Position.Y);
|
||||||
|
SIndices.Clear();
|
||||||
|
foreach (var item in ordered) SIndices.AddRange(item);
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
if (Version < 2) GenerateNormals(false);
|
||||||
|
|
||||||
|
SComplete(gd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DGRP3DGeometry(string[] splitName, OBJ obj, List<int[]> indices, DGRP source, GraphicsDevice gd)
|
||||||
|
{
|
||||||
|
if (splitName[1] == "SPR")
|
||||||
|
{
|
||||||
|
PixelSPR = ushort.Parse(splitName[3]);
|
||||||
|
PixelDir = ushort.Parse(splitName[2].Substring(3));
|
||||||
|
Pixel = source.GetImage(1, 3, PixelDir).Sprites[PixelSPR].GetTexture(gd);
|
||||||
|
}
|
||||||
|
else if (splitName[1] != "MASK")
|
||||||
|
{
|
||||||
|
PixelSPR = ushort.Parse(splitName[2]);
|
||||||
|
CustomTexture = 1;
|
||||||
|
PixelDir = 65535;
|
||||||
|
|
||||||
|
var name = source.ChunkParent.Filename.Replace('.', '_').Replace("spf", "iff");
|
||||||
|
name += "_TEX_" + PixelSPR + ".png";
|
||||||
|
Pixel = ReplTextureProvider(name);
|
||||||
|
if (Pixel == null)
|
||||||
|
{
|
||||||
|
Pixel = source.ChunkParent.Get<MTEX>(PixelSPR)?.GetTexture(gd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SVerts = new List<DGRP3DVert>();
|
||||||
|
SIndices = new List<int>();
|
||||||
|
var dict = new Dictionary<Tuple<int, int, int>, int>();
|
||||||
|
var hasNormals = false;
|
||||||
|
|
||||||
|
foreach (var ind in indices)
|
||||||
|
{
|
||||||
|
var tup = new Tuple<int, int, int>(ind[0], ind[1], (ind.Length > 2) ? ind[2] : -1);
|
||||||
|
int targ;
|
||||||
|
if (!dict.TryGetValue(tup, out targ))
|
||||||
|
{
|
||||||
|
//add a vertex
|
||||||
|
targ = SVerts.Count;
|
||||||
|
Vector3 normal = Vector3.Zero;
|
||||||
|
if (tup.Item3 > -1)
|
||||||
|
{
|
||||||
|
normal = obj.Normals[tup.Item3 - 1];
|
||||||
|
hasNormals = true;
|
||||||
|
}
|
||||||
|
var vert = new DGRP3DVert(obj.Vertices[ind[0] - 1], normal, obj.TextureCoords[ind[1] - 1]);
|
||||||
|
vert.TextureCoordinate.Y = 1 - vert.TextureCoordinate.Y;
|
||||||
|
SVerts.Add(vert);
|
||||||
|
dict[tup] = targ;
|
||||||
|
}
|
||||||
|
SIndices.Add(targ);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasNormals) GenerateNormals(false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
var triBase = new int[SIndices.Count / 3][];
|
||||||
|
for (int i = 0; i < triBase.Length; i++) triBase[i] = new int[] { SIndices[i * 3], SIndices[i * 3 + 1], SIndices[i * 3 + 2] };
|
||||||
|
|
||||||
|
var ordered = triBase.OrderBy(x => SVerts[x[0]].Position.Y + SVerts[x[1]].Position.Y + SVerts[x[2]].Position.Y);
|
||||||
|
SIndices.Clear();
|
||||||
|
foreach (var item in ordered) SIndices.AddRange(item);
|
||||||
|
*/
|
||||||
|
|
||||||
|
SComplete(gd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GenerateNormals(bool invert)
|
||||||
|
{
|
||||||
|
DGRP3DVert.GenerateNormals(invert, SVerts, SIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(IoWriter io)
|
||||||
|
{
|
||||||
|
io.WriteUInt16(PixelSPR);
|
||||||
|
io.WriteUInt16(PixelDir);
|
||||||
|
io.WriteInt32(SVerts.Count);
|
||||||
|
foreach (var vert in SVerts)
|
||||||
|
{
|
||||||
|
io.WriteFloat(vert.Position.X);
|
||||||
|
io.WriteFloat(vert.Position.Y);
|
||||||
|
io.WriteFloat(vert.Position.Z);
|
||||||
|
io.WriteFloat(vert.TextureCoordinate.X);
|
||||||
|
io.WriteFloat(vert.TextureCoordinate.Y);
|
||||||
|
io.WriteFloat(vert.Normal.X);
|
||||||
|
io.WriteFloat(vert.Normal.Y);
|
||||||
|
io.WriteFloat(vert.Normal.Z);
|
||||||
|
}
|
||||||
|
io.WriteInt32(SIndices.Count);
|
||||||
|
io.WriteBytes(ToByteArray(SIndices.ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetOName(int dyn)
|
||||||
|
{
|
||||||
|
if (CustomTexture == 0)
|
||||||
|
{
|
||||||
|
return dyn + "_SPR_rot" + PixelDir + "_" + PixelSPR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return dyn + "_TEX_" + PixelSPR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveOBJ(StreamWriter io, int dyn, ref int baseInd)
|
||||||
|
{
|
||||||
|
string o_name = GetOName(dyn);
|
||||||
|
|
||||||
|
io.WriteLine("usemtl " + o_name);
|
||||||
|
io.WriteLine("o " + o_name);
|
||||||
|
foreach (var vert in SVerts)
|
||||||
|
{
|
||||||
|
io.WriteLine("v " + vert.Position.X.ToString(CultureInfo.InvariantCulture) + " " + vert.Position.Y.ToString(CultureInfo.InvariantCulture) + " " + vert.Position.Z.ToString(CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
foreach (var vert in SVerts)
|
||||||
|
{
|
||||||
|
io.WriteLine("vt " + vert.TextureCoordinate.X.ToString(CultureInfo.InvariantCulture) + " " + (1 - vert.TextureCoordinate.Y).ToString(CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
foreach (var vert in SVerts)
|
||||||
|
{
|
||||||
|
io.WriteLine("vn " + vert.Normal.X.ToString(CultureInfo.InvariantCulture) + " " + vert.Normal.Y.ToString(CultureInfo.InvariantCulture) + " " + vert.Normal.Z.ToString(CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
|
||||||
|
io.Write("f ");
|
||||||
|
var ticker = 0;
|
||||||
|
var j = 0;
|
||||||
|
foreach (var ind in SIndices)
|
||||||
|
{
|
||||||
|
var i = ind + baseInd;
|
||||||
|
io.Write(i + "/" + i + "/" + i + " ");
|
||||||
|
if (++ticker == 3)
|
||||||
|
{
|
||||||
|
io.WriteLine("");
|
||||||
|
if (j < SIndices.Count - 1) io.Write("f ");
|
||||||
|
ticker = 0;
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
baseInd += SVerts.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveMTL(StreamWriter io, int dyn, string path)
|
||||||
|
{
|
||||||
|
var oname = GetOName(dyn);
|
||||||
|
if (Pixel != null)
|
||||||
|
{
|
||||||
|
Common.Utils.GameThread.NextUpdate(x =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var io2 = File.Open(Path.Combine(path, oname + ".png"), FileMode.Create))
|
||||||
|
Pixel.SaveAsPng(io2, Pixel.Width, Pixel.Height);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
io.WriteLine("newmtl " + oname);
|
||||||
|
io.WriteLine("Ka 1.000 1.000 1.000");
|
||||||
|
io.WriteLine("Kd 1.000 1.000 1.000");
|
||||||
|
io.WriteLine("Ks 0.000 0.000 0.000");
|
||||||
|
|
||||||
|
io.WriteLine("Ns 10.0000");
|
||||||
|
io.WriteLine("illum 2");
|
||||||
|
|
||||||
|
io.WriteLine("map_Kd " + oname + ".png");
|
||||||
|
io.WriteLine("map_d " + oname + ".png");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Verts?.Dispose();
|
||||||
|
Indices?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T[] ToTArray<T>(byte[] input)
|
||||||
|
{
|
||||||
|
var result = new T[input.Length / Marshal.SizeOf(typeof(T))];
|
||||||
|
Buffer.BlockCopy(input, 0, result, 0, input.Length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] ToByteArray<T>(T[] input)
|
||||||
|
{
|
||||||
|
var result = new byte[input.Length * Marshal.SizeOf(typeof(T))];
|
||||||
|
Buffer.BlockCopy(input, 0, result, 0, result.Length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
736
server/tso.files/RC/DGRP3DMesh.cs
Executable file
736
server/tso.files/RC/DGRP3DMesh.cs
Executable file
|
@ -0,0 +1,736 @@
|
||||||
|
using FSO.Common.MeshSimplify;
|
||||||
|
using FSO.Common.Rendering;
|
||||||
|
using FSO.Common.Utils;
|
||||||
|
using FSO.Files.Formats.IFF.Chunks;
|
||||||
|
using FSO.Files.RC.Utils;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace FSO.Files.RC
|
||||||
|
{
|
||||||
|
public class DGRP3DMesh
|
||||||
|
{
|
||||||
|
//1: initial 3d format
|
||||||
|
//2: normals
|
||||||
|
//3: depth mask (for sinks, fireplaces)
|
||||||
|
public static int CURRENT_VERSION = 3;
|
||||||
|
public static int CURRENT_RECONSTRUCT = 2;
|
||||||
|
|
||||||
|
public static DGRPRCParams DefaultParams = new DGRPRCParams();
|
||||||
|
public static Dictionary<string, DGRPRCParams> ParamsByIff = new Dictionary<string, DGRPRCParams>()
|
||||||
|
{
|
||||||
|
{"windows2.iff", new DGRPRCParams() { Rotations = new bool[] {true, true, false, false } } },
|
||||||
|
{"windows.iff", new DGRPRCParams() { Rotations = new bool[] {true, true, false, false } } },
|
||||||
|
{"windows5.iff", new DGRPRCParams() { DoorFix = true } },
|
||||||
|
|
||||||
|
{"windowslodge.iff", new DGRPRCParams() { DoorFix = true } },
|
||||||
|
{"doors.iff", new DGRPRCParams() { DoorFix = true } },
|
||||||
|
{"doors5.iff", new DGRPRCParams() { DoorFix = true } },
|
||||||
|
{"doorsmagic.iff", new DGRPRCParams() { DoorFix = true } },
|
||||||
|
|
||||||
|
{"phones.iff", new DGRPRCParams() { Rotations = new bool[] {true, true, false, false }, StartDGRP = 200, EndDGRP = 207 } },
|
||||||
|
|
||||||
|
{"countercasino.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"counters.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"counters2.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"counters3.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"counters4.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"counters5.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"counters6.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"counterwall.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"oj-rest-counters.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"oj-rest-pickup-counters.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"dishwashers.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"trashcompactor.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"3tileclock.iff", new DGRPRCParams() { BlenderTweak = true } },
|
||||||
|
|
||||||
|
{"fencessuperstar.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"fencesnowbank.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"fencesunleashed.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"fencelodgestone.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"fenceparty.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"fencecarnival.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"fencesspellbound.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"columnarchmagic.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
|
||||||
|
{"awnings.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"awnings3.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"awnings4.iff", new DGRPRCParams() { CounterFix = true } },
|
||||||
|
{"awningthatch.iff", new DGRPRCParams() { CounterFix = true } }
|
||||||
|
};
|
||||||
|
|
||||||
|
//STATIC: multithreading for
|
||||||
|
|
||||||
|
public static Queue<Action> QueuedRC = new Queue<Action>();
|
||||||
|
public static AutoResetEvent NewRecon = new AutoResetEvent(false);
|
||||||
|
|
||||||
|
public static bool Sync;
|
||||||
|
public static void InitRCWorkers()
|
||||||
|
{
|
||||||
|
var cores = Math.Max(1, Environment.ProcessorCount-1); //maybe detect hyperthreading somehow
|
||||||
|
for (int i=0; i<cores; i++)
|
||||||
|
{
|
||||||
|
var thread = new Thread(RCWorkerLoop);
|
||||||
|
thread.Priority = ThreadPriority.BelowNormal;
|
||||||
|
//todo: priority below normal, so we dont disrupt the game?
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void QueueWork(Action work)
|
||||||
|
{
|
||||||
|
if (Sync) work();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lock (QueuedRC) QueuedRC.Enqueue(work);
|
||||||
|
NewRecon.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int GetWorkCount()
|
||||||
|
{
|
||||||
|
lock (QueuedRC) return QueuedRC.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RCWorkerLoop()
|
||||||
|
{
|
||||||
|
while (!GameThread.Killed)
|
||||||
|
{
|
||||||
|
Action item = null;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
lock (QueuedRC)
|
||||||
|
{
|
||||||
|
if (QueuedRC.Count > 0) item = QueuedRC.Dequeue();
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
item?.Invoke();
|
||||||
|
}
|
||||||
|
WaitHandle.WaitAny(new WaitHandle[] { NewRecon, GameThread.OnKilled });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//END STATIC
|
||||||
|
|
||||||
|
public int Version = CURRENT_VERSION;
|
||||||
|
public int ReconstructVersion;
|
||||||
|
public string Name;
|
||||||
|
public List<Dictionary<Texture2D, DGRP3DGeometry>> Geoms;
|
||||||
|
public DGRP3DMaskType MaskType = DGRP3DMaskType.None;
|
||||||
|
public DGRP3DGeometry DepthMask;
|
||||||
|
public BoundingBox? Bounds;
|
||||||
|
|
||||||
|
|
||||||
|
//for internal use
|
||||||
|
private int TotalSprites;
|
||||||
|
private int CompletedCount;
|
||||||
|
private float MaxAllowedSq = 0.065f * 0.065f;
|
||||||
|
public List<Vector3> BoundPts = new List<Vector3>();
|
||||||
|
|
||||||
|
|
||||||
|
public DGRP3DMesh(DGRP dgrp, Stream source, GraphicsDevice gd)
|
||||||
|
{
|
||||||
|
using (var cstream = new GZipStream(source, CompressionMode.Decompress))
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(cstream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var fsom = io.ReadCString(4);
|
||||||
|
Version = io.ReadInt32();
|
||||||
|
ReconstructVersion = io.ReadInt32();
|
||||||
|
if (ReconstructVersion != 0 && ReconstructVersion < CURRENT_RECONSTRUCT)
|
||||||
|
throw new Exception("Reconstruction outdated, must be rerun!");
|
||||||
|
Name = io.ReadPascalString();
|
||||||
|
|
||||||
|
var geomCount = io.ReadInt32();
|
||||||
|
Geoms = new List<Dictionary<Texture2D, DGRP3DGeometry>>();
|
||||||
|
for (int i = 0; i < geomCount; i++)
|
||||||
|
{
|
||||||
|
var d = new Dictionary<Texture2D, DGRP3DGeometry>();
|
||||||
|
var subCount = io.ReadInt32();
|
||||||
|
for (int j = 0; j < subCount; j++)
|
||||||
|
{
|
||||||
|
var geom = new DGRP3DGeometry(io, dgrp, gd, Version);
|
||||||
|
if (geom.Pixel == null && geom.PrimCount > 0) throw new Exception("Invalid Mesh! (old format)");
|
||||||
|
d.Add(geom.Pixel, geom);
|
||||||
|
}
|
||||||
|
Geoms.Add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Version > 2)
|
||||||
|
{
|
||||||
|
MaskType = (DGRP3DMaskType)io.ReadInt32();
|
||||||
|
if (MaskType > DGRP3DMaskType.None)
|
||||||
|
DepthMask = new DGRP3DGeometry(io, dgrp, gd, Version);
|
||||||
|
}
|
||||||
|
|
||||||
|
var x = io.ReadFloat();
|
||||||
|
var y = io.ReadFloat();
|
||||||
|
var z = io.ReadFloat();
|
||||||
|
var x2 = io.ReadFloat();
|
||||||
|
var y2 = io.ReadFloat();
|
||||||
|
var z2 = io.ReadFloat();
|
||||||
|
Bounds = new BoundingBox(new Vector3(x, y, z), new Vector3(x2, y2, z2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SaveDirectory;
|
||||||
|
|
||||||
|
public DGRP3DMesh(DGRP dgrp, OBJD obj, GraphicsDevice gd, string saveDirectory)
|
||||||
|
{
|
||||||
|
ReconstructVersion = CURRENT_RECONSTRUCT;
|
||||||
|
SaveDirectory = saveDirectory;
|
||||||
|
Geoms = new List<Dictionary<Texture2D, DGRP3DGeometry>>();
|
||||||
|
if (dgrp == null) return;
|
||||||
|
Name = obj.ChunkParent.Filename.Replace('.', '_') + "_" + dgrp.ChunkID;
|
||||||
|
var lower = obj.ChunkParent.Filename.ToLowerInvariant();
|
||||||
|
var config = obj.ChunkParent.List<FSOR>()?.FirstOrDefault()?.Params;
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
if (!ParamsByIff.TryGetValue(lower, out config)) config = DefaultParams;
|
||||||
|
}
|
||||||
|
if (!config.InRange(dgrp.ChunkID)) config = DefaultParams;
|
||||||
|
|
||||||
|
int totalSpr = 0;
|
||||||
|
for (uint rotation = 0; rotation < 4; rotation++)
|
||||||
|
{
|
||||||
|
if (config.DoorFix)
|
||||||
|
{
|
||||||
|
if ((obj.SubIndex & 0xFF) == 1)
|
||||||
|
{
|
||||||
|
if ((rotation+1)%4 > 1) continue;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
if ((rotation + 1) % 4 < 2) continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!config.Rotations[rotation]) continue;
|
||||||
|
var img = dgrp.GetImage(1, 3, rotation);
|
||||||
|
|
||||||
|
var zOff = (config.BlenderTweak) ? -57.5f : -55f;
|
||||||
|
|
||||||
|
var mat = Matrix.CreateTranslation(new Vector3(-72, -344, zOff));
|
||||||
|
mat *= Matrix.CreateScale((1f / (128)) * 1.43f);//1.4142135623730f);
|
||||||
|
mat *= Matrix.CreateScale(1, -1, 1);
|
||||||
|
|
||||||
|
mat *= Matrix.CreateRotationX((float)Math.PI / -6);
|
||||||
|
mat *= Matrix.CreateRotationY(((float)Math.PI / 4) * (1+rotation*2));
|
||||||
|
|
||||||
|
var factor = (config.BlenderTweak) ? 0.40f : 0.39f;
|
||||||
|
|
||||||
|
int curSpr = 0;
|
||||||
|
foreach (var sprite in img.Sprites)
|
||||||
|
{
|
||||||
|
var sprMat = mat * Matrix.CreateTranslation(new Vector3(sprite.ObjectOffset.X, sprite.ObjectOffset.Z, sprite.ObjectOffset.Y) * new Vector3(1f / 16f, 1f / 5f, 1f / 16f));
|
||||||
|
var inv = Matrix.Invert(sprMat);
|
||||||
|
var tex = sprite.GetTexture(gd);
|
||||||
|
|
||||||
|
if (tex == null)
|
||||||
|
{
|
||||||
|
curSpr++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var isDynamic = sprite.SpriteID >= obj.DynamicSpriteBaseId && sprite.SpriteID < (obj.DynamicSpriteBaseId + obj.NumDynamicSprites);
|
||||||
|
var dynid = (isDynamic) ? (int)(1 + sprite.SpriteID - obj.DynamicSpriteBaseId) : 0;
|
||||||
|
|
||||||
|
while (Geoms.Count <= dynid) Geoms.Add(new Dictionary<Texture2D, DGRP3DGeometry>());
|
||||||
|
|
||||||
|
DGRP3DGeometry geom = null;
|
||||||
|
if (!Geoms[dynid].TryGetValue(tex, out geom))
|
||||||
|
{
|
||||||
|
geom = new DGRP3DGeometry() { Pixel = tex };
|
||||||
|
Geoms[dynid][geom.Pixel] = geom;
|
||||||
|
}
|
||||||
|
geom.PixelDir = (ushort)rotation;
|
||||||
|
geom.PixelSPR = (ushort)(curSpr++);
|
||||||
|
totalSpr++;
|
||||||
|
|
||||||
|
var depthB = sprite.GetDepth();
|
||||||
|
|
||||||
|
var useDequantize = false;
|
||||||
|
float[] depth = null;
|
||||||
|
int iterations = 125;
|
||||||
|
int triDivisor = 100;
|
||||||
|
float aggressiveness = 3.5f;
|
||||||
|
if (useDequantize)
|
||||||
|
{
|
||||||
|
var dtex = new Texture2D(gd, ((TextureInfo)tex.Tag).Size.X, ((TextureInfo)tex.Tag).Size.Y, false, SurfaceFormat.Color);
|
||||||
|
dtex.SetData(depthB.Select(x => new Color(x, x, x, x)).ToArray());
|
||||||
|
depth = DepthTreatment.DequantizeDepth(gd, dtex);
|
||||||
|
dtex.Dispose();
|
||||||
|
|
||||||
|
iterations = 500;
|
||||||
|
aggressiveness = 2.5f;
|
||||||
|
MaxAllowedSq = 0.05f * 0.05f;
|
||||||
|
}
|
||||||
|
else if (depthB != null)
|
||||||
|
{
|
||||||
|
depth = depthB.Select(x => x / 255f).ToArray();
|
||||||
|
iterations = 125;
|
||||||
|
aggressiveness = 3.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth == null) continue;
|
||||||
|
|
||||||
|
QueueWork(() =>
|
||||||
|
{
|
||||||
|
var boundPts = new List<Vector3>();
|
||||||
|
//begin async part
|
||||||
|
var w = ((TextureInfo)tex.Tag).Size.X;
|
||||||
|
var h = ((TextureInfo)tex.Tag).Size.Y;
|
||||||
|
|
||||||
|
var pos = sprite.SpriteOffset + new Vector2(72, 348 - h);
|
||||||
|
var tl = Vector3.Transform(new Vector3(pos, 0), sprMat);
|
||||||
|
var tr = Vector3.Transform(new Vector3(pos + new Vector2(w, 0), 0), sprMat);
|
||||||
|
var bl = Vector3.Transform(new Vector3(pos + new Vector2(0, h), 0), sprMat);
|
||||||
|
var tlFront = Vector3.Transform(new Vector3(pos, 110.851251f), sprMat);
|
||||||
|
|
||||||
|
var xInc = (tr - tl) / w;
|
||||||
|
var yInc = (bl - tl) / h;
|
||||||
|
var dFactor = (tlFront - tl) / (factor);
|
||||||
|
|
||||||
|
if (sprite.Flip)
|
||||||
|
{
|
||||||
|
tl = tr;
|
||||||
|
xInc *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dict = new Dictionary<int, int>();
|
||||||
|
var verts = new List<VertexPositionTexture>();
|
||||||
|
var indices = new List<int>();
|
||||||
|
|
||||||
|
var lastPt = new Vector3();
|
||||||
|
var i = 0;
|
||||||
|
var verti = 0;
|
||||||
|
for (int y = 0; y < h; y++)
|
||||||
|
{
|
||||||
|
if (y > 0) boundPts.Add(lastPt);
|
||||||
|
bool first = true;
|
||||||
|
var vpos = tl;
|
||||||
|
for (int x = 0; x < w; x++)
|
||||||
|
{
|
||||||
|
var d = depth[i++];
|
||||||
|
if (d < 0.999f)
|
||||||
|
{
|
||||||
|
lastPt = vpos + (1f - d) * dFactor;
|
||||||
|
if (first) { boundPts.Add(lastPt); first = false; }
|
||||||
|
var vert = new VertexPositionTexture(lastPt, new Vector2((float)x / w, (float)y / h));
|
||||||
|
verts.Add(vert);
|
||||||
|
dict.Add(y * w + x, verti++);
|
||||||
|
}
|
||||||
|
vpos += xInc;
|
||||||
|
}
|
||||||
|
tl += yInc;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < h - 1; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w - 1; x++)
|
||||||
|
{
|
||||||
|
//try make a triangle or two
|
||||||
|
var quad = new int?[] {
|
||||||
|
QuickTryGet(dict, x+y*w),
|
||||||
|
QuickTryGet(dict, x+1+y*w),
|
||||||
|
QuickTryGet(dict, x+1+(y+1)*w),
|
||||||
|
QuickTryGet(dict, x+(y+1)*w)
|
||||||
|
};
|
||||||
|
var total = quad.Sum(v => (v == null) ? 0 : 1);
|
||||||
|
if (total == 4)
|
||||||
|
{
|
||||||
|
var d1 = Vector3.DistanceSquared(verts[quad[0].Value].Position, verts[quad[2].Value].Position);
|
||||||
|
var d2 = Vector3.DistanceSquared(verts[quad[1].Value].Position, verts[quad[3].Value].Position);
|
||||||
|
|
||||||
|
if (d1 > MaxAllowedSq || d2 > MaxAllowedSq) continue;
|
||||||
|
|
||||||
|
indices.Add(quad[0].Value);
|
||||||
|
indices.Add(quad[1].Value);
|
||||||
|
indices.Add(quad[2].Value);
|
||||||
|
|
||||||
|
indices.Add(quad[0].Value);
|
||||||
|
indices.Add(quad[2].Value);
|
||||||
|
indices.Add(quad[3].Value);
|
||||||
|
}
|
||||||
|
else if (total == 3)
|
||||||
|
{
|
||||||
|
//clockwise anyways. we can only make one
|
||||||
|
int? last = null;
|
||||||
|
int? first = null;
|
||||||
|
bool exit = false;
|
||||||
|
foreach (var v in quad)
|
||||||
|
{
|
||||||
|
if (v != null)
|
||||||
|
{
|
||||||
|
if (last != null && Vector3.DistanceSquared(verts[last.Value].Position, verts[v.Value].Position) > MaxAllowedSq)
|
||||||
|
{
|
||||||
|
exit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last = v.Value;
|
||||||
|
if (first == null) first = last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exit && Vector3.DistanceSquared(verts[last.Value].Position, verts[first.Value].Position) > MaxAllowedSq) exit = true;
|
||||||
|
if (exit) continue;
|
||||||
|
|
||||||
|
foreach (var v in quad)
|
||||||
|
{
|
||||||
|
if (v != null) indices.Add(v.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.CounterFix)
|
||||||
|
{
|
||||||
|
//x axis extrapolation
|
||||||
|
//clip: -0.4 to 0.4
|
||||||
|
|
||||||
|
//identify vertices very close to clipping range(border)
|
||||||
|
//! for each vertex outwith clipping range
|
||||||
|
//- idendify closest border pixel bp in image space
|
||||||
|
//- result.zy = bp.zy
|
||||||
|
//- result.x = (resultIMAGE.x - bpIMAGE.x) / 64;
|
||||||
|
//- clip x to -0.5, 0.5f.
|
||||||
|
|
||||||
|
var clip = 0.4;
|
||||||
|
var bWidth = 0.02;
|
||||||
|
var border1 = new List<Tuple<Vector2, Vector3>>();
|
||||||
|
var invalid1 = new List<KeyValuePair<int, int>>();
|
||||||
|
var border2 = new List<Tuple<Vector2, Vector3>>();
|
||||||
|
var invalid2 = new List<KeyValuePair<int, int>>();
|
||||||
|
foreach (var vert in dict) {
|
||||||
|
var vpos = verts[vert.Value].Position;
|
||||||
|
var dist = Math.Abs(vpos.X);
|
||||||
|
if (dist > clip)
|
||||||
|
{
|
||||||
|
if (vpos.X > 0)
|
||||||
|
invalid1.Add(vert);
|
||||||
|
else
|
||||||
|
invalid2.Add(vert);
|
||||||
|
} else if (dist > (clip - bWidth))
|
||||||
|
{
|
||||||
|
if (vpos.X > 0)
|
||||||
|
border1.Add(new Tuple<Vector2, Vector3>(new Vector2(vert.Key % w, vert.Key / w), vpos));
|
||||||
|
else
|
||||||
|
border2.Add(new Tuple<Vector2, Vector3>(new Vector2(vert.Key % w, vert.Key / w), vpos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var edge = 0.498f + 0.001f * (rotation % 2);
|
||||||
|
|
||||||
|
if (border1.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var vert in invalid1)
|
||||||
|
{
|
||||||
|
var vstr = verts[vert.Value];
|
||||||
|
var pos2d = new Vector2(vert.Key % w, vert.Key / w);
|
||||||
|
var vpos = vstr.Position;
|
||||||
|
var closest = border1.OrderBy(x => Vector2.DistanceSquared(x.Item1, pos2d)).First();
|
||||||
|
|
||||||
|
vpos.X = closest.Item2.X + Vector2.Distance(closest.Item1, pos2d) / 71.55f;
|
||||||
|
if (vpos.X > 0.5f)
|
||||||
|
{
|
||||||
|
vpos.X = edge;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vpos.Y = closest.Item2.Y;
|
||||||
|
vpos.Z = closest.Item2.Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
vstr.Position = vpos;
|
||||||
|
verts[vert.Value] = vstr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (border2.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var vert in invalid2)
|
||||||
|
{
|
||||||
|
var vstr = verts[vert.Value];
|
||||||
|
var pos2d = new Vector2(vert.Key % w, vert.Key / w);
|
||||||
|
var vpos = vstr.Position;
|
||||||
|
var closest = border2.OrderBy(x => Vector2.DistanceSquared(x.Item1, pos2d)).First();
|
||||||
|
|
||||||
|
vpos.X = closest.Item2.X - Vector2.Distance(closest.Item1, pos2d) / 71.55f;
|
||||||
|
if (vpos.X < -0.5f)
|
||||||
|
{
|
||||||
|
vpos.X = -edge;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vpos.Y = closest.Item2.Y;
|
||||||
|
vpos.Z = closest.Item2.Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
vstr.Position = vpos;
|
||||||
|
verts[vert.Value] = vstr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lock (BoundPts) BoundPts.AddRange(boundPts);
|
||||||
|
var useSimplification = config.Simplify;
|
||||||
|
|
||||||
|
if (useSimplification)
|
||||||
|
{
|
||||||
|
var simple = new Simplify();
|
||||||
|
simple.vertices = verts.Select(x => new MSVertex() { p = x.Position, t = x.TextureCoordinate }).ToList();
|
||||||
|
for (int t = 0; t < indices.Count; t += 3)
|
||||||
|
{
|
||||||
|
simple.triangles.Add(new MSTriangle()
|
||||||
|
{
|
||||||
|
v = new int[] { indices[t], indices[t + 1], indices[t + 2] }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
simple.simplify_mesh(simple.triangles.Count / triDivisor, agressiveness: aggressiveness, iterations: iterations);
|
||||||
|
|
||||||
|
verts = simple.vertices.Select(x =>
|
||||||
|
{
|
||||||
|
var iv = Vector3.Transform(x.p, inv);
|
||||||
|
//DGRP3DVert
|
||||||
|
return new VertexPositionTexture(x.p,
|
||||||
|
new Vector2(
|
||||||
|
(sprite.Flip) ? (1 - ((iv.X - pos.X + 0.5f) / w)) : ((iv.X - pos.X + 0.5f) / w),
|
||||||
|
(iv.Y - pos.Y + 0.5f) / h));
|
||||||
|
}
|
||||||
|
).ToList();
|
||||||
|
indices.Clear();
|
||||||
|
foreach (var t in simple.triangles)
|
||||||
|
{
|
||||||
|
indices.Add(t.v[0]);
|
||||||
|
indices.Add(t.v[1]);
|
||||||
|
indices.Add(t.v[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameThread.NextUpdate(x =>
|
||||||
|
{
|
||||||
|
if (geom.SVerts == null)
|
||||||
|
{
|
||||||
|
geom.SVerts = new List<DGRP3DVert>();
|
||||||
|
geom.SIndices = new List<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var bID = geom.SVerts.Count;
|
||||||
|
foreach (var id in indices) geom.SIndices.Add(id + bID);
|
||||||
|
var verts2 = verts.Select(v => new DGRP3DVert(v.Position, Vector3.Zero, v.TextureCoordinate)).ToList();
|
||||||
|
DGRP3DVert.GenerateNormals(!sprite.Flip, verts2, indices);
|
||||||
|
geom.SVerts.AddRange(verts2);
|
||||||
|
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (++CompletedCount == TotalSprites) Complete(gd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameThread.NextUpdate(x =>
|
||||||
|
{
|
||||||
|
if (geom.SVerts == null)
|
||||||
|
{
|
||||||
|
geom.SVerts = new List<DGRP3DVert>();
|
||||||
|
geom.SIndices = new List<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseID = geom.SVerts.Count;
|
||||||
|
foreach (var id in indices) geom.SIndices.Add(id + baseID);
|
||||||
|
var verts2 = verts.Select(v => new DGRP3DVert(v.Position, Vector3.Zero, v.TextureCoordinate)).ToList();
|
||||||
|
DGRP3DVert.GenerateNormals(!sprite.Flip, verts2, indices);
|
||||||
|
geom.SVerts.AddRange(verts2);
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (++CompletedCount == TotalSprites) Complete(gd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TotalSprites = totalSpr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a DGRPMesh from a .OBJ file.
|
||||||
|
/// </summary>
|
||||||
|
public DGRP3DMesh(DGRP dgrp, OBJ source, GraphicsDevice gd)
|
||||||
|
{
|
||||||
|
Bounds = source.Vertices.Count>0?BoundingBox.CreateFromPoints(source.Vertices):new BoundingBox();
|
||||||
|
Geoms = new List<Dictionary<Texture2D, DGRP3DGeometry>>();
|
||||||
|
if (dgrp == null) return;
|
||||||
|
Name = dgrp.ChunkParent.Filename.Replace('.', '_').Replace("spf", "iff") + "_" + dgrp.ChunkID;
|
||||||
|
|
||||||
|
foreach (var obj in source.FacesByObjgroup.OrderBy(x => x.Key))
|
||||||
|
{
|
||||||
|
if (obj.Key == "_default") continue;
|
||||||
|
var split = obj.Key.Split('_');
|
||||||
|
if (split[0] == "DEPTH")
|
||||||
|
{
|
||||||
|
DepthMask = new DGRP3DGeometry(split, source, obj.Value, dgrp, gd);
|
||||||
|
if (split.Length > 2 && split[2] == "PORTAL")
|
||||||
|
{
|
||||||
|
MaskType = DGRP3DMaskType.Portal;
|
||||||
|
|
||||||
|
var verts = new List<Vector3>();
|
||||||
|
var objs = source.FacesByObjgroup.Where(x => !x.Key.StartsWith("DEPTH_MASK_PORTAL")).Select(x => x.Value);
|
||||||
|
foreach (var obj2 in objs)
|
||||||
|
{
|
||||||
|
foreach (var tri in obj2)
|
||||||
|
{
|
||||||
|
verts.Add(source.Vertices[tri[0] - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bounds = BoundingBox.CreateFromPoints(verts);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
MaskType = DGRP3DMaskType.Normal;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//0: dynsprite id, 1: SPR or custom, 2: rotation, 3: index
|
||||||
|
var id = int.Parse(split[0]);
|
||||||
|
while (Geoms.Count <= id) Geoms.Add(new Dictionary<Texture2D, DGRP3DGeometry>());
|
||||||
|
var dict = Geoms[id];
|
||||||
|
var geom = new DGRP3DGeometry(split, source, obj.Value, dgrp, gd);
|
||||||
|
dict[geom.Pixel] = geom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Complete(GraphicsDevice gd)
|
||||||
|
{
|
||||||
|
Bounds = (BoundPts.Count == 0) ? new BoundingBox() : BoundingBox.CreateFromPoints(BoundPts);
|
||||||
|
BoundPts = null;
|
||||||
|
Save();
|
||||||
|
foreach (var g in Geoms)
|
||||||
|
foreach (var e in g)
|
||||||
|
{
|
||||||
|
e.Value.SComplete(gd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
if (SaveDirectory == null) return;
|
||||||
|
var dir = Path.Combine(SaveDirectory, Name + ".fsom");
|
||||||
|
Directory.CreateDirectory(SaveDirectory);
|
||||||
|
using (var stream = File.Open(dir, FileMode.Create))
|
||||||
|
{
|
||||||
|
using (var cstream = new GZipStream(stream, CompressionMode.Compress))
|
||||||
|
Save(cstream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteCString("FSOm", 4);
|
||||||
|
io.WriteInt32(CURRENT_VERSION);
|
||||||
|
io.WriteInt32(ReconstructVersion);
|
||||||
|
io.WritePascalString(Name);
|
||||||
|
|
||||||
|
io.WriteInt32(Geoms.Count);
|
||||||
|
foreach (var g in Geoms)
|
||||||
|
{
|
||||||
|
io.WriteInt32(g.Count);
|
||||||
|
foreach (var m in g.Values)
|
||||||
|
{
|
||||||
|
m.Save(io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteInt32((int)MaskType);
|
||||||
|
if (DepthMask != null)
|
||||||
|
{
|
||||||
|
DepthMask.Save(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
var b = Bounds.Value;
|
||||||
|
io.WriteFloat(b.Min.X);
|
||||||
|
io.WriteFloat(b.Min.Y);
|
||||||
|
io.WriteFloat(b.Min.Z);
|
||||||
|
io.WriteFloat(b.Max.X);
|
||||||
|
io.WriteFloat(b.Max.Y);
|
||||||
|
io.WriteFloat(b.Max.Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveOBJ(Stream stream, string filename)
|
||||||
|
{
|
||||||
|
using (var io = new StreamWriter(stream))
|
||||||
|
{
|
||||||
|
io.WriteLine("# Generated by the FreeSO FSOm Exporter tool.");
|
||||||
|
io.WriteLine("# Meshes can be cleaned up then re-imported via Volcanic.");
|
||||||
|
io.WriteLine("# One material per object... Note that material names must follow this format:");
|
||||||
|
io.WriteLine("# - '$_SPR_rot#_#': uses the texture from the SPR this DGRP would normally use, ");
|
||||||
|
io.WriteLine("# at the given rotation and index.");
|
||||||
|
io.WriteLine("# - '$_TEX_#': import a custom PNG texture at the given chunk ID. Textures can be ");
|
||||||
|
io.WriteLine("# shared across multiple DGRPs by using the same ID.");
|
||||||
|
io.WriteLine("# Replace $ with the dynamic sprite index. 0 means base. (untoggleable)");
|
||||||
|
io.WriteLine("# Textures are assumed to have a filename equivalent to their material name, plus png.");
|
||||||
|
|
||||||
|
|
||||||
|
io.WriteLine("mtllib "+filename+".mtl");
|
||||||
|
io.WriteLine("s 1");
|
||||||
|
|
||||||
|
int dyn = 0;
|
||||||
|
int indCount = 1;
|
||||||
|
foreach (var g in Geoms)
|
||||||
|
{
|
||||||
|
foreach (var m in g.Values)
|
||||||
|
{
|
||||||
|
m.SaveOBJ(io, dyn, ref indCount);
|
||||||
|
}
|
||||||
|
dyn++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveMTL(Stream stream, string path)
|
||||||
|
{
|
||||||
|
using (var io = new StreamWriter(stream))
|
||||||
|
{
|
||||||
|
io.WriteLine("# Generated by the FreeSO FSOm Exporter tool.");
|
||||||
|
io.WriteLine("# Contains material information for exported objects.");
|
||||||
|
io.WriteLine("# See the associated .obj file for more information.");
|
||||||
|
|
||||||
|
int dyn = 0;
|
||||||
|
foreach (var g in Geoms)
|
||||||
|
{
|
||||||
|
foreach (var m in g.Values)
|
||||||
|
{
|
||||||
|
m.SaveMTL(io, dyn, path);
|
||||||
|
}
|
||||||
|
dyn++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int? QuickTryGet(Dictionary<int, int> dict, int pt)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
if (dict.TryGetValue(pt, out result)) return result;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DGRP3DMaskType
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Normal = 1,
|
||||||
|
Portal = 2
|
||||||
|
}
|
||||||
|
}
|
111
server/tso.files/RC/DGRP3DVert.cs
Executable file
111
server/tso.files/RC/DGRP3DVert.cs
Executable file
|
@ -0,0 +1,111 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Microsoft.Xna.Framework.Graphics
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct DGRP3DVert : IVertexType
|
||||||
|
{
|
||||||
|
public Vector3 Position;
|
||||||
|
public Vector2 TextureCoordinate;
|
||||||
|
public Vector3 Normal;
|
||||||
|
public static readonly VertexDeclaration VertexDeclaration;
|
||||||
|
public DGRP3DVert(Vector3 position, Vector3 normal, Vector2 textureCoordinate)
|
||||||
|
{
|
||||||
|
this.Position = position;
|
||||||
|
this.Normal = normal;
|
||||||
|
this.TextureCoordinate = textureCoordinate;
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexDeclaration IVertexType.VertexDeclaration
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return VertexDeclaration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
// TODO: FIc gethashcode
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "{{Position:" + this.Position + " Normal:" + this.Normal + " TextureCoordinate:" + this.TextureCoordinate + "}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(DGRP3DVert left, DGRP3DVert right)
|
||||||
|
{
|
||||||
|
return (((left.Position == right.Position) && (left.Normal == right.Normal)) && (left.TextureCoordinate == right.TextureCoordinate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(DGRP3DVert left, DGRP3DVert right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (obj.GetType() != base.GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (this == ((DGRP3DVert)obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
static DGRP3DVert()
|
||||||
|
{
|
||||||
|
VertexElement[] elements = new VertexElement[] { new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), new VertexElement(12, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), new VertexElement(20, VertexElementFormat.Vector3, VertexElementUsage.TextureCoordinate, 1) };
|
||||||
|
VertexDeclaration declaration = new VertexDeclaration(elements);
|
||||||
|
VertexDeclaration = declaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GenerateNormals(bool invert, List<DGRP3DVert> verts, IList<int> indices)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < indices.Count; i += 3)
|
||||||
|
{
|
||||||
|
var v1 = verts[indices[i + 1]].Position - verts[indices[i]].Position;
|
||||||
|
var v2 = verts[indices[i + 2]].Position - verts[indices[i + 1]].Position;
|
||||||
|
var cross = invert ? Vector3.Cross(v2, v1) : Vector3.Cross(v1, v2);
|
||||||
|
for (int j = 0; j < 3; j++)
|
||||||
|
{
|
||||||
|
var id = indices[i + j];
|
||||||
|
var v = verts[id];
|
||||||
|
v.Normal += cross;
|
||||||
|
verts[id] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < verts.Count; i++)
|
||||||
|
{
|
||||||
|
var v = verts[i];
|
||||||
|
v.Normal.Normalize();
|
||||||
|
verts[i] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<int> StripToTri(List<int> ind)
|
||||||
|
{
|
||||||
|
var result = new List<int>();
|
||||||
|
for (int i = 0; i < ind.Count; i += 2)
|
||||||
|
{
|
||||||
|
if (i>0)
|
||||||
|
{
|
||||||
|
result.Add(ind[i - 2]);
|
||||||
|
result.Add(ind[i - 1]);
|
||||||
|
result.Add(ind[i]);
|
||||||
|
|
||||||
|
result.Add(ind[i - 1]);
|
||||||
|
result.Add(ind[i + 1]);
|
||||||
|
result.Add(ind[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
server/tso.files/RC/DGRPRCParams.cs
Executable file
45
server/tso.files/RC/DGRPRCParams.cs
Executable file
|
@ -0,0 +1,45 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
|
||||||
|
namespace FSO.Files.RC
|
||||||
|
{
|
||||||
|
public class DGRPRCParams
|
||||||
|
{
|
||||||
|
public bool[] Rotations = new bool[] { true, true, true, true };
|
||||||
|
public bool DoorFix; //depending on subtile, disable certain rotations to fix door.
|
||||||
|
public bool CounterFix; //extrapolate z on sides of counter to the edge of the tile.
|
||||||
|
|
||||||
|
public int StartDGRP;
|
||||||
|
public int EndDGRP;
|
||||||
|
public bool BlenderTweak;
|
||||||
|
public bool Simplify = true;
|
||||||
|
|
||||||
|
public bool InRange(int dgrp)
|
||||||
|
{
|
||||||
|
return ((StartDGRP == EndDGRP && EndDGRP == 0) || (dgrp >= StartDGRP && dgrp <= EndDGRP));
|
||||||
|
}
|
||||||
|
|
||||||
|
public DGRPRCParams() { }
|
||||||
|
public DGRPRCParams(IoBuffer io, int version)
|
||||||
|
{
|
||||||
|
Rotations = new bool[4];
|
||||||
|
for (int i = 0; i < 4; i++) Rotations[i] = io.ReadByte() > 0;
|
||||||
|
DoorFix = io.ReadByte() > 0;
|
||||||
|
CounterFix = io.ReadByte() > 0;
|
||||||
|
StartDGRP = io.ReadInt32();
|
||||||
|
EndDGRP = io.ReadInt32();
|
||||||
|
BlenderTweak = io.ReadByte() > 0;
|
||||||
|
Simplify = io.ReadByte() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(IoWriter io)
|
||||||
|
{
|
||||||
|
foreach (var rotation in Rotations) io.WriteByte((byte)(rotation ? 1 : 0));
|
||||||
|
io.WriteByte((byte)(DoorFix ? 1 : 0));
|
||||||
|
io.WriteByte((byte)(CounterFix ? 1 : 0));
|
||||||
|
io.WriteInt32(StartDGRP);
|
||||||
|
io.WriteInt32(EndDGRP);
|
||||||
|
io.WriteByte((byte)(BlenderTweak ? 1 : 0));
|
||||||
|
io.WriteByte((byte)(Simplify ? 1 : 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
267
server/tso.files/RC/FSOF.cs
Executable file
267
server/tso.files/RC/FSOF.cs
Executable file
|
@ -0,0 +1,267 @@
|
||||||
|
using FSO.Common;
|
||||||
|
using FSO.Common.Utils;
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace FSO.Files.RC
|
||||||
|
{
|
||||||
|
public class FSOF
|
||||||
|
{
|
||||||
|
public int TexCompressionType; //RGBA8, DXT5
|
||||||
|
|
||||||
|
public int FloorWidth;
|
||||||
|
public int FloorHeight;
|
||||||
|
public int WallWidth;
|
||||||
|
public int WallHeight;
|
||||||
|
|
||||||
|
public Color NightLightColor;
|
||||||
|
|
||||||
|
public byte[] FloorTextureData;
|
||||||
|
public byte[] WallTextureData;
|
||||||
|
|
||||||
|
public byte[] NightFloorTextureData;
|
||||||
|
public byte[] NightWallTextureData;
|
||||||
|
|
||||||
|
public int[] FloorIndices;
|
||||||
|
public DGRP3DVert[] FloorVertices;
|
||||||
|
|
||||||
|
public int[] WallIndices;
|
||||||
|
public DGRP3DVert[] WallVertices;
|
||||||
|
|
||||||
|
//loaded data
|
||||||
|
public Texture2D FloorTexture;
|
||||||
|
public Texture2D WallTexture;
|
||||||
|
public Texture2D NightFloorTexture;
|
||||||
|
public Texture2D NightWallTexture;
|
||||||
|
|
||||||
|
public VertexBuffer FloorVGPU;
|
||||||
|
public IndexBuffer FloorIGPU;
|
||||||
|
public int FloorPrims;
|
||||||
|
|
||||||
|
public VertexBuffer WallVGPU;
|
||||||
|
public IndexBuffer WallIGPU;
|
||||||
|
public int WallPrims;
|
||||||
|
|
||||||
|
public static int CURRENT_VERSION = 1;
|
||||||
|
public int Version = CURRENT_VERSION;
|
||||||
|
public bool Compressed = true;
|
||||||
|
|
||||||
|
public void Save(Stream stream)
|
||||||
|
{
|
||||||
|
var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN);
|
||||||
|
io.WriteCString("FSOf", 4);
|
||||||
|
io.WriteInt32(CURRENT_VERSION);
|
||||||
|
|
||||||
|
io.WriteByte((byte)(Compressed ? 1 : 0));
|
||||||
|
|
||||||
|
MemoryStream target = null;
|
||||||
|
GZipStream compressed = null;
|
||||||
|
var cio = io;
|
||||||
|
if (Compressed)
|
||||||
|
{
|
||||||
|
//target = new MemoryStream();
|
||||||
|
compressed = new GZipStream(stream, CompressionMode.Compress);
|
||||||
|
cio = IoWriter.FromStream(compressed, ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
cio.WriteInt32(TexCompressionType);
|
||||||
|
cio.WriteInt32(FloorWidth);
|
||||||
|
cio.WriteInt32(FloorHeight);
|
||||||
|
cio.WriteInt32(WallWidth);
|
||||||
|
cio.WriteInt32(WallHeight);
|
||||||
|
cio.WriteByte((byte)((NightFloorTextureData == null)?0:1)); //has night tex?
|
||||||
|
|
||||||
|
cio.WriteInt32(FloorTextureData.Length);
|
||||||
|
cio.WriteBytes(FloorTextureData);
|
||||||
|
cio.WriteInt32(WallTextureData.Length);
|
||||||
|
cio.WriteBytes(WallTextureData);
|
||||||
|
|
||||||
|
if (NightFloorTextureData != null)
|
||||||
|
{
|
||||||
|
cio.WriteInt32(NightFloorTextureData.Length);
|
||||||
|
cio.WriteBytes(NightFloorTextureData);
|
||||||
|
cio.WriteInt32(NightWallTextureData.Length);
|
||||||
|
cio.WriteBytes(NightWallTextureData);
|
||||||
|
cio.WriteUInt32(NightLightColor.PackedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteVerts(FloorVertices, FloorIndices, cio);
|
||||||
|
WriteVerts(WallVertices, WallIndices, cio);
|
||||||
|
|
||||||
|
if (Compressed)
|
||||||
|
{
|
||||||
|
compressed.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Read(Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var fsof = io.ReadCString(4);
|
||||||
|
if (fsof != "FSOf") throw new Exception("Invalid FSOf!");
|
||||||
|
Version = io.ReadInt32();
|
||||||
|
Compressed = io.ReadByte() > 0;
|
||||||
|
|
||||||
|
GZipStream compressed = null;
|
||||||
|
var cio = io;
|
||||||
|
if (Compressed)
|
||||||
|
{
|
||||||
|
compressed = new GZipStream(stream, CompressionMode.Decompress);
|
||||||
|
cio = IoBuffer.FromStream(compressed, ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
TexCompressionType = cio.ReadInt32();
|
||||||
|
FloorWidth = cio.ReadInt32();
|
||||||
|
FloorHeight = cio.ReadInt32();
|
||||||
|
WallWidth = cio.ReadInt32();
|
||||||
|
WallHeight = cio.ReadInt32();
|
||||||
|
var hasNight = cio.ReadByte() > 0;
|
||||||
|
|
||||||
|
var floorTSize = cio.ReadInt32();
|
||||||
|
FloorTextureData = cio.ReadBytes(floorTSize);
|
||||||
|
var wallTSize = cio.ReadInt32();
|
||||||
|
WallTextureData = cio.ReadBytes(wallTSize);
|
||||||
|
|
||||||
|
if (hasNight)
|
||||||
|
{
|
||||||
|
floorTSize = cio.ReadInt32();
|
||||||
|
NightFloorTextureData = cio.ReadBytes(floorTSize);
|
||||||
|
wallTSize = cio.ReadInt32();
|
||||||
|
NightWallTextureData = cio.ReadBytes(wallTSize);
|
||||||
|
NightLightColor = new Color(cio.ReadUInt32());
|
||||||
|
}
|
||||||
|
|
||||||
|
var floor = ReadVerts(cio);
|
||||||
|
FloorVertices = floor.Item1;
|
||||||
|
FloorIndices = floor.Item2;
|
||||||
|
var wall = ReadVerts(cio);
|
||||||
|
WallVertices = wall.Item1;
|
||||||
|
WallIndices = wall.Item2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SurfaceFormat GetTexFormat()
|
||||||
|
{
|
||||||
|
return (TexCompressionType == 1) ? SurfaceFormat.Dxt5 : SurfaceFormat.Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadGPU(GraphicsDevice gd)
|
||||||
|
{
|
||||||
|
var format = GetTexFormat();
|
||||||
|
if (format == SurfaceFormat.Dxt5 && !FSOEnvironment.TexCompressSupport)
|
||||||
|
{
|
||||||
|
//todo: software decode DXT5
|
||||||
|
FloorTexture = new Texture2D(gd, FloorWidth, FloorHeight, false, SurfaceFormat.Color);
|
||||||
|
FloorTexture.SetData(TextureUtils.DXT5Decompress(FloorTextureData, FloorWidth, FloorHeight));
|
||||||
|
WallTexture = new Texture2D(gd, WallWidth, WallHeight, false, SurfaceFormat.Color);
|
||||||
|
WallTexture.SetData(TextureUtils.DXT5Decompress(WallTextureData, WallWidth, WallHeight));
|
||||||
|
if (NightFloorTextureData != null)
|
||||||
|
{
|
||||||
|
NightFloorTexture = new Texture2D(gd, FloorWidth, FloorHeight, false, SurfaceFormat.Color);
|
||||||
|
NightFloorTexture.SetData(TextureUtils.DXT5Decompress(NightFloorTextureData, FloorWidth, FloorHeight));
|
||||||
|
NightWallTexture = new Texture2D(gd, WallWidth, WallHeight, false, SurfaceFormat.Color);
|
||||||
|
NightWallTexture.SetData(TextureUtils.DXT5Decompress(NightWallTextureData, WallWidth, WallHeight));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FloorTexture = new Texture2D(gd, FloorWidth, FloorHeight, false, format);
|
||||||
|
FloorTexture.SetData(FloorTextureData);
|
||||||
|
WallTexture = new Texture2D(gd, WallWidth, WallHeight, false, format);
|
||||||
|
WallTexture.SetData(WallTextureData);
|
||||||
|
if (NightFloorTextureData != null)
|
||||||
|
{
|
||||||
|
NightFloorTexture = new Texture2D(gd, FloorWidth, FloorHeight, false, format);
|
||||||
|
NightFloorTexture.SetData(NightFloorTextureData);
|
||||||
|
NightWallTexture = new Texture2D(gd, WallWidth, WallHeight, false, format);
|
||||||
|
NightWallTexture.SetData(NightWallTextureData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FloorVertices.Length > 0)
|
||||||
|
{
|
||||||
|
FloorVGPU = new VertexBuffer(gd, typeof(DGRP3DVert), FloorVertices.Length, BufferUsage.None);
|
||||||
|
FloorVGPU.SetData(FloorVertices);
|
||||||
|
FloorIGPU = new IndexBuffer(gd, IndexElementSize.ThirtyTwoBits, FloorIndices.Length, BufferUsage.None);
|
||||||
|
FloorIGPU.SetData(FloorIndices);
|
||||||
|
FloorPrims = FloorIndices.Length / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WallVertices.Length > 0)
|
||||||
|
{
|
||||||
|
WallVGPU = new VertexBuffer(gd, typeof(DGRP3DVert), WallVertices.Length, BufferUsage.None);
|
||||||
|
WallVGPU.SetData(WallVertices);
|
||||||
|
WallIGPU = new IndexBuffer(gd, IndexElementSize.ThirtyTwoBits, WallIndices.Length, BufferUsage.None);
|
||||||
|
WallIGPU.SetData(WallIndices);
|
||||||
|
WallPrims = WallIndices.Length / 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
FloorTexture?.Dispose();
|
||||||
|
WallTexture?.Dispose();
|
||||||
|
NightFloorTexture?.Dispose();
|
||||||
|
NightWallTexture?.Dispose();
|
||||||
|
FloorVGPU?.Dispose();
|
||||||
|
FloorIGPU?.Dispose();
|
||||||
|
WallVGPU?.Dispose();
|
||||||
|
WallIGPU?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tuple<DGRP3DVert[], int[]> ReadVerts(IoBuffer io)
|
||||||
|
{
|
||||||
|
var vertCount = io.ReadInt32();
|
||||||
|
var bytes = io.ReadBytes(vertCount * Marshal.SizeOf(typeof(DGRP3DVert)));
|
||||||
|
var readVerts = new DGRP3DVert[vertCount];
|
||||||
|
var pinnedHandle = GCHandle.Alloc(readVerts, GCHandleType.Pinned);
|
||||||
|
Marshal.Copy(bytes, 0, pinnedHandle.AddrOfPinnedObject(), bytes.Length);
|
||||||
|
pinnedHandle.Free();
|
||||||
|
|
||||||
|
var indCount = io.ReadInt32();
|
||||||
|
var indices = ToTArray<int>(io.ReadBytes(indCount * 4));
|
||||||
|
|
||||||
|
return new Tuple<DGRP3DVert[], int[]>(readVerts, indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteVerts(DGRP3DVert[] verts, int[] indices, IoWriter io)
|
||||||
|
{
|
||||||
|
io.WriteInt32(verts.Length);
|
||||||
|
foreach (var vert in verts)
|
||||||
|
{
|
||||||
|
io.WriteFloat(vert.Position.X);
|
||||||
|
io.WriteFloat(vert.Position.Y);
|
||||||
|
io.WriteFloat(vert.Position.Z);
|
||||||
|
io.WriteFloat(vert.TextureCoordinate.X);
|
||||||
|
io.WriteFloat(vert.TextureCoordinate.Y);
|
||||||
|
io.WriteFloat(vert.Normal.X);
|
||||||
|
io.WriteFloat(vert.Normal.Y);
|
||||||
|
io.WriteFloat(vert.Normal.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteInt32(indices.Length);
|
||||||
|
io.WriteBytes(ToByteArray(indices.ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T[] ToTArray<T>(byte[] input)
|
||||||
|
{
|
||||||
|
var result = new T[input.Length / Marshal.SizeOf(typeof(T))];
|
||||||
|
Buffer.BlockCopy(input, 0, result, 0, input.Length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] ToByteArray<T>(T[] input)
|
||||||
|
{
|
||||||
|
var result = new byte[input.Length * Marshal.SizeOf(typeof(T))];
|
||||||
|
Buffer.BlockCopy(input, 0, result, 0, result.Length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
server/tso.files/RC/NBHm.cs
Executable file
104
server/tso.files/RC/NBHm.cs
Executable file
|
@ -0,0 +1,104 @@
|
||||||
|
using FSO.Files.Utils;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FSO.Files.RC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Neighbourhood model. Defines the 3D positions of all houses, and provides a model.
|
||||||
|
/// There is a grass layer rendered with the grass shader, and various other layers drawn with textures.
|
||||||
|
/// </summary>
|
||||||
|
public class NBHm
|
||||||
|
{
|
||||||
|
public static int CURRENT_VERSION = 1;
|
||||||
|
public int Version = CURRENT_VERSION;
|
||||||
|
public Dictionary<short, NBHmHouse> Houses = new Dictionary<short, NBHmHouse>();
|
||||||
|
|
||||||
|
public NBHm(OBJ obj)
|
||||||
|
{
|
||||||
|
foreach (var group in obj.FacesByObjgroup)
|
||||||
|
{
|
||||||
|
if (group.Key[0] == 'p')
|
||||||
|
{
|
||||||
|
//lot group
|
||||||
|
var split = group.Key.Split('_');
|
||||||
|
if (split.Length > 1 && split[1] == "floor")
|
||||||
|
{
|
||||||
|
Vector2 minLocation = new Vector2(999999999, 999999999);
|
||||||
|
var candidates = new List<Vector3>();
|
||||||
|
foreach (var inds in group.Value)
|
||||||
|
{
|
||||||
|
var vtx = obj.Vertices[inds[0]-1];
|
||||||
|
if (vtx.X < minLocation.X) { minLocation.X = vtx.X; candidates.Clear(); }
|
||||||
|
if (vtx.Z < minLocation.Y) { minLocation.Y = vtx.Z; candidates.Clear(); }
|
||||||
|
if ((int)vtx.Z == (int)minLocation.Y && (int)vtx.X == (int)minLocation.X) candidates.Add(vtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
var minLocation3 = new Vector3(minLocation.X, candidates.Min(x => x.Y), minLocation.Y);
|
||||||
|
|
||||||
|
//add this house
|
||||||
|
var house = new NBHmHouse()
|
||||||
|
{
|
||||||
|
HouseNum = short.Parse(split[0].Substring(1)),
|
||||||
|
Position = minLocation3
|
||||||
|
};
|
||||||
|
Houses[house.HouseNum] = house;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
io.WriteCString("NBHm", 4);
|
||||||
|
io.WriteInt32(CURRENT_VERSION);
|
||||||
|
io.WriteInt32(Houses.Count);
|
||||||
|
foreach (var house in Houses)
|
||||||
|
{
|
||||||
|
io.WriteInt16(house.Key);
|
||||||
|
io.WriteFloat(house.Value.Position.X);
|
||||||
|
io.WriteFloat(house.Value.Position.Y);
|
||||||
|
io.WriteFloat(house.Value.Position.Z);
|
||||||
|
}
|
||||||
|
io.WriteByte(0); //has model. right now there is no model.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load(Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
|
||||||
|
{
|
||||||
|
var magic = io.ReadCString(4);
|
||||||
|
if (magic != "NBHm") throw new Exception("Not a valid neighbourhood model!");
|
||||||
|
Version = io.ReadInt32();
|
||||||
|
var houseC = io.ReadInt32();
|
||||||
|
for (int i = 0; i < houseC; i++)
|
||||||
|
{
|
||||||
|
var house = new NBHmHouse()
|
||||||
|
{
|
||||||
|
HouseNum = io.ReadInt16(),
|
||||||
|
Position = new Vector3()
|
||||||
|
{
|
||||||
|
X = io.ReadFloat(),
|
||||||
|
Y = io.ReadFloat(),
|
||||||
|
Z = io.ReadFloat()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Houses[house.HouseNum] = house;
|
||||||
|
}
|
||||||
|
io.ReadByte(); //has model. right now there is no model.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NBHmHouse
|
||||||
|
{
|
||||||
|
public short HouseNum;
|
||||||
|
public Vector3 Position;
|
||||||
|
}
|
||||||
|
}
|
70
server/tso.files/RC/OBJReader.cs
Executable file
70
server/tso.files/RC/OBJReader.cs
Executable file
|
@ -0,0 +1,70 @@
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.RC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Simple reader for .OBJ files. Only made to import from blender.
|
||||||
|
/// </summary>
|
||||||
|
public class OBJ
|
||||||
|
{
|
||||||
|
public List<Vector3> Vertices = new List<Vector3>();
|
||||||
|
public List<Vector2> TextureCoords = new List<Vector2>();
|
||||||
|
public List<Vector3> Normals = new List<Vector3>();
|
||||||
|
public Dictionary<string, List<int[]>> FacesByObjgroup = new Dictionary<string, List<int[]>>();
|
||||||
|
|
||||||
|
public OBJ(Stream obj)
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(obj))
|
||||||
|
Read(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Read(StreamReader read)
|
||||||
|
{
|
||||||
|
string objGroup = "_default";
|
||||||
|
string line = "";
|
||||||
|
List<int[]> indices = new List<int[]>();
|
||||||
|
FacesByObjgroup[objGroup] = indices;
|
||||||
|
while ((line = read.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
line = line.TrimStart();
|
||||||
|
var comInd = line.IndexOf("#");
|
||||||
|
if (comInd != -1) line = line.Substring(0, comInd);
|
||||||
|
var split = line.Split(' ');
|
||||||
|
if (split.Length == 0) continue;
|
||||||
|
switch (split[0])
|
||||||
|
{
|
||||||
|
case "o": //set object group
|
||||||
|
objGroup = split[1];
|
||||||
|
if (!FacesByObjgroup.TryGetValue(objGroup, out indices))
|
||||||
|
{
|
||||||
|
indices = new List<int[]>();
|
||||||
|
FacesByObjgroup[objGroup] = indices;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "v":
|
||||||
|
Vertices.Add(new Vector3(float.Parse(split[1], CultureInfo.InvariantCulture), float.Parse(split[2], CultureInfo.InvariantCulture), float.Parse(split[3], CultureInfo.InvariantCulture)));
|
||||||
|
break;
|
||||||
|
case "vt":
|
||||||
|
TextureCoords.Add(new Vector2(float.Parse(split[1], CultureInfo.InvariantCulture), float.Parse(split[2], CultureInfo.InvariantCulture)));
|
||||||
|
break;
|
||||||
|
case "vn":
|
||||||
|
Normals.Add(new Vector3(float.Parse(split[1], CultureInfo.InvariantCulture), float.Parse(split[2], CultureInfo.InvariantCulture), float.Parse(split[3], CultureInfo.InvariantCulture)));
|
||||||
|
break;
|
||||||
|
case "f":
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
var split2 = split[i + 1].Split('/');
|
||||||
|
if (split2.Length == 2)
|
||||||
|
indices.Add(new int[] { int.Parse(split2[0]), int.Parse(split2[1]) });
|
||||||
|
else if (split2.Length == 3)
|
||||||
|
indices.Add(new int[] { int.Parse(split2[0]), int.Parse(split2[1]), int.Parse(split2[2]) });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
server/tso.files/RC/Utils/DepthTreatment.cs
Executable file
65
server/tso.files/RC/Utils/DepthTreatment.cs
Executable file
|
@ -0,0 +1,65 @@
|
||||||
|
using FSO.Common.Utils;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace FSO.Files.RC.Utils
|
||||||
|
{
|
||||||
|
public class DepthTreatment
|
||||||
|
{
|
||||||
|
public static Effect SpriteEffect;
|
||||||
|
public static SpriteBatch Batch;
|
||||||
|
|
||||||
|
public DepthTreatment()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EnsureBatch(GraphicsDevice gd)
|
||||||
|
{
|
||||||
|
if (Batch == null)
|
||||||
|
{
|
||||||
|
Batch = new SpriteBatch(gd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float[] DequantizeDepth(GraphicsDevice gd, Texture2D depthIn)
|
||||||
|
{
|
||||||
|
var wait = new AutoResetEvent(false);
|
||||||
|
float[] data = null;
|
||||||
|
GameThread.InUpdate(() =>
|
||||||
|
{
|
||||||
|
var targetPrep = new RenderTarget2D(gd, depthIn.Width, depthIn.Height, false, SurfaceFormat.Vector4, DepthFormat.None);
|
||||||
|
var target = new RenderTarget2D(gd, depthIn.Width, depthIn.Height, false, SurfaceFormat.Single, DepthFormat.None);
|
||||||
|
gd.SetRenderTarget(targetPrep);
|
||||||
|
|
||||||
|
var effect = SpriteEffect;
|
||||||
|
EnsureBatch(gd);
|
||||||
|
|
||||||
|
effect.CurrentTechnique = effect.Techniques["DerivativeDepth"];
|
||||||
|
effect.Parameters["pixelSize"].SetValue(new Vector2(1f / depthIn.Width, 1f / depthIn.Height));
|
||||||
|
|
||||||
|
Batch.Begin(rasterizerState: RasterizerState.CullNone, effect: effect);
|
||||||
|
Batch.Draw(depthIn, Vector2.Zero, Color.White);
|
||||||
|
Batch.End();
|
||||||
|
|
||||||
|
gd.SetRenderTarget(target);
|
||||||
|
|
||||||
|
effect.CurrentTechnique = effect.Techniques["DequantizeDepth"];
|
||||||
|
|
||||||
|
Batch.Begin(rasterizerState: RasterizerState.CullNone, effect: effect);
|
||||||
|
Batch.Draw(targetPrep, Vector2.Zero, Color.White);
|
||||||
|
Batch.End();
|
||||||
|
|
||||||
|
gd.SetRenderTarget(null);
|
||||||
|
|
||||||
|
data = new float[depthIn.Width * depthIn.Height];
|
||||||
|
target.GetData<float>(data);
|
||||||
|
target.Dispose();
|
||||||
|
targetPrep.Dispose();
|
||||||
|
wait.Set();
|
||||||
|
});
|
||||||
|
wait.WaitOne();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
147
server/tso.files/Tuning.cs
Executable file
147
server/tso.files/Tuning.cs
Executable file
|
@ -0,0 +1,147 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using FSO.Files.FAR3;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace FSO.Files
|
||||||
|
{
|
||||||
|
public struct TuningEntry
|
||||||
|
{
|
||||||
|
public string EntryName;
|
||||||
|
public uint KeyValueCount;
|
||||||
|
public Dictionary<string, string> KeyValues;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return EntryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetNum(string key)
|
||||||
|
{
|
||||||
|
CultureInfo floatParse = CultureInfo.InvariantCulture;
|
||||||
|
return float.Parse(KeyValues[key], floatParse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TuningEntry DEFAULT = new TuningEntry
|
||||||
|
{
|
||||||
|
EntryName = "",
|
||||||
|
KeyValueCount = 0,
|
||||||
|
KeyValues = new Dictionary<string, string>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//NOTE: important tuning variables:
|
||||||
|
//28: object wear repair - important because this stuff doen't run in simantics anymore
|
||||||
|
//36: sim motive decay
|
||||||
|
//24: object deprecation
|
||||||
|
//21: "motivesTunning": regen motives when not online, dead makes motives drain faster...
|
||||||
|
//17: motive score weights for different lot types
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loader/parser for tuning.dat.
|
||||||
|
/// </summary>
|
||||||
|
public class Tuning
|
||||||
|
{
|
||||||
|
private BinaryReader m_Reader;
|
||||||
|
|
||||||
|
public uint EntryCount = 0;
|
||||||
|
public Dictionary<string, TuningEntry> EntriesByName = new Dictionary<string, TuningEntry>();
|
||||||
|
|
||||||
|
public Tuning(byte[] Data)
|
||||||
|
{
|
||||||
|
m_Reader = new BinaryReader(new MemoryStream(Data));
|
||||||
|
|
||||||
|
Create(m_Reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tuning(string Path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Reader = new BinaryReader(File.OpenRead(Path));
|
||||||
|
}
|
||||||
|
catch(Exception)
|
||||||
|
{
|
||||||
|
throw new Exception("Tuning.cs: Invalid path: "+Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Create(m_Reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Create(BinaryReader Reader)
|
||||||
|
{
|
||||||
|
byte BodyType = m_Reader.ReadByte();
|
||||||
|
|
||||||
|
if (BodyType != 0x01 && BodyType != 0x03)
|
||||||
|
throw (new Exception("Tuning.cs: Unknown Persist BodyType!"));
|
||||||
|
|
||||||
|
uint DecompressedSize = m_Reader.ReadUInt32();
|
||||||
|
uint CompressedSize = m_Reader.ReadUInt32();
|
||||||
|
uint StreamBodySize = m_Reader.ReadUInt32(); //same as compressed size for all current examples
|
||||||
|
//Note: wiki.niotso.org says this is actually one byte Compressor and four bytes CompressionParameters.
|
||||||
|
ushort CompressionID = m_Reader.ReadUInt16();
|
||||||
|
|
||||||
|
if (CompressionID != 0xFB10)
|
||||||
|
throw (new Exception("Tuning.cs: Unknown CompressionID!"));
|
||||||
|
|
||||||
|
byte[] Dummy = m_Reader.ReadBytes(3);
|
||||||
|
uint DecompressedSize2 = (uint)((Dummy[0] << 0x10) | (Dummy[1] << 0x08) | +Dummy[2]);
|
||||||
|
|
||||||
|
Decompresser Dec = new Decompresser();
|
||||||
|
Dec.CompressedSize = CompressedSize;
|
||||||
|
Dec.DecompressedSize = DecompressedSize;
|
||||||
|
|
||||||
|
var decompressedData = Dec.Decompress(m_Reader.ReadBytes((int)CompressedSize));
|
||||||
|
|
||||||
|
if (decompressedData == null)
|
||||||
|
throw (new Exception("Tuning.cs: Decompression failed!"));
|
||||||
|
|
||||||
|
m_Reader = new BinaryReader(new MemoryStream(decompressedData));
|
||||||
|
|
||||||
|
EntryCount = m_Reader.ReadUInt32();
|
||||||
|
|
||||||
|
for (int i = 0; i < EntryCount; i++)
|
||||||
|
{
|
||||||
|
TuningEntry Entry = new TuningEntry();
|
||||||
|
Entry.EntryName = DecodeString(m_Reader);
|
||||||
|
Entry.KeyValueCount = m_Reader.ReadUInt32();
|
||||||
|
Entry.KeyValues = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
for (int j = 0; j < Entry.KeyValueCount; j++)
|
||||||
|
{
|
||||||
|
string Key = DecodeString(m_Reader);
|
||||||
|
string Val = DecodeString(m_Reader);
|
||||||
|
|
||||||
|
Entry.KeyValues.Add(Key, Val);
|
||||||
|
}
|
||||||
|
EntriesByName.Add(Entry.EntryName.ToLowerInvariant(), Entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a variable-length pascal string offset by 13 characters.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A decoded string.</returns>
|
||||||
|
private string DecodeString(BinaryReader reader)
|
||||||
|
{
|
||||||
|
int length = 0;
|
||||||
|
byte last = reader.ReadByte();
|
||||||
|
byte j = 0;
|
||||||
|
while ((last & 0x80) > 0)
|
||||||
|
{
|
||||||
|
length |= (last&0x7F) << ((j++) * 7);
|
||||||
|
last = reader.ReadByte();
|
||||||
|
}
|
||||||
|
length |= last << ((j++) * 7);
|
||||||
|
|
||||||
|
byte[] Chars = reader.ReadBytes(length);
|
||||||
|
|
||||||
|
for(int i = 0; i < Chars.Length; i++)
|
||||||
|
Chars[i] = (byte)(Chars[i] - 13);
|
||||||
|
|
||||||
|
return Encoding.ASCII.GetString(Chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
436
server/tso.files/UTK/UTKFile2.cs
Executable file
436
server/tso.files/UTK/UTKFile2.cs
Executable file
|
@ -0,0 +1,436 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.UTK
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a *.UTK file.
|
||||||
|
/// It is used to store compressed wav data.
|
||||||
|
/// </summary>
|
||||||
|
public class UTKFile2
|
||||||
|
{
|
||||||
|
public float[] UTKCosine = {
|
||||||
|
0.0f, -.99677598476409912109375f, -.99032700061798095703125f, -.983879029750823974609375f, -.977430999279022216796875f,
|
||||||
|
-.970982015132904052734375f, -.964533984661102294921875f, -.958085000514984130859375f, -.9516370296478271484375f,
|
||||||
|
-.930754005908966064453125f, -.904959976673126220703125f, -.879167020320892333984375f, -.853372991085052490234375f,
|
||||||
|
-.827579021453857421875f, -.801786005496978759765625f, -.775991976261138916015625f, -.75019800662994384765625f,
|
||||||
|
-.724404990673065185546875f, -.6986110210418701171875f, -.6706349849700927734375f, -.61904799938201904296875f,
|
||||||
|
-.567460000514984130859375f, -.515873014926910400390625f, -.4642859995365142822265625f, -.4126980006694793701171875f,
|
||||||
|
-.361110985279083251953125f, -.309523999691009521484375f, -.257937014102935791015625f, -.20634900033473968505859375f,
|
||||||
|
-.1547619998455047607421875f, -.10317499935626983642578125f, -.05158700048923492431640625f,
|
||||||
|
0.0f,
|
||||||
|
+.05158700048923492431640625f, +.10317499935626983642578125f, +.1547619998455047607421875f, +.20634900033473968505859375f,
|
||||||
|
+.257937014102935791015625f, +.309523999691009521484375f, +.361110985279083251953125f, +.4126980006694793701171875f,
|
||||||
|
+.4642859995365142822265625f, +.515873014926910400390625f, +.567460000514984130859375f, +.61904799938201904296875f,
|
||||||
|
+.6706349849700927734375f, +.6986110210418701171875f, +.724404990673065185546875f, +.75019800662994384765625f,
|
||||||
|
+.775991976261138916015625f, +.801786005496978759765625f, +.827579021453857421875f, +.853372991085052490234375f,
|
||||||
|
+.879167020320892333984375f, +.904959976673126220703125f, +.930754005908966064453125f, +.9516370296478271484375f,
|
||||||
|
+.958085000514984130859375f, +.964533984661102294921875f, +.970982015132904052734375f, +.977430999279022216796875f,
|
||||||
|
+.983879029750823974609375f, +.99032700061798095703125f, +.99677598476409912109375f
|
||||||
|
};
|
||||||
|
|
||||||
|
private byte[] UTKCodebook = {
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 25,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 0,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 26,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||||
|
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 2,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||||
|
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3
|
||||||
|
};
|
||||||
|
|
||||||
|
private byte[] m_UTKCodeSkips = { 8, 7, 8, 7, 2, 2, 2, 3, 3, 4, 4, 3, 3, 5, 5, 4, 4, 6, 6, 5, 5, 7, 7, 6, 6, 8, 8, 7, 7 };
|
||||||
|
|
||||||
|
private string m_ID = ""; //A 4-byte string identifier equal to "UTM0".
|
||||||
|
private uint m_DecompressedSize; //The decompressed size of the audio stream.
|
||||||
|
private uint m_WaveFormatXSize; //The size in bytes of the WAVEFORMATEX structure to follow; must be 20.
|
||||||
|
|
||||||
|
//WAVEFORMATEX
|
||||||
|
//The decoded audio format; set to WAVE_FORMAT_PCM (0x0001).
|
||||||
|
private ushort m_FormatTag;
|
||||||
|
//Number of channels in the decoded audio data.
|
||||||
|
private ushort m_NumChannels;
|
||||||
|
//Sampling rate used in the decoded audio data.
|
||||||
|
private uint m_SamplesPerSec;
|
||||||
|
//Bytes per second consumed by the decoded audio data; equal to
|
||||||
|
//nChannels*nSamplesPerSec*wBitsPerSample/8 or nSamplesPerSec*nBlockAlign
|
||||||
|
private uint m_AvgBytesPerSec;
|
||||||
|
//The number of bytes consumed by an audio frame (one sample for each channel) in the decoded audio data;
|
||||||
|
//equal to nChannels*wBitsPerSample/8.
|
||||||
|
private ushort m_BlockAlign;
|
||||||
|
//The bits per sample for one audio channel in the decoded audio data; 8-, 16-, or 24-bit, etc.
|
||||||
|
private ushort m_BitsPerSample;
|
||||||
|
//The size in bytes of extra format information appended to the end of the WAVEFORMATEX structure;
|
||||||
|
//must be 0. Note that in the original WAVEFORMATEX, this parameter is a WORD, not a DWORD.
|
||||||
|
private uint m_AppendSize;
|
||||||
|
|
||||||
|
private MemoryStream m_DecompressedStream;
|
||||||
|
private BinaryReader m_Reader;
|
||||||
|
private BinaryWriter m_Writer;
|
||||||
|
private int m_UnreadBitsValue, m_UnreadBitsCount;
|
||||||
|
private bool m_HalvedExcitation;
|
||||||
|
private byte m_VoicedThreshold;
|
||||||
|
private float[] m_InnovationPower = new float[64];
|
||||||
|
private float[] m_RC = new float[12];
|
||||||
|
private float[] m_History = new float[12];
|
||||||
|
private float[] m_DecompressedFrame = new float[756];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a decompressed wav stream.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] DecompressedWav
|
||||||
|
{
|
||||||
|
get { return m_DecompressedStream.ToArray(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new UTKFile2 instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="FileData">The file data to read from.</param>
|
||||||
|
public UTKFile2(byte[] FileData)
|
||||||
|
{
|
||||||
|
m_DecompressedStream = new MemoryStream();
|
||||||
|
m_Reader = new BinaryReader(new MemoryStream(FileData));
|
||||||
|
|
||||||
|
ReadHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new UTKFile2 instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Filepath">The path to a *.utk file to read from.</param>
|
||||||
|
public UTKFile2(string Filepath)
|
||||||
|
{
|
||||||
|
m_DecompressedStream = new MemoryStream();
|
||||||
|
m_Reader = new BinaryReader(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||||
|
|
||||||
|
ReadHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the header of a .utk file.
|
||||||
|
/// </summary>
|
||||||
|
private void ReadHeader()
|
||||||
|
{
|
||||||
|
m_ID = new string(m_Reader.ReadChars(4));
|
||||||
|
m_DecompressedSize = m_Reader.ReadUInt32();
|
||||||
|
m_WaveFormatXSize = m_Reader.ReadUInt32();
|
||||||
|
m_FormatTag = m_Reader.ReadUInt16();
|
||||||
|
m_NumChannels = m_Reader.ReadUInt16();
|
||||||
|
m_SamplesPerSec = m_Reader.ReadUInt32();
|
||||||
|
m_AvgBytesPerSec = m_Reader.ReadUInt32();
|
||||||
|
m_BlockAlign = m_Reader.ReadUInt16();
|
||||||
|
m_BitsPerSample = m_Reader.ReadUInt16();
|
||||||
|
m_AppendSize = m_Reader.ReadUInt32();
|
||||||
|
|
||||||
|
m_DecompressedStream = new MemoryStream();
|
||||||
|
m_Writer = new BinaryWriter(m_DecompressedStream);
|
||||||
|
|
||||||
|
//Write wav-header.
|
||||||
|
uint dwDataSize = m_DecompressedSize;
|
||||||
|
uint dwFMTSize = 16;
|
||||||
|
uint dwRIFFSize = 36 + dwDataSize;
|
||||||
|
|
||||||
|
m_Writer.Write(new char[] { 'R', 'I', 'F', 'F' });
|
||||||
|
m_Writer.Write(dwRIFFSize); //Size of file minus this field and the above field.
|
||||||
|
m_Writer.Write(new char[] { 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ' });
|
||||||
|
m_Writer.Write(dwFMTSize); //Size of WAVEFORMATEX structure (all the data that comes after this field).
|
||||||
|
m_Writer.Write(m_FormatTag);
|
||||||
|
m_Writer.Write(m_NumChannels);
|
||||||
|
m_Writer.Write(m_SamplesPerSec);
|
||||||
|
m_Writer.Write(m_AvgBytesPerSec);
|
||||||
|
m_Writer.Write(m_BlockAlign);
|
||||||
|
m_Writer.Write(m_BitsPerSample);
|
||||||
|
m_Writer.Write(new char[] { 'd', 'a', 't', 'a' });
|
||||||
|
m_Writer.Write(dwDataSize);
|
||||||
|
|
||||||
|
m_UnreadBitsValue = m_Reader.ReadByte();
|
||||||
|
m_UnreadBitsCount = 8;
|
||||||
|
m_HalvedExcitation = (ReadBits(1) != 0);
|
||||||
|
m_VoicedThreshold = (byte)(32 - ReadBits(4));
|
||||||
|
|
||||||
|
m_InnovationPower[0] = (ReadBits(4) + 1) * 8; //Significand.
|
||||||
|
|
||||||
|
float Base = 1.04f + (float)(ReadBits(6)) / 1000.0f;
|
||||||
|
for (int i = 1; i < 64; i++)
|
||||||
|
m_InnovationPower[i] = m_InnovationPower[i - 1] * Base;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte ReadBits(byte Bits)
|
||||||
|
{
|
||||||
|
byte Value = (byte)(m_UnreadBitsValue & (255 >> (8 - Bits)));
|
||||||
|
m_UnreadBitsValue >>= Bits;
|
||||||
|
m_UnreadBitsCount -= Bits;
|
||||||
|
|
||||||
|
if ((m_UnreadBitsCount < 8) && (m_Reader.BaseStream.Position < m_Reader.BaseStream.Length))
|
||||||
|
{
|
||||||
|
m_UnreadBitsValue |= m_Reader.ReadByte() << m_UnreadBitsCount;
|
||||||
|
m_UnreadBitsCount += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the lesser of two ints.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="A">The first value.</param>
|
||||||
|
/// <param name="B">The second value.</param>
|
||||||
|
/// <returns>The lesser of the two ints.</returns>
|
||||||
|
private int Lesser(int A, int B)
|
||||||
|
{
|
||||||
|
return (A < B ? A : B);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clamps a value between an upper and lower bound.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the value to clamp.</typeparam>
|
||||||
|
/// <param name="value">The value to clamp.</param>
|
||||||
|
/// <param name="max">Upper bound.</param>
|
||||||
|
/// <param name="min">Lower bound.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T Clamp<T>(T value, T max, T min)
|
||||||
|
where T : System.IComparable<T>
|
||||||
|
{
|
||||||
|
T result = value;
|
||||||
|
if (value.CompareTo(max) < 0)
|
||||||
|
result = max;
|
||||||
|
if (value.CompareTo(min) > 0)
|
||||||
|
result = min;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes the UTK data in this file.
|
||||||
|
/// </summary>
|
||||||
|
public void UTKDecode()
|
||||||
|
{
|
||||||
|
uint Frames = m_DecompressedSize / m_BlockAlign;
|
||||||
|
|
||||||
|
while (Frames > 0)
|
||||||
|
{
|
||||||
|
int BlockSize = Lesser((int)Frames, 432);
|
||||||
|
DecodeFrame();
|
||||||
|
|
||||||
|
for (int i = 0; i < BlockSize; i++)
|
||||||
|
{
|
||||||
|
int Value = (int)Math.Round(m_DecompressedFrame[324 + i]);
|
||||||
|
Value = Clamp<int>(Value, -32768, 32767);
|
||||||
|
m_Writer.Write((ushort)Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Frames -= (uint)BlockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Writer.Flush();
|
||||||
|
|
||||||
|
m_Reader.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DecodeFrame()
|
||||||
|
{
|
||||||
|
float[] Excitation = new float[118]; //includes 5 0-valued samples to both the left and the right.
|
||||||
|
float[] RCDelta = new float[12];
|
||||||
|
bool Voiced = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
byte index = ReadBits((byte)((i < 4) ? 6 : 5));
|
||||||
|
|
||||||
|
if (i == 0 && index < m_VoicedThreshold)
|
||||||
|
Voiced = true;
|
||||||
|
|
||||||
|
RCDelta[i] = (UTKCosine[index + ((i < 4) ? 0 : 16)] - m_RC[i]) / 4.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
int Phase = ReadBits(8);
|
||||||
|
|
||||||
|
if (i == 0 && Phase > 216)
|
||||||
|
Phase = 216;
|
||||||
|
|
||||||
|
float PitchGain = (float)(ReadBits(4)) / 15.0f;
|
||||||
|
float InnovationGain = m_InnovationPower[ReadBits(6)];
|
||||||
|
|
||||||
|
if (m_HalvedExcitation == false)
|
||||||
|
{
|
||||||
|
GenerateExcitation(5, ref Excitation, Voiced, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Fill the excitation window with half as many samples and interpolate the rest.
|
||||||
|
int Alignment = ReadBits(1); //whether to fill the even or odd samples.
|
||||||
|
bool FillWithZero = (ReadBits(1) != 0);
|
||||||
|
int Offset = 5 + (1 - Alignment);
|
||||||
|
|
||||||
|
GenerateExcitation(5 + Alignment, ref Excitation, Voiced, 2);
|
||||||
|
|
||||||
|
if (FillWithZero)
|
||||||
|
{
|
||||||
|
for (int j = Offset; j < Offset + 108; j += 2)
|
||||||
|
Excitation[j] = 0.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Use sinc interpolation with 6 neighboring samples.
|
||||||
|
for (int j = Offset; j < Offset + 108; j += 2)
|
||||||
|
Excitation[j] =
|
||||||
|
(Excitation[j - 1] + Excitation[j + 1]) * .5973859429f
|
||||||
|
- (Excitation[j - 3] + Excitation[j + 3]) * .1145915613f
|
||||||
|
+ (Excitation[j - 5] + Excitation[j + 5]) * .0180326793f;
|
||||||
|
|
||||||
|
InnovationGain /= 2.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < 108; j++)
|
||||||
|
m_DecompressedFrame[324 + 108 * i + j] = InnovationGain * Excitation[5 + j] + PitchGain * m_DecompressedFrame[108 * i + j + (216 - Phase)];
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Copy(m_DecompressedFrame, 324 + 108, m_DecompressedFrame, 0, 324);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
//Linearly interpolate the reflection coefficients for the current subframe.
|
||||||
|
for (int j = 0; j < 12; j++)
|
||||||
|
m_RC[j] += RCDelta[j];
|
||||||
|
|
||||||
|
Synthesize(i * 12, (i != 3) ? 12 : 396);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateExcitation(int Offset, ref float[] Excitation, bool Voiced, int Interval)
|
||||||
|
{
|
||||||
|
if (Voiced)
|
||||||
|
{
|
||||||
|
int Table = 0;
|
||||||
|
int i = Offset;
|
||||||
|
|
||||||
|
while (i < Offset + 108)
|
||||||
|
{
|
||||||
|
byte code = UTKCodebook[(Table << 8) | (m_UnreadBitsValue & 0xFF)];
|
||||||
|
//Table = (code < 2 || code > 8);
|
||||||
|
Table = (code < 2 || code > 8) ? 1 : 0;
|
||||||
|
ReadBits(m_UTKCodeSkips[code]);
|
||||||
|
|
||||||
|
if (code >= 4)
|
||||||
|
{
|
||||||
|
//Fill a sample with a value specified by the code; magnitude is limited to 6.0
|
||||||
|
Excitation[i] = (code - 1) / 4;
|
||||||
|
if ((code & 1) != 0)
|
||||||
|
Excitation[i] *= -1.0f;
|
||||||
|
|
||||||
|
i += Interval;
|
||||||
|
}
|
||||||
|
else if (code >= 2)
|
||||||
|
{
|
||||||
|
//Fill between 7 and 70 samples with 0s
|
||||||
|
int x = ReadBits(6) + 7;
|
||||||
|
x = Lesser(x, (Offset + 108 - i) / Interval);
|
||||||
|
|
||||||
|
while (x > 0)
|
||||||
|
{
|
||||||
|
Excitation[i] = 0.0f;
|
||||||
|
|
||||||
|
i += Interval;
|
||||||
|
x--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Fill a sample with a custom value with magnitude >= 7.0
|
||||||
|
Excitation[i] = 7.0f;
|
||||||
|
while (ReadBits(1) != 0)
|
||||||
|
Excitation[i]++;
|
||||||
|
|
||||||
|
if (ReadBits(1) == 0)
|
||||||
|
Excitation[i] *= -1.0f;
|
||||||
|
|
||||||
|
i += Interval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Unvoiced: restrict all samples to 0.0, -2.0, or +2.0 without using the codebook
|
||||||
|
for (int i = Offset; i < Offset + 108; i += Interval)
|
||||||
|
{
|
||||||
|
if (ReadBits(1) == 0) Excitation[i] = 0.0f;
|
||||||
|
else if (ReadBits(1) == 0) Excitation[i] = -2.0f;
|
||||||
|
else Excitation[i] = 2.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Synthesize(int Sample, int Samples)
|
||||||
|
{
|
||||||
|
float[] LPC = new float[12];
|
||||||
|
int offset = -1;
|
||||||
|
RCtoLPC(ref m_RC, ref LPC);
|
||||||
|
|
||||||
|
while (Samples > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
if (++offset == 12) offset = 0;
|
||||||
|
m_DecompressedFrame[324 + Sample] += LPC[i] * m_History[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
m_History[offset--] = m_DecompressedFrame[324 + Sample++];
|
||||||
|
Samples--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RCtoLPC(ref float[] RC, ref float[] LPC)
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
float[] RCTemp = new float[12], LPCTemp = new float[12];
|
||||||
|
RCTemp[0] = 1.0f;
|
||||||
|
Array.Copy(RC, 0, RCTemp, 1, 11);
|
||||||
|
|
||||||
|
for (i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
LPC[i] = 0.0f;
|
||||||
|
|
||||||
|
for (j = 11; j >= 0; j--)
|
||||||
|
{
|
||||||
|
LPC[i] -= RC[j] * RCTemp[j];
|
||||||
|
if (j != 11)
|
||||||
|
RCTemp[j + 1] = RCTemp[j] + RC[j] * LPC[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
RCTemp[0] = LPCTemp[i] = LPC[i];
|
||||||
|
|
||||||
|
for (j = 0; j < i; j++)
|
||||||
|
LPC[i] -= LPCTemp[i - j - 1] * LPC[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
server/tso.files/Utils/BCFReadProxy.cs
Executable file
128
server/tso.files/Utils/BCFReadProxy.cs
Executable file
|
@ -0,0 +1,128 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FSO.Files.Utils
|
||||||
|
{
|
||||||
|
public interface BCFReadProxy : IDisposable
|
||||||
|
{
|
||||||
|
byte ReadByte();
|
||||||
|
ushort ReadUInt16();
|
||||||
|
short ReadInt16();
|
||||||
|
int ReadInt32();
|
||||||
|
uint ReadUInt32();
|
||||||
|
float ReadFloat();
|
||||||
|
string ReadPascalString();
|
||||||
|
string ReadLongPascalString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface BCFWriteProxy : IDisposable
|
||||||
|
{
|
||||||
|
void WriteByte(byte data);
|
||||||
|
void WriteUInt16(ushort data);
|
||||||
|
void WriteInt16(short data);
|
||||||
|
void WriteInt32(int data);
|
||||||
|
void WriteUInt32(uint data);
|
||||||
|
void WriteFloat(float data);
|
||||||
|
void WritePascalString(string data);
|
||||||
|
void WriteLongPascalString(string data);
|
||||||
|
|
||||||
|
void SetGrouping(int groupSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BCFReadString : BCFReadProxy
|
||||||
|
{
|
||||||
|
private StreamReader Reader;
|
||||||
|
public int Version;
|
||||||
|
private string[] NumBuf = new string[0];
|
||||||
|
private int NumInd = 1;
|
||||||
|
|
||||||
|
public BCFReadString(Stream input, bool version)
|
||||||
|
{
|
||||||
|
Reader = new StreamReader(input);
|
||||||
|
|
||||||
|
if (!version) return;
|
||||||
|
//skip to version
|
||||||
|
var line = "";
|
||||||
|
while (!line.StartsWith("version "))
|
||||||
|
line = Reader.ReadLine();
|
||||||
|
Version = int.Parse(line.Substring(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ReadNum()
|
||||||
|
{
|
||||||
|
//contrary to popular belief, this function that returns a string does indeed read a number
|
||||||
|
if (NumInd >= NumBuf.Length)
|
||||||
|
{
|
||||||
|
NumBuf = Reader.ReadLine().Trim().Split(' ').ToArray();
|
||||||
|
NumInd = 0;
|
||||||
|
}
|
||||||
|
return NumBuf[NumInd++];
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte ReadByte() { return byte.Parse(ReadNum()); }
|
||||||
|
public ushort ReadUInt16() { return ushort.Parse(ReadNum()); }
|
||||||
|
public short ReadInt16() { return short.Parse(ReadNum()); }
|
||||||
|
public int ReadInt32() { return int.Parse(ReadNum()); }
|
||||||
|
public uint ReadUInt32() { return uint.Parse(ReadNum()); }
|
||||||
|
public float ReadFloat() { return float.Parse(ReadNum(), CultureInfo.InvariantCulture); }
|
||||||
|
public string ReadPascalString() { return Reader.ReadLine(); }
|
||||||
|
public string ReadLongPascalString() { return Reader.ReadLine(); }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Reader.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BCFWriteString : BCFWriteProxy
|
||||||
|
{
|
||||||
|
private StreamWriter Writer;
|
||||||
|
public int Version;
|
||||||
|
private int GroupSize = 1;
|
||||||
|
private int GroupInd;
|
||||||
|
|
||||||
|
public BCFWriteString(Stream input, bool version)
|
||||||
|
{
|
||||||
|
Writer = new StreamWriter(input);
|
||||||
|
|
||||||
|
if (!version) return;
|
||||||
|
//write out default version
|
||||||
|
Writer.WriteLine("version 300");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetGrouping(int groupSize)
|
||||||
|
{
|
||||||
|
if (GroupInd > 0) Writer.WriteLine();
|
||||||
|
GroupInd = 0;
|
||||||
|
GroupSize = groupSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteNum(string num)
|
||||||
|
{
|
||||||
|
if (GroupSize-1 >= GroupInd)
|
||||||
|
{
|
||||||
|
Writer.WriteLine(num);
|
||||||
|
GroupInd = 0;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
Writer.Write(num + " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteByte(byte data) { WriteNum(data.ToString()); }
|
||||||
|
public void WriteUInt16(ushort data) { WriteNum(data.ToString()); }
|
||||||
|
public void WriteInt16(short data) { WriteNum(data.ToString()); }
|
||||||
|
public void WriteInt32(int data) { WriteNum(data.ToString()); }
|
||||||
|
public void WriteUInt32(uint data) { WriteNum(data.ToString()); }
|
||||||
|
public void WriteFloat(float data) { WriteNum(data.ToString(CultureInfo.InvariantCulture)); }
|
||||||
|
public void WritePascalString(string data) { WriteNum(data); }
|
||||||
|
public void WriteLongPascalString(string data) { WriteNum(data); }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Writer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
server/tso.files/Utils/CurLoader.cs
Executable file
74
server/tso.files/Utils/CurLoader.cs
Executable file
|
@ -0,0 +1,74 @@
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Utils
|
||||||
|
{
|
||||||
|
public static class CurLoader
|
||||||
|
{
|
||||||
|
public static MouseCursor LoadMonoCursor(GraphicsDevice gd, Stream stream)
|
||||||
|
{
|
||||||
|
var cur = LoadCursor(gd, stream);
|
||||||
|
return MouseCursor.FromTexture2D(cur.Item1, cur.Item2.X, cur.Item2.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Tuple<Texture2D, Point> LoadCursor(GraphicsDevice gd, Stream stream)
|
||||||
|
{
|
||||||
|
using (var io = new BinaryReader(stream))
|
||||||
|
{
|
||||||
|
//little endian
|
||||||
|
var tempbmp = new MemoryStream();
|
||||||
|
var outIO = new BinaryWriter(tempbmp);
|
||||||
|
|
||||||
|
var reserved = io.ReadInt16();
|
||||||
|
var type = io.ReadInt16();
|
||||||
|
if (type != 2) throw new Exception("Not a cursor!");
|
||||||
|
var images = io.ReadInt16(); //currently discard extra images...
|
||||||
|
|
||||||
|
//read first image
|
||||||
|
var width = io.ReadByte();
|
||||||
|
var height = io.ReadByte();
|
||||||
|
var colors = io.ReadByte();
|
||||||
|
var reserved2 = io.ReadByte();
|
||||||
|
var xOffset = io.ReadInt16();
|
||||||
|
var yOffset = io.ReadInt16();
|
||||||
|
var size = io.ReadInt32();
|
||||||
|
var offset = io.ReadInt32();
|
||||||
|
stream.Seek(offset - 22, SeekOrigin.Current);
|
||||||
|
|
||||||
|
//ok... now write the bitmap data to a fake bitmap
|
||||||
|
outIO.Write(new char[] { 'B', 'M' });
|
||||||
|
outIO.Write(size + 14); //size, plus header
|
||||||
|
outIO.Write(0);
|
||||||
|
outIO.Write(14);
|
||||||
|
var data = new byte[size];
|
||||||
|
stream.Read(data, 0, size);
|
||||||
|
outIO.Write(data);
|
||||||
|
|
||||||
|
tempbmp.Seek(0, SeekOrigin.Begin);
|
||||||
|
var tex = ImageLoader.FromStream(gd, tempbmp);
|
||||||
|
|
||||||
|
//our mask is on top. the image is on bottom.
|
||||||
|
var odata = new byte[tex.Width * tex.Height * 4];
|
||||||
|
tex.GetData(odata);
|
||||||
|
var ndata = new byte[tex.Width * tex.Height * 2];
|
||||||
|
var limit = ndata.Length;
|
||||||
|
for (int i=0; i< limit; i+=4)
|
||||||
|
{
|
||||||
|
var j = i + limit;
|
||||||
|
ndata[i] = (byte)((~odata[i]) & odata[j]);
|
||||||
|
ndata[i+1] = ndata[i];
|
||||||
|
ndata[i+2] = ndata[i];
|
||||||
|
ndata[i+3] = (byte)(~odata[i]);
|
||||||
|
}
|
||||||
|
var oTex = new Texture2D(gd, width, height);
|
||||||
|
oTex.SetData(ndata);
|
||||||
|
tex.Dispose();
|
||||||
|
|
||||||
|
return new Tuple<Texture2D, Point>(oTex, new Point(xOffset, yOffset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
server/tso.files/Utils/DiffGenerator.cs
Executable file
130
server/tso.files/Utils/DiffGenerator.cs
Executable file
|
@ -0,0 +1,130 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using xxHashSharp;
|
||||||
|
|
||||||
|
namespace FSO.Files.Utils
|
||||||
|
{
|
||||||
|
public class DiffGenerator
|
||||||
|
{
|
||||||
|
public HashSet<string> SourceFiles; //aka before
|
||||||
|
public HashSet<string> DestFiles; //aka after
|
||||||
|
|
||||||
|
public HashSet<string> AddFiles;
|
||||||
|
public HashSet<string> RemovedFiles;
|
||||||
|
public HashSet<string> SameFiles;
|
||||||
|
|
||||||
|
public string SourcePath;
|
||||||
|
public string DestPath;
|
||||||
|
|
||||||
|
public static List<FileDiff> GetDiffs(string sourcePath, string destPath)
|
||||||
|
{
|
||||||
|
var sourceFiles = new HashSet<string>();
|
||||||
|
RecursiveDirectoryScan(sourcePath, sourceFiles, sourcePath);
|
||||||
|
|
||||||
|
var destFiles = new HashSet<string>();
|
||||||
|
RecursiveDirectoryScan(destPath, destFiles, destPath);
|
||||||
|
|
||||||
|
var addFiles = new HashSet<string>(destFiles);
|
||||||
|
addFiles.ExceptWith(sourceFiles);
|
||||||
|
|
||||||
|
var removedFiles = new HashSet<string>(sourceFiles);
|
||||||
|
removedFiles.ExceptWith(destFiles);
|
||||||
|
|
||||||
|
var sameFiles = new HashSet<string>(sourceFiles);
|
||||||
|
sameFiles.IntersectWith(destFiles);
|
||||||
|
|
||||||
|
var diffs = new List<FileDiff>();
|
||||||
|
|
||||||
|
foreach (var removed in removedFiles)
|
||||||
|
{
|
||||||
|
var bytes = GetFileBytes(Path.Combine(sourcePath, removed));
|
||||||
|
var hash = xxHash.CalculateHash(bytes).ToString("x8");
|
||||||
|
diffs.Add(new FileDiff(FileDiffType.Remove, removed, hash, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var added in addFiles)
|
||||||
|
{
|
||||||
|
var bytes = GetFileBytes(Path.Combine(destPath, added));
|
||||||
|
var hash = xxHash.CalculateHash(bytes).ToString("x8");
|
||||||
|
diffs.Add(new FileDiff(FileDiffType.Add, added, null, hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var same in sameFiles)
|
||||||
|
{
|
||||||
|
var bytesBefore = GetFileBytes(Path.Combine(sourcePath, same));
|
||||||
|
var bytesAfter = GetFileBytes(Path.Combine(destPath, same));
|
||||||
|
var hashBefore = xxHash.CalculateHash(bytesBefore).ToString("x8");
|
||||||
|
var hashAfter = xxHash.CalculateHash(bytesAfter).ToString("x8");
|
||||||
|
diffs.Add(new FileDiff(
|
||||||
|
(hashBefore == hashAfter) ? FileDiffType.Unchanged : FileDiffType.Modify,
|
||||||
|
same, hashBefore, hashAfter));
|
||||||
|
}
|
||||||
|
|
||||||
|
return diffs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] GetFileBytes(string path)
|
||||||
|
{
|
||||||
|
using (var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
|
{
|
||||||
|
var memory = new MemoryStream();
|
||||||
|
stream.CopyTo(memory);
|
||||||
|
var bytes = memory.ToArray();
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RecursiveDirectoryScan(string folder, HashSet<string> fileNames, string basePath)
|
||||||
|
{
|
||||||
|
var files = Directory.GetFiles(folder);
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
fileNames.Add(GetRelativePath(basePath, file));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirs = Directory.GetDirectories(folder);
|
||||||
|
foreach (var dir in dirs)
|
||||||
|
{
|
||||||
|
RecursiveDirectoryScan(dir, fileNames, basePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRelativePath(string relativeTo, string path)
|
||||||
|
{
|
||||||
|
if (!(relativeTo.EndsWith("/") || relativeTo.EndsWith("\\"))) relativeTo += "/";
|
||||||
|
var uri = new Uri(relativeTo);
|
||||||
|
|
||||||
|
var rel = Uri.UnescapeDataString(uri.MakeRelativeUri(new Uri(path)).ToString()).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||||
|
if (rel.Contains(Path.DirectorySeparatorChar.ToString()) == false)
|
||||||
|
{
|
||||||
|
rel = $".{ Path.DirectorySeparatorChar }{ rel }";
|
||||||
|
}
|
||||||
|
return rel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileDiff
|
||||||
|
{
|
||||||
|
public FileDiffType DiffType;
|
||||||
|
public string Path;
|
||||||
|
public string BeforeHash;
|
||||||
|
public string AfterHash;
|
||||||
|
|
||||||
|
public FileDiff(FileDiffType type, string path, string beforeHash, string afterHash)
|
||||||
|
{
|
||||||
|
DiffType = type;
|
||||||
|
Path = path;
|
||||||
|
BeforeHash = beforeHash;
|
||||||
|
AfterHash = afterHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FileDiffType
|
||||||
|
{
|
||||||
|
Add, //after hash only
|
||||||
|
Modify,
|
||||||
|
Remove, //before hash only
|
||||||
|
Unchanged
|
||||||
|
}
|
||||||
|
}
|
7
server/tso.files/Utils/IFileInfoUtilizer.cs
Executable file
7
server/tso.files/Utils/IFileInfoUtilizer.cs
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace FSO.Files.Utils
|
||||||
|
{
|
||||||
|
public interface IFileInfoUtilizer
|
||||||
|
{
|
||||||
|
void SetFilename(string filename);
|
||||||
|
}
|
||||||
|
}
|
9
server/tso.files/Utils/ITextureProvider.cs
Executable file
9
server/tso.files/Utils/ITextureProvider.cs
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace FSO.Files.Utils
|
||||||
|
{
|
||||||
|
public interface ITextureProvider
|
||||||
|
{
|
||||||
|
Texture2D GetTexture(GraphicsDevice device);
|
||||||
|
}
|
||||||
|
}
|
9
server/tso.files/Utils/IWorldTextureProvider.cs
Executable file
9
server/tso.files/Utils/IWorldTextureProvider.cs
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace FSO.Files.Utils
|
||||||
|
{
|
||||||
|
public interface IWorldTextureProvider
|
||||||
|
{
|
||||||
|
WorldTexture GetWorldTexture(GraphicsDevice device);
|
||||||
|
}
|
||||||
|
}
|
357
server/tso.files/Utils/IoBuffer.cs
Executable file
357
server/tso.files/Utils/IoBuffer.cs
Executable file
|
@ -0,0 +1,357 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The order to read bytes in.
|
||||||
|
/// </summary>
|
||||||
|
public enum ByteOrder
|
||||||
|
{
|
||||||
|
BIG_ENDIAN,
|
||||||
|
LITTLE_ENDIAN
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IOBuffer is a very basic wrapper over System.BinaryReader that inherits from IDisposable.
|
||||||
|
/// </summary>
|
||||||
|
public class IoBuffer : IDisposable, BCFReadProxy
|
||||||
|
{
|
||||||
|
private Stream Stream;
|
||||||
|
private BinaryReader Reader;
|
||||||
|
public ByteOrder ByteOrder = ByteOrder.BIG_ENDIAN;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IOBuffer instance from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream"></param>
|
||||||
|
public IoBuffer(Stream stream)
|
||||||
|
{
|
||||||
|
this.Stream = stream;
|
||||||
|
this.Reader = new BinaryReader(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// More to read in this stream?
|
||||||
|
/// </summary>
|
||||||
|
public bool HasMore
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Stream.Position < Stream.Length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skips a number of bytes in the current stream, starting from the current position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="numBytes">Number of bytes to skip.</param>
|
||||||
|
public void Skip(long numBytes)
|
||||||
|
{
|
||||||
|
Reader.BaseStream.Seek(numBytes, SeekOrigin.Current);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Seeks in the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="origin">Where to start from.</param>
|
||||||
|
/// <param name="offset">The offset to seek to.</param>
|
||||||
|
public void Seek(SeekOrigin origin, long offset)
|
||||||
|
{
|
||||||
|
Reader.BaseStream.Seek(offset, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long Position => Stream.Position;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a variable length unsigned integer from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A uint.</returns>
|
||||||
|
public uint ReadVarLen()
|
||||||
|
{
|
||||||
|
uint result = 0;
|
||||||
|
int shift = 0;
|
||||||
|
byte read = 0x80;
|
||||||
|
while ((read&0x80) > 0)
|
||||||
|
{
|
||||||
|
read = ReadByte();
|
||||||
|
result |= (uint)((read & 0x7F) << shift);
|
||||||
|
shift += 7;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads an unsigned 16bit integer from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A ushort.</returns>
|
||||||
|
public ushort ReadUInt16()
|
||||||
|
{
|
||||||
|
var value = Reader.ReadUInt16();
|
||||||
|
if (ByteOrder == ByteOrder.BIG_ENDIAN)
|
||||||
|
{
|
||||||
|
value = Endian.SwapUInt16(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a 16bit integer from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A short.</returns>
|
||||||
|
public short ReadInt16()
|
||||||
|
{
|
||||||
|
var value = Reader.ReadInt16();
|
||||||
|
if (ByteOrder == ByteOrder.BIG_ENDIAN)
|
||||||
|
{
|
||||||
|
value = Endian.SwapInt16(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a 32bit integer from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An int.</returns>
|
||||||
|
public int ReadInt32()
|
||||||
|
{
|
||||||
|
var value = Reader.ReadInt32();
|
||||||
|
if (ByteOrder == ByteOrder.BIG_ENDIAN)
|
||||||
|
{
|
||||||
|
value = Endian.SwapInt32(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a 64bit integer from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An int.</returns>
|
||||||
|
public long ReadInt64()
|
||||||
|
{
|
||||||
|
var value = Reader.ReadInt64();
|
||||||
|
if (ByteOrder == ByteOrder.BIG_ENDIAN)
|
||||||
|
{
|
||||||
|
value = Endian.SwapInt64(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads an unsigned 32bit integer from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A uint.</returns>
|
||||||
|
public uint ReadUInt32()
|
||||||
|
{
|
||||||
|
var value = Reader.ReadUInt32();
|
||||||
|
if (ByteOrder == ByteOrder.BIG_ENDIAN)
|
||||||
|
{
|
||||||
|
value = Endian.SwapUInt32(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a number of ASCII characters from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="num">The number of characters to read.</param>
|
||||||
|
/// <returns>A string, INCLUDING the trailing 0.</returns>
|
||||||
|
public string ReadCString(int num)
|
||||||
|
{
|
||||||
|
return ReadCString(num, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a number of ASCII characters from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="num">The number of characters to read.</param>
|
||||||
|
/// <param name="trimNull">Trim the trailing 0?</param>
|
||||||
|
/// <returns>A string, with or without the trailing 0.</returns>
|
||||||
|
public string ReadCString(int num, bool trimNull)
|
||||||
|
{
|
||||||
|
var result = ASCIIEncoding.ASCII.GetString(Reader.ReadBytes(num));
|
||||||
|
if (trimNull)
|
||||||
|
{
|
||||||
|
/** Trim on \0 **/
|
||||||
|
var io = result.IndexOf('\0');
|
||||||
|
if (io != -1)
|
||||||
|
{
|
||||||
|
result = result.Substring(0, io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a byte from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A byte.</returns>
|
||||||
|
public byte ReadByte()
|
||||||
|
{
|
||||||
|
return Reader.ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a number of bytes from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="num">Number of bytes to read.</param>
|
||||||
|
/// <returns>An byte array.</returns>
|
||||||
|
public byte[] ReadBytes(uint num)
|
||||||
|
{
|
||||||
|
return Reader.ReadBytes((int)num);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a number of bytes from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="num">Number of bytes to read.</param>
|
||||||
|
/// <returns>An byte array.</returns>
|
||||||
|
public byte[] ReadBytes(int num)
|
||||||
|
{
|
||||||
|
return Reader.ReadBytes(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a pascal string from the current stream, which is prefixed by a 16bit short.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string.</returns>
|
||||||
|
public string ReadLongPascalString()
|
||||||
|
{
|
||||||
|
var length = ReadInt16();
|
||||||
|
return Encoding.ASCII.GetString(Reader.ReadBytes(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a C string from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string.</returns>
|
||||||
|
public string ReadNullTerminatedString()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
while (true){
|
||||||
|
char ch = (char)Reader.ReadByte();
|
||||||
|
if (ch == '\0'){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sb.Append(ch);
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadNullTerminatedUTF8()
|
||||||
|
{
|
||||||
|
var sb = new List<byte>();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var b = Reader.ReadByte();
|
||||||
|
if (b == 0) break;
|
||||||
|
sb.Add(b);
|
||||||
|
}
|
||||||
|
return Encoding.UTF8.GetString(sb.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a pascal string from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string.</returns>
|
||||||
|
public string ReadVariableLengthPascalString()
|
||||||
|
{
|
||||||
|
return Reader.ReadString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a pascal string from the current stream, prefixed by a byte.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string.</returns>
|
||||||
|
public string ReadPascalString()
|
||||||
|
{
|
||||||
|
var length = ReadByte();
|
||||||
|
return Encoding.ASCII.GetString(Reader.ReadBytes(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a float from the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A float.</returns>
|
||||||
|
[System.Security.SecuritySafeCritical] // auto-generated
|
||||||
|
public virtual unsafe float ReadFloat()
|
||||||
|
{
|
||||||
|
return Reader.ReadSingle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a mark at the current position in the stream.
|
||||||
|
/// </summary>
|
||||||
|
private long _Mark;
|
||||||
|
public void Mark()
|
||||||
|
{
|
||||||
|
_Mark = Reader.BaseStream.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Seeks in the current stream from the current mark plus the number of bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="numBytes">The number of bytes to add to the offset (mark).</param>
|
||||||
|
public void SeekFromMark(long numBytes)
|
||||||
|
{
|
||||||
|
Reader.BaseStream.Seek(_Mark + numBytes, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable Members
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IOBuffer instance from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">A stream.</param>
|
||||||
|
/// <returns>A new IOBuffer instance.</returns>
|
||||||
|
public static IoBuffer FromStream(Stream stream)
|
||||||
|
{
|
||||||
|
return new IoBuffer(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IOBuffer instance from a stream, using a specified byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">A stream.</param>
|
||||||
|
/// <param name="order">Byte order to use.</param>
|
||||||
|
/// <returns>A new IOBuffer instance.</returns>
|
||||||
|
public static IoBuffer FromStream(Stream stream, ByteOrder order)
|
||||||
|
{
|
||||||
|
var item = FromStream(stream);
|
||||||
|
item.ByteOrder = order;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IOBuffer instance from a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">The byte array to use.</param>
|
||||||
|
/// <returns>A new IOBuffer instance.</returns>
|
||||||
|
public static IoBuffer FromBytes(byte[] bytes)
|
||||||
|
{
|
||||||
|
return FromStream(new MemoryStream(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IOBuffer instance from a byte array, using a specified byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">The byte array to use.</param>
|
||||||
|
/// <param name="order">Byte order to use.</param>
|
||||||
|
/// <returns>A new IOBuffer instance.</returns>
|
||||||
|
public static IoBuffer FromBytes(byte[] bytes, ByteOrder order)
|
||||||
|
{
|
||||||
|
return FromStream(new MemoryStream(bytes), order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
284
server/tso.files/Utils/IoWriter.cs
Executable file
284
server/tso.files/Utils/IoWriter.cs
Executable file
|
@ -0,0 +1,284 @@
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FSO.Files.Utils
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IOBuffer is a very basic wrapper over System.BinaryReader that inherits from IDisposable.
|
||||||
|
/// </summary>
|
||||||
|
public class IoWriter : IDisposable, BCFWriteProxy
|
||||||
|
{
|
||||||
|
private Stream Stream;
|
||||||
|
private BinaryWriter Writer;
|
||||||
|
private bool FloatSwap = false;
|
||||||
|
public ByteOrder ByteOrder = ByteOrder.BIG_ENDIAN;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IOBuffer instance from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream"></param>
|
||||||
|
public IoWriter(Stream stream)
|
||||||
|
{
|
||||||
|
this.Stream = stream;
|
||||||
|
this.Writer = new BinaryWriter(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// More to read in this stream?
|
||||||
|
/// </summary>
|
||||||
|
public bool HasMore
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Stream.Position < Stream.Length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skips a number of bytes in the current stream, starting from the current position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="numBytes">Number of bytes to skip.</param>
|
||||||
|
public void Skip(long numBytes)
|
||||||
|
{
|
||||||
|
Writer.BaseStream.Seek(numBytes, SeekOrigin.Current);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Seeks in the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="origin">Where to start from.</param>
|
||||||
|
/// <param name="offset">The offset to seek to.</param>
|
||||||
|
public void Seek(SeekOrigin origin, long offset)
|
||||||
|
{
|
||||||
|
Writer.BaseStream.Seek(offset, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a variable length unsigned integer to the current stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value to write.</param>
|
||||||
|
public void WriteVarLen(uint value)
|
||||||
|
{
|
||||||
|
bool first = true;
|
||||||
|
while (value > 0 || first)
|
||||||
|
{
|
||||||
|
WriteByte((byte)(((value > 127)?(uint)128:0) | (value & 127)));
|
||||||
|
value >>= 7;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an unsigned 16bit integer to the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A ushort.</returns>
|
||||||
|
public void WriteUInt16(ushort value)
|
||||||
|
{
|
||||||
|
if (ByteOrder == ByteOrder.BIG_ENDIAN)
|
||||||
|
{
|
||||||
|
value = Endian.SwapUInt16(value);
|
||||||
|
}
|
||||||
|
Writer.Write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a 16bit integer to the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A short.</returns>
|
||||||
|
public void WriteInt16(short value)
|
||||||
|
{
|
||||||
|
if (ByteOrder == ByteOrder.BIG_ENDIAN)
|
||||||
|
{
|
||||||
|
value = Endian.SwapInt16(value);
|
||||||
|
}
|
||||||
|
Writer.Write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a 32bit integer to the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An int.</returns>
|
||||||
|
public void WriteInt32(int value)
|
||||||
|
{
|
||||||
|
if (ByteOrder == ByteOrder.BIG_ENDIAN)
|
||||||
|
{
|
||||||
|
value = Endian.SwapInt32(value);
|
||||||
|
}
|
||||||
|
Writer.Write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a 32bit integer to the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An int.</returns>
|
||||||
|
public void WriteInt64(long value)
|
||||||
|
{
|
||||||
|
if (ByteOrder == ByteOrder.BIG_ENDIAN)
|
||||||
|
{
|
||||||
|
value = Endian.SwapInt64(value);
|
||||||
|
}
|
||||||
|
Writer.Write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an unsigned 32bit integer from to current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A uint.</returns>
|
||||||
|
public void WriteUInt32(uint value)
|
||||||
|
{
|
||||||
|
if (ByteOrder == ByteOrder.BIG_ENDIAN)
|
||||||
|
{
|
||||||
|
value = Endian.SwapUInt32(value);
|
||||||
|
}
|
||||||
|
Writer.Write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a number of ASCII characters to the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The string to write.</param>
|
||||||
|
/// <param name="num">The number of bytes to write into.</param>
|
||||||
|
public void WriteCString(string value, int num)
|
||||||
|
{
|
||||||
|
value = value.PadRight(num, '\0');
|
||||||
|
Writer.Write(ASCIIEncoding.ASCII.GetBytes(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteCString(string value)
|
||||||
|
{
|
||||||
|
WriteCString(value, value.Length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a byte to the current stream.
|
||||||
|
/// </summary>
|
||||||
|
public void WriteByte(byte value)
|
||||||
|
{
|
||||||
|
Writer.Write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a number of bytes to the current stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">Bytes to write out.</param>
|
||||||
|
public void WriteBytes(byte[] bytes)
|
||||||
|
{
|
||||||
|
Writer.Write(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a pascal string to the current stream, which is prefixed by a 16bit short.
|
||||||
|
/// </summary>
|
||||||
|
public void WriteLongPascalString(string value)
|
||||||
|
{
|
||||||
|
WriteInt16((short)value.Length);
|
||||||
|
WriteBytes(Encoding.ASCII.GetBytes(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a C string to the current stream.
|
||||||
|
/// </summary>
|
||||||
|
public void WriteNullTerminatedString(string value)
|
||||||
|
{
|
||||||
|
if (value != null) WriteBytes(Encoding.ASCII.GetBytes(value));
|
||||||
|
WriteByte(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a pascal string to the current stream.
|
||||||
|
/// </summary>
|
||||||
|
public void WriteVariableLengthPascalString(string value)
|
||||||
|
{
|
||||||
|
Writer.Write((value == null)?"":value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a pascal string to the current stream, prefixed by a byte.
|
||||||
|
/// </summary>
|
||||||
|
public void WritePascalString(string value)
|
||||||
|
{
|
||||||
|
WriteByte((byte)value.Length);
|
||||||
|
WriteBytes(Encoding.ASCII.GetBytes(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a float to the current stream.
|
||||||
|
/// </summary>
|
||||||
|
public void WriteFloat(float value)
|
||||||
|
{
|
||||||
|
var bytes = BitConverter.GetBytes(value);
|
||||||
|
|
||||||
|
if (ByteOrder == ByteOrder.BIG_ENDIAN && FloatSwap)
|
||||||
|
{
|
||||||
|
Array.Reverse(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Writer.Write(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable Members
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IoWriter instance from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">A stream.</param>
|
||||||
|
/// <returns>A new IoWriter instance.</returns>
|
||||||
|
public static IoWriter FromStream(Stream stream)
|
||||||
|
{
|
||||||
|
return new IoWriter(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IoWriter instance from a stream, using a specified byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">A stream.</param>
|
||||||
|
/// <param name="order">Byte order to use.</param>
|
||||||
|
/// <returns>A new IoWriter instance.</returns>
|
||||||
|
public static IoWriter FromStream(Stream stream, ByteOrder order)
|
||||||
|
{
|
||||||
|
var item = FromStream(stream);
|
||||||
|
item.ByteOrder = order;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IOBuffer instance from a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">The byte array to use.</param>
|
||||||
|
/// <returns>A new IOBuffer instance.</returns>
|
||||||
|
public static IoWriter FromBytes(byte[] bytes)
|
||||||
|
{
|
||||||
|
return FromStream(new MemoryStream(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IOBuffer instance from a byte array, using a specified byte order.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">The byte array to use.</param>
|
||||||
|
/// <param name="order">Byte order to use.</param>
|
||||||
|
/// <returns>A new IOBuffer instance.</returns>
|
||||||
|
public static IoWriter FromBytes(byte[] bytes, ByteOrder order)
|
||||||
|
{
|
||||||
|
return FromStream(new MemoryStream(bytes), order);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used by BCFWriteProxy's string mode, but does not do anything here.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="groupSize">The size of value groups</param>
|
||||||
|
public void SetGrouping(int groupSize)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue