Merge branch 'tuyoogame:main' into main
commit
cfbf6e23ec
|
@ -2,6 +2,32 @@
|
|||
|
||||
All notable changes to this package will be documented in this file.
|
||||
|
||||
## [1.4.4-preview] - 2023-02-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- (#65)修复了AssetBundle构建宏逻辑错误。
|
||||
- 修复了AssetBundle加载宏逻辑错误。
|
||||
|
||||
## [1.4.3-preview] - 2023-02-10
|
||||
|
||||
全新的缓存系统!
|
||||
|
||||
### Fixed
|
||||
|
||||
- 修复了WebGL平台本地文件验证报错。
|
||||
- 修复了WEBGL平台加载原生文件失败的问题。
|
||||
- 修复了通过Handle句柄查询资源包下载进度为零的问题。
|
||||
|
||||
### Changed
|
||||
|
||||
- 着色器变种收集增加分批次处理功能。
|
||||
- Unity2021版本开始不再支持内置构建管线。
|
||||
|
||||
### Removed
|
||||
|
||||
- 太空战机DEMO移除了BetterStreamingAssets插件。
|
||||
|
||||
## [1.4.2-preview] - 2023-01-03
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -220,15 +220,27 @@ namespace YooAsset.Editor
|
|||
|
||||
private void RefreshWindow()
|
||||
{
|
||||
var buildPipeline = AssetBundleBuilderSettingData.Setting.BuildPipeline;
|
||||
var buildMode = AssetBundleBuilderSettingData.Setting.BuildMode;
|
||||
var copyOption = AssetBundleBuilderSettingData.Setting.CopyBuildinFileOption;
|
||||
bool enableElement = buildMode == EBuildMode.ForceRebuild;
|
||||
bool tagsFiledVisible = copyOption == ECopyBuildinFileOption.ClearAndCopyByTags || copyOption == ECopyBuildinFileOption.OnlyCopyByTags;
|
||||
_encryptionField.SetEnabled(enableElement);
|
||||
|
||||
if (buildPipeline == EBuildPipeline.BuiltinBuildPipeline)
|
||||
{
|
||||
_compressionField.SetEnabled(enableElement);
|
||||
_outputNameStyleField.SetEnabled(enableElement);
|
||||
_copyBuildinFileOptionField.SetEnabled(enableElement);
|
||||
_copyBuildinFileTagsField.SetEnabled(enableElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
_compressionField.SetEnabled(true);
|
||||
_outputNameStyleField.SetEnabled(true);
|
||||
_copyBuildinFileOptionField.SetEnabled(true);
|
||||
_copyBuildinFileTagsField.SetEnabled(true);
|
||||
}
|
||||
|
||||
_copyBuildinFileTagsField.visible = tagsFiledVisible;
|
||||
}
|
||||
private void SaveBtn_clicked()
|
||||
|
|
|
@ -16,12 +16,17 @@ namespace YooAsset.Editor
|
|||
|
||||
var buildParameters = buildParametersContext.Parameters;
|
||||
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
if (buildParameters.BuildPipeline == EBuildPipeline.BuiltinBuildPipeline)
|
||||
throw new Exception($"Unity2021 or newer not support {nameof(EBuildPipeline.BuiltinBuildPipeline)}, Please use {nameof(EBuildPipeline.ScriptableBuildPipeline)}");
|
||||
#endif
|
||||
|
||||
// 检测构建参数合法性
|
||||
if (buildParameters.BuildTarget == BuildTarget.NoTarget)
|
||||
throw new Exception("请选择目标平台");
|
||||
if (string.IsNullOrEmpty(buildParameters.PackageName))
|
||||
throw new Exception("包裹名称不能为空");
|
||||
if(string.IsNullOrEmpty(buildParameters.PackageVersion))
|
||||
if (string.IsNullOrEmpty(buildParameters.PackageVersion))
|
||||
throw new Exception("包裹版本不能为空");
|
||||
|
||||
if (buildParameters.BuildMode != EBuildMode.SimulateBuild)
|
||||
|
|
|
@ -351,6 +351,9 @@ namespace YooAsset
|
|||
|
||||
// 新增下载需求
|
||||
#if UNITY_WEBGL
|
||||
if (bundleInfo.Bundle.IsRawFile)
|
||||
loader = new RawBundleFileLoader(this, bundleInfo);
|
||||
else
|
||||
loader = new AssetBundleWebLoader(this, bundleInfo);
|
||||
#else
|
||||
if (bundleInfo.Bundle.IsRawFile)
|
||||
|
|
|
@ -3,10 +3,34 @@ using System.IO;
|
|||
|
||||
namespace YooAsset
|
||||
{
|
||||
[Serializable]
|
||||
internal class CacheFileInfo
|
||||
{
|
||||
public string FileCRC;
|
||||
public long FileSize;
|
||||
private static readonly BufferWriter SharedBuffer = new BufferWriter(1024);
|
||||
|
||||
/// <summary>
|
||||
/// 写入资源包信息
|
||||
/// </summary>
|
||||
public static void WriteInfoToFile(string filePath, string dataFileCRC, long dataFileSize)
|
||||
{
|
||||
using (FileStream fs = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
SharedBuffer.Clear();
|
||||
SharedBuffer.WriteUTF8(dataFileCRC);
|
||||
SharedBuffer.WriteInt64(dataFileSize);
|
||||
SharedBuffer.WriteToStream(fs);
|
||||
fs.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取资源包信息
|
||||
/// </summary>
|
||||
public static void ReadInfoFromFile(string filePath, out string dataFileCRC, out long dataFileSize)
|
||||
{
|
||||
byte[] binaryData = FileUtility.ReadAllBytes(filePath);
|
||||
BufferReader buffer = new BufferReader(binaryData);
|
||||
dataFileCRC = buffer.ReadUTF8();
|
||||
dataFileSize = buffer.ReadInt64();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -78,10 +78,7 @@ namespace YooAsset
|
|||
return EVerifyResult.InfoFileNotExisted;
|
||||
|
||||
// 解析信息文件获取验证数据
|
||||
string jsonContent = FileUtility.ReadAllText(infoFilePath);
|
||||
CacheFileInfo fileInfo = UnityEngine.JsonUtility.FromJson<CacheFileInfo>(jsonContent);
|
||||
element.DataFileCRC = fileInfo.FileCRC;
|
||||
element.DataFileSize = fileInfo.FileSize;
|
||||
CacheFileInfo.ReadInfoFromFile(infoFilePath, out element.DataFileCRC, out element.DataFileSize);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
|
|
@ -220,22 +220,24 @@ namespace YooAsset
|
|||
{
|
||||
try
|
||||
{
|
||||
string destFilePath = _bundleInfo.Bundle.CachedDataFilePath;
|
||||
if (File.Exists(destFilePath))
|
||||
File.Delete(destFilePath);
|
||||
string infoFilePath = _bundleInfo.Bundle.CachedInfoFilePath;
|
||||
string dataFilePath = _bundleInfo.Bundle.CachedDataFilePath;
|
||||
string dataFileCRC = _bundleInfo.Bundle.FileCRC;
|
||||
long dataFileSize = _bundleInfo.Bundle.FileSize;
|
||||
|
||||
if (File.Exists(infoFilePath))
|
||||
File.Delete(infoFilePath);
|
||||
if (File.Exists(dataFilePath))
|
||||
File.Delete(dataFilePath);
|
||||
|
||||
FileInfo fileInfo = new FileInfo(_tempFilePath);
|
||||
fileInfo.MoveTo(destFilePath);
|
||||
fileInfo.MoveTo(dataFilePath);
|
||||
|
||||
// 写入信息文件记录验证数据
|
||||
CacheFileInfo cacheInfo = new CacheFileInfo();
|
||||
cacheInfo.FileCRC = _bundleInfo.Bundle.FileCRC;
|
||||
cacheInfo.FileSize = _bundleInfo.Bundle.FileSize;
|
||||
string jsonContent = UnityEngine.JsonUtility.ToJson(cacheInfo);
|
||||
FileUtility.CreateFile(_bundleInfo.Bundle.CachedInfoFilePath, jsonContent);
|
||||
CacheFileInfo.WriteInfoToFile(infoFilePath, dataFileCRC, dataFileSize);
|
||||
|
||||
// 记录缓存文件
|
||||
var wrapper = new PackageCache.RecordWrapper(_bundleInfo.Bundle.CachedInfoFilePath, _bundleInfo.Bundle.CachedDataFilePath, _bundleInfo.Bundle.FileCRC, _bundleInfo.Bundle.FileSize);
|
||||
var wrapper = new PackageCache.RecordWrapper(infoFilePath, dataFilePath, dataFileCRC, dataFileSize);
|
||||
CacheSystem.RecordFile(_bundleInfo.Bundle.PackageName, _bundleInfo.Bundle.CacheGUID, wrapper);
|
||||
|
||||
_lastError = string.Empty;
|
||||
|
|
|
@ -8,6 +8,8 @@ namespace YooAsset
|
|||
{
|
||||
internal static class PatchManifestTools
|
||||
{
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 序列化(JSON文件)
|
||||
/// </summary>
|
||||
|
@ -151,6 +153,7 @@ namespace YooAsset
|
|||
|
||||
return manifest;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 生成Bundle文件的正式名称
|
||||
|
|
|
@ -28,6 +28,14 @@ namespace YooAsset
|
|||
get { return _buffer.Length; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空缓冲区
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_index = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将有效数据写入文件流
|
||||
/// </summary>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root Version="2.2">
|
||||
<Common AutoAddressable="True" UniqueBundleName="False" ShowPackageView="False" ShowEditorAlias="False" />
|
||||
<Package PackageName="DefaultPackage" PackageDesc="">
|
||||
<Group GroupName="battle" GroupDesc="" AssetTags="">
|
||||
<Collector CollectPath="Assets/Samples/Space Shooter/GameRes/Effect" CollectGUID="80d76514758554baaa96a9efffe9f3ef" CollectType="MainAssetCollector" AddressRule="AddressByFileName" PackRule="PackDirectory" FilterRule="CollectAll" AssetTags="" />
|
||||
<Collector CollectPath="Assets/Samples/Space Shooter/GameRes/Entity" CollectGUID="4d7c84745db8e884f8020a8c5356e67c" CollectType="MainAssetCollector" AddressRule="AddressByFileName" PackRule="PackDirectory" FilterRule="CollectAll" AssetTags="" />
|
||||
<Collector CollectPath="Assets/Samples/Space Shooter/GameRes/Audio" CollectGUID="306075fbe00b24251b6c889984a7a7d5" CollectType="MainAssetCollector" AddressRule="AddressByFileName" PackRule="PackDirectory" FilterRule="CollectAll" AssetTags="" />
|
||||
</Group>
|
||||
<Group GroupName="shader" GroupDesc="" AssetTags="">
|
||||
<Collector CollectPath="Assets/Samples/Space Shooter/GameArt/ShaderVariants" CollectGUID="00781758c26692e40a9634ddeac838be" CollectType="MainAssetCollector" AddressRule="AddressByFileName" PackRule="PackShaderVariants" FilterRule="CollectShaderVariants" AssetTags="" />
|
||||
</Group>
|
||||
<Group GroupName="scene" GroupDesc="" AssetTags="">
|
||||
<Collector CollectPath="Assets/Samples/Space Shooter/GameRes/Scene" CollectGUID="6070eae1192f2994887f2983a177d503" CollectType="MainAssetCollector" AddressRule="AddressByFileName" PackRule="PackSeparately" FilterRule="CollectAll" AssetTags="" />
|
||||
</Group>
|
||||
<Group GroupName="ugui" GroupDesc="" AssetTags="">
|
||||
<Collector CollectPath="Assets/Samples/Space Shooter/GameRes/UIImage" CollectGUID="30c57db62bb02a24590e7046d3a9e33a" CollectType="MainAssetCollector" AddressRule="AddressByFileName" PackRule="PackDirectory" FilterRule="CollectAll" AssetTags="" />
|
||||
<Collector CollectPath="Assets/Samples/Space Shooter/GameRes/UIPanel" CollectGUID="12d33f33f3a55224c9c747d7bffa1c68" CollectType="MainAssetCollector" AddressRule="AddressByFileName" PackRule="PackSeparately" FilterRule="CollectAll" AssetTags="" />
|
||||
<Collector CollectPath="Assets/Samples/Space Shooter/GameRes/UISprite" CollectGUID="01865ad6f7c806147b6cb37f2d83bc96" CollectType="MainAssetCollector" AddressRule="AddressByFileName" PackRule="PackTopDirectory" FilterRule="CollectAll" AssetTags="" />
|
||||
</Group>
|
||||
</Package>
|
||||
</root>
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 08dafaa194a7143109cbe1c6403a2ec5
|
||||
guid: 218816e46e4f6304eaecd6fdc3a99f4d
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"ShaderTotalCount": 12,
|
||||
"VariantTotalCount": 16,
|
||||
"ShaderTotalCount": 13,
|
||||
"VariantTotalCount": 24,
|
||||
"ShaderVariantInfos": [
|
||||
{
|
||||
"AssetPath": "Resources/unity_builtin_extra",
|
||||
|
@ -14,6 +14,109 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AssetPath": "Resources/unity_builtin_extra",
|
||||
"ShaderName": "Standard",
|
||||
"ShaderVariantElements": [
|
||||
{
|
||||
"PassType": 4,
|
||||
"Keywords": [
|
||||
"DIRECTIONAL",
|
||||
"LIGHTPROBE_SH",
|
||||
"SHADOWS_SCREEN",
|
||||
"SHADOWS_SPLIT_SPHERES"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 4,
|
||||
"Keywords": [
|
||||
"DIRECTIONAL",
|
||||
"LIGHTPROBE_SH",
|
||||
"SHADOWS_SCREEN",
|
||||
"SHADOWS_SPLIT_SPHERES",
|
||||
"_EMISSION"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 4,
|
||||
"Keywords": [
|
||||
"DIRECTIONAL",
|
||||
"LIGHTPROBE_SH",
|
||||
"SHADOWS_SCREEN",
|
||||
"SHADOWS_SPLIT_SPHERES",
|
||||
"_NORMALMAP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 4,
|
||||
"Keywords": [
|
||||
"DIRECTIONAL",
|
||||
"LIGHTPROBE_SH",
|
||||
"SHADOWS_SCREEN",
|
||||
"SHADOWS_SPLIT_SPHERES",
|
||||
"_EMISSION",
|
||||
"_NORMALMAP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"Keywords": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"Keywords": [
|
||||
"SHADOWS_DEPTH",
|
||||
"SHADOWS_SPLIT_SPHERES"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"Keywords": [
|
||||
"_EMISSION"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"Keywords": [
|
||||
"SHADOWS_DEPTH",
|
||||
"SHADOWS_SPLIT_SPHERES",
|
||||
"_EMISSION"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"Keywords": [
|
||||
"_NORMALMAP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"Keywords": [
|
||||
"SHADOWS_DEPTH",
|
||||
"SHADOWS_SPLIT_SPHERES",
|
||||
"_NORMALMAP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"Keywords": [
|
||||
"_EMISSION",
|
||||
"_NORMALMAP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"Keywords": [
|
||||
"SHADOWS_DEPTH",
|
||||
"SHADOWS_SPLIT_SPHERES",
|
||||
"_EMISSION",
|
||||
"_NORMALMAP"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AssetPath": "Resources/unity_builtin_extra",
|
||||
"ShaderName": "Skybox/Procedural",
|
||||
|
@ -21,12 +124,23 @@
|
|||
{
|
||||
"PassType": 0,
|
||||
"Keywords": [
|
||||
"BILLBOARD_FACE_CAMERA_POS",
|
||||
"_SUNDISK_SIMPLE"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AssetPath": "Resources/unity_builtin_extra",
|
||||
"ShaderName": "Legacy Shaders/Particles/Additive",
|
||||
"ShaderVariantElements": [
|
||||
{
|
||||
"PassType": 0,
|
||||
"Keywords": [
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AssetPath": "Resources/unity_builtin_extra",
|
||||
"ShaderName": "Hidden/Internal-GUITextureClip",
|
||||
|
@ -87,18 +201,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AssetPath": "Resources/unity_builtin_extra",
|
||||
"ShaderName": "Hidden/Internal-GUIRoundedRectWithColorPerBorder",
|
||||
"ShaderVariantElements": [
|
||||
{
|
||||
"PassType": 0,
|
||||
"Keywords": [
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AssetPath": "Resources/unity_builtin_extra",
|
||||
"ShaderName": "Hidden/Internal-UIRAtlasBlitCopy",
|
||||
|
@ -113,80 +215,37 @@
|
|||
},
|
||||
{
|
||||
"AssetPath": "Resources/unity_builtin_extra",
|
||||
"ShaderName": "Hidden/UIElements/EditorUIE",
|
||||
"ShaderName": "Hidden/Internal-GUIRoundedRectWithColorPerBorder",
|
||||
"ShaderVariantElements": [
|
||||
{
|
||||
"PassType": 0,
|
||||
"Keywords": [
|
||||
"BILLBOARD_FACE_CAMERA_POS"
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AssetPath": "Resources/unity_builtin_extra",
|
||||
"ShaderName": "Mobile/Diffuse",
|
||||
"ShaderName": "Mobile/Particles/Additive",
|
||||
"ShaderVariantElements": [
|
||||
{
|
||||
"PassType": 4,
|
||||
"Keywords": [
|
||||
"BILLBOARD_FACE_CAMERA_POS",
|
||||
"DIRECTIONAL",
|
||||
"LIGHTPROBE_SH",
|
||||
"SHADOWS_SCREEN",
|
||||
"SHADOWS_SOFT",
|
||||
"SHADOWS_SPLIT_SPHERES"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"PassType": 0,
|
||||
"Keywords": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"Keywords": [
|
||||
"SHADOWS_DEPTH",
|
||||
"SHADOWS_SOFT",
|
||||
"SHADOWS_SPLIT_SPHERES"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"AssetPath": "Assets/Samples/Basic Sample/GameArt/Shaders/StandardMobile.shader",
|
||||
"ShaderName": "Mobile/Standard",
|
||||
"AssetPath": "Resources/unity_builtin_extra",
|
||||
"ShaderName": "Unlit/Texture",
|
||||
"ShaderVariantElements": [
|
||||
{
|
||||
"PassType": 1,
|
||||
"PassType": 0,
|
||||
"Keywords": [
|
||||
"BILLBOARD_FACE_CAMERA_POS",
|
||||
"SHADOWS_SCREEN",
|
||||
"SHADOWS_SOFT",
|
||||
"SHADOWS_SPLIT_SPHERES",
|
||||
"_EMISSION",
|
||||
"_METALLICGLOSSMAP",
|
||||
"_NORMALMAP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"Keywords": [
|
||||
"_EMISSION",
|
||||
"_METALLICGLOSSMAP",
|
||||
"_NORMALMAP"
|
||||
]
|
||||
},
|
||||
{
|
||||
"PassType": 8,
|
||||
"Keywords": [
|
||||
"SHADOWS_DEPTH",
|
||||
"SHADOWS_SOFT",
|
||||
"SHADOWS_SPLIT_SPHERES",
|
||||
"_EMISSION",
|
||||
"_METALLICGLOSSMAP",
|
||||
"_NORMALMAP"
|
||||
"SHADOWS_SPLIT_SPHERES"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -13,10 +13,45 @@ ShaderVariantCollection:
|
|||
variants:
|
||||
- keywords:
|
||||
passType: 0
|
||||
- first: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
|
||||
second:
|
||||
variants:
|
||||
- keywords: DIRECTIONAL LIGHTPROBE_SH SHADOWS_SCREEN SHADOWS_SPLIT_SPHERES
|
||||
passType: 4
|
||||
- keywords: DIRECTIONAL LIGHTPROBE_SH SHADOWS_SCREEN SHADOWS_SPLIT_SPHERES
|
||||
_EMISSION
|
||||
passType: 4
|
||||
- keywords: DIRECTIONAL LIGHTPROBE_SH SHADOWS_SCREEN SHADOWS_SPLIT_SPHERES
|
||||
_NORMALMAP
|
||||
passType: 4
|
||||
- keywords: DIRECTIONAL LIGHTPROBE_SH SHADOWS_SCREEN SHADOWS_SPLIT_SPHERES
|
||||
_EMISSION _NORMALMAP
|
||||
passType: 4
|
||||
- keywords:
|
||||
passType: 8
|
||||
- keywords: SHADOWS_DEPTH SHADOWS_SPLIT_SPHERES
|
||||
passType: 8
|
||||
- keywords: _EMISSION
|
||||
passType: 8
|
||||
- keywords: SHADOWS_DEPTH SHADOWS_SPLIT_SPHERES _EMISSION
|
||||
passType: 8
|
||||
- keywords: _NORMALMAP
|
||||
passType: 8
|
||||
- keywords: SHADOWS_DEPTH SHADOWS_SPLIT_SPHERES _NORMALMAP
|
||||
passType: 8
|
||||
- keywords: _EMISSION _NORMALMAP
|
||||
passType: 8
|
||||
- keywords: SHADOWS_DEPTH SHADOWS_SPLIT_SPHERES _EMISSION _NORMALMAP
|
||||
passType: 8
|
||||
- first: {fileID: 106, guid: 0000000000000000f000000000000000, type: 0}
|
||||
second:
|
||||
variants:
|
||||
- keywords: BILLBOARD_FACE_CAMERA_POS _SUNDISK_SIMPLE
|
||||
- keywords: _SUNDISK_SIMPLE
|
||||
passType: 0
|
||||
- first: {fileID: 200, guid: 0000000000000000f000000000000000, type: 0}
|
||||
second:
|
||||
variants:
|
||||
- keywords:
|
||||
passType: 0
|
||||
- first: {fileID: 9000, guid: 0000000000000000f000000000000000, type: 0}
|
||||
second:
|
||||
|
@ -43,39 +78,23 @@ ShaderVariantCollection:
|
|||
variants:
|
||||
- keywords:
|
||||
passType: 0
|
||||
- first: {fileID: 9006, guid: 0000000000000000f000000000000000, type: 0}
|
||||
second:
|
||||
variants:
|
||||
- keywords:
|
||||
passType: 0
|
||||
- first: {fileID: 9007, guid: 0000000000000000f000000000000000, type: 0}
|
||||
second:
|
||||
variants:
|
||||
- keywords:
|
||||
passType: 0
|
||||
- first: {fileID: 9101, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- first: {fileID: 10720, guid: 0000000000000000f000000000000000, type: 0}
|
||||
second:
|
||||
variants:
|
||||
- keywords:
|
||||
passType: 0
|
||||
- first: {fileID: 9103, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- first: {fileID: 10752, guid: 0000000000000000f000000000000000, type: 0}
|
||||
second:
|
||||
variants:
|
||||
- keywords: BILLBOARD_FACE_CAMERA_POS
|
||||
- keywords: SHADOWS_SCREEN SHADOWS_SPLIT_SPHERES
|
||||
passType: 0
|
||||
- first: {fileID: 10703, guid: 0000000000000000f000000000000000, type: 0}
|
||||
second:
|
||||
variants:
|
||||
- keywords: BILLBOARD_FACE_CAMERA_POS DIRECTIONAL LIGHTPROBE_SH SHADOWS_SCREEN
|
||||
SHADOWS_SOFT SHADOWS_SPLIT_SPHERES
|
||||
passType: 4
|
||||
- keywords:
|
||||
passType: 8
|
||||
- keywords: SHADOWS_DEPTH SHADOWS_SOFT SHADOWS_SPLIT_SPHERES
|
||||
passType: 8
|
||||
- first: {fileID: 4800000, guid: ba67c8b1d5e59dc428ad9fc9270f8353, type: 3}
|
||||
second:
|
||||
variants:
|
||||
- keywords: BILLBOARD_FACE_CAMERA_POS SHADOWS_SCREEN SHADOWS_SOFT SHADOWS_SPLIT_SPHERES
|
||||
_EMISSION _METALLICGLOSSMAP _NORMALMAP
|
||||
passType: 1
|
||||
- keywords: _EMISSION _METALLICGLOSSMAP _NORMALMAP
|
||||
passType: 8
|
||||
- keywords: SHADOWS_DEPTH SHADOWS_SOFT SHADOWS_SPLIT_SPHERES _EMISSION _METALLICGLOSSMAP
|
||||
_NORMALMAP
|
||||
passType: 8
|
||||
|
|
|
@ -18,9 +18,6 @@ public class Boot : MonoBehaviour
|
|||
}
|
||||
void Start()
|
||||
{
|
||||
// 初始化BetterStreaming
|
||||
BetterStreamingAssets.Initialize();
|
||||
|
||||
// 初始化事件系统
|
||||
UniEvent.Initalize();
|
||||
|
||||
|
|
|
@ -122,9 +122,8 @@ internal class FsmInitialize : IStateNode
|
|||
{
|
||||
public bool QueryStreamingAssets(string fileName)
|
||||
{
|
||||
// 注意:使用了BetterStreamingAssets插件,使用前需要初始化该插件!
|
||||
string buildinFolderName = YooAssets.GetStreamingAssetBuildinFolderName();
|
||||
return BetterStreamingAssets.FileExists($"{buildinFolderName}/{fileName}");
|
||||
return StreamingAssetsHelper.FileExists($"{buildinFolderName}/{fileName}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
/[Ll]ibrary/
|
||||
/[Tt]emp/
|
||||
/[Oo]bj/
|
||||
/[Bb]uild/
|
||||
/[Bb]uilds/
|
||||
/Assets/AssetStoreTools*
|
||||
|
||||
# Visual Studio 2015 cache directory
|
||||
/.vs/
|
||||
|
||||
# Autogenerated VS/MD/Consulo solution and project files
|
||||
ExportedObj/
|
||||
.consulo/
|
||||
*.csproj
|
||||
*.unityproj
|
||||
*.sln
|
||||
*.suo
|
||||
*.tmp
|
||||
*.user
|
||||
*.userprefs
|
||||
*.pidb
|
||||
*.booproj
|
||||
*.svd
|
||||
*.pdb
|
||||
|
||||
# Unity3D generated meta files
|
||||
*.pidb.meta
|
||||
|
||||
# Unity3D Generated File On Crash Reports
|
||||
sysinfo.txt
|
||||
|
||||
# Builds
|
||||
*.apk
|
||||
*.unitypackage
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 gwiazdorrr
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,117 +0,0 @@
|
|||
# Better Streaming Assets
|
||||
|
||||
Better Streaming Assets is a plugin that lets you access Streaming Assets directly in an uniform and thread-safe way, with tiny overhead. Mostly beneficial for Android projects, where the alternatives are to use archaic and hugely inefficient WWW or embed data in Asset Bundles. API is based on Syste.IO.File and System.IO.Directory classes.
|
||||
|
||||
# Note on Android & App Bundles
|
||||
|
||||
App Bundles (.aab) builds are bugged when it comes to Streaming Assets. See https://github.com/gwiazdorrr/BetterStreamingAssets/issues/10 for details. The bottom line is:
|
||||
|
||||
⚠️ **Keep all file names in Streaming Assets lowercase!** ⚠️
|
||||
|
||||
# Getting started
|
||||
|
||||
This plugin can be installed in following ways:
|
||||
* Select "Add package from git URL..." in the Unity Package Manager and use this URL: `https://github.com/gwiazdorrr/BetterStreamingAssets.git`
|
||||
* Clone this repository and copy `Runtime` directory to your project.
|
||||
* Download the latest release from the [Asset Store](https://assetstore.unity.com/packages/tools/input-management/better-streaming-assets-103788).
|
||||
|
||||
# Usage
|
||||
|
||||
Check examples below. Note that all the paths are relative to StreamingAssets directory. That is, if you have files
|
||||
|
||||
```
|
||||
<project>/Assets/StreamingAssets/foo.bar
|
||||
<project>/Assets/StreamingAssets/dir/foo.bar
|
||||
````
|
||||
|
||||
You are expected to use following paths:
|
||||
|
||||
```
|
||||
foo.bar (or /foo.bar)
|
||||
dir/foo.bar (or /dir/foo.bar)
|
||||
```
|
||||
|
||||
# Examples
|
||||
|
||||
Initialization (before first use, needs to be called on main thread):
|
||||
|
||||
```csharp
|
||||
BetterStreamingAssets.Initialize();
|
||||
```
|
||||
|
||||
Typical scenario, deserializing from Xml:
|
||||
|
||||
```csharp
|
||||
public static Foo ReadFromXml(string path)
|
||||
{
|
||||
if ( !BetterStreamingAssets.FileExists(path) )
|
||||
{
|
||||
Debug.LogErrorFormat("Streaming asset not found: {0}", path);
|
||||
return null;
|
||||
}
|
||||
|
||||
using ( var stream = BetterStreamingAssets.OpenRead(path) )
|
||||
{
|
||||
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(Foo));
|
||||
return (Foo)serializer.Deserialize(stream);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that ReadFromXml can be called from any thread, as long as Foo's constructor doesn't make any UnityEngine calls.
|
||||
|
||||
Listing all Streaming Assets in with .xml extension:
|
||||
|
||||
```csharp
|
||||
// all the xmls
|
||||
string[] paths = BetterStreamingAssets.GetFiles("\\", "*.xml", SearchOption.AllDirectories);
|
||||
// just xmls in Config directory (and nested)
|
||||
string[] paths = BetterStreamingAssets.GetFiles("Config", "*.xml", SearchOption.AllDirectories);
|
||||
```
|
||||
|
||||
Checking if a directory exists:
|
||||
|
||||
```csharp
|
||||
Debug.Assert( BetterStreamingAssets.DirectoryExists("Config") );
|
||||
```
|
||||
|
||||
Ways of reading a file:
|
||||
|
||||
```csharp
|
||||
// all at once
|
||||
byte[] data = BetterStreamingAssets.ReadAllBytes("Foo/bar.data");
|
||||
|
||||
// as stream, last 10 bytes
|
||||
byte[] footer = new byte[10];
|
||||
using (var stream = BetterStreamingAssets.OpenRead("Foo/bar.data"))
|
||||
{
|
||||
stream.Seek(-footer.Length, SeekOrigin.End);
|
||||
stream.Read(footer, 0, footer.Length);
|
||||
}
|
||||
```
|
||||
|
||||
Asset bundles (again, main thread only):
|
||||
|
||||
```csharp
|
||||
// synchronous
|
||||
var bundle = BetterStreamingAssets.LoadAssetBundle(path);
|
||||
// async
|
||||
var bundleOp = BetterStreamingAssets.LoadAssetBundleAsync(path);
|
||||
```
|
||||
|
||||
# (Android) False-positive compressed Streaming Assets messages
|
||||
|
||||
Streaming Assets end up in the same part of APK as files added by many custom plugins (`assets` directory), so it is impossible to tell whether a compressed file is a Streaming Asset (an indication something has gone terribly wrong) or not. This tool acts conservatively and logs errors whenever it finds a compressed file inside of `assets`, but outside of `assets/bin`. If you are annoyed by this and are certain a compressed file was not meant to be a Streaming Asset, add a file like this in the same assembly as Better Streaming Assets:
|
||||
|
||||
```csharp
|
||||
partial class BetterStreamingAssets
|
||||
{
|
||||
static partial void AndroidIsCompressedFileStreamingAsset(string path, ref bool result)
|
||||
{
|
||||
if ( path == "assets/my_custom_plugin_settings.json")
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c59a049ac5cdb4a48996edcefd9ac9c7
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,663 +0,0 @@
|
|||
// Better Streaming Assets, Piotr Gwiazdowski <gwiazdorrr+github at gmail.com>, 2017
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Better;
|
||||
using Better.StreamingAssets;
|
||||
using Better.StreamingAssets.ZipArchive;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using BetterStreamingAssetsImp = BetterStreamingAssets.EditorImpl;
|
||||
#elif UNITY_ANDROID
|
||||
using BetterStreamingAssetsImp = BetterStreamingAssets.ApkImpl;
|
||||
#else
|
||||
using BetterStreamingAssetsImp = BetterStreamingAssets.LooseFilesImpl;
|
||||
#endif
|
||||
|
||||
public static partial class BetterStreamingAssets
|
||||
{
|
||||
internal struct ReadInfo
|
||||
{
|
||||
public string readPath;
|
||||
public long size;
|
||||
public long offset;
|
||||
public uint crc32;
|
||||
}
|
||||
|
||||
public static string Root
|
||||
{
|
||||
get { return BetterStreamingAssetsImp.s_root; }
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
BetterStreamingAssetsImp.Initialize(Application.dataPath, Application.streamingAssetsPath);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static void InitializeWithExternalApk(string apkPath)
|
||||
{
|
||||
BetterStreamingAssetsImp.ApkMode = true;
|
||||
BetterStreamingAssetsImp.Initialize(apkPath, "jar:file://" + apkPath + "!/assets/");
|
||||
}
|
||||
|
||||
public static void InitializeWithExternalDirectories(string dataPath, string streamingAssetsPath)
|
||||
{
|
||||
BetterStreamingAssetsImp.ApkMode = false;
|
||||
BetterStreamingAssetsImp.Initialize(dataPath, streamingAssetsPath);
|
||||
}
|
||||
#endif
|
||||
|
||||
public static bool FileExists(string path)
|
||||
{
|
||||
ReadInfo info;
|
||||
return BetterStreamingAssetsImp.TryGetInfo(path, out info);
|
||||
}
|
||||
|
||||
public static bool DirectoryExists(string path)
|
||||
{
|
||||
return BetterStreamingAssetsImp.DirectoryExists(path);
|
||||
}
|
||||
|
||||
public static AssetBundleCreateRequest LoadAssetBundleAsync(string path, uint crc = 0)
|
||||
{
|
||||
var info = GetInfoOrThrow(path);
|
||||
return AssetBundle.LoadFromFileAsync(info.readPath, crc, (ulong)info.offset);
|
||||
}
|
||||
|
||||
public static AssetBundle LoadAssetBundle(string path, uint crc = 0)
|
||||
{
|
||||
var info = GetInfoOrThrow(path);
|
||||
return AssetBundle.LoadFromFile(info.readPath, crc, (ulong)info.offset);
|
||||
}
|
||||
|
||||
public static System.IO.Stream OpenRead(string path)
|
||||
{
|
||||
if ( path == null )
|
||||
throw new ArgumentNullException("path");
|
||||
if ( path.Length == 0 )
|
||||
throw new ArgumentException("Empty path", "path");
|
||||
|
||||
return BetterStreamingAssetsImp.OpenRead(path);
|
||||
}
|
||||
|
||||
public static System.IO.StreamReader OpenText(string path)
|
||||
{
|
||||
Stream str = OpenRead(path);
|
||||
try
|
||||
{
|
||||
return new StreamReader(str);
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
if (str != null)
|
||||
str.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReadAllText(string path)
|
||||
{
|
||||
using ( var sr = OpenText(path) )
|
||||
{
|
||||
return sr.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public static string[] ReadAllLines(string path)
|
||||
{
|
||||
string line;
|
||||
var lines = new List<string>();
|
||||
|
||||
using ( var sr = OpenText(path) )
|
||||
{
|
||||
while ( ( line = sr.ReadLine() ) != null )
|
||||
{
|
||||
lines.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] ReadAllBytes(string path)
|
||||
{
|
||||
if ( path == null )
|
||||
throw new ArgumentNullException("path");
|
||||
if ( path.Length == 0 )
|
||||
throw new ArgumentException("Empty path", "path");
|
||||
|
||||
return BetterStreamingAssetsImp.ReadAllBytes(path);
|
||||
}
|
||||
|
||||
public static string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
|
||||
{
|
||||
return BetterStreamingAssetsImp.GetFiles(path, searchPattern, searchOption);
|
||||
}
|
||||
|
||||
public static string[] GetFiles(string path)
|
||||
{
|
||||
return GetFiles(path, null);
|
||||
}
|
||||
|
||||
public static string[] GetFiles(string path, string searchPattern)
|
||||
{
|
||||
return GetFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
|
||||
private static ReadInfo GetInfoOrThrow(string path)
|
||||
{
|
||||
ReadInfo result;
|
||||
if ( !BetterStreamingAssetsImp.TryGetInfo(path, out result) )
|
||||
ThrowFileNotFound(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ThrowFileNotFound(string path)
|
||||
{
|
||||
throw new FileNotFoundException("File not found", path);
|
||||
}
|
||||
|
||||
static partial void AndroidIsCompressedFileStreamingAsset(string path, ref bool result);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal static class EditorImpl
|
||||
{
|
||||
public static bool ApkMode = false;
|
||||
|
||||
public static string s_root
|
||||
{
|
||||
get { return ApkMode ? ApkImpl.s_root : LooseFilesImpl.s_root; }
|
||||
}
|
||||
|
||||
internal static void Initialize(string dataPath, string streamingAssetsPath)
|
||||
{
|
||||
if ( ApkMode )
|
||||
{
|
||||
ApkImpl.Initialize(dataPath, streamingAssetsPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
LooseFilesImpl.Initialize(dataPath, streamingAssetsPath);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool TryGetInfo(string path, out ReadInfo info)
|
||||
{
|
||||
if ( ApkMode )
|
||||
return ApkImpl.TryGetInfo(path, out info);
|
||||
else
|
||||
return LooseFilesImpl.TryGetInfo(path, out info);
|
||||
}
|
||||
|
||||
internal static bool DirectoryExists(string path)
|
||||
{
|
||||
if ( ApkMode )
|
||||
return ApkImpl.DirectoryExists(path);
|
||||
else
|
||||
return LooseFilesImpl.DirectoryExists(path);
|
||||
}
|
||||
|
||||
internal static Stream OpenRead(string path)
|
||||
{
|
||||
if ( ApkMode )
|
||||
return ApkImpl.OpenRead(path);
|
||||
else
|
||||
return LooseFilesImpl.OpenRead(path);
|
||||
}
|
||||
|
||||
internal static byte[] ReadAllBytes(string path)
|
||||
{
|
||||
if ( ApkMode )
|
||||
return ApkImpl.ReadAllBytes(path);
|
||||
else
|
||||
return LooseFilesImpl.ReadAllBytes(path);
|
||||
}
|
||||
|
||||
internal static string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
|
||||
{
|
||||
if ( ApkMode )
|
||||
return ApkImpl.GetFiles(path, searchPattern, searchOption);
|
||||
else
|
||||
return LooseFilesImpl.GetFiles(path, searchPattern, searchOption);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR || !UNITY_ANDROID
|
||||
internal static class LooseFilesImpl
|
||||
{
|
||||
public static string s_root;
|
||||
private static string[] s_emptyArray = new string[0];
|
||||
|
||||
public static void Initialize(string dataPath, string streamingAssetsPath)
|
||||
{
|
||||
s_root = Path.GetFullPath(streamingAssetsPath).Replace('\\', '/').TrimEnd('/');
|
||||
}
|
||||
|
||||
public static string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
|
||||
{
|
||||
if (!Directory.Exists(s_root))
|
||||
return s_emptyArray;
|
||||
|
||||
// this will throw if something is fishy
|
||||
path = PathUtil.NormalizeRelativePath(path, forceTrailingSlash : true);
|
||||
|
||||
Debug.Assert(s_root.Last() != '\\' && s_root.Last() != '/' && path.StartsWith("/"));
|
||||
|
||||
var files = Directory.GetFiles(s_root + path, searchPattern ?? "*", searchOption);
|
||||
|
||||
for ( int i = 0; i < files.Length; ++i )
|
||||
{
|
||||
Debug.Assert(files[i].StartsWith(s_root));
|
||||
files[i] = files[i].Substring(s_root.Length + 1).Replace('\\', '/');
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// purge meta files
|
||||
{
|
||||
int j = 0;
|
||||
for ( int i = 0; i < files.Length; ++i )
|
||||
{
|
||||
if ( !files[i].EndsWith(".meta") )
|
||||
{
|
||||
files[j++] = files[i];
|
||||
}
|
||||
}
|
||||
Array.Resize(ref files, j);
|
||||
}
|
||||
|
||||
#endif
|
||||
return files;
|
||||
}
|
||||
|
||||
public static bool TryGetInfo(string path, out ReadInfo info)
|
||||
{
|
||||
path = PathUtil.NormalizeRelativePath(path);
|
||||
|
||||
info = new ReadInfo();
|
||||
|
||||
var fullPath = s_root + path;
|
||||
if ( !File.Exists(fullPath) )
|
||||
return false;
|
||||
|
||||
info.readPath = fullPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool DirectoryExists(string path)
|
||||
{
|
||||
var normalized = PathUtil.NormalizeRelativePath(path);
|
||||
return Directory.Exists(s_root + normalized);
|
||||
}
|
||||
|
||||
public static byte[] ReadAllBytes(string path)
|
||||
{
|
||||
ReadInfo info;
|
||||
|
||||
if ( !TryGetInfo(path, out info) )
|
||||
ThrowFileNotFound(path);
|
||||
|
||||
return File.ReadAllBytes(info.readPath);
|
||||
}
|
||||
|
||||
public static System.IO.Stream OpenRead(string path)
|
||||
{
|
||||
ReadInfo info;
|
||||
if ( !TryGetInfo(path, out info) )
|
||||
ThrowFileNotFound(path);
|
||||
|
||||
Stream fileStream = File.OpenRead(info.readPath);
|
||||
try
|
||||
{
|
||||
return new SubReadOnlyStream(fileStream, leaveOpen: false);
|
||||
}
|
||||
catch ( System.Exception )
|
||||
{
|
||||
fileStream.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR || UNITY_ANDROID
|
||||
internal static class ApkImpl
|
||||
{
|
||||
private static string[] s_paths;
|
||||
private static PartInfo[] s_streamingAssets;
|
||||
public static string s_root;
|
||||
|
||||
private struct PartInfo
|
||||
{
|
||||
public long size;
|
||||
public long offset;
|
||||
public uint crc32;
|
||||
}
|
||||
|
||||
public static void Initialize(string dataPath, string streamingAssetsPath)
|
||||
{
|
||||
s_root = dataPath;
|
||||
|
||||
List<string> paths = new List<string>();
|
||||
List<PartInfo> parts = new List<PartInfo>();
|
||||
|
||||
GetStreamingAssetsInfoFromJar(s_root, paths, parts);
|
||||
|
||||
if (paths.Count == 0 && !Application.isEditor && Path.GetFileName(dataPath) != "base.apk")
|
||||
{
|
||||
// maybe split?
|
||||
var newDataPath = Path.GetDirectoryName(dataPath) + "/base.apk";
|
||||
if (File.Exists(newDataPath))
|
||||
{
|
||||
s_root = newDataPath;
|
||||
GetStreamingAssetsInfoFromJar(newDataPath, paths, parts);
|
||||
}
|
||||
}
|
||||
|
||||
s_paths = paths.ToArray();
|
||||
s_streamingAssets = parts.ToArray();
|
||||
}
|
||||
|
||||
public static bool TryGetInfo(string path, out ReadInfo info)
|
||||
{
|
||||
path = PathUtil.NormalizeRelativePath(path);
|
||||
info = new ReadInfo();
|
||||
|
||||
var index = Array.BinarySearch(s_paths, path, StringComparer.OrdinalIgnoreCase);
|
||||
if ( index < 0 )
|
||||
return false;
|
||||
|
||||
var dataInfo = s_streamingAssets[index];
|
||||
info.crc32 = dataInfo.crc32;
|
||||
info.offset = dataInfo.offset;
|
||||
info.size = dataInfo.size;
|
||||
info.readPath = s_root;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool DirectoryExists(string path)
|
||||
{
|
||||
var normalized = PathUtil.NormalizeRelativePath(path, forceTrailingSlash : true);
|
||||
var dirIndex = GetDirectoryIndex(normalized);
|
||||
return dirIndex >= 0 && dirIndex < s_paths.Length;
|
||||
}
|
||||
|
||||
public static string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
|
||||
{
|
||||
if ( path == null )
|
||||
throw new ArgumentNullException("path");
|
||||
|
||||
var actualDirPath = PathUtil.NormalizeRelativePath(path, forceTrailingSlash : true);
|
||||
|
||||
// find first file there
|
||||
var index = GetDirectoryIndex(actualDirPath);
|
||||
if ( index < 0 )
|
||||
throw new IOException();
|
||||
if ( index == s_paths.Length )
|
||||
throw new DirectoryNotFoundException();
|
||||
|
||||
Predicate<string> filter;
|
||||
if ( string.IsNullOrEmpty(searchPattern) || searchPattern == "*" )
|
||||
{
|
||||
filter = null;
|
||||
}
|
||||
else if ( searchPattern.IndexOf('*') >= 0 || searchPattern.IndexOf('?') >= 0 )
|
||||
{
|
||||
var regex = PathUtil.WildcardToRegex(searchPattern);
|
||||
filter = (x) => regex.IsMatch(x);
|
||||
}
|
||||
else
|
||||
{
|
||||
filter = (x) => string.Compare(x, searchPattern, true) == 0;
|
||||
}
|
||||
|
||||
List<string> results = new List<string>();
|
||||
string fixedPath = null;
|
||||
|
||||
for ( int i = index; i < s_paths.Length; ++i )
|
||||
{
|
||||
var filePath = s_paths[i];
|
||||
|
||||
if ( !filePath.StartsWith(actualDirPath) )
|
||||
break;
|
||||
|
||||
string fileName;
|
||||
|
||||
var dirSeparatorIndex = filePath.LastIndexOf('/', filePath.Length - 1, filePath.Length - actualDirPath.Length);
|
||||
if ( dirSeparatorIndex >= 0 )
|
||||
{
|
||||
if ( searchOption == SearchOption.TopDirectoryOnly )
|
||||
continue;
|
||||
|
||||
fileName = filePath.Substring(dirSeparatorIndex + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileName = filePath.Substring(actualDirPath.Length);
|
||||
}
|
||||
|
||||
// now do a match
|
||||
if ( filter == null || filter(fileName) )
|
||||
{
|
||||
var normalizedPart = filePath.Substring(actualDirPath.Length);
|
||||
|
||||
if ( fixedPath == null )
|
||||
{
|
||||
fixedPath = PathUtil.FixTrailingDirectorySeparators(path);
|
||||
if ( fixedPath == "/" )
|
||||
fixedPath = string.Empty;
|
||||
}
|
||||
|
||||
var result = PathUtil.CombineSlash(fixedPath, normalizedPart);
|
||||
results.Add(result);
|
||||
}
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] ReadAllBytes(string path)
|
||||
{
|
||||
ReadInfo info;
|
||||
if ( !TryGetInfo(path, out info) )
|
||||
ThrowFileNotFound(path);
|
||||
|
||||
byte[] buffer;
|
||||
using ( var fileStream = File.OpenRead(info.readPath) )
|
||||
{
|
||||
if ( info.offset != 0 )
|
||||
{
|
||||
if ( fileStream.Seek(info.offset, SeekOrigin.Begin) != info.offset )
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
if ( info.size > (long)int.MaxValue )
|
||||
throw new IOException();
|
||||
|
||||
int count = (int)info.size;
|
||||
int offset = 0;
|
||||
|
||||
buffer = new byte[count];
|
||||
while ( count > 0 )
|
||||
{
|
||||
int num = fileStream.Read(buffer, offset, count);
|
||||
if ( num == 0 )
|
||||
throw new EndOfStreamException();
|
||||
offset += num;
|
||||
count -= num;
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static System.IO.Stream OpenRead(string path)
|
||||
{
|
||||
ReadInfo info;
|
||||
if ( !TryGetInfo(path, out info) )
|
||||
ThrowFileNotFound(path);
|
||||
|
||||
Stream fileStream = File.OpenRead(info.readPath);
|
||||
try
|
||||
{
|
||||
return new SubReadOnlyStream(fileStream, info.offset, info.size, leaveOpen : false);
|
||||
}
|
||||
catch ( System.Exception )
|
||||
{
|
||||
fileStream.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetDirectoryIndex(string path)
|
||||
{
|
||||
Debug.Assert(s_paths != null);
|
||||
|
||||
// find first file there
|
||||
var index = Array.BinarySearch(s_paths, path, StringComparer.OrdinalIgnoreCase);
|
||||
if ( index >= 0 )
|
||||
return ~index;
|
||||
|
||||
// if the end, no such directory exists
|
||||
index = ~index;
|
||||
if ( index == s_paths.Length )
|
||||
return index;
|
||||
|
||||
for ( int i = index; i < s_paths.Length && s_paths[i].StartsWith(path); ++i )
|
||||
{
|
||||
// because otherwise there would be a match
|
||||
Debug.Assert(s_paths[i].Length > path.Length);
|
||||
|
||||
if ( path[path.Length - 1] == '/' )
|
||||
return i;
|
||||
|
||||
if ( s_paths[i][path.Length] == '/' )
|
||||
return i;
|
||||
}
|
||||
|
||||
return s_paths.Length;
|
||||
}
|
||||
|
||||
private static void GetStreamingAssetsInfoFromJar(string apkPath, List<string> paths, List<PartInfo> parts)
|
||||
{
|
||||
using ( var stream = File.OpenRead(apkPath) )
|
||||
using ( var reader = new BinaryReader(stream) )
|
||||
{
|
||||
if ( !stream.CanRead )
|
||||
throw new ArgumentException();
|
||||
if ( !stream.CanSeek )
|
||||
throw new ArgumentException();
|
||||
|
||||
long expectedNumberOfEntries;
|
||||
long centralDirectoryStart;
|
||||
ZipArchiveUtils.ReadEndOfCentralDirectory(stream, reader, out expectedNumberOfEntries, out centralDirectoryStart);
|
||||
|
||||
try
|
||||
{
|
||||
stream.Seek(centralDirectoryStart, SeekOrigin.Begin);
|
||||
|
||||
long numberOfEntries = 0;
|
||||
|
||||
ZipCentralDirectoryFileHeader header;
|
||||
|
||||
const int prefixLength = 7;
|
||||
const string prefix = "assets/";
|
||||
const string assetsPrefix = "assets/bin/";
|
||||
Debug.Assert(prefixLength == prefix.Length);
|
||||
|
||||
while ( ZipCentralDirectoryFileHeader.TryReadBlock(reader, out header) )
|
||||
{
|
||||
if ( header.CompressedSize != header.UncompressedSize )
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
var fileName = Encoding.UTF8.GetString(header.Filename);
|
||||
if (fileName.StartsWith(prefix) && !fileName.StartsWith(assetsPrefix))
|
||||
{
|
||||
bool isStreamingAsset = true;
|
||||
AndroidIsCompressedFileStreamingAsset(fileName, ref isStreamingAsset);
|
||||
if (isStreamingAsset)
|
||||
{
|
||||
Debug.LogAssertionFormat("BetterStreamingAssets: file {0} is where Streaming Assets are put, but is compressed. " +
|
||||
"If this is a App Bundle build, see README for a possible workaround. " +
|
||||
"If this file is not a Streaming Asset (has been on purpose by hand or by another plug-in), implement " +
|
||||
"BetterStreamingAssets.AndroidIsCompressedFileStreamingAsset partial method to prevent this message from appearing again. ",
|
||||
fileName);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// we only want uncompressed files
|
||||
}
|
||||
else
|
||||
{
|
||||
var fileName = Encoding.UTF8.GetString(header.Filename);
|
||||
|
||||
if (fileName.EndsWith("/"))
|
||||
{
|
||||
// there's some strangeness when it comes to OBB: directories are listed as files
|
||||
// simply ignoring them should be enough
|
||||
Debug.Assert(header.UncompressedSize == 0);
|
||||
}
|
||||
else if ( fileName.StartsWith(prefix) )
|
||||
{
|
||||
// ignore normal assets...
|
||||
if ( fileName.StartsWith(assetsPrefix) )
|
||||
{
|
||||
// Note: if you put bin directory in your StreamingAssets you will get false negative here
|
||||
}
|
||||
else
|
||||
{
|
||||
var relativePath = fileName.Substring(prefixLength - 1);
|
||||
var entry = new PartInfo()
|
||||
{
|
||||
crc32 = header.Crc32,
|
||||
offset = header.RelativeOffsetOfLocalHeader, // this offset will need fixing later on
|
||||
size = header.UncompressedSize
|
||||
};
|
||||
|
||||
var index = paths.BinarySearch(relativePath, StringComparer.OrdinalIgnoreCase);
|
||||
if ( index >= 0 )
|
||||
throw new System.InvalidOperationException("Paths duplicate! " + fileName);
|
||||
|
||||
paths.Insert(~index, relativePath);
|
||||
parts.Insert(~index, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
numberOfEntries++;
|
||||
}
|
||||
|
||||
if ( numberOfEntries != expectedNumberOfEntries )
|
||||
throw new ZipArchiveException("Number of entries does not match");
|
||||
|
||||
}
|
||||
catch ( EndOfStreamException ex )
|
||||
{
|
||||
throw new ZipArchiveException("CentralDirectoryInvalid", ex);
|
||||
}
|
||||
|
||||
// now fix offsets
|
||||
for ( int i = 0; i < parts.Count; ++i )
|
||||
{
|
||||
var entry = parts[i];
|
||||
stream.Seek(entry.offset, SeekOrigin.Begin);
|
||||
|
||||
if ( !ZipLocalFileHeader.TrySkipBlock(reader) )
|
||||
throw new ZipArchiveException("Local file header corrupt");
|
||||
|
||||
entry.offset = stream.Position;
|
||||
|
||||
parts[i] = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
// Better Streaming Assets, Piotr Gwiazdowski <gwiazdorrr+github at gmail.com>, 2017
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Better.StreamingAssets
|
||||
{
|
||||
public static partial class PathUtil
|
||||
{
|
||||
private enum NormalizeState
|
||||
{
|
||||
PrevSlash,
|
||||
PrevDot,
|
||||
PrevDoubleDot,
|
||||
NothingSpecial,
|
||||
}
|
||||
|
||||
public static bool IsDirectorySeparator(char c)
|
||||
{
|
||||
return c == '/' || c == '\\';
|
||||
}
|
||||
|
||||
public static string FixTrailingDirectorySeparators(string path)
|
||||
{
|
||||
if ( path.Length >= 2 )
|
||||
{
|
||||
var lastChar = path[path.Length - 1];
|
||||
var prevChar = path[path.Length - 2];
|
||||
if ( PathUtil.IsDirectorySeparator(lastChar) && PathUtil.IsDirectorySeparator(prevChar) )
|
||||
{
|
||||
return path.TrimEnd('\\', '/') + lastChar;
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string CombineSlash(string a, string b)
|
||||
{
|
||||
if ( a == null )
|
||||
throw new ArgumentNullException("a");
|
||||
if ( b == null )
|
||||
throw new ArgumentNullException("b");
|
||||
|
||||
if ( string.IsNullOrEmpty(b) )
|
||||
return a;
|
||||
if ( string.IsNullOrEmpty(a) )
|
||||
return b;
|
||||
|
||||
if (b[0] == '/')
|
||||
return b;
|
||||
|
||||
if ( IsDirectorySeparator(a[a.Length -1]) )
|
||||
return a + b;
|
||||
else
|
||||
return a + '/' + b;
|
||||
}
|
||||
|
||||
public static string NormalizeRelativePath(string relative, bool forceTrailingSlash = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(relative))
|
||||
throw new System.ArgumentException("Empty or null", "relative");
|
||||
|
||||
StringBuilder output = new StringBuilder(relative.Length);
|
||||
|
||||
NormalizeState state = NormalizeState.PrevSlash;
|
||||
output.Append('/');
|
||||
|
||||
int startIndex = 0;
|
||||
int lastIndexPlus1 = relative.Length;
|
||||
|
||||
if ( relative[0] == '"' && relative.Length > 2 && relative[relative.Length - 1] == '"')
|
||||
{
|
||||
startIndex++;
|
||||
lastIndexPlus1--;
|
||||
}
|
||||
|
||||
for ( int i = startIndex; i <= lastIndexPlus1; ++i )
|
||||
{
|
||||
if (i == lastIndexPlus1 || relative[i] == '/' || relative[i] == '\\')
|
||||
{
|
||||
if ( state == NormalizeState.PrevSlash || state == NormalizeState.PrevDot )
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
else if ( state == NormalizeState.PrevDoubleDot )
|
||||
{
|
||||
if ( output.Length == 1 )
|
||||
throw new System.IO.IOException("Invalid path: double dot error (before " + i + ")");
|
||||
|
||||
// on level up!
|
||||
int j;
|
||||
for ( j = output.Length - 2; j >= 0 && output[j] != '/'; --j)
|
||||
{
|
||||
}
|
||||
|
||||
output.Remove(j + 1, output.Length - j - 1);
|
||||
}
|
||||
else if ( i < lastIndexPlus1 || forceTrailingSlash )
|
||||
{
|
||||
output.Append('/');
|
||||
}
|
||||
|
||||
state = NormalizeState.PrevSlash;
|
||||
}
|
||||
else if ( relative[i] == '.' )
|
||||
{
|
||||
if ( state == NormalizeState.PrevSlash )
|
||||
{
|
||||
state = NormalizeState.PrevDot;
|
||||
}
|
||||
else if ( state == NormalizeState.PrevDot )
|
||||
{
|
||||
state = NormalizeState.PrevDoubleDot;
|
||||
}
|
||||
else if ( state == NormalizeState.PrevDoubleDot )
|
||||
{
|
||||
state = NormalizeState.NothingSpecial;
|
||||
output.Append("...");
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Append('.');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( state == NormalizeState.PrevDot )
|
||||
{
|
||||
output.Append('.');
|
||||
}
|
||||
else if ( state == NormalizeState.PrevDoubleDot )
|
||||
{
|
||||
output.Append("..");
|
||||
}
|
||||
|
||||
if (!IsValidCharacter(relative[i]))
|
||||
throw new System.IO.IOException("Invalid characters");
|
||||
|
||||
output.Append(relative[i]);
|
||||
state = NormalizeState.NothingSpecial;
|
||||
}
|
||||
}
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
public static bool IsValidCharacter(char c)
|
||||
{
|
||||
if (c == '\"' || c == '<' || c == '>' || c == '|' || c < 32 || c == ':' || c == '*' || c == '?')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Regex WildcardToRegex(string pattern)
|
||||
{
|
||||
return new Regex("^" + Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$", RegexOptions.IgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
// Better Streaming Assets, Piotr Gwiazdowski <gwiazdorrr+github at gmail.com>, 2017
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Better.StreamingAssets
|
||||
{
|
||||
internal class SubReadOnlyStream: Stream
|
||||
{
|
||||
private readonly long m_offset;
|
||||
private readonly bool m_leaveOpen;
|
||||
|
||||
private long? m_length;
|
||||
private Stream m_actualStream;
|
||||
private long m_position;
|
||||
|
||||
public SubReadOnlyStream(Stream actualStream, bool leaveOpen = false)
|
||||
{
|
||||
if (actualStream == null)
|
||||
throw new ArgumentNullException("superStream");
|
||||
|
||||
m_actualStream = actualStream;
|
||||
m_leaveOpen = leaveOpen;
|
||||
}
|
||||
|
||||
public SubReadOnlyStream(Stream actualStream, long offset, long length, bool leaveOpen = false)
|
||||
: this(actualStream, leaveOpen)
|
||||
{
|
||||
if (offset < 0)
|
||||
throw new ArgumentOutOfRangeException("offset");
|
||||
|
||||
if (length < 0)
|
||||
throw new ArgumentOutOfRangeException("length");
|
||||
|
||||
Debug.Assert(offset <= actualStream.Length);
|
||||
Debug.Assert(actualStream.Length >= length);
|
||||
Debug.Assert(offset + length <= actualStream.Length);
|
||||
|
||||
m_offset = offset;
|
||||
m_position = offset;
|
||||
m_length = length;
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (!m_length.HasValue)
|
||||
m_length = m_actualStream.Length - m_offset;
|
||||
|
||||
return m_length.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return m_position - m_offset;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
m_position = m_offset + value;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanRead { get { return m_actualStream.CanRead; } }
|
||||
|
||||
public override bool CanSeek { get { return m_actualStream.CanSeek; } }
|
||||
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
ThrowIfCantRead();
|
||||
ThrowIfDisposed();
|
||||
|
||||
if ( m_actualStream.Position != m_position )
|
||||
m_actualStream.Seek(m_position, SeekOrigin.Begin);
|
||||
|
||||
if ( m_length.HasValue )
|
||||
{
|
||||
var endPosition = m_offset + m_length.Value;
|
||||
if (m_position + count > endPosition)
|
||||
{
|
||||
count = (int)(endPosition - m_position);
|
||||
}
|
||||
}
|
||||
|
||||
int bytesRead = m_actualStream.Read(buffer, offset, count);
|
||||
m_position += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if ( origin == SeekOrigin.Begin )
|
||||
{
|
||||
m_position = m_actualStream.Seek(m_offset + offset, SeekOrigin.Begin);
|
||||
}
|
||||
else if ( origin == SeekOrigin.End )
|
||||
{
|
||||
m_position = m_actualStream.Seek(m_offset + Length + offset, SeekOrigin.Begin);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_position = m_actualStream.Seek(offset, SeekOrigin.Current);
|
||||
}
|
||||
return m_position - m_offset;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
// Close the stream for reading. Note that this does NOT close the superStream (since
|
||||
// the substream is just 'a chunk' of the super-stream
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if ( disposing )
|
||||
{
|
||||
if (m_actualStream != null)
|
||||
{
|
||||
if (!m_leaveOpen)
|
||||
m_actualStream.Dispose();
|
||||
|
||||
m_actualStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (m_actualStream == null)
|
||||
throw new ObjectDisposedException(GetType().ToString(), "");
|
||||
}
|
||||
|
||||
private void ThrowIfCantRead()
|
||||
{
|
||||
if (!CanRead)
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 23985058976064b4aa47f955e25b32fb
|
||||
timeCreated: 1506207467
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,649 +0,0 @@
|
|||
// Better Streaming Assets, Piotr Gwiazdowski <gwiazdorrr+github at gmail.com>, 2017
|
||||
// Bits below are copied from or inspired by System.IO.Compression.dll; leaving comments from
|
||||
// original source code and attaching license
|
||||
|
||||
// The MIT License(MIT)
|
||||
//
|
||||
// Copyright(c) .NET Foundation and Contributors
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Better.StreamingAssets.ZipArchive
|
||||
{
|
||||
// All blocks.TryReadBlock do a check to see if signature is correct. Generic extra field is slightly different
|
||||
// all of the TryReadBlocks will throw if there are not enough bytes in the stream
|
||||
|
||||
internal struct ZipGenericExtraField
|
||||
{
|
||||
private const int SizeOfHeader = 4;
|
||||
|
||||
private ushort _tag;
|
||||
private ushort _size;
|
||||
private byte[] _data;
|
||||
|
||||
public ushort Tag { get { return _tag; } }
|
||||
// returns size of data, not of the entire block
|
||||
public ushort Size { get { return _size; } }
|
||||
public byte[] Data { get { return _data; } }
|
||||
|
||||
// shouldn't ever read the byte at position endExtraField
|
||||
// assumes we are positioned at the beginning of an extra field subfield
|
||||
public static bool TryReadBlock(BinaryReader reader, long endExtraField, out ZipGenericExtraField field)
|
||||
{
|
||||
field = new ZipGenericExtraField();
|
||||
|
||||
// not enough bytes to read tag + size
|
||||
if ( endExtraField - reader.BaseStream.Position < 4 )
|
||||
return false;
|
||||
|
||||
field._tag = reader.ReadUInt16();
|
||||
field._size = reader.ReadUInt16();
|
||||
|
||||
// not enough bytes to read the data
|
||||
if ( endExtraField - reader.BaseStream.Position < field._size )
|
||||
return false;
|
||||
|
||||
field._data = reader.ReadBytes(field._size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal struct Zip64ExtraField
|
||||
{
|
||||
// Size is size of the record not including the tag or size fields
|
||||
// If the extra field is going in the local header, it cannot include only
|
||||
// one of uncompressed/compressed size
|
||||
|
||||
public const int OffsetToFirstField = 4;
|
||||
private const ushort TagConstant = 1;
|
||||
|
||||
private ushort _size;
|
||||
private long? _uncompressedSize;
|
||||
private long? _compressedSize;
|
||||
private long? _localHeaderOffset;
|
||||
private int? _startDiskNumber;
|
||||
|
||||
|
||||
public long? UncompressedSize
|
||||
{
|
||||
get { return _uncompressedSize; }
|
||||
set { _uncompressedSize = value; UpdateSize(); }
|
||||
}
|
||||
public long? CompressedSize
|
||||
{
|
||||
get { return _compressedSize; }
|
||||
set { _compressedSize = value; UpdateSize(); }
|
||||
}
|
||||
public long? LocalHeaderOffset
|
||||
{
|
||||
get { return _localHeaderOffset; }
|
||||
set { _localHeaderOffset = value; UpdateSize(); }
|
||||
}
|
||||
public int? StartDiskNumber { get { return _startDiskNumber; } }
|
||||
|
||||
private void UpdateSize()
|
||||
{
|
||||
_size = 0;
|
||||
if ( _uncompressedSize != null ) _size += 8;
|
||||
if ( _compressedSize != null ) _size += 8;
|
||||
if ( _localHeaderOffset != null ) _size += 8;
|
||||
if ( _startDiskNumber != null ) _size += 4;
|
||||
}
|
||||
|
||||
// There is a small chance that something very weird could happen here. The code calling into this function
|
||||
// will ask for a value from the extra field if the field was masked with FF's. It's theoretically possible
|
||||
// that a field was FF's legitimately, and the writer didn't decide to write the corresponding extra field.
|
||||
// Also, at the same time, other fields were masked with FF's to indicate looking in the zip64 record.
|
||||
// Then, the search for the zip64 record will fail because the expected size is wrong,
|
||||
// and a nulled out Zip64ExtraField will be returned. Thus, even though there was Zip64 data,
|
||||
// it will not be used. It is questionable whether this situation is possible to detect
|
||||
|
||||
// unlike the other functions that have try-pattern semantics, these functions always return a
|
||||
// Zip64ExtraField. If a Zip64 extra field actually doesn't exist, all of the fields in the
|
||||
// returned struct will be null
|
||||
//
|
||||
// If there are more than one Zip64 extra fields, we take the first one that has the expected size
|
||||
//
|
||||
public static Zip64ExtraField GetJustZip64Block(Stream extraFieldStream,
|
||||
bool readUncompressedSize, bool readCompressedSize,
|
||||
bool readLocalHeaderOffset, bool readStartDiskNumber)
|
||||
{
|
||||
Zip64ExtraField zip64Field;
|
||||
using ( BinaryReader reader = new BinaryReader(extraFieldStream) )
|
||||
{
|
||||
ZipGenericExtraField currentExtraField;
|
||||
while ( ZipGenericExtraField.TryReadBlock(reader, extraFieldStream.Length, out currentExtraField) )
|
||||
{
|
||||
if ( TryGetZip64BlockFromGenericExtraField(currentExtraField, readUncompressedSize,
|
||||
readCompressedSize, readLocalHeaderOffset, readStartDiskNumber, out zip64Field) )
|
||||
{
|
||||
return zip64Field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zip64Field = new Zip64ExtraField();
|
||||
|
||||
zip64Field._compressedSize = null;
|
||||
zip64Field._uncompressedSize = null;
|
||||
zip64Field._localHeaderOffset = null;
|
||||
zip64Field._startDiskNumber = null;
|
||||
|
||||
return zip64Field;
|
||||
}
|
||||
|
||||
private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField extraField,
|
||||
bool readUncompressedSize, bool readCompressedSize,
|
||||
bool readLocalHeaderOffset, bool readStartDiskNumber,
|
||||
out Zip64ExtraField zip64Block)
|
||||
{
|
||||
zip64Block = new Zip64ExtraField();
|
||||
|
||||
zip64Block._compressedSize = null;
|
||||
zip64Block._uncompressedSize = null;
|
||||
zip64Block._localHeaderOffset = null;
|
||||
zip64Block._startDiskNumber = null;
|
||||
|
||||
if ( extraField.Tag != TagConstant )
|
||||
return false;
|
||||
|
||||
// this pattern needed because nested using blocks trigger CA2202
|
||||
MemoryStream ms = null;
|
||||
try
|
||||
{
|
||||
ms = new MemoryStream(extraField.Data);
|
||||
using ( BinaryReader reader = new BinaryReader(ms) )
|
||||
{
|
||||
ms = null;
|
||||
|
||||
zip64Block._size = extraField.Size;
|
||||
|
||||
ushort expectedSize = 0;
|
||||
|
||||
if ( readUncompressedSize ) expectedSize += 8;
|
||||
if ( readCompressedSize ) expectedSize += 8;
|
||||
if ( readLocalHeaderOffset ) expectedSize += 8;
|
||||
if ( readStartDiskNumber ) expectedSize += 4;
|
||||
|
||||
// if it is not the expected size, perhaps there is another extra field that matches
|
||||
if ( expectedSize != zip64Block._size )
|
||||
return false;
|
||||
|
||||
if ( readUncompressedSize ) zip64Block._uncompressedSize = reader.ReadInt64();
|
||||
if ( readCompressedSize ) zip64Block._compressedSize = reader.ReadInt64();
|
||||
if ( readLocalHeaderOffset ) zip64Block._localHeaderOffset = reader.ReadInt64();
|
||||
if ( readStartDiskNumber ) zip64Block._startDiskNumber = reader.ReadInt32();
|
||||
|
||||
// original values are unsigned, so implies value is too big to fit in signed integer
|
||||
if ( zip64Block._uncompressedSize < 0 ) throw new ZipArchiveException("FieldTooBigUncompressedSize");
|
||||
if ( zip64Block._compressedSize < 0 ) throw new ZipArchiveException("FieldTooBigCompressedSize");
|
||||
if ( zip64Block._localHeaderOffset < 0 ) throw new ZipArchiveException("FieldTooBigLocalHeaderOffset");
|
||||
if ( zip64Block._startDiskNumber < 0 ) throw new ZipArchiveException("FieldTooBigStartDiskNumber");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ( ms != null )
|
||||
ms.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
internal struct Zip64EndOfCentralDirectoryLocator
|
||||
{
|
||||
public const uint SignatureConstant = 0x07064B50;
|
||||
public const int SizeOfBlockWithoutSignature = 16;
|
||||
|
||||
public uint NumberOfDiskWithZip64EOCD;
|
||||
public ulong OffsetOfZip64EOCD;
|
||||
public uint TotalNumberOfDisks;
|
||||
|
||||
public static bool TryReadBlock(BinaryReader reader, out Zip64EndOfCentralDirectoryLocator zip64EOCDLocator)
|
||||
{
|
||||
zip64EOCDLocator = new Zip64EndOfCentralDirectoryLocator();
|
||||
|
||||
if ( reader.ReadUInt32() != SignatureConstant )
|
||||
return false;
|
||||
|
||||
zip64EOCDLocator.NumberOfDiskWithZip64EOCD = reader.ReadUInt32();
|
||||
zip64EOCDLocator.OffsetOfZip64EOCD = reader.ReadUInt64();
|
||||
zip64EOCDLocator.TotalNumberOfDisks = reader.ReadUInt32();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal struct Zip64EndOfCentralDirectoryRecord
|
||||
{
|
||||
private const uint SignatureConstant = 0x06064B50;
|
||||
private const ulong NormalSize = 0x2C; // the size of the data excluding the size/signature fields if no extra data included
|
||||
|
||||
public ulong SizeOfThisRecord;
|
||||
public ushort VersionMadeBy;
|
||||
public ushort VersionNeededToExtract;
|
||||
public uint NumberOfThisDisk;
|
||||
public uint NumberOfDiskWithStartOfCD;
|
||||
public ulong NumberOfEntriesOnThisDisk;
|
||||
public ulong NumberOfEntriesTotal;
|
||||
public ulong SizeOfCentralDirectory;
|
||||
public ulong OffsetOfCentralDirectory;
|
||||
|
||||
public static bool TryReadBlock(BinaryReader reader, out Zip64EndOfCentralDirectoryRecord zip64EOCDRecord)
|
||||
{
|
||||
zip64EOCDRecord = new Zip64EndOfCentralDirectoryRecord();
|
||||
|
||||
if ( reader.ReadUInt32() != SignatureConstant )
|
||||
return false;
|
||||
|
||||
zip64EOCDRecord.SizeOfThisRecord = reader.ReadUInt64();
|
||||
zip64EOCDRecord.VersionMadeBy = reader.ReadUInt16();
|
||||
zip64EOCDRecord.VersionNeededToExtract = reader.ReadUInt16();
|
||||
zip64EOCDRecord.NumberOfThisDisk = reader.ReadUInt32();
|
||||
zip64EOCDRecord.NumberOfDiskWithStartOfCD = reader.ReadUInt32();
|
||||
zip64EOCDRecord.NumberOfEntriesOnThisDisk = reader.ReadUInt64();
|
||||
zip64EOCDRecord.NumberOfEntriesTotal = reader.ReadUInt64();
|
||||
zip64EOCDRecord.SizeOfCentralDirectory = reader.ReadUInt64();
|
||||
zip64EOCDRecord.OffsetOfCentralDirectory = reader.ReadUInt64();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal struct ZipLocalFileHeader
|
||||
{
|
||||
public const uint DataDescriptorSignature = 0x08074B50;
|
||||
public const uint SignatureConstant = 0x04034B50;
|
||||
public const int OffsetToCrcFromHeaderStart = 14;
|
||||
public const int OffsetToBitFlagFromHeaderStart = 6;
|
||||
public const int SizeOfLocalHeader = 30;
|
||||
|
||||
|
||||
// will not throw end of stream exception
|
||||
public static bool TrySkipBlock(BinaryReader reader)
|
||||
{
|
||||
const int OffsetToFilenameLength = 22; // from the point after the signature
|
||||
|
||||
if ( reader.ReadUInt32() != SignatureConstant )
|
||||
return false;
|
||||
|
||||
|
||||
if ( reader.BaseStream.Length < reader.BaseStream.Position + OffsetToFilenameLength )
|
||||
return false;
|
||||
|
||||
reader.BaseStream.Seek(OffsetToFilenameLength, SeekOrigin.Current);
|
||||
|
||||
ushort filenameLength = reader.ReadUInt16();
|
||||
ushort extraFieldLength = reader.ReadUInt16();
|
||||
|
||||
if ( reader.BaseStream.Length < reader.BaseStream.Position + filenameLength + extraFieldLength )
|
||||
return false;
|
||||
|
||||
reader.BaseStream.Seek(filenameLength + extraFieldLength, SeekOrigin.Current);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal struct ZipCentralDirectoryFileHeader
|
||||
{
|
||||
public const uint SignatureConstant = 0x02014B50;
|
||||
public byte VersionMadeByCompatibility;
|
||||
public byte VersionMadeBySpecification;
|
||||
public ushort VersionNeededToExtract;
|
||||
public ushort GeneralPurposeBitFlag;
|
||||
public ushort CompressionMethod;
|
||||
public uint LastModified; // convert this on the fly
|
||||
public uint Crc32;
|
||||
public long CompressedSize;
|
||||
public long UncompressedSize;
|
||||
public ushort FilenameLength;
|
||||
public ushort ExtraFieldLength;
|
||||
public ushort FileCommentLength;
|
||||
public int DiskNumberStart;
|
||||
public ushort InternalFileAttributes;
|
||||
public uint ExternalFileAttributes;
|
||||
public long RelativeOffsetOfLocalHeader;
|
||||
|
||||
public byte[] Filename;
|
||||
public byte[] FileComment;
|
||||
public List<ZipGenericExtraField> ExtraFields;
|
||||
|
||||
// if saveExtraFieldsAndComments is false, FileComment and ExtraFields will be null
|
||||
// in either case, the zip64 extra field info will be incorporated into other fields
|
||||
public static bool TryReadBlock(BinaryReader reader, out ZipCentralDirectoryFileHeader header)
|
||||
{
|
||||
header = new ZipCentralDirectoryFileHeader();
|
||||
|
||||
if ( reader.ReadUInt32() != SignatureConstant )
|
||||
return false;
|
||||
header.VersionMadeBySpecification = reader.ReadByte();
|
||||
header.VersionMadeByCompatibility = reader.ReadByte();
|
||||
header.VersionNeededToExtract = reader.ReadUInt16();
|
||||
header.GeneralPurposeBitFlag = reader.ReadUInt16();
|
||||
header.CompressionMethod = reader.ReadUInt16();
|
||||
header.LastModified = reader.ReadUInt32();
|
||||
header.Crc32 = reader.ReadUInt32();
|
||||
uint compressedSizeSmall = reader.ReadUInt32();
|
||||
uint uncompressedSizeSmall = reader.ReadUInt32();
|
||||
header.FilenameLength = reader.ReadUInt16();
|
||||
header.ExtraFieldLength = reader.ReadUInt16();
|
||||
header.FileCommentLength = reader.ReadUInt16();
|
||||
ushort diskNumberStartSmall = reader.ReadUInt16();
|
||||
header.InternalFileAttributes = reader.ReadUInt16();
|
||||
header.ExternalFileAttributes = reader.ReadUInt32();
|
||||
uint relativeOffsetOfLocalHeaderSmall = reader.ReadUInt32();
|
||||
|
||||
header.Filename = reader.ReadBytes(header.FilenameLength);
|
||||
|
||||
bool uncompressedSizeInZip64 = uncompressedSizeSmall == ZipHelper.Mask32Bit;
|
||||
bool compressedSizeInZip64 = compressedSizeSmall == ZipHelper.Mask32Bit;
|
||||
bool relativeOffsetInZip64 = relativeOffsetOfLocalHeaderSmall == ZipHelper.Mask32Bit;
|
||||
bool diskNumberStartInZip64 = diskNumberStartSmall == ZipHelper.Mask16Bit;
|
||||
|
||||
Zip64ExtraField zip64;
|
||||
|
||||
long endExtraFields = reader.BaseStream.Position + header.ExtraFieldLength;
|
||||
using ( Stream str = new SubReadOnlyStream(reader.BaseStream, reader.BaseStream.Position, header.ExtraFieldLength, leaveOpen: true) )
|
||||
{
|
||||
header.ExtraFields = null;
|
||||
zip64 = Zip64ExtraField.GetJustZip64Block(str,
|
||||
uncompressedSizeInZip64, compressedSizeInZip64,
|
||||
relativeOffsetInZip64, diskNumberStartInZip64);
|
||||
}
|
||||
|
||||
// There are zip files that have malformed ExtraField blocks in which GetJustZip64Block() silently bails out without reading all the way to the end
|
||||
// of the ExtraField block. Thus we must force the stream's position to the proper place.
|
||||
reader.BaseStream.AdvanceToPosition(endExtraFields);
|
||||
|
||||
reader.BaseStream.Position += header.FileCommentLength;
|
||||
header.FileComment = null;
|
||||
|
||||
header.UncompressedSize = zip64.UncompressedSize == null
|
||||
? uncompressedSizeSmall
|
||||
: zip64.UncompressedSize.Value;
|
||||
header.CompressedSize = zip64.CompressedSize == null
|
||||
? compressedSizeSmall
|
||||
: zip64.CompressedSize.Value;
|
||||
header.RelativeOffsetOfLocalHeader = zip64.LocalHeaderOffset == null
|
||||
? relativeOffsetOfLocalHeaderSmall
|
||||
: zip64.LocalHeaderOffset.Value;
|
||||
header.DiskNumberStart = zip64.StartDiskNumber == null
|
||||
? diskNumberStartSmall
|
||||
: zip64.StartDiskNumber.Value;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal struct ZipEndOfCentralDirectoryBlock
|
||||
{
|
||||
public const uint SignatureConstant = 0x06054B50;
|
||||
public const int SizeOfBlockWithoutSignature = 18;
|
||||
public uint Signature;
|
||||
public ushort NumberOfThisDisk;
|
||||
public ushort NumberOfTheDiskWithTheStartOfTheCentralDirectory;
|
||||
public ushort NumberOfEntriesInTheCentralDirectoryOnThisDisk;
|
||||
public ushort NumberOfEntriesInTheCentralDirectory;
|
||||
public uint SizeOfCentralDirectory;
|
||||
public uint OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber;
|
||||
public byte[] ArchiveComment;
|
||||
|
||||
|
||||
public static bool TryReadBlock(BinaryReader reader, out ZipEndOfCentralDirectoryBlock eocdBlock)
|
||||
{
|
||||
eocdBlock = new ZipEndOfCentralDirectoryBlock();
|
||||
if ( reader.ReadUInt32() != SignatureConstant )
|
||||
return false;
|
||||
|
||||
eocdBlock.Signature = SignatureConstant;
|
||||
eocdBlock.NumberOfThisDisk = reader.ReadUInt16();
|
||||
eocdBlock.NumberOfTheDiskWithTheStartOfTheCentralDirectory = reader.ReadUInt16();
|
||||
eocdBlock.NumberOfEntriesInTheCentralDirectoryOnThisDisk = reader.ReadUInt16();
|
||||
eocdBlock.NumberOfEntriesInTheCentralDirectory = reader.ReadUInt16();
|
||||
eocdBlock.SizeOfCentralDirectory = reader.ReadUInt32();
|
||||
eocdBlock.OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber = reader.ReadUInt32();
|
||||
|
||||
ushort commentLength = reader.ReadUInt16();
|
||||
eocdBlock.ArchiveComment = reader.ReadBytes(commentLength);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ZipHelper
|
||||
{
|
||||
internal const uint Mask32Bit = 0xFFFFFFFF;
|
||||
internal const ushort Mask16Bit = 0xFFFF;
|
||||
|
||||
private const int BackwardsSeekingBufferSize = 32;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads exactly bytesToRead out of stream, unless it is out of bytes
|
||||
/// </summary>
|
||||
internal static void ReadBytes(Stream stream, byte[] buffer, int bytesToRead)
|
||||
{
|
||||
int bytesLeftToRead = bytesToRead;
|
||||
|
||||
int totalBytesRead = 0;
|
||||
|
||||
while (bytesLeftToRead > 0)
|
||||
{
|
||||
int bytesRead = stream.Read(buffer, totalBytesRead, bytesLeftToRead);
|
||||
if (bytesRead == 0) throw new IOException();
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
bytesLeftToRead -= bytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// assumes all bytes of signatureToFind are non zero, looks backwards from current position in stream,
|
||||
// if the signature is found then returns true and positions stream at first byte of signature
|
||||
// if the signature is not found, returns false
|
||||
internal static bool SeekBackwardsToSignature(Stream stream, uint signatureToFind)
|
||||
{
|
||||
int bufferPointer = 0;
|
||||
uint currentSignature = 0;
|
||||
byte[] buffer = new byte[BackwardsSeekingBufferSize];
|
||||
|
||||
bool outOfBytes = false;
|
||||
bool signatureFound = false;
|
||||
|
||||
while (!signatureFound && !outOfBytes)
|
||||
{
|
||||
outOfBytes = SeekBackwardsAndRead(stream, buffer, out bufferPointer);
|
||||
|
||||
Debug.Assert(bufferPointer < buffer.Length);
|
||||
|
||||
while (bufferPointer >= 0 && !signatureFound)
|
||||
{
|
||||
currentSignature = (currentSignature << 8) | ((uint)buffer[bufferPointer]);
|
||||
if (currentSignature == signatureToFind)
|
||||
{
|
||||
signatureFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferPointer--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!signatureFound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.Seek(bufferPointer, SeekOrigin.Current);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip to a further position downstream (without relying on the stream being seekable)
|
||||
internal static void AdvanceToPosition(this Stream stream, long position)
|
||||
{
|
||||
long numBytesLeft = position - stream.Position;
|
||||
Debug.Assert(numBytesLeft >= 0);
|
||||
while (numBytesLeft != 0)
|
||||
{
|
||||
const int throwAwayBufferSize = 64;
|
||||
int numBytesToSkip = (numBytesLeft > throwAwayBufferSize) ? throwAwayBufferSize : (int)numBytesLeft;
|
||||
int numBytesActuallySkipped = stream.Read(new byte[throwAwayBufferSize], 0, numBytesToSkip);
|
||||
if (numBytesActuallySkipped == 0)
|
||||
throw new IOException();
|
||||
numBytesLeft -= numBytesActuallySkipped;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if we are out of bytes
|
||||
private static bool SeekBackwardsAndRead(Stream stream, byte[] buffer, out int bufferPointer)
|
||||
{
|
||||
if (stream.Position >= buffer.Length)
|
||||
{
|
||||
stream.Seek(-buffer.Length, SeekOrigin.Current);
|
||||
ReadBytes(stream, buffer, buffer.Length);
|
||||
stream.Seek(-buffer.Length, SeekOrigin.Current);
|
||||
bufferPointer = buffer.Length - 1;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
int bytesToRead = (int)stream.Position;
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
ReadBytes(stream, buffer, bytesToRead);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
bufferPointer = bytesToRead - 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ZipArchiveException : Exception
|
||||
{
|
||||
public ZipArchiveException(string msg) : base(msg)
|
||||
{ }
|
||||
|
||||
public ZipArchiveException(string msg, Exception inner)
|
||||
: base(msg, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static class ZipArchiveUtils
|
||||
{
|
||||
public static void ReadEndOfCentralDirectory(Stream stream, BinaryReader reader, out long expectedNumberOfEntries, out long centralDirectoryStart)
|
||||
{
|
||||
try
|
||||
{
|
||||
// this seeks to the start of the end of central directory record
|
||||
stream.Seek(-ZipEndOfCentralDirectoryBlock.SizeOfBlockWithoutSignature, SeekOrigin.End);
|
||||
if (!ZipHelper.SeekBackwardsToSignature(stream, ZipEndOfCentralDirectoryBlock.SignatureConstant))
|
||||
throw new ZipArchiveException("SignatureConstant");
|
||||
|
||||
long eocdStart = stream.Position;
|
||||
|
||||
// read the EOCD
|
||||
ZipEndOfCentralDirectoryBlock eocd;
|
||||
bool eocdProper = ZipEndOfCentralDirectoryBlock.TryReadBlock(reader, out eocd);
|
||||
Debug.Assert(eocdProper); // we just found this using the signature finder, so it should be okay
|
||||
|
||||
if (eocd.NumberOfThisDisk != eocd.NumberOfTheDiskWithTheStartOfTheCentralDirectory)
|
||||
throw new ZipArchiveException("SplitSpanned");
|
||||
|
||||
centralDirectoryStart = eocd.OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber;
|
||||
if (eocd.NumberOfEntriesInTheCentralDirectory != eocd.NumberOfEntriesInTheCentralDirectoryOnThisDisk)
|
||||
throw new ZipArchiveException("SplitSpanned");
|
||||
expectedNumberOfEntries = eocd.NumberOfEntriesInTheCentralDirectory;
|
||||
|
||||
|
||||
// only bother looking for zip64 EOCD stuff if we suspect it is needed because some value is FFFFFFFFF
|
||||
// because these are the only two values we need, we only worry about these
|
||||
// if we don't find the zip64 EOCD, we just give up and try to use the original values
|
||||
if (eocd.NumberOfThisDisk == ZipHelper.Mask16Bit ||
|
||||
eocd.OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber == ZipHelper.Mask32Bit ||
|
||||
eocd.NumberOfEntriesInTheCentralDirectory == ZipHelper.Mask16Bit)
|
||||
{
|
||||
// we need to look for zip 64 EOCD stuff
|
||||
// seek to the zip 64 EOCD locator
|
||||
stream.Seek(eocdStart - Zip64EndOfCentralDirectoryLocator.SizeOfBlockWithoutSignature, SeekOrigin.Begin);
|
||||
// if we don't find it, assume it doesn't exist and use data from normal eocd
|
||||
if (ZipHelper.SeekBackwardsToSignature(stream, Zip64EndOfCentralDirectoryLocator.SignatureConstant))
|
||||
{
|
||||
// use locator to get to Zip64EOCD
|
||||
Zip64EndOfCentralDirectoryLocator locator;
|
||||
bool zip64eocdLocatorProper = Zip64EndOfCentralDirectoryLocator.TryReadBlock(reader, out locator);
|
||||
Debug.Assert(zip64eocdLocatorProper); // we just found this using the signature finder, so it should be okay
|
||||
|
||||
if (locator.OffsetOfZip64EOCD > long.MaxValue)
|
||||
throw new ZipArchiveException("FieldTooBigOffsetToZip64EOCD");
|
||||
long zip64EOCDOffset = (long)locator.OffsetOfZip64EOCD;
|
||||
|
||||
stream.Seek(zip64EOCDOffset, SeekOrigin.Begin);
|
||||
|
||||
// read Zip64EOCD
|
||||
Zip64EndOfCentralDirectoryRecord record;
|
||||
if (!Zip64EndOfCentralDirectoryRecord.TryReadBlock(reader, out record))
|
||||
throw new ZipArchiveException("Zip64EOCDNotWhereExpected");
|
||||
|
||||
if (record.NumberOfEntriesTotal > long.MaxValue)
|
||||
throw new ZipArchiveException("FieldTooBigNumEntries");
|
||||
if (record.OffsetOfCentralDirectory > long.MaxValue)
|
||||
throw new ZipArchiveException("FieldTooBigOffsetToCD");
|
||||
if (record.NumberOfEntriesTotal != record.NumberOfEntriesOnThisDisk)
|
||||
throw new ZipArchiveException("SplitSpanned");
|
||||
|
||||
expectedNumberOfEntries = (long)record.NumberOfEntriesTotal;
|
||||
centralDirectoryStart = (long)record.OffsetOfCentralDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
if (centralDirectoryStart > stream.Length)
|
||||
{
|
||||
throw new ZipArchiveException("FieldTooBigOffsetToCD");
|
||||
}
|
||||
}
|
||||
catch (EndOfStreamException ex)
|
||||
{
|
||||
throw new ZipArchiveException("CDCorrupt", ex);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
throw new ZipArchiveException("CDCorrupt", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4ae0668083250b649a64c7836ec20f11
|
||||
timeCreated: 1506207467
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"name": "BetterStreamingAssets",
|
||||
"references": [],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5e90fee04fdf7164fa71eeef34c6a431
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"name": "com.gwiazdorrr.betterstreamingassets",
|
||||
"displayName": "Better Streaming Assets",
|
||||
"author": { "name": "Piotr Gwiazdowski" },
|
||||
"version": "1.6.0",
|
||||
"documentationUrl": "https://github.com/gwiazdorrr/BetterStreamingAssets",
|
||||
"changelogUrl": "https://github.com/gwiazdorrr/BetterStreamingAssets",
|
||||
"licensesUrl": "https://github.com/gwiazdorrr/BetterStreamingAssets/blob/master/LICENSE",
|
||||
"description": "Access Streaming Assets directly in an uniform and thread-safe way, with tiny overhead.",
|
||||
"samples": [
|
||||
{
|
||||
"displayName": "Better Streaming Assets Samples",
|
||||
"path": "Samples~/BetterStreamingAssets"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 89357359fd1b3f74fa7cb048a1ffa2b6
|
||||
guid: 0dc8cc2d1f4fd7149a0a1f13f8f8e54b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
|
@ -1,5 +1,6 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f3d9e435137e34d34a54f181bf3dc7c2
|
||||
guid: 4b1fa97e8bb8c5c46ad030b9554e1b5c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
|
@ -0,0 +1,39 @@
|
|||
//-------------------------------------
|
||||
// 作者:Stark
|
||||
//-------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// 为Github对开发者的友好,采用自动补充UnityPlayerActivity.java文件的通用姿势满足各个开发者
|
||||
/// </summary>
|
||||
internal class AndroidPost : UnityEditor.Android.IPostGenerateGradleAndroidProject
|
||||
{
|
||||
public int callbackOrder => 99;
|
||||
public void OnPostGenerateGradleAndroidProject(string path)
|
||||
{
|
||||
path = path.Replace("\\", "/");
|
||||
string untityActivityFilePath = $"{path}/src/main/java/com/unity3d/player/UnityPlayerActivity.java";
|
||||
var readContent = System.IO.File.ReadAllLines(untityActivityFilePath);
|
||||
string postContent = " //auto-gen-function\n" +
|
||||
" public boolean CheckAssetExist(String filePath){\n" +
|
||||
" android.content.res.AssetManager assetManager = getAssets();\n" +
|
||||
" java.io.InputStream inputStream = null;\n" +
|
||||
" try {\n" +
|
||||
" inputStream = assetManager.open(filePath);\n" +
|
||||
" if(null != inputStream)return true;\n" +
|
||||
" }catch(java.io.IOException e) {\n" +
|
||||
" e.printStackTrace();\n" +
|
||||
" }finally{\n" +
|
||||
" try {\n" +
|
||||
" inputStream.close();\n" +
|
||||
" } catch (java.io.IOException e) {\n" +
|
||||
" e.printStackTrace();\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" return false;\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
if (!readContent[readContent.Length - 18].Contains("CheckAssetExist"))
|
||||
readContent[readContent.Length - 1] = postContent;
|
||||
System.IO.File.WriteAllLines(untityActivityFilePath, readContent);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 12bc032fc47a6c34fb90f0f4a48f4441
|
||||
timeCreated: 1506516969
|
||||
licenseType: Pro
|
||||
guid: 7bfd05221983858429246096617dff1d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 89bc753a71211472587f0baa7a08d0e7
|
||||
guid: b19a7385dee2d0141901167dbfda9300
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
|
@ -0,0 +1,44 @@
|
|||
//-------------------------------------
|
||||
// 作者:Stark
|
||||
//-------------------------------------
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class StreamingAssetsHelper
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
private static AndroidJavaClass _unityPlayerClass;
|
||||
public static AndroidJavaClass UnityPlayerClass
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_unityPlayerClass == null)
|
||||
_unityPlayerClass = new UnityEngine.AndroidJavaClass("com.unity3d.player.UnityPlayer");
|
||||
return _unityPlayerClass;
|
||||
}
|
||||
}
|
||||
|
||||
private static AndroidJavaObject _currentActivity;
|
||||
public static AndroidJavaObject CurrentActivity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_currentActivity == null)
|
||||
_currentActivity = UnityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");
|
||||
return _currentActivity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 利用安卓原生接口查询内置文件是否存在
|
||||
/// </summary>
|
||||
public static bool FileExists(string filePath)
|
||||
{
|
||||
return CurrentActivity.Call<bool>("CheckAssetExist", filePath);
|
||||
}
|
||||
#else
|
||||
public static bool FileExists(string filePath)
|
||||
{
|
||||
return System.IO.File.Exists(System.IO.Path.Combine(Application.streamingAssetsPath, filePath));
|
||||
}
|
||||
#endif
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 34f6b4de9962c114299b3b9bfdd590d5
|
||||
timeCreated: 1506546248
|
||||
licenseType: Free
|
||||
guid: 358145d67e230b34883002b08b23cba3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "com.tuyoogame.yooasset",
|
||||
"displayName": "YooAsset",
|
||||
"version": "1.4.2-preview",
|
||||
"version": "1.4.4-preview",
|
||||
"unity": "2019.4",
|
||||
"description": "unity3d resources management system.",
|
||||
"author": {
|
||||
|
|
Loading…
Reference in New Issue