diff --git a/Editor/LCProjectPatcherEditorWindow.cs b/Editor/LCProjectPatcherEditorWindow.cs
index dc0cd13..48b1acb 100644
--- a/Editor/LCProjectPatcherEditorWindow.cs
+++ b/Editor/LCProjectPatcherEditorWindow.cs
@@ -33,6 +33,8 @@ private void CreateGUI() {
ModuleUtility.CreateDirectory(settings.GetAssetStorePath(fullPath: true));
ModuleUtility.CreateDirectory(settings.GetModsPath(fullPath: true));
ModuleUtility.CreateDirectory(settings.GetToolsPath(fullPath: true));
+ ModuleUtility.CreateDirectory(settings.GetResourcesPath(fullPath: true));
+ ModuleUtility.CreateDirectory(settings.GetStreamingAssetsPath(fullPath: true));
AssetDatabase.Refresh();
diff --git a/Editor/LCProjectPatcherSteps.cs b/Editor/LCProjectPatcherSteps.cs
index fccd26b..fb22220 100644
--- a/Editor/LCProjectPatcherSteps.cs
+++ b/Editor/LCProjectPatcherSteps.cs
@@ -94,7 +94,8 @@ private static async UniTask RunStep() {
public static async UniTask RunPreProcessGroup(LCPatcherSettings settings) {
AssetDatabase.StartAssetEditing();
- try {
+ try
+ {
InitialProjectModule.MoveNativeFiles(settings);
// asset ripper
@@ -180,6 +181,9 @@ public static async UniTask RunPostProcessGroup(LCPatcherSettings settings) {
InputActionsModule.FixAll(settings);
+ // shaders
+ AssetsToolsModule.GetShaders(settings);
+
BepInExModule.CopyTemplateFolder();
await BepInExModule.Install(settings);
BepInExModule.InstallMonoMod(settings);
diff --git a/Editor/Libs/AssetsTools.NET.meta b/Editor/Libs/AssetsTools.NET.meta
new file mode 100644
index 0000000..366b9a4
--- /dev/null
+++ b/Editor/Libs/AssetsTools.NET.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5dba7e3d438c4d3419193a5209e37a4a
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Libs/AssetsTools.NET/Editor.meta b/Editor/Libs/AssetsTools.NET/Editor.meta
new file mode 100644
index 0000000..15226a1
--- /dev/null
+++ b/Editor/Libs/AssetsTools.NET/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 27568949337f84c4bb7aa68079fea5ec
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.dll b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.dll
new file mode 100644
index 0000000..6611363
Binary files /dev/null and b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.dll differ
diff --git a/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.dll.meta b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.dll.meta
new file mode 100644
index 0000000..0fd468a
--- /dev/null
+++ b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.dll.meta
@@ -0,0 +1,33 @@
+fileFormatVersion: 2
+guid: 3b60f7138db84f5468eb6f75d0df8a50
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 1
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 1
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.pdb b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.pdb
new file mode 100644
index 0000000..9cdf3ae
Binary files /dev/null and b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.pdb differ
diff --git a/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.pdb.meta b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.pdb.meta
new file mode 100644
index 0000000..229b806
--- /dev/null
+++ b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.pdb.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 5782fc03631e9b3438a10d16d5a8fb92
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.xml b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.xml
new file mode 100644
index 0000000..bb2dbd7
--- /dev/null
+++ b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.xml
@@ -0,0 +1,1852 @@
+
+
+
+ AssetsTools.NET
+
+
+
+
+ No flags.
+
+
+
+
+ Use the editor version of the TPK instead of the the player version. Use this if you are
+ generating new assets for an editor project.
+
+
+
+
+ If the file doesn't have a type tree, decide whether to skip calling
+ AssetsManager.MonoTempGenerator to add the MonoBehaviour fields to the end of the base
+ MonoBehaviour field or not.
+
+
+
+
+ If the file is using a type tree, force it to use the loaded class database instead.
+
+
+
+
+ A wrapper around an with information such as the path to the file
+ (used for handling dependencies) and the bundle it belongs to.
+
+
+
+
+ The full path to the file. This path can be fake if it is not from disk.
+
+
+
+
+ The name of the file. This is the file name part of the path.
+
+
+
+
+ The base .
+
+
+
+
+ The bundle this file is a part of, if there is one.
+
+
+
+
+ The stream the assets file uses.
+
+
+
+
+ Load an from a stream with a path.
+ Use the version of this method to skip the path argument.
+ If a file with that name is already loaded, it will be returned instead.
+
+ The stream to read from.
+ The path to set on the .
+ Load all dependencies immediately?
+ The parent bundle, if one exists.
+ The loaded .
+
+
+
+ Load an from a stream.
+ Assigns the 's path from the stream's file path.
+ If a file with that name is already loaded, it will be returned instead.
+
+ The stream to read from.
+ Load all dependencies immediately?
+ The loaded .
+
+
+
+ Load an from a path.
+ If a file with that name is already loaded, it will be returned instead.
+
+ The path of the file to read from.
+ Load all dependencies immediately?
+ The loaded .
+
+
+
+ Unload an by path.
+
+ The path of the to unload.
+ True if the file was found and closed, and false if it wasn't found.
+
+
+
+ Unload an .
+
+ The to unload.
+ True if the file was found and closed, and false if it wasn't found.
+
+
+
+ Unload all s.
+
+ Clear the cache? Recommended if you plan on reopening files later.
+ True if there are files that can be cleared, and false if no files are loaded.
+
+
+
+ Load a from a stream with a path.
+ Use the version of this method to skip the path argument.
+ If the bundle is large, you may want to set to false
+ so you can manually decompress to file.
+
+ The stream to read from.
+ The path to set on the .
+ Unpack the bundle if it's compressed?
+ The loaded .
+
+
+
+ Load a from a stream.
+ Assigns the 's path from the stream's file path.
+ If the bundle is large, you may want to set to false
+ so you can manually decompress to file.
+
+ The stream to read from.
+ Unpack the bundle if it's compressed?
+ The loaded .
+
+
+
+ Load a from a path.
+ If the bundle is large, you may want to set to false
+ so you can manually decompress to file.
+
+ The path of the file to read from.
+ Unpack the bundle if it's compressed?
+ The loaded .
+
+
+
+ Unload an by path.
+
+ The path of the to unload.
+ True if the file was found and closed, and false if it wasn't found.
+
+
+
+ Unload an .
+
+ The to unload.
+ True if the file was found and closed, and false if it wasn't found.
+
+
+
+ Unload all s.
+
+ True if there are files that can be cleared, and false if no files are loaded.
+
+
+
+ Load an from a by index.
+
+ The bundle to load from.
+ The index of the file in the bundle to load from.
+ Load all dependencies immediately?
+ The loaded .
+
+
+
+ Load an from a by name.
+
+ The bundle to load from.
+ The name of the file in the bundle to load from.
+ Load all dependencies immediately?
+ The loaded .
+
+
+
+ List of loaded assets files for this bundle.
+
+
+ This list does not contain every assets file for the bundle,
+ instead only the ones that have been loaded so far.
+
+
+
+
+ Hash of this entry.
+
+
+
+
+ List of blocks in this bundle.
+ Do not modify this array, it's needed to read the existing file correctly.
+
+
+
+
+ List of file infos in this bundle.
+ You can add new infos or make changes to existing ones and they will be
+ updated on write.
+
+
+
+
+ Decompressed size of this block.
+
+
+
+
+ Compressed size of this block. If uncompressed, this is the same as DecompressedSize.
+
+
+
+
+ Flags of this block.
+ First 6 bits (0x3f mask): Compression mode. 0 for uncompressed, 1 for LZMA, 2/3 for LZ4/LZ4HC.
+ 0x40: Streamed if unset (will be read in blocks)
+
+
+
+
+ Offset from bundle's data start (header.GetFileDataOffset()).
+
+
+
+
+ Decompressed size of this entry.
+
+
+
+
+ Flags of this entry.
+ 0x01: Entry is a directory. Unknown usage.
+ 0x02: Entry is deleted. Unknown usage.
+ 0x04: Entry is serialized file. Assets files should enable this, and other files like .resS or .resource(s) should disable this.
+
+
+
+
+ Name of this entry.
+
+
+
+
+ Replacer which can be set by the user.
+ You can use or
+ for convenience.
+
+
+
+
+ Replacer type such as modified or removed.
+
+
+
+
+ Is the replacer non-null and does the replacer has a preview?
+
+
+
+
+ Sets the bytes used when the AssetBundleFile is written.
+
+
+
+
+ Sets the assets file to use when the AssetBundleFile is written.
+
+
+
+
+ Set the asset to be removed when the AssetBundleFile is written.
+
+
+
+
+ Creates a new directory info.
+
+ Name of the file.
+ Is the file serialized (i.e. is it an assets file)?
+ The new directory info
+
+
+
+ Bundle header. Contains bundle engine version.
+
+
+
+
+ List of compression blocks and file info (file names, address in file, etc.)
+
+
+
+
+ Reader for data block of bundle
+
+
+
+
+ Is data reader reading compressed data? Only LZMA bundles set this to true.
+
+
+
+
+ Closes the reader.
+
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+ Where in the stream to start writing. Use -1 to start writing at the current stream position.
+
+
+
+ Unpack and write the uncompressed with the provided writer.
+ You must write to a new file or stream when calling this method.
+
+ The writer to use.
+
+
+
+ Pack and write the compressed with the provided writer.
+ You must write to a new file or stream when calling this method.
+
+ The writer to use.
+ The compression type to use. LZ4 compresses worse but faster, LZMA compresses better but slower.
+ Put block and directory list at end? This skips creating temporary files, but is not officially used.
+ Optional callback for compression progress.
+
+
+
+ Returns the main compression type the bundle uses (the first uncompressed block type).
+
+ The compression type
+
+
+
+ Is the file at the index an ?
+ Note: this checks by reading the first bit of the file instead of reading the directory flag.
+
+ Index of the file in the directory info list.
+ True if the file at the index is an .
+
+
+
+ Returns the index of the file in the directory list with the given name.
+
+ The name to search for.
+ The index of the file in the directory list or -1 if no file is found.
+
+
+
+ Returns the name of the file at the index in the directory list.
+
+ The index to look at.
+ The name of the file in the directory list or null if the index is out of bounds.
+
+
+
+ Returns the file range of a file.
+ Use instead of to read data.
+
+ The index to look at.
+ The offset in the data stream, or -1 if the index is out of bounds.
+ The length of the file, or 0 if the index is out of bounds.
+
+
+
+ Returns a list of file names in the bundle.
+
+ The file names in the bundle.
+
+
+
+ Size of entire file.
+
+
+
+
+ Size of the compressed data. This is the same as DecompressedSize if not compressed.
+
+
+
+
+ Size of the decompressed data.
+
+
+
+
+ Flags of this bundle.
+ First 6 bits (0x3f mask): Compression mode. 0 for uncompressed, 1 for LZMA, 2/3 for LZ4/LZ4HC.
+ 0x40: Has directory info. Should always be true for 5.2+.
+ 0x80: Block and directory info is at end. The Unity editor does not usually use this.
+
+
+
+
+ Magic appearing at the beginning of all bundles. Possible options are:
+ UnityFS, UnityWeb, UnityRaw, UnityArchive
+
+
+
+
+ Version of this file.
+
+
+
+
+ Generation version string. For Unity 5 bundles this is always "5.x.x"
+
+
+
+
+ Engine version. This is the specific version string being used. For example, "2019.4.2f1"
+
+
+
+
+ Header for bundles with a UnityFS Signature.
+
+
+
+
+ Path ID of the asset.
+
+
+
+
+ Address of the asset's data from the header's DataOffset.
+ Use for the real file position.
+ If the asset has a replacer, this field is ignored.
+
+
+
+
+ Byte size of the asset data. If the asset has a replacer, this field is ignored.
+
+
+
+
+ Before version 16 this is the type ID of the asset. In version 16 and later this is the
+ index into the type tree list. In versions 15 and below, this is the same as TypeId
+ except in MonoBehaviours where this acts similar to ScriptTypeIndex (negative).
+ You should use TypeId for the type ID in either version.
+
+
+
+
+ Old Type ID of the asset (officially called class ID). This field is only used in versions
+ 15 and below and is the same as TypeId, except when TypeId is negative, in which case
+ the old type ID will be a MonoBehaviour (0x72) and TypeId will be the same as TypeIdOrIndex.
+ You should use TypeId for the type ID in either version.
+
+
+
+
+ Script type index of the asset. Assets other than MonoBehaviours will have 0xffff for
+ this field. This value is stored in the type tree starting at version 17. Note this is
+ not the same as taking
+
+
+
+
+ Marks if the type in the type tree has been stripped (?)
+
+
+
+
+ The type ID of the asset. This field works in both versions. This field is only for
+ convenience; modifying the type ID in the type tree in later versions will not update the
+ ID here, and modifying this field will not update the type ID when saved.
+
+
+
+
+ Replacer which can be set by the user.
+ You can use or
+ for convenience.
+
+
+
+
+ Replacer type such as modified or removed.
+
+
+
+
+ Is the replacer non-null and does the replacer has a preview?
+
+
+
+
+ Get the Type ID of the asset.
+
+
+
+
+ Get the Type ID of the asset.
+
+
+
+
+ Get the Type ID of the asset.
+
+
+
+
+ Address of the asset's data from the start of the file.
+
+
+
+
+ Address of the asset's data from the start of the file.
+
+
+
+
+ Address of the asset's data from the start of the file.
+
+
+
+
+ Sets the bytes used when the AssetsFile is written.
+
+
+
+
+ Sets the bytes to the base field's data used when the AssetsFile is written.
+
+
+
+
+ Set the asset to be removed when the AssetsFile is written.
+
+
+
+
+ Creates a new asset info. If the type has not appeared in this file yet, pass
+ to pull new type info from.
+
+ The assets file this info will belong to.
+ The path ID to use.
+ The type ID to use.
+ The class database to use if the type does not appear in the assets file yet.
+ Read from the editor version of this type if available?
+ The new asset info, or null if the type can't be found in the type tree or class database.
+
+
+
+ Creates a new asset info. If the type has not appeared in this file yet, pass
+ to pull new type info from. If the asset is
+ a MonoBehaviour, add the type manually to
+ and if version 16 or later, set to the script type index
+ or if ealier than version 16, set the negative type id.
+
+ The assets file this info will belong to.
+ The path ID to use.
+ The type ID to use.
+ The script type index to use.
+ The class database to use if the type does not appear in the assets file yet.
+ Read from the editor version of this type if available?
+ The new asset info, or null if the type can't be found in the type tree or class database.
+
+
+
+ File path of the pointer. If empty or null, FileId will be used.
+
+
+
+
+ File ID of the pointer.
+
+
+
+
+ Path ID of the pointer.
+
+
+
+
+ Assets file header.
+
+
+
+
+ Contains metadata about the file (TypeTree, engine version, dependencies, etc.)
+
+
+
+
+ The that reads the file.
+
+
+
+
+ Closes the reader.
+
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Read the with the provided stream.
+
+ The stream to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+ Where in the stream to start writing. Use -1 to start writing at the current stream position.
+
+
+
+ Get the script index for an .
+ Always use this method instead of ScriptTypeIndex, as it handles all versions.
+
+ The file info to check.
+ The script index of the asset.
+
+
+
+ Check if a file at a path is an or not.
+
+ The file path to read from and check.
+ True if the file is an assets file, otherwise false.
+
+
+
+ Check if a file at a position in a stream is an or not.
+
+ The reader to use.
+ The offset to start at (this value cannot be -1).
+ The length of the file. You can use reader.BaseStream.Length for this.
+
+
+
+
+ Get an from a path ID.
+
+ The path ID to search for.
+ An info for that path ID.
+
+
+
+ Generate a dictionary lookup for assets instead of a brute force search.
+ Takes a little bit more memory but results in quicker lookups.
+
+
+
+
+ Get all assets of a specific type ID.
+
+ The type ID to search for.
+ A list of infos for that type ID.
+
+
+
+ Get all assets of a specific type ID.
+
+ The type ID to search for.
+ A list of infos for that type ID.
+
+
+
+ Get all assets of a specific type ID and script index. The script index of an asset can be
+ found from or .
+
+ The type ID to search for.
+ The script index to search for.
+ A list of infos for that type ID and script index.
+
+
+
+ Get all assets of a specific type ID and script index. The script index of an asset can be
+ found from or .
+
+ The type ID to search for.
+ The script index to search for.
+ A list of infos for that type ID and script index.
+
+
+
+ A list of all asset infos in this file.
+
+
+
+
+ Unknown.
+
+
+
+
+ GUID for dependencies used in editor. Otherwise this is 0.
+
+
+
+
+ Dependency type.
+
+
+
+
+ Real path name to the other file.
+
+
+
+
+ Original path name listed in the assets file (if it was changed).
+ You shouldn't modify this.
+
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ Size of the metadata block (not including this header).
+
+
+
+
+ Size of the entire file.
+
+
+
+
+ Version of this file. This only affects the structure of the serialized file, not asset data.
+
+
+
+
+ Offset to the data of the first asset.
+
+
+
+
+ File endianness. Little endian is false and big endian is true.
+
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ Engine version this file uses.
+
+
+
+
+ Target platform this file uses.
+
+
+
+
+ Marks whether the type info contains type tree data.
+
+
+
+
+ List of type tree types.
+
+
+
+
+ List of asset infos. Do not add or remove from this list directly, instead use the
+ or methods.
+
+
+
+
+ List of script type pointers. This list should match up with ScriptTypeIndex in the type
+ tree types list.
+
+
+
+
+ List of externals (references to other files).
+
+
+
+
+ List of reference types.
+
+
+
+
+ Unknown.
+
+
+
+
+ Read the with the provided reader and file header.
+
+ The reader to use.
+ The header to use.
+
+
+
+ Read the with the provided reader and format version.
+
+ The reader to use.
+ The version of the file.
+
+
+
+ Write the with the provided reader and format version.
+
+ The writer to use.
+ The version of the file.
+
+
+
+ Get an from a path ID.
+
+ The path ID to search for.
+ An info for that path ID.
+
+
+
+ Adds an to the info list.
+
+ The info to add
+
+
+
+ Removes an from the info list.
+
+
+ It is suggested to set to
+ if you want to keep the info in the list but save without it.
+
+ The info to add
+
+
+
+ Generate a dictionary lookup for assets instead of a brute force search.
+ Takes a little bit more memory but results in quicker lookups.
+
+
+
+
+ Get all assets of a specific type ID.
+
+ The type ID to search for.
+ A list of infos for that type ID.
+
+
+
+ Get all assets of a specific type ID and script index. The script index of an asset can be
+ found from or .
+
+ The type ID to search for.
+ The script index to search for.
+ A list of infos for that type ID and script index.
+
+
+
+ Get all assets of a specific type ID.
+
+ The type ID to search for.
+ A list of infos for that type ID.
+
+
+
+ Get all assets of a specific type ID and script index. The script index of an asset can be
+ found from or .
+
+ The type ID to search for.
+ The script index to search for.
+ A list of infos for that type ID and script index.
+
+
+
+ Get the type tree type by type ID.
+
+ The type ID to search for.
+ The type tree type with this ID.
+
+
+
+ Get the type tree type by type ID and script index. The script index of an asset can be
+ found from or .
+ For games before 5.5, is ignored since this data is read
+ from the negative value of . In 5.5 and later, MonoBehaviours are always
+ 0x72, so is used instead.
+
+ The type ID to search for.
+ The script index to search for.
+ The type tree type with this ID and script index.
+
+
+
+ Get the type tree type index by type ID and script index. The script index of an asset can be
+ found from or .
+ For games before 5.5, is ignored since this data is read
+ from the negative value of . In 5.5 and later, MonoBehaviours are always
+ 0x72, so is used instead.
+
+ The type ID to search for.
+ The script index to search for.
+ The type tree type index with this ID and script index, or -1 if not found.
+
+
+
+ Get the type tree type by script index. The script index of an asset can be
+ found from or .
+
+ The script index to search for.
+ The type tree type with this script index.
+
+
+
+ Get the type tree type by name.
+
+ The type name to search for.
+ The type tree type with this name.
+
+
+
+ Get the type tree ref type by script index.
+
+ The script index to search for.
+ The type tree ref type with this script index.
+
+
+
+ Read the with the provided reader, used in
+ reading .
+
+ The reader to use.
+
+
+
+ Read the with the provided reader, used in
+ reading .
+
+ The reader to use.
+
+
+
+ Write the with the provided writer, used in
+ writing .
+
+ The writer to use.
+
+
+
+ Write the with the provided writer, used in
+ writing .
+
+ The writer to use.
+
+
+
+ Version of the node.
+
+
+
+
+ Level of the node (0 for root, 1 for child, etc.)
+
+
+
+
+ Information about whether the node is an array, registry, etc.
+
+
+
+
+ Offset of the type string in the string table.
+
+
+
+
+ Offset of the name string in the string table.
+
+
+
+
+ Byte size of the field's type (for example, int is 4).
+ If the field isn't a value type, then this value is a sum of all children sizes.
+ If the size is variable, this is set to -1.
+
+
+
+
+ Index in the type tree. This should always be the same as the index in the array.
+
+
+
+
+ 0x4000 if aligned.
+
+
+
+
+ Unknown.
+
+
+
+
+ Read the with the provided reader and format version.
+
+ The reader to use.
+ The version of the file.
+
+
+
+ Write the with the provided writer and format version.
+
+ The writer to use.
+ The version of the file.
+
+
+
+ Get the type name from the string table (from ).
+
+ The string table to use.
+
+ The common string table to use, if the builtin one is outdated.
+ See .
+
+ The node type name.
+
+
+
+ Get the name name from the string table (from ).
+
+ The string table to use.
+
+ The common string table to use, if the builtin one is outdated.
+ See .
+
+ The node name.
+
+
+
+ Type tree node is an array.
+
+
+
+
+ Type tree node is a ref type. For example, "managedRefArrayItem" would be an
+ array item that is a reference to an object in the registry.
+
+
+
+
+ Type tree node is a registry. Should just be "ManagedReferencesRegistry references".
+
+
+
+
+ Type tree node is an array of ref types. This occurs if the SerializeReference was
+ added to a list or array instead of just a single field. This is not applied to the
+ Array child of the field, just the field itself.
+
+
+
+
+ ID for this type.
+
+
+
+
+ Marks whether the type is stripped or not. Stripped types do not have any fields.
+
+
+
+
+ Script index for this type. Only used in MonoBehaviours, and MonoBehaviours of the same
+ script have the same index.
+
+
+
+
+ Hash of the script's fields. Two different scripts with the same fields can have the same hash.
+
+
+
+
+ Hash of the type's fields.
+
+
+
+
+ Nodes for this type. This list will be empty if the type is stripped.
+
+
+
+
+ String table bytes for this type.
+
+
+
+
+ Is the type a reference type?
+
+
+
+
+ Type dependencies for this type. Used by MonoBehaviours referencing ref types. Only used
+ when IsRefType is false.
+
+
+
+
+ Type reference information. Only used when IsRefType is true.
+
+
+
+
+ Read the with the provided reader and format version.
+
+ The reader to use.
+ The version of the file.
+ Is type tree enabled for this file?
+ Is this type part of the ref type list?
+
+
+
+ Write the with the provided writer and format version.
+
+ The writer to use.
+ The version of the file.
+ Is type tree enabled for this file?
+
+
+
+ The string table used for commonly occuring strings in type trees.
+
+
+
+
+ Name of the field.
+
+
+
+
+ Type name of the field.
+
+
+
+
+ Type of the field (as an enum).
+
+
+
+
+ Is the field an array?
+
+
+
+
+ Is the field aligned? This aligns four bytes after all children have been read/written.
+
+
+
+
+ Does the field have value? (i.e. is the field a numeric / string / array type?)
+
+
+
+
+ Children of the field.
+
+
+
+
+ Read the template field from a type tree type.
+
+ The type tree type to read from.
+
+
+
+ Read the template field from a class database type.
+
+ The class database file to read from.
+ The class database type to read.
+ Read from the editor version of this type if available?
+
+
+
+ Deserialize an asset into a value field.
+
+ The reader to use.
+ The ref type manager to use, if reading a MonoBehaviour using a ref type.
+ The deserialized base field.
+
+
+
+ Deserialize an asset into a value field.
+
+ The reader to use.
+ The position to start reading from.
+ The ref type manager to use, if reading a MonoBehaviour using a ref type.
+ The deserialized value field.
+
+
+
+ Deserialize a single field and its children.
+
+ The reader to use.
+ The empty base value field to use.
+ The ref type manager to use, if reading a MonoBehaviour using a ref type.
+ The deserialized base field.
+
+
+
+ Clone the field.
+
+ The cloned field.
+
+
+
+ Template field corresponding to this value field.
+
+
+
+
+ Value of this field.
+
+
+
+
+ Children of this field.
+
+
+
+
+ The field which indicates that a field that was accessed does not exist.
+
+
+
+
+ Read the from a value, template field, and children.
+
+ The value to use.
+ The template field to use.
+ The children to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ Write the with a new writer to a byte array.
+
+ Write in big endian?
+
+
+
+ Clear the ref type lookup dictionaries.
+
+
+
+
+ Load the lookup from the type tree ref types of a serialized file.
+
+ The metadata to load from.
+
+
+
+ Initialize a lookup for MonoBehaviours.
+
+ The metadata to load from.
+ The mono template generator to use.
+ The cache to use.
+
+
+
+ Gets the template field from a reference.
+
+ The type reference to use.
+ A template field for this reference.
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer and compression type.
+
+ The writer to use.
+ The compression method to use.
+
+
+
+ Find a class database type by type ID.
+
+ The type's type ID to search for.
+ The type of that type ID.
+
+
+
+ Find a class database type by type name.
+
+ The type's type name to search for.
+ The type of that type name.
+
+
+
+ Get a string from the string table.
+
+ The index of the string in the table.
+ The string at that index.
+
+
+
+ Read the with the provided reader.
+ Note only new CLDB files are supported. Original UABE cldb files are no longer supported.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ Get a string from the string table.
+
+ The index of the string in the table.
+ The string at that index.
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ Get either the release root node or the editor root node. If only release
+ or only editor is available, that one will be selected regardless of
+ , otherwise it will select editor or release.
+
+ Read from the editor version of this type if available?
+ The class database type root node.
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ None of the flags apply to this class
+
+
+
+
+ Is the class abstract?
+
+
+
+
+ Is the class sealed? Not necessarily accurate.
+
+
+
+
+ Does the class only appear in the editor?
+
+
+
+
+ Does the class only appear in game files? Not currently used.
+
+
+
+
+ Is the class stripped?
+
+
+
+
+ Not currently used
+
+
+
+
+ Does the class have an editor root node?
+
+
+
+
+ Does the class have a release root node?
+
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Read the at the given path.
+
+ The path to read from.
+
+
+
+ Write the with the provided writer and compression type.
+
+ The writer to use.
+ The compression type to use.
+
+
+
+ Write the at the given path and compression type.
+
+ The path to write to.
+ The compression type to use.
+
+
+
+ Make a class database for a version.
+
+ The version to make the class database for.
+ A class database for that version.
+
+
+
+ Make a class database for a version.
+
+ The version to make the class database for.
+ A class database for that version.
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ Get the latest version of a type before or at a version.
+
+ The version to get the type for.
+ The type at that version.
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ Get the length of the common string for a version.
+ Since the common string is only appended in new versions, never edited, only the
+ length of the string for each version needs to be stored rather than the string
+ in its entirety.
+
+
+ The length of the common string.
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ Read the with the provided reader and class ID.
+
+ The reader to use.
+ The class ID to assign.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ Read the with the provided reader.
+
+ The reader to use.
+
+
+
+ Write the with the provided writer.
+
+ The writer to use.
+
+
+
+ Write the content with provided writer.
+
+
+
+
+ Does the content has a preview stream? This will be true if the data
+ is readily available (i.e. buffer or stream) and false if the data
+ isn't readily available because it needs to be calculated (assets).
+
+
+
+
+ Returns the preview stream.
+
+
+
+
+ The replacer type such as modified or removed.
+
+
+
+ Safe LZ4 codec.
+
+
+
+ Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.)
+ Increasing memory usage improves compression ratio
+ Reduced memory usage can improve speed, due to cache effect
+ Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache
+
+
+
+
+ Decreasing this value will make the algorithm skip faster data segments considered "incompressible"
+ This may decrease compression ratio dramatically, but will be faster on incompressible data
+ Increasing this value will make the algorithm search more before declaring a segment "incompressible"
+ This could improve compression a bit, but will be slower on incompressible data
+ The default value (6) is recommended
+
+
+
+ Buffer length when Buffer.BlockCopy becomes faster than straight loop.
+ Please note that safe implementation REQUIRES it to be greater (not even equal) than 8.
+
+
+ Gets maximum the length of the output.
+ Length of the input.
+ Maximum number of bytes needed for compressed buffer.
+
+
+ Encodes the specified input.
+ The input.
+ The input offset.
+ Length of the input.
+ The output.
+ The output offset.
+ Length of the output.
+ Number of bytes written.
+
+
+ Encodes the specified input.
+ The input.
+ The input offset.
+ Length of the input.
+ Compressed buffer.
+
+
+ Encodes the specified input.
+ The input.
+ The input offset.
+ Length of the input.
+ The output.
+ The output offset.
+ Length of the output.
+ Number of bytes written.
+
+
+ Encodes the specified input.
+ The input.
+ The input offset.
+ Length of the input.
+ Compressed buffer.
+
+
+ Decodes the specified input.
+ The input.
+ The input offset.
+ Length of the input.
+ The output.
+ The output offset.
+ Length of the output.
+ Set it to true if output length is known.
+ Number of bytes written.
+
+
+ Decodes the specified input.
+ The input.
+ The input offset.
+ Length of the input.
+ Length of the output.
+ Decompressed buffer.
+
+
+ Decodes the specified input.
+ The input.
+ The input offset.
+ Length of the input.
+ The output.
+ The output offset.
+ Length of the output.
+ Set it to true if output length is known.
+ Number of bytes written.
+
+
+ Decodes the specified input.
+ The input.
+ The input offset.
+ Length of the input.
+ Length of the output.
+ Decompressed buffer.
+
+
+ Encodes the specified input using HC codec.
+ The input.
+ The input offset.
+ Length of the input.
+ The output.
+ The output offset.
+ Length of the output.
+ Number of bytes written. NOTE: when output buffer is too small it returns negative value.
+
+
+ Encodes the specified input using HC codec.
+ The input.
+ The input offset.
+ Length of the input.
+ Buffer with compressed data (NOTE: it can be bigger than input).
+
+
+ Encodes the specified input using HC codec.
+ The input.
+ The input offset.
+ Length of the input.
+ The output.
+ The output offset.
+ Length of the output.
+ Number of bytes written. NOTE: when output buffer is too small it returns negative value.
+
+
+ Encodes the specified input using HC codec.
+ The input.
+ The input offset.
+ Length of the input.
+ Buffer with compressed data (NOTE: it can be bigger than input).
+
+
+
+ The exception that is thrown when an error in input stream occurs during decoding.
+
+
+
+
+ The exception that is thrown when the value of an argument is outside the allowable range.
+
+
+
+
+ Callback progress.
+
+
+ input size. -1 if unknown.
+
+
+ output size. -1 if unknown.
+
+
+
+
+ Codes streams.
+
+
+ input Stream.
+
+
+ output Stream.
+
+
+ input Size. -1 if unknown.
+
+
+ output Size. -1 if unknown.
+
+
+ callback progress reference.
+
+
+ if input stream is not valid
+
+
+
+
+ Provides the fields that represent properties idenitifiers for compressing.
+
+
+
+
+ Specifies default property.
+
+
+
+
+ Specifies size of dictionary.
+
+
+
+
+ Specifies size of memory for PPM*.
+
+
+
+
+ Specifies order for PPM methods.
+
+
+
+
+ Specifies Block Size.
+
+
+
+
+
+
+
+ Specifies number of fast bytes for LZ*.
+
+
+
+
+ Specifies match finder. LZMA: "BT2", "BT4" or "BT4B".
+
+
+
+
+ Specifies the number of match finder cyckes.
+
+
+
+
+ Specifies number of passes.
+
+
+
+
+ Specifies number of algorithm.
+
+
+
+
+ Specifies the number of threads.
+
+
+
+
+ Specifies mode with end marker.
+
+
+
+
diff --git a/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.xml.meta b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.xml.meta
new file mode 100644
index 0000000..72e4c1b
--- /dev/null
+++ b/Editor/Libs/AssetsTools.NET/Editor/AssetsTools.NET.xml.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 04fca029668b34440ae4f0276957451e
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Libs/AssetsTools.NET/uncompressed.tpk b/Editor/Libs/AssetsTools.NET/uncompressed.tpk
new file mode 100644
index 0000000..b65458f
Binary files /dev/null and b/Editor/Libs/AssetsTools.NET/uncompressed.tpk differ
diff --git a/Editor/Libs/AssetsTools.NET/uncompressed.tpk.meta b/Editor/Libs/AssetsTools.NET/uncompressed.tpk.meta
new file mode 100644
index 0000000..42c6374
--- /dev/null
+++ b/Editor/Libs/AssetsTools.NET/uncompressed.tpk.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f0bcc169a354d9f41b2d45bcf4480bdc
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Modules/AssetBundleModule.cs b/Editor/Modules/AssetBundleModule.cs
new file mode 100644
index 0000000..88e7266
--- /dev/null
+++ b/Editor/Modules/AssetBundleModule.cs
@@ -0,0 +1,22 @@
+using System.IO;
+using UnityEditor;
+using UnityEngine;
+
+namespace Nomnom.LCProjectPatcher.Editor.Modules {
+ public static class AssetBundleModule {
+ public const string AssetBundleShaderPath = "Packages/com.nomnom.lc-project-patcher/Editor/Resources/Posterization/Dummy.shader";
+ public static void CreateShaderBundle(string shaderName) {
+ AssetBundleBuild assetBundleBuild = new();
+ assetBundleBuild.assetBundleName = shaderName;
+ assetBundleBuild.assetNames = new []{ AssetBundleShaderPath };
+
+ BuildAssetBundlesParameters buildParameters = new() {
+ outputPath = Application.temporaryCachePath,
+ options = BuildAssetBundleOptions.ForceRebuildAssetBundle,
+ bundleDefinitions = new []{ assetBundleBuild }
+ };
+
+ BuildPipeline.BuildAssetBundles(buildParameters);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/Modules/AssetBundleModule.cs.meta b/Editor/Modules/AssetBundleModule.cs.meta
new file mode 100644
index 0000000..63bdea0
--- /dev/null
+++ b/Editor/Modules/AssetBundleModule.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a33e496a1ed34b9cb1bf5da1226dbd1c
+timeCreated: 1708056559
\ No newline at end of file
diff --git a/Editor/Modules/AssetsToolsModule.cs b/Editor/Modules/AssetsToolsModule.cs
new file mode 100644
index 0000000..6c54468
--- /dev/null
+++ b/Editor/Modules/AssetsToolsModule.cs
@@ -0,0 +1,317 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using AssetsTools.NET;
+using AssetsTools.NET.Extra;
+using JetBrains.Annotations;
+using UnityEditor;
+using UnityEngine;
+
+// This may be redundant for AssetRipper, but I'm not familiar enough with AssetRipper's codebase to make it do this
+namespace Nomnom.LCProjectPatcher.Editor.Modules {
+ public static class AssetsToolsModule {
+ public static readonly string ShaderInjectionSettingsPath = "Assets/Resources/ShaderInjectionSettings.asset";
+ // TODO: Replace this so all custom shaders are automatically replaced, instead of needing a hardcoded list
+ public static readonly List ShadersToGrab = new() {
+ "Shader Graphs/PosterizationFilter",
+ "Shader Graphs/WaterShaderHDRP",
+ "Shader Graphs/BlobShader",
+ "Shader Graphs/HologramShader",
+ // "Hidden/VFX/FlyingBugs/System/Output Particle HDRP Lit Mesh" // disabled as-is because the VisualEffect is broken anyways
+ };
+
+ private static List _loadedAssetsFilePaths = new();
+
+ public static void GetShaders(LCPatcherSettings settings) {
+ AssetsManager assetsManager = new();
+
+ // clear loaded paths
+ _loadedAssetsFilePaths.Clear();
+
+ // Load Unity type tree file so we can actually use our game's asset files
+ var classPackagePath = Path.GetFullPath("Packages/com.nomnom.lc-project-patcher/Editor/Libs/AssetsTools.NET/uncompressed.tpk");
+ assetsManager.LoadClassPackage(classPackagePath);
+
+ // Get assets files and attempt to find our shaders
+ var assetsFileInstances = LoadAssetsFilesFromDataPath(ModuleUtility.GameDataPath, assetsManager);
+
+ // create dummy shader bundle. not sure if there's a way to create a 100% new assetbundle using AssetsTools.net
+ AssetBundleModule.CreateShaderBundle("dummy");
+
+ // setup resources folder for shader bundles
+ var shaderDirectory = Path.Join(settings.GetStreamingAssetsPath(true), "ShaderInjections");
+ Directory.CreateDirectory(shaderDirectory);
+
+ // get all project materials/shaders to filter against later
+ var materials = AssetDatabase.FindAssets("t:material")
+ .Select(guid => AssetDatabase.GUIDToAssetPath(guid))
+ .Select(path => AssetDatabase.LoadAssetAtPath(path))
+ .ToList();
+ var shaders = AssetDatabase.FindAssets("t:shader")
+ .Select(guid => AssetDatabase.GUIDToAssetPath(guid))
+ .Select(path => AssetDatabase.LoadAssetAtPath(path))
+ .ToList();
+
+ List shaderInjections = new();
+
+ for (int i = 0; i < ShadersToGrab.Count; i++) {
+ var shaderToGrab = ShadersToGrab[i];
+ var shader = GetShaderFromAssetsFiles(shaderToGrab, assetsFileInstances, assetsManager);
+ if (shader == null) {
+ continue;
+ }
+
+ // may need better parsing if more complex shader names are ever filtered against
+ var shortShaderName = shaderToGrab.Split("/").Last().Replace(" ", "").ToLower();
+ InjectShaderIntoExistingAssetBundle(
+ Path.Join(Application.temporaryCachePath, "dummy"),
+ Path.Join(shaderDirectory, $"{shortShaderName}.shaderinject"),
+ shortShaderName,
+ i,
+ shader,
+ assetsManager);
+
+ shaderInjections.Add(GetShaderInjection(shaderToGrab, shortShaderName, materials, shaders));
+ }
+
+ // Create shaderInjection SO
+ var injectionSettings = ScriptableObject.CreateInstance();
+ injectionSettings.ShaderInjections = shaderInjections;
+
+ // TODO: User-facing warning this is destructive or something?
+ if (AssetDatabase.FindAssets(ShaderInjectionSettingsPath).Length > 0) {
+ AssetDatabase.DeleteAsset(ShaderInjectionSettingsPath);
+ }
+ AssetDatabase.CreateAsset(injectionSettings, ShaderInjectionSettingsPath);
+
+ // Get rid of progress bar from loading the asset files
+ EditorUtility.ClearProgressBar();
+
+ // Unload
+ assetsManager.UnloadAll();
+ }
+
+ private static ShaderInjection GetShaderInjection(string shaderName, string bundleName, List materials, List shaders) {
+ var filteredMaterials = materials.Where(x => x.shader.name == shaderName).ToList();
+ var filteredShaders = shaders.Where(x => x.name == shaderName).ToList();
+
+ var shaderInjection = new ShaderInjection {
+ ShaderName = shaderName,
+ BundleName = bundleName,
+ DummyShaders = filteredShaders,
+ Materials = filteredMaterials
+ };
+
+ return shaderInjection;
+ }
+
+ private static void InjectShaderIntoExistingAssetBundle(string currentBundlePath, string newBundlePath, string shaderName, int index, AssetTypeValueField shader, AssetsManager assetsManager) {
+ // I'm not sure if we can easily create fully new AssetBundles using only AssetsTools.NET
+ // If we can, refactor this to use CreateValueBaseField to add the actual shader in a brand new assetbundle
+ var bundleFileInstance = assetsManager.LoadBundleFile(currentBundlePath);
+ var assetsFileInstance = assetsManager.LoadAssetsFileFromBundle(bundleFileInstance, 0);
+ var assetsFile = assetsFileInstance?.file;
+ if (assetsFileInstance == null || assetsFile == null) {
+ throw new Exception($"Could not load bundle file at {currentBundlePath}");
+ }
+
+ // Load unity version so we can get the correct class database setup
+ string unityVersion = assetsFile.Metadata.UnityVersion;
+ assetsManager.LoadClassDatabaseFromPackage(unityVersion);
+
+ // Inject the actual shader
+ var (shaderInfo, shaderData) = GetFirstAssetInfoAndBaseOfClassID(assetsFileInstance, AssetClassID.Shader, assetsManager);
+ var newPathId = shaderInfo.PathId - (index + 1) * 20; // prevent path id overlaps from other assets
+
+ // Get AssetBundle asset
+ var (assetBundleInfo, assetBundleData) = GetFirstAssetInfoAndBaseOfClassID(assetsFileInstance, AssetClassID.AssetBundle, assetsManager);
+
+ // Force set AssetBundle name & paths to line up
+ // If you try to load an AssetBundle with the same name/assets twice, it will refuse to load
+ assetBundleData["m_Name"].AsString = shaderName;
+ assetBundleData["m_AssetBundleName"].AsString = shaderName;
+
+ // Replace name with a fake "assets" path that won't conflict with anything
+ assetBundleData["m_Container.Array"].Children[0]["first"].AsString = $"assets/injectedshaders/{shaderName}.shader";
+
+ // Replace m_Container (assetbundle internal object array) with our new Path ID
+ assetBundleData["m_Container.Array"].Children[0]["second"]["asset"]["m_PathID"].AsLong = newPathId;
+
+ // Remap the preload table pptrs. Only this method worked. I have no idea why.
+ RemapPPtrs(assetBundleData["m_PreloadTable.Array"], new Dictionary<(int fileId, long pathId), (int fileId, long pathId)> {
+ {(0, shaderInfo.PathId), (0, newPathId)}
+ });
+
+ // Remap shader dependencies. I pray there is never more than the shader graph fallback error
+ // This just destroys the dependency array for now. That's probably fine, right?
+ // Needed to avoid "illegal LocalPathID in PersistentManager" error
+ shader["m_Dependencies.Array"].Children = new List();
+
+ // Finally, set shader iD and our new data
+ shaderInfo.PathId = newPathId;
+ shaderInfo.SetNewData(shader);
+ assetBundleInfo.SetNewData(assetBundleData);
+
+ // Overwrite AssetsFile in bundle, then write everything to the new path
+ bundleFileInstance.file.BlockAndDirInfo.DirectoryInfos[0].SetNewData(assetsFile);
+ using AssetsFileWriter writer = new AssetsFileWriter(newBundlePath);
+ bundleFileInstance.file.Write(writer);
+ }
+
+ // https://github.com/PassivePicasso/BundleKit/blob/0b53bdf51b968094a3aa753f695237d13a97f649/Editor/Utility/AssetsToolsExtensions.cs#L179
+ public static void RemapPPtrs(this AssetTypeValueField field, IDictionary<(int fileId, long pathId), (int fileId, long pathId)> map) {
+ var fieldStack = new Stack();
+ fieldStack.Push(field);
+ while (fieldStack.Any()) {
+ var current = fieldStack.Pop();
+ foreach (AssetTypeValueField child in current.Children) {
+ //not a value (ie not an int)
+ if (!child.TemplateField.HasValue) {
+ //not array of values either
+ if (child.TemplateField.IsArray && child.TemplateField.Children[1].ValueType != AssetValueType.None) {
+ continue;
+ }
+
+ string typeName = child.TemplateField.Type;
+ //is a pptr
+ if (typeName.StartsWith("PPtr<") && typeName.EndsWith(">")) {
+ var fileIdField = child.Get("m_FileID").Value;
+ var pathIdField = child.Get("m_PathID").Value;
+ var pathId = pathIdField.AsLong;
+ var fileId = fileIdField.AsInt;
+ if (!map.ContainsKey((fileId, pathId))) {
+ continue;
+ }
+ var newPPtr = map[(fileId, pathId)];
+ fileIdField.AsInt = newPPtr.fileId;
+ pathIdField.AsLong = newPPtr.pathId;
+ }
+
+ //recurse through dependencies
+ fieldStack.Push(child);
+ }
+ }
+ }
+ }
+
+ [CanBeNull]
+ private static AssetTypeValueField GetShaderFromAssetsFiles(string requiredShaderName, List assetsFileInstances, AssetsManager assetsManager) {
+ for (int i = 0; i < assetsFileInstances.Count; i++) {
+ var assetsFileInstance = assetsFileInstances[i];
+ EditorUtility.DisplayProgressBar("Extracting shaders", $"Extracting from {assetsFileInstance.name}", (float)i / assetsFileInstances.Count);
+
+ var shader = GetAssetInfoAndBaseOfClassID(assetsFileInstance, AssetClassID.Shader, assetsManager)
+ .Select(x => x.assetBase)
+ .FirstOrDefault(x => x["m_ParsedForm"]["m_Name"].AsString == requiredShaderName);
+ if (shader != null) {
+ return shader;
+ }
+ }
+
+ return null;
+ }
+
+ // Load an assets file, and any non-loaded dependencies
+ private static List RecursivelyLoadAssetsFile(string assetsFilePath, AssetsManager assetsManager, int? depth = 0) {
+ if (_loadedAssetsFilePaths.Contains(assetsFilePath) || depth > 50) {
+ return new();
+ }
+ _loadedAssetsFilePaths.Add(assetsFilePath);
+
+ // Attempt to recursively load all assetsfiles and dependents
+ List assetsFileInstances = new();
+
+ var assetsInstance = assetsManager.LoadAssetsFile(assetsFilePath, true);
+ var assetsFile = assetsInstance.file;
+ assetsFile.GenerateQuickLookup(); // not 100% sure what this does
+
+ if (assetsInstance == null || assetsFile == null) {
+ throw new Exception($"Could not load assets file at {assetsFilePath}");
+ }
+
+ // Load unity version so we can get the correct class database setup
+ string unityVersion = assetsFile.Metadata.UnityVersion;
+ assetsManager.LoadClassDatabaseFromPackage(unityVersion);
+
+ for (int i = 0; i < assetsFile.Metadata.Externals.Count; i++) {
+ AssetsFileInstance dependency = assetsInstance.GetDependency(assetsManager, i);
+ if (dependency == null) {
+ continue;
+ }
+
+ string dependencyPath = dependency.path.ToLower();
+ if (!_loadedAssetsFilePaths.Contains(dependencyPath)) {
+ assetsFileInstances.AddRange(RecursivelyLoadAssetsFile(dependencyPath, assetsManager, depth + 1));
+ }
+ }
+
+ assetsFileInstances.Add(assetsInstance);
+ return assetsFileInstances;
+ }
+
+ private static List LoadAssetsFilesFromDataPath(string dataPath, AssetsManager assetsManager) {
+ List assetsFileInstances = new();
+
+ foreach (var assetsFilePath in GetAssetsFilePathsFromDataPath(dataPath)) {
+ assetsFileInstances.AddRange(RecursivelyLoadAssetsFile(assetsFilePath, assetsManager));
+ }
+
+ return assetsFileInstances;
+ }
+
+ // handles all the annoying null checks for assetfile/assetdata
+ [CanBeNull]
+ private static (AssetFileInfo assetInfo, AssetTypeValueField assetBase) GetFirstAssetInfoAndBaseOfClassID(AssetsFileInstance assetsFileInstance, AssetClassID assetClassID, AssetsManager assetsManager) {
+ var assetsInfoAndData = GetAssetInfoAndBaseOfClassID(assetsFileInstance, assetClassID, assetsManager);
+ var assetInfoAndData = assetsInfoAndData?.FirstOrDefault();
+ if (assetInfoAndData == null) {
+ throw new Exception($"Could not find asset of class {assetClassID} in assetsFileInstance.");
+ }
+ return assetInfoAndData!.Value;
+ }
+
+ // handles all the annoying null checks for assetfile/assetdata
+ private static List<(AssetFileInfo assetInfo, AssetTypeValueField assetBase)> GetAssetInfoAndBaseOfClassID(AssetsFileInstance assetsFileInstance, AssetClassID assetClassID, AssetsManager assetsManager) {
+ var assetsFile = assetsFileInstance.file;
+ if (assetsFile == null) {
+ return new();
+ }
+ List<(AssetFileInfo, AssetTypeValueField)> assetsInfoAndData = new();
+ foreach (var assetInfo in assetsFile.GetAssetsOfType(assetClassID)) {
+ if (assetInfo == null) {
+ continue;
+ }
+ var assetBase = assetsManager.GetBaseField(assetsFileInstance, assetInfo);
+ if (assetBase == null) {
+ continue;
+ }
+ assetsInfoAndData.Add((assetInfo, assetBase));
+ }
+
+ return assetsInfoAndData;
+ }
+
+ private static IEnumerable GetAssetsFilePathsFromDataPath(string dataPath) {
+ if (!Directory.Exists(dataPath)) {
+ throw new DirectoryNotFoundException("Could not find data folder");
+ }
+
+ List assetsFilePaths = new();
+
+ foreach (var file in Directory.GetFiles(dataPath)) {
+ var fileName = Path.GetFileName(file);
+ if (Path.GetExtension(file) != ".assets") {
+ continue;
+ }
+ // sharedassets *may* be unnecessary, not sure if all the game's assets are in the resources folder
+ if (!fileName.StartsWith("sharedassets") && !fileName.StartsWith("resources")) {
+ continue;
+ }
+ assetsFilePaths.Add(file);
+ }
+
+ return assetsFilePaths;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/Modules/AssetsToolsModule.cs.meta b/Editor/Modules/AssetsToolsModule.cs.meta
new file mode 100644
index 0000000..f90c87a
--- /dev/null
+++ b/Editor/Modules/AssetsToolsModule.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 8b661adad7194b5c9a7aaae725ff6f78
+timeCreated: 1708050095
\ No newline at end of file
diff --git a/Editor/Modules/InitialProjectModule.cs b/Editor/Modules/InitialProjectModule.cs
index 062579d..bb167d3 100644
--- a/Editor/Modules/InitialProjectModule.cs
+++ b/Editor/Modules/InitialProjectModule.cs
@@ -19,6 +19,8 @@ public static void MoveNativeFiles(LCPatcherSettings settings) {
ModuleUtility.CreateDirectory(settings.GetAssetStorePath(fullPath: true));
ModuleUtility.CreateDirectory(settings.GetModsPath(fullPath: true));
ModuleUtility.CreateDirectory(settings.GetToolsPath(fullPath: true));
+ ModuleUtility.CreateDirectory(settings.GetResourcesPath(fullPath: true));
+ ModuleUtility.CreateDirectory(settings.GetStreamingAssetsPath(fullPath: true));
var gamePath = settings.GetLethalCompanyGamePath(fullPath: true);
if (Directory.Exists(gamePath)) {
diff --git a/Editor/Resources/Posterization/Dummy.shader b/Editor/Resources/Posterization/Dummy.shader
new file mode 100644
index 0000000..f12f841
--- /dev/null
+++ b/Editor/Resources/Posterization/Dummy.shader
@@ -0,0 +1,58 @@
+Shader "Unlit/Dummy"
+{
+ Properties
+ {
+ _MainTex ("Texture", 2D) = "white" {}
+ }
+ SubShader
+ {
+ Tags { "RenderType"="Opaque" }
+ LOD 100
+
+ Pass
+ {
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+ // make fog work
+ #pragma multi_compile_fog
+
+ #include "UnityCG.cginc"
+
+ struct appdata
+ {
+ float4 vertex : POSITION;
+ float2 uv : TEXCOORD0;
+ };
+
+ struct v2f
+ {
+ float2 uv : TEXCOORD0;
+ UNITY_FOG_COORDS(1)
+ float4 vertex : SV_POSITION;
+ };
+
+ sampler2D _MainTex;
+ float4 _MainTex_ST;
+
+ v2f vert (appdata v)
+ {
+ v2f o;
+ o.vertex = UnityObjectToClipPos(v.vertex);
+ o.uv = TRANSFORM_TEX(v.uv, _MainTex);
+ UNITY_TRANSFER_FOG(o,o.vertex);
+ return o;
+ }
+
+ fixed4 frag (v2f i) : SV_Target
+ {
+ // sample the texture
+ fixed4 col = tex2D(_MainTex, i.uv);
+ // apply fog
+ UNITY_APPLY_FOG(i.fogCoord, col);
+ return col;
+ }
+ ENDCG
+ }
+ }
+}
diff --git a/Editor/Resources/Posterization/Dummy.shader.meta b/Editor/Resources/Posterization/Dummy.shader.meta
new file mode 100644
index 0000000..ad632f5
--- /dev/null
+++ b/Editor/Resources/Posterization/Dummy.shader.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 93f2d095ee8352049a31a02d38d62d63
+ShaderImporter:
+ externalObjects: {}
+ defaultTextures: []
+ nonModifiableTextures: []
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/ShaderInjectionEditorPatch.cs b/Editor/ShaderInjectionEditorPatch.cs
new file mode 100644
index 0000000..ace9500
--- /dev/null
+++ b/Editor/ShaderInjectionEditorPatch.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using JetBrains.Annotations;
+using Nomnom.LCProjectPatcher.Editor.Modules;
+using UnityEditor;
+using UnityEngine;
+
+namespace Nomnom.LCProjectPatcher.Editor {
+ [InitializeOnLoad]
+ public class ShaderInjectionEditorPatch : AssetPostprocessor {
+ [CanBeNull]
+ private static LCPatcherShaderInjectionSettings _shaderInjectionSettings = null;
+
+ // Called in editor startup and domain reload, every injected shader likely got saved as null
+ // So we'll have to re-initialize all of them
+ static ShaderInjectionEditorPatch() {
+ var injectionSettings = TryGetInjectionSettings();
+ if (injectionSettings == null) {
+ return;
+ }
+
+ injectionSettings.InjectAllShadersIntoMaterials();
+ }
+
+ private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload) {
+ string[] materials = importedAssets.Where(path => path.EndsWith(".mat")).ToArray();
+
+ // We're only looking for modified materials with injected values
+ if (materials.Length == 0)
+ return;
+
+ var injectionSettings = TryGetInjectionSettings();
+ if (injectionSettings == null) {
+ return;
+ }
+
+ Dictionary> updatedMaterialsByShaderInjection = new();
+ foreach (var shaderInjection in injectionSettings.ShaderInjections) {
+ foreach (var injectedMaterial in shaderInjection.Materials) {
+ var injectedMaterialPath = AssetDatabase.GetAssetPath(injectedMaterial);
+ foreach (var material in materials) {
+ if (injectedMaterialPath == material && (injectedMaterial.shader == null || injectedMaterial.shader.name == "Hidden/InternalErrorShader")) {
+ if (!updatedMaterialsByShaderInjection.TryGetValue(shaderInjection, out List updatedMaterials)) {
+ updatedMaterials = new();
+ }
+ updatedMaterials.Add(injectedMaterial);
+ updatedMaterialsByShaderInjection[shaderInjection] = updatedMaterials;
+ }
+ }
+ }
+ }
+
+ foreach (var (shaderInjection, updatedMaterials) in updatedMaterialsByShaderInjection) {
+ // Set material shaders
+ foreach (var material in updatedMaterials) {
+ material.shader = shaderInjection.GetInjectedShader();
+ }
+ }
+ }
+
+ private static LCPatcherShaderInjectionSettings TryGetInjectionSettings() {
+ // TODO: look into the performance implications of this
+ if (_shaderInjectionSettings == null) {
+ _shaderInjectionSettings = Resources.Load("ShaderInjectionSettings");
+ }
+
+ return _shaderInjectionSettings;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/ShaderInjectionEditorPatch.cs.meta b/Editor/ShaderInjectionEditorPatch.cs.meta
new file mode 100644
index 0000000..6a131ad
--- /dev/null
+++ b/Editor/ShaderInjectionEditorPatch.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 4c758a09a57741228a3337149c36bcb5
+timeCreated: 1708085841
\ No newline at end of file
diff --git a/Editor/nomnom.lc-project-patcher.Editor.asmdef b/Editor/nomnom.lc-project-patcher.Editor.asmdef
index 16e93ee..75b2c7a 100644
--- a/Editor/nomnom.lc-project-patcher.Editor.asmdef
+++ b/Editor/nomnom.lc-project-patcher.Editor.asmdef
@@ -15,7 +15,8 @@
"overrideReferences": true,
"precompiledReferences": [
"Nomnom.LCProjectPatcherScriptCleaner.dll",
- "Newtonsoft.Json.dll"
+ "Newtonsoft.Json.dll",
+ "AssetsTools.NET.dll"
],
"autoReferenced": true,
"defineConstraints": [],
diff --git a/README.md b/README.md
index 0ff5c88..f23fc81 100644
--- a/README.md
+++ b/README.md
@@ -227,6 +227,9 @@ So for now I'm not supporting it. Use it with cation if you use it anyway!
- GameViewSizeShortcut - https://gist.github.com/wappenull/668a492c80f7b7fda0f7c7f42b3ae0b0
- BepInEx - https://github.com/BepInEx/BepInEx
- Newtonsoft Json.NET - https://www.newtonsoft.com/json
+- AssetsTools.NET - https://github.com/nesrak1/AssetsTools.NET
+- UABEA - https://github.com/nesrak1/UABEA
+- BundleKit - https://github.com/PassivePicasso/BundleKit
- IntegrityChaos - for the posterization shader remake
diff --git a/Runtime/GeneralSettings.cs b/Runtime/GeneralSettings.cs
index 2579203..1c59a6a 100644
--- a/Runtime/GeneralSettings.cs
+++ b/Runtime/GeneralSettings.cs
@@ -21,6 +21,14 @@ public class GeneralSettings {
[SerializedPath(nameof(LCPatcherSettings.GetBaseLethalCompanyPath))]
[SerializeField]
private string _toolsPath = "Tools";
+
+ [SerializedPath(nameof(LCPatcherSettings.GetBaseLethalCompanyPath))]
+ [SerializeField]
+ private string _resourcesPath = "Resources";
+
+ [SerializedPath(nameof(LCPatcherSettings.GetBaseLethalCompanyPath))]
+ [SerializeField]
+ private string _streamingAssetsPath = "StreamingAssets";
public string GetNativePath(string path) {
return Path.Combine(path, _nativePath);
@@ -37,5 +45,13 @@ public string GetModsPath(string path) {
public string GetToolsPath(string path) {
return Path.Combine(path, _toolsPath);
}
+
+ public string GetResourcesPath(string path) {
+ return Path.Combine(path, _resourcesPath);
+ }
+
+ public string GetStreamingAssetsPath(string path) {
+ return Path.Combine(path, _streamingAssetsPath);
+ }
}
}
diff --git a/Runtime/LCPatcherSettings.cs b/Runtime/LCPatcherSettings.cs
index 9eae384..f08171f 100644
--- a/Runtime/LCPatcherSettings.cs
+++ b/Runtime/LCPatcherSettings.cs
@@ -49,6 +49,14 @@ public string GetToolsPath(bool fullPath = false) {
return GetFullPathOrNot(_generalSettings.GetToolsPath(GetBaseLethalCompanyPath()), fullPath);
}
+ public string GetResourcesPath(bool fullPath = false) {
+ return GetFullPathOrNot(_generalSettings.GetResourcesPath(GetBasePath()), fullPath);
+ }
+
+ public string GetStreamingAssetsPath(bool fullPath = false) {
+ return GetFullPathOrNot(_generalSettings.GetStreamingAssetsPath(GetBasePath()), fullPath);
+ }
+
private string GetFullPathOrNot(string path, bool fullPath) {
return fullPath ? Path.GetFullPath(path) : path;
}
diff --git a/Runtime/LCPatcherShaderInjectionSettings.cs b/Runtime/LCPatcherShaderInjectionSettings.cs
new file mode 100644
index 0000000..2596fd5
--- /dev/null
+++ b/Runtime/LCPatcherShaderInjectionSettings.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Nomnom.LCProjectPatcher {
+ // note: needs to be in Resources to be picked up in runtime builds
+ [CreateAssetMenu(fileName = "ShaderInjectionSettings", menuName = "LC Project Patcher/Shader Injection Settings")]
+ public class LCPatcherShaderInjectionSettings: ScriptableObject {
+ public List ShaderInjections = new();
+
+ public void InjectAllShadersIntoMaterials() {
+ foreach (var shaderInjection in ShaderInjections) {
+ foreach (var material in shaderInjection.Materials) {
+ if (shaderInjection.MaterialNeedsInjection(material)) {
+ material.shader = shaderInjection.GetInjectedShader();
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/LCPatcherShaderInjectionSettings.cs.meta b/Runtime/LCPatcherShaderInjectionSettings.cs.meta
new file mode 100644
index 0000000..ea5b5a7
--- /dev/null
+++ b/Runtime/LCPatcherShaderInjectionSettings.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 21d621da411d47ca8a78fec158ae365b
+timeCreated: 1708064581
\ No newline at end of file
diff --git a/Runtime/Patches.meta b/Runtime/Patches.meta
new file mode 100644
index 0000000..cb557af
--- /dev/null
+++ b/Runtime/Patches.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3aa240fecf37417bb36556e891c608c6
+timeCreated: 1708089749
\ No newline at end of file
diff --git a/Runtime/Patches/CustomPassRuntimePatch.cs b/Runtime/Patches/CustomPassRuntimePatch.cs
new file mode 100644
index 0000000..a6600cf
--- /dev/null
+++ b/Runtime/Patches/CustomPassRuntimePatch.cs
@@ -0,0 +1,52 @@
+using System.Linq;
+using JetBrains.Annotations;
+using UnityEngine;
+using UnityEngine.Rendering;
+using UnityEngine.Rendering.HighDefinition;
+using UnityEngine.SceneManagement;
+
+namespace Nomnom.LCProjectPatcher.Patches {
+ // Apply custom pass (not sure why it gets nuked in the project?)
+ // Ideally this would be sorted upon project creation, but for now I'll do this
+ public class CustomPassRuntimePatch {
+ [CanBeNull]
+ private static LCPatcherShaderInjectionSettings _injectionSettings = null;
+
+ [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
+ static void OnBeforeSceneLoad()
+ {
+ SceneManager.sceneLoaded += OnSceneLoaded;
+ }
+
+ // A bit slow but it should do the job just fine
+ private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) {
+ // Get shader injection settings so we can get the injected posterization material
+ _injectionSettings = _injectionSettings ?? Resources.Load("ShaderInjectionSettings");
+ var passMaterial = _injectionSettings?.ShaderInjections
+ .Where(shaderInjection => shaderInjection.ShaderName == "Shader Graphs/PosterizationFilter")
+ .SelectMany(shaderInjection => shaderInjection.Materials)
+ .FirstOrDefault();
+ if (_injectionSettings == null || passMaterial == null) {
+ return;
+ }
+
+ // Find existing base game custom pass
+ var customPassGameObject = GameObject.Find("Systems/Rendering/CustomPass");
+ var customPassVolume = customPassGameObject?.GetComponent();
+ if (customPassVolume == null || customPassVolume.customPasses.Count > 0) {
+ return;
+ }
+
+ // Create the actual custom pass, with base game (or near base game, i haven't checked 1000%) values
+ var drawRenderersCustomPass = new DrawRenderersCustomPass();
+ drawRenderersCustomPass.layerMask = ~0; // LayerMask.Everything
+ drawRenderersCustomPass.overrideMaterial = passMaterial;
+ drawRenderersCustomPass.overrideMaterialPassName = "ForwardOnly";
+ drawRenderersCustomPass.depthWrite = true;
+ drawRenderersCustomPass.overrideDepthState = true;
+ drawRenderersCustomPass.depthCompareFunction = CompareFunction.Equal;
+ drawRenderersCustomPass.sortingCriteria = SortingCriteria.CommonOpaque;
+ customPassVolume.customPasses.Add(drawRenderersCustomPass);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Patches/CustomPassRuntimePatch.cs.meta b/Runtime/Patches/CustomPassRuntimePatch.cs.meta
new file mode 100644
index 0000000..0fb73e0
--- /dev/null
+++ b/Runtime/Patches/CustomPassRuntimePatch.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3f06dd9392f3420990a00977ea302458
+timeCreated: 1708090046
\ No newline at end of file
diff --git a/Runtime/Patches/ShaderInjectionRuntimePatch.cs b/Runtime/Patches/ShaderInjectionRuntimePatch.cs
new file mode 100644
index 0000000..2c71ab7
--- /dev/null
+++ b/Runtime/Patches/ShaderInjectionRuntimePatch.cs
@@ -0,0 +1,21 @@
+using UnityEngine;
+
+namespace Nomnom.LCProjectPatcher.Patches {
+ // Essentially just trying to get ShaderInjection behaviour in builds without having to directly interact with the game
+ // This should work completely generically
+ public class ShaderInjectionRuntimePatch {
+ #if !UNITY_EDITOR
+ [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
+ static void OnBeforeSceneLoad()
+ {
+ var injectionSettings = Resources.Load("ShaderInjectionSettings");
+ if (injectionSettings == null) {
+ return;
+ }
+
+ Debug.Log("Applying all injected shaders.");
+ injectionSettings.InjectAllShadersIntoMaterials();
+ }
+ #endif
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Patches/ShaderInjectionRuntimePatch.cs.meta b/Runtime/Patches/ShaderInjectionRuntimePatch.cs.meta
new file mode 100644
index 0000000..b78ca1b
--- /dev/null
+++ b/Runtime/Patches/ShaderInjectionRuntimePatch.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 75bf2858b74e460e98bdeaab2ba7ebac
+timeCreated: 1708089295
\ No newline at end of file
diff --git a/Runtime/ShaderInjection.cs b/Runtime/ShaderInjection.cs
new file mode 100644
index 0000000..1860889
--- /dev/null
+++ b/Runtime/ShaderInjection.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using JetBrains.Annotations;
+using UnityEngine;
+
+namespace Nomnom.LCProjectPatcher {
+ [Serializable]
+ public struct ShaderInjection {
+ public string ShaderName;
+ public string BundleName;
+ public List DummyShaders;
+ public List Materials;
+
+ [NonSerialized, CanBeNull]
+ public Shader InjectedShader;
+
+ public Shader GetInjectedShader() {
+ // Load bundle
+ var bundlePath = Path.Join(Application.streamingAssetsPath, "ShaderInjections",
+ $"{BundleName}.shaderinject");
+ var bundle = AssetBundle.LoadFromFile(bundlePath);
+ InjectedShader = bundle.LoadAsset($"assets/injectedshaders/{BundleName}.shader");
+
+ // Unload
+ bundle.Unload(false);
+
+ return InjectedShader;
+ }
+
+ public bool MaterialNeedsInjection(Material material) {
+ // null/internalerrorshader happens when it was serialized using a temporary injected shader
+ if (material.shader == null || material.shader.name == "Hidden/InternalErrorShader") {
+ return true;
+ }
+ // shaders are initially using AssetRipper's 'Dummy' shaders
+ if (DummyShaders.Any(x => x == material.shader)) {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/ShaderInjection.cs.meta b/Runtime/ShaderInjection.cs.meta
new file mode 100644
index 0000000..2c70a20
--- /dev/null
+++ b/Runtime/ShaderInjection.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 86fa1b56ef164937b37608cb58a62e3e
+timeCreated: 1708064911
\ No newline at end of file
diff --git a/Runtime/nomnom.lc-project-patcher.asmdef b/Runtime/nomnom.lc-project-patcher.asmdef
index 13c9cfa..fef44ed 100644
--- a/Runtime/nomnom.lc-project-patcher.asmdef
+++ b/Runtime/nomnom.lc-project-patcher.asmdef
@@ -1,7 +1,9 @@
{
"name": "nomnom.lc-project-patcher",
"rootNamespace": "Nomnom.LCProjectPatcher",
- "references": [],
+ "references": [
+ "GUID:457756d89b35d2941b3e7b37b4ece6f1"
+ ],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,