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,