-
Notifications
You must be signed in to change notification settings - Fork 4
Add Matroschka processing. #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mnadareski
merged 54 commits into
SabreTools:main
from
HeroponRikiBestest:matroschka-changes
Sep 20, 2025
Merged
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
56d8fc2
Made changes
HeroponRikiBestest f651740
Temporary hack to not rely on models without significantly changing c…
HeroponRikiBestest dee16a1
small fixes
HeroponRikiBestest c3738a9
Store matroschka section as PE extension
HeroponRikiBestest 24a24f8
Move extractor out of deserializer, remove weird hack
HeroponRikiBestest cce09c9
Potential GA fix
HeroponRikiBestest 254f26a
More potential GA fixes.
HeroponRikiBestest e4cc377
I have no idea why GA hits that error but not me
HeroponRikiBestest 7bd1900
Giving up on GA for now
HeroponRikiBestest 33112f9
fix locking issues
HeroponRikiBestest c2d3a07
Fix GA building; thank you sabre
HeroponRikiBestest 0502c6e
Minor improvements all around
HeroponRikiBestest 7999699
Catch some braced single-line if statements
HeroponRikiBestest 6ce4954
Use var more
HeroponRikiBestest 6d01e14
Seperate deserializer into helper methods
HeroponRikiBestest 67b6ed1
Make file path reading much more sane
HeroponRikiBestest 2379b03
Removed MatroschkaHeaderType enum
HeroponRikiBestest 4cf8cbe
Removed MatroschkaGapType enum, further simplify matgaphelper.
HeroponRikiBestest 9eef4a6
Remove MatroschkaHasUnknown enum, further simplify Unknown value read…
HeroponRikiBestest adeea2a
Cache initial offset.
HeroponRikiBestest efd7534
Remove TryCreate patterns.
HeroponRikiBestest 8abfbae
Rename matroschka variable to package
HeroponRikiBestest 4e13235
Newline after object
HeroponRikiBestest 6461862
Rename to obj
HeroponRikiBestest fdd20a6
Remove a few unecessary TODOs
HeroponRikiBestest d5c118f
Seperate hexstring byte read to another line.
HeroponRikiBestest c77984f
Fix documentation.
HeroponRikiBestest 3adf499
More private static
HeroponRikiBestest d11eba1
Changed data.position setting to seeking. NTS: check if this broke an…
HeroponRikiBestest da8dbf1
rename entries to obj
HeroponRikiBestest e4795b4
MatroschkaEntry to var
HeroponRikiBestest 13086f4
Newline
HeroponRikiBestest 8254129
Alphabetical
HeroponRikiBestest 9e8d504
More alphabetical.
HeroponRikiBestest 2d4ea64
section to package
HeroponRikiBestest 561b2de
Move private variables.
HeroponRikiBestest eecd9d1
Move to extension properties.
HeroponRikiBestest e581cc6
Revert section finding.
HeroponRikiBestest 45c0cd0
Remove uneeded _dataSource lock and access.
HeroponRikiBestest 1bcf0c5
combine lines and make var
HeroponRikiBestest fa1de8b
Combine two null checks.
HeroponRikiBestest c02c080
Packaged files, some past commits I think I forgot to push.
HeroponRikiBestest e999ac9
Missed two
HeroponRikiBestest 00d35bd
newline
HeroponRikiBestest ae30fdf
space
HeroponRikiBestest 8e54c08
newline
HeroponRikiBestest 7d5046a
Combine two lines
HeroponRikiBestest b445af1
Removed comment
HeroponRikiBestest e0b76fc
Return false explicitly
HeroponRikiBestest f6ad633
Change hashing string implementation
HeroponRikiBestest afde07e
Fix order.
HeroponRikiBestest 53f0313
Use offset reading instead of filedataarray
HeroponRikiBestest 581a96c
Change file reading around a little preemptively for BOS
HeroponRikiBestest 51b06fd
Merge branch 'main' into matroschka-changes
mnadareski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
149 changes: 149 additions & 0 deletions
149
SabreTools.Serialization/Deserializers/SecuROMMatroschkaPackage.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| using System.IO; | ||
| using System.Text; | ||
| using SabreTools.IO.Extensions; | ||
| using SabreTools.Matching; | ||
| using SabreTools.Models.SecuROM; | ||
| using static SabreTools.Models.SecuROM.Constants; | ||
|
|
||
| namespace SabreTools.Serialization.Deserializers | ||
| { | ||
| public class SecuROMMatroschkaPackage : BaseBinaryDeserializer<MatroshkaPackage> | ||
| { | ||
| /// <inheritdoc/> | ||
| public override MatroshkaPackage? Deserialize(Stream? data) | ||
| { | ||
| // If the data is invalid | ||
| if (data == null || !data.CanRead) | ||
| return null; | ||
|
|
||
| try | ||
| { | ||
| // Cache the initial offset | ||
| long initialOffset = data.Position; | ||
|
|
||
| // TODO: Unify matroschka spelling. They spell it matroschka in all official stuff, as far as has been observed. Will double check. | ||
| // Try to parse the header | ||
| var package = ParsePreEntryHeader(data); | ||
| if (package == null) | ||
| return null; | ||
|
|
||
| var entries = ParseEntries(data, package); | ||
| if (entries == null) | ||
| return null; | ||
|
|
||
| package.Entries = entries; | ||
| return package; | ||
| } | ||
| catch | ||
| { | ||
| // Ignore the actual error | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| private static MatroshkaPackage? ParsePreEntryHeader(Stream data) | ||
| { | ||
| var obj = new MatroshkaPackage(); | ||
|
|
||
| byte[] magic = data.ReadBytes(4); | ||
mnadareski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| obj.Signature = Encoding.ASCII.GetString(magic); | ||
| if (obj.Signature != MatroshkaMagicString) | ||
| return null; | ||
|
|
||
| obj.EntryCount = data.ReadUInt32LittleEndian(); | ||
| if (obj.EntryCount == 0) | ||
| return null; | ||
|
|
||
| // Check if "matrosch" section is a longer header one or not based on whether the next uint is 0 or 1. Anything | ||
| // else will just already be starting the filename string, which is never going to start with this. | ||
| // Previously thought that the longer header was correlated with RC, but at least one executable | ||
| // (NecroVisioN.exe from the GamersGate patch NecroVisioN_Patch1.2_GG.exe) isn't RC and still has it. | ||
| long tempPosition = data.Position; | ||
| uint tempValue = data.ReadUInt32LittleEndian(); | ||
| data.Seek(tempPosition, SeekOrigin.Begin); | ||
|
|
||
| if (tempValue < 2) // Only little-endian 0 or 1 have been observed for long sections. | ||
| { | ||
| obj.UnknownRCValue1 = data.ReadUInt32LittleEndian(); | ||
| obj.UnknownRCValue2 = data.ReadUInt32LittleEndian(); | ||
| obj.UnknownRCValue3 = data.ReadUInt32LittleEndian(); | ||
|
|
||
| // Exact byte count has to be used because non-RC executables have all 0x00 here. | ||
| var keyHexBytes = data.ReadBytes(32); | ||
| obj.KeyHexString = Encoding.ASCII.GetString(keyHexBytes); | ||
| if (!data.ReadBytes(4).EqualsExactly([0x00, 0x00, 0x00, 0x00])) | ||
| return null; | ||
| } | ||
| return obj; | ||
| } | ||
|
|
||
| private static MatroshkaEntry[]? ParseEntries(Stream data, MatroshkaPackage package) | ||
| { | ||
|
|
||
| // If we have any entries | ||
| var obj = new MatroshkaEntry[package.EntryCount]; | ||
|
|
||
| int matGapType = 0; | ||
| bool? matHasUnknown = null; | ||
|
|
||
| // Read entries | ||
| for (int i = 0; i < obj.Length; i++) | ||
| { | ||
| var entry = new MatroshkaEntry(); | ||
| // Determine if file path size is 256 or 512 bytes | ||
| if (matGapType == 0) | ||
| matGapType = GapHelper(data); | ||
|
|
||
| // TODO: Spaces/non-ASCII have not yet been observed. Still, probably safer to store as byte array? | ||
mnadareski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // TODO: Read as string and trim once models is bumped. For now, this needs to be trimmed by anything reading it. | ||
| entry.Path = data.ReadBytes((int)matGapType); | ||
|
|
||
| // Entry type isn't currently validated as it's always predictable anyways, nor necessary to know. | ||
| entry.EntryType = (MatroshkaEntryType)data.ReadUInt32LittleEndian(); | ||
| entry.Size = data.ReadUInt32LittleEndian(); | ||
| entry.Offset = data.ReadUInt32LittleEndian(); | ||
|
|
||
| // Check for unknown 4-byte 0x00 value. Not correlated with 256 vs 512-byte gaps. | ||
| if (matHasUnknown == null) | ||
| matHasUnknown = UnknownHelper(data, entry); | ||
|
|
||
| if (matHasUnknown == true) // If already known, read or don't read the unknown value. | ||
| entry.Unknown = data.ReadUInt32LittleEndian(); // TODO: Validate it's zero? | ||
|
|
||
| entry.ModifiedTime = data.ReadUInt64LittleEndian(); | ||
| entry.CreatedTime = data.ReadUInt64LittleEndian(); | ||
| entry.AccessedTime = data.ReadUInt64LittleEndian(); | ||
| entry.MD5 = data.ReadBytes(16); | ||
|
|
||
| obj[i] = entry; | ||
| } | ||
|
|
||
| return obj; | ||
| } | ||
|
|
||
| private static int GapHelper(Stream data) | ||
| { | ||
| var tempPosition = data.Position; | ||
| data.Seek(data.Position + 256, SeekOrigin.Begin); | ||
| var tempValue = data.ReadUInt32LittleEndian(); | ||
| data.Seek(tempPosition, SeekOrigin.Begin); | ||
| if (tempValue <= 0) // Gap is 512 bytes. Actually just == 0, but ST prefers ranges. | ||
| return 512; | ||
|
|
||
| // Otherwise, gap is 256 bytes. | ||
| return 256; | ||
| } | ||
|
|
||
| private static bool UnknownHelper(Stream data, MatroshkaEntry entry) | ||
| { | ||
| var tempPosition = data.Position; | ||
| var tempValue = data.ReadUInt32LittleEndian(); | ||
| data.Seek(tempPosition, SeekOrigin.Begin); | ||
| if (tempValue > 0) // Entry does not have the Unknown value. | ||
| return false; | ||
|
|
||
| // Entry does have the unknown value. | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.