diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79755bf..626f7db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,10 +7,38 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## Unreleased
### Added
+- `OpenWrite` and `OpenRead` static functions in `MetaStream`.
+- Experimental `JsonMetaStreamReader` and `JsonMetaStreamWriter` classes. You can export serialize objects to JSON files in `MetaStream`.
+- Support for encrypted meta stream files with `LegacyEncryptedStream` which matches with Telltale Tool in `MetaStream`. This requires a workspace. The user also has the option to encrypt/decrypt using the `LegacyEncryption` class provided with a Blowfish key and an archive version.
+- An abstract `Close` method.
+- Experimental support for compressed meta stream resources.
+- `ContainerStreamParams` class.
+- `StreamVersion` to `GameProfile`.
+- A constructor with a boolean argument in `Blowfish`.
+- `Workspace` parameter in deserialize methods in `Toolkit`.
+- Helper methods for `VersionInfo`.
### Changed
+- Rename the following MetaStream implementations: `MetaStreamReader`/`MetaStreamWriter` to `BinaryMetaStreamReader`/`BinaryMetaStreamWriter`.
+- Rename `MetaStreamConfig` to `MetaStreamParams`.
+- Update database files with the new changes.
+- Exposed `GetClass(ulong, uint)` method from `MetaClassRegistry`.
+- Refactor `MetaStream` internally.
+- Refactor `MetaStreamParams` with new fields and methods to handle compression and encryption.
+- Reorganize some namespaces.
+- Make the default serializer not throw errors and use logger instead.
+- `Serialize` from `MetaStream` now automatically preserializes.
+
+### Fixed
+- A regression that did not allow files being copied to the output folder.
+- Blowfish encryption not working correctly.
### Removed
+- A csproj folder.
+- Redundant `MetaStreamVersion` from `GameProfile`.
+- `AreSymbolsHashed` from `GameProfile` as symbols are always hashed.
+- `PreSerialize` from `MetaStream`.
+- Redundant `Version` from `MetaStreamParams`.
## [0.2.0] - 2026-05-28
diff --git a/data/CHANGELOG.md b/data/CHANGELOG.md
index a8b8494..c709344 100644
--- a/data/CHANGELOG.md
+++ b/data/CHANGELOG.md
@@ -1,12 +1,19 @@
## Database Changelog
+### 1st of June, 2026
+
+#### Game Profiles
+- Added `StreamVersion` field that matches Telltale Tool. Please refer to the [docs](README.md#game-profile-fields).
+- Removed redundant `MetaStreamVersion` and `AreSymbolsHashed` fields.
+- Updated database files with the new changes.
+
### 28th of May, 2026
#### Game Profiles
-- Added initial game profile for The Wolf Among Us (2013) (Thank you Pumba)
+- Added initial game profile for The Wolf Among Us (2013)(Thank you Pumba!).
#### Version Database
-- Added initial version database for The Wolf Among Us (2013)
+- Added initial version database for The Wolf Among Us (2013).
### 28th of April, 2026
@@ -23,15 +30,15 @@
### 24th of April, 2026
#### Game Profiles
-- Added the year to TWDS2 game profile json file name to match other entries (Thank you @gamma-02)
+- Added the year to TWDS2 game profile json file name to match other entries (Thank you @gamma-02!).
### 17th of March, 2026
#### Game Profiles
-- Added initial game profile for Poker Night at the Inventory Remastered (2026) (Thank you @gamma-02)
+- Added initial game profile for Poker Night at the Inventory Remastered (2026) (Thank you @gamma-02!).
#### Version Database
-- Added initial version database for aforementoned Poker Night at the Inventory Remastered (2026)
+- Added initial version database for aforementoned Poker Night at the Inventory Remastered (2026).
### 18th of February, 2026
diff --git a/data/README.md b/data/README.md
index 2defa22..f1a746e 100644
--- a/data/README.md
+++ b/data/README.md
@@ -58,17 +58,16 @@ For consistency, all database file names are in slug form with the appropriate e
## Game Profile Fields
-| Field | Type | Description | Example |
-| ------------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
-| `Name` | `string` | Display name of the game. | `"The Walking Dead: Season 2"` |
-| `Description` | `string` | A brief description of the game. | `"The Walking Dead: Season 2 (2013) for PC."` |
-| `BlowfishKey` | `string` (see below) | Blowfish encryption key for encrypting/decrypting files. Can be a custom key in a string form or an enum name from the [`T3BlowfishKey`](/src/TelltaleToolKit/Utility/T3BlowfishKey.cs) class. | `"Twds2"` |
-| `IsTtarch2` | `bool` | Whether the game uses the TTArch2 archive format (`true` for TTArch2, `false` for TTArch). | `true` |
-| `TtarchVersion` | `int` | The version of the archive format used by the game. If `IsTtarch2` is true, use versions 2-4, otherwise - versions 0-9. | `3` |
-| `LuaVersion` | `string` | Lua scripting engine version used by the game. It can be either `5.0.2`, `5.1.2` or `5.2.3`. | `"5.1.2"` or `"5.1"` |
-| `MetaStreamVersion` | `string` | Version of the metastream format used in the game. Can be `MBIN`, `MTRE`, `MSV5` or `MSV6`. | `"MSV5"` |
-| `AreSymbolsHashed` | `bool` | Whether the game's symbols are hashed (`true` or `false`). This should almost always be `true`. Only `MBIN` are allowed to not be hashed. | `true` |
-| `EnableOodleCompression` | `bool` | Whether Oodle compression is enabled for game archives. | `false` |
+| Field | Type | Description | Example |
+| ------------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
+| `Name` | `string` | Display name of the game. | `"The Walking Dead: Season 2"` |
+| `Description` | `string` | A brief description of the game. | `"The Walking Dead: Season 2 (2013) for PC."` |
+| `BlowfishKey` | `string` (see below) | Blowfish encryption key for encrypting/decrypting files. Can be a custom key in a string form or an enum name from the [`T3BlowfishKey`](../src/TelltaleToolKit/Utility/Blowfish/T3BlowfishKey.cs) class. | `"Twds2"` |
+| `IsTtarch2` | `bool` | Whether the game uses the TTArch2 archive format (`true` for TTArch2, `false` for TTArch). | `true` |
+| `TtarchVersion` | `int` | The version of the archive format used by the game. If `IsTtarch2` is true, use versions 2-4, otherwise - versions 0-9. | `3` |
+| `LuaVersion` | `string` | Lua scripting engine version used by the game. It can be either `5.0.2`, `5.1.2` or `5.2.3`. | `"5.1.2"` or `"5.1"` |
+| `streamVersion` | `uint` | Version of the metastream format used in the game. This value ranges from 1 to 6. You can determine the version by inspecting the magic (FourCC) of a metastream file and using [this](../src/TelltaleToolKit/Serialization/MetaStreamMagic.cs) as a reference. | `5` |
+| `EnableOodleCompression` | `bool` | Whether Oodle compression is enabled for game archives. | `false` |
### Game Profile Full Example
@@ -80,8 +79,7 @@ For consistency, all database file names are in slug form with the appropriate e
"IsTtarch2": true,
"TtarchVersion": 3,
"LuaVersion": "5.1.2",
- "MetaStreamVersion": "MSV5",
- "AreSymbolsHashed": true,
+ "streamVersion": 5,
"EnableOodleCompression": false,
}
```
diff --git a/data/game_profiles/batman-the-enemy-within-2017.json b/data/game_profiles/batman-the-enemy-within-2017.json
index 7297049..5ce1391 100644
--- a/data/game_profiles/batman-the-enemy-within-2017.json
+++ b/data/game_profiles/batman-the-enemy-within-2017.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/batman-the-telltale-series-2016-12.json b/data/game_profiles/batman-the-telltale-series-2016-12.json
index 0ffb5b5..b84da84 100644
--- a/data/game_profiles/batman-the-telltale-series-2016-12.json
+++ b/data/game_profiles/batman-the-telltale-series-2016-12.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/bone-act-1-2-combo-pack-2007.json b/data/game_profiles/bone-act-1-2-combo-pack-2007.json
index e942b7e..e60b63d 100644
--- a/data/game_profiles/bone-act-1-2-combo-pack-2007.json
+++ b/data/game_profiles/bone-act-1-2-combo-pack-2007.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 4,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/bone-out-from-boneville-2005.json b/data/game_profiles/bone-out-from-boneville-2005.json
index a6165cb..6cd0fd0 100644
--- a/data/game_profiles/bone-out-from-boneville-2005.json
+++ b/data/game_profiles/bone-out-from-boneville-2005.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 4,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/bone-out-from-boneville-2007.json b/data/game_profiles/bone-out-from-boneville-2007.json
index dd27c23..f911f2b 100644
--- a/data/game_profiles/bone-out-from-boneville-2007.json
+++ b/data/game_profiles/bone-out-from-boneville-2007.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 4,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/bone-the-great-cowrace-2006.json b/data/game_profiles/bone-the-great-cowrace-2006.json
index 92773ef..25c05e6 100644
--- a/data/game_profiles/bone-the-great-cowrace-2006.json
+++ b/data/game_profiles/bone-the-great-cowrace-2006.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 4,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/borderlands-2015.json b/data/game_profiles/borderlands-2015.json
index d490ad4..28b8fd4 100644
--- a/data/game_profiles/borderlands-2015.json
+++ b/data/game_profiles/borderlands-2015.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.1",
- "metaStreamVersion": "MSV5",
+ "streamVersion": 5,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/csi-3-dimensions-of-murder-2006-demo.json b/data/game_profiles/csi-3-dimensions-of-murder-2006-demo.json
index 59b5de6..6fa16ed 100644
--- a/data/game_profiles/csi-3-dimensions-of-murder-2006-demo.json
+++ b/data/game_profiles/csi-3-dimensions-of-murder-2006-demo.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 4,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/csi-hard-evidence-2007.json b/data/game_profiles/csi-hard-evidence-2007.json
index b2c171d..c1d2a22 100644
--- a/data/game_profiles/csi-hard-evidence-2007.json
+++ b/data/game_profiles/csi-hard-evidence-2007.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 3,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/game-of-thrones-2015.json b/data/game_profiles/game-of-thrones-2015.json
index ff13697..bf111cf 100644
--- a/data/game_profiles/game-of-thrones-2015.json
+++ b/data/game_profiles/game-of-thrones-2015.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 3,
"luaVersion": "5.1.2",
- "metaStreamVersion": "MSV5",
+ "streamVersion": 5,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/guardians-of-the-galaxy-2017.json b/data/game_profiles/guardians-of-the-galaxy-2017.json
index 040151d..12cd486 100644
--- a/data/game_profiles/guardians-of-the-galaxy-2017.json
+++ b/data/game_profiles/guardians-of-the-galaxy-2017.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/minecraft-story-mode-2015-old.json b/data/game_profiles/minecraft-story-mode-2015-old.json
index 25ff103..1e456aa 100644
--- a/data/game_profiles/minecraft-story-mode-2015-old.json
+++ b/data/game_profiles/minecraft-story-mode-2015-old.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 3,
"luaVersion": "5.1.2",
- "metaStreamVersion": "MSV5",
+ "streamVersion": 5,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/minecraft-story-mode-2015.json b/data/game_profiles/minecraft-story-mode-2015.json
index 25ff103..1e456aa 100644
--- a/data/game_profiles/minecraft-story-mode-2015.json
+++ b/data/game_profiles/minecraft-story-mode-2015.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 3,
"luaVersion": "5.1.2",
- "metaStreamVersion": "MSV5",
+ "streamVersion": 5,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/minecraft-story-mode-season-2-2017.json b/data/game_profiles/minecraft-story-mode-season-2-2017.json
index 9bd9489..6203f12 100644
--- a/data/game_profiles/minecraft-story-mode-season-2-2017.json
+++ b/data/game_profiles/minecraft-story-mode-season-2-2017.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/poker-night-2-2013.json b/data/game_profiles/poker-night-2-2013.json
index a67afa8..3d308b2 100644
--- a/data/game_profiles/poker-night-2-2013.json
+++ b/data/game_profiles/poker-night-2-2013.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 9,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/poker-night-at-the-inventory-remastered-2026.json b/data/game_profiles/poker-night-at-the-inventory-remastered-2026.json
index ce78c92..fa35e04 100644
--- a/data/game_profiles/poker-night-at-the-inventory-remastered-2026.json
+++ b/data/game_profiles/poker-night-at-the-inventory-remastered-2026.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/sam-and-max-101-2006-demo.json b/data/game_profiles/sam-and-max-101-2006-demo.json
index 8449d4c..2a216b7 100644
--- a/data/game_profiles/sam-and-max-101-2006-demo.json
+++ b/data/game_profiles/sam-and-max-101-2006-demo.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 3,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/sam-and-max-102-2006-demo.json b/data/game_profiles/sam-and-max-102-2006-demo.json
index 05dedd5..191befa 100644
--- a/data/game_profiles/sam-and-max-102-2006-demo.json
+++ b/data/game_profiles/sam-and-max-102-2006-demo.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 3,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/sam-and-max-103-2006-demo.json b/data/game_profiles/sam-and-max-103-2006-demo.json
index ffa7db1..e1d373e 100644
--- a/data/game_profiles/sam-and-max-103-2006-demo.json
+++ b/data/game_profiles/sam-and-max-103-2006-demo.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 3,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/sam-and-max-104-2006-demo.json b/data/game_profiles/sam-and-max-104-2006-demo.json
index 0f8ffed..5243ccc 100644
--- a/data/game_profiles/sam-and-max-104-2006-demo.json
+++ b/data/game_profiles/sam-and-max-104-2006-demo.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 3,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/sam-and-max-105-2006-demo.json b/data/game_profiles/sam-and-max-105-2006-demo.json
index 10c03c5..8256d63 100644
--- a/data/game_profiles/sam-and-max-105-2006-demo.json
+++ b/data/game_profiles/sam-and-max-105-2006-demo.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 3,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/sam-and-max-106-2006-demo.json b/data/game_profiles/sam-and-max-106-2006-demo.json
index 01c8cc4..b0aaa6c 100644
--- a/data/game_profiles/sam-and-max-106-2006-demo.json
+++ b/data/game_profiles/sam-and-max-106-2006-demo.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 3,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/sam-and-max-201-2007.json b/data/game_profiles/sam-and-max-201-2007.json
index a8183ea..0cea5c2 100644
--- a/data/game_profiles/sam-and-max-201-2007.json
+++ b/data/game_profiles/sam-and-max-201-2007.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 2,
"luaVersion": "5.0",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/sam-and-max-203-2008.json b/data/game_profiles/sam-and-max-203-2008.json
index 5917173..ab203a1 100644
--- a/data/game_profiles/sam-and-max-203-2008.json
+++ b/data/game_profiles/sam-and-max-203-2008.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 0,
"luaVersion": "5.0.2",
- "metaStreamVersion": "MBIN",
+ "streamVersion": 1,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/sam-and-max-season-1-remastered-2019.json b/data/game_profiles/sam-and-max-season-1-remastered-2019.json
index 096409d..697fe0a 100644
--- a/data/game_profiles/sam-and-max-season-1-remastered-2019.json
+++ b/data/game_profiles/sam-and-max-season-1-remastered-2019.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/tales-from-the-borderlands-2021-pc.json b/data/game_profiles/tales-from-the-borderlands-2021-pc.json
index b593d41..4812231 100644
--- a/data/game_profiles/tales-from-the-borderlands-2021-pc.json
+++ b/data/game_profiles/tales-from-the-borderlands-2021-pc.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/tales-from-the-borderlands-2021-switch.json b/data/game_profiles/tales-from-the-borderlands-2021-switch.json
index d2b67be..2716fed 100644
--- a/data/game_profiles/tales-from-the-borderlands-2021-switch.json
+++ b/data/game_profiles/tales-from-the-borderlands-2021-switch.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/telltale-texas-hold-em-2005.json b/data/game_profiles/telltale-texas-hold-em-2005.json
index 664abee..04aa468 100644
--- a/data/game_profiles/telltale-texas-hold-em-2005.json
+++ b/data/game_profiles/telltale-texas-hold-em-2005.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/the-walking-dead-collection-2017.json b/data/game_profiles/the-walking-dead-collection-2017.json
index 84fe4c9..f890f10 100644
--- a/data/game_profiles/the-walking-dead-collection-2017.json
+++ b/data/game_profiles/the-walking-dead-collection-2017.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/the-walking-dead-definitive-series-2019.json b/data/game_profiles/the-walking-dead-definitive-series-2019.json
index 9ca8e43..1f06218 100644
--- a/data/game_profiles/the-walking-dead-definitive-series-2019.json
+++ b/data/game_profiles/the-walking-dead-definitive-series-2019.json
@@ -5,7 +5,7 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true,
"areSymbolsHashed": true
}
\ No newline at end of file
diff --git a/data/game_profiles/the-walking-dead-michonne-2016.json b/data/game_profiles/the-walking-dead-michonne-2016.json
index 90237df..508ecee 100644
--- a/data/game_profiles/the-walking-dead-michonne-2016.json
+++ b/data/game_profiles/the-walking-dead-michonne-2016.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV5",
+ "streamVersion": 5,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/the-walking-dead-season-2-2013.json b/data/game_profiles/the-walking-dead-season-2-2013.json
index 9b926d2..81a8f0e 100644
--- a/data/game_profiles/the-walking-dead-season-2-2013.json
+++ b/data/game_profiles/the-walking-dead-season-2-2013.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 3,
"luaVersion": "5.1.2",
- "metaStreamVersion": "MSV5",
+ "streamVersion": 5,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/the-walking-dead-season-3-2016.json b/data/game_profiles/the-walking-dead-season-3-2016.json
index 9530fc4..a1d95e7 100644
--- a/data/game_profiles/the-walking-dead-season-3-2016.json
+++ b/data/game_profiles/the-walking-dead-season-3-2016.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/the-walking-dead-season-4-2018.json b/data/game_profiles/the-walking-dead-season-4-2018.json
index 0b1f050..63cf355 100644
--- a/data/game_profiles/the-walking-dead-season-4-2018.json
+++ b/data/game_profiles/the-walking-dead-season-4-2018.json
@@ -5,6 +5,6 @@
"isTtarch2": true,
"ttarchVersion": 4,
"luaVersion": "5.2.3",
- "metaStreamVersion": "MSV6",
+ "streamVersion": 6,
"enableOodleCompression": true
}
\ No newline at end of file
diff --git a/data/game_profiles/the-walking-dead.json b/data/game_profiles/the-walking-dead.json
index d3d614c..c72c5c6 100644
--- a/data/game_profiles/the-walking-dead.json
+++ b/data/game_profiles/the-walking-dead.json
@@ -5,6 +5,6 @@
"isTtarch2": false,
"ttarchVersion": 9,
"luaVersion": "5.1.2",
- "metaStreamVersion": "MTRE",
+ "streamVersion": 3,
"enableOodleCompression": false
}
\ No newline at end of file
diff --git a/data/game_profiles/the-wolf-among-us-2013.json b/data/game_profiles/the-wolf-among-us-2013.json
index a79161c..abc387c 100644
--- a/data/game_profiles/the-wolf-among-us-2013.json
+++ b/data/game_profiles/the-wolf-among-us-2013.json
@@ -1 +1 @@
-{"name":"the-wolf-among-us-2013","description":"","blowfishKey":"Fables","isTtarch2":true,"ttarchVersion":3,"luaVersion":"5.1.2","metaStreamVersion":"MSV5","enableOodleCompression":false}
\ No newline at end of file
+{"name":"the-wolf-among-us-2013","description":"","blowfishKey":"Fables","isTtarch2":true,"ttarchVersion":3,"luaVersion":"5.1.2","streamVersion":5,"enableOodleCompression":false}
\ No newline at end of file
diff --git a/samples/VersionDatabaseCreator/Program.cs b/samples/VersionDatabaseCreator/Program.cs
index 1392dab..22de66b 100644
--- a/samples/VersionDatabaseCreator/Program.cs
+++ b/samples/VersionDatabaseCreator/Program.cs
@@ -6,9 +6,11 @@
using TelltaleToolKit;
using TelltaleToolKit.GamesDatabase;
using TelltaleToolKit.Reflection;
+using TelltaleToolKit.Serialization;
using TelltaleToolKit.Serialization.Binary;
using TelltaleToolKit.TelltaleArchives;
using TelltaleToolKit.Utility.Blowfish;
+using TelltaleToolKit.Utility.Lua;
// !!!IMPORTANT!!!
// This project creates ONLY the VERSION DATABASE for a specific game.
@@ -52,8 +54,7 @@
// The reason being is that when extracting a single entry directly from a ttarch, that entry is directly loaded in memory.
// And sometimes...there are huge assets.
// It takes a couple of minutes to scan TWD:DE with a stock Ryzen 5 2600.
-var areSymbolsHashed = false;
-var msv = MetaStreamVersion.Mbin;
+uint msv = 2;
TTArchiveVersion ttarchVersion = 0;
await Parallel.ForEachAsync(archivePaths, async (filePath, _) =>
{
@@ -70,10 +71,9 @@ await Parallel.ForEachAsync(archivePaths, async (filePath, _) =>
if (Toolkit.IsMetaFile(archive.GetAllEntries().First().Name))
{
- MetaStreamConfiguration config = new MetaStreamReader(firstFile).Configuration;
+ MetaStreamParams config = new BinaryMetaStreamReader(firstFile, null).Params;
- areSymbolsHashed = config.AreSymbolsHashed;
- msv = config.Version;
+ msv = config.StreamVersion;
}
}
@@ -86,27 +86,30 @@ await Parallel.ForEachAsync(archivePaths, async (filePath, _) =>
if (!Toolkit.IsMetaFile(file))
continue;
- MetaStreamConfiguration config = new MetaStreamReader(file).Configuration;
+ MetaStreamParams config = new BinaryMetaStreamReader(file).Params;
- foreach (MetaClass desc in config.SerializedClasses)
+ foreach (MetaClass desc in config.GetRegisteredClasses())
serializedMetaClasses.TryAdd(desc, 0);
- foreach ((ulong, uint) type in config.UnregisteredTypes)
+ var localUnregisteredTypes = config.GetUnregisteredTypes();
+ var localUnregisteredClasses = config.GetUnregisteredClasses();
+
+ foreach ((ulong, uint) type in localUnregisteredTypes)
unregisteredTypes.TryAdd(type, 0);
- foreach ((MetaClassType, uint crc32) urDesc in config.UnregisteredClasses)
+ foreach ((MetaClassType, uint crc32) urDesc in localUnregisteredClasses)
unrecognizedMetaClassDescriptions.TryAdd(urDesc, 0);
- if (config.UnregisteredTypes.Count > 0)
+ if (localUnregisteredTypes.Count > 0)
{
Console.Error.WriteLine(
- $"File {entry.Name} has {config.UnregisteredTypes.Count} unregistered types in {Path.GetFileName(filePath)}.");
+ $"File {entry.Name} has {localUnregisteredTypes.Count} unregistered types in {Path.GetFileName(filePath)}.");
}
- if (config.UnregisteredClasses.Count > 0)
+ if (localUnregisteredClasses.Count > 0)
{
Console.Error.WriteLine(
- $"File {entry.Name} has {config.UnregisteredClasses.Count} unregistered classes in {Path.GetFileName(filePath)}.");
+ $"File {entry.Name} has {localUnregisteredClasses.Count} unregistered classes in {Path.GetFileName(filePath)}.");
}
}
}
@@ -139,7 +142,7 @@ await Parallel.ForEachAsync(archivePaths, async (filePath, _) =>
// continue;
// }
//
-// MetaStreamConfiguration config = TTK.ExtractMetaStreamConfiguration(archive.ExtractFile(entry.Name));
+// MetaStreamParams config = TTK.ExtractMetaStreamConfiguration(archive.ExtractFile(entry.Name));
//
// // Use this if you want to debug which file has an unknown type.
// if (config.UnregisteredTypes.Count > 0)
@@ -230,12 +233,11 @@ await Parallel.ForEachAsync(archivePaths, async (filePath, _) =>
{
Id = sluggedName,
Name = sluggedName,
- AreSymbolsHashed = areSymbolsHashed,
BlowfishKey = blowfishKey.ToString(),
EnableOodleCompression = false,
IsTtarch2 = filter == "*.ttarch2",
LuaVersion = LuaVersion.Lua502, // Determining lua versions is too much for me, sorry.
- MetaStreamVersion = msv,
+ StreamVersion = msv,
TtarchVersion = ttarchVersion,
};
diff --git a/src/TelltaleToolKit/GamesDatabase/GameProfile.cs b/src/TelltaleToolKit/GamesDatabase/GameProfile.cs
index fbffdf5..c7d4201 100644
--- a/src/TelltaleToolKit/GamesDatabase/GameProfile.cs
+++ b/src/TelltaleToolKit/GamesDatabase/GameProfile.cs
@@ -1,83 +1,80 @@
using TelltaleToolKit.Reflection;
-using TelltaleToolKit.Serialization.Binary;
+using TelltaleToolKit.Serialization;
using TelltaleToolKit.TelltaleArchives;
using TelltaleToolKit.Utility.Blowfish;
+using TelltaleToolKit.Utility.Lua;
namespace TelltaleToolKit.GamesDatabase;
///
-/// Represents a registry entry for a Telltale game configuration.
-/// Each entry is identified by its file name following the convention:
-/// [slugified-title]-[year]-[month]-[platform]-[demo]
-/// This registry contains general information about the game, versioning, encryption, and a mapping of metaclasses.
+/// Represents a registry entry for a Telltale game configuration.
+/// Each entry is identified by its file name following the convention:
+/// [slugified-title]-[year]-[month]-[platform]-[demo]
+/// This registry contains general information about the game, versioning, encryption, and a mapping of metaclasses.
///
public sealed class GameProfile
{
///
- /// Gets or sets the unique identifier for the registry entry, derived from the file name.
+ /// Gets or sets the unique identifier for the registry entry, derived from the file name.
///
public string Id { get; set; } = string.Empty;
///
- /// Gets the display name of the game.
+ /// Gets the display name of the game.
///
public string Name { get; set; } = string.Empty;
///
- /// Gets a textual description of the game.
+ /// Gets a textual description of the game.
///
public string Description { get; init; } = string.Empty;
///
- /// Gets the Blowfish encryption key used for securing game archives.
- /// In the config file, you can either use the name of or a custom string.
+ /// Gets the Blowfish encryption key used for securing game archives.
+ /// In the config file, you can either use the name of or a custom string.
///
public string BlowfishKey { get; set; } = string.Empty;
///
- /// Gets a value indicating whether the game uses the TTARCH2 archive format.
+ /// Gets a value indicating whether the game uses the TTARCH2 archive format.
///
public bool IsTtarch2 { get; set; }
///
- /// Gets the version of the TTARCH archive format used by the game.
+ /// Gets the version of the TTARCH archive format used by the game.
///
- ///
+ ///
public TTArchiveVersion TtarchVersion { get; set; }
///
- /// Gets the Lua scripting engine version used by the game.
+ /// Gets the Lua scripting engine version used by the game.
///
- ///
+ ///
public LuaVersion LuaVersion { get; init; }
///
- /// Gets the version of the stream format used in the game.
+ /// Gets the version of the stream format used in the game.
///
- ///
- public MetaStreamVersion MetaStreamVersion { get; set; }
-
- // TODO: Automatically assign false if MetaStreamVersion is MBIN.
- public bool AreSymbolsHashed { get; set; }
+ public uint StreamVersion { get; set; }
///
- /// Gets a value indicating whether Oodle compression is enabled for game archives.
+ /// Gets a value indicating whether Oodle compression is enabled for game archives.
///
public bool EnableOodleCompression { get; init; }
-
+
///
- /// Identifier (name) of the metaclass database used for resolving MetaClass definitions for this game.
- /// The value is a slug title from the already existing game profiles.
- /// By default, this value is the same as . Use this property to override the database name
- /// when multiple games share a common metaclass database, or when the metaclass DB is stored under a different id.
- /// If empty, resolution should default to .
- /// If invalid (no metaclass database exists)
+ /// Identifier (name) of the metaclass database used for resolving MetaClass definitions for this game.
+ /// The value is a slug title from the already existing game profiles.
+ /// By default, this value is the same as . Use this property to override the database name
+ /// when multiple games share a common metaclass database, or when the metaclass DB is stored under a different id.
+ /// If empty, resolution should default to .
+ /// If invalid (no metaclass database exists)
///
public string MetaClassDatabase { get; init; } = string.Empty;
///
- /// Gets a dictionary mapping to their corresponding IDs.
- /// This represents the metaclasses available in the game and their identifiers.
+ /// Gets a dictionary mapping to their corresponding IDs.
+ /// This represents the metaclasses available in the game and their identifiers.
///
public Dictionary Classes { get; init; } = new();
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/GamesDatabase/GameProfileJsonConverter.cs b/src/TelltaleToolKit/GamesDatabase/GameProfileJsonConverter.cs
index 981b0cd..179eccd 100644
--- a/src/TelltaleToolKit/GamesDatabase/GameProfileJsonConverter.cs
+++ b/src/TelltaleToolKit/GamesDatabase/GameProfileJsonConverter.cs
@@ -1,24 +1,24 @@
using System.Text.Json;
using System.Text.Json.Serialization;
-using TelltaleToolKit.Serialization.Binary;
using TelltaleToolKit.TelltaleArchives;
using TelltaleToolKit.Utility.Blowfish;
+using TelltaleToolKit.Utility.Lua;
namespace TelltaleToolKit.GamesDatabase;
///
-/// Custom JSON converter for objects.
-/// Handles conversion between JSON and game registry entries.
+/// Custom JSON converter for objects.
+/// Handles conversion between JSON and game registry entries.
///
public class GameRegistryJsonConverter : JsonConverter
{
///
- /// Reads and converts the JSON to a object.
+ /// Reads and converts the JSON to a object.
///
/// The reader to read from.
/// The type of object to convert.
/// Serializer options.
- /// The converted object.
+ /// The converted object.
public override GameProfile Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using JsonDocument doc = JsonDocument.ParseValue(ref reader);
@@ -31,22 +31,9 @@ public override GameProfile Read(ref Utf8JsonReader reader, Type typeToConvert,
bool isTtarch2 = obj.GetProperty("isTtarch2").GetBoolean();
int ttarchVersion = obj.GetProperty("ttarchVersion").GetInt32();
string luaVersion = obj.GetProperty("luaVersion").GetString() ?? string.Empty;
- string metaStreamVersion = obj.GetProperty("metaStreamVersion").GetString() ?? string.Empty;
+ uint streamVersion = obj.GetProperty("streamVersion").GetUInt32();
bool enableOodleCompression = obj.GetProperty("enableOodleCompression").GetBoolean();
- var areSymbolsHashed = true;
- if (obj.TryGetProperty("areSymbolsHashed", out JsonElement value))
- {
- if (value.ValueKind is JsonValueKind.True or JsonValueKind.False)
- {
- areSymbolsHashed = value.GetBoolean();
- }
- else if (value.ValueKind == JsonValueKind.String &&
- bool.TryParse(value.GetString(), out bool parsed))
- {
- areSymbolsHashed = parsed;
- }
- }
-
+
// Attempt to resolve the blowfish key from known enum values. If not, use the string as a key.
if (Enum.TryParse(blowfishKey, out T3BlowfishKey enumBlowfishKey))
{
@@ -61,17 +48,16 @@ public override GameProfile Read(ref Utf8JsonReader reader, Type typeToConvert,
IsTtarch2 = isTtarch2,
TtarchVersion = ttarchVersion.TtarchVersionFromNumber(isTtarch2),
LuaVersion = luaVersion.ParseLuaVersion(),
- MetaStreamVersion = metaStreamVersion.StreamVersionFromString(),
- EnableOodleCompression = enableOodleCompression,
- AreSymbolsHashed = areSymbolsHashed
+ StreamVersion = streamVersion,
+ EnableOodleCompression = enableOodleCompression
};
}
///
- /// Writes a object as JSON.
+ /// Writes a object as JSON.
///
- /// The to write to.
- /// The value to write.
+ /// The to write to.
+ /// The value to write.
/// Serializer options.
public override void Write(Utf8JsonWriter writer, GameProfile value, JsonSerializerOptions options)
{
@@ -82,8 +68,8 @@ public override void Write(Utf8JsonWriter writer, GameProfile value, JsonSeriali
writer.WriteBoolean("isTtarch2", value.IsTtarch2);
writer.WriteNumber("ttarchVersion", value.TtarchVersion.ToJsonNumber());
writer.WriteString("luaVersion", value.LuaVersion.ToVersionString());
- writer.WriteString("metaStreamVersion", value.MetaStreamVersion.ToJsonString());
+ writer.WriteNumber("streamVersion", value.StreamVersion);
writer.WriteBoolean("enableOodleCompression", value.EnableOodleCompression);
writer.WriteEndObject();
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/GamesDatabase/GameProfileJsonExtensions.cs b/src/TelltaleToolKit/GamesDatabase/GameProfileJsonExtensions.cs
index 9b496b6..64a1c5b 100644
--- a/src/TelltaleToolKit/GamesDatabase/GameProfileJsonExtensions.cs
+++ b/src/TelltaleToolKit/GamesDatabase/GameProfileJsonExtensions.cs
@@ -6,11 +6,11 @@ namespace TelltaleToolKit.GamesDatabase;
public class GameProfileJsonExtensions
{
///
- /// Serializes GameProfile.Classes to JSON.
+ /// Serializes GameProfile.Classes to JSON.
///
public static string ToClassesJson(GameProfile profile, JsonSerializerOptions options)
{
- var classes = new SortedDictionary(
+ SortedDictionary classes = new(
profile.Classes.ToDictionary(
kvp => kvp.Key.Symbol.DebugString,
kvp => kvp.Value
@@ -21,13 +21,15 @@ public static string ToClassesJson(GameProfile profile, JsonSerializerOptions op
}
///
- /// Reads JSON produced by and applies it back onto GameProfile.Classes.
+ /// Reads JSON produced by and applies it back onto GameProfile.Classes.
///
public static void ReadClassesJsonInto(GameProfile profile, string json, JsonSerializerOptions options)
{
- var dict = JsonSerializer.Deserialize>(json, options);
+ Dictionary? dict = JsonSerializer.Deserialize>(json, options);
if (dict is null)
+ {
return;
+ }
profile.Classes.Clear();
diff --git a/src/TelltaleToolKit/Reflection/MetaClassRegistry.cs b/src/TelltaleToolKit/Reflection/MetaClassRegistry.cs
index e01ab33..03d7221 100644
--- a/src/TelltaleToolKit/Reflection/MetaClassRegistry.cs
+++ b/src/TelltaleToolKit/Reflection/MetaClassRegistry.cs
@@ -21,7 +21,7 @@ public class MetaClassRegistry
///
/// Retrieves a class by type hash (CRC64) and CRC32.
///
- private MetaClass? GetClass(ulong typeHash, uint crc32)
+ public MetaClass? GetClass(ulong typeHash, uint crc32)
=> GetClass((typeHash, crc32));
///
@@ -35,7 +35,7 @@ public class MetaClassRegistry
///
private MetaClass? GetClass((ulong TypeHash, uint Crc32) key)
{
- return Classes.TryGetValue(key, out MetaClass? result) ? result : null;
+ return Classes.GetValueOrDefault(key);
}
///
diff --git a/src/TelltaleToolKit/Serialization/Binary/BinaryMetaStreamReader.cs b/src/TelltaleToolKit/Serialization/Binary/BinaryMetaStreamReader.cs
new file mode 100644
index 0000000..7eb8ff0
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/Binary/BinaryMetaStreamReader.cs
@@ -0,0 +1,293 @@
+using System.Text;
+using TelltaleToolKit.Reflection;
+using TelltaleToolKit.T3Types;
+using TelltaleToolKit.Utility.Hashing;
+
+namespace TelltaleToolKit.Serialization.Binary;
+
+public sealed class BinaryMetaStreamReader : MetaStream
+{
+ private BinaryReader Reader = null!;
+
+ public BinaryMetaStreamReader(Stream inputStream, Workspace? workspace = null)
+ {
+ BaseStream = inputStream;
+ Params.Workspace = workspace;
+
+ bool isValid = ReadHeader();
+
+ if (!isValid)
+ {
+ throw new InvalidDataException("Invalid MetaStream.");
+ }
+
+ long offset = Sections[0].CompressedSize;
+ for (int i = 1; i <= 3; i++)
+ {
+ //for each section (default,async,debug)
+ SectionInfo currentSect = Sections[i];
+ if (currentSect.CompressedSize > 0)
+ {
+ currentSect.Stream = new SubStream(Sections[0].Stream, offset, currentSect.CompressedSize);
+ if (currentSect.IsCompressed)
+ {
+ if (Params.Workspace is null)
+ {
+ throw new InvalidOperationException("[MetaStream] Workspace is not set for encrypted streams.");
+ }
+
+ ContainerStream cs = new(currentSect.Stream, Params.Workspace.BlowfishKey);
+ currentSect.Stream = cs;
+ SetSection((SectionType)i);
+
+ // No idea why this is skipped
+ this.ReadUInt64();
+ }
+
+ offset += currentSect.CompressedSize;
+ }
+ }
+
+ SetSection(SectionType.Default);
+ }
+
+ public override MetaStreamMode Mode => MetaStreamMode.Read;
+
+ private bool ReadHeader()
+ {
+ if (BaseStream.Length < 4)
+ {
+ Toolkit.Instance.Logger.LogError("Invalid MetaHeader: Stream too short.");
+ return false;
+ }
+
+ Sections[(int)SectionType.Header].Stream = BaseStream;
+ Reader = new BinaryReader(Sections[(int)SectionType.Header].Stream, Encoding.UTF8, true);
+ SetSection(SectionType.Header);
+
+ MetaStreamMagic magic = (MetaStreamMagic)this.ReadUInt32();
+ Params.StreamVersion = magic.GetMetaStreamVersion();
+ uint streamVersion = Params.StreamVersion;
+
+ if (Params.StreamVersion == 0)
+ {
+ Toolkit.Instance.Logger.LogError($"Not a valid meta stream version: {magic}");
+ return false;
+ }
+
+ long defaultSize = 0, debugSize = 0, asyncSize = 0;
+
+ if (streamVersion >= 4)
+ {
+ if (streamVersion >= 5)
+ {
+ defaultSize = this.ReadUInt32();
+ }
+
+ debugSize = this.ReadUInt32();
+ asyncSize = this.ReadUInt32();
+
+ if ((defaultSize & 0x80000000) != 0)
+ {
+ defaultSize &= 0x7FFFFFFF;
+ Sections[1].IsCompressed = true;
+ }
+
+ if ((debugSize & 0x80000000) != 0)
+ {
+ debugSize &= 0x7FFFFFFF;
+ Sections[2].IsCompressed = true;
+ }
+
+ if ((asyncSize & 0x80000000) != 0)
+ {
+ asyncSize &= 0x7FFFFFFF;
+ Sections[3].IsCompressed = true;
+ }
+ }
+ else
+ {
+ Params.Encrypt = true;
+ // For MCOM, there is an extra 4-byte field to skip
+ if (magic is MetaStreamMagic.Mcom or MetaStreamMagic.EncryptedMcom)
+ {
+ _ = this.ReadUInt32(); // Actually, this might be the fully decompressed data size.
+
+ if (magic is MetaStreamMagic.Mcom)
+ {
+ Params.Encrypt = false;
+ /* TODO: This is not a priority, because Telltale haven't shipped any games with MCOM or encrypted MCOM.
+ But it would be nice to support them in the future.
+ In TWDS1, there's a function called "ReadUncompressAndStoreInMemory" which reads and decompresses the whole stream in memory.
+ It uses zlib compression.
+ This means MCOM is just a compressed version of the entire file.
+ While the encrypted version is still compressed, but the decompressed data is encrypted.
+ */
+ }
+
+ Toolkit.Instance.Logger.LogError("[MetaStream] Mcom or EncryptedMcom is not supported.");
+ return false;
+ }
+
+ if (Params.Workspace is null)
+ {
+ Toolkit.Instance.Logger.LogError("[MetaStream] Workspace is not set for encrypted streams.");
+ return false;
+ }
+
+ // Create a LegacyEncryptedStream that starts after the magic (and possible extra)
+ LegacyEncryptedStream encryptedStream = new(BaseStream, magic.GetMetaStreamVersion(),
+ GetPosition(),
+ Params.Workspace.Blowfish);
+
+ // Replace the underlying stream and reinitialize the BinaryReader
+ Sections[0].Stream = encryptedStream;
+ Reader = new BinaryReader(Sections[0].Stream, Encoding.UTF8, true);
+ }
+
+ // Read the number of serialized classes
+ uint numVers = this.ReadUInt32();
+
+ if (numVers >= 1024)
+ {
+ Toolkit.Instance.Logger.LogError("[MetaStream] Too many serialized classes.");
+ return false;
+ }
+
+ Params.VersionInfo.Capacity = (int)numVers;
+ for (int i = 0; i < numVers; i++)
+ {
+ MetaVersionInfo verInfo = new();
+
+ if (magic.GetMetaStreamVersion() >= 3) // mStreamVersion >= 3 uses hashed symbols
+ {
+ verInfo.TypeSymbolCrc = this.ReadUInt64();
+ }
+ else
+ {
+ string typeName = this.ReadString();
+ verInfo.TypeSymbolCrc = Crc64.Compute(MetaClassType.GetStrippedTypeName(typeName));
+ }
+
+ verInfo.VersionCrc = this.ReadUInt32();
+ Params.VersionInfo.Add(verInfo);
+ }
+
+ long headerSize = Sections[(int)SectionType.Header].Stream!.Position;
+
+ defaultSize = Params.StreamVersion >= 5
+ ? defaultSize
+ : BaseStream.Length - headerSize - debugSize - asyncSize;
+
+ // The encrypted header is lost in the process, so we need to add it back
+ if (Params.Encrypt)
+ {
+ defaultSize -= 4;
+ }
+
+ Sections[0].CompressedSize = headerSize;
+ Sections[1].CompressedSize = defaultSize;
+ Sections[2].CompressedSize = debugSize;
+ Sections[3].CompressedSize = asyncSize;
+
+ return true;
+ }
+
+ protected override void SetSection(SectionType newSection)
+ {
+ if (_currentSection == newSection)
+ {
+ return;
+ }
+
+ _currentSection = newSection;
+ Reader = new BinaryReader(Sections[(int)_currentSection].Stream, Encoding.UTF8, true);
+ }
+
+ public override void BeginBlock()
+ {
+ int size = this.ReadInt32();
+ long expectedPosition = Sections[(int)_currentSection].Stream!.Position + size - sizeof(int);
+ Sections[(int)_currentSection].Blocks.Push(expectedPosition);
+ }
+
+ public override void EndBlock()
+ {
+ SectionInfo currentSectionInfo = Sections[(int)_currentSection];
+ long expectedPosition = currentSectionInfo.Blocks.Pop();
+ long currentPosition = currentSectionInfo.Stream!.Position;
+ if (expectedPosition == currentPosition)
+ {
+ return;
+ }
+
+ // Telltale Tool skips the block if it's not read fully.
+ // I have no idea how they got there in the first place, but I saw one such file (ui_mainmenu_background.scene) which benefits from this.
+ // Previously, I used to throw exceptions since I thought it was impossible, but this seems better for stability purposes.
+ // This matches the implementation of the engine behaviour.
+ Toolkit.Instance.Logger.LogWarning(
+ $"[BinaryMetaStreamReader] Invalid data position! Current Position: {currentPosition}. Expected Position: {expectedPosition}.");
+ currentSectionInfo.Stream.Position = expectedPosition;
+ }
+
+ public override void Serialize(ref bool value) =>
+ value = Reader.ReadChar() switch
+ {
+ '1' => true,
+ '0' => false,
+ _ => throw new InvalidBooleanException($"Invalid boolean at position: {Reader.BaseStream.Position}!")
+ };
+
+ public override void Serialize(ref float value) => value = Reader.ReadSingle();
+
+ public override void Serialize(ref double value) => value = Reader.ReadDouble();
+
+ public override void Serialize(ref short value) => value = Reader.ReadInt16();
+
+ public override void Serialize(ref int value) => value = Reader.ReadInt32();
+
+ public override void Serialize(ref long value) => value = Reader.ReadInt64();
+
+ public override void Serialize(ref ushort value) => value = Reader.ReadUInt16();
+
+ public override void Serialize(ref uint value) => value = Reader.ReadUInt32();
+
+ public override void Serialize(ref ulong value) => value = Reader.ReadUInt64();
+
+ public override void Serialize(ref string value)
+ {
+ int strLength = Reader.ReadInt32();
+
+ if (strLength < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(strLength));
+ }
+
+ byte[] buffer = new byte[strLength];
+ int realLength = Reader.Read(buffer, 0, strLength);
+
+ if (realLength != strLength)
+ {
+ throw new EndOfStreamException($"Expected {strLength} bytes but read {realLength}.");
+ }
+
+ value = Encoding.UTF8.GetString(buffer, 0, realLength);
+ }
+
+ public override void Serialize(ref char value) => value = Reader.ReadChar();
+
+ public override void Serialize(ref byte value) => value = Reader.ReadByte();
+
+ public override void Serialize(ref sbyte value) => value = Reader.ReadSByte();
+
+ public override void Serialize(ref Symbol value)
+ {
+ ulong crc64 = Reader.ReadUInt64();
+ value = Symbol.FromCrc64(crc64);
+ Params.SerializedSymbols.Add(value);
+ }
+
+ public override void Serialize(byte[] values, int offset, int count) => Reader.Read(values, offset, count);
+
+ public override void Close() => Dispose(true);
+}
diff --git a/src/TelltaleToolKit/Serialization/Binary/BinaryMetaStreamWriter.cs b/src/TelltaleToolKit/Serialization/Binary/BinaryMetaStreamWriter.cs
new file mode 100644
index 0000000..7a71535
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/Binary/BinaryMetaStreamWriter.cs
@@ -0,0 +1,219 @@
+using System.Text;
+using TelltaleToolKit.T3Types;
+
+namespace TelltaleToolKit.Serialization.Binary;
+
+public sealed class BinaryMetaStreamWriter : MetaStream
+{
+ private readonly Stream _outputStream;
+ private bool _closed;
+
+ public BinaryMetaStreamWriter(Stream outputStream, MetaStreamParams @params)
+ {
+ _outputStream = outputStream;
+ Params = @params;
+ Params.SerializedSymbols.Clear();
+
+ Sections[0].Stream = new MemoryStream(0x100);
+ SetSection(SectionType.Default);
+ }
+
+ private BinaryWriter Writer { get; set; } = null!;
+
+ public override MetaStreamMode Mode => MetaStreamMode.Write;
+
+ private void WriteHeader()
+ {
+ SetSection(SectionType.Header);
+ this.Write((uint)Params.ResolveMagic());
+ uint streamVersion = Params.StreamVersion;
+
+ if (streamVersion >= 4)
+ {
+ uint defaultSize = (uint)(GetSection(SectionType.Default).Stream?.Length ?? 0);
+ uint debugSize = (uint)(GetSection(SectionType.Debug).Stream?.Length ?? 0);
+ uint asyncSize = (uint)(GetSection(SectionType.Async).Stream?.Length ?? 0);
+
+ if (GetSection(SectionType.Default).IsCompressed)
+ {
+ defaultSize |= 0x80000000;
+ }
+
+ if (GetSection(SectionType.Debug).IsCompressed)
+ {
+ debugSize |= 0x80000000;
+ }
+
+ if (GetSection(SectionType.Async).IsCompressed)
+ {
+ asyncSize |= 0x80000000;
+ }
+
+ if (streamVersion >= 5)
+ {
+ this.Write(defaultSize);
+ }
+
+ this.Write(debugSize);
+ this.Write(asyncSize);
+ }
+
+ Params.VersionInfo = Params.VersionInfo.Distinct().ToList();
+
+ this.Write(Params.VersionInfo.Count);
+
+ foreach (MetaVersionInfo? versionInfo in Params.VersionInfo)
+ {
+ if (streamVersion >= 3)
+ {
+ this.Write(versionInfo.TypeSymbolCrc);
+ }
+ else
+ {
+ this.Write(versionInfo.GetMetaClassType()!.FullTypeName);
+ }
+
+ this.Write(versionInfo.VersionCrc);
+ }
+ }
+
+ protected override void SetSection(SectionType newSection)
+ {
+ if (_currentSection == newSection)
+ {
+ return;
+ }
+
+ _currentSection = newSection;
+ Stream sectionStream = CurrentSection.Stream ?? new MemoryStream(0x4000);
+ Writer = new BinaryWriter(sectionStream, Encoding.UTF8, true);
+ CurrentSection.Stream ??= sectionStream;
+ }
+
+ public override void BeginBlock()
+ {
+ long position = CurrentSection.Stream!.Position;
+ CurrentSection.Blocks.Push(position);
+ this.Write(0);
+ }
+
+ public override void EndBlock()
+ {
+ long position = CurrentSection.Blocks.Pop();
+
+ long currentPosition = CurrentSection.Stream!.Position;
+ int blockSize = (int)(currentPosition - position);
+
+ CurrentSection.Stream.Seek(position, SeekOrigin.Begin);
+ this.Write(blockSize);
+ CurrentSection.Stream.Seek(currentPosition, SeekOrigin.Begin);
+ }
+
+ public override void Serialize(ref bool value) => Writer.Write(value ? '1' : '0');
+
+ public override void Serialize(ref float value) => Writer.Write(value);
+
+ public override void Serialize(ref double value) => Writer.Write(value);
+
+ public override void Serialize(ref short value) => Writer.Write(value);
+
+ public override void Serialize(ref int value) => Writer.Write(value);
+
+ public override void Serialize(ref long value) => Writer.Write(value);
+
+ public override void Serialize(ref ushort value) => Writer.Write(value);
+
+ public override void Serialize(ref uint value) => Writer.Write(value);
+
+ public override void Serialize(ref ulong value) => Writer.Write(value);
+
+ public override void Serialize(ref string value)
+ {
+ byte[] strBytes = Encoding.UTF8.GetBytes(value);
+ Writer.Write(strBytes.Length);
+ Writer.Write(strBytes);
+ }
+
+ public override void Serialize(ref char value) => Writer.Write(value);
+
+ public override void Serialize(ref byte value) => Writer.Write(value);
+
+ public override void Serialize(ref sbyte value) => Writer.Write(value);
+
+ public override void Serialize(byte[] values, int offset, int count) => Writer.Write(values, offset, count);
+
+ private void FinalizeStream()
+ {
+ if (_closed)
+ {
+ throw new ObjectDisposedException(nameof(BinaryMetaStreamWriter));
+ }
+
+ uint streamVersion = Params.StreamVersion;
+ bool canCompress = streamVersion >= 4;
+ // Telltale does not compress the header section, so we can skip it.
+ for (int i = 1; i <= 3; i++)
+ {
+ SectionInfo section = Sections[i];
+ if (section.Stream is { Length: > 0 } && Params.Compress && canCompress)
+ {
+ ContainerStreamParams options = new();
+ MemoryStream outStream = new(0x10000);
+ ContainerStream.Create(outStream, section.Stream, options);
+ section.Stream.Dispose();
+ section.Stream = outStream;
+ section.CompressedSize = outStream.Length;
+ section.IsCompressed = true;
+ }
+ else
+ {
+ section.CompressedSize = section.Stream?.Length ?? 0;
+ section.IsCompressed = false;
+ }
+ }
+ }
+
+ public override void Close()
+ {
+ if (_closed)
+ {
+ return;
+ }
+
+ FinalizeStream();
+ WriteHeader();
+
+ // Iterate each section (header, default, debug, async) and copy its data to the output stream.
+ foreach (SectionInfo section in Sections)
+ {
+ if (section.Stream is null)
+ {
+ continue;
+ }
+
+ section.Stream.Seek(0, SeekOrigin.Begin);
+ section.Stream.CopyTo(_outputStream);
+ }
+
+ if (Params is { Encrypt: true, StreamVersion: <= 3 })
+ {
+ MetaStreamMagic targetMagic = Params.ResolveMagic();
+
+ if (!_outputStream.CanRead || !_outputStream.CanSeek)
+ throw new InvalidOperationException(
+ "Legacy encryption requires a readable and seekable output stream (e.g. MemoryStream or FileStream opened with FileAccess.ReadWrite). " +
+ "The encryption is applied in-place after writing.");
+
+ LegacyEncryption.Encrypt(_outputStream, targetMagic, Params.Workspace!.Blowfish);
+ }
+
+ _closed = true;
+ Dispose(true);
+ }
+
+ public override void Serialize(ref Symbol value)
+ {
+ Writer.Write(value.Crc64);
+ Params.SerializedSymbols.Add(value);
+ }
+}
diff --git a/src/TelltaleToolKit/Serialization/Binary/InvalidBooleanException.cs b/src/TelltaleToolKit/Serialization/Binary/InvalidBooleanException.cs
index 805e39f..a31544b 100644
--- a/src/TelltaleToolKit/Serialization/Binary/InvalidBooleanException.cs
+++ b/src/TelltaleToolKit/Serialization/Binary/InvalidBooleanException.cs
@@ -15,4 +15,4 @@ public InvalidBooleanException(string message, Exception innerException)
: base(message, innerException)
{
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/Serialization/Binary/LegacyEncryption.cs b/src/TelltaleToolKit/Serialization/Binary/LegacyEncryption.cs
new file mode 100644
index 0000000..52d4697
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/Binary/LegacyEncryption.cs
@@ -0,0 +1,230 @@
+using System.Buffers;
+using System.Buffers.Binary;
+using TelltaleToolKit.Utility.Blowfish;
+
+namespace TelltaleToolKit.Serialization.Binary;
+
+///
+/// Provides static helpers to encrypt and decrypt legacy Telltale MetaStream files
+/// directly on a seekable , one block at a time.
+/// No full-file buffering is performed — memory usage is O(blockSize).
+///
+public static class LegacyEncryption
+{
+ ///
+ public static void Decrypt(Stream stream, string blowfishKey, int archiveVersion = 7)
+ => Decrypt(stream, new Blowfish(blowfishKey, archiveVersion));
+
+ ///
+ /// Decrypts a legacy-encrypted MetaStream in-place by reading and rewriting
+ /// one block at a time. The magic is patched to its plaintext equivalent.
+ ///
+ ///
+ /// A readable, writable, seekable stream positioned anywhere.
+ /// The stream must be at least 4 bytes long.
+ ///
+ /// Pre-built Blowfish instance for this game.
+ ///
+ /// Thrown when the stream does not support reading, writing, or seeking.
+ ///
+ public static void Decrypt(Stream stream, Blowfish blowfish)
+ {
+ EnsureStream(stream);
+ if (stream.Length < 4)
+ {
+ return;
+ }
+
+ MetaStreamMagic magic = ReadMagic(stream);
+ (int BlockSize, int CryptInterval, int CleanInterval) config = GetBlockConfig(magic);
+ if (config.BlockSize == 0)
+ {
+ return; // not an encrypted magic
+ }
+
+ ProcessBlocks(stream, blowfish, config, false);
+
+ // Patch magic to the readable plaintext equivalent
+ WriteMagic(stream, GetPlaintextMagic(magic));
+ }
+
+ // -------------------------------------------------------------------------
+ // Public API — Encrypt
+ // -------------------------------------------------------------------------
+
+ ///
+ public static void Encrypt(Stream stream, MetaStreamMagic targetMagic, string blowfishKey, int archiveVersion = 7)
+ => Encrypt(stream, targetMagic, new Blowfish(blowfishKey, archiveVersion));
+
+ ///
+ /// Encrypts a plaintext MetaStream in-place by reading and rewriting one block
+ /// at a time. The magic is patched to first so
+ /// the block layout matches what the decryptor will expect.
+ ///
+ ///
+ /// A readable, writable, seekable stream containing a plaintext MetaStream
+ /// (Mbin or Mtre magic at offset 0). Must be at least 4 bytes.
+ ///
+ ///
+ /// The encrypted magic to embed, e.g. .
+ /// Must be a recognised encrypted magic — drives the block configuration.
+ ///
+ /// Pre-built Blowfish instance for this game.
+ ///
+ /// Thrown when has no encryption scheme.
+ ///
+ ///
+ /// Thrown when the stream does not support reading, writing, or seeking.
+ ///
+ public static void Encrypt(Stream stream, MetaStreamMagic targetMagic, Blowfish blowfish)
+ {
+ EnsureStream(stream);
+ if (stream.Length < 4)
+ {
+ return;
+ }
+
+ (int BlockSize, int CryptInterval, int CleanInterval) config = GetBlockConfig(targetMagic);
+ if (config.BlockSize == 0)
+ {
+ throw new ArgumentException(
+ $"{targetMagic} is not a recognised encrypted magic.", nameof(targetMagic));
+ }
+
+ // Patch magic before processing so block offsets are consistent with the decryptor.
+ WriteMagic(stream, targetMagic);
+ ProcessBlocks(stream, blowfish, config, true);
+ }
+
+
+ private static void ProcessBlocks(Stream stream, Blowfish blowfish,
+ (int BlockSize, int CryptInterval, int CleanInterval) config, bool encrypt)
+ {
+ // Blocks begin immediately after the 4-byte magic.
+ const int dataStart = 4;
+ long dataLength = stream.Length - dataStart;
+ if (dataLength <= 0)
+ {
+ return;
+ }
+
+ int blockSize = config.BlockSize;
+ long blockCount = dataLength / blockSize;
+ byte[] buffer = ArrayPool.Shared.Rent(blockSize);
+ // Trailing partial block: untouched, matches reference behaviour.
+
+ // Rent exactly blockSize bytes — never more — so large files stay lean.
+ try
+ {
+ Span block = buffer.AsSpan(0, blockSize);
+ for (long i = 0; i < blockCount; i++)
+ {
+ long offset = dataStart + i * blockSize;
+ if (i % config.CryptInterval == 0)
+ {
+ ReadExactly(stream, offset, block);
+ if (encrypt)
+ blowfish.Encipher(block, block.Length);
+ else
+ blowfish.Decipher(block, block.Length);
+ WriteAt(stream, offset, block);
+ }
+ else if (i % config.CleanInterval == 0 && i > 0)
+ {
+ // clean block – do nothing
+ }
+ else
+ {
+ ReadExactly(stream, offset, block);
+ XorBlock(block, 0xFF);
+ WriteAt(stream, offset, block);
+ }
+ }
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
+ }
+ }
+
+ private static void ReadExactly(Stream stream, long offset, Span buffer)
+ {
+ stream.Seek(offset, SeekOrigin.Begin);
+ int total = 0;
+ while (total < buffer.Length)
+ {
+ int read = stream.Read(buffer.Slice(total));
+ if (read == 0)
+ throw new EndOfStreamException($"Expected {buffer.Length} bytes at offset {offset}, got {total}.");
+ total += read;
+ }
+ }
+
+ private static void WriteAt(Stream stream, long offset, ReadOnlySpan data)
+ {
+ stream.Seek(offset, SeekOrigin.Begin);
+ stream.Write(data);
+ }
+
+ private static MetaStreamMagic ReadMagic(Stream stream)
+ {
+ stream.Seek(0, SeekOrigin.Begin);
+ Span buf = stackalloc byte[4];
+ ReadExactly(stream, 0, buf);
+ return (MetaStreamMagic)BitConverter.ToUInt32(buf);
+ }
+
+ private static void WriteMagic(Stream stream, MetaStreamMagic magic)
+ {
+ stream.Seek(0, SeekOrigin.Begin);
+ Span buf = stackalloc byte[4];
+ BitConverter.TryWriteBytes(buf, (uint)magic);
+ stream.Write(buf);
+ }
+
+ private static void EnsureStream(Stream stream)
+ {
+ if (!stream.CanRead)
+ {
+ throw new NotSupportedException("Stream must be readable.");
+ }
+
+ if (!stream.CanWrite)
+ {
+ throw new NotSupportedException("Stream must be writable.");
+ }
+
+ if (!stream.CanSeek)
+ {
+ throw new NotSupportedException("Stream must be seekable.");
+ }
+ }
+
+ private static void XorBlock(Span block, byte mask)
+ {
+ for (int i = 0; i < block.Length; i++)
+ block[i] ^= mask;
+ }
+
+ private static (int BlockSize, int CryptInterval, int CleanInterval) GetBlockConfig(MetaStreamMagic magic) =>
+ magic switch
+ {
+ MetaStreamMagic.EncryptedMbin1
+ or MetaStreamMagic.EncryptedMbin2
+ or MetaStreamMagic.EncryptedMbin3 => (128, 32, 80),
+ MetaStreamMagic.EncryptedMtre
+ or MetaStreamMagic.EncryptedMcom => (256, 8, 24),
+ MetaStreamMagic.Mbes => (64, 64, 100),
+ _ => default
+ };
+
+ private static MetaStreamMagic GetPlaintextMagic(MetaStreamMagic encrypted) => encrypted switch
+ {
+ MetaStreamMagic.EncryptedMbin1
+ or MetaStreamMagic.EncryptedMbin2
+ or MetaStreamMagic.EncryptedMbin3 or MetaStreamMagic.Mbes => MetaStreamMagic.Mbin,
+ MetaStreamMagic.EncryptedMtre => MetaStreamMagic.Mtre,
+ MetaStreamMagic.EncryptedMcom => MetaStreamMagic.Mcom,
+ _ => encrypted
+ };
+}
diff --git a/src/TelltaleToolKit/Serialization/Binary/MetaStream.cs b/src/TelltaleToolKit/Serialization/Binary/MetaStream.cs
deleted file mode 100644
index 9cd8b96..0000000
--- a/src/TelltaleToolKit/Serialization/Binary/MetaStream.cs
+++ /dev/null
@@ -1,246 +0,0 @@
-using TelltaleToolKit.HashDatabase;
-using TelltaleToolKit.Reflection;
-using TelltaleToolKit.T3Types;
-
-namespace TelltaleToolKit.Serialization.Binary;
-
-///
-/// Stream wrapper used for serializing telltale assets.
-/// A single Telltale Tool file has 1-3 stream sections (Main, Debug, Async) depending on its metastream version.
-///
-public abstract class MetaStream : IDisposable
-{
- private SectionType _currentSection = SectionType.Main;
-
- protected enum SectionType
- {
- Main,
- Debug,
- Async
- }
-
- protected MetaSection[] Sections =
- [
- new(), // Main section
- new(), // Debug section
- new() // Async section
- ];
-
- public MetaStreamConfiguration Configuration { get; set; } = new();
-
- protected Stream UnderlyingStream { get; set; } = null!;
-
- protected Stream CurrentSubstream => GetCurrentSection().Stream;
-
- public void Dispose()
- {
- foreach (MetaSection section in Sections)
- {
- section.Stream.Dispose();
- }
-
- UnderlyingStream.Dispose();
- GC.SuppressFinalize(this);
- }
-
- protected MetaSection GetCurrentSection() => Sections[(int)_currentSection];
-
- public void BeginAsyncSection()
- {
- if (_currentSection is SectionType.Async ||
- Configuration.Version is not (MetaStreamVersion.Msv5 or MetaStreamVersion.Msv6))
- return;
- _currentSection = SectionType.Async;
- InitSerializer();
- }
-
- public void EndAsyncSection()
- {
- if (_currentSection is not SectionType.Async ||
- Configuration.Version is not (MetaStreamVersion.Msv5 or MetaStreamVersion.Msv6))
- return;
- _currentSection = SectionType.Main; // Switch back to header section
- InitSerializer();
- }
-
- public void BeginDebugSection()
- {
- if (_currentSection is SectionType.Debug ||
- Configuration.Version is not (MetaStreamVersion.Msv4 or MetaStreamVersion.Msv5 or MetaStreamVersion.Msv6))
- return;
- _currentSection = SectionType.Debug;
- InitSerializer();
- }
-
- public void EndDebugSection()
- {
- if (_currentSection is not SectionType.Debug ||
- Configuration.Version is not (MetaStreamVersion.Msv4 or MetaStreamVersion.Msv5 or MetaStreamVersion.Msv6))
- return;
- _currentSection = SectionType.Main;
- InitSerializer();
- }
-
- public abstract void SerializeMetaHeader();
-
- protected abstract void InitSerializer();
-
- public abstract void BeginBlock();
-
- public abstract void EndBlock();
-
- public bool IsSectionEmpty()
- => GetCurrentSection().Stream.Length == 0;
-
- public bool IsClassSerialized(string typeName)
- => Configuration.SerializedClasses.Any(id => id.ClassType.Symbol.DebugString == typeName);
-
- public abstract MetaClass? GetMetaClass(Type type);
-
- public abstract MetaClass? GetMetaClass(Symbol symbol);
-
- ///
- /// Serializes the specified boolean value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref bool value);
-
- ///
- /// Serializes the specified float value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref float value);
-
- ///
- /// Serializes the specified double value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref double value);
-
- ///
- /// Serializes the specified short value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref short value);
-
- ///
- /// Serializes the specified integer value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref int value);
-
- ///
- /// Serializes the specified long value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref long value);
-
- ///
- /// Serializes the specified ushort value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref ushort value);
-
- ///
- /// Serializes the specified unsigned integer value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref uint value);
-
- ///
- /// Serializes the specified unsigned long value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref ulong value);
-
- ///
- /// Serializes the specified string value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref string value);
-
- ///
- /// Serializes the specified char value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref char value);
-
- ///
- /// Serializes the specified byte value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref byte value);
-
- ///
- /// Serializes the specified signed byte value.
- ///
- /// The value to serialize
- public abstract void Serialize(ref sbyte value);
-
- ///
- /// Serializes the Symbol class.
- ///
- /// The value to serialize
- public abstract void Serialize(ref Symbol value);
-
- ///
- /// Serializes the MetaClassType class. A special reader/writer for types.
- ///
- /// The value to serialize
- public abstract void Serialize(ref MetaClassType value);
-
- ///
- /// Serializes the specified byte array.
- ///
- /// The buffer to serialize.
- /// The starting offset in the buffer to begin serializing.
- /// The size, in bytes, to serialize.
- public abstract void Serialize(byte[] values, int offset, int count);
-
- public void ThrowIfNotEndOfFile()
- {
- foreach (MetaSection stream in Sections)
- {
- var reader = new StreamReader(stream.Stream);
- if (reader.BaseStream.Position != stream.Stream.Length)
- {
- throw new InvalidDataException(
- $"FILE END NOT REACHED! Current position: {reader.BaseStream.Position}. Total size: {reader.BaseStream.Length}. Left to read: {reader.BaseStream.Length - reader.BaseStream.Position}");
- }
- }
- }
-
- // If there are assets larger than 2GBs, I need to rethink my life.
- public int GetRemainingSectionBytes()
- => (int)(GetCurrentSection().Stream.Length - GetCurrentSection().Stream.Position);
-
- public long GetCurrentPosition()
- => GetCurrentSection().Stream.Position;
-
- public void SetCurrentPosition(long position)
- => GetCurrentSection().Stream.Position = position;
-
- public void ResolveSymbols(ISymbolResolver symbolResolver)
- {
- foreach (Symbol entry in Configuration.SerializedSymbols)
- {
- symbolResolver.ResolveSymbol(entry);
-
- if (entry.DebugString is null)
- {
- Toolkit.Instance.Logger.LogWarning($"[MetaStream] Symbol not resolved: {entry.Crc64}");
- }
- }
- }
-
- public class MetaSection
- {
- // For blocks. In read, stores the sizes, in write stores the block offset initial.
- public Stack Blocks = new();
-
- public bool IsCompressed = false;
-
- // Section data stream
- public Stream Stream = null!;
- }
-}
diff --git a/src/TelltaleToolKit/Serialization/Binary/MetaStreamConfiguration.cs b/src/TelltaleToolKit/Serialization/Binary/MetaStreamConfiguration.cs
deleted file mode 100644
index 04452c2..0000000
--- a/src/TelltaleToolKit/Serialization/Binary/MetaStreamConfiguration.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using TelltaleToolKit.Reflection;
-using TelltaleToolKit.T3Types;
-
-namespace TelltaleToolKit.Serialization.Binary;
-
-///
-/// Holds configuration parameters and metadata for a MetaStream during serialization or deserialization.
-///
-public class MetaStreamConfiguration
-{
- ///
- /// Gets or sets the MetaStream version associated with this configuration.
- ///
- public MetaStreamVersion Version { get; set; }
-
- ///
- /// Gets or sets a value indicating whether symbols are stored as hashes instead of names.
- /// This exists only because MBIN is a hybrid version (Hashed in Sam and Max S2).
- ///
- public bool AreSymbolsHashed { get; set; }
-
- ///
- /// Gets the list of objects for all classes that have been serialized in the current stream.
- ///
- public List SerializedClasses { get; set; } = [];
-
- ///
- /// Gets the list of classes found in the stream that are not registered in the current game profile.
- /// Each entry is a tuple containing the and its CRC32 checksum.
- ///
- public List<(MetaClassType, uint crc32)> UnregisteredClasses { get; set; } = [];
-
- ///
- /// Gets the list of type hashes and checksums for types encountered in the stream that could not be resolved.
- /// Each entry is a tuple of the type hash (ulong) and CRC32 checksum.
- ///
- public List<(ulong, uint)> UnregisteredTypes { get; set; } = [];
-
- ///
- /// Gets the list of serialized objects found in the stream.
- ///
- public List SerializedSymbols { get; set; } = [];
-
- ///
- /// Gets or sets a value indicating whether the list of serialized classes can be modified after initialization.
- ///
- public bool CanModifySerializedClassesList { get; set; }
-
- ///
- /// Gets the current workspace. Required for serializing if custom types are added.
- ///
- public Workspace? Workspace { get; set; }
-}
\ No newline at end of file
diff --git a/src/TelltaleToolKit/Serialization/Binary/MetaStreamReader.cs b/src/TelltaleToolKit/Serialization/Binary/MetaStreamReader.cs
deleted file mode 100644
index 5e5e520..0000000
--- a/src/TelltaleToolKit/Serialization/Binary/MetaStreamReader.cs
+++ /dev/null
@@ -1,277 +0,0 @@
-using System.Text;
-using TelltaleToolKit.Reflection;
-using TelltaleToolKit.T3Types;
-
-namespace TelltaleToolKit.Serialization.Binary;
-
-public sealed class MetaStreamReader : MetaStream
-{
- public MetaStreamReader(Stream inputStream)
- {
- Reader = new BinaryReader(inputStream, Encoding.UTF8, true);
- UnderlyingStream = inputStream;
- SerializeMetaHeader();
- }
-
- private BinaryReader Reader { get; set; }
-
- private void ValidateMetaHeader()
- {
- if (UnderlyingStream.Length < 4)
- throw new InvalidDataException("Invalid MetaHeader: Stream too short.");
-
- UnderlyingStream.Position = 0;
- Configuration.Version = (MetaStreamVersion)this.ReadUInt32();
-
- if (!Enum.IsDefined(typeof(MetaStreamVersion), Configuration.Version))
- throw new InvalidDataException($"Not a valid meta stream version: {Configuration.Version}");
- }
-
- public override void SerializeMetaHeader()
- {
- ValidateMetaHeader();
-
- uint mainSize = 0, debugSize = 0, asyncSize = 0;
-
- switch (Configuration.Version)
- {
- case MetaStreamVersion.Mbes:
- throw new NotSupportedException("MBES is not supported yet.");
- // TODO optional compressed sections (set top bit of size to 1)
- case MetaStreamVersion.Msv4:
- mainSize = this.ReadUInt32();
- asyncSize = this.ReadUInt32();
- break;
- case MetaStreamVersion.Msv6
- or MetaStreamVersion.Msv5:
- mainSize = this.ReadUInt32();
- debugSize = this.ReadUInt32();
- asyncSize = this.ReadUInt32();
- break;
- }
-
- // Read the number of serialized classes
- uint metaClassNum = this.ReadUInt32();
-
- if (metaClassNum >= 1000)
- throw new InvalidDataException("Too many classes in the meta stream.");
-
- for (var i = 0; i < metaClassNum; i++)
- {
- uint size = this.ReadUInt32();
-
- // Read the type hash or name (256 is a good number to use for the size of the name)
- Configuration.AreSymbolsHashed = size > 255;
-
- Reader.BaseStream.Seek(-4, SeekOrigin.Current);
-
- MetaClassType type = this.ReadMetaClassType();
- uint crc32 = this.ReadUInt32();
-
- // If the type is not recognized at all
- if (type.FullTypeName == string.Empty)
- {
- if (Configuration.UnregisteredTypes.Count > 0)
- {
- int lastIndex = Configuration.UnregisteredTypes.Count - 1;
- (ulong, uint) lastTuple = Configuration.UnregisteredTypes[lastIndex];
- Configuration.UnregisteredTypes[lastIndex] = (lastTuple.Item1, crc32);
- }
-
- continue;
- }
-
- MetaClass? mcs = Toolkit.Instance.ClassRegistry.GetClass(type.Symbol, crc32);
-
- if (mcs is not null)
- {
- // If the class description exists, add it. Otherwise TODO
- Configuration.SerializedClasses.Add(mcs);
- }
- else
- {
- Configuration.UnregisteredClasses.Add((type, crc32));
- }
- }
-
- long currentPosition = Reader.BaseStream.Position;
- if (Configuration.Version is MetaStreamVersion.Msv6 or MetaStreamVersion.Msv5)
- {
- Sections[(int)SectionType.Main].Stream = new SubStream(UnderlyingStream, currentPosition, mainSize);
- Sections[(int)SectionType.Debug].Stream =
- new SubStream(UnderlyingStream, currentPosition + mainSize, debugSize);
- Sections[(int)SectionType.Async].Stream = new SubStream(
- UnderlyingStream,
- currentPosition + mainSize + debugSize,
- asyncSize
- );
- }
- else
- {
- // For older versions, the entire stream is the main section
- Sections[(int)SectionType.Main].Stream = new SubStream(
- UnderlyingStream,
- currentPosition,
- UnderlyingStream.Length - currentPosition
- );
- Sections[(int)SectionType.Debug].Stream = new SubStream(UnderlyingStream, 0, 0);
- Sections[(int)SectionType.Async].Stream = new SubStream(UnderlyingStream, 0, 0);
- }
-
- InitSerializer();
- }
-
- public static bool IsValidMetaStream(Stream stream)
- {
- if (stream.Length < 4)
- return false;
-
- long originalPosition = stream.Position;
-
- try
- {
- using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
- uint version = reader.ReadUInt32();
-
- // Check if it's a valid MetaStream version
- return Enum.IsDefined(typeof(MetaStreamVersion), version);
- }
- finally
- {
- stream.Position = originalPosition;
- }
- }
-
- protected override void InitSerializer() => Reader = new BinaryReader(CurrentSubstream, Encoding.UTF8, true);
-
- public override void BeginBlock()
- {
- int size = this.ReadInt32();
- long expectedPosition = GetCurrentSection().Stream.Position + size - sizeof(int);
- GetCurrentSection().Blocks.Push(expectedPosition);
- }
-
- ///
- public override void EndBlock()
- {
- MetaSection currentSection = GetCurrentSection();
- long expectedPosition = currentSection.Blocks.Pop();
- long currentPosition = currentSection.Stream.Position;
- if (expectedPosition == currentPosition)
- return;
- // Telltale Tool skips the block if it's not read fully.
- // I have no idea how they got there in the first place, but I saw one such file (ui_mainmenu_background.scene) which benefits from this.
- // Previously, I used to throw exceptions since I thought it was impossible, but this seems better for stability purposes.
- // This matches the implementation of the engine behaviour.
- Toolkit.Instance.Logger.LogWarning($"[MetaStreamReader] Invalid data position! Current Position: {currentPosition}. Expected Position: {expectedPosition}.");
- GetCurrentSection().Stream.Position = expectedPosition;
- }
-
- ///
- /// Skip a block.
- /// This is internally used, even by Telltale themselves.
- ///
- public void SkipToEndOfCurrentBlock()
- {
- MetaSection currentSection = GetCurrentSection();
- long expectedPosition = currentSection.Blocks.Pop();
- currentSection.Stream.Seek(expectedPosition, SeekOrigin.Begin);
- }
-
- public override MetaClass? GetMetaClass(Type type) =>
- Configuration.SerializedClasses.FirstOrDefault(tc => tc.ClassType.LinkingType == type);
-
- public override MetaClass? GetMetaClass(Symbol symbol) =>
- Configuration.SerializedClasses.FirstOrDefault(tc => tc.ClassType.Symbol.Crc64 == symbol.Crc64);
-
- ///
- public override void Serialize(ref bool value)
- {
- value = Reader.ReadChar() switch
- {
- '1' => true,
- '0' => false,
- _ => throw new InvalidBooleanException($"Invalid boolean at position: {Reader.BaseStream.Position}!")
- };
- }
-
- ///
- public override void Serialize(ref float value) => value = Reader.ReadSingle();
-
- ///
- public override void Serialize(ref double value) => value = Reader.ReadDouble();
-
- ///
- public override void Serialize(ref short value) => value = Reader.ReadInt16();
-
- ///
- public override void Serialize(ref int value) => value = Reader.ReadInt32();
-
- ///
- public override void Serialize(ref long value) => value = Reader.ReadInt64();
-
- ///
- public override void Serialize(ref ushort value) => value = Reader.ReadUInt16();
-
- ///
- public override void Serialize(ref uint value) => value = Reader.ReadUInt32();
-
- ///
- public override void Serialize(ref ulong value) => value = Reader.ReadUInt64();
-
- ///
- public override void Serialize(ref string value)
- {
- int strLength = Reader.ReadInt32();
-
- if (strLength < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(strLength));
- }
-
- var buffer = new byte[strLength];
- int realLength = Reader.Read(buffer, 0, strLength);
-
- value = Encoding.UTF8.GetString(buffer, 0, realLength);
- }
-
- public override void Serialize(ref char value) => value = Reader.ReadChar();
-
- public override void Serialize(ref byte value) => value = Reader.ReadByte();
-
- public override void Serialize(ref sbyte value) => value = Reader.ReadSByte();
-
- public override void Serialize(ref Symbol value)
- {
- ulong crc64 = Reader.ReadUInt64();
- value = Symbol.FromCrc64(crc64);
- Configuration.SerializedSymbols.Add(value);
- }
-
- public override void Serialize(ref MetaClassType value)
- {
- if (Configuration.AreSymbolsHashed)
- {
- ulong hash = this.ReadUInt64();
- MetaClassType? type = MetaClassTypeRegistry.GetByHash(hash);
-
- if (type == null)
- {
- Toolkit.Instance.Logger.LogWarning($"[MetaStreamReader] Unknown MetaClassType: {hash:X16})");
- Configuration.UnregisteredTypes.Add((hash, 0));
- }
- else
- {
- value = type;
- }
- }
- else
- {
- string fullType = this.ReadString();
- value = MetaClassTypeRegistry.GetByName(MetaClassType.GetStrippedTypeName(fullType));
- // I don't check for unregistered types in old games. Since types are unhashed, they are probably all registered. :)
- }
- }
-
- public override void Serialize(byte[] values, int offset, int count) => Reader.Read(values, offset, count);
-}
diff --git a/src/TelltaleToolKit/Serialization/Binary/MetaStreamVersion.cs b/src/TelltaleToolKit/Serialization/Binary/MetaStreamVersion.cs
deleted file mode 100644
index 03c89aa..0000000
--- a/src/TelltaleToolKit/Serialization/Binary/MetaStreamVersion.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace TelltaleToolKit.Serialization.Binary;
-
-public enum MetaStreamVersion : uint
-{
- Mbin = 0x4D42494E,
- Mbes = 0x4D424553,
- Mtre = 0x4D545245,
- Mcom = 0x4D434F4D,
- Msv4 = 0x4D535356,
- Msv5 = 0x4D535635,
- Msv6 = 0x4D535636,
-
- // I have no idea why Telltale got these values
- Unknown1 = 0xFB4A1764,
- Unknown2 = 0xEB794091,
- Unknown3 = 0x64AFDEFB,
- Unknown4 = 0x64AFDEAA,
-}
\ No newline at end of file
diff --git a/src/TelltaleToolKit/Serialization/Binary/MetaStreamVersionExtensions.cs b/src/TelltaleToolKit/Serialization/Binary/MetaStreamVersionExtensions.cs
deleted file mode 100644
index 96c1695..0000000
--- a/src/TelltaleToolKit/Serialization/Binary/MetaStreamVersionExtensions.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-namespace TelltaleToolKit.Serialization.Binary;
-
-public static class StreamVersionExtensions
-{
- ///
- /// Converts a string to StreamVersion.
- ///
- public static MetaStreamVersion StreamVersionFromString(this string value)
- {
- return value switch
- {
- "MBIN" => MetaStreamVersion.Mbin,
- "MBES" => MetaStreamVersion.Mbes,
- "MTRE" => MetaStreamVersion.Mtre,
- "MCOM" => MetaStreamVersion.Mcom,
- "MSV4" => MetaStreamVersion.Msv4,
- "MSV5" => MetaStreamVersion.Msv5,
- "MSV6" => MetaStreamVersion.Msv6,
- _ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
- };
- }
-
- ///
- /// Converts a StreamVersion to its string name.
- ///
- public static string ToJsonString(this MetaStreamVersion version)
- {
- return version switch
- {
- MetaStreamVersion.Mbin => "MBIN",
- MetaStreamVersion.Mbes => "MBES",
- MetaStreamVersion.Mcom => "MCOM",
- MetaStreamVersion.Mtre => "MTRE",
- MetaStreamVersion.Msv4 => "MSV4",
- MetaStreamVersion.Msv5 => "MSV5",
- MetaStreamVersion.Msv6 => "MSV6",
- _ => string.Empty
- };
- }
-}
\ No newline at end of file
diff --git a/src/TelltaleToolKit/Serialization/Binary/MetaStreamWriter.cs b/src/TelltaleToolKit/Serialization/Binary/MetaStreamWriter.cs
deleted file mode 100644
index 26bc12e..0000000
--- a/src/TelltaleToolKit/Serialization/Binary/MetaStreamWriter.cs
+++ /dev/null
@@ -1,183 +0,0 @@
-using System.Text;
-using TelltaleToolKit.Reflection;
-using TelltaleToolKit.T3Types;
-
-namespace TelltaleToolKit.Serialization.Binary;
-
-public sealed class MetaStreamWriter : MetaStream
-{
- public MetaStreamWriter(Stream inputStream, MetaStreamConfiguration configuration)
- {
- UnderlyingStream = inputStream;
-
- Sections[(int)SectionType.Main].Stream = new MemoryStream();
- Sections[(int)SectionType.Debug].Stream = new MemoryStream();
- Sections[(int)SectionType.Async].Stream = new MemoryStream();
- Configuration = configuration;
-
- InitSerializer();
- }
-
- private BinaryWriter Writer { get; set; } = null!;
-
- public void Save()
- {
- SerializeMetaHeader();
-
- foreach (MetaSection section in Sections)
- {
- section.Stream.Seek(0, SeekOrigin.Begin);
- section.Stream.CopyTo(UnderlyingStream);
- }
-
- // Truncate, otherwise there are some leftover bytes.
- UnderlyingStream.SetLength(UnderlyingStream.Position);
- }
-
- public override void SerializeMetaHeader()
- {
- Writer = new BinaryWriter(UnderlyingStream);
- Writer.BaseStream.Seek(0, SeekOrigin.Begin);
-
- if (!Enum.IsDefined(typeof(MetaStreamVersion), Configuration.Version))
- throw new InvalidDataException("Version not defined");
-
- this.Write((uint)Configuration.Version);
-
- switch (Configuration.Version)
- {
- case MetaStreamVersion.Mbes:
- throw new NotSupportedException("MBES is not supported yet.");
- // TODO: optional compressed sections (set top bit of size to 1)
- case MetaStreamVersion.Msv6
- or MetaStreamVersion.Msv5:
- this.Write((int)Sections[(int)SectionType.Main].Stream.Length);
- this.Write((int)Sections[(int)SectionType.Debug].Stream.Length);
- this.Write((int)Sections[(int)SectionType.Async].Stream.Length);
- break;
- }
-
- Configuration.SerializedClasses = Configuration.SerializedClasses.Distinct().ToList();
-
- this.Write(Configuration.SerializedClasses.Count);
-
- foreach (MetaClass t in Configuration.SerializedClasses)
- {
- MetaClassType metaClassType = t.ClassType;
- Serialize(ref metaClassType);
- this.Write(t.Crc32);
- }
- }
-
- protected override void InitSerializer() => Writer = new BinaryWriter(CurrentSubstream, Encoding.UTF8, true);
-
- public override void BeginBlock()
- {
- GetCurrentSection().Blocks.Push(GetCurrentSection().Stream.Position);
- this.Write(0);
- }
-
- public override void EndBlock()
- {
- long position = GetCurrentSection().Blocks.Pop();
-
- var blockSize = (int)(GetCurrentSection().Stream.Position - position);
- long currentPosition = GetCurrentSection().Stream.Position;
-
- GetCurrentSection().Stream.Seek(position, SeekOrigin.Begin);
- this.Write(blockSize);
- GetCurrentSection().Stream.Seek(currentPosition, SeekOrigin.Begin);
- }
-
- public override MetaClass? GetMetaClass(Type type)
- {
- // Do not modify the metaclass descriptions. This operation is not recommended for complex types, especially those which have PropertySet (properties).
- if (!Configuration.CanModifySerializedClassesList)
- {
- return Configuration.SerializedClasses.FirstOrDefault(tc => tc.ClassType.LinkingType == type);
- }
-
- return Configuration.Workspace?.GetMetaClassDescription(type);
- }
-
- public override MetaClass? GetMetaClass(Symbol symbol)
- {
- // Do not modify the metaclass descriptions. This operation is not recommended for complex types, especially those which have PropertySet (properties).
- if (!Configuration.CanModifySerializedClassesList)
- {
- return Configuration.SerializedClasses.FirstOrDefault(tc => tc.ClassType.Symbol.Crc64 == symbol.Crc64);
- }
-
- return Configuration.Workspace?.GetMetaClassDescription(symbol);
- }
-
- public override void Serialize(ref bool value) => Writer.Write(value ? '1' : '0');
-
- public override void Serialize(ref float value) => Writer.Write(value);
-
- public override void Serialize(ref double value) => Writer.Write(value);
-
- public override void Serialize(ref short value) => Writer.Write(value);
-
- public override void Serialize(ref int value) => Writer.Write(value);
-
- public override void Serialize(ref long value) => Writer.Write(value);
-
- public override void Serialize(ref ushort value) => Writer.Write(value);
-
- public override void Serialize(ref uint value) => Writer.Write(value);
-
- public override void Serialize(ref ulong value) => Writer.Write(value);
-
- public override void Serialize(ref string value)
- {
- byte[] strBytes = Encoding.UTF8.GetBytes(value);
- Writer.Write(strBytes.Length);
- Writer.Write(strBytes);
- }
-
- public override void Serialize(ref char value) => Writer.Write(value);
-
- public override void Serialize(ref byte value) => Writer.Write(value);
-
- public override void Serialize(ref sbyte value) => Writer.Write(value);
-
- public override void Serialize(ref MetaClassType value)
- {
- if (Configuration.AreSymbolsHashed)
- {
- this.Write(value.Symbol.Crc64);
- }
- else
- {
- this.Write(value.FullTypeName);
- }
- }
-
- public override void Serialize(byte[] values, int offset, int count) => Writer.Write(values, offset, count);
-
- public override void Serialize(ref Symbol value)
- {
- if (Configuration.AreSymbolsHashed)
- {
- MetaClass? description = GetMetaClass(typeof(Symbol));
- AddVersionInfo(description);
- Writer.Write(value.Crc64);
- }
- else
- {
- string valueSymbolName = value.DebugString;
- Serialize(ref valueSymbolName);
- }
- }
-
- public void AddVersionInfo(MetaClass? desc)
- {
- if (desc is null || Configuration.SerializedClasses.Contains(desc))
- {
- return;
- }
-
- Configuration.SerializedClasses.Add(desc);
- }
-}
diff --git a/src/TelltaleToolKit/Serialization/Binary/StreamExtensions.cs b/src/TelltaleToolKit/Serialization/Binary/StreamExtensions.cs
deleted file mode 100644
index 40c59e5..0000000
--- a/src/TelltaleToolKit/Serialization/Binary/StreamExtensions.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace TelltaleToolKit.Serialization.Binary;
-
-///
-/// Provides extension methods.
-///
-public static class StreamExtensions
-{
- ///
- /// Returns a substream instance sourcing from this stream at the specified offset with the specified length.
- ///
- /// The sourcing stream.
- /// The offset.
- /// The length.
- /// A substream.
- public static SubStream Substream(this Stream stream, long offset, long length) =>
- new SubStream(stream, offset, length);
-}
\ No newline at end of file
diff --git a/src/TelltaleToolKit/TelltaleArchives/ContainerMagic.cs b/src/TelltaleToolKit/Serialization/ContainerMagic.cs
similarity index 95%
rename from src/TelltaleToolKit/TelltaleArchives/ContainerMagic.cs
rename to src/TelltaleToolKit/Serialization/ContainerMagic.cs
index 651a467..4a984ab 100644
--- a/src/TelltaleToolKit/TelltaleArchives/ContainerMagic.cs
+++ b/src/TelltaleToolKit/Serialization/ContainerMagic.cs
@@ -1,4 +1,4 @@
-namespace TelltaleToolKit.TelltaleArchives;
+namespace TelltaleToolKit.Serialization;
///
/// Four‑byte magic numbers identifying the container format of a file.
diff --git a/src/TelltaleToolKit/TelltaleArchives/IO/ContainerStream.cs b/src/TelltaleToolKit/Serialization/ContainerStream.cs
similarity index 75%
rename from src/TelltaleToolKit/TelltaleArchives/IO/ContainerStream.cs
rename to src/TelltaleToolKit/Serialization/ContainerStream.cs
index 2b0e9ee..c236c4c 100644
--- a/src/TelltaleToolKit/TelltaleArchives/IO/ContainerStream.cs
+++ b/src/TelltaleToolKit/Serialization/ContainerStream.cs
@@ -1,9 +1,11 @@
-using System.Text;
-using TelltaleToolKit.TelltaleArchives.Caching;
-using TelltaleToolKit.TelltaleArchives.Formats;
+using System.IO.Compression;
+using System.Text;
+using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using TelltaleToolKit.Utility.Blowfish;
+using TelltaleToolKit.Utility.Caching;
+using TelltaleToolKit.Utility.Compression;
-namespace TelltaleToolKit.TelltaleArchives.IO;
+namespace TelltaleToolKit.Serialization;
///
/// A forward-readable, seekable that decodes the outer
@@ -12,16 +14,11 @@ namespace TelltaleToolKit.TelltaleArchives.IO;
///
///
/// Single responsibility: parse the container header and expose the
-/// inner TTA2/3/4 payload as a readable stream so can walk
+/// inner TTA2/3/4 payload as a readable stream so can walk
/// the entry table and name-page stream. It is disposed immediately after that forward
/// pass — extraction never goes through it.
///
///
-/// No caching. Header parsing is a single sequential forward pass over
-/// a tiny fraction of the archive. Decoding a chunk once and discarding it is always
-/// enough. This avoids any heap accumulation for 2 GB+ archives.
-///
-///
/// O(1) chunk offset lookup. A prefix-sum table ()
/// is built once from the chunk-size array during construction, so seeking to any chunk
/// does not require walking the size array.
@@ -33,7 +30,7 @@ public sealed class ContainerStream : Stream
private readonly IChunkCache _cache;
// Prefix-sum table: _chunkPrefixSums[i] = sum of compressed sizes of chunks 0..i-1.
- // Length = ChunkCount + 1 so _chunkPrefixSums[ChunkCount] = total compressed payload size.
+ // Length = NumPages + 1 so _chunkPrefixSums[NumPages] = total compressed payload size.
// Built once during construction for O(1) chunk-offset lookup.
private readonly ulong[] _chunkPrefixSums;
// -------------------------------------------------------------------------
@@ -65,60 +62,37 @@ public ContainerStream(Stream source, string blowfishKey, IChunkCache? cache = n
// ---- Container magic ----
ContainerMagic = (ContainerMagic)reader.ReadUInt32();
- ArchiveFlags flags = ArchiveFlags.None;
-
- switch (ContainerMagic)
+ if (!Enum.IsDefined(typeof(ContainerMagic), ContainerMagic))
{
- case ContainerMagic.TTCN:
- // No encryption, no compression — nothing to add.
- break;
-
- case ContainerMagic.TTCE:
- flags |= ArchiveFlags.IsEncrypted | ArchiveFlags.IsRawDeflateCompressed;
- break;
-
- case ContainerMagic.TTCZ:
- flags |= ArchiveFlags.IsRawDeflateCompressed;
- break;
-
- case ContainerMagic.TTCe:
- case ContainerMagic.TTCz:
- {
- if (ContainerMagic is ContainerMagic.TTCe)
- {
- flags |= ArchiveFlags.IsEncrypted;
- }
-
- uint compressionType = reader.ReadUInt32();
- flags |= compressionType switch
- {
- 0 => ArchiveFlags.IsRawDeflateCompressed,
- 1 => ArchiveFlags.IsOodleCompressed,
- _ => throw new NotSupportedException(
- $"[ContainerStream] Unknown compression type {compressionType}.")
- };
- break;
- }
-
- default:
- throw new NotSupportedException(
- $"[ContainerStream] Unknown container version 0x{(uint)ContainerMagic:X8}.");
+ throw new NotSupportedException($"[ContainerStream] Unknown container magic {ContainerMagic}.");
}
- Flags = flags;
+ Compression algorithm = ContainerMagic switch
+ {
+ ContainerMagic.TTCN => Compression.None,
+ ContainerMagic.TTCe or ContainerMagic.TTCz => ReadCompressionType(reader),
+ _ => Compression.Deflate
+ };
+
+ Params = new ContainerStreamParams
+ {
+ Encrypt = ContainerMagic is ContainerMagic.TTCE or ContainerMagic.TTCe,
+ BlowfishKey = blowfishKey,
+ Algorithm = algorithm
+ };
- if (flags.IsCompressed())
+ if (Params.Algorithm != Compression.None)
{
- ChunkSize = reader.ReadUInt32();
+ WindowSize = reader.ReadUInt32();
- ChunkCount = reader.ReadUInt32();
- ChunkBlockSizes = new ulong[ChunkCount];
+ NumPages = reader.ReadUInt32();
+ ChunkBlockSizes = new ulong[NumPages];
- // The on-disk table stores ChunkCount + 1 cumulative absolute offsets.
+ // The on-disk table stores NumPages + 1 cumulative absolute offsets.
// Element 0 is the start of the first chunk (always 0 relative to payload start).
// We derive per-chunk sizes from successive differences.
ulong prev = reader.ReadUInt64(); // always 0
- for (int i = 0; i < ChunkCount; i++)
+ for (int i = 0; i < NumPages; i++)
{
ulong next = reader.ReadUInt64();
ChunkBlockSizes[i] = next - prev;
@@ -126,9 +100,9 @@ public ContainerStream(Stream source, string blowfishKey, IChunkCache? cache = n
}
// Build prefix-sum table for O(1) chunk-offset lookup.
- _chunkPrefixSums = BuildPrefixSums(ChunkBlockSizes, ChunkCount);
+ _chunkPrefixSums = BuildPrefixSums(ChunkBlockSizes, NumPages);
- Length = ChunkCount * ChunkSize;
+ Length = NumPages * WindowSize;
}
else
{
@@ -140,6 +114,18 @@ public ContainerStream(Stream source, string blowfishKey, IChunkCache? cache = n
PayloadStart = source.Position; // first byte of TTA2/3/4 payload
}
+ private static Compression ReadCompressionType(BinaryReader reader)
+ {
+ uint compressionType = reader.ReadUInt32();
+ return compressionType switch
+ {
+ 0 => Compression.Deflate,
+ 1 => Compression.Oodle,
+ _ => throw new NotSupportedException(
+ $"[ContainerStream] Unknown compression type {compressionType}.")
+ };
+ }
+
// -------------------------------------------------------------------------
// Properties exposed to TTArchive2
// -------------------------------------------------------------------------
@@ -148,13 +134,13 @@ public ContainerStream(Stream source, string blowfishKey, IChunkCache? cache = n
public ContainerMagic ContainerMagic { get; }
/// Encryption and compression flags derived from the container magic.
- public ArchiveFlags Flags { get; }
+ public ContainerStreamParams Params { get; }
/// Decompressed size of each chunk in bytes. Zero for uncompressed containers.
- public uint ChunkSize { get; }
+ public uint WindowSize { get; }
/// Number of compressed chunks. Zero for uncompressed containers.
- public uint ChunkCount { get; }
+ public uint NumPages { get; }
/// Compressed byte-length of each chunk. Empty for uncompressed containers.
public ulong[] ChunkBlockSizes { get; }
@@ -222,7 +208,7 @@ public override int Read(Span buffer)
}
// ---- Uncompressed — backed by MemoryStream ----
- if (!Flags.IsCompressed())
+ if (Params.Algorithm is Compression.None)
{
_source.Position = PayloadStart + _position;
int bytesToRead = (int)Math.Min(buffer.Length, Length - _position);
@@ -238,7 +224,7 @@ public override int Read(Span buffer)
return 0;
}
- if (Flags.IsEncrypted())
+ if (Params.Encrypt)
{
Blowfish bf = new(_blowfishKey, 7);
bf.Decipher(temp, read);
@@ -256,8 +242,8 @@ public override int Read(Span buffer)
while (totalRead < buffer.Length)
{
- int chunkIndex = (int)(_position / ChunkSize);
- if ((uint)chunkIndex >= ChunkCount)
+ int chunkIndex = (int)(_position / WindowSize);
+ if ((uint)chunkIndex >= NumPages)
{
break; // past end of payload
}
@@ -265,7 +251,7 @@ public override int Read(Span buffer)
// Use cache to get decoded chunk
byte[] decoded = _cache.GetOrDecode(chunkIndex, DecodeChunk);
- int inChunk = (int)(_position % ChunkSize);
+ int inChunk = (int)(_position % WindowSize);
int avail = decoded.Length - inChunk;
if (avail <= 0)
{
@@ -290,8 +276,44 @@ private byte[] DecodeChunk(int chunkIndex)
byte[] compressed = new byte[ChunkBlockSizes[chunkIndex]];
ChunkDecoder.ReadExact(_source, compressed);
- ArchiveInfo tempInfo = new() { Flags = Flags, BlowfishKey = _blowfishKey };
- return ChunkDecoder.DecryptAndDecompress(compressed, ChunkSize, tempInfo);
+ if (Params.Encrypt)
+ {
+ Blowfish bf = new(Params.BlowfishKey, 7);
+ bf.Decipher(compressed.AsSpan(), compressed.Length);
+ }
+
+ return DecompressBlock(compressed, (int)WindowSize, Params.Algorithm);
+ }
+
+ public static byte[] DecompressBlock(byte[] compressedData, int expectedSize,
+ Compression compression)
+ {
+ // This is a hack? In TWD:DE, 401_txmesh, there's a page which is the same size as the expected size (65535)
+ // C#'s raw deflate fails.
+ // ttarchext returns the same data:
+ // https://github.com/infernokun/TelltaleGamesExtractionGUI/blob/fec9fc1a70b545bcc67062fedc3fe3f7cd0d3e1b/bin/Debug/net6.0-windows/ttarchext/ttarchext.c#L1512
+ if (compressedData.Length == expectedSize)
+ {
+ return compressedData;
+ }
+
+ return compression switch
+ {
+ Compression.Deflate => Decompress(ms => new DeflateStream(ms, CompressionMode.Decompress)),
+ Compression.Zlib => Decompress(ms => new InflaterInputStream(ms)),
+ Compression.Oodle => throw new NotSupportedException("Oodle compression is not supported yet."),
+ // No compression, return the original data
+ _ => compressedData
+ };
+
+ // Local function to handle decompression
+ byte[] Decompress(Func streamFactory)
+ {
+ using MemoryStream outputStream = new(expectedSize);
+ using Stream decompressStream = streamFactory(new MemoryStream(compressedData));
+ decompressStream.CopyTo(outputStream);
+ return outputStream.ToArray();
+ }
}
// -------------------------------------------------------------------------
@@ -332,6 +354,8 @@ private static ulong[] BuildPrefixSums(ulong[] sizes, uint count)
return sums;
}
+
+
///
/// Writes a Telltale Tool Container (TTC*) around the given payload.
/// Automatically selects the magic number based on encryption and compression settings.
@@ -339,7 +363,7 @@ private static ulong[] BuildPrefixSums(ulong[] sizes, uint count)
/// Stream to write the container to. Will be left open.
/// Uncompressed, unencrypted inner data (TTA header + files).
/// Compression, encryption, chunk size, Blowfish key, etc.
- public static void Create(Stream destination, ReadOnlySpan payload, ArchiveWriteOptions options)
+ public static void Create(Stream destination, ReadOnlySpan payload, ContainerStreamParams options)
{
using MemoryStream ms = new(payload.ToArray());
Create(destination, ms, options);
@@ -353,9 +377,9 @@ public static void Create(Stream destination, ReadOnlySpan payload, Archiv
/// Stream containing the uncompressed inner payload. Must support seeking and provide a correct Length.
///
/// Archive options (compression, encryption, chunk size, key).
- public static void Create(Stream destination, Stream payloadStream, ArchiveWriteOptions options)
+ public static void Create(Stream destination, Stream payloadStream, ContainerStreamParams options)
{
- if (options.Algorithm == CompressionAlgorithm.Oodle)
+ if (options.Algorithm == Compression.Oodle)
{
throw new NotSupportedException("Oodle compression not yet implemented.");
}
@@ -363,12 +387,12 @@ public static void Create(Stream destination, Stream payloadStream, ArchiveWrite
// Choose magic automatically
ContainerMagic magic = (options.Encrypt, options.Algorithm) switch
{
- (false, CompressionAlgorithm.None) => ContainerMagic.TTCN,
- (true, CompressionAlgorithm.Deflate) => ContainerMagic.TTCE,
- (false, CompressionAlgorithm.Deflate) => ContainerMagic.TTCZ,
- (true, CompressionAlgorithm.Oodle) => ContainerMagic
+ (false, Compression.None) => ContainerMagic.TTCN,
+ (true, Compression.Deflate) => ContainerMagic.TTCE,
+ (false, Compression.Deflate) => ContainerMagic.TTCZ,
+ (true, Compression.Oodle) => ContainerMagic
.TTCe, // encryption without compression? Rare, but fallback
- (false, CompressionAlgorithm.Oodle) => ContainerMagic
+ (false, Compression.Oodle) => ContainerMagic
.TTCz, // encryption without compression? Rare, but fallback
_ => throw new NotSupportedException(
$"Unsupported combination: Encrypt={options.Encrypt}, Algorithm={options.Algorithm}")
@@ -379,11 +403,11 @@ public static void Create(Stream destination, Stream payloadStream, ArchiveWrite
if (magic is ContainerMagic.TTCe or ContainerMagic.TTCz)
{
- uint comprType = options.Algorithm == CompressionAlgorithm.Deflate ? 0u : 1u; // 1 = Oodle
+ uint comprType = options.Algorithm == Compression.Deflate ? 0u : 1u; // 1 = Oodle
writer.Write(comprType);
}
- bool compress = options.Algorithm != CompressionAlgorithm.None;
+ bool compress = options.Algorithm != Compression.None;
Blowfish blowfish = new(options.BlowfishKey, 7);
if (!compress)
@@ -429,7 +453,7 @@ public static void Create(Stream destination, Stream payloadStream, ArchiveWrite
throw new EndOfStreamException("Unexpected end of payload stream.");
}
- int compressedLen = TelltaleArchiveUtilities.CompressBlock(chunk, options.Algorithm, compressedBuffer);
+ int compressedLen = Utility.Compression.ChunkDecoder.CompressBlock(chunk, options.Algorithm, compressedBuffer);
byte[] finalData = compressedBuffer.AsSpan(0, compressedLen).ToArray();
if (options.Encrypt)
{
diff --git a/src/TelltaleToolKit/Serialization/ContainerStreamParams.cs b/src/TelltaleToolKit/Serialization/ContainerStreamParams.cs
new file mode 100644
index 0000000..4b8fa81
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/ContainerStreamParams.cs
@@ -0,0 +1,28 @@
+using TelltaleToolKit.Utility.Compression;
+
+namespace TelltaleToolKit.Serialization;
+
+///
+/// Parameters for creating a Telltale Tool Container stream.
+/// Mirrors the engine's DataStreamContainerParams.
+///
+public sealed class ContainerStreamParams
+{
+ /// Decompressed size of each chunk. Defaults to 0x10000 (64 KB).
+ public uint ChunkSize { get; init; } = 0x10000;
+
+ /// Whether to encrypt compressed chunks with Blowfish.
+ public bool Encrypt { get; init; }
+
+ ///
+ /// The compression algorithm used to compress the container.
+ /// Defaults to Deflate (ZLIB in Telltale's terms).
+ ///
+ public Compression Algorithm { get; init; } = Compression.Deflate;
+
+ ///
+ /// Blowfish key used when is true.
+ /// Kept separate from pure format parameters — pass string.Empty when not encrypting.
+ ///
+ public string BlowfishKey { get; init; } = string.Empty;
+}
diff --git a/src/TelltaleToolKit/Serialization/Json/JsonMetaStreamReader.cs b/src/TelltaleToolKit/Serialization/Json/JsonMetaStreamReader.cs
new file mode 100644
index 0000000..3502c84
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/Json/JsonMetaStreamReader.cs
@@ -0,0 +1,371 @@
+using System.Text.Json;
+using TelltaleToolKit.T3Types;
+
+namespace TelltaleToolKit.Serialization.Json;
+
+///
+/// Reads a JSON file written by .
+/// The file must be an array with two elements: header object and payload object.
+///
+public sealed class JsonMetaStreamReader : MetaStream
+{
+ private readonly JsonDocument? _document;
+ private readonly JsonElement _current;
+ private readonly Stack _frames = new();
+ private bool _closed;
+
+ // Tracks whether the next value read should be preceded by a key.
+ // In JSON, keys are consumed by Key(), values by Serialize().
+ private bool _expectKey;
+
+ public JsonMetaStreamReader(Stream inputStream, MetaStreamParams @params)
+ {
+ Stream inputStream1 = inputStream ?? throw new ArgumentNullException(nameof(inputStream));
+ Params = @params;
+
+ // Load the entire JSON document (we need random access for header & payload)
+ _document = JsonDocument.Parse(inputStream1);
+ JsonElement root = _document.RootElement;
+
+ // Expect an array with exactly two elements
+ if (root.ValueKind != JsonValueKind.Array || root.GetArrayLength() != 2)
+ throw new InvalidDataException("JSON root must be an array of [header, payload].");
+
+ JsonElement header = root[0];
+ JsonElement payload = root[1];
+
+ // Parse header to populate MetaStreamParams.VersionInfo
+ ParseHeader(header);
+
+ // Start reading the payload
+ _current = payload;
+ _frames.Clear();
+ _expectKey = false;
+ }
+
+ private void ParseHeader(JsonElement header)
+ {
+ if (!header.TryGetProperty("_metaVersionInfo", out JsonElement versionInfoElement))
+ return; // no version info, ignore
+
+ if (!versionInfoElement.TryGetProperty("mVersionInfo", out JsonElement arrayElement))
+ return;
+
+ if (arrayElement.ValueKind != JsonValueKind.Array)
+ return;
+
+ var versionInfoList = new List();
+ foreach (JsonElement item in arrayElement.EnumerateArray())
+ {
+ if (!item.TryGetProperty("mTypeSymbolCrc", out JsonElement crcElem) ||
+ !item.TryGetProperty("mVersionCrc", out JsonElement versionCrcElem))
+ continue;
+
+ ulong typeCrc = crcElem.GetUInt64();
+ uint versionCrc = versionCrcElem.GetUInt32();
+
+ versionInfoList.Add(new MetaVersionInfo { TypeSymbolCrc = typeCrc, VersionCrc = versionCrc });
+ }
+
+ Params.VersionInfo.Clear();
+ Params.VersionInfo.AddRange(versionInfoList);
+ }
+
+ public override MetaStreamMode Mode => MetaStreamMode.Read;
+
+ protected override void SetSection(SectionType section)
+ {
+ }
+
+ public override void BeginAsyncSection()
+ {
+ }
+
+ public override void EndAsyncSection()
+ {
+ }
+
+ public override void BeginDebugSection()
+ {
+ }
+
+ public override void EndDebugSection()
+ {
+ }
+
+ public override void BeginObject(string name, bool isArray = false)
+ {
+ if (_frames.Count == 0)
+ {
+ // Root – we are already at the payload object, just push it
+ _frames.Push(new ReaderFrame(_current, isArray));
+ _expectKey = false;
+ return;
+ }
+
+ ReaderFrame currentFrame = _frames.Peek();
+
+ if (currentFrame.IsArray)
+ {
+ // Inside an array: read the next element (the array itself must be an object)
+ JsonElement child = currentFrame.GetNextArrayElement();
+ if (isArray)
+ {
+ if (child.ValueKind != JsonValueKind.Array)
+ throw new InvalidDataException("Expected JSON array.");
+ }
+ else
+ {
+ if (child.ValueKind != JsonValueKind.Object)
+ throw new InvalidDataException("Expected JSON object.");
+ }
+
+ _frames.Push(new ReaderFrame(child, isArray));
+ _expectKey = false;
+ }
+ else
+ {
+ // Inside an object: consume a property with the given name
+ if (!currentFrame.TryGetProperty(name, out JsonElement child))
+ throw new InvalidDataException($"Property '{name}' not found in JSON object.");
+
+ if (isArray)
+ {
+ if (child.ValueKind != JsonValueKind.Array)
+ throw new InvalidDataException($"Property '{name}' is not a JSON array.");
+ }
+ else
+ {
+ if (child.ValueKind != JsonValueKind.Object)
+ throw new InvalidDataException($"Property '{name}' is not a JSON object.");
+ }
+
+ _frames.Push(new ReaderFrame(child, isArray));
+ _expectKey = false;
+ }
+ }
+
+ public override void EndObject(string name)
+ {
+ if (_frames.Count == 0)
+ throw new InvalidOperationException("EndObject called without matching BeginObject.");
+ _frames.Pop();
+ _expectKey = false;
+ }
+
+ public override void Key(string name)
+ {
+ if (_frames.Count == 0)
+ throw new InvalidOperationException("Key called outside of an object.");
+ ReaderFrame frame = _frames.Peek();
+ if (frame.IsArray)
+ return; // keys are ignored inside arrays (writer never calls Key there)
+
+ // Verify that the next property name matches the expected name
+ if (!frame.TryGetNextProperty(out string actualName))
+ throw new InvalidDataException("No more properties in current object.");
+
+ if (actualName != name)
+ throw new InvalidDataException($"Expected property '{name}' but found '{actualName}'.");
+
+ _expectKey = true;
+ }
+
+ private void EnsureValue()
+ {
+ if (!_expectKey)
+ throw new InvalidOperationException("Key must be called before reading a value.");
+ _expectKey = false;
+ }
+
+ private JsonElement GetCurrentValue()
+ {
+ ReaderFrame frame = _frames.Peek();
+ if (frame.IsArray)
+ return frame.GetNextArrayElement();
+ else
+ return frame.GetCurrentValue(); // after Key, we already advanced to the value
+ }
+
+ public override void Serialize(ref bool value)
+ {
+ EnsureValue();
+ value = GetCurrentValue().GetBoolean();
+ }
+
+ public override void Serialize(ref float value)
+ {
+ EnsureValue();
+ value = (float)GetCurrentValue().GetDouble();
+ }
+
+ public override void Serialize(ref double value)
+ {
+ EnsureValue();
+ value = GetCurrentValue().GetDouble();
+ }
+
+ public override void Serialize(ref short value)
+ {
+ EnsureValue();
+ value = GetCurrentValue().GetInt16();
+ }
+
+ public override void Serialize(ref int value)
+ {
+ EnsureValue();
+ value = GetCurrentValue().GetInt32();
+ }
+
+ public override void Serialize(ref long value)
+ {
+ EnsureValue();
+ value = GetCurrentValue().GetInt64();
+ }
+
+ public override void Serialize(ref ushort value)
+ {
+ EnsureValue();
+ value = GetCurrentValue().GetUInt16();
+ }
+
+ public override void Serialize(ref uint value)
+ {
+ EnsureValue();
+ value = GetCurrentValue().GetUInt32();
+ }
+
+ public override void Serialize(ref ulong value)
+ {
+ EnsureValue();
+ value = GetCurrentValue().GetUInt64();
+ }
+
+ public override void Serialize(ref string value)
+ {
+ EnsureValue();
+ value = GetCurrentValue().GetString() ?? string.Empty;
+ }
+
+ public override void Serialize(ref char value)
+ {
+ EnsureValue();
+ string s = GetCurrentValue().GetString() ?? string.Empty;
+ value = s.Length > 0 ? s[0] : '\0';
+ }
+
+ public override void Serialize(ref byte value)
+ {
+ EnsureValue();
+ value = GetCurrentValue().GetByte();
+ }
+
+ public override void Serialize(ref sbyte value)
+ {
+ EnsureValue();
+ value = GetCurrentValue().GetSByte();
+ }
+
+ public override void Serialize(ref Symbol value)
+ {
+ EnsureValue();
+ string str = GetCurrentValue().GetString() ?? "";
+ // Try to parse as hex CRC, otherwise treat as debug string
+ if (str.StartsWith("0x", StringComparison.OrdinalIgnoreCase) &&
+ ulong.TryParse(str.AsSpan(2), System.Globalization.NumberStyles.HexNumber, null, out ulong crc))
+ {
+ value = Symbol.FromCrc64(crc);
+ }
+ else
+ {
+ value = Symbol.FromName(str);
+ }
+
+ Params.SerializedSymbols.Add(value);
+ }
+
+ public override void Serialize(byte[] values, int offset, int count)
+ {
+ EnsureValue();
+ byte[] base64 = GetCurrentValue().GetBytesFromBase64();
+ if (base64.Length < offset + count)
+ throw new InvalidDataException("Base64 data too short.");
+ Array.Copy(base64, offset, values, 0, count);
+ }
+
+
+ public override void BeginBlock()
+ {
+ }
+
+ public override void EndBlock()
+ {
+ }
+
+ public override void Close()
+ {
+ if (_closed) return;
+ _document?.Dispose();
+ _closed = true;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_closed)
+ Close();
+ base.Dispose(disposing);
+ }
+
+
+ private class ReaderFrame
+ {
+ private JsonElement Element { get; }
+ public bool IsArray { get; }
+ private readonly IEnumerator? _propertyEnumerator;
+ private readonly IEnumerator? _arrayEnumerator;
+ private JsonElement _currentValue; // after Key() we store the value
+
+ public ReaderFrame(JsonElement element, bool isArray)
+ {
+ Element = element;
+ IsArray = isArray;
+ if (IsArray)
+ _arrayEnumerator = element.EnumerateArray().GetEnumerator();
+ else
+ _propertyEnumerator = element.EnumerateObject().GetEnumerator();
+ }
+
+ public bool TryGetProperty(string name, out JsonElement child)
+ {
+ if (IsArray)
+ throw new InvalidOperationException("Cannot get property by name from an array.");
+ return Element.TryGetProperty(name, out child);
+ }
+
+ public bool TryGetNextProperty(out string name)
+ {
+ if (IsArray || _propertyEnumerator == null)
+ throw new InvalidOperationException("Not in an object.");
+ if (_propertyEnumerator.MoveNext())
+ {
+ name = _propertyEnumerator.Current.Name;
+ _currentValue = _propertyEnumerator.Current.Value;
+ return true;
+ }
+
+ name = null!;
+ return false;
+ }
+
+ public JsonElement GetCurrentValue() => _currentValue;
+
+ public JsonElement GetNextArrayElement()
+ {
+ if (!IsArray || _arrayEnumerator == null)
+ throw new InvalidOperationException("Not in an array.");
+ if (!_arrayEnumerator.MoveNext())
+ throw new InvalidDataException("Unexpected end of JSON array.");
+ return _arrayEnumerator.Current;
+ }
+ }
+}
diff --git a/src/TelltaleToolKit/Serialization/Json/JsonMetaStreamWriter.cs b/src/TelltaleToolKit/Serialization/Json/JsonMetaStreamWriter.cs
new file mode 100644
index 0000000..8738739
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/Json/JsonMetaStreamWriter.cs
@@ -0,0 +1,337 @@
+using System.Text.Json;
+using TelltaleToolKit.T3Types;
+
+namespace TelltaleToolKit.Serialization.Json;
+
+///
+/// Writes a MetaStream as human-readable JSON, following the same two-part layout
+/// as Telltale Tool Lib's MetaStream_JSON with minor improvements to make visualization better.:
+///
+/// - A header object (watermark, game ID, version info).
+/// - A payload object containing the serialized asset data.
+///
+///
+public sealed class JsonMetaStreamWriter : MetaStream
+{
+ private readonly Stream _outputStream;
+ private bool _closed;
+
+ // The payload writer — accumulates the asset data between Open and Close.
+ private readonly MemoryStream _payloadBuffer = new(0x4000);
+ private readonly Utf8JsonWriter _payload;
+ private bool _keyPending; // true after Key() until a value is writte
+
+ // Tracks whether the current JSON container is an array (true) or object (false).
+ // Mirrors C++ mbObjectIsArrayStack.
+ private readonly Stack _objectIsArrayStack = new();
+
+ public JsonMetaStreamWriter(Stream outputStream, MetaStreamParams @params)
+ {
+ _outputStream = outputStream ?? throw new ArgumentNullException(nameof(outputStream));
+ Params = @params;
+
+ _payload = new Utf8JsonWriter(_payloadBuffer, new JsonWriterOptions { Indented = true });
+ _payload.WriteStartObject(); // root payload object — closed in Close()
+ _keyPending = false;
+ // JSON has no real sections; we still need a non-null stream so base-class
+ // helpers (GetRemainingSectionBytes, etc.) don't throw.
+ Sections[(int)SectionType.Default].Stream = _payloadBuffer;
+ _currentSection = SectionType.Default;
+ }
+
+ public override MetaStreamMode Mode => MetaStreamMode.Write;
+
+ protected override void SetSection(SectionType section)
+ {
+ // Intentional no-op: JSON folds all sections into the payload object via
+ // BeginAsyncSection / BeginDebugSection named sub-objects.
+ _currentSection = section;
+ }
+
+ public override void BeginAsyncSection()
+ {
+ if (Params.StreamVersion < 4) return;
+ if (_currentSection is SectionType.Async) return;
+
+ _payload.WritePropertyName("Async Data");
+ _payload.WriteStartObject();
+ _currentSection = SectionType.Async;
+ }
+
+ public override void EndAsyncSection()
+ {
+ if (_currentSection is not SectionType.Async) return;
+
+ _payload.WriteEndObject();
+ _currentSection = SectionType.Default;
+ }
+
+ public override void BeginDebugSection()
+ {
+ if (Params.StreamVersion < 4) return;
+ if (_currentSection is SectionType.Debug) return;
+
+ _payload.WritePropertyName("Debug Data");
+ _payload.WriteStartObject();
+ _currentSection = SectionType.Debug;
+ }
+
+ public override void EndDebugSection()
+ {
+ if (_currentSection is not SectionType.Debug) return;
+
+ _payload.WriteEndObject();
+ _currentSection = SectionType.Default;
+ }
+
+ public override void BeginObject(string name, bool isArray = false)
+ {
+ EnsureKey();
+ _payload.WritePropertyName(name);
+ if (isArray)
+ _payload.WriteStartArray();
+ else
+ _payload.WriteStartObject();
+ _objectIsArrayStack.Push(isArray);
+ _keyPending = false; // the key has been consumed
+ }
+
+ public override void EndObject(string name)
+ {
+ if (_objectIsArrayStack.Count == 0)
+ throw new InvalidOperationException("EndObject called without a matching BeginObject.");
+ bool wasArray = _objectIsArrayStack.Pop();
+ if (wasArray)
+ _payload.WriteEndArray();
+ else
+ _payload.WriteEndObject();
+ }
+
+ ///
+ /// Emits a JSON property key. Only has effect when the current container is an object.
+ /// Mirrors MetaStream_JSON::Key.
+ ///
+ public override void Key(string name)
+ {
+ // Inside an array context a key makes no sense — silently ignore,
+ // matching the C++ NeedsKey() guard.
+ bool insideArray = _objectIsArrayStack.Count > 0 && _objectIsArrayStack.Peek();
+ if (!insideArray)
+ {
+ _payload.WritePropertyName(name);
+ _keyPending = true;
+ }
+ }
+
+ public override void BeginBlock()
+ {
+ }
+
+ public override void EndBlock()
+ {
+ }
+
+ private void EnsureKey()
+ {
+ // Only need a key if we're inside an object (not an array)
+ if (_objectIsArrayStack.Count > 0 && !_objectIsArrayStack.Peek())
+ {
+ if (!_keyPending)
+ {
+ // No key provided – write a default property name
+ _payload.WritePropertyName("__default");
+ }
+
+ // Key is consumed (either the real one or the default)
+ _keyPending = false;
+ }
+ }
+
+ public override void Serialize(ref bool value)
+ {
+ EnsureKey();
+ _payload.WriteBooleanValue(value);
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref float value)
+ {
+ EnsureKey();
+ _payload.WriteNumberValue(value);
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref double value)
+ {
+ EnsureKey();
+ _payload.WriteNumberValue(value);
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref short value)
+ {
+ EnsureKey();
+ _payload.WriteNumberValue(value);
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref int value)
+ {
+ EnsureKey();
+ _payload.WriteNumberValue(value);
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref long value)
+ {
+ EnsureKey();
+ _payload.WriteNumberValue(value);
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref ushort value)
+ {
+ EnsureKey();
+ _payload.WriteNumberValue(value);
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref uint value)
+ {
+ EnsureKey();
+ _payload.WriteNumberValue(value);
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref ulong value)
+ {
+ EnsureKey();
+ _payload.WriteNumberValue(value);
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref string value)
+ {
+ EnsureKey();
+ _payload.WriteStringValue(value);
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref char value)
+ {
+ EnsureKey();
+ _payload.WriteStringValue(value.ToString());
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref byte value)
+ {
+ EnsureKey();
+ _payload.WriteNumberValue(value);
+ _keyPending = false;
+ }
+
+ public override void Serialize(ref sbyte value)
+ {
+ EnsureKey();
+ _payload.WriteNumberValue(value);
+ _keyPending = false;
+ }
+
+ ///
+ /// Symbols are written as strings. If a debug string is available it is used;
+ /// otherwise the CRC is written as a hex string, matching C++ serialize_Symbol.
+ ///
+ public override void Serialize(ref Symbol value)
+ {
+ EnsureKey();
+ string display = value.DebugString ?? $"0x{value.Crc64:X16}";
+ _payload.WriteStringValue(display);
+ Params.SerializedSymbols.Add(value);
+ _keyPending = false;
+ }
+
+ ///
+ /// Binary blobs are Base64-encoded, matching C++ WriteData.
+ ///
+ public override void Serialize(byte[] values, int offset, int count)
+ {
+ EnsureKey();
+ _payload.WriteBase64StringValue(values.AsSpan(offset, count));
+ _keyPending = false;
+ }
+
+ public override void Close()
+ {
+ if (_closed) return;
+
+ // Finish the root payload object opened in the constructor.
+ _payload.WriteEndObject();
+ _payload.Flush();
+ _payloadBuffer.Seek(0, SeekOrigin.Begin);
+
+ using JsonDocument payloadDoc = JsonDocument.Parse(_payloadBuffer);
+ using MemoryStream finalBuffer = new(0x1000);
+
+ using (Utf8JsonWriter final = new(finalBuffer, new JsonWriterOptions { Indented = true }))
+ {
+ final.WriteStartArray();
+
+ final.WriteStartObject();
+ // Version info — type CRC, human-readable type name, version CRC.
+ final.WriteString("Watermark",
+ $"Converted to JSON by TelltaleToolKit v{typeof(MetaStream).Assembly.GetName().Version}");
+ final.WritePropertyName("_metaVersionInfo");
+ final.WriteStartObject();
+ {
+ // Skip entries whose type cannot be resolved, matching C++ behaviour.
+ List resolvable = Params.VersionInfo
+ .Where(v => v.IsTypeRegistered())
+ .ToList();
+
+ final.WriteNumber("mSize", resolvable.Count);
+ final.WritePropertyName("mVersionInfo");
+ final.WriteStartArray();
+ foreach (MetaVersionInfo v in resolvable)
+ {
+ final.WriteStartObject();
+ final.WriteNumber("mTypeSymbolCrc", v.TypeSymbolCrc); // ulong → JSON number
+ final.WriteString("Type Name", v.GetMetaClassType()!.FullTypeName);
+ final.WriteNumber("mVersionCrc", v.VersionCrc);
+ final.WriteEndObject();
+ }
+
+ final.WriteEndArray();
+ }
+ final.WriteEndObject();
+ final.WriteEndObject();
+
+ payloadDoc.RootElement.WriteTo(final);
+
+ final.WriteEndArray(); // end header root
+ }
+
+ if (_outputStream.CanSeek)
+ {
+ _outputStream.SetLength(0); // remove any existing content
+ _outputStream.Seek(0, SeekOrigin.Begin);
+ }
+
+ finalBuffer.Seek(0, SeekOrigin.Begin);
+ finalBuffer.CopyTo(_outputStream);
+
+ _closed = true;
+ Dispose(true);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _payload.Dispose();
+ _payloadBuffer.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+}
diff --git a/src/TelltaleToolKit/Serialization/LegacyEncryptedStream.cs b/src/TelltaleToolKit/Serialization/LegacyEncryptedStream.cs
new file mode 100644
index 0000000..e39ca88
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/LegacyEncryptedStream.cs
@@ -0,0 +1,220 @@
+using TelltaleToolKit.Utility.Blowfish;
+
+namespace TelltaleToolKit.Serialization.Binary;
+
+///
+/// Stream wrapper that decrypts TTArchive per‑file data on the fly.
+/// For simplicity, it reads the entire file into memory – these files are small
+/// (config files, scripts) so the overhead is acceptable.
+///
+internal sealed class LegacyEncryptedStream : Stream
+{
+ private readonly Stream _baseStream;
+ private readonly long _headerSize;
+ private readonly long _dataLength;
+ private readonly int _blockSize;
+ private readonly int _cryptInterval; // Blowfish every N blocks
+ private readonly int _cleanInterval; // skip XOR every M blocks (if not Blowfish)
+ private readonly Blowfish _blowfish;
+
+ private long _position;
+ private int _cachedBlockIndex = -1;
+ private byte[]? _cachedBlockData;
+ private int _cachedBlockValidLength; // for last partial block
+
+ ///
+ /// Initializes a new legacy encrypted stream.
+ ///
+ /// Underlying stream (contains the asset header + encrypted data).
+ /// MetaStreamVersion that determines the block parameters.
+ /// Number of bytes at the beginning of that form the asset header (e.g. the magic). This header is not decrypted and is skipped.
+ /// The Blowfish algorithm used for decrypting.
+ /// Thrown when is null.
+ /// Thrown when is negative or larger than the stream length.
+ public LegacyEncryptedStream(Stream baseStream, uint version, long headerSize, Blowfish blowfish)
+ : this(baseStream, GetBlockConfiguration(version), headerSize, blowfish)
+ {
+ }
+
+ private LegacyEncryptedStream(Stream baseStream, (int blockSize, int cryptInterval, int cleanInterval) config,
+ long headerSize, Blowfish blowfish)
+ {
+ _baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
+ if (!_baseStream.CanRead) throw new NotSupportedException("Base stream must be readable.");
+ if (!_baseStream.CanSeek) throw new NotSupportedException("Base stream must be seekable.");
+
+ if (headerSize < 0 || headerSize > _baseStream.Length)
+ throw new ArgumentException("Invalid header size", nameof(headerSize));
+
+ _headerSize = headerSize;
+ _dataLength = _baseStream.Length - headerSize;
+ _blockSize = config.blockSize;
+ _cryptInterval = config.cryptInterval;
+ _cleanInterval = config.cleanInterval;
+ _blowfish = blowfish;
+ _position = 0;
+ }
+
+ ///
+ public override bool CanRead => true;
+
+ ///
+ public override bool CanSeek => true;
+
+ ///
+ public override bool CanWrite => false;
+
+ ///
+ public override long Length => _dataLength;
+
+ ///
+ public override long Position
+ {
+ get => _position;
+ set
+ {
+ if (value < 0 || value > _dataLength)
+ throw new ArgumentOutOfRangeException(nameof(value));
+ _position = value;
+ }
+ }
+
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ => Read(buffer.AsSpan(offset, count));
+
+ ///
+ public override int Read(Span buffer)
+ {
+ if (buffer.IsEmpty) return 0;
+ long remaining = _dataLength - _position;
+ if (remaining <= 0) return 0;
+ int toRead = (int)Math.Min(buffer.Length, remaining);
+
+ int totalRead = 0;
+ while (totalRead < toRead)
+ {
+ // Determine current block index
+ int blockIdx = (int)(_position / _blockSize);
+ int blockOffset = (int)(_position % _blockSize);
+ int bytesInBlock = (int)Math.Min(_blockSize - blockOffset, _dataLength - _position);
+
+ // Get decrypted block data
+ EnsureBlockDecrypted(blockIdx, out byte[] blockData, out int _);
+
+ int copyLen = Math.Min(bytesInBlock, toRead - totalRead);
+ blockData.AsSpan(blockOffset, copyLen).CopyTo(buffer[totalRead..]);
+
+ totalRead += copyLen;
+ _position += copyLen;
+ }
+
+ return totalRead;
+ }
+
+ private void EnsureBlockDecrypted(int blockIdx, out byte[] blockData, out int validLength)
+ {
+ if (_cachedBlockIndex == blockIdx && _cachedBlockData != null)
+ {
+ blockData = _cachedBlockData;
+ validLength = _cachedBlockValidLength;
+ return;
+ }
+
+ // Read encrypted block from base stream
+ long blockStartInBase = _headerSize + (long)blockIdx * _blockSize;
+ int blockSizeThis = _blockSize;
+ bool isFullBlock = true;
+ if (blockStartInBase + _blockSize > _baseStream.Length)
+ {
+ blockSizeThis = (int)(_baseStream.Length - blockStartInBase);
+ isFullBlock = false;
+ }
+
+ if (blockSizeThis <= 0)
+ {
+ blockData = [];
+ validLength = 0;
+ _cachedBlockIndex = blockIdx;
+ _cachedBlockData = blockData;
+ _cachedBlockValidLength = 0;
+ return;
+ }
+
+ _baseStream.Seek(blockStartInBase, SeekOrigin.Begin);
+ byte[] encrypted = new byte[blockSizeThis];
+ int read = _baseStream.Read(encrypted, 0, blockSizeThis);
+ if (read != blockSizeThis)
+ throw new EndOfStreamException("Unexpected end of base stream while reading block.");
+
+ // Only apply decryption to FULL blocks (the trailing partial block is plaintext)
+ if (isFullBlock)
+ {
+ if (blockIdx % _cryptInterval == 0)
+ {
+ _blowfish.Decipher(encrypted.AsSpan(), encrypted.Length);
+ }
+ else if (blockIdx % _cleanInterval != 0) // not a clean block → XOR
+ {
+ for (int i = 0; i < encrypted.Length; i++)
+ encrypted[i] = (byte)(encrypted[i] ^ 0xFF);
+ }
+ // else: clean block – leave as is
+ }
+
+ _cachedBlockData = encrypted;
+ _cachedBlockValidLength = encrypted.Length;
+ _cachedBlockIndex = blockIdx;
+ blockData = encrypted;
+ validLength = encrypted.Length;
+ }
+
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ long newPos = origin switch
+ {
+ SeekOrigin.Begin => offset,
+ SeekOrigin.Current => _position + offset,
+ SeekOrigin.End => _dataLength + offset,
+ _ => throw new ArgumentOutOfRangeException(nameof(origin))
+ };
+ if (newPos < 0 || newPos > _dataLength)
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ _position = newPos;
+ return _position;
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public override void SetLength(long value) => throw new NotSupportedException();
+ public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // We do not own the base stream; do not dispose it.
+ _cachedBlockData = null;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ // Helper to get block configuration from MetaStreamVersion
+ private static (int blockSize, int cryptInterval, int cleanInterval) GetBlockConfiguration(
+ uint version)
+ {
+ return version switch
+ {
+ 1 => (64, 64, 100),
+ 2 => (128, 32, 80),
+ 3 => (256, 8, 24),
+ _ => throw new ArgumentException($"No legacy encryption scheme defined for version {version}",
+ nameof(version))
+ };
+ }
+}
diff --git a/src/TelltaleToolKit/Serialization/MetaStream.cs b/src/TelltaleToolKit/Serialization/MetaStream.cs
new file mode 100644
index 0000000..6cb7110
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/MetaStream.cs
@@ -0,0 +1,449 @@
+using System.Text;
+using TelltaleToolKit.Reflection;
+using TelltaleToolKit.Serialization.Binary;
+using TelltaleToolKit.Serialization.Json;
+using TelltaleToolKit.T3Types;
+
+namespace TelltaleToolKit.Serialization;
+
+///
+/// Stream wrapper used for serializing telltale assets.
+/// A single Telltale Tool file has 1-4 stream sections (Header, Default, Debug, Async) depending on its metastream
+/// version.
+///
+public abstract class MetaStream : IDisposable
+{
+ public enum SectionType
+ {
+ Header,
+ Default,
+ Debug,
+ Async
+ }
+
+ protected readonly SectionInfo[] Sections =
+ [
+ new(), // Header section
+ new(), // Default section
+ new(), // Debug section
+ new() // Async section
+ ];
+
+ /// Gets the currently active section (Header, Default, Debug, Async).
+ protected SectionType _currentSection = SectionType.Header;
+
+ public MetaStreamParams Params { get; set; } = new();
+
+ protected Stream BaseStream { get; set; } = null!;
+
+ ///
+ /// Gets the mode of this stream (read or write).
+ ///
+ public abstract MetaStreamMode Mode { get; }
+
+ /// Gets the currently active section.
+ protected SectionInfo CurrentSection => Sections[(int)_currentSection];
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Opens a MetaStream for reading from the specified input stream.
+ ///
+ /// The stream containing the Telltale asset.
+ /// Optional workspace required for legacy decryption.
+ /// A MetaStream configured for reading.
+ public static MetaStream OpenRead(Stream inputStream, Workspace? workspace = null)
+ => new BinaryMetaStreamReader(inputStream, workspace);
+
+ ///
+ /// Opens a MetaStream for writing to the specified output stream.
+ ///
+ /// The stream that will receive the serialized data.
+ /// Parameters that define the version, registered classes, etc.
+ /// A MetaStream configured for writing.
+ public static MetaStream OpenWrite(Stream outputStream, MetaStreamParams configuration)
+ => new BinaryMetaStreamWriter(outputStream, configuration);
+
+ ///
+ /// Opens a MetaStream for reading from the specified input stream.
+ ///
+ /// The stream containing the Telltale asset.
+ /// Parameters that define the version, registered classes, etc.
+ /// A MetaStream configured for writing.
+ public static MetaStream OpenJsonRead(Stream inputStream, MetaStreamParams configuration)
+ => new JsonMetaStreamReader(inputStream, configuration);
+
+ ///
+ /// Opens a MetaStream for writing to the specified output stream.
+ ///
+ /// The stream that will receive the serialized data.
+ /// Parameters that define the version, registered classes, etc.
+ /// A MetaStream configured for writing.
+ public static MetaStream OpenJsonWrite(Stream outputStream, MetaStreamParams configuration)
+ => new JsonMetaStreamWriter(outputStream, configuration);
+
+
+ public virtual void BeginAsyncSection()
+ {
+ if (_currentSection is SectionType.Async || Params.StreamVersion < 4)
+ {
+ return;
+ }
+
+ SetSection(SectionType.Async);
+ }
+
+ public virtual void EndAsyncSection()
+ {
+ if (_currentSection is not SectionType.Async || Params.StreamVersion < 4)
+ {
+ return;
+ }
+
+ SetSection(SectionType.Default);
+ }
+
+ public virtual void BeginDebugSection()
+ {
+ if (_currentSection is SectionType.Debug || Params.StreamVersion < 4)
+ {
+ return;
+ }
+
+ SetSection(SectionType.Debug);
+ }
+
+ public virtual void EndDebugSection()
+ {
+ if (_currentSection is not SectionType.Debug || Params.StreamVersion < 4)
+ {
+ return;
+ }
+
+ SetSection(SectionType.Default);
+ }
+
+ ///
+ /// Emits a property key for the next value. No-op in binary streams;
+ /// in JSON streams this labels the next value within an object container.
+ ///
+ /// The property/field name.
+ public virtual void Key(string name) { }
+
+ ///
+ /// Begins a named object or array container.
+ /// No-op in binary streams. In JSON streams emits the opening brace or bracket.
+ ///
+ public virtual void BeginObject(string name, bool isArray = false) { }
+
+ ///
+ /// Ends the most recently opened named container.
+ /// No-op in binary streams. In JSON streams emits the closing brace or bracket.
+ ///
+ public virtual void EndObject(string name) { }
+
+ ///
+ /// Called when the current section changes. Implementations should set up their reader/writer
+ /// to point to the new section's stream.
+ ///
+ /// The section that is now active.
+ protected abstract void SetSection(SectionType section);
+
+ public abstract void BeginBlock();
+
+ public abstract void EndBlock();
+
+ public bool IsSectionEmpty()
+ => Sections[(int)_currentSection].Stream?.Length == 0;
+
+ public bool IsClassSerialized(string typeName)
+ => Params.GetRegisteredClasses().Any(id => id.ClassType.Symbol.DebugString == typeName);
+
+ public MetaClass? GetMetaClass(Type type)
+ {
+ if (!Params.CanModifySerializedClassesList)
+ {
+ return Params.VersionInfo
+ .FirstOrDefault(versionInfo => versionInfo.GetMetaClassType()?.LinkingType == type)?.GetMetaClass();
+ }
+
+ return Params.Workspace?.GetMetaClassDescription(type);
+ }
+
+ public MetaClass? GetMetaClass(Symbol symbol)
+ {
+ if (!Params.CanModifySerializedClassesList)
+ {
+ return Params.VersionInfo.FirstOrDefault(versionInfo => versionInfo.TypeSymbolCrc == symbol.Crc64)
+ ?.GetMetaClass();
+ }
+
+ return Params.Workspace?.GetMetaClassDescription(symbol);
+ }
+
+ ///
+ /// Serializes the specified boolean value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref bool value);
+
+ ///
+ /// Serializes the specified float value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref float value);
+
+ ///
+ /// Serializes the specified double value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref double value);
+
+ ///
+ /// Serializes the specified short value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref short value);
+
+ ///
+ /// Serializes the specified integer value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref int value);
+
+ ///
+ /// Serializes the specified long value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref long value);
+
+ ///
+ /// Serializes the specified ushort value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref ushort value);
+
+ ///
+ /// Serializes the specified unsigned integer value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref uint value);
+
+ ///
+ /// Serializes the specified unsigned long value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref ulong value);
+
+ ///
+ /// Serializes the specified string value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref string value);
+
+ ///
+ /// Serializes the specified char value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref char value);
+
+ ///
+ /// Serializes the specified byte value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref byte value);
+
+ ///
+ /// Serializes the specified signed byte value.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref sbyte value);
+
+ ///
+ /// Serializes the Symbol class.
+ ///
+ /// The value to serialize
+ public abstract void Serialize(ref Symbol value);
+
+ ///
+ /// Serializes the specified byte array.
+ ///
+ /// The buffer to serialize.
+ /// The starting offset in the buffer to begin serializing.
+ /// The size, in bytes, to serialize.
+ public abstract void Serialize(byte[] values, int offset, int count);
+
+ public bool IsEndOfStream()
+ {
+ if (Mode is not MetaStreamMode.Read)
+ {
+ return false;
+ }
+
+ for (int i = 1; i <= 3; i++)
+ {
+ //for each section (default,async,debug)
+ SectionInfo currentSect = Sections[i];
+ if (currentSect.Stream == null)
+ {
+ continue;
+ }
+
+ if (currentSect.Stream.Position != currentSect.Stream.Length)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Skip a block.
+ /// This is internally used, even by Telltale themselves.
+ ///
+ public void SkipToEndOfCurrentBlock()
+ {
+ if (Mode is not MetaStreamMode.Read)
+ {
+ return;
+ }
+
+ SectionInfo currentSectionInfo = Sections[(int)_currentSection];
+ if (currentSectionInfo.Blocks.Count == 0)
+ {
+ return;
+ }
+
+ long expectedPosition = currentSectionInfo.Blocks.Pop();
+ currentSectionInfo.Stream?.Seek(expectedPosition, SeekOrigin.Begin);
+ }
+
+ /// Gets the underlying stream for the specified section.
+ /// The section type (Header, Default, Debug, Async).
+ /// The stream if the section exists and is initialized; otherwise null.
+ public SectionInfo GetSection(SectionType type)
+ => Sections[(int)type];
+
+ // If there are assets larger than 2GBs, I need to rethink my life.
+ public long GetRemainingSectionBytes()
+ {
+ Stream? stream = CurrentSection.Stream;
+ if (stream == null)
+ {
+ return 0;
+ }
+
+ return stream.Length - stream.Position;
+ }
+
+ public long GetRemainingSectionBytes(SectionType section)
+ {
+ Stream? stream = Sections[(int)section].Stream;
+ if (stream == null)
+ {
+ return 0;
+ }
+
+ return stream.Length - stream.Position;
+ }
+
+ public long GetPosition()
+ {
+ Stream? stream = CurrentSection.Stream;
+ if (stream == null)
+ {
+ return 0;
+ }
+
+ return stream.Position;
+ }
+
+ public void SetPosition(long position)
+ {
+ Stream? stream = CurrentSection.Stream;
+ if (stream == null)
+ {
+ throw new InvalidOperationException("Cannot set position on a null section stream.");
+ }
+
+ stream.Position = position;
+ }
+
+ public void AddVersionInfo(MetaClass? desc)
+ {
+ if (desc is null)
+ {
+ return;
+ }
+
+ foreach (MetaVersionInfo? versInfo in Params.VersionInfo)
+ {
+ if (versInfo.TypeSymbolCrc == desc.ClassType.Symbol.Crc64)
+ {
+ if (versInfo.VersionCrc != desc.Crc32)
+ {
+ throw new InvalidOperationException("Version CRC mismatch");
+ }
+
+ return;
+ }
+ }
+
+ MetaVersionInfo versionInfo = new() { TypeSymbolCrc = desc.ClassType.Symbol.Crc64, VersionCrc = desc.Crc32 };
+ Params.VersionInfo.Add(versionInfo);
+ }
+
+ ///
+ /// Closes the stream and finalizes writing (if in write mode).
+ ///
+ public abstract void Close();
+
+ public static bool IsValidMetaStream(Stream stream)
+ {
+ if (stream.Length < 4)
+ {
+ return false;
+ }
+
+ long originalPosition = stream.Position;
+
+ try
+ {
+ using BinaryReader reader = new(stream, Encoding.UTF8, true);
+ uint version = reader.ReadUInt32();
+ return Enum.IsDefined(typeof(MetaStreamMagic), version);
+ }
+ finally
+ {
+ stream.Position = originalPosition;
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ foreach (SectionInfo section in Sections)
+ {
+ section.Stream?.Dispose();
+ }
+ }
+ }
+
+ public class SectionInfo
+ {
+ // For blocks. In read, stores the sizes, in write stores the block offset initial.
+ public readonly Stack Blocks = [];
+
+ public long CompressedSize = 0;
+ public bool IsCompressed = false;
+
+ // Section data stream
+ public Stream? Stream = null;
+ }
+}
diff --git a/src/TelltaleToolKit/Serialization/Binary/MetaStreamExtensions.cs b/src/TelltaleToolKit/Serialization/MetaStreamExtensions.cs
similarity index 78%
rename from src/TelltaleToolKit/Serialization/Binary/MetaStreamExtensions.cs
rename to src/TelltaleToolKit/Serialization/MetaStreamExtensions.cs
index 3857ec6..0a6b588 100644
--- a/src/TelltaleToolKit/Serialization/Binary/MetaStreamExtensions.cs
+++ b/src/TelltaleToolKit/Serialization/MetaStreamExtensions.cs
@@ -2,7 +2,7 @@
using TelltaleToolKit.Reflection;
using TelltaleToolKit.T3Types;
-namespace TelltaleToolKit.Serialization.Binary;
+namespace TelltaleToolKit.Serialization;
public static class MetaStreamExtensions
{
@@ -12,7 +12,7 @@ public static class MetaStreamExtensions
/// The stream.
/// A boolean value read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool ReadBoolean(this MetaStreamReader stream)
+ public static bool ReadBoolean(this MetaStream stream)
{
bool value = false;
stream.Serialize(ref value);
@@ -25,7 +25,7 @@ public static bool ReadBoolean(this MetaStreamReader stream)
/// The stream.
/// A 4-byte floating point value read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float ReadSingle(this MetaStreamReader stream)
+ public static float ReadSingle(this MetaStream stream)
{
float value = 0.0f;
stream.Serialize(ref value);
@@ -38,7 +38,7 @@ public static float ReadSingle(this MetaStreamReader stream)
/// The stream.
/// An 8-byte floating point value read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static double ReadDouble(this MetaStreamReader stream)
+ public static double ReadDouble(this MetaStream stream)
{
double value = 0.0;
stream.Serialize(ref value);
@@ -51,7 +51,7 @@ public static double ReadDouble(this MetaStreamReader stream)
/// The stream.
/// A 2-byte signed integer read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static short ReadInt16(this MetaStreamReader stream)
+ public static short ReadInt16(this MetaStream stream)
{
short value = 0;
stream.Serialize(ref value);
@@ -64,7 +64,7 @@ public static short ReadInt16(this MetaStreamReader stream)
/// The stream.
/// A 4-byte signed integer read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static int ReadInt32(this MetaStreamReader stream)
+ public static int ReadInt32(this MetaStream stream)
{
int value = 0;
stream.Serialize(ref value);
@@ -77,7 +77,7 @@ public static int ReadInt32(this MetaStreamReader stream)
/// The stream.
/// An 8-byte signed integer read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static long ReadInt64(this MetaStreamReader stream)
+ public static long ReadInt64(this MetaStream stream)
{
long value = 0;
stream.Serialize(ref value);
@@ -90,7 +90,7 @@ public static long ReadInt64(this MetaStreamReader stream)
/// The stream.
/// A 2-byte unsigned integer read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ushort ReadUInt16(this MetaStreamReader stream)
+ public static ushort ReadUInt16(this MetaStream stream)
{
ushort value = 0;
stream.Serialize(ref value);
@@ -103,7 +103,7 @@ public static ushort ReadUInt16(this MetaStreamReader stream)
/// The stream.
/// A 4-byte unsigned integer read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static uint ReadUInt32(this MetaStreamReader stream)
+ public static uint ReadUInt32(this MetaStream stream)
{
uint value = 0;
stream.Serialize(ref value);
@@ -116,7 +116,7 @@ public static uint ReadUInt32(this MetaStreamReader stream)
/// The stream.
/// An 8-byte unsigned integer read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ulong ReadUInt64(this MetaStreamReader stream)
+ public static ulong ReadUInt64(this MetaStream stream)
{
ulong value = 0;
stream.Serialize(ref value);
@@ -129,7 +129,7 @@ public static ulong ReadUInt64(this MetaStreamReader stream)
/// The stream.
/// A string read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string ReadString(this MetaStreamReader stream)
+ public static string ReadString(this MetaStream stream)
{
string value = null!;
stream.Serialize(ref value);
@@ -142,7 +142,7 @@ public static string ReadString(this MetaStreamReader stream)
/// The stream.
/// A Unicode character read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static char ReadChar(this MetaStreamReader stream)
+ public static char ReadChar(this MetaStream stream)
{
char value = '\0';
stream.Serialize(ref value);
@@ -155,7 +155,7 @@ public static char ReadChar(this MetaStreamReader stream)
/// The stream.
/// An unsigned byte read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static byte ReadByte(this MetaStreamReader stream)
+ public static byte ReadByte(this MetaStream stream)
{
byte value = 0;
stream.Serialize(ref value);
@@ -168,7 +168,7 @@ public static byte ReadByte(this MetaStreamReader stream)
/// The stream.
/// A signed byte read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static sbyte ReadSByte(this MetaStreamReader stream)
+ public static sbyte ReadSByte(this MetaStream stream)
{
sbyte value = 0;
stream.Serialize(ref value);
@@ -182,7 +182,7 @@ public static sbyte ReadSByte(this MetaStreamReader stream)
///
/// A byte array containing the data read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static byte[] ReadBytes(this MetaStreamReader stream, int count)
+ public static byte[] ReadBytes(this MetaStream stream, int count)
{
byte[] value = new byte[count];
stream.Serialize(value, 0, count);
@@ -195,7 +195,7 @@ public static byte[] ReadBytes(this MetaStreamReader stream, int count)
/// The stream.
/// A string read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Symbol ReadSymbol(this MetaStreamReader stream)
+ public static Symbol ReadSymbol(this MetaStream stream)
{
var value = Symbol.Empty;
stream.Serialize(ref value);
@@ -209,10 +209,23 @@ public static Symbol ReadSymbol(this MetaStreamReader stream)
/// The stream.
/// A string read from the stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaClassType ReadMetaClassType(this MetaStreamReader stream)
+ public static MetaClassType? ReadMetaClassType(this MetaStream stream)
{
- MetaClassType value = MetaClassType.UninitializedClassType;
- stream.Serialize(ref value);
+ MetaClassType? value;
+ if (stream.Params.StreamVersion >= 3) // mStreamVersion >= 3 uses hashed types
+ {
+ ulong typeSymbolCrc = stream.ReadUInt64();
+ value = MetaClassTypeRegistry.GetByHash(typeSymbolCrc);
+
+ if (value == null)
+ Toolkit.Instance.Logger.LogWarning($"Unknown type symbol CRC64: {typeSymbolCrc}");
+ }
+ else
+ {
+ string typeName = stream.ReadString();
+ value = MetaClassTypeRegistry.GetByName(typeName);
+ }
+
return value;
}
@@ -223,7 +236,7 @@ public static MetaClassType ReadMetaClassType(this MetaStreamReader stream)
/// The boolean value to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, bool value)
+ public static MetaStream Write(this MetaStream stream, bool value)
{
stream.Serialize(ref value);
return stream;
@@ -236,7 +249,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, bool value)
/// The 4-byte floating point value to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, float value)
+ public static MetaStream Write(this MetaStream stream, float value)
{
stream.Serialize(ref value);
return stream;
@@ -249,7 +262,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, float value)
/// The 8-byte floating point value to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, double value)
+ public static MetaStream Write(this MetaStream stream, double value)
{
stream.Serialize(ref value);
return stream;
@@ -262,7 +275,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, double value)
/// The 2-byte signed integer to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, short value)
+ public static MetaStream Write(this MetaStream stream, short value)
{
stream.Serialize(ref value);
return stream;
@@ -275,7 +288,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, short value)
/// The 4-byte signed integer to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, int value)
+ public static MetaStream Write(this MetaStream stream, int value)
{
stream.Serialize(ref value);
return stream;
@@ -288,7 +301,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, int value)
/// The 8-byte signed integer to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, long value)
+ public static MetaStream Write(this MetaStream stream, long value)
{
stream.Serialize(ref value);
return stream;
@@ -301,7 +314,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, long value)
/// The 2-byte unsigned integer to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, ushort value)
+ public static MetaStream Write(this MetaStream stream, ushort value)
{
stream.Serialize(ref value);
return stream;
@@ -314,7 +327,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, ushort value)
/// The 4-byte unsigned integer to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, uint value)
+ public static MetaStream Write(this MetaStream stream, uint value)
{
stream.Serialize(ref value);
return stream;
@@ -327,7 +340,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, uint value)
/// The 8-byte unsigned integer to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, ulong value)
+ public static MetaStream Write(this MetaStream stream, ulong value)
{
stream.Serialize(ref value);
return stream;
@@ -340,7 +353,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, ulong value)
/// The string to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, string value)
+ public static MetaStream Write(this MetaStream stream, string value)
{
stream.Serialize(ref value);
return stream;
@@ -353,7 +366,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, string value)
/// The Unicode character to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, char value)
+ public static MetaStream Write(this MetaStream stream, char value)
{
stream.Serialize(ref value);
return stream;
@@ -366,7 +379,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, char value)
/// The unsigned byte to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, byte value)
+ public static MetaStream Write(this MetaStream stream, byte value)
{
stream.Serialize(ref value);
return stream;
@@ -379,7 +392,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, byte value)
/// The signed byte to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, sbyte value)
+ public static MetaStream Write(this MetaStream stream, sbyte value)
{
stream.Serialize(ref value);
return stream;
@@ -392,7 +405,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, sbyte value)
/// The signed byte to write.
/// The stream.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, Symbol value)
+ public static MetaStream Write(this MetaStream stream, Symbol value)
{
stream.Serialize(ref value);
return stream;
@@ -409,7 +422,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, Symbol value)
/// The stream.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, byte[] values, int offset, int count)
+ public static MetaStream Write(this MetaStream stream, byte[] values, int offset, int count)
{
stream.Serialize(values, offset, count);
return stream;
@@ -424,7 +437,7 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, byte[] values
/// The stream.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, byte[] values)
+ public static MetaStream Write(this MetaStream stream, byte[] values)
{
stream.Serialize(values, 0, values.Length);
return stream;
@@ -432,24 +445,18 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, byte[] values
// TODO: Fix documentation
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MetaStreamWriter Write(this MetaStreamWriter stream, MetaClassType value)
+ public static MetaStream Write(this MetaStream stream, MetaClassType value)
{
- stream.Serialize(ref value);
- return stream;
- }
+ if (stream.Params.StreamVersion >= 3) // mStreamVersion >= 3 uses hashed types
+ {
+ stream.Write(value.Symbol.Crc64);
+ }
+ else
+ {
+ stream.Write(value.FullTypeName);
+ }
- ///
- /// Performs the first step of serialization or deserialization using the Telltale ToolKit context.
- ///
- ///
- /// Typically, it will instantiate the object if [null], and if it's a collection, clear it.
- ///
- /// The object to process.
- /// The stream to serialize or deserialize to.
- ///
- public static void PreSerialize(this MetaStream stream, ref T obj, MetaClassType? type = null) where T : new()
- {
- Toolkit.Instance.GetSerializer().PreSerialize(ref obj, stream, type);
+ return stream;
}
///
@@ -457,8 +464,12 @@ public static MetaStreamWriter Write(this MetaStreamWriter stream, MetaClassType
///
/// The object to serialize or deserialize.
/// The stream to serialize or deserialize to.
- public static void Serialize(this MetaStream stream, ref T obj) where T : new()
+ ///
+ public static void Serialize(this MetaStream stream, ref T obj, MetaClassType? type = null) where T : new()
{
- Toolkit.Instance.GetSerializer().Serialize(ref obj, stream);
+ var serializer = Toolkit.Instance.GetSerializer();
+
+ serializer.PreSerialize(ref obj, stream, type);
+ serializer.Serialize(ref obj, stream);
}
}
diff --git a/src/TelltaleToolKit/Serialization/MetaStreamMagic.cs b/src/TelltaleToolKit/Serialization/MetaStreamMagic.cs
new file mode 100644
index 0000000..1c0fea9
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/MetaStreamMagic.cs
@@ -0,0 +1,162 @@
+namespace TelltaleToolKit.Serialization;
+
+///
+/// Four‑character codes (magic numbers) that identify the format and encryption state
+/// of a Telltale MetaStream file.
+///
+///
+/// The magic appears as the first 4 bytes of the file. Plaintext magics are human‑readable
+/// ASCII strings (e.g. "NIBM" in little-Endian). Encrypted magics are arbitrary 32‑bit values chosen by
+/// Telltale to avoid easy pattern matching.
+///
+public enum MetaStreamMagic : uint
+{
+ ///
+ /// MetaStream version 1.
+ /// Expansion: "Meta Binary".
+ /// ASCII value: "MBIN" in big-Endian, or "NIBM" in little-Endian.
+ /// Earliest Telltale asset version.
+ /// Version info is plain text..
+ ///
+ Mbin = 0x4D42494E,
+
+ ///
+ /// MetaStream version 1 (encrypted).
+ /// Expansion: "Meta Binary Encrypted Stream".
+ /// ASCII value: "MBES" in big-Endian, or "SEBM" in little-Endian.
+ /// Used in some early encrypted assets.
+ /// Version info is plain text after decryption.
+ ///
+ Mbes = 0x4D424553,
+
+ ///
+ /// MetaStream version 2 (encrypted, variant 1).
+ /// Telltale used three different arbitrary values for version 2 assets.
+ /// These are determined by a number generator.
+ /// Version info is plain text after decryption.
+ ///
+ EncryptedMbin1 = 0xFB4A1764,
+
+ ///
+ /// MetaStream version 2 (encrypted, variant 2).
+ /// Telltale used three different arbitrary values for version 2 assets.
+ /// These are determined by a number generator.
+ /// Version info is plain text after decryption.
+ ///
+ EncryptedMbin2 = 0xEB794091,
+
+ ///
+ /// MetaStream version 2 (encrypted, variant 3).
+ /// Telltale used three different arbitrary values for version 2 assets.
+ /// These are determined by a number generator.
+ /// Version info is plain text after decryption.
+ ///
+ EncryptedMbin3 = 0x64AFDEFB,
+
+ ///
+ /// MetaStream version 3.
+ /// Expansion: "Meta T(h)ree" or "Meta Type Reference."
+ /// ASCII value: "MTRE" in big-Endian, or "ERTM" in little-Endian.
+ /// Version info is hashed.
+ ///
+ Mtre = 0x4D545245,
+
+ ///
+ /// MetaStream version 3 (encrypted).
+ /// Version info is hashed after decryption.
+ ///
+ EncryptedMtre = 0x64AFDEAA,
+
+ ///
+ /// MetaStream version 3 (compressed).
+ /// Expansion: "Meta Compressed".
+ /// ASCII value: "MCOM" in big-Endian, or "MOCM" in little-Endian.
+ /// Version info is hashed after decompression.
+ ///
+ Mcom = 0x4D434F4D,
+
+ ///
+ /// MetaStream version 3 (compressed, encrypted).
+ /// Version info is hashed after decompression and decryption (presumably).
+ ///
+ EncryptedMcom = 0x64AFDEBB,
+
+ ///
+ /// MetaStream version 4 (optional compression).
+ /// Expansion: "Meta Stream Version 4".
+ /// ASCII value: "MSV4" in big-Endian, or "4VSM" in little-Endian.
+ /// Version info is hashed.
+ /// Unused in any games.
+ ///
+ Msv4 = 0x4D535356,
+
+ ///
+ /// MetaStream version 5 (optional compression).
+ /// Expansion: "Meta Stream Version 5".
+ /// ASCII value: "MSV5" in big-Endian, or "5VSM" in little-Endian.
+ /// Version info is hashed.
+ ///
+ Msv5 = 0x4D535635,
+
+ ///
+ /// MetaStream version 6 (optional compression).
+ /// Expansion: "Meta Stream Version 6".
+ /// ASCII value: "MSV6" in big-Endian, or "6VSM" in little-Endian.
+ /// Version info is hashed.
+ ///
+ Msv6 = 0x4D535636
+}
+
+///
+/// Extension methods for .
+///
+public static class MetaStreamVersionExtensions
+{
+ ///
+ /// Returns the MetaStream format version associated with the given magic number.
+ ///
+ /// The magic value read from the file header.
+ ///
+ /// An integer representing the version of the MetaStream format:
+ ///
+ /// -
+ /// 1Plaintext (Mbin, Mbes) or encrypted Mbes
+ ///
+ /// -
+ /// 2Encrypted version 2 (EncryptedMbin1/2/3)
+ ///
+ /// -
+ /// 3Version 3 (Mtre, Mcom, EncryptedMtre, EncryptedMcom)
+ ///
+ /// -
+ /// 4Version 4 (Msv4)
+ ///
+ /// -
+ /// 5Version 5 (Msv5)
+ ///
+ /// -
+ /// 6Version 6 (Msv6)
+ ///
+ /// -
+ /// 0Unknown or unsupported magic
+ ///
+ ///
+ ///
+ ///
+ /// The returned version corresponds to the
+ /// that should be used when reading or writing the stream.
+ ///
+ public static uint GetMetaStreamVersion(this MetaStreamMagic magic) =>
+ magic switch
+ {
+ MetaStreamMagic.Mbin or MetaStreamMagic.Mbes => 1,
+ MetaStreamMagic.EncryptedMbin1 or MetaStreamMagic.EncryptedMbin2
+ or MetaStreamMagic.EncryptedMbin3 => 2, // no basic version
+ MetaStreamMagic.Mtre or MetaStreamMagic.Mcom or MetaStreamMagic.EncryptedMcom
+ or MetaStreamMagic.EncryptedMtre => 3,
+ MetaStreamMagic.Msv4 => 4,
+ MetaStreamMagic.Msv5 => 5,
+ MetaStreamMagic.Msv6 => 6,
+ _ => 0
+ };
+}
diff --git a/src/TelltaleToolKit/Serialization/MetaStreamMagicExtensions.cs b/src/TelltaleToolKit/Serialization/MetaStreamMagicExtensions.cs
new file mode 100644
index 0000000..9826c25
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/MetaStreamMagicExtensions.cs
@@ -0,0 +1,40 @@
+namespace TelltaleToolKit.Serialization;
+
+public static class MetaStreamMagicExtensions
+{
+ ///
+ /// Converts a string to StreamVersion.
+ ///
+ public static MetaStreamMagic StreamVersionFromString(this string value)
+ {
+ return value switch
+ {
+ "MBIN" => MetaStreamMagic.Mbin,
+ "MBES" => MetaStreamMagic.Mbes,
+ "MTRE" => MetaStreamMagic.Mtre,
+ "MCOM" => MetaStreamMagic.Mcom,
+ "MSV4" => MetaStreamMagic.Msv4,
+ "MSV5" => MetaStreamMagic.Msv5,
+ "MSV6" => MetaStreamMagic.Msv6,
+ _ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
+ };
+ }
+
+ ///
+ /// Converts a StreamVersion to its string name.
+ ///
+ public static string ToJsonString(this MetaStreamMagic magic)
+ {
+ return magic switch
+ {
+ MetaStreamMagic.Mbin => "MBIN",
+ MetaStreamMagic.Mbes => "MBES",
+ MetaStreamMagic.Mcom => "MCOM",
+ MetaStreamMagic.Mtre => "MTRE",
+ MetaStreamMagic.Msv4 => "MSV4",
+ MetaStreamMagic.Msv5 => "MSV5",
+ MetaStreamMagic.Msv6 => "MSV6",
+ _ => string.Empty
+ };
+ }
+}
diff --git a/src/TelltaleToolKit/Serialization/MetaStreamMode.cs b/src/TelltaleToolKit/Serialization/MetaStreamMode.cs
new file mode 100644
index 0000000..b780672
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/MetaStreamMode.cs
@@ -0,0 +1,17 @@
+namespace TelltaleToolKit.Serialization;
+
+///
+/// Enumerates the different mode of serialization (either serialization or deserialization).
+///
+public enum MetaStreamMode
+{
+ ///
+ /// The serializer is in serialize mode.
+ ///
+ Read,
+
+ ///
+ /// The serializer is in deserialize mode.
+ ///
+ Write,
+}
diff --git a/src/TelltaleToolKit/Serialization/MetaStreamParams.cs b/src/TelltaleToolKit/Serialization/MetaStreamParams.cs
new file mode 100644
index 0000000..2e8be5d
--- /dev/null
+++ b/src/TelltaleToolKit/Serialization/MetaStreamParams.cs
@@ -0,0 +1,143 @@
+using TelltaleToolKit.Reflection;
+using TelltaleToolKit.T3Types;
+
+namespace TelltaleToolKit.Serialization;
+
+///
+/// Holds parameters and metadata for a MetaStream during serialization or deserialization.
+///
+public class MetaStreamParams
+{
+ ///
+ /// Gets or sets the MetaStream version associated with this stream.
+ ///
+ public uint StreamVersion { get; set; }
+
+ ///
+ /// True if all version CRCs match the compiled ones.
+ ///
+ public bool IsCompiledVersion { get; set; }
+
+ ///
+ /// Whether to apply legacy Blowfish block encryption (versions 2–3 only).
+ /// For version 4+, encryption is handled per-section via ContainerStream.
+ ///
+ public bool Encrypt { get; set; }
+
+ ///
+ /// Whether to compress sections (versions 4+ only, via ContainerStream/Deflate).
+ ///
+ public bool Compress { get; set; }
+
+ ///
+ /// Gets the list of objects for all classes that have been serialized in the current stream.
+ ///
+ public List VersionInfo { get; set; } = [];
+
+ ///
+ /// Gets the list of serialized objects found in the stream.
+ ///
+ public List SerializedSymbols { get; set; } = [];
+
+ ///
+ /// Gets or sets a value indicating whether the list of serialized classes can be modified after initialization.
+ ///
+ public bool CanModifySerializedClassesList { get; set; }
+
+ ///
+ /// Gets the current workspace. Required for serializing if custom types are added.
+ ///
+ public Workspace? Workspace { get; set; }
+
+ ///
+ /// Gets the list of objects for all classes that have been serialized in the current stream.
+ ///
+ public List GetRegisteredClasses()
+ {
+ List classes = [];
+
+ foreach (MetaVersionInfo versionInfo in VersionInfo)
+ {
+ if (versionInfo.IsRegistered())
+ classes.Add(versionInfo.GetMetaClass()!);
+ }
+
+ return classes;
+ }
+
+ ///
+ /// Gets the list of classes found in the stream that are not registered in the current game profile.
+ /// Each entry is a tuple containing the and its CRC32 checksum.
+ ///
+ public List<(MetaClassType Type, uint VersionCrc)> GetUnregisteredClasses()
+ {
+ List<(MetaClassType Type, uint VersionCrc)> unregistered = [];
+
+ foreach (MetaVersionInfo versionInfo in VersionInfo)
+ {
+ if (!versionInfo.IsRegistered() && versionInfo.IsTypeRegistered())
+ unregistered.Add((versionInfo.GetMetaClassType()!, versionInfo.VersionCrc));
+ }
+
+ return unregistered;
+ }
+
+ ///
+ /// Gets the list of type hashes and checksums for types encountered in the stream that could not be resolved.
+ /// Each entry is a tuple of the type hash (ulong) and CRC32 checksum.
+ ///
+ public List<(ulong TypeSymbolCrc, uint VersionCrc)> GetUnregisteredTypes()
+ {
+ List<(ulong TypeSymbolCrc, uint VersionCrc)> unregisteredTypes = [];
+
+ foreach (var versionInfo in VersionInfo)
+ {
+ if (!versionInfo.IsTypeRegistered())
+ unregisteredTypes.Add((versionInfo.TypeSymbolCrc, versionInfo.VersionCrc));
+ }
+
+ return unregisteredTypes;
+ }
+
+ ///
+ /// Derives the correct on-disk magic FourCC from Version + Encrypt.
+ /// Throws if the combination is invalid or not yet supported.
+ ///
+ internal MetaStreamMagic ResolveMagic()
+ {
+ // TODO: support MCOM and EncryptedMCOM, despite not being used in games
+ return (StreamVersion, Encrypt) switch
+ {
+ (1, false) => MetaStreamMagic.Mbin,
+ (1, true) => MetaStreamMagic.Mbes,
+ (2, false) => MetaStreamMagic.Mbin, // Telltale used multiple;
+ (2, true) => GetRandomEncryptedMbinMagic(), // Telltale used multiple;
+ (3, false) => MetaStreamMagic.Mtre,
+ (3, true) => MetaStreamMagic.EncryptedMtre,
+ (4, (false)) => MetaStreamMagic.Msv4,
+ (5, false) => MetaStreamMagic.Msv5,
+ (6, false) => MetaStreamMagic.Msv6,
+ _ => throw new NotSupportedException(
+ $"No valid magic for Version={StreamVersion}, Encrypt={Encrypt}.")
+ };
+ }
+
+ private static readonly Random s_random = new();
+ private static readonly object s_lock = new();
+
+ private static MetaStreamMagic GetRandomEncryptedMbinMagic()
+ {
+ int choice;
+ lock (s_lock)
+ {
+ choice = s_random.Next(0, 3); // 0, 1, or 2
+ }
+ return choice switch
+ {
+ 0 => MetaStreamMagic.EncryptedMbin1,
+ 1 => MetaStreamMagic.EncryptedMbin2,
+ 2 => MetaStreamMagic.EncryptedMbin3,
+ _ => MetaStreamMagic.EncryptedMbin1 // fallback
+ };
+ }
+}
diff --git a/src/TelltaleToolKit/Serialization/Serializers/ByteArraySerializer.cs b/src/TelltaleToolKit/Serialization/Serializers/ByteArraySerializer.cs
index 4a919ee..695ad89 100644
--- a/src/TelltaleToolKit/Serialization/Serializers/ByteArraySerializer.cs
+++ b/src/TelltaleToolKit/Serialization/Serializers/ByteArraySerializer.cs
@@ -5,15 +5,14 @@ namespace TelltaleToolKit.Serialization.Serializers;
[MetaClassSerializerGlobal(typeof(ByteArraySerializer))]
public class ByteArraySerializer : MetaClassSerializer
{
- ///
public override void Serialize(ref byte[] obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
- streamWriter.Write(obj);
- else if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Write)
+ stream.Write(obj);
+ else if (stream.Mode is MetaStreamMode.Read)
{
- int size = streamReader.ReadInt32();
- obj = streamReader.ReadBytes(size);
+ int size = stream.ReadInt32();
+ obj = stream.ReadBytes(size);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/Serialization/Serializers/CollectionSerializers.cs b/src/TelltaleToolKit/Serialization/Serializers/CollectionSerializers.cs
index 73697b5..6b44b93 100644
--- a/src/TelltaleToolKit/Serialization/Serializers/CollectionSerializers.cs
+++ b/src/TelltaleToolKit/Serialization/Serializers/CollectionSerializers.cs
@@ -17,7 +17,7 @@ public override void Initialize(MetaClassSerializerSelector selector)
///
public override void PreSerialize(ref List obj, MetaStream stream, MetaClassType? type = null)
{
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
if (obj is null)
obj = [];
@@ -29,18 +29,18 @@ public override void PreSerialize(ref List obj, MetaStream stream, MetaClassT
///
public override void Serialize(ref List obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- streamWriter.Write(obj.Count);
+ stream.Write(obj.Count);
foreach (T entry in obj)
{
_itemDataClassSerializer.Serialize(entry, stream);
}
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- int count = streamReader.ReadInt32();
+ int count = stream.ReadInt32();
obj.Capacity = count;
for (var i = 0; i < count; i++)
@@ -68,7 +68,7 @@ public override void Initialize(MetaClassSerializerSelector selector)
///
public override void PreSerialize(ref HashSet obj, MetaStream stream, MetaClassType? type = null)
{
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
if (obj is null)
obj = [];
@@ -77,22 +77,21 @@ public override void PreSerialize(ref HashSet obj, MetaStream stream, MetaCla
}
}
-
///
public override void Serialize(ref HashSet obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- streamWriter.Write(obj.Count);
+ stream.Write(obj.Count);
foreach (T entry in obj)
{
_itemDataClassSerializer.Serialize(entry, stream);
}
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- int count = streamReader.ReadInt32();
+ int count = stream.ReadInt32();
for (var i = 0; i < count; i++)
{
@@ -116,7 +115,7 @@ public sealed class DictionarySerializer : MetaClassSerializer
public override void PreSerialize(ref Dictionary obj, MetaStream stream, MetaClassType? type = null)
{
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
if (obj is null)
obj = new Dictionary();
@@ -136,9 +135,9 @@ public override void Serialize(ref Dictionary obj, MetaStream stre
{
PreSerialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- streamWriter.Write(obj.Count);
+ stream.Write(obj.Count);
foreach (KeyValuePair item in obj)
{
@@ -146,9 +145,9 @@ public override void Serialize(ref Dictionary obj, MetaStream stre
_valueClassSerializer.Serialize(item.Value, stream);
}
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- int count = streamReader.ReadInt32();
+ int count = stream.ReadInt32();
for (var i = 0; i < count; i++)
{
@@ -173,7 +172,6 @@ public override void Initialize(MetaClassSerializerSelector selector)
_itemDataClassSerializer = selector.GetSerializer();
}
- ///
public override void Serialize(ref T[] obj, MetaStream stream)
{
for (var i = 0; i < obj.Length; ++i)
@@ -182,4 +180,4 @@ public override void Serialize(ref T[] obj, MetaStream stream)
_itemDataClassSerializer.Serialize(ref obj[i], stream);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/Serialization/Serializers/DefaultClassSerializer.cs b/src/TelltaleToolKit/Serialization/Serializers/DefaultClassSerializer.cs
index 0bff35d..8257820 100644
--- a/src/TelltaleToolKit/Serialization/Serializers/DefaultClassSerializer.cs
+++ b/src/TelltaleToolKit/Serialization/Serializers/DefaultClassSerializer.cs
@@ -20,10 +20,6 @@ namespace TelltaleToolKit.Serialization.Serializers;
/// The type of the class being serialized.
public class DefaultClassSerializer : MetaClassSerializer where T : new()
{
- // TODO: For v0.2.0 or v0.3.0 extract this to a database.
- // This is required for implementing JSON serializers and allow UI libraries to link to those fields in the future.
- // Explanation: Telltale's JSON serializing is...interesting.
-
private static readonly Dictionary<(string, Type), CachedMember> MemberCache;
// Initialize the mapping dictionary.
@@ -49,50 +45,79 @@ public override void Serialize(ref T obj, MetaStream stream)
{
MetaClass? description = stream.GetMetaClass(typeof(T));
- // Console.WriteLine($"Reading {typeof(T).Name}.");
+ Toolkit.Instance.Logger.LogInfo($"Reading {typeof(T).Name}.");
if (description is null || !description.ClassType.IsSerialized())
{
if (description is null)
- Toolkit.Instance.Logger.LogWarning($"[DefaultClassSerializer] No description available for {typeof(T).Name}.");
+ Toolkit.Instance.Logger.LogWarning(
+ $"[DefaultClassSerializer] No description available for {typeof(T).Name}.");
return;
}
+ stream.BeginObject(typeof(T).Name);
// Add this class to the metaclass header.
- if (stream is MetaStreamWriter { Configuration: { CanModifySerializedClassesList: true } } streamWriter)
+ if (stream is { Mode: MetaStreamMode.Write, Params.CanModifySerializedClassesList: true })
{
- streamWriter.AddVersionInfo(description);
+ stream.AddVersionInfo(description);
}
// Loop through the metaclass's properties/members
foreach (MetaMember propDesc in description.Members.Where(propDesc => propDesc.IsSerialized()))
{
- CachedMember cached = ResolveMember(propDesc);
+ CachedMember? cached = ResolveMember(propDesc);
if (propDesc.Type.IsBlocked())
stream.BeginBlock();
- object? value = cached.Getter(ref obj);
- MetaClassSerializer serializer = Toolkit.Instance.GetSerializer(cached.Property.PropertyType);
+ stream.Key(propDesc.MemberName);
+
+ if (propDesc.Type.LinkingType.IsPrimitive || (propDesc.Type.LinkingType == typeof(string) ||
+ propDesc.Type.LinkingType == typeof(byte[]) ||
+ propDesc.Type.LinkingType == typeof(Symbol)))
+ {
+
+ }
+ else
+ {
+ stream.BeginObject(propDesc.MemberName);
+ }
+
+ object? value = cached?.Getter(ref obj);
+
+ MetaClassSerializer serializer =
+ Toolkit.Instance.GetSerializer(cached?.Property.PropertyType ?? propDesc.Type.LinkingType);
serializer.PreSerialize(ref value, stream, propDesc.Type);
serializer.Serialize(ref value, stream);
- // Console.WriteLine($"{propDesc.MemberName} - {value}");
+ Toolkit.Instance.Logger.LogInfo($"{propDesc.MemberName} - {value}");
- if (stream is MetaStreamReader)
- cached.Setter(ref obj, value);
+ if (stream.Mode is MetaStreamMode.Read)
+ cached?.Setter(ref obj, value);
+
+ if (propDesc.Type.LinkingType.IsPrimitive || propDesc.Type.LinkingType == typeof(string) ||
+ propDesc.Type.LinkingType == typeof(byte[]) ||
+ propDesc.Type.LinkingType == typeof(Symbol))
+ {
+
+ }
+ else
+ {
+ stream.EndObject(propDesc.MemberName);
+ }
if (propDesc.Type.IsBlocked())
stream.EndBlock();
}
- // Console.WriteLine($"Ending {typeof(T).Name}.");
+ stream.EndObject(typeof(T).Name);
+ Toolkit.Instance.Logger.LogInfo($"Ending {typeof(T).Name}.");
}
public override void PreSerialize(ref T obj, MetaStream stream, MetaClassType? type = null)
{
- if (stream is MetaStreamReader && obj == null)
+ if (stream.Mode is MetaStreamMode.Read && obj == null)
{
obj = new T();
}
@@ -104,7 +129,7 @@ public override void PreSerialize(ref T obj, MetaStream stream, MetaClassType? t
///
///
///
- private static CachedMember ResolveMember(MetaMember propDesc)
+ private static CachedMember? ResolveMember(MetaMember propDesc)
{
// 1. Try direct match
if (MemberCache.TryGetValue((propDesc.MemberName, propDesc.Type.LinkingType), out CachedMember? cached))
@@ -120,25 +145,6 @@ private static CachedMember ResolveMember(MetaMember propDesc)
return cachedAlt;
}
- // 3. String<->Symbol fallback
- if (propDesc.Type.LinkingType == typeof(string) || propDesc.Type.LinkingType == typeof(Symbol))
- {
- throw new MetaMemberNotFoundException(
- $"Property {propDesc.MemberName} with type {propDesc.Type.LinkingType} not found in class {typeof(T)}");
- if (MemberCache.TryGetValue((propDesc.MemberName, typeof(Symbol)), out CachedMember cachedAlt))
- return cachedAlt;
- }
-
- //4. String<->Symbol fallback
- if (propDesc.Type.LinkingType == typeof(Dictionary))
- {
- throw new MetaMemberNotFoundException(
- $"Property {propDesc.MemberName} with type {propDesc.Type.LinkingType} not found in class {typeof(T)}");
- if (MemberCache.TryGetValue((propDesc.MemberName, typeof(Dictionary)),
- out CachedMember? cachedAlt))
- return cachedAlt;
- }
-
// 5. int<->Flags
if (propDesc.Type.LinkingType == typeof(int))
{
@@ -146,29 +152,7 @@ private static CachedMember ResolveMember(MetaMember propDesc)
return cachedAlt;
}
- // 6. string<->Symbol (dictionary)
- if (propDesc.Type.LinkingType == typeof(Dictionary))
- {
- throw new MetaMemberNotFoundException(
- $"Property {propDesc.MemberName} with type {propDesc.Type.LinkingType} not found in class {typeof(T)}");
- if (MemberCache.TryGetValue(
- (propDesc.MemberName, typeof(Dictionary)),
- out CachedMember? cachedAlt))
- return cachedAlt;
- }
-
- // 7. string<->Symbol (dictionary)
- if (propDesc.Type.LinkingType == typeof(Dictionary))
- {
- throw new MetaMemberNotFoundException(
- $"Property {propDesc.MemberName} with type {propDesc.Type.LinkingType} not found in class {typeof(T)}");
- if (MemberCache.TryGetValue((propDesc.MemberName, typeof(Dictionary)),
- out CachedMember? cachedAlt))
- return cachedAlt;
- }
-
// Final: First available
-
if (propDesc.Type.LinkingType == typeof(int) || propDesc.Type.LinkingType == typeof(uint))
{
CachedMember? cachedAltFinal = MemberCache
@@ -178,8 +162,9 @@ private static CachedMember ResolveMember(MetaMember propDesc)
return cachedAltFinal;
}
- throw new MetaMemberNotFoundException(
+ Toolkit.Instance.Logger.LogError(
$"Property {propDesc.MemberName} with type {propDesc.Type.LinkingType} not found in class {typeof(T)}");
+ return null;
}
// Delegate signatures for ref struct support
diff --git a/src/TelltaleToolKit/Serialization/Serializers/MathTypeSerializer.cs b/src/TelltaleToolKit/Serialization/Serializers/MathTypeSerializer.cs
index e81031a..41b5b68 100644
--- a/src/TelltaleToolKit/Serialization/Serializers/MathTypeSerializer.cs
+++ b/src/TelltaleToolKit/Serialization/Serializers/MathTypeSerializer.cs
@@ -9,21 +9,21 @@ public class Vector2Serializer : MetaClassSerializer
{
public override void Serialize(ref Vector2 obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- if (streamWriter.Configuration.CanModifySerializedClassesList)
+ if (stream.Params.CanModifySerializedClassesList)
{
MetaClass? description = stream.GetMetaClass(typeof(Vector2));
- streamWriter.AddVersionInfo(description);
+ stream.AddVersionInfo(description);
}
- streamWriter.Write(obj.X);
- streamWriter.Write(obj.Y);
+ stream.Write(obj.X);
+ stream.Write(obj.Y);
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj.X = streamReader.ReadSingle();
- obj.Y = streamReader.ReadSingle();
+ obj.X = stream.ReadSingle();
+ obj.Y = stream.ReadSingle();
}
}
}
@@ -33,23 +33,23 @@ public class Vector3Serializer : MetaClassSerializer
{
public override void Serialize(ref Vector3 obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- if (streamWriter.Configuration.CanModifySerializedClassesList)
+ if (stream.Params.CanModifySerializedClassesList)
{
MetaClass? description = stream.GetMetaClass(typeof(Vector3));
- streamWriter.AddVersionInfo(description);
+ stream.AddVersionInfo(description);
}
- streamWriter.Write(obj.X);
- streamWriter.Write(obj.Y);
- streamWriter.Write(obj.Z);
+ stream.Write(obj.X);
+ stream.Write(obj.Y);
+ stream.Write(obj.Z);
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj.X = streamReader.ReadSingle();
- obj.Y = streamReader.ReadSingle();
- obj.Z = streamReader.ReadSingle();
+ obj.X = stream.ReadSingle();
+ obj.Y = stream.ReadSingle();
+ obj.Z = stream.ReadSingle();
}
}
}
@@ -59,25 +59,25 @@ public class Vector4Serializer : MetaClassSerializer
{
public override void Serialize(ref Vector4 obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- if (streamWriter.Configuration.CanModifySerializedClassesList)
+ if (stream.Params.CanModifySerializedClassesList)
{
MetaClass? description = stream.GetMetaClass(typeof(Vector4));
- streamWriter.AddVersionInfo(description);
+ stream.AddVersionInfo(description);
}
- streamWriter.Write(obj.X);
- streamWriter.Write(obj.Y);
- streamWriter.Write(obj.Z);
- streamWriter.Write(obj.W);
+ stream.Write(obj.X);
+ stream.Write(obj.Y);
+ stream.Write(obj.Z);
+ stream.Write(obj.W);
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj.X = streamReader.ReadSingle();
- obj.Y = streamReader.ReadSingle();
- obj.Z = streamReader.ReadSingle();
- obj.W = streamReader.ReadSingle();
+ obj.X = stream.ReadSingle();
+ obj.Y = stream.ReadSingle();
+ obj.Z = stream.ReadSingle();
+ obj.W = stream.ReadSingle();
}
}
}
@@ -87,25 +87,25 @@ public class QuaternionSerializer : MetaClassSerializer
{
public override void Serialize(ref Quaternion obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- if (streamWriter.Configuration.CanModifySerializedClassesList)
+ if (stream.Params.CanModifySerializedClassesList)
{
MetaClass? description = stream.GetMetaClass(typeof(Quaternion));
- streamWriter.AddVersionInfo(description);
+ stream.AddVersionInfo(description);
}
- streamWriter.Write(obj.X);
- streamWriter.Write(obj.Y);
- streamWriter.Write(obj.Z);
- streamWriter.Write(obj.W);
+ stream.Write(obj.X);
+ stream.Write(obj.Y);
+ stream.Write(obj.Z);
+ stream.Write(obj.W);
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj.X = streamReader.ReadSingle();
- obj.Y = streamReader.ReadSingle();
- obj.Z = streamReader.ReadSingle();
- obj.W = streamReader.ReadSingle();
+ obj.X = stream.ReadSingle();
+ obj.Y = stream.ReadSingle();
+ obj.Z = stream.ReadSingle();
+ obj.W = stream.ReadSingle();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/Serialization/Serializers/PrimitiveTypeSerializer.cs b/src/TelltaleToolKit/Serialization/Serializers/PrimitiveTypeSerializer.cs
index eea6289..c11b08c 100644
--- a/src/TelltaleToolKit/Serialization/Serializers/PrimitiveTypeSerializer.cs
+++ b/src/TelltaleToolKit/Serialization/Serializers/PrimitiveTypeSerializer.cs
@@ -133,13 +133,13 @@ public sealed class EnumSerializer : MetaClassSerializer where TEn
///
public override void Serialize(ref TEnum obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- streamWriter.Write(Convert.ToInt32(obj));
+ stream.Write(Convert.ToInt32(obj));
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj = (TEnum)Enum.ToObject(typeof(TEnum), streamReader.ReadInt32());
+ obj = (TEnum)Enum.ToObject(typeof(TEnum), stream.ReadInt32());
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/Serialization/Serializers/SymbolSerializer.cs b/src/TelltaleToolKit/Serialization/Serializers/SymbolSerializer.cs
index 953f903..1cc34e9 100644
--- a/src/TelltaleToolKit/Serialization/Serializers/SymbolSerializer.cs
+++ b/src/TelltaleToolKit/Serialization/Serializers/SymbolSerializer.cs
@@ -1,5 +1,4 @@
using TelltaleToolKit.Reflection;
-using TelltaleToolKit.Serialization.Binary;
using TelltaleToolKit.T3Types;
namespace TelltaleToolKit.Serialization.Serializers;
@@ -14,6 +13,18 @@ public override void PreSerialize(ref Symbol obj, MetaStream stream, MetaClassTy
public override void Serialize(ref Symbol obj, MetaStream stream)
{
+ MetaClass? description = stream.GetMetaClass(typeof(Symbol));
+
+ if (description is null)
+ {
+ throw new InvalidOperationException("Symbol description not found.");
+ }
+
+ if (stream.Mode == MetaStreamMode.Write)
+ {
+ stream.AddVersionInfo(description);
+ }
+
stream.Serialize(ref obj);
}
}
diff --git a/src/TelltaleToolKit/Serialization/Binary/Substream.cs b/src/TelltaleToolKit/Serialization/SubStream.cs
similarity index 92%
rename from src/TelltaleToolKit/Serialization/Binary/Substream.cs
rename to src/TelltaleToolKit/Serialization/SubStream.cs
index b96cbf3..eff15b2 100644
--- a/src/TelltaleToolKit/Serialization/Binary/Substream.cs
+++ b/src/TelltaleToolKit/Serialization/SubStream.cs
@@ -1,12 +1,21 @@
-namespace TelltaleToolKit.Serialization.Binary;
+namespace TelltaleToolKit.Serialization;
///
-/// Represents a substream of an underlying .
+/// Represents a substream of an underlying .
///
public class SubStream : Stream
{
+ private readonly long _length;
+
+ private readonly long _offset;
+
+ private readonly Stream _stream;
+
+ private long _position;
+
///
- /// Creates a new substream instance using the specified underlying stream at the specified offset with the specified length.
+ /// Creates a new substream instance using the specified underlying stream at the specified offset with the specified
+ /// length.
///
/// The underlying stream.
/// The offset.
@@ -26,7 +35,7 @@ public SubStream(Stream stream, long offset, long length)
throw new NotSupportedException("Stream does not support seeking.");
}
- this._stream = stream;
+ _stream = stream;
if (offset < 0)
{
@@ -42,6 +51,55 @@ public SubStream(Stream stream, long offset, long length)
_length = length;
}
+ ///
+ public override long Length => _length;
+
+ ///
+ public override bool CanRead => _stream.CanRead;
+
+ ///
+ public override bool CanSeek => _stream.CanSeek;
+
+ ///
+ public override bool CanWrite => _stream.CanWrite;
+
+ ///
+ public override bool CanTimeout => _stream.CanTimeout;
+
+ ///
+ public override int ReadTimeout
+ {
+ get => _stream.ReadTimeout;
+ set => throw new NotSupportedException("Cannot set the read timeout of a substream.");
+ }
+
+ ///
+ public override int WriteTimeout
+ {
+ get => _stream.WriteTimeout;
+ set => throw new NotSupportedException("Cannot set the write timeout of a substream.");
+ }
+
+ ///
+ public override long Position
+ {
+ get => _position;
+ set
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException("Position cannot be less than zero.");
+ }
+
+ if (value > _length)
+ {
+ throw new ArgumentOutOfRangeException("Position cannot be greater than the length.");
+ }
+
+ _stream.Position = _offset + (_position = value);
+ }
+ }
+
///
public override int Read(byte[] buffer, int offset, int count)
{
@@ -116,7 +174,7 @@ public override long Seek(long offset, SeekOrigin origin)
"Offset cannot be less than the length of the substream.");
}
- _stream.Seek(_position = (_length + offset), SeekOrigin.End);
+ _stream.Seek(_position = _length + offset, SeekOrigin.End);
break;
case SeekOrigin.Current:
@@ -139,70 +197,11 @@ public override long Seek(long offset, SeekOrigin origin)
}
///
- public override void SetLength(long value)
- {
+ public override void SetLength(long value) => throw
// While other Stream implementations allow the caller to set the length, this makes little sense in the context of a substream.
// Perhaps, in the future, we can allow callers to reduce the length but not expand the length.
-
- throw new NotSupportedException("Cannot set the length of a fixed substream.");
- }
+ new NotSupportedException("Cannot set the length of a fixed substream.");
///
public override void Flush() => _stream.Flush();
-
- ///
- public override long Length => _length;
-
- ///
- public override bool CanRead => _stream.CanRead;
-
- ///
- public override bool CanSeek => _stream.CanSeek;
-
- ///
- public override bool CanWrite => _stream.CanWrite;
-
- ///
- public override bool CanTimeout => _stream.CanTimeout;
-
- ///
- public override int ReadTimeout
- {
- get => _stream.ReadTimeout;
- set => throw new NotSupportedException("Cannot set the read timeout of a substream.");
- }
-
- ///
- public override int WriteTimeout
- {
- get => _stream.WriteTimeout;
- set => throw new NotSupportedException("Cannot set the write timeout of a substream.");
- }
-
- ///
- public override long Position
- {
- get => _position;
- set
- {
- if (value < 0)
- {
- throw new ArgumentOutOfRangeException("Position cannot be less than zero.");
- }
-
- if (value > _length)
- {
- throw new ArgumentOutOfRangeException("Position cannot be greater than the length.");
- }
-
- _stream.Position = _offset + (_position = value);
- }
- }
-
- private readonly Stream _stream;
-
- private readonly long _offset;
- private readonly long _length;
-
- private long _position;
}
diff --git a/src/TelltaleToolKit/T3Types/Animations/Animation.cs b/src/TelltaleToolKit/T3Types/Animations/Animation.cs
index 3f87a75..2e73014 100644
--- a/src/TelltaleToolKit/T3Types/Animations/Animation.cs
+++ b/src/TelltaleToolKit/T3Types/Animations/Animation.cs
@@ -36,7 +36,7 @@ public class InterfaceInfo
public int ValueCount { get; set; }
public uint Version { get; set; }
}
-
+
public List Descriptors { get; set; } = [];
public List Values { get; set; } = [];
@@ -61,31 +61,33 @@ public override void Serialize(ref Animation obj, MetaStream stream)
return;
}
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotSupportedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- int numTotalValues = streamReader.ReadInt32();
+ int numTotalValues = stream.ReadInt32();
obj.Values = new List(numTotalValues);
// Runtime buffer (not needed for now)
- int dataBufferSize = streamReader.ReadInt32();
+ int dataBufferSize = stream.ReadInt32();
obj.Buffer = new byte[dataBufferSize];
-
- int numValueTypes = streamReader.ReadInt32();
+
+ int numValueTypes = stream.ReadInt32();
obj.Descriptors = new List(numValueTypes);
for (var i = 0; i < numValueTypes; i++)
{
- MetaClassType typeSymbol = streamReader.ReadMetaClassType(); // The type of the class
+ MetaClassType? typeSymbol = stream.ReadMetaClassType(); // The type of the class
+ if (typeSymbol is null)
+ throw new InvalidOperationException("[Animation] Type symbol is not registered.");
- int numOfType = streamReader.ReadInt16(); // The number of times that type has been serialized
+ int numOfType = stream.ReadInt16(); // The number of times that type has been serialized
// TODO: Verify what these versions actually represent.
// TelltaleToolLib casts to unsigned int and uses it as a CRC32.
- var version = (uint)streamReader.ReadInt16();
+ var version = (uint)stream.ReadInt16();
var interfaceInfo = new InterfaceInfo
{
@@ -103,7 +105,7 @@ public override void Serialize(ref Animation obj, MetaStream stream)
for (var j = 0; j < desc.ValueCount; j++)
{
object? propertyValue = null;
-
+
serializer.PreSerialize(ref propertyValue, stream);
serializer.Serialize(ref propertyValue, stream);
@@ -111,23 +113,23 @@ public override void Serialize(ref Animation obj, MetaStream stream)
obj.Values.Add(value);
}
}
-
+
// These are very weird hacks by Telltale. What an...interesting system
// If for some reason the serializer for the baseclass of type `AnimationValueInterfaceBase` is not called,
// we read its values - the Symbol (CRC64 in this case) and the flags.
foreach (IAnimatedValueInterface value in obj.Values)
{
// TODO: Set other flags?
- value.AnimationValueInterfaceBase.Flags = streamReader.ReadInt32();
+ value.AnimationValueInterfaceBase.Flags = stream.ReadInt32();
}
- ushort isNotInterface = streamReader.ReadUInt16();
+ ushort isNotInterface = stream.ReadUInt16();
if (isNotInterface == 0)
{
foreach (IAnimatedValueInterface value in obj.Values)
{
- value.AnimationValueInterfaceBase.Name = streamReader.ReadSymbol();
+ value.AnimationValueInterfaceBase.Name = stream.ReadSymbol();
}
}
}
@@ -138,20 +140,23 @@ public override void Serialize(ref Animation obj, MetaStream stream)
public void SerializeOldAnimation(ref Animation obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotSupportedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- int numTotalValues = streamReader.ReadInt32();
- int numValueTypes = streamReader.ReadInt32();
+ int numTotalValues = stream.ReadInt32();
+ int numValueTypes = stream.ReadInt32();
for (var i = 0; i < numValueTypes; i++)
{
- MetaClassType typeSymbol = streamReader.ReadMetaClassType(); // The type of the class
- int numOfType = streamReader.ReadInt32(); // The number of times that type has been serialized
+ MetaClassType? typeSymbol = stream.ReadMetaClassType(); // The type of the class
+ if (typeSymbol is null)
+ throw new InvalidOperationException("[Animation] Type symbol is not registered.");
+
+ int numOfType = stream.ReadInt32(); // The number of times that type has been serialized
MetaClassSerializer serializer = Toolkit.Instance.GetSerializer(typeSymbol.LinkingType);
@@ -169,4 +174,4 @@ public void SerializeOldAnimation(ref Animation obj, MetaStream stream)
stream.EndBlock();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Animations/CompressedQuaternionKeys.cs b/src/TelltaleToolKit/T3Types/Animations/CompressedQuaternionKeys.cs
index d20c7c3..1ffe2b5 100644
--- a/src/TelltaleToolKit/T3Types/Animations/CompressedQuaternionKeys.cs
+++ b/src/TelltaleToolKit/T3Types/Animations/CompressedQuaternionKeys.cs
@@ -19,20 +19,20 @@ public override void Serialize(ref CompressedQuaternionKeys obj, MetaStream stre
{
// TODO: Test this type.
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj.Name = streamReader.ReadString();
- obj.Flags = streamReader.ReadInt32();
- obj.MinTime = streamReader.ReadSingle();
- obj.MaxTime = streamReader.ReadSingle();
+ obj.Name = stream.ReadString();
+ obj.Flags = stream.ReadInt32();
+ obj.MinTime = stream.ReadSingle();
+ obj.MaxTime = stream.ReadSingle();
- short numSamples = streamReader.ReadInt16();
+ short numSamples = stream.ReadInt16();
- obj.Buffer = streamReader.ReadBytes(numSamples * 6);
+ obj.Buffer = stream.ReadBytes(numSamples * 6);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Animations/CompressedSkeletonPoseKeys.cs b/src/TelltaleToolKit/T3Types/Animations/CompressedSkeletonPoseKeys.cs
index 089d402..c970d6b 100644
--- a/src/TelltaleToolKit/T3Types/Animations/CompressedSkeletonPoseKeys.cs
+++ b/src/TelltaleToolKit/T3Types/Animations/CompressedSkeletonPoseKeys.cs
@@ -60,16 +60,16 @@ public override void PreSerialize(ref CompressedSkeletonPoseKeys2 obj, MetaStrea
public override void Serialize(ref CompressedSkeletonPoseKeys2 obj, MetaStream stream)
{
// TODO: Test this type.
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
// TODO: Try experimenting with Marshall.
- obj.DataSize = streamReader.ReadInt32(); // this is the size of struct
+ obj.DataSize = stream.ReadInt32(); // this is the size of struct
- obj.Data = streamReader.ReadBytes(obj.DataSize);
+ obj.Data = stream.ReadBytes(obj.DataSize);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Animations/CompressedSkeletonPoseKeys2.cs b/src/TelltaleToolKit/T3Types/Animations/CompressedSkeletonPoseKeys2.cs
index 6406166..4879681 100644
--- a/src/TelltaleToolKit/T3Types/Animations/CompressedSkeletonPoseKeys2.cs
+++ b/src/TelltaleToolKit/T3Types/Animations/CompressedSkeletonPoseKeys2.cs
@@ -59,16 +59,16 @@ public override void PreSerialize(ref CompressedSkeletonPoseKeys2 obj, MetaStrea
public override void Serialize(ref CompressedSkeletonPoseKeys2 obj, MetaStream stream)
{
// TODO: Test this type.
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
// TODO: Try experimenting with Marshall.
- obj.DataSize = streamReader.ReadInt32(); // this is the size of struct
+ obj.DataSize = stream.ReadInt32(); // this is the size of struct
- obj.Data = streamReader.ReadBytes(obj.DataSize);
+ obj.Data = stream.ReadBytes(obj.DataSize);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Animations/CompressedVector3Keys.cs b/src/TelltaleToolKit/T3Types/Animations/CompressedVector3Keys.cs
index 72a0cfd..34c6ed3 100644
--- a/src/TelltaleToolKit/T3Types/Animations/CompressedVector3Keys.cs
+++ b/src/TelltaleToolKit/T3Types/Animations/CompressedVector3Keys.cs
@@ -21,24 +21,24 @@ public override void Serialize(ref CompressedVector3Keys obj, MetaStream stream)
{
// TODO: Test this type.
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj.Name = streamReader.ReadString();
- obj.Flags = streamReader.ReadInt32();
-
- obj.Min = new Vector3 { X = streamReader.ReadSingle(), Y = streamReader.ReadSingle(), Z = streamReader.ReadSingle() };
- obj.Max = new Vector3 { X = streamReader.ReadSingle(), Y = streamReader.ReadSingle(), Z = streamReader.ReadSingle() };
+ obj.Name = stream.ReadString();
+ obj.Flags = stream.ReadInt32();
- obj.MinTime = streamReader.ReadSingle();
- obj.MaxTime = streamReader.ReadSingle();
+ obj.Min = new Vector3 { X = stream.ReadSingle(), Y = stream.ReadSingle(), Z = stream.ReadSingle() };
+ obj.Max = new Vector3 { X = stream.ReadSingle(), Y = stream.ReadSingle(), Z = stream.ReadSingle() };
- short numSamples = streamReader.ReadInt16();
+ obj.MinTime = stream.ReadSingle();
+ obj.MaxTime = stream.ReadSingle();
- obj.Buffer = streamReader.ReadBytes(numSamples * 6);
+ short numSamples = stream.ReadInt16();
+
+ obj.Buffer = stream.ReadBytes(numSamples * 6);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Animations/KeyframedValue.cs b/src/TelltaleToolKit/T3Types/Animations/KeyframedValue.cs
index 1c6bed2..3811b69 100644
--- a/src/TelltaleToolKit/T3Types/Animations/KeyframedValue.cs
+++ b/src/TelltaleToolKit/T3Types/Animations/KeyframedValue.cs
@@ -62,10 +62,10 @@ public override void Serialize(ref KeyframedValue obj, MetaStream stream)
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
}
}
@@ -81,4 +81,4 @@ public enum TangentMode
Knot = 2,
Smooth = 3,
Flat = 4
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Animations/ProceduralLookAt.cs b/src/TelltaleToolKit/T3Types/Animations/ProceduralLookAt.cs
index 991e771..d57415c 100644
--- a/src/TelltaleToolKit/T3Types/Animations/ProceduralLookAt.cs
+++ b/src/TelltaleToolKit/T3Types/Animations/ProceduralLookAt.cs
@@ -89,9 +89,9 @@ public class Serializer : MetaClassSerializer
public override void Serialize(ref ProceduralLookAt obj, MetaStream stream)
{
// TODO: Check with meta stream version.
- if (stream is MetaStreamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- if (stream.Configuration.Version is MetaStreamVersion.Msv5 or MetaStreamVersion.Msv6)
+ if (stream.Params.StreamVersion >= 5)
{
return;
}
@@ -99,9 +99,9 @@ public override void Serialize(ref ProceduralLookAt obj, MetaStream stream)
Animation objAnimation = obj.Animation;
Toolkit.Instance.GetSerializer().Serialize(ref objAnimation, stream);
}
- else if (stream is MetaStreamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- if (stream.Configuration.Version is MetaStreamVersion.Msv5 or MetaStreamVersion.Msv6)
+ if (stream.Params.StreamVersion >= 5)
{
return;
}
@@ -113,4 +113,4 @@ public override void Serialize(ref ProceduralLookAt obj, MetaStream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Animations/ProceduralLookAtValue.cs b/src/TelltaleToolKit/T3Types/Animations/ProceduralLookAtValue.cs
index e1c4bfc..6ff368a 100644
--- a/src/TelltaleToolKit/T3Types/Animations/ProceduralLookAtValue.cs
+++ b/src/TelltaleToolKit/T3Types/Animations/ProceduralLookAtValue.cs
@@ -15,12 +15,12 @@ public class Serializer : MetaClassSerializer
{
public override void Serialize(ref ProceduralLookAtValue obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Audio/AudioData.cs b/src/TelltaleToolKit/T3Types/Audio/AudioData.cs
index c665961..28634fd 100644
--- a/src/TelltaleToolKit/T3Types/Audio/AudioData.cs
+++ b/src/TelltaleToolKit/T3Types/Audio/AudioData.cs
@@ -57,25 +57,25 @@ public override void Serialize(ref AudioData obj, MetaStream stream)
{
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- if (stream.Configuration.Version is MetaStreamVersion.Msv5 or MetaStreamVersion.Msv6)
+ if (stream.Params.StreamVersion >= 4)
{
- obj.NumChannels = streamReader.ReadInt16();
- short bitDepth = streamReader.ReadInt16();
- int sampleRate = streamReader.ReadInt32();
- int bytesPerSecond = streamReader.ReadInt32();
- obj.SampleSizeBytes = streamReader.ReadInt16();
- short always1 = streamReader.ReadInt16();
- int always328160 = streamReader.ReadInt32();
- int bytesPerSecCopy = streamReader.ReadInt32();
+ obj.NumChannels = stream.ReadInt16();
+ short bitDepth = stream.ReadInt16();
+ int sampleRate = stream.ReadInt32();
+ int bytesPerSecond = stream.ReadInt32();
+ obj.SampleSizeBytes = stream.ReadInt16();
+ short always1 = stream.ReadInt16();
+ int always328160 = stream.ReadInt32();
+ int bytesPerSecCopy = stream.ReadInt32();
- obj.OggBuffer = streamReader.ReadBytes(stream.GetRemainingSectionBytes());
+ obj.OggBuffer = stream.ReadBytes((int)stream.GetRemainingSectionBytes());
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/BinaryBuffer.cs b/src/TelltaleToolKit/T3Types/BinaryBuffer.cs
index 66bc392..30e47f7 100644
--- a/src/TelltaleToolKit/T3Types/BinaryBuffer.cs
+++ b/src/TelltaleToolKit/T3Types/BinaryBuffer.cs
@@ -13,21 +13,21 @@ public override void PreSerialize(ref BinaryBuffer obj, MetaStream stream, MetaC
{
obj ??= new BinaryBuffer();
}
-
+
public override void Serialize(ref BinaryBuffer obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- streamWriter.Write(obj.Data.Length);
- streamWriter.Write(obj.Data);
+ stream.Write(obj.Data.Length);
+ stream.Write(obj.Data);
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- int bufferSize = streamReader.ReadInt32();
- obj.Data = streamReader.ReadBytes(bufferSize);
+ int bufferSize = stream.ReadInt32();
+ obj.Data = stream.ReadBytes(bufferSize);
}
}
}
public byte[] Data { get; set; }
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Chores/Chore.cs b/src/TelltaleToolKit/T3Types/Chores/Chore.cs
index 7bbb17b..126ebf8 100644
--- a/src/TelltaleToolKit/T3Types/Chores/Chore.cs
+++ b/src/TelltaleToolKit/T3Types/Chores/Chore.cs
@@ -14,10 +14,10 @@ public class Chore
{
// [MetaMember("mpChore")]
// public Name { get; set; } = string.Empty;
-
+
[MetaMember("mName")]
public string Name { get; set; } = string.Empty;
-
+
[MetaMember("mFlags")]
public Flags Flags { get; set; }
@@ -65,30 +65,30 @@ public class Chore
[MetaMember("mSynchronizedToLocalization")]
public LocalizeInfo SynchronizedToLocalization { get; set; }
-
+
[MetaMember("mDependencies")]
public DependencyLoader Dependencies { get; set; }
[MetaMember("mToolProps")]
public ToolProps ToolProps { get; set; }
-
+
[MetaMember("mWalkPaths")]
public Dictionary WalkPaths { get; set; }
public class Serializer : MetaClassSerializer
{
private static readonly DefaultClassSerializer DefaultSerializer = new();
-
+
public override void Serialize(ref Chore obj, MetaStream stream)
{
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
MetaClass? choreMetaClassDescription = stream.GetMetaClass(typeof(Chore));
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
if (obj.NumResources > 0 && !choreMetaClassDescription.ContainsMember("mResourcesChore"))
{
@@ -97,8 +97,7 @@ public override void Serialize(ref Chore obj, MetaStream stream)
for (var i = 0; i < obj.NumResources; i++)
{
var choreResource = new ChoreResource();
- Toolkit.Instance.GetSerializer().PreSerialize(ref choreResource, stream);
- Toolkit.Instance.GetSerializer().Serialize(ref choreResource, stream);
+ stream.Serialize(ref choreResource);
obj.ResourcesChore.Add(choreResource);
}
}
@@ -121,4 +120,4 @@ public override void Serialize(ref Chore obj, MetaStream stream)
public class EnumExtentsMode
{
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Chores/ChoreResource.cs b/src/TelltaleToolKit/T3Types/Chores/ChoreResource.cs
index e17e09c..66b53ea 100644
--- a/src/TelltaleToolKit/T3Types/Chores/ChoreResource.cs
+++ b/src/TelltaleToolKit/T3Types/Chores/ChoreResource.cs
@@ -12,13 +12,13 @@ public class ChoreResource
{
[MetaMember("mVersion")]
public int Version { get; set; }
-
+
[MetaMember("mResName")]
public string ResName { get; set; } = string.Empty;
[MetaMember("mResName")]
public Symbol ResNameSymbol { get; set; }
-
+
[MetaMember("mResLength")]
public float ResLength { get; set; }
@@ -54,7 +54,7 @@ public class ChoreResource
[MetaMember("mbViewResourceGroups")]
public bool ViewResourceGroups { get; set; }
-
+
[MetaMember("mbViewEmptyGraphs")]
public bool ViewEmptyGraphs { get; set; }
@@ -63,7 +63,7 @@ public class ChoreResource
[MetaMember("mResourceGroupInclude")]
public Dictionary ResourceGroupInclude { get; set; } = new();
-
+
[MetaMember("mResourceGroupInclude")]
public Dictionary ResourceGroupIncludeSymbol { get; set; } = new();
@@ -75,7 +75,7 @@ public class ChoreResource
[MetaMember("mhObject")]
public HandleBase ObjectHandle { get; set; } = new(); // Bone only
-
+
public object? Embedded { get; set; }
[MetaClassSerializerGlobal(typeof(DefaultClassSerializer))]
@@ -110,15 +110,18 @@ public override void Serialize(ref ChoreResource obj, MetaStream stream)
MetaClass? description = stream.GetMetaClass(typeof(ChoreResource));
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
if (description != null && description.ContainsMember("mbEmbedded") && obj.HasEmbedded)
{
- MetaClassType embeddedClassType = streamReader.ReadMetaClassType();
- Symbol _ = streamReader.ReadSymbol();
+ MetaClassType? embeddedClassType = stream.ReadMetaClassType();
+ if (embeddedClassType is null)
+ throw new InvalidOperationException("[ChoreResource] Embedded type is not registered.");
+
+ Symbol _ = stream.ReadSymbol();
object? embedded = Activator.CreateInstance(embeddedClassType.LinkingType);
@@ -128,4 +131,4 @@ public override void Serialize(ref ChoreResource obj, MetaStream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Chores/WalkPath.cs b/src/TelltaleToolKit/T3Types/Chores/WalkPath.cs
index 7e14a07..81ba6d0 100644
--- a/src/TelltaleToolKit/T3Types/Chores/WalkPath.cs
+++ b/src/TelltaleToolKit/T3Types/Chores/WalkPath.cs
@@ -10,7 +10,7 @@ public class WalkPath
{
[MetaMember("mName")]
public string Name { get; set; }
-
+
public List Paths { get; set; }
public class Serializer : MetaClassSerializer
{
@@ -21,18 +21,21 @@ public override void Serialize(ref WalkPath obj, MetaStream stream)
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException($"There is no serializer for {SerializationType}");
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- int count = streamReader.ReadInt32();
- MetaClassType type = streamReader.ReadMetaClassType();
+ int count = stream.ReadInt32();
+ MetaClassType? type = stream.ReadMetaClassType();
+ if (type is null)
+ throw new InvalidOperationException("[WalkPath] Type is not registered.");
+
MetaClassSerializer classTypeSerializer =
Toolkit.Instance.GetSerializer(type.LinkingType);
-
+
for (var i = 0; i < count; i++)
{
object? value = null;
@@ -48,4 +51,4 @@ public override void Serialize(ref WalkPath obj, MetaStream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/ChildSets/DlgChildSet.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/ChildSets/DlgChildSet.cs
index 71895ab..4876e46 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/ChildSets/DlgChildSet.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/ChildSets/DlgChildSet.cs
@@ -25,18 +25,21 @@ public override void PreSerialize(ref DlgChildSet obj, MetaStream stream, MetaCl
public override void Serialize(ref DlgChildSet obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotSupportedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- int numChildren = streamReader.ReadInt32();
+ int numChildren = stream.ReadInt32();
for (var i = 0; i < numChildren; i++)
{
- MetaClassType type = streamReader.ReadMetaClassType();
+ MetaClassType? type = stream.ReadMetaClassType();
+ if (type == null)
+ throw new InvalidOperationException("[DlgChildSet] Type is not registered.");
+
MetaClassSerializer childSerializer = Toolkit.Instance.GetSerializer(type.LinkingType);
object? dlgChild = null;
@@ -49,4 +52,4 @@ public override void Serialize(ref DlgChildSet obj, MetaStream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Conditions/DlgConditionSet.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Conditions/DlgConditionSet.cs
index 6bcfee3..567c819 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Conditions/DlgConditionSet.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Conditions/DlgConditionSet.cs
@@ -8,29 +8,32 @@ namespace TelltaleToolKit.T3Types.Dialogs.Dlg;
public class DlgConditionSet
{
public List Conditions { get; set; } = [];
-
+
public class Serializer : MetaClassSerializer
{
- public override void PreSerialize(ref DlgConditionSet obj, MetaStream stream, MetaClassType? type = null )
+ public override void PreSerialize(ref DlgConditionSet obj, MetaStream stream, MetaClassType? type = null)
{
obj ??= new DlgConditionSet();
}
-
-
+
+
public override void Serialize(ref DlgConditionSet obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotSupportedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- int numChildren = streamReader.ReadInt32();
+ int numChildren = stream.ReadInt32();
for (var i = 0; i < numChildren; i++)
{
- MetaClassType type = streamReader.ReadMetaClassType();
+ MetaClassType? type = stream.ReadMetaClassType();
+ if (type == null)
+ throw new InvalidOperationException("[DlgConditionSet] Type is not registered.");
+
MetaClassSerializer conditionSerializer = Toolkit.Instance.GetSerializer(type.LinkingType);
object? dlgConditionSet = null;
@@ -43,4 +46,4 @@ public override void Serialize(ref DlgConditionSet obj, MetaStream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DepedencyLoader.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DepedencyLoader.cs
index d1eacf2..bf6d0b4 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DepedencyLoader.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DepedencyLoader.cs
@@ -14,14 +14,14 @@ public class Serializer : MetaClassSerializer
{
public override void Serialize(ref DependencyLoader obj, MetaStream stream)
{
- if (stream is MetaStreamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- bool hasResourceNames = streamReader.ReadBoolean();
+ bool hasResourceNames = stream.ReadBoolean();
if (!hasResourceNames)
return;
@@ -29,7 +29,10 @@ public override void Serialize(ref DependencyLoader obj, MetaStream stream)
// This is a little bit weird.
// In the real serialization function, the object is base-casted to DCArray. (base-casting is casting to a parent class).
// Which leads to the question - which type does inherit DCArray? It does not make any sense.
- MetaClassType type = streamReader.ReadMetaClassType();
+ MetaClassType? type = stream.ReadMetaClassType();
+
+ if (type == null)
+ throw new InvalidOperationException("[DependencyLoader] Type is not registered.");
if (type.Symbol.DebugString != null && !type.Symbol.DebugString.Equals("DCArray"))
{
@@ -37,7 +40,6 @@ public override void Serialize(ref DependencyLoader obj, MetaStream stream)
}
List objResourceNames = obj.ResourceNames;
- stream.PreSerialize(ref objResourceNames);
stream.Serialize(ref objResourceNames);
}
}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Dlg.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Dlg.cs
index be41bf8..0083d8b 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Dlg.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Dlg.cs
@@ -68,29 +68,32 @@ public override void Serialize(ref Dlg obj, MetaStream stream)
MetaClass metaClass = stream.GetMetaClass(typeof(Dlg))!;
- if (stream is MetaStreamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- int folderCount = streamReader.ReadInt32();
+ int folderCount = stream.ReadInt32();
obj.Folders.Capacity = folderCount;
for (var i = 0; i < folderCount; i++)
{
var folder = new DlgFolder();
- stream.PreSerialize(ref folder);
stream.Serialize(ref folder);
obj.Folders.Add(folder);
}
- int nodeCount = streamReader.ReadInt32();
+ int nodeCount = stream.ReadInt32();
obj.Nodes.Capacity = nodeCount;
for (var i = 0; i < nodeCount; i++)
{
- MetaClassType type = streamReader.ReadMetaClassType();
+ MetaClassType? type = stream.ReadMetaClassType();
+
+ if (type == null)
+ throw new InvalidOperationException("[Dlg] Type is not registered.");
+
MetaClassSerializer metaClassSerializer = Toolkit.Instance.GetSerializer(type.LinkingType);
object node = null!;
@@ -106,9 +109,9 @@ public override void Serialize(ref Dlg obj, MetaStream stream)
if (metaClass.ContainsMember("mbHasToolOnlyData"))
{
// Read runtime boolean
- _ = streamReader.ReadBoolean();
+ _ = stream.ReadBoolean();
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DlgObjectProps.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DlgObjectProps.cs
index 2d66c91..30d5ebf 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DlgObjectProps.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DlgObjectProps.cs
@@ -14,10 +14,10 @@ public enum PropsType {
ProductionProps = 2,
ToolProps = 4
}
-
+
[MetaMember("mFlags")]
public Flags Flags { get; set; }
-
+
public PropertySet UserProperties { get; set; }
public PropertySet ProductionProperties { get; set; }
public PropertySet ToolProperties { get; set; }
@@ -30,37 +30,34 @@ public override void Serialize(ref DlgObjectProps obj, MetaStream stream)
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
if ((obj.Flags.Data & (int)PropsType.UserProps) != 0)
{
var userProps = new PropertySet();
- stream.PreSerialize(ref userProps);
stream.Serialize(ref userProps);
obj.UserProperties = userProps;
}
-
+
if ((obj.Flags.Data & (int)PropsType.ProductionProps) != 0)
{
var prodProps = new PropertySet();
- stream.PreSerialize(ref prodProps);
stream.Serialize(ref prodProps);
obj.ProductionProperties = prodProps;
}
-
+
if ((obj.Flags.Data & (int)PropsType.ToolProps) != 0)
{
var toolProps = new PropertySet();
- stream.PreSerialize(ref toolProps);
stream.Serialize(ref toolProps);
obj.ToolProperties = toolProps;
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DlgVisibilityConditions.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DlgVisibilityConditions.cs
index 423fc9c..da70453 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DlgVisibilityConditions.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/DlgVisibilityConditions.cs
@@ -32,18 +32,17 @@ public override void Serialize(ref DlgVisibilityConditions obj, MetaStream strea
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
if ((obj.Flags.Data & 1) == 0)
return;
Rule objRule = obj.Rule;
- stream.PreSerialize(ref objRule);
stream.Serialize(ref objRule);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/JiraRecordManager.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/JiraRecordManager.cs
index b16e5a6..2bbecc3 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/JiraRecordManager.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/JiraRecordManager.cs
@@ -30,18 +30,18 @@ public override void Serialize(ref JiraRecordManager obj, MetaStream stream)
return;
}
- if (stream is MetaStreamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- int numRecords = streamReader.ReadInt32();
-
+ int numRecords = stream.ReadInt32();
+
for (int i = 0; i < numRecords; i++)
{
- string key = streamReader.ReadString();
+ string key = stream.ReadString();
var record = new JiraRecord();
obj.Records[key] = record;
@@ -51,4 +51,4 @@ public override void Serialize(ref JiraRecordManager obj, MetaStream stream)
stream.EndDebugSection();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Nodes/DlgNodeExchange.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Nodes/DlgNodeExchange.cs
index cce0b47..1d27654 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Nodes/DlgNodeExchange.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Nodes/DlgNodeExchange.cs
@@ -50,17 +50,16 @@ public override void Serialize(ref DlgNodeExchange obj, MetaStream stream)
DefaultClassSerializer.PreSerialize(ref obj, stream);
DefaultClassSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotSupportedException();
}
- if (stream is MetaStreamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
if ((obj.DlgNode.Flags.Data & 1) != 0)
{
var notes = new NoteCollection();
- stream.PreSerialize(ref notes);
stream.Serialize(ref notes);
obj.Notes = notes;
}
@@ -68,11 +67,10 @@ public override void Serialize(ref DlgNodeExchange obj, MetaStream stream)
if ((obj.DlgNode.Flags.Data & 2) != 0)
{
var lines = new DlgLineCollection();
- stream.PreSerialize(ref lines);
stream.Serialize(ref lines);
obj.Lines = lines;
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Note.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Note.cs
index 6313efc..2332a49 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Note.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/Note.cs
@@ -14,7 +14,7 @@ public class Note : IGenerator, IOwner
[MetaMember("Baseclass_UID::Owner")]
public Owner Owner { get; set; }
-
+
[MetaMember("mEntries")]
public List Entries { get; set; } = [];
@@ -51,23 +51,22 @@ public override void Serialize(ref Note obj, MetaStream stream)
DefaultClassSerializer.PreSerialize(ref obj, stream);
DefaultClassSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotSupportedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
// mEntries is not serialized.
- int numEntries = streamReader.ReadInt32();
+ int numEntries = stream.ReadInt32();
for (var i = 0; i < numEntries; i++)
{
Entry? entry = null;
- stream.PreSerialize(ref entry);
stream.Serialize(ref entry);
obj.Entries.Add(entry);
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/NoteCollection.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/NoteCollection.cs
index 321903a..5093548 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlg/NoteCollection.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlg/NoteCollection.cs
@@ -18,19 +18,18 @@ public class Serializer : MetaClassSerializer
{
public override void Serialize(ref NoteCollection obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotSupportedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
// mEntries is not serialized.
- int numNotes = streamReader.ReadInt32();
+ int numNotes = stream.ReadInt32();
for (var i = 0; i < numNotes; i++)
{
Note? note = null;
- stream.PreSerialize(ref note);
stream.Serialize(ref note);
// TODO: Set the correct IDs. Not a priority, I haven't seen this serialized anywhere.
obj.Notes.Add(i, note);
@@ -38,4 +37,4 @@ public override void Serialize(ref NoteCollection obj, MetaStream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/DlgSettings/DlgObjectPropsMap.cs b/src/TelltaleToolKit/T3Types/Dialogs/DlgSettings/DlgObjectPropsMap.cs
index 42655b1..e437654 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/DlgSettings/DlgObjectPropsMap.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/DlgSettings/DlgObjectPropsMap.cs
@@ -33,23 +33,22 @@ public class Serializer : MetaClassSerializer
{
public override void Serialize(ref DlgObjectPropsMap obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotSupportedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- int groupDefinitionSize = streamReader.ReadInt32();
+ int groupDefinitionSize = stream.ReadInt32();
for (var i = 0; i < groupDefinitionSize; i++)
{
var groupDefinition = new GroupDefinition();
- stream.PreSerialize(ref groupDefinition);
stream.Serialize(ref groupDefinition);
obj.GroupDefinitions.Add(groupDefinition);
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlog/DialogBase.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlog/DialogBase.cs
index 172fe66..e5da38a 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlog/DialogBase.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlog/DialogBase.cs
@@ -48,14 +48,14 @@ public override void Serialize(ref DialogBase obj, MetaStream stream)
{
new DefaultClassSerializer().Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
if (obj.HasStyleGuides)
{
Toolkit.Instance.GetSerializer>().Serialize(ref obj.StyleGuideRefs, stream);
}
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
if (obj.HasStyleGuides)
{
@@ -64,4 +64,4 @@ public override void Serialize(ref DialogBase obj, MetaStream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Dialogs/Dlog/DialogResource.cs b/src/TelltaleToolKit/T3Types/Dialogs/Dlog/DialogResource.cs
index a86aacf..bf8776a 100644
--- a/src/TelltaleToolKit/T3Types/Dialogs/Dlog/DialogResource.cs
+++ b/src/TelltaleToolKit/T3Types/Dialogs/Dlog/DialogResource.cs
@@ -54,26 +54,26 @@ public class DialogResource
public class Serializer : MetaClassSerializer
{
private static readonly DefaultClassSerializer DefaultSerializer = new();
-
+
public override void Serialize(ref DialogResource obj, MetaStream stream)
{
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException($"Serializer is not implement for {SerializationType}");
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- // Console.WriteLine("Current position: " + stream.GetCurrentPosition());
- int dialogsCount = streamReader.ReadInt32();
- int branchesCount = streamReader.ReadInt32();
- int itemsCount = streamReader.ReadInt32();
- int exchangesCount = streamReader.ReadInt32();
- int linesCount = streamReader.ReadInt32();
- int textCount = streamReader.ReadInt32();
+ // Console.WriteLine("Current position: " + stream.GetPosition());
+ int dialogsCount = stream.ReadInt32();
+ int branchesCount = stream.ReadInt32();
+ int itemsCount = stream.ReadInt32();
+ int exchangesCount = stream.ReadInt32();
+ int linesCount = stream.ReadInt32();
+ int textCount = stream.ReadInt32();
SerializeDialogBaseArray(obj.Dialogs, dialogsCount, stream);
SerializeDialogBaseArray(obj.Branches, branchesCount, stream);
@@ -87,10 +87,10 @@ public override void Serialize(ref DialogResource obj, MetaStream stream)
private static void SerializeDialogBaseArray(List list, int count, MetaStream stream)
where T : IDialogBase, new()
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
// Console.WriteLine(count);
if (count <= 0)
@@ -101,7 +101,7 @@ private static void SerializeDialogBaseArray(List list, int count, MetaStr
var ids = new int[count];
for (int i = 0; i < count; i++)
{
- ids[i] = streamReader.ReadInt32();
+ ids[i] = stream.ReadInt32();
}
var dialog = default(T);
@@ -115,4 +115,4 @@ private static void SerializeDialogBaseArray(List list, int count, MetaStr
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Handle.cs b/src/TelltaleToolKit/T3Types/Handle.cs
index bd725bf..6362fbb 100644
--- a/src/TelltaleToolKit/T3Types/Handle.cs
+++ b/src/TelltaleToolKit/T3Types/Handle.cs
@@ -35,8 +35,7 @@ public override void PreSerialize(ref HandleBase obj, MetaStream stream, MetaCla
public override void Serialize(ref HandleBase obj, MetaStream stream)
{
- if (stream.Configuration.Version is MetaStreamVersion.Msv4 or MetaStreamVersion.Msv5
- or MetaStreamVersion.Msv6)
+ if (stream.Params.StreamVersion >= 4)
{
stream.Serialize(ref obj.ObjectInfo.ObjectName);
}
diff --git a/src/TelltaleToolKit/T3Types/Languages/Landb/LanguageDb.cs b/src/TelltaleToolKit/T3Types/Languages/Landb/LanguageDb.cs
index 76bd7dc..6b34700 100644
--- a/src/TelltaleToolKit/T3Types/Languages/Landb/LanguageDb.cs
+++ b/src/TelltaleToolKit/T3Types/Languages/Landb/LanguageDb.cs
@@ -41,11 +41,11 @@ public override void Serialize(ref LanguageDb obj, MetaStream stream)
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
// There should be things in the debug section that I would like to parse.
// It's not required, but it may contain useful information.
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Mathematics/Color.cs b/src/TelltaleToolKit/T3Types/Mathematics/Color.cs
index 81c7190..8f09ab5 100644
--- a/src/TelltaleToolKit/T3Types/Mathematics/Color.cs
+++ b/src/TelltaleToolKit/T3Types/Mathematics/Color.cs
@@ -26,25 +26,25 @@ public class Serializer : MetaClassSerializer
{
public override void Serialize(ref Color obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- if (streamWriter.Configuration.CanModifySerializedClassesList)
+ if (stream.Params.CanModifySerializedClassesList)
{
MetaClass? description = stream.GetMetaClass(typeof(Color));
- streamWriter.AddVersionInfo(description);
+ stream.AddVersionInfo(description);
}
- streamWriter.Write(obj.R);
- streamWriter.Write(obj.G);
- streamWriter.Write(obj.B);
- streamWriter.Write(obj.A);
+ stream.Write(obj.R);
+ stream.Write(obj.G);
+ stream.Write(obj.B);
+ stream.Write(obj.A);
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj.R = streamReader.ReadSingle();
- obj.G = streamReader.ReadSingle();
- obj.B = streamReader.ReadSingle();
- obj.A = streamReader.ReadSingle();
+ obj.R = stream.ReadSingle();
+ obj.G = stream.ReadSingle();
+ obj.B = stream.ReadSingle();
+ obj.A = stream.ReadSingle();
}
}
}
@@ -53,4 +53,4 @@ public override string ToString()
{
return $"Color: ({R}, {G}, {B}, {A})";
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Mathematics/Polar.cs b/src/TelltaleToolKit/T3Types/Mathematics/Polar.cs
index 9fca8dc..6101e05 100644
--- a/src/TelltaleToolKit/T3Types/Mathematics/Polar.cs
+++ b/src/TelltaleToolKit/T3Types/Mathematics/Polar.cs
@@ -21,17 +21,17 @@ public static void Serialize(ref object obj, MetaStream stream, MetaClass desc)
{
Polar instance = (Polar)obj;
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- streamWriter.Write(instance.Radius);
- streamWriter.Write(instance.Theta);
- streamWriter.Write(instance.Phi);
+ stream.Write(instance.Radius);
+ stream.Write(instance.Theta);
+ stream.Write(instance.Phi);
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- instance.Radius = streamReader.ReadSingle();
- instance.Theta = streamReader.ReadSingle();
- instance.Phi = streamReader.ReadSingle();
+ instance.Radius = stream.ReadSingle();
+ instance.Theta = stream.ReadSingle();
+ instance.Phi = stream.ReadSingle();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Mathematics/TRange.cs b/src/TelltaleToolKit/T3Types/Mathematics/TRange.cs
index cc68b80..1b0d9d3 100644
--- a/src/TelltaleToolKit/T3Types/Mathematics/TRange.cs
+++ b/src/TelltaleToolKit/T3Types/Mathematics/TRange.cs
@@ -22,7 +22,7 @@ public class RangeClassSerializer : MetaClassSerializer>
///
public override void PreSerialize(ref Range obj, MetaStream stream, MetaClassType? type)
{
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
if (obj is null)
obj = new Range();
@@ -34,4 +34,4 @@ public override void Serialize(ref Range obj, MetaStream stream)
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Mathematics/TRect.cs b/src/TelltaleToolKit/T3Types/Mathematics/TRect.cs
index 35eb0fe..1d5ec3e 100644
--- a/src/TelltaleToolKit/T3Types/Mathematics/TRect.cs
+++ b/src/TelltaleToolKit/T3Types/Mathematics/TRect.cs
@@ -23,7 +23,7 @@ public class RectClassSerializer : MetaClassSerializer>
///
public override void PreSerialize(ref Rect obj, MetaStream stream, MetaClassType? type)
{
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
if (obj is null)
obj = new Rect();
@@ -35,4 +35,4 @@ public override void Serialize(ref Rect obj, MetaStream stream)
{
DefaultSerializer.Serialize(ref obj, stream);
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Meshes/D3DMesh.cs b/src/TelltaleToolKit/T3Types/Meshes/D3DMesh.cs
index ca076ca..f7d8e70 100644
--- a/src/TelltaleToolKit/T3Types/Meshes/D3DMesh.cs
+++ b/src/TelltaleToolKit/T3Types/Meshes/D3DMesh.cs
@@ -302,17 +302,17 @@ public override void Serialize(ref TriangleSet obj, MetaStream stream)
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
if (stream.GetMetaClass(typeof(TriangleSet)).ContainsMember("mbHasPixelShader_RemoveMe"))
{
- string shaderName = streamReader.ReadString();
+ string shaderName = stream.ReadString();
if (obj.HasPixelShaderRemoveMe)
{
- string pixelShader = streamReader.ReadString();
+ string pixelShader = stream.ReadString();
}
}
}
@@ -355,7 +355,7 @@ public override void Serialize(ref D3DMesh obj, MetaStream stream)
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
if (obj.Version < 19)
{
@@ -369,13 +369,13 @@ public override void Serialize(ref D3DMesh obj, MetaStream stream)
SerializeInternalResources(ref obj, stream);
}
- streamWriter.Write(0);
+ stream.Write(0);
if (obj.Version >= 52)
{
bool hasOcclusionData = obj.OcclusionMeshData != null;
- streamWriter.Write(hasOcclusionData);
+ stream.Write(hasOcclusionData);
if (hasOcclusionData)
{
@@ -394,7 +394,7 @@ public override void Serialize(ref D3DMesh obj, MetaStream stream)
Toolkit.Instance.GetSerializer().Serialize(ref objMeshData, stream);
stream.EndBlock();
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
// According to Telltale, the new meshes start from version 20.
if (obj.Version == 0)
@@ -414,19 +414,19 @@ public override void Serialize(ref D3DMesh obj, MetaStream stream)
SerializeInternalResources(ref obj, stream);
}
- int toolData = streamReader.ReadInt32();
+ int toolData = stream.ReadInt32();
// Telltale skip this data.
if (toolData > 0)
{
stream.BeginBlock();
- streamReader.SkipToEndOfCurrentBlock();
+ stream.SkipToEndOfCurrentBlock();
stream.EndBlock();
}
// Probably new meshes?
if (obj.Version >= 52)
{
- bool hasOcclusionData = streamReader.ReadBoolean();
+ bool hasOcclusionData = stream.ReadBoolean();
if (hasOcclusionData)
{
// TODO: Assign this.
@@ -448,12 +448,12 @@ public override void Serialize(ref D3DMesh obj, MetaStream stream)
private static void SerializeOldMedD3DMesh(ref D3DMesh obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotSupportedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
obj.T3VertexBuffers = new T3VertexBuffer[15];
@@ -562,7 +562,7 @@ private static void SerializeOldMedD3DMesh(ref D3DMesh obj, MetaStream stream)
obj.T3VertexBuffers[13] = buffer;
}
// Read Index Buffer
- // bool hasIndexBuffer = streamReader.ReadBoolean();
+ // bool hasIndexBuffer = stream.ReadBoolean();
//
// if (hasIndexBuffer)
// {
@@ -580,7 +580,7 @@ private static void SerializeOldMedD3DMesh(ref D3DMesh obj, MetaStream stream)
//
// for (int i = 0; i < obj.T3VertexBuffers.Length; i++)
// {
- // bool hasVertexBuffer = streamReader.ReadBoolean();
+ // bool hasVertexBuffer = stream.ReadBoolean();
//
// if (!hasVertexBuffer)
// continue;
@@ -595,15 +595,15 @@ private static void SerializeOldMedD3DMesh(ref D3DMesh obj, MetaStream stream)
private static void SerializeOldD3DMesh(ref D3DMesh obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotSupportedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
// Read Index Buffer
- bool hasIndexBuffer = streamReader.ReadBoolean();
+ bool hasIndexBuffer = stream.ReadBoolean();
if (hasIndexBuffer)
{
@@ -623,7 +623,7 @@ private static void SerializeOldD3DMesh(ref D3DMesh obj, MetaStream stream)
for (int i = 0; i < obj.T3VertexBuffers.Length; i++)
{
- bool hasVertexBuffer = streamReader.ReadBoolean();
+ bool hasVertexBuffer = stream.ReadBoolean();
if (!hasVertexBuffer)
continue;
@@ -638,11 +638,11 @@ private static void SerializeOldD3DMesh(ref D3DMesh obj, MetaStream stream)
private static void SerializeInternalResources(ref D3DMesh obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
obj.InternalResources ??= [];
- streamWriter.Write(obj.InternalResources.Count);
+ stream.Write(obj.InternalResources.Count);
foreach (HandleBase handle in obj.InternalResources)
{
@@ -657,8 +657,8 @@ private static void SerializeInternalResources(ref D3DMesh obj, MetaStream strea
MetaClassType typeSymbol = metaClass.ClassType;
handle.ObjectInfo.Type = typeSymbol;
- streamWriter.Write(symbol);
- streamWriter.Write(typeSymbol.Symbol.Crc64);
+ stream.Write(symbol);
+ stream.Write(typeSymbol.Symbol.Crc64);
stream.BeginBlock();
@@ -676,25 +676,32 @@ private static void SerializeInternalResources(ref D3DMesh obj, MetaStream strea
return;
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
obj.InternalResources = [];
// Part of DC Array
- int numRes = streamReader.ReadInt32();
+ int numRes = stream.ReadInt32();
for (var i = 0; i < numRes; i++)
{
// Handle name?
- Symbol symbol = streamReader.ReadSymbol();
- MetaClassType typeSymbol = streamReader.ReadMetaClassType();
+ Symbol symbol = stream.ReadSymbol();
+ MetaClassType? typeSymbol = stream.ReadMetaClassType();
stream.BeginBlock();
+ if (typeSymbol is null)
+ {
+ Toolkit.Instance.Logger.LogError($"[D3DMesh] Internal resource '{symbol}' has no registered type.");
+ stream.EndBlock();
+ continue;
+ }
+
object? propertyValue = null;
Toolkit.Instance.GetSerializer(typeSymbol.LinkingType)
- .PreSerialize(ref propertyValue, stream, typeSymbol);
+ .PreSerialize(ref propertyValue!, stream, typeSymbol);
Toolkit.Instance.GetSerializer(typeSymbol.LinkingType)
.Serialize(ref propertyValue, stream);
diff --git a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3GFXBuffer.cs b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3GFXBuffer.cs
index 9a53d05..9ae1d63 100644
--- a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3GFXBuffer.cs
+++ b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3GFXBuffer.cs
@@ -17,8 +17,8 @@ public class T3GFXBuffer
public GFXPlatformFormat BufferFormat { get; set; }
// [MetaMember("mBufferUsage")] This fails, because there's no flag set for this.
- // public GFXPlatformBufferUsage BufferUsage { get; set; }
-
+ // public GFXPlatformBufferUsage BufferUsage { get; set; }
+
[MetaMember("mBufferUsage")]
public uint BufferUsage { get; set; }
@@ -46,13 +46,13 @@ public override void Serialize(ref T3GFXBuffer obj, MetaStream stream)
// I assume it's done like for better performance
// This requires further testing
stream.BeginAsyncSection();
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- streamWriter.Write(obj.Buffer);
+ stream.Write(obj.Buffer);
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj.Buffer = streamReader.ReadBytes((int)(obj.Count * obj.Stride));
+ obj.Buffer = stream.ReadBytes((int)(obj.Count * obj.Stride));
}
stream.EndAsyncSection();
@@ -90,4 +90,4 @@ public enum GFXPlatformBufferUsage
ShaderReadWriteRaw = 0x38,
DrawIndirectArgs = 0x40,
SingleValue = 0x80,
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3GFXVertexState.cs b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3GFXVertexState.cs
index 418211b..27823b1 100644
--- a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3GFXVertexState.cs
+++ b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3GFXVertexState.cs
@@ -37,7 +37,7 @@ public override void Serialize(ref T3GFXVertexState obj, MetaStream stream)
MetaClass? desc = stream.GetMetaClass(typeof(T3GFXVertexState));
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
if (obj.AttributeCount > 32)
throw new InvalidDataException("AttributeCount is too large");
@@ -53,7 +53,6 @@ public override void Serialize(ref T3GFXVertexState obj, MetaStream stream)
for (var i = 0; i < obj.Attributes.Count; i++)
{
GFXPlatformAttributeParams attribute = obj.Attributes[i];
- stream.PreSerialize(ref attribute);
stream.Serialize(ref attribute);
obj.Attributes[i] = attribute;
}
@@ -63,7 +62,6 @@ public override void Serialize(ref T3GFXVertexState obj, MetaStream stream)
for (var i = 0; i < obj.IndexBuffer.Count; i++)
{
T3GFXBuffer indexBuffer = obj.IndexBuffer[i];
- stream.PreSerialize(ref indexBuffer);
stream.Serialize(ref indexBuffer);
obj.IndexBuffer[i] = indexBuffer;
}
@@ -71,12 +69,11 @@ public override void Serialize(ref T3GFXVertexState obj, MetaStream stream)
else
{
bool hasIndexBuffer = obj.IndexBuffer.Count > 0;
- streamWriter.Write(hasIndexBuffer);
+ stream.Write(hasIndexBuffer);
if (hasIndexBuffer)
{
T3GFXBuffer indexBuffer = obj.IndexBuffer[0];
- stream.PreSerialize(ref indexBuffer);
stream.Serialize(ref indexBuffer);
obj.IndexBuffer[0] = indexBuffer;
}
@@ -85,13 +82,12 @@ public override void Serialize(ref T3GFXVertexState obj, MetaStream stream)
for (var i = 0; i < obj.VertexBuffer.Count; i++)
{
T3GFXBuffer vertexBuffer = obj.VertexBuffer[i];
- stream.PreSerialize(ref vertexBuffer);
stream.Serialize(ref vertexBuffer);
obj.VertexBuffer[i] = vertexBuffer;
}
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
if (obj.AttributeCount > 32)
throw new InvalidDataException("AttributeCount is too large");
@@ -105,7 +101,6 @@ public override void Serialize(ref T3GFXVertexState obj, MetaStream stream)
for (var i = 0; i < obj.AttributeCount; i++)
{
var attribute = new GFXPlatformAttributeParams();
- stream.PreSerialize(ref attribute);
stream.Serialize(ref attribute);
obj.Attributes.Add(attribute);
}
@@ -115,7 +110,6 @@ public override void Serialize(ref T3GFXVertexState obj, MetaStream stream)
for (var i = 0; i < obj.IndexBufferCount; i++)
{
var indexBuffer = new T3GFXBuffer();
- stream.PreSerialize(ref indexBuffer);
stream.Serialize(ref indexBuffer);
obj.IndexBuffer.Add(indexBuffer);
}
@@ -124,11 +118,10 @@ public override void Serialize(ref T3GFXVertexState obj, MetaStream stream)
{
obj.IndexBufferCount = 1;
- bool hasIndexBuffer = streamReader.ReadBoolean();
+ bool hasIndexBuffer = stream.ReadBoolean();
if (hasIndexBuffer)
{
var indexBuffer = new T3GFXBuffer();
- stream.PreSerialize(ref indexBuffer);
stream.Serialize(ref indexBuffer);
obj.IndexBuffer.Add(indexBuffer);
}
@@ -137,11 +130,10 @@ public override void Serialize(ref T3GFXVertexState obj, MetaStream stream)
for (var i = 0; i < obj.VertexBufferCount; i++)
{
var submesh = new T3GFXBuffer();
- stream.PreSerialize(ref submesh);
stream.Serialize(ref submesh);
obj.VertexBuffer.Add(submesh);
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3IndexBuffer.cs b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3IndexBuffer.cs
index cdb2701..97f948c 100644
--- a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3IndexBuffer.cs
+++ b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3IndexBuffer.cs
@@ -16,10 +16,10 @@ public class T3IndexBuffer
[MetaMember("mFormat")]
public int Format { get; set; }
-
+
[MetaMember("mFlags")]
public Flags Flags { get; set; }
-
+
[MetaMember("mUsage")]
public int Usage { get; set; }
@@ -35,15 +35,15 @@ public class T3IndexBuffer
public class T3IndexBufferSerializer : MetaClassSerializer
{
private static readonly DefaultClassSerializer DefaultSerializer = new();
-
+
public override void Serialize(ref T3IndexBuffer obj, MetaStream stream)
{
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
int bufferBytes = obj.Format switch
{
@@ -57,7 +57,7 @@ public override void Serialize(ref T3IndexBuffer obj, MetaStream stream)
obj.IndexByteSize = bufferBytes;
}
- obj.Buffer = streamReader.ReadBytes(bufferBytes * obj.NumIndices);
+ obj.Buffer = stream.ReadBytes(bufferBytes * obj.NumIndices);
}
}
}
@@ -71,10 +71,10 @@ public class T3VertexComponent
[MetaMember("mCount")]
public uint Count { get; set; }
-
+
[MetaMember("mType")]
public EnumType Type { get; set; }
-
+
[MetaClassSerializerGlobal(typeof(EnumSerializer))]
public enum EnumType {
VTypeNone = 0,
@@ -108,10 +108,10 @@ public enum EnumType {
// {
// new DefaultClassSerializer().Serialize(ref obj, stream, desc);
//
-// if (stream is MetaStreamWriter streamWriter)
+// if (stream.Mode is MetaStreamMode.Write)
// {
// }
-// else if (stream is MetaStreamReader streamReader)
+// else if (stream.Mode is MetaStreamMode.Read)
// {
// var bufferBytes = 2;
// if (obj.Format != 101)
@@ -119,8 +119,8 @@ public enum EnumType {
// bufferBytes = 4;
// }
//
-// obj.IndexBufferData = streamReader.ReadBytes(obj.IndexByteSize * obj.NumIndices);
+// obj.IndexBufferData = stream.ReadBytes(obj.IndexByteSize * obj.NumIndices);
// }
// }
// }
-// }
\ No newline at end of file
+// }
diff --git a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MaterialData.cs b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MaterialData.cs
index 50d1916..bd6ee1c 100644
--- a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MaterialData.cs
+++ b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MaterialData.cs
@@ -64,15 +64,15 @@ public override void Serialize(ref T3MaterialData obj, MetaStream stream)
if (classDescription == null || classDescription.ContainsMember("mCompiledData2"))
return;
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
obj.CompiledData2 ??= [];
- streamWriter.Write(obj.CompiledData2.Count);
+ stream.Write(obj.CompiledData2.Count);
for (var i = 0; i < obj.CompiledData2.Count; i++)
{
- streamWriter.Write(i);
+ stream.Write(i);
T3MaterialCompiledData compiledData = obj.CompiledData2[i];
@@ -86,9 +86,9 @@ public override void Serialize(ref T3MaterialData obj, MetaStream stream)
return;
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- int numCompiledData = streamReader.ReadInt32();
+ int numCompiledData = stream.ReadInt32();
obj.CompiledData2 = new List(numCompiledData);
@@ -97,7 +97,7 @@ public override void Serialize(ref T3MaterialData obj, MetaStream stream)
for (var i = 0; i < numCompiledData; i++)
{
- int materialIndex = streamReader.ReadInt32();
+ int materialIndex = stream.ReadInt32();
if ((uint)materialIndex >= (uint)numCompiledData)
throw new InvalidDataException(
@@ -114,4 +114,4 @@ public override void Serialize(ref T3MaterialData obj, MetaStream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MeshData.cs b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MeshData.cs
index 9110c87..0cade51 100644
--- a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MeshData.cs
+++ b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MeshData.cs
@@ -102,7 +102,7 @@ public override void Serialize(ref T3MeshData obj, MetaStream stream)
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
uint uvTransformCount = 0;
// Referenced from Lucas Saragosa's serialization code
@@ -115,7 +115,7 @@ public override void Serialize(ref T3MeshData obj, MetaStream stream)
}
}
- streamWriter.Write(uvTransformCount);
+ stream.Write(uvTransformCount);
for (var i = 0; i < Math.Min(obj.TexCoordTransform.Length, 4); i++)
{
@@ -126,8 +126,7 @@ public override void Serialize(ref T3MeshData obj, MetaStream stream)
continue;
}
- streamWriter.Write((uint)i);
- stream.PreSerialize(ref transform);
+ stream.Write((uint)i);
stream.Serialize(ref transform);
obj.TexCoordTransform[i] = transform;
}
@@ -136,32 +135,29 @@ public override void Serialize(ref T3MeshData obj, MetaStream stream)
if (hasCpuSkinning)
{
T3MeshCPUSkinningData cpuSkinning = obj.CPUSkinningData;
- stream.PreSerialize(ref cpuSkinning);
stream.Serialize(ref cpuSkinning);
obj.CPUSkinningData = cpuSkinning;
}
- streamWriter.Write(obj.VertexStates.Count);
+ stream.Write(obj.VertexStates.Count);
for (var i = 0; i < obj.VertexStates.Count; i++)
{
T3GFXVertexState state = obj.VertexStates[i];
- stream.PreSerialize(ref state);
stream.Serialize(ref state);
obj.VertexStates[i] = state;
}
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- uint uvTransforms = streamReader.ReadUInt32();
+ uint uvTransforms = stream.ReadUInt32();
for (var i = 0; i < uvTransforms; i++)
{
- int uvLayer = streamReader.ReadInt32();
+ int uvLayer = stream.ReadInt32();
// Experimenting with a helper function.
- stream.PreSerialize(ref obj.TexCoordTransform[uvLayer]);
stream.Serialize(ref obj.TexCoordTransform[uvLayer]);
// TTKContext.Instance().GetSerializer().Serialize(ref obj.TexCoordTransform[vector], stream);
}
@@ -169,12 +165,11 @@ public override void Serialize(ref T3MeshData obj, MetaStream stream)
if ((obj.Flags.Data & (int)MeshFlags.eHasCPUSkinning) != 0)
{
T3MeshCPUSkinningData t3MeshCpuSkinningData = obj.CPUSkinningData;
- stream.PreSerialize(ref t3MeshCpuSkinningData);
stream.Serialize(ref t3MeshCpuSkinningData);
obj.CPUSkinningData = t3MeshCpuSkinningData;
}
- int vertexStates = streamReader.ReadInt32();
+ int vertexStates = stream.ReadInt32();
for (var i = 0; i < vertexStates; i++)
{
@@ -201,4 +196,4 @@ public class T3MeshBonePaletteEntry
[MetaMember("mNumVerts")]
public int NumVerts { get; set; }
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MeshTextureIndices.cs b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MeshTextureIndices.cs
index eae88bc..1d46624 100644
--- a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MeshTextureIndices.cs
+++ b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3MeshTextureIndices.cs
@@ -12,7 +12,7 @@ public class Serializer : MetaClassSerializer
{
public override void Serialize(ref T3MeshTextureIndices obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
if (obj?.Index != null)
{
@@ -22,28 +22,28 @@ public override void Serialize(ref T3MeshTextureIndices obj, MetaStream stream)
if ((index & int.MinValue) == 0) // int.MinValue == 0x80000000
{
int idx = i;
- streamWriter.Write(idx);
- streamWriter.Write(index);
+ stream.Write(idx);
+ stream.Write(index);
}
}
}
// Write the end
- streamWriter.Write(-1);
+ stream.Write(-1);
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- int i = streamReader.ReadInt32();
+ int i = stream.ReadInt32();
while ((i & int.MinValue) == 0)
{
- int x = streamReader.ReadInt32();
+ int x = stream.ReadInt32();
if (i < 2)
obj.Index[i] = x;
- i = streamReader.ReadInt32();
+ i = stream.ReadInt32();
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3VertexBuffer.cs b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3VertexBuffer.cs
index c967429..3f9733e 100644
--- a/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3VertexBuffer.cs
+++ b/src/TelltaleToolKit/T3Types/Meshes/T3Types/T3VertexBuffer.cs
@@ -49,12 +49,12 @@ public override void Serialize(ref T3VertexBuffer obj, MetaStream stream)
{
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException($"There is no serializer for {SerializationType}");
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
int totalBytes = obj.StoreCompressed switch
{
@@ -62,7 +62,7 @@ public override void Serialize(ref T3VertexBuffer obj, MetaStream stream)
_ => obj.NumVerts * obj.VertSize
};
- obj.Buffer = streamReader.ReadBytes(totalBytes);
+ obj.Buffer = stream.ReadBytes(totalBytes);
}
}
}
@@ -88,12 +88,12 @@ public override void Serialize(ref T3VertexBuffer obj, MetaStream stream)
// {
// new DefaultClassSerializer().Serialize(ref obj, stream, desc);
//
-// if (stream is MetaStreamWriter streamWriter)
+// if (stream.Mode is MetaStreamMode.Write)
// {
// }
-// else if (stream is MetaStreamReader streamReader)
+// else if (stream.Mode is MetaStreamMode.Read)
// {
// }
// }
// }
-// }
\ No newline at end of file
+// }
diff --git a/src/TelltaleToolKit/T3Types/MetaVersionInfo.cs b/src/TelltaleToolKit/T3Types/MetaVersionInfo.cs
index 5946eca..a334a1c 100644
--- a/src/TelltaleToolKit/T3Types/MetaVersionInfo.cs
+++ b/src/TelltaleToolKit/T3Types/MetaVersionInfo.cs
@@ -4,12 +4,27 @@
namespace TelltaleToolKit.T3Types;
+///
+/// Represents a single version info entry.
+///
[MetaClassSerializerGlobal(typeof(DefaultClassSerializer))]
public class MetaVersionInfo
{
[MetaMember("mTypeSymbolCrc")]
- public ulong TypeSymbolCrc;
+ public ulong TypeSymbolCrc { get; set; }
[MetaMember("mVersionCrc")]
- public uint mVersionCrc;
-}
\ No newline at end of file
+ public uint VersionCrc { get; set; }
+
+ public bool IsRegistered()
+ => Toolkit.Instance.ClassRegistry.GetClass(TypeSymbolCrc, VersionCrc) is not null;
+
+ public bool IsTypeRegistered()
+ => GetMetaClassType() is not null;
+
+ public MetaClassType? GetMetaClassType()
+ => MetaClassTypeRegistry.GetByHash(TypeSymbolCrc);
+
+ public MetaClass? GetMetaClass()
+ => Toolkit.Instance.ClassRegistry.GetClass(TypeSymbolCrc, VersionCrc);
+}
diff --git a/src/TelltaleToolKit/T3Types/Properties/PropertySet.cs b/src/TelltaleToolKit/T3Types/Properties/PropertySet.cs
index bdc56ba..9ef6f57 100644
--- a/src/TelltaleToolKit/T3Types/Properties/PropertySet.cs
+++ b/src/TelltaleToolKit/T3Types/Properties/PropertySet.cs
@@ -55,13 +55,13 @@ public override void Serialize(ref PropertySet obj, MetaStream stream)
stream.BeginBlock();
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
// Write ParentList when PropVersion > 0
if (obj.PropVersion > 0)
{
int parentCount = obj.ParentList.Count;
- streamWriter.Write(parentCount);
+ stream.Write(parentCount);
MetaClassSerializer> parentSerializer =
Toolkit.Instance.GetSerializer>();
@@ -107,7 +107,7 @@ public override void Serialize(ref PropertySet obj, MetaStream stream)
}
// Write number of distinct meta types
- streamWriter.Write(groups.Count);
+ stream.Write(groups.Count);
// Stable order: order by the MetaClassType's identity marker (e.g., its symbol or linking-type name)
// Use MetaClassType.Symbol if present (prefer preserving the meta-symbol ordering), otherwise LinkingType.FullName
@@ -124,15 +124,15 @@ public override void Serialize(ref PropertySet obj, MetaStream stream)
foreach ((MetaClassType typeSymbol, List> entries) in ordered)
{
- streamWriter.Write(typeSymbol);
- streamWriter.Write(entries.Count);
+ stream.Write(typeSymbol);
+ stream.Write(entries.Count);
MetaClassSerializer classTypeSerializer =
Toolkit.Instance.GetSerializer(typeSymbol.LinkingType);
foreach ((Symbol key, PropertyEntry value) in entries)
{
- streamWriter.Write(key);
+ stream.Write(key);
object? propertyValue = value.Value;
@@ -144,11 +144,11 @@ public override void Serialize(ref PropertySet obj, MetaStream stream)
}
}
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
if (obj.PropVersion > 1)
{
- obj.ParentList.Capacity = streamReader.ReadInt32();
+ obj.ParentList.Capacity = stream.ReadInt32();
for (var i = 0; i < obj.ParentList.Capacity; i++)
{
@@ -158,26 +158,29 @@ public override void Serialize(ref PropertySet obj, MetaStream stream)
}
}
- if (obj.PropVersion == 1 && !stream.GetMetaClass(typeof(PropertySet)).ContainsMember("mParentList"))
+ if (obj.PropVersion == 1 && !stream.GetMetaClass(typeof(PropertySet))!.ContainsMember("mParentList"))
{
stream.EndBlock();
stream.BeginBlock();
}
- int numTypes = streamReader.ReadInt32();
+ int numTypes = stream.ReadInt32();
for (var i = 0; i < numTypes; i++)
{
// The type of the class
- MetaClassType typeSymbol = streamReader.ReadMetaClassType();
+ MetaClassType? typeSymbol = stream.ReadMetaClassType();
// The number of times that type has been serialized
- int numOfType = streamReader.ReadInt32();
+ int numOfType = stream.ReadInt32();
+
+ if (typeSymbol is null)
+ throw new InvalidOperationException("[PropertySet] Type symbol is null");
MetaClassSerializer classTypeSerializer = Toolkit.Instance.GetSerializer(typeSymbol.LinkingType);
for (var j = 0; j < numOfType; j++)
{
// The property key
- Symbol propertyKey = streamReader.ReadSymbol();
+ Symbol propertyKey = stream.ReadSymbol();
object? propertyValue = null;
@@ -192,7 +195,6 @@ public override void Serialize(ref PropertySet obj, MetaStream stream)
if (embeddedParentProps)
{
- stream.PreSerialize(ref obj.ParentProperties);
stream.Serialize(ref obj.ParentProperties);
}
diff --git a/src/TelltaleToolKit/T3Types/Properties/ToolProps.cs b/src/TelltaleToolKit/T3Types/Properties/ToolProps.cs
index 139cd19..81caf3d 100644
--- a/src/TelltaleToolKit/T3Types/Properties/ToolProps.cs
+++ b/src/TelltaleToolKit/T3Types/Properties/ToolProps.cs
@@ -22,16 +22,16 @@ public override void PreSerialize(ref ToolProps obj, MetaStream stream, MetaClas
public override void Serialize(ref ToolProps obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
// Quoting Lucas: "we are not in the tool engine, runtime engine. would normally be a #ifdef COMPILING_AS_TOOL etc"
obj.HasProps = false;
- streamWriter.Write(obj.HasProps);
+ stream.Write(obj.HasProps);
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj.HasProps = streamReader.ReadBoolean();
-
+ obj.HasProps = stream.ReadBoolean();
+
if (!obj.HasProps)
return;
@@ -41,4 +41,4 @@ public override void Serialize(ref ToolProps obj, MetaStream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Rules/Rule.cs b/src/TelltaleToolKit/T3Types/Rules/Rule.cs
index ac7c4d7..3684a4b 100644
--- a/src/TelltaleToolKit/T3Types/Rules/Rule.cs
+++ b/src/TelltaleToolKit/T3Types/Rules/Rule.cs
@@ -77,12 +77,12 @@ public override void Serialize(ref Rule obj, MetaStream stream)
}
stream.BeginBlock();
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- // var numAgents = streamReader.ReadInt32();
+ // var numAgents = stream.ReadInt32();
List agents = obj.AgentInformation;
Toolkit.Instance.GetSerializer>().Serialize(ref agents, stream);
@@ -91,8 +91,8 @@ public override void Serialize(ref Rule obj, MetaStream stream)
// {
// var agent = new Rule.AgentInfo();
// MetaClass? ruleDesc = stream.GetMetaClass(typeof(Rule));
- //
- //
+ //
+ //
// }
//
//
@@ -103,4 +103,4 @@ public override void Serialize(ref Rule obj, MetaStream stream)
stream.EndBlock();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Rules/Rules.cs b/src/TelltaleToolKit/T3Types/Rules/Rules.cs
index 159922f..74aba5c 100644
--- a/src/TelltaleToolKit/T3Types/Rules/Rules.cs
+++ b/src/TelltaleToolKit/T3Types/Rules/Rules.cs
@@ -30,28 +30,25 @@ public override void Serialize(ref Rules obj, MetaStream stream)
DefaultSerializer.Serialize(ref obj, stream);
stream.BeginBlock();
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
HashSet rulesSet = obj.RulesSet;
- Toolkit.Instance.GetSerializer>()
- .Serialize(ref rulesSet, stream);
+ stream.Serialize(ref rulesSet);
////
- MetaClassSerializer ruleSerializer = Toolkit.Instance.GetSerializer();
-
obj.RuleMap = new Dictionary();
foreach (string t in obj.RulesSet)
{
var rule = new Rule();
- ruleSerializer.Serialize(ref rule, stream);
+ stream.Serialize(ref rule);
obj.RuleMap.Add(t, rule);
}
@@ -60,4 +57,4 @@ public override void Serialize(ref Rules obj, MetaStream stream)
stream.EndBlock();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/SaveGame.cs b/src/TelltaleToolKit/T3Types/SaveGame.cs
index 462d36b..db9af29 100644
--- a/src/TelltaleToolKit/T3Types/SaveGame.cs
+++ b/src/TelltaleToolKit/T3Types/SaveGame.cs
@@ -43,8 +43,8 @@ public class AgentInfo
[MetaMember("mAttachedToNode")]
public string AttachedToNode { get; set; } = string.Empty;
}
-
-
+
+
public class Serializer : MetaClassSerializer
{
private static readonly DefaultClassSerializer DefaultSaveGameSerializer = new();
@@ -53,12 +53,12 @@ public override void Serialize(ref SaveGame obj, MetaStream stream)
{
DefaultSaveGameSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
if (stream.GetMetaClass(typeof(PropertySet)) != null)
{
@@ -69,4 +69,4 @@ public override void Serialize(ref SaveGame obj, MetaStream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Scenes/Scene.cs b/src/TelltaleToolKit/T3Types/Scenes/Scene.cs
index 4811929..3af7b5c 100644
--- a/src/TelltaleToolKit/T3Types/Scenes/Scene.cs
+++ b/src/TelltaleToolKit/T3Types/Scenes/Scene.cs
@@ -32,14 +32,14 @@ public override void Serialize(ref Scene obj, MetaStream stream)
stream.BeginBlock();
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- int numAgents = streamReader.ReadInt32();
+ int numAgents = stream.ReadInt32();
obj.Agents.Capacity = numAgents;
for (var i = 0; i < numAgents; i++)
@@ -53,7 +53,7 @@ public override void Serialize(ref Scene obj, MetaStream stream)
stream.EndBlock();
}
}
-
+
[MetaClassSerializerGlobal(typeof(DefaultClassSerializer))]
public class AgentInfo
{
@@ -97,4 +97,4 @@ public class AgentQualitySettings
[MetaMember("mFlags")]
public Flags Flags { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/StyleGuides/ActingPaletteClass.cs b/src/TelltaleToolKit/T3Types/StyleGuides/ActingPaletteClass.cs
index 3e3237f..7506fa4 100644
--- a/src/TelltaleToolKit/T3Types/StyleGuides/ActingPaletteClass.cs
+++ b/src/TelltaleToolKit/T3Types/StyleGuides/ActingPaletteClass.cs
@@ -54,16 +54,16 @@ public override void Serialize(ref ActingPaletteClass obj, MetaStream stream)
if (classDescription is not null && classDescription.ContainsMember("mFlags"))
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
throw new NotImplementedException();
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/StyleGuides/StyleGuide.cs b/src/TelltaleToolKit/T3Types/StyleGuides/StyleGuide.cs
index 996d4f9..f367e0b 100644
--- a/src/TelltaleToolKit/T3Types/StyleGuides/StyleGuide.cs
+++ b/src/TelltaleToolKit/T3Types/StyleGuides/StyleGuide.cs
@@ -50,19 +50,18 @@ public override void Serialize(ref StyleGuide obj, MetaStream stream)
if (classDescription is not null && classDescription.ContainsMember("mFlags"))
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- int values = streamReader.ReadInt32();
+ int values = stream.ReadInt32();
for (var i = 0; i < values; i++)
{
var child = new ActingPaletteClass();
- stream.PreSerialize(ref child);
stream.Serialize(ref child);
obj.PaletteClassesPtrs.Add(child);
}
@@ -70,4 +69,4 @@ public override void Serialize(ref StyleGuide obj, MetaStream stream)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Textures/D3DTexture.cs b/src/TelltaleToolKit/T3Types/Textures/D3DTexture.cs
index 584d9aa..7dcb370 100644
--- a/src/TelltaleToolKit/T3Types/Textures/D3DTexture.cs
+++ b/src/TelltaleToolKit/T3Types/Textures/D3DTexture.cs
@@ -47,12 +47,12 @@ namespace TelltaleToolKit.T3Types.Textures;
// // Default Serializer
// new DefaultClassSerializer().Serialize(ref obj, stream, desc);
//
-// if (stream is MetaStreamWriter streamWriter)
+// if (stream.Mode is MetaStreamMode.Write)
// {
-// streamWriter.Write(obj.DdsTextureData.Length);
-// streamWriter.Write(obj.DdsTextureData);
+// stream.Write(obj.DdsTextureData.Length);
+// stream.Write(obj.DdsTextureData);
// }
-// else if (stream is MetaStreamReader streamReader)
+// else if (stream.Mode is MetaStreamMode.Read)
// {
// if (!obj.HasTextureData)
// {
@@ -60,9 +60,9 @@ namespace TelltaleToolKit.T3Types.Textures;
// }
//
// // T3MetaStream.SerializeByteArray(ref texture.DdsTextureData, stream);
-// var bufferSize = streamReader.ReadInt32();
-// obj.DdsTextureData = streamReader.ReadBytes(bufferSize);
+// var bufferSize = stream.ReadInt32();
+// obj.DdsTextureData = stream.ReadBytes(bufferSize);
// }
// }
// }
-// }
\ No newline at end of file
+// }
diff --git a/src/TelltaleToolKit/T3Types/Textures/T3Texture.cs b/src/TelltaleToolKit/T3Types/Textures/T3Texture.cs
index 99d32d2..a2cc716 100644
--- a/src/TelltaleToolKit/T3Types/Textures/T3Texture.cs
+++ b/src/TelltaleToolKit/T3Types/Textures/T3Texture.cs
@@ -356,7 +356,7 @@ public override void Serialize(ref T3Texture obj, MetaStream stream)
// The default serializer will throw if there is no serialized T3Texture.
MetaClass classDescription = stream.GetMetaClass(typeof(T3Texture))!;
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
if (classDescription.ContainsMember("mVersion"))
{
@@ -388,23 +388,27 @@ public override void Serialize(ref T3Texture obj, MetaStream stream)
stream.BeginAsyncSection();
foreach (RegionStreamHeader region in obj.RegionHeaders)
{
- streamWriter.Write(region.RegionData);
+ stream.Write(region.RegionData);
}
stream.EndAsyncSection();
}
else
{
- streamWriter.Write(obj.DdsTextureData.Length);
- streamWriter.Write(obj.DdsTextureData);
+ stream.Key("DDSLength");
+ stream.Write(obj.DdsTextureData.Length);
+
+ stream.Key("DDSTextureData");
+ stream.Write(obj.DdsTextureData);
if (obj.TplTextureDataSize > 0)
{
- streamWriter.Write(obj.TplTextureData);
+ stream.Key("TplTextureData");
+ stream.Write(obj.TplTextureData);
}
}
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
// This is easier than checking for region stream headers. That can also work.
if (classDescription.ContainsMember("mVersion"))
@@ -452,7 +456,7 @@ public override void Serialize(ref T3Texture obj, MetaStream stream)
region.SlicePitch = region.DataSize;
}
- region.RegionData = streamReader.ReadBytes(region.DataSize);
+ region.RegionData = stream.ReadBytes(region.DataSize);
}
stream.EndAsyncSection();
@@ -464,14 +468,14 @@ public override void Serialize(ref T3Texture obj, MetaStream stream)
return;
}
- int bufferSize = streamReader.ReadInt32();
- obj.DdsTextureData = streamReader.ReadBytes(bufferSize);
+ int bufferSize = stream.ReadInt32();
+ obj.DdsTextureData = stream.ReadBytes(bufferSize);
if (obj.TplTextureDataSize > 0)
{
obj.TplTextureData = new byte[obj.TplTextureDataSize];
- obj.TplTextureData = streamReader.ReadBytes(obj.TplTextureDataSize);
+ obj.TplTextureData = stream.ReadBytes(obj.TplTextureDataSize);
}
}
}
diff --git a/src/TelltaleToolKit/T3Types/UID.cs b/src/TelltaleToolKit/T3Types/UID.cs
index bf60e20..919a5e9 100644
--- a/src/TelltaleToolKit/T3Types/UID.cs
+++ b/src/TelltaleToolKit/T3Types/UID.cs
@@ -15,6 +15,7 @@ public class Generator
[MetaMember("miNextUniqueID")]
public int NextUniqueId { get; set; }
}
+
public interface IGenerator
{
public Generator Generator { get; set; }
@@ -68,15 +69,15 @@ public override void PreSerialize(ref ActingOverridablePropOwner obj, MetaStream
public override void Serialize(ref ActingOverridablePropOwner obj, MetaStream stream)
{
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
throw new NotImplementedException();
}
- if (stream is MetaStreamReader streamReader)
+ if (stream.Mode is MetaStreamMode.Read)
{
- long currPos = streamReader.GetCurrentPosition();
- uint value = streamReader.ReadUInt32();
+ long currPos = stream.GetPosition();
+ uint value = stream.ReadUInt32();
if (value == ActingOverridablePropOwner.kHeader)
{
@@ -85,14 +86,13 @@ public override void Serialize(ref ActingOverridablePropOwner obj, MetaStream st
return;
}
- streamReader.SetCurrentPosition(currPos);
+ stream.SetPosition(currPos);
if ((obj.SerializationFlags.Data & 1) == 0)
return;
PropertySet propertySet = obj.OverridableValues;
- Toolkit.Instance.GetSerializer().PreSerialize(ref propertySet, stream);
- Toolkit.Instance.GetSerializer().Serialize(ref propertySet, stream);
+ stream.Serialize(ref propertySet);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Vers.cs b/src/TelltaleToolKit/T3Types/Vers.cs
index bc8bb85..6036193 100644
--- a/src/TelltaleToolKit/T3Types/Vers.cs
+++ b/src/TelltaleToolKit/T3Types/Vers.cs
@@ -34,45 +34,45 @@ public override void Serialize(ref Vers obj, MetaStream stream)
{
PreSerialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- streamWriter.Write(obj.FileName);
- streamWriter.Write(obj.ClassName);
- streamWriter.Write(obj.Crc32);
- streamWriter.Write(obj.Size);
- streamWriter.Write(obj.IsBlocked);
- streamWriter.Write(obj.Crc32);
- streamWriter.Write(obj.Registries.Count);
+ stream.Write(obj.FileName);
+ stream.Write(obj.ClassName);
+ stream.Write(obj.Crc32);
+ stream.Write(obj.Size);
+ stream.Write(obj.IsBlocked);
+ stream.Write(obj.Crc32);
+ stream.Write(obj.Registries.Count);
for (int i = 0; i < obj.Registries.Capacity; i++)
{
- streamWriter.Write(obj.Registries[i].MemberName);
- streamWriter.Write(obj.Registries[i].MemberType);
- streamWriter.Write(obj.Registries[i].Size);
- streamWriter.Write(obj.Registries[i].IsBlocked);
- streamWriter.Write(obj.Registries[i].Crc32);
+ stream.Write(obj.Registries[i].MemberName);
+ stream.Write(obj.Registries[i].MemberType);
+ stream.Write(obj.Registries[i].Size);
+ stream.Write(obj.Registries[i].IsBlocked);
+ stream.Write(obj.Registries[i].Crc32);
}
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj.FileName = streamReader.ReadString();
- obj.ClassName = streamReader.ReadString();
- obj.Crc32 = streamReader.ReadUInt32();
- obj.Size = streamReader.ReadUInt32();
- obj.IsBlocked = streamReader.ReadBoolean();
+ obj.FileName = stream.ReadString();
+ obj.ClassName = stream.ReadString();
+ obj.Crc32 = stream.ReadUInt32();
+ obj.Size = stream.ReadUInt32();
+ obj.IsBlocked = stream.ReadBoolean();
- obj.Registries.Capacity = streamReader.ReadInt32();
+ obj.Registries.Capacity = stream.ReadInt32();
for (int i = 0; i < obj.Registries.Capacity; i++)
{
obj.Registries.Add(new MemberRegistry());
- obj.Registries[i].MemberName = streamReader.ReadString();
- obj.Registries[i].MemberType = streamReader.ReadString();
- obj.Registries[i].Size = streamReader.ReadUInt32();
- obj.Registries[i].IsBlocked = streamReader.ReadBoolean();
- obj.Registries[i].Crc32 = streamReader.ReadUInt32();
+ obj.Registries[i].MemberName = stream.ReadString();
+ obj.Registries[i].MemberType = stream.ReadString();
+ obj.Registries[i].Size = stream.ReadUInt32();
+ obj.Registries[i].IsBlocked = stream.ReadBoolean();
+ obj.Registries[i].Crc32 = stream.ReadUInt32();
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/T3Types/Voice/VoiceData.cs b/src/TelltaleToolKit/T3Types/Voice/VoiceData.cs
index 133de0d..c773b33 100644
--- a/src/TelltaleToolKit/T3Types/Voice/VoiceData.cs
+++ b/src/TelltaleToolKit/T3Types/Voice/VoiceData.cs
@@ -41,14 +41,14 @@ public override void Serialize(ref VoiceData obj, MetaStream stream)
DefaultSerializer.PreSerialize(ref obj, stream);
DefaultSerializer.Serialize(ref obj, stream);
- if (stream is MetaStreamWriter streamWriter)
+ if (stream.Mode is MetaStreamMode.Write)
{
- streamWriter.Write(obj.VoiceDataBuffer);
+ stream.Write(obj.VoiceDataBuffer);
}
- else if (stream is MetaStreamReader streamReader)
+ else if (stream.Mode is MetaStreamMode.Read)
{
- obj.VoiceDataBuffer = streamReader.ReadBytes(obj.AllPacketsSize);
+ obj.VoiceDataBuffer = stream.ReadBytes(obj.AllPacketsSize);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/TelltaleArchives/Archive.cs b/src/TelltaleToolKit/TelltaleArchives/Archive.cs
index 466bb8d..798932c 100644
--- a/src/TelltaleToolKit/TelltaleArchives/Archive.cs
+++ b/src/TelltaleToolKit/TelltaleArchives/Archive.cs
@@ -40,19 +40,19 @@ public abstract class Archive : IDisposable
///
/// Gets the original file path of the archive, if it was loaded from a file.
- /// when the archive was opened from a .
+ /// when the archive was opened from a .
///
public string? FilePath { get; private set; }
///
/// Gets the length of the underlying stream in bytes, if the stream supports seeking.
- /// when the stream is not seekable (e.g., a network stream).
+ /// when the stream is not seekable (e.g., a network stream).
///
public long? Length => BaseStream?.CanSeek == true ? BaseStream.Length : null;
///
/// Gets the file name (without directory) of the archive, if a file path is available.
- /// when is .
+ /// when is .
///
public string? FileName => FilePath is not null ? Path.GetFileName(FilePath) : null;
diff --git a/src/TelltaleToolKit/TelltaleArchives/ArchiveWriteOptions.cs b/src/TelltaleToolKit/TelltaleArchives/ArchiveWriteOptions.cs
index 01f9ff8..ddec78a 100644
--- a/src/TelltaleToolKit/TelltaleArchives/ArchiveWriteOptions.cs
+++ b/src/TelltaleToolKit/TelltaleArchives/ArchiveWriteOptions.cs
@@ -1,4 +1,7 @@
-namespace TelltaleToolKit.TelltaleArchives;
+using TelltaleToolKit.Serialization;
+using TelltaleToolKit.Utility.Compression;
+
+namespace TelltaleToolKit.TelltaleArchives;
///
/// Controls how an archive is written or repacked.
@@ -24,9 +27,9 @@ public sealed class ArchiveWriteOptions
///
/// Gets or sets the compression algorithm to use.
- /// Defaults to .
+ /// Defaults to .
///
- public CompressionAlgorithm Algorithm { get; set; } = CompressionAlgorithm.Deflate;
+ public Compression Algorithm { get; set; } = Compression.Deflate;
///
/// Gets or sets the decompressed size of each chunk in bytes.
@@ -50,20 +53,4 @@ public sealed class ArchiveWriteOptions
public string BlowfishKey { get; set; } = string.Empty;
}
-/// Compression algorithm to use when writing archive chunks.
-public enum CompressionAlgorithm
-{
- /// No compression.
- None,
-
- /// Raw DEFLATE (no zlib header). Used by most Telltale games.
- Deflate,
-
- /// Zlib-wrapped DEFLATE. Used by some CSI and earlier titles.
- Zlib,
- ///
- /// Oodle compression.
- ///
- Oodle
-}
diff --git a/src/TelltaleToolKit/TelltaleArchives/Formats/TTArchive.cs b/src/TelltaleToolKit/TelltaleArchives/Formats/TTArchive.cs
index 7c1e53e..cb45112 100644
--- a/src/TelltaleToolKit/TelltaleArchives/Formats/TTArchive.cs
+++ b/src/TelltaleToolKit/TelltaleArchives/Formats/TTArchive.cs
@@ -2,9 +2,10 @@
using System.IO.Hashing;
using System.Text;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
-using TelltaleToolKit.Serialization.Binary;
+using TelltaleToolKit.Serialization;
using TelltaleToolKit.TelltaleArchives.IO;
using TelltaleToolKit.Utility.Blowfish;
+using TelltaleToolKit.Utility.Compression;
using Crc64 = TelltaleToolKit.Utility.Hashing.Crc64;
namespace TelltaleToolKit.TelltaleArchives.Formats;
@@ -24,7 +25,7 @@ public class TTArchive : Archive
private Stream? _dataStream; // decompressed view of the entire file-data region
- protected override void Activate()
+ protected override void Activate()
{
using BinaryReader reader = new(BaseStream!, Encoding.UTF8, true);
@@ -143,7 +144,9 @@ protected override void Activate()
}
if (decryptionMode == 1)
+ {
_blowfish.Decipher(header, header.Length);
+ }
Info.FilesOffset = (ulong)reader.BaseStream.Position;
ParseEntries(new MemoryStream(header));
@@ -262,7 +265,6 @@ private Stream BuildFileDataStream(int version, uint filesMode, uint decryptionM
throw new InvalidOperationException("Archive not loaded.");
}
- // TODO: Implement decrypting in MetaStream
return entry is null ? null : new SubStream(_dataStream, (long)entry.Offset, entry.Size);
}
@@ -292,7 +294,7 @@ public static void Create(Stream output, IEnumerable<(string name, Stream dataSt
}
bool encrypt = options.Encrypt;
- bool compressFileData = options.Algorithm != CompressionAlgorithm.None && version >= 3;
+ bool compressFileData = options.Algorithm != Compression.None && version >= 3;
bool compressHeader = compressFileData && version >= 6; // compressed entry table block (filesMode=2)
Blowfish bf = new(options.BlowfishKey, version);
@@ -350,7 +352,7 @@ public static void Create(Stream output, IEnumerable<(string name, Stream dataSt
byte[] CompressBlock(byte[] data)
{
using MemoryStream ms = new();
- if (options.Algorithm == CompressionAlgorithm.Zlib)
+ if (options.Algorithm == Compression.Zlib)
{
using DeflaterOutputStream zlib = new(ms);
zlib.Write(data, 0, data.Length);
diff --git a/src/TelltaleToolKit/TelltaleArchives/Formats/TTArchive2.cs b/src/TelltaleToolKit/TelltaleArchives/Formats/TTArchive2.cs
index e183c1d..d6900ae 100644
--- a/src/TelltaleToolKit/TelltaleArchives/Formats/TTArchive2.cs
+++ b/src/TelltaleToolKit/TelltaleArchives/Formats/TTArchive2.cs
@@ -1,6 +1,6 @@
using System.Text;
-using TelltaleToolKit.Serialization.Binary;
-using TelltaleToolKit.TelltaleArchives.IO;
+using TelltaleToolKit.Serialization;
+using TelltaleToolKit.Utility.Compression;
using TelltaleToolKit.Utility.Hashing;
namespace TelltaleToolKit.TelltaleArchives.Formats;
@@ -33,9 +33,15 @@ protected override void Activate()
_containerStream = new ContainerStream(BaseStream!, Info.BlowfishKey);
// Copy container state back to Info so the rest of the code (and callers
// that inspect Info) see the right values.
- Info.Flags = _containerStream.Flags;
- Info.ChunkSize = _containerStream.ChunkSize;
- Info.ChunkCount = _containerStream.ChunkCount;
+ Info.Flags |= _containerStream.Params.Encrypt ? ArchiveFlags.IsEncrypted : 0;
+ Info.Flags |= _containerStream.Params.Algorithm == Compression.Deflate
+ ? ArchiveFlags.IsRawDeflateCompressed
+ : 0;
+ Info.Flags |= _containerStream.Params.Algorithm == Compression.Oodle
+ ? ArchiveFlags.IsOodleCompressed
+ : 0;
+ Info.ChunkSize = _containerStream.WindowSize;
+ Info.ChunkCount = _containerStream.NumPages;
Info.ChunkBlockSizes = _containerStream.ChunkBlockSizes;
ReadTtaHeader(_containerStream);
@@ -311,7 +317,15 @@ public static void Create(Stream output, IEnumerable<(string name, Stream dataSt
using (FileStream innerReader = new(tempFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096,
FileOptions.DeleteOnClose | FileOptions.SequentialScan))
{
- ContainerStream.Create(output, innerReader, options);
+ ContainerStreamParams containerOptions = new()
+ {
+ Encrypt = options.Encrypt,
+ ChunkSize = options.ChunkSize,
+ Algorithm = options.Algorithm,
+ BlowfishKey = options.BlowfishKey
+ };
+
+ ContainerStream.Create(output, innerReader, containerOptions);
}
}
finally
diff --git a/src/TelltaleToolKit/TelltaleArchives/IO/LegacyEncrypted.cs b/src/TelltaleToolKit/TelltaleArchives/IO/LegacyEncrypted.cs
deleted file mode 100644
index 01d93ca..0000000
--- a/src/TelltaleToolKit/TelltaleArchives/IO/LegacyEncrypted.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using TelltaleToolKit.Utility.Blowfish;
-
-namespace TelltaleToolKit.TelltaleArchives.IO;
-
-///
-/// Stream wrapper that decrypts TTArchive per‑file data on the fly.
-/// For simplicity, it reads the entire file into memory – these files are small
-/// (config files, scripts) so the overhead is acceptable.
-///
-internal sealed class LegacyEncrypted : Stream
-{
- private readonly Blowfish _blowfish;
- private readonly Stream _inner;
-
- public LegacyEncrypted(Stream inner, Blowfish blowfish)
- {
- _inner = inner;
- _blowfish = blowfish;
- // Read the whole file, decrypt it, and keep the result in memory.
- using MemoryStream ms = new();
- _inner.CopyTo(ms);
- // _decryptedData = ms.ToArray();
- // TelltaleArchiveUtilities.DecryptFile(_decryptedData, _blowfish);
- }
-
- public override bool CanRead => true;
- public override bool CanSeek => true;
- public override bool CanWrite => false;
-
- public override long Length { get; }
- // public override long Length => _decryptedData.Length;
-
- public override long Position { get; set; }
-
- public override int Read(byte[] buffer, int offset, int count)
- => Read(buffer.AsSpan(offset, count));
-
- public override int Read(Span buffer)
- {
- long remaining = Length - Position;
- if (remaining <= 0)
- {
- return 0;
- }
-
- int toRead = (int)Math.Min(buffer.Length, remaining);
- // _decryptedData.AsSpan((int)_position, toRead).CopyTo(buffer);
- Position += toRead;
- return toRead;
- }
-
- public override long Seek(long offset, SeekOrigin origin)
- {
- Position = origin switch
- {
- SeekOrigin.Begin => offset,
- SeekOrigin.Current => Position + offset,
- SeekOrigin.End => Length + offset,
- _ => throw new ArgumentOutOfRangeException(nameof(origin))
- };
- return Position;
- }
-
- public override void Flush()
- {
- }
-
- public override void SetLength(long value) => throw new NotSupportedException();
- public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
-}
diff --git a/src/TelltaleToolKit/TelltaleArchives/IO/TtarchiveChunkedDataStream.cs b/src/TelltaleToolKit/TelltaleArchives/IO/TtarchiveChunkedDataStream.cs
index c464ba7..0b4a919 100644
--- a/src/TelltaleToolKit/TelltaleArchives/IO/TtarchiveChunkedDataStream.cs
+++ b/src/TelltaleToolKit/TelltaleArchives/IO/TtarchiveChunkedDataStream.cs
@@ -1,5 +1,6 @@
-using TelltaleToolKit.TelltaleArchives.Caching;
-using TelltaleToolKit.Utility.Blowfish;
+using TelltaleToolKit.Utility.Blowfish;
+using TelltaleToolKit.Utility.Caching;
+using TelltaleToolKit.Utility.Compression;
namespace TelltaleToolKit.TelltaleArchives.IO;
@@ -15,9 +16,9 @@ internal sealed class TtarchiveChunkedDataStream : Stream
private readonly uint _chunkSize;
private readonly ulong[] _chunkSizes;
private readonly long _dataStart;
- private ArchiveFlags _flags;
private readonly bool _isEncrypted;
private readonly Stream _source;
+ private ArchiveFlags _flags;
private long _position;
public TtarchiveChunkedDataStream(
@@ -126,7 +127,7 @@ private byte[] DecodeChunk(int chunkIdx)
// Decompress the chunk
ArchiveFlags flags = _flags;
- var res = ChunkDecoder.DecompressBlock(compressed, (int)_chunkSize, ref flags);
+ byte[] res = ChunkDecoder.DecompressBlock(compressed, (int)_chunkSize, ref flags);
_flags = flags;
return res;
}
diff --git a/src/TelltaleToolKit/TelltaleArchives/TelltaleArchiveUtilities.cs b/src/TelltaleToolKit/TelltaleArchives/TelltaleArchiveUtilities.cs
deleted file mode 100644
index 6f0adc7..0000000
--- a/src/TelltaleToolKit/TelltaleArchives/TelltaleArchiveUtilities.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-using System.IO.Compression;
-using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
-using TelltaleToolKit.Serialization.Binary;
-using TelltaleToolKit.Utility.Blowfish;
-using BlockConfiguration = (int blockSize, int cryptInterval, int cleanInterval);
-
-namespace TelltaleToolKit.TelltaleArchives;
-
-public static class TelltaleArchiveUtilities
-{
- public static void DecryptFile(byte[] file, string blowfishKey, int archiveVersion)
- {
- if (file.Length < 4)
- {
- return;
- }
-
- DecryptFile(file, new Blowfish(blowfishKey, archiveVersion));
- }
-
- public static void DecryptFile(byte[] file, Blowfish blowfish)
- {
- if (file.Length < 4)
- {
- return;
- }
-
- MetaStreamVersion magicFourCc = (MetaStreamVersion)BitConverter.ToUInt32(file, 0);
- BlockConfiguration config = GetBlockConfiguration(magicFourCc);
-
- if (config.blockSize > 0)
- {
- DecryptFileHelper(file, blowfish, config);
- }
- }
-
- //(int blockSize, int cryptInterval, int cleanInterval);
- private static BlockConfiguration GetBlockConfiguration(MetaStreamVersion type)
- => type switch
- {
- MetaStreamVersion.Unknown1 or MetaStreamVersion.Unknown2 or MetaStreamVersion.Unknown3 => (128, 32, 80),
- MetaStreamVersion.Unknown4 => (256, 8, 24),
- MetaStreamVersion.Mbes => (64, 64, 100),
- _ => (0, 0, 0)
- };
-
- private static void DecryptFileHelper(byte[] fileData, Blowfish blowFish, BlockConfiguration config)
- {
- // If the blocksize is 0 (Stream version is not existent), use regular blowfish
- if (config.blockSize == 0)
- {
- blowFish.Decipher(fileData, fileData.Length);
- return;
- }
-
- // Replace the magic number with Mbin (Might be replaced with MBIN/MTRE)
- Array.Copy(BitConverter.GetBytes((uint)MetaStreamVersion.Mbin), 0, fileData, 0, 4);
-
- long blocks = (fileData.Length - 4) / config.blockSize;
-
- for (int i = 0; i < blocks; i++)
- {
- Span block = new(fileData, 4 + i * config.blockSize, config.blockSize);
-
- if (i % config.cryptInterval == 0)
- {
- blowFish.Decipher(block, block.Length);
- }
- else if (i % config.cleanInterval == 0 && i > 0)
- {
- // Clean interval, skip this block
- }
- else
- {
- XorBlock(block, 0xFF);
- }
- }
- }
-
- public static void XorBlock(Span block, byte xor)
- {
- for (int i = 0; i < block.Length; i++)
- {
- block[i] ^= xor;
- }
- }
-
- private static CompressionAlgorithm DetectCompressionAlgorithm(byte[] chunk)
- {
- // Zlib header: first byte 0x78, second byte in {0x9C, 0xDA, 0x01, 0x5E, 0x9E, ...}
- if (chunk[0] == 0x78 && (chunk[1] & 0xF0) == 0xC0) // 0x78 0x9C, 0x78 0xDA, etc.
- {
- return CompressionAlgorithm.Zlib;
- }
-
- return CompressionAlgorithm.Deflate; // raw Deflate has no header
- }
-
- ///
- /// Compresses a single chunk using the specified algorithm.
- ///
- /// Number of compressed bytes written into .
- public static int CompressBlock(ReadOnlySpan input, CompressionAlgorithm algorithm, byte[] outputBuffer)
- {
- using MemoryStream ms = new(outputBuffer, true);
- if (algorithm == CompressionAlgorithm.Zlib)
- {
- using DeflaterOutputStream zlib = new(ms);
- zlib.Write(input);
- zlib.Finish();
- }
- else // Deflate (raw)
- {
- using DeflateStream deflate = new(ms, CompressionLevel.Optimal, true);
- deflate.Write(input);
- }
-
- return (int)ms.Position;
- }
-}
diff --git a/src/TelltaleToolKit/TelltaleToolKit.csproj b/src/TelltaleToolKit/TelltaleToolKit.csproj
index 0dd8c46..289231e 100644
--- a/src/TelltaleToolKit/TelltaleToolKit.csproj
+++ b/src/TelltaleToolKit/TelltaleToolKit.csproj
@@ -6,7 +6,7 @@
latest
13
TelltaleToolKit
- 0.2.1
+ 0.3.0
TelltaleToolKit
Ivan Panayotov
https://github.com/iMrShadow/TelltaleToolKit
@@ -30,15 +30,18 @@
+ PackagePath="contentFiles\any\any\ttk-data\game_profiles\%(RecursiveDir)%(Filename)%(Extension)"
+ CopyToOutputDirectory="PreserveNewest"/>
+ PackagePath="contentFiles\any\any\ttk-data\hashdb\%(RecursiveDir)%(Filename)%(Extension)"
+ CopyToOutputDirectory="PreserveNewest"/>
+ PackagePath="contentFiles\any\any\ttk-data\versiondb\%(RecursiveDir)%(Filename)%(Extension)"
+ CopyToOutputDirectory="PreserveNewest"/>
@@ -66,8 +69,4 @@
-
-
-
-
diff --git a/src/TelltaleToolKit/Toolkit.cs b/src/TelltaleToolKit/Toolkit.cs
index 5761f46..6671eda 100644
--- a/src/TelltaleToolKit/Toolkit.cs
+++ b/src/TelltaleToolKit/Toolkit.cs
@@ -4,7 +4,6 @@
using TelltaleToolKit.Logging;
using TelltaleToolKit.Reflection;
using TelltaleToolKit.Serialization;
-using TelltaleToolKit.Serialization.Binary;
using TelltaleToolKit.T3Types;
using TelltaleToolKit.TelltaleArchives;
using TelltaleToolKit.TelltaleArchives.Formats;
@@ -65,7 +64,7 @@ public static Toolkit Instance
"TelltaleToolkit must be initialized first. Call Toolkit.Initialize().");
///
- /// Gets the configuration supplied at time.
+ /// Gets the @params supplied at time.
///
public Configuration Config { get; }
@@ -110,7 +109,7 @@ public static Toolkit Instance
/// Must be called exactly once before using any other API.
///
///
- /// Optional configuration. When , defaults are used
+ /// Optional @params. When , defaults are used
/// (DataFolder = "ttk-data").
///
///
@@ -220,21 +219,21 @@ public void ResolveSymbols(IEnumerable symbols)
///
/// Deserializes a MetaStream object of type from a file,
- /// also returning the that was embedded in the stream.
+ /// also returning the that was embedded in the stream.
///
/// The target type. It must have a parameterless constructor.
/// Path to the MetaStream file on disk.
///
- /// A tuple containing the deserialized object and the configuration embedded in the stream,
+ /// A tuple containing the deserialized object and the @params embedded in the stream,
/// or (null, null) if reading or parsing fails.
///
- public (T? Asset, MetaStreamConfiguration? MetaConfig) DeserializeWithConfig(string fileName)
+ public (T? Asset, MetaStreamParams? MetaConfig) DeserializeWithConfig(string fileName, Workspace? workspace = null)
where T : class, new()
{
try
{
using FileStream stream = File.OpenRead(fileName);
- var result = DeserializeInternal(stream);
+ var result = DeserializeInternal(stream, workspace);
if (result.Asset == null)
{
@@ -261,7 +260,7 @@ public void ResolveSymbols(IEnumerable symbols)
///
/// Deserializes a MetaStream object of type from a stream,
- /// also returning the that was embedded in the stream.
+ /// also returning the that was embedded in the stream.
///
/// The target type. It must have a parameterless constructor.
///
@@ -269,13 +268,13 @@ public void ResolveSymbols(IEnumerable symbols)
/// Not disposed by this method.
///
///
- /// A tuple containing the deserialized object and the configuration embedded in the stream,
+ /// A tuple containing the deserialized object and the @params embedded in the stream,
/// or (null, null) if parsing fails.
///
- public (T? Asset, MetaStreamConfiguration? MetaConfig) DeserializeWithConfig(Stream stream)
+ public (T? Asset, MetaStreamParams? MetaConfig) DeserializeWithConfig(Stream stream, Workspace? workspace = null)
where T : class, new()
{
- var result = DeserializeInternal(stream);
+ var result = DeserializeInternal(stream, workspace);
if (result.Asset == null)
{
@@ -287,19 +286,19 @@ public void ResolveSymbols(IEnumerable symbols)
///
/// Deserializes a MetaStream object of type from a file,
- /// discarding the embedded configuration.
+ /// discarding the embedded @params.
///
/// The target type. It must have a parameterless constructor.
/// Path to the MetaStream file on disk.
///
/// The deserialized object, or if reading or parsing fails.
///
- public T? Deserialize(string fileName) where T : class, new()
+ public T? Deserialize(string fileName, Workspace? workspace = null) where T : class, new()
{
try
{
using FileStream stream = File.OpenRead(fileName);
- var result = DeserializeInternal(stream);
+ var result = DeserializeInternal(stream, workspace);
if (result.Asset == null)
{
@@ -322,7 +321,7 @@ public void ResolveSymbols(IEnumerable symbols)
///
/// Deserializes a MetaStream object of type from a stream,
- /// discarding the embedded configuration.
+ /// discarding the embedded @params.
///
/// The target type. It must have a parameterless constructor.
///
@@ -332,9 +331,9 @@ public void ResolveSymbols(IEnumerable symbols)
///
/// The deserialized object, or if parsing fails.
///
- public T? Deserialize(Stream stream) where T : class, new()
+ public T? Deserialize(Stream stream, Workspace? workspace = null) where T : class, new()
{
- var result = DeserializeInternal(stream);
+ var result = DeserializeInternal(stream, workspace);
if (result.Asset == null)
{
@@ -356,9 +355,9 @@ public void ResolveSymbols(IEnumerable symbols)
/// The object to serialize.
/// Destination file path. The file is overwritten if it exists.
///
- /// The controlling format version, class list, and so on.
+ /// The controlling format version, class list, and so on.
///
- public void Serialize(T obj, string fileName, MetaStreamConfiguration config) where T : class, new()
+ public void Serialize(T obj, string fileName, MetaStreamParams config) where T : class, new()
{
using FileStream stream = File.Create(fileName);
SerializeInternal(obj, stream, config);
@@ -372,8 +371,8 @@ public void ResolveSymbols(IEnumerable symbols)
///
/// A writable stream. Not disposed by this method.
///
- /// The MetaStream configuration to embed in the output.
- public void Serialize(T obj, Stream stream, MetaStreamConfiguration config) where T : class, new()
+ /// The MetaStream @params to embed in the output.
+ public void Serialize(T obj, Stream stream, MetaStreamParams config) where T : class, new()
=> SerializeInternal(obj, stream, config);
// -------------------------------------------------------------------------
@@ -420,14 +419,14 @@ public Archive LoadArchive(string archivePath, string blowfishKey)
///
/// Path to the file to check.
///
- /// if the header matches a known ;
+ /// if the header matches a known ;
/// otherwise.
///
public bool IsMetaStreamFile(string fileName)
{
// Use a using block so the file handle is released immediately after the check.
using FileStream stream = File.OpenRead(fileName);
- return MetaStreamReader.IsValidMetaStream(stream);
+ return MetaStream.IsValidMetaStream(stream);
}
///
@@ -438,11 +437,11 @@ public bool IsMetaStreamFile(string fileName)
/// A readable, seekable stream. The stream position is restored after the check.
///
///
- /// if the header matches a known ;
+ /// if the header matches a known ;
/// otherwise.
///
public bool IsMetaStreamFile(Stream stream)
- => MetaStreamReader.IsValidMetaStream(stream);
+ => MetaStream.IsValidMetaStream(stream);
///
/// Returns if has an extension
@@ -468,7 +467,7 @@ public static bool IsMetaFile(string fileName)
///
/// Returns if the stream's header matches a known
- /// value.
+ /// value.
///
///
/// A readable, seekable stream. The stream position is restored after the check.
@@ -484,8 +483,8 @@ public static bool IsMetaFile(Stream stream)
if (stream.Read(header, 0, 4) < 4)
return false;
- var version = (MetaStreamVersion)BitConverter.ToInt32(header);
- return Enum.IsDefined(typeof(MetaStreamVersion), version);
+ var version = (MetaStreamMagic)BitConverter.ToInt32(header);
+ return Enum.IsDefined(typeof(MetaStreamMagic), version);
}
finally
{
@@ -605,49 +604,55 @@ private void LoadMetaClassDescriptions()
ClassRegistry.Register(metaClasses);
}
- private (T? Asset, MetaStreamConfiguration? Config) DeserializeInternal(Stream stream)
+ private (T? Asset, MetaStreamParams? Config) DeserializeInternal(Stream stream, Workspace? workspace = null)
where T : class, new()
{
try
{
var result = new T();
- var reader = new MetaStreamReader(stream);
+ var metaStream = MetaStream.OpenRead(stream, workspace);
- reader.PreSerialize(ref result);
- reader.Serialize(ref result);
+ metaStream.Serialize(ref result);
- MetaStreamConfiguration config = reader.Configuration;
+ MetaStreamParams config = metaStream.Params;
- Config.Logger.LogInfo(
- $"[Toolkit] Successfully read {typeof(T).Name}, version: {config.Version}, symbols: {config.SerializedSymbols.Count}");
+ if (metaStream.IsEndOfStream())
+ {
+ Config.Logger.LogInfo(
+ $"[Toolkit] Successfully read {typeof(T).Name}, stream version: {config.StreamVersion}.");
+ }
+ else
+ {
+ Config.Logger.LogWarning($"[Toolkit] Unexpected end of stream while reading {typeof(T).Name}.");
+ }
if (Config.AutoResolveSymbols)
{
ResolveSymbols(config.SerializedSymbols);
}
+ metaStream.Close();
return (result, config);
}
catch (Exception ex)
{
// Internal method only logs debug details, doesn't warn/error
- Config.Logger.LogError($"[Toolkit] Deserialization internal error: {ex.Message}");
+ Config.Logger.LogError($"[Toolkit] Deserialization internal error: {ex.Message} {ex.StackTrace}");
return (null, null);
}
}
- private void SerializeInternal(T obj, Stream stream, MetaStreamConfiguration config)
+ private void SerializeInternal(T obj, Stream stream, MetaStreamParams config)
where T : class, new()
{
- var writer = new MetaStreamWriter(stream, config);
+ var metaStream = MetaStream.OpenWrite(stream, config);
T refObj = obj;
- writer.PreSerialize(ref refObj);
- writer.Serialize(ref refObj);
- writer.Save();
+ metaStream.Serialize(ref refObj);
+ metaStream.Close();
}
///
- /// Configuration supplied to .
+ /// Params supplied to .
///
public class Configuration
{
@@ -660,7 +665,7 @@ public class Configuration
///
/// Gets or sets a value indicating whether symbols found in the
- /// table are automatically
+ /// table are automatically
/// resolved against the global hash database after every successful
/// call.
///
diff --git a/src/TelltaleToolKit/Utility/Blowfish/Blowfish.cs b/src/TelltaleToolKit/Utility/Blowfish/Blowfish.cs
index 679ed21..444a727 100644
--- a/src/TelltaleToolKit/Utility/Blowfish/Blowfish.cs
+++ b/src/TelltaleToolKit/Utility/Blowfish/Blowfish.cs
@@ -4,31 +4,32 @@
namespace TelltaleToolKit.Utility.Blowfish;
///
-/// Class that provides 'Blowfish' encryption.
-/// There are two versions:
-/// - The regular Blowfish encryption which is used in older games until ttarch version 6.
-/// - The modified Blowfish encryption which is used in all new games.
+/// Class that provides 'Blowfish' encryption.
+/// There are two versions:
+/// - The regular Blowfish encryption, which is used in older games until ttarch version 6.
+/// - The modified Blowfish encryption, which is used in all new games.
///
public class Blowfish
{
private const int N = 16;
+ private readonly bool _isModifiedBlowfishEncryption;
private readonly uint[] _p;
private readonly uint[,] _s;
- private readonly bool _isModifiedBlowfishEncryption;
///
- /// Constructs and initializes a blowfish instance with the supplied key.
+ /// Constructs and initializes a blowfish instance with the supplied key.
///
/// The key to cipher with.
- ///
- public Blowfish(Span key, int version)
+ /// If true, uses the modified Blowfish encryption (version >= 7).
+ public Blowfish(Span key, bool isModified)
{
- _isModifiedBlowfishEncryption = version >= 7;
+ _isModifiedBlowfishEncryption = isModified;
_p = BlowfishConstants.P.Clone() as uint[] ?? throw new InvalidOperationException();
_s = BlowfishConstants.S.Clone() as uint[,] ?? throw new InvalidOperationException();
+ // Key expansion
for (short i = 0, j = 0; i < N + 2; ++i)
{
uint data = 0x00000000;
@@ -46,9 +47,7 @@ public Blowfish(Span key, int version)
_s[0, 118] = BinaryPrimitives.ReverseEndianness(_s[0, 118]);
}
- uint dataLeft = 0x00000000;
- uint dataRight = 0x00000000;
-
+ uint dataLeft = 0, dataRight = 0;
for (short i = 0; i < N + 2; i += 2)
{
Encipher(ref dataLeft, ref dataRight);
@@ -57,41 +56,57 @@ public Blowfish(Span key, int version)
}
for (short i = 0; i < 4; ++i)
+ for (short j = 0; j < 256; j += 2)
{
- for (short j = 0; j < 256; j += 2)
- {
- Encipher(ref dataLeft, ref dataRight);
-
- _s[i, j] = dataLeft;
- _s[i, j + 1] = dataRight;
- }
+ Encipher(ref dataLeft, ref dataRight);
+ _s[i, j] = dataLeft;
+ _s[i, j + 1] = dataRight;
}
}
///
- /// Constructs and initializes a blowfish instance with the supplied key.
+ /// Constructs and initializes a blowfish instance with the supplied key.
+ ///
+ /// The key to cipher with.
+ /// Version number; version >= 7 enables modified encryption.
+ public Blowfish(Span key, int version)
+ : this(key, version >= 7)
+ {
+ }
+
+ ///
+ /// Constructs and initializes a blowfish instance with the supplied key.
///
/// The key to cipher with.
- ///
+ /// If true, uses the modified Blowfish encryption (version >= 7).
+ public Blowfish(string key, bool isModified)
+ : this(Encoding.GetEncoding("ISO-8859-1").GetBytes(key), isModified)
+ {
+ }
+
+ ///
+ /// Constructs and initializes a blowfish instance with the supplied key.
+ ///
+ /// The key to cipher with.
+ /// Version number; version >= 7 enables modified encryption.
public Blowfish(string key, int version)
- : this(Encoding.GetEncoding("ISO-8859-1").GetBytes(key), version)
+ : this(key, version >= 7)
{
}
///
- ///
///
///
///
private uint F(uint x)
{
- var d = (ushort)(x & 0x00FF);
+ ushort d = (ushort)(x & 0x00FF);
x >>= 8;
- var c = (ushort)(x & 0x00FF);
+ ushort c = (ushort)(x & 0x00FF);
x >>= 8;
- var b = (ushort)(x & 0x00FF);
+ ushort b = (ushort)(x & 0x00FF);
x >>= 8;
- var a = (ushort)(x & 0x00FF);
+ ushort a = (ushort)(x & 0x00FF);
// y = ((S[0][a] + S[1][b]) ^ S[2][c]) + S[3][d];
uint y = _s[0, a] + _s[1, b];
y ^= _s[2, c];
@@ -101,7 +116,7 @@ private uint F(uint x)
}
///
- /// Encrypts a byte array in place.
+ /// Encrypts a byte array in place.
///
/// The array to encrypt.
/// The amount to encrypt.
@@ -109,10 +124,10 @@ public void Encipher(Span data, int length)
{
int fullBlocks = length - length % 8; // Ensure only complete blocks are processed
- for (var i = 0; i < fullBlocks; i += 8)
+ for (int i = 0; i < fullBlocks; i += 8)
{
- var xl = (uint)((data[i + 3] << 24) | (data[i + 2] << 16) | (data[i + 1] << 8) | data[i]);
- var xr = (uint)((data[i + 7] << 24) | (data[i + 6] << 16) | (data[i + 5] << 8) | data[i + 4]);
+ uint xl = (uint)((data[i + 3] << 24) | (data[i + 2] << 16) | (data[i + 1] << 8) | data[i]);
+ uint xr = (uint)((data[i + 7] << 24) | (data[i + 6] << 16) | (data[i + 5] << 8) | data[i + 4]);
if (_isModifiedBlowfishEncryption)
{
@@ -123,19 +138,19 @@ public void Encipher(Span data, int length)
Encipher(ref xl, ref xr);
}
- data[i] = (byte)(xl >> 24);
- data[i + 1] = (byte)(xl >> 16);
- data[i + 2] = (byte)(xl >> 8);
- data[i + 3] = (byte)(xl);
- data[i + 4] = (byte)(xr >> 24);
- data[i + 5] = (byte)(xr >> 16);
- data[i + 6] = (byte)(xr >> 8);
- data[i + 7] = (byte)(xr);
+ data[i + 3] = (byte)(xl >> 24);
+ data[i + 2] = (byte)(xl >> 16);
+ data[i + 1] = (byte)(xl >> 8);
+ data[i + 0] = (byte)xl;
+ data[i + 7] = (byte)(xr >> 24);
+ data[i + 6] = (byte)(xr >> 16);
+ data[i + 5] = (byte)(xr >> 8);
+ data[i + 4] = (byte)xr;
}
}
///
- /// Encrypts 8 bytes of data (1 block)
+ /// Encrypts 8 bytes of data (1 block)
///
/// The left part of the 8 bytes.
/// The right part of the 8 bytes.
@@ -162,7 +177,7 @@ private void Encipher(ref uint xl, ref uint xr)
}
///
- /// Encrypts 8 bytes of data (1 block)
+ /// Encrypts 8 bytes of data (1 block)
///
/// The left part of the 8 bytes.
/// The right part of the 8 bytes.
@@ -199,7 +214,7 @@ private void Encipher7(ref uint xl, ref uint xr)
}
///
- /// Encrypts a string.
+ /// Encrypts a string.
///
/// The string to encrypt
/// Encrypted string
@@ -212,7 +227,7 @@ public string Encipher(string data)
}
///
- /// Decrypts a byte array in place.
+ /// Decrypts a byte array in place.
///
/// The array to decrypt.
/// The amount to decrypt.
@@ -222,10 +237,10 @@ public void Decipher(Span data, int length)
// Ensure only complete blocks are processed
int fullBlocks = length - length % 8;
- for (var i = 0; i < fullBlocks; i += 8)
+ for (int i = 0; i < fullBlocks; i += 8)
{
- var xl = (uint)((data[i + 3] << 24) | (data[i + 2] << 16) | (data[i + 1] << 8) | data[i]);
- var xr = (uint)((data[i + 7] << 24) | (data[i + 6] << 16) | (data[i + 5] << 8) | data[i + 4]);
+ uint xl = (uint)((data[i + 3] << 24) | (data[i + 2] << 16) | (data[i + 1] << 8) | data[i]);
+ uint xr = (uint)((data[i + 7] << 24) | (data[i + 6] << 16) | (data[i + 5] << 8) | data[i + 4]);
Decipher(ref xl, ref xr);
data[i + 3] = (byte)(xl >> 24);
@@ -240,7 +255,7 @@ public void Decipher(Span data, int length)
}
///
- /// Decrypts 8 bytes of data (1 block)
+ /// Decrypts 8 bytes of data (1 block)
///
/// The left part of the 8 bytes.
/// The right part of the 8 bytes.
@@ -283,4 +298,4 @@ private void Decipher(ref uint xl, ref uint xr)
(xl, xr) = (xlCopy, xrCopy);
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/TelltaleArchives/Caching/ChunkCache.cs b/src/TelltaleToolKit/Utility/Caching/ChunkCache.cs
similarity index 95%
rename from src/TelltaleToolKit/TelltaleArchives/Caching/ChunkCache.cs
rename to src/TelltaleToolKit/Utility/Caching/ChunkCache.cs
index ffb1546..e1ed7e4 100644
--- a/src/TelltaleToolKit/TelltaleArchives/Caching/ChunkCache.cs
+++ b/src/TelltaleToolKit/Utility/Caching/ChunkCache.cs
@@ -1,4 +1,4 @@
-namespace TelltaleToolKit.TelltaleArchives.Caching;
+namespace TelltaleToolKit.Utility.Caching;
///
/// Defines the contract for a chunk-level cache.
diff --git a/src/TelltaleToolKit/TelltaleArchives/Caching/LruChunkCache.cs b/src/TelltaleToolKit/Utility/Caching/LruChunkCache.cs
similarity index 91%
rename from src/TelltaleToolKit/TelltaleArchives/Caching/LruChunkCache.cs
rename to src/TelltaleToolKit/Utility/Caching/LruChunkCache.cs
index 5785a1d..b3c077e 100644
--- a/src/TelltaleToolKit/TelltaleArchives/Caching/LruChunkCache.cs
+++ b/src/TelltaleToolKit/Utility/Caching/LruChunkCache.cs
@@ -1,11 +1,11 @@
-namespace TelltaleToolKit.TelltaleArchives.Caching;
+namespace TelltaleToolKit.Utility.Caching;
///
/// Cache strategy that retains the N most-recently-used decoded chunks.
///
///
///
-/// Memory overhead: up to N × ChunkSize bytes (e.g., N=8, ChunkSize=64 KiB → 512 KiB).
+/// Memory overhead: up to N * WindowSize bytes (e.g., N=8, WindowSize=64 KiB -> 512 KiB).
///
///
/// Not thread-safe. Each concurrent extraction path should use its own instance.
@@ -25,7 +25,7 @@ public sealed class LruChunkCache : IChunkCache
///
/// Maximum number of decoded chunks to retain simultaneously.
/// Typical values: 4 (conservative), 8 (balanced), 16 (aggressive).
- /// Must be ≥ 1.
+ /// Must be >= 1.
///
public LruChunkCache(int capacity = 8) =>
// ArgumentOutOfRangeException.ThrowIfLessThan(capacity, 1);
diff --git a/src/TelltaleToolKit/TelltaleArchives/Caching/SinglePageCache.cs b/src/TelltaleToolKit/Utility/Caching/SinglePageCache.cs
similarity index 95%
rename from src/TelltaleToolKit/TelltaleArchives/Caching/SinglePageCache.cs
rename to src/TelltaleToolKit/Utility/Caching/SinglePageCache.cs
index 9c6a0e8..9d9e72f 100644
--- a/src/TelltaleToolKit/TelltaleArchives/Caching/SinglePageCache.cs
+++ b/src/TelltaleToolKit/Utility/Caching/SinglePageCache.cs
@@ -1,4 +1,4 @@
-namespace TelltaleToolKit.TelltaleArchives.Caching;
+namespace TelltaleToolKit.Utility.Caching;
///
/// Cache strategy that keeps exactly one decoded chunk in memory at a time.
diff --git a/src/TelltaleToolKit/TelltaleArchives/IO/ChunkDecoder.cs b/src/TelltaleToolKit/Utility/Compression/ChunkDecoder.cs
similarity index 74%
rename from src/TelltaleToolKit/TelltaleArchives/IO/ChunkDecoder.cs
rename to src/TelltaleToolKit/Utility/Compression/ChunkDecoder.cs
index c2fce78..10bd278 100644
--- a/src/TelltaleToolKit/TelltaleArchives/IO/ChunkDecoder.cs
+++ b/src/TelltaleToolKit/Utility/Compression/ChunkDecoder.cs
@@ -1,43 +1,22 @@
using System.IO.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
-using TelltaleToolKit.TelltaleArchives.Caching;
-using TelltaleToolKit.TelltaleArchives.Formats;
-using TelltaleToolKit.Utility.Blowfish;
+using TelltaleToolKit.Serialization;
+using TelltaleToolKit.TelltaleArchives;
-namespace TelltaleToolKit.TelltaleArchives.IO;
+namespace TelltaleToolKit.Utility.Compression;
///
-/// Provides chunk-level decrypt + decompress logic shared by both
-/// and extraction paths.
+/// Provides chunk-level decrypt + decompress logic.
///
///
///
/// All methods are thread-safe with respect to the archive stream as long as each
-/// concurrent caller holds its own instance and does not
+/// concurrent caller holds its own instance and does not
/// share a reference.
///
///
internal static class ChunkDecoder
{
- ///
- /// Decrypts (if encrypted) and decompresses a raw compressed chunk read from disk.
- /// Returns the decoded byte array.
- ///
- public static byte[] DecryptAndDecompress(byte[] compressed, uint expectedSize, ArchiveInfo info)
- {
- // Decrypt in place when the archive uses chunk-level encryption.
- if (info.Flags.IsEncrypted())
- {
- Blowfish bf = new(info.BlowfishKey, 7);
- bf.Decipher(compressed.AsSpan(), compressed.Length);
- }
-
- ArchiveFlags flags = info.Flags;
- byte[] arr = DecompressBlock(compressed, (int)expectedSize, ref flags);
- info.Flags = flags;
- return arr;
- }
-
// -------------------------------------------------------------------------
// Build a block-offset table for the range [blockStart, blockEnd]
// -------------------------------------------------------------------------
@@ -80,7 +59,7 @@ public static byte[] DecompressBlock(byte[] compressedData, int expectedSize, re
// According to TTG Tools, version 8 archives use zlib (presumably for the files themselves)
if (flags.HasFlag(ArchiveFlags.IsRawDeflateCompressed) && flags.HasFlag(ArchiveFlags.IsZlibCompressed))
{
- // CompressionAlgorithm compressionAlgorithm = DetectCompressionAlgorithm(compressedData);
+ // Compression compressionAlgorithm = DetectCompressionAlgorithm(compressedData);
try
{
@@ -135,4 +114,37 @@ byte[] Decompress(Func streamFactory)
return outputStream.ToArray();
}
}
+
+ private static Compression DetectCompressionAlgorithm(byte[] chunk)
+ {
+ // Zlib header: first byte 0x78, second byte in {0x9C, 0xDA, 0x01, 0x5E, 0x9E, ...}
+ if (chunk[0] == 0x78 && (chunk[1] & 0xF0) == 0xC0) // 0x78 0x9C, 0x78 0xDA, etc.
+ {
+ return Compression.Zlib;
+ }
+
+ return Compression.Deflate; // raw Deflate has no header
+ }
+
+ ///
+ /// Compresses a single chunk using the specified algorithm.
+ ///
+ /// Number of compressed bytes written into .
+ public static int CompressBlock(ReadOnlySpan input, Compression algorithm, byte[] outputBuffer)
+ {
+ using MemoryStream ms = new(outputBuffer, true);
+ if (algorithm == Compression.Zlib)
+ {
+ using DeflaterOutputStream zlib = new(ms);
+ zlib.Write(input);
+ zlib.Finish();
+ }
+ else // Deflate (raw)
+ {
+ using DeflateStream deflate = new(ms, CompressionLevel.Optimal, true);
+ deflate.Write(input);
+ }
+
+ return (int)ms.Position;
+ }
}
diff --git a/src/TelltaleToolKit/Utility/Compression/Compression.cs b/src/TelltaleToolKit/Utility/Compression/Compression.cs
new file mode 100644
index 0000000..4b1b516
--- /dev/null
+++ b/src/TelltaleToolKit/Utility/Compression/Compression.cs
@@ -0,0 +1,19 @@
+namespace TelltaleToolKit.Utility.Compression;
+
+/// Compression algorithm to use when writing archive chunks.
+public enum Compression
+{
+ /// No compression.
+ None,
+
+ /// Raw DEFLATE (no zlib header). Used by most Telltale games.
+ Deflate,
+
+ /// Zlib-wrapped DEFLATE. Used by some CSI and earlier titles.
+ Zlib,
+
+ ///
+ /// Oodle compression.
+ ///
+ Oodle
+}
diff --git a/src/TelltaleToolKit/Utility/Lua/LuaVersion.cs b/src/TelltaleToolKit/Utility/Lua/LuaVersion.cs
index 3f5e03e..acc2931 100644
--- a/src/TelltaleToolKit/Utility/Lua/LuaVersion.cs
+++ b/src/TelltaleToolKit/Utility/Lua/LuaVersion.cs
@@ -1,4 +1,4 @@
-namespace TelltaleToolKit.GamesDatabase;
+namespace TelltaleToolKit.Utility.Lua;
///
/// Specifies the supported Lua scripting engine versions for Telltale games.
@@ -19,4 +19,4 @@ public enum LuaVersion
/// Lua version 5.2.3.
///
Lua523,
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/Utility/Lua/LuaVersionExtensions.cs b/src/TelltaleToolKit/Utility/Lua/LuaVersionExtensions.cs
index 2804b2c..0078c77 100644
--- a/src/TelltaleToolKit/Utility/Lua/LuaVersionExtensions.cs
+++ b/src/TelltaleToolKit/Utility/Lua/LuaVersionExtensions.cs
@@ -1,4 +1,4 @@
-namespace TelltaleToolKit.GamesDatabase;
+namespace TelltaleToolKit.Utility.Lua;
///
/// Extension methods for to handle conversions between string representations and enum values.
@@ -38,4 +38,4 @@ public static string ToVersionString(this LuaVersion version)
_ => throw new ArgumentOutOfRangeException(nameof(version), version, "Unsupported LuaVersion enum value.")
};
}
-}
\ No newline at end of file
+}
diff --git a/src/TelltaleToolKit/Workspace.cs b/src/TelltaleToolKit/Workspace.cs
index bf196e0..a398237 100644
--- a/src/TelltaleToolKit/Workspace.cs
+++ b/src/TelltaleToolKit/Workspace.cs
@@ -3,7 +3,7 @@
using TelltaleToolKit.GamesDatabase;
using TelltaleToolKit.Reflection;
using TelltaleToolKit.Resource;
-using TelltaleToolKit.Serialization.Binary;
+using TelltaleToolKit.Serialization;
using TelltaleToolKit.T3Types;
using TelltaleToolKit.TelltaleArchives;
using TelltaleToolKit.Utility.Blowfish;
@@ -62,10 +62,12 @@ internal Workspace(string name, Toolkit toolkit, GameProfile profile)
Profile = profile ?? throw new ArgumentNullException(nameof(profile));
LocalHashDatabase = new HashDatabase.HashDatabase();
- DefaultMetaStreamConfig = new MetaStreamConfiguration
+ bool isModifiedBlowfish = Profile.TtarchVersion >= TTArchiveVersion.Seven;
+ Blowfish = new Blowfish(profile.BlowfishKey, isModifiedBlowfish);
+
+ DefaultMetaStreamConfig = new MetaStreamParams
{
- AreSymbolsHashed = profile.AreSymbolsHashed,
- Version = profile.MetaStreamVersion,
+ StreamVersion = profile.StreamVersion,
Workspace = this,
CanModifySerializedClassesList = true
};
@@ -81,15 +83,18 @@ internal Workspace(string name, Toolkit toolkit, GameProfile profile)
public string GameName => Profile.Name;
///
- /// Gets the default for this workspace,
+ /// Gets the default for this workspace,
/// pre-populated from the game profile.
/// Passed to serialization helpers unless overridden by the caller.
///
- public MetaStreamConfiguration DefaultMetaStreamConfig { get; }
+ public MetaStreamParams DefaultMetaStreamConfig { get; }
/// Gets the blowfish key used to decrypt archives and resource descriptions for this game.
public string BlowfishKey => Profile.BlowfishKey;
+ /// Gets the blowfish instance used to decrypt archives and resource descriptions for this game.
+ public Blowfish Blowfish { get; }
+
///
/// Gets the workspace-local hash database used for symbol resolution.
/// Entries here complement (and are checked after) the global database in .
@@ -534,31 +539,31 @@ public void ClearAllContexts()
///
/// Loads and deserializes an asset of type by filename, also
- /// returning the embedded in the stream.
+ /// returning the embedded in the stream.
///
/// The target typ. It must have a parameterless constructor.
/// The bare filename of the asset.
///
- /// A tuple containing the deserialized asset and its configuration,
+ /// A tuple containing the deserialized asset and its @params,
/// or (null, null) if not found.
///
- public (T? Asset, MetaStreamConfiguration? MetaConfig) LoadAssetWithConfig(string name) where T : class, new()
+ public (T? Asset, MetaStreamParams? MetaConfig) LoadAssetWithConfig(string name) where T : class, new()
=> LoadAssetWithConfig(Crc64.Compute(name));
///
/// Loads and deserializes an asset of type by CRC-64, also
- /// returning the embedded in the stream.
+ /// returning the embedded in the stream.
///
/// The target type. It must have a parameterless constructor.
/// Pre-computed CRC-64 of the asset filename.
///
- /// A tuple containing the deserialized asset and its configuration,
+ /// A tuple containing the deserialized asset and its @params,
/// or (null, null) if not found.
///
- public (T? Asset, MetaStreamConfiguration? MetaConfig) LoadAssetWithConfig(ulong crc64) where T : class, new()
+ public (T? Asset, MetaStreamParams? MetaConfig) LoadAssetWithConfig(ulong crc64) where T : class, new()
=> LoadAssetInternal(crc64);
- private (T? Value, MetaStreamConfiguration? MetaConfig) LoadAssetInternal(ulong crc64) where T : class, new()
+ private (T? Value, MetaStreamParams? MetaConfig) LoadAssetInternal(ulong crc64) where T : class, new()
{
for (int i = _contexts.Count - 1; i >= 0; i--)
{
@@ -573,11 +578,12 @@ public void ClearAllContexts()
? $"[Workspace:{Name}] Found asset '{assetName}' in context: {_contexts[i].Name}"
: $"[Workspace:{Name}] Found asset 0x{crc64:X16} in context: {_contexts[i].Name}");
- var asset = _toolkit.DeserializeWithConfig(stream);
+ var asset = _toolkit.DeserializeWithConfig(stream, this);
if (asset.Asset == null)
{
- _toolkit.Config.Logger.LogWarning($"[Workspace:{Name}] Asset {(assetName ?? $"0x{crc64:X16}")} found but failed to load in context: {_contexts[i].Name}");
+ _toolkit.Config.Logger.LogWarning(
+ $"[Workspace:{Name}] Asset {(assetName ?? $"0x{crc64:X16}")} found but failed to load in context: {_contexts[i].Name}");
return (null, null);
}
@@ -614,7 +620,7 @@ public void ClearAllContexts()
///
/// Serializes to a file on disk using a custom
- /// .
+ /// .
///
///
/// Use this overload when you need to override the MetaStream version or class-list
@@ -627,8 +633,8 @@ public void ClearAllContexts()
/// Destination file path on disk.
/// The file is created or overwritten.
///
- /// The MetaStream configuration to embed in the output.
- public void ExportAsset(T obj, string fileName, MetaStreamConfiguration config) where T : class, new()
+ /// The MetaStream @params to embed in the output.
+ public void ExportAsset(T obj, string fileName, MetaStreamParams config) where T : class, new()
=> _toolkit.Serialize(obj, fileName, config);
///
@@ -692,7 +698,7 @@ public bool ContainsFile(string name)
=> ContainsFile(Crc64.Compute(name));
///
- /// Finds and returns the for the given CRC-64,
+ /// Finds and returns the for the given CRC-64,
/// searching enabled contexts from the highest priority to lowest.
///
/// The entry, or if not found.
@@ -741,7 +747,7 @@ public bool ContainsFile(string name)
/// specific file.
///
///
- /// A flat sequence of objects from all enabled contexts.
+ /// A flat sequence of objects from all enabled contexts.
///
public IEnumerable GetAllEntries()
=> _contexts.Where(c => c.IsEnabled).SelectMany(c => c.GetAllEntries());
@@ -770,7 +776,8 @@ public IEnumerable GetAllEntries()
var fileEntry = FindFileEntry(crc64);
if (fileEntry?.Name != null)
{
- _toolkit.Config.Logger.LogInfo($"[Workspace:{Name}] Resolved 0x{crc64:X16} as '{fileEntry.Name}' from archive");
+ _toolkit.Config.Logger.LogInfo(
+ $"[Workspace:{Name}] Resolved 0x{crc64:X16} as '{fileEntry.Name}' from archive");
return fileEntry.Name;
}
diff --git a/tools/TTKDatabaseGenerator/Program.cs b/tools/TTKDatabaseGenerator/Program.cs
index a99ec05..3aa2d28 100644
--- a/tools/TTKDatabaseGenerator/Program.cs
+++ b/tools/TTKDatabaseGenerator/Program.cs
@@ -8,9 +8,11 @@
using TelltaleToolKit;
using TelltaleToolKit.GamesDatabase;
using TelltaleToolKit.Reflection;
+using TelltaleToolKit.Serialization;
using TelltaleToolKit.Serialization.Binary;
using TelltaleToolKit.TelltaleArchives;
using TelltaleToolKit.Utility.Blowfish;
+using TelltaleToolKit.Utility.Lua;
namespace DatabaseGenerator;
@@ -135,7 +137,7 @@ static async Task Main(string[] args)
var unregisteredTypes = new ConcurrentDictionary<(ulong, uint), byte>();
var areSymbolsHashed = true;
- var msv = MetaStreamVersion.Mtre;
+ uint msv = 3;
TTArchiveVersion ttarchVersion = 0;
var processed = 0;
@@ -162,9 +164,8 @@ await Parallel.ForEachAsync(archivePaths, parallelOptions, async (filePath, _) =
if (Toolkit.IsMetaFile(archive.GetAllEntries().First().Name))
{
- MetaStreamConfiguration config = new MetaStreamReader(firstFile).Configuration;
- areSymbolsHashed = config.AreSymbolsHashed;
- msv = config.Version;
+ MetaStreamParams config = new BinaryMetaStreamReader(firstFile, null).Params;
+ msv = config.StreamVersion;
}
}
@@ -175,22 +176,25 @@ await Parallel.ForEachAsync(archivePaths, parallelOptions, async (filePath, _) =
if (!Toolkit.IsMetaFile(file))
continue;
- MetaStreamConfiguration config = new MetaStreamReader(file).Configuration;
+ MetaStreamParams config = new BinaryMetaStreamReader(file, null).Params;
- foreach (MetaClass desc in config.SerializedClasses)
+ foreach (MetaClass desc in config.GetRegisteredClasses())
serializedMetaClasses.TryAdd(desc, 0);
- foreach ((ulong, uint) t in config.UnregisteredTypes)
+ var localUnregisteredTypes = config.GetUnregisteredTypes();
+ var localUnregisteredClasses = config.GetUnregisteredClasses();
+
+ foreach ((ulong, uint) t in localUnregisteredTypes)
unregisteredTypes.TryAdd(t, 0);
- foreach ((MetaClassType, uint crc32) c in config.UnregisteredClasses)
+ foreach ((MetaClassType, uint crc32) c in localUnregisteredClasses)
unrecognizedMeta.TryAdd(c, 0);
- if (config.UnregisteredTypes.Count > 0)
- PrintWarning($"{entry.Name} -> {config.UnregisteredTypes.Count} unknown types", logWriter);
+ if (localUnregisteredTypes.Count > 0)
+ PrintWarning($"{entry.Name} -> {localUnregisteredTypes.Count} unknown types", logWriter);
- if (config.UnregisteredClasses.Count > 0)
- PrintWarning($"{entry.Name} -> {config.UnregisteredClasses.Count} unknown classes",
+ if (localUnregisteredClasses.Count > 0)
+ PrintWarning($"{entry.Name} -> {localUnregisteredClasses.Count} unknown classes",
logWriter);
}
}
@@ -282,12 +286,11 @@ await Parallel.ForEachAsync(archivePaths, parallelOptions, async (filePath, _) =
{
Id = slug,
Name = slug,
- AreSymbolsHashed = areSymbolsHashed,
BlowfishKey = (key.ToString() ?? customKey) ?? "UNKNOWN",
EnableOodleCompression = false,
IsTtarch2 = selectedFilter == "*ttarch2",
LuaVersion = LuaVersion.Lua512,
- MetaStreamVersion = msv,
+ StreamVersion = msv,
TtarchVersion = ttarchVersion,
};