Merge branch 'tuyoogame:main' into main

pull/62/head
Sayo 2023-02-14 15:14:06 +08:00 committed by GitHub
commit cfbf6e23ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 389 additions and 2009 deletions

View File

@ -2,6 +2,32 @@
All notable changes to this package will be documented in this file. 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 ## [1.4.2-preview] - 2023-01-03
### Fixed ### Fixed

View File

@ -220,15 +220,27 @@ namespace YooAsset.Editor
private void RefreshWindow() private void RefreshWindow()
{ {
var buildPipeline = AssetBundleBuilderSettingData.Setting.BuildPipeline;
var buildMode = AssetBundleBuilderSettingData.Setting.BuildMode; var buildMode = AssetBundleBuilderSettingData.Setting.BuildMode;
var copyOption = AssetBundleBuilderSettingData.Setting.CopyBuildinFileOption; var copyOption = AssetBundleBuilderSettingData.Setting.CopyBuildinFileOption;
bool enableElement = buildMode == EBuildMode.ForceRebuild; bool enableElement = buildMode == EBuildMode.ForceRebuild;
bool tagsFiledVisible = copyOption == ECopyBuildinFileOption.ClearAndCopyByTags || copyOption == ECopyBuildinFileOption.OnlyCopyByTags; bool tagsFiledVisible = copyOption == ECopyBuildinFileOption.ClearAndCopyByTags || copyOption == ECopyBuildinFileOption.OnlyCopyByTags;
_encryptionField.SetEnabled(enableElement);
if (buildPipeline == EBuildPipeline.BuiltinBuildPipeline)
{
_compressionField.SetEnabled(enableElement); _compressionField.SetEnabled(enableElement);
_outputNameStyleField.SetEnabled(enableElement); _outputNameStyleField.SetEnabled(enableElement);
_copyBuildinFileOptionField.SetEnabled(enableElement); _copyBuildinFileOptionField.SetEnabled(enableElement);
_copyBuildinFileTagsField.SetEnabled(enableElement); _copyBuildinFileTagsField.SetEnabled(enableElement);
}
else
{
_compressionField.SetEnabled(true);
_outputNameStyleField.SetEnabled(true);
_copyBuildinFileOptionField.SetEnabled(true);
_copyBuildinFileTagsField.SetEnabled(true);
}
_copyBuildinFileTagsField.visible = tagsFiledVisible; _copyBuildinFileTagsField.visible = tagsFiledVisible;
} }
private void SaveBtn_clicked() private void SaveBtn_clicked()

View File

@ -16,12 +16,17 @@ namespace YooAsset.Editor
var buildParameters = buildParametersContext.Parameters; 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) if (buildParameters.BuildTarget == BuildTarget.NoTarget)
throw new Exception("请选择目标平台"); throw new Exception("请选择目标平台");
if (string.IsNullOrEmpty(buildParameters.PackageName)) if (string.IsNullOrEmpty(buildParameters.PackageName))
throw new Exception("包裹名称不能为空"); throw new Exception("包裹名称不能为空");
if(string.IsNullOrEmpty(buildParameters.PackageVersion)) if (string.IsNullOrEmpty(buildParameters.PackageVersion))
throw new Exception("包裹版本不能为空"); throw new Exception("包裹版本不能为空");
if (buildParameters.BuildMode != EBuildMode.SimulateBuild) if (buildParameters.BuildMode != EBuildMode.SimulateBuild)

View File

@ -351,6 +351,9 @@ namespace YooAsset
// 新增下载需求 // 新增下载需求
#if UNITY_WEBGL #if UNITY_WEBGL
if (bundleInfo.Bundle.IsRawFile)
loader = new RawBundleFileLoader(this, bundleInfo);
else
loader = new AssetBundleWebLoader(this, bundleInfo); loader = new AssetBundleWebLoader(this, bundleInfo);
#else #else
if (bundleInfo.Bundle.IsRawFile) if (bundleInfo.Bundle.IsRawFile)

View File

@ -3,10 +3,34 @@ using System.IO;
namespace YooAsset namespace YooAsset
{ {
[Serializable]
internal class CacheFileInfo internal class CacheFileInfo
{ {
public string FileCRC; private static readonly BufferWriter SharedBuffer = new BufferWriter(1024);
public long FileSize;
/// <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();
}
} }
} }

View File

@ -78,10 +78,7 @@ namespace YooAsset
return EVerifyResult.InfoFileNotExisted; return EVerifyResult.InfoFileNotExisted;
// 解析信息文件获取验证数据 // 解析信息文件获取验证数据
string jsonContent = FileUtility.ReadAllText(infoFilePath); CacheFileInfo.ReadInfoFromFile(infoFilePath, out element.DataFileCRC, out element.DataFileSize);
CacheFileInfo fileInfo = UnityEngine.JsonUtility.FromJson<CacheFileInfo>(jsonContent);
element.DataFileCRC = fileInfo.FileCRC;
element.DataFileSize = fileInfo.FileSize;
} }
catch (Exception) catch (Exception)
{ {

View File

@ -220,22 +220,24 @@ namespace YooAsset
{ {
try try
{ {
string destFilePath = _bundleInfo.Bundle.CachedDataFilePath; string infoFilePath = _bundleInfo.Bundle.CachedInfoFilePath;
if (File.Exists(destFilePath)) string dataFilePath = _bundleInfo.Bundle.CachedDataFilePath;
File.Delete(destFilePath); 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 fileInfo = new FileInfo(_tempFilePath);
fileInfo.MoveTo(destFilePath); fileInfo.MoveTo(dataFilePath);
// 写入信息文件记录验证数据 // 写入信息文件记录验证数据
CacheFileInfo cacheInfo = new CacheFileInfo(); CacheFileInfo.WriteInfoToFile(infoFilePath, dataFileCRC, dataFileSize);
cacheInfo.FileCRC = _bundleInfo.Bundle.FileCRC;
cacheInfo.FileSize = _bundleInfo.Bundle.FileSize;
string jsonContent = UnityEngine.JsonUtility.ToJson(cacheInfo);
FileUtility.CreateFile(_bundleInfo.Bundle.CachedInfoFilePath, jsonContent);
// 记录缓存文件 // 记录缓存文件
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); CacheSystem.RecordFile(_bundleInfo.Bundle.PackageName, _bundleInfo.Bundle.CacheGUID, wrapper);
_lastError = string.Empty; _lastError = string.Empty;

View File

@ -8,6 +8,8 @@ namespace YooAsset
{ {
internal static class PatchManifestTools internal static class PatchManifestTools
{ {
#if UNITY_EDITOR
/// <summary> /// <summary>
/// 序列化JSON文件 /// 序列化JSON文件
/// </summary> /// </summary>
@ -151,6 +153,7 @@ namespace YooAsset
return manifest; return manifest;
} }
#endif
/// <summary> /// <summary>
/// 生成Bundle文件的正式名称 /// 生成Bundle文件的正式名称

View File

@ -28,6 +28,14 @@ namespace YooAsset
get { return _buffer.Length; } get { return _buffer.Length; }
} }
/// <summary>
/// 清空缓冲区
/// </summary>
public void Clear()
{
_index = 0;
}
/// <summary> /// <summary>
/// 将有效数据写入文件流 /// 将有效数据写入文件流
/// </summary> /// </summary>

View File

@ -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>

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 08dafaa194a7143109cbe1c6403a2ec5 guid: 218816e46e4f6304eaecd6fdc3a99f4d
TextScriptImporter: TextScriptImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

@ -1,6 +1,6 @@
{ {
"ShaderTotalCount": 12, "ShaderTotalCount": 13,
"VariantTotalCount": 16, "VariantTotalCount": 24,
"ShaderVariantInfos": [ "ShaderVariantInfos": [
{ {
"AssetPath": "Resources/unity_builtin_extra", "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", "AssetPath": "Resources/unity_builtin_extra",
"ShaderName": "Skybox/Procedural", "ShaderName": "Skybox/Procedural",
@ -21,12 +124,23 @@
{ {
"PassType": 0, "PassType": 0,
"Keywords": [ "Keywords": [
"BILLBOARD_FACE_CAMERA_POS",
"_SUNDISK_SIMPLE" "_SUNDISK_SIMPLE"
] ]
} }
] ]
}, },
{
"AssetPath": "Resources/unity_builtin_extra",
"ShaderName": "Legacy Shaders/Particles/Additive",
"ShaderVariantElements": [
{
"PassType": 0,
"Keywords": [
""
]
}
]
},
{ {
"AssetPath": "Resources/unity_builtin_extra", "AssetPath": "Resources/unity_builtin_extra",
"ShaderName": "Hidden/Internal-GUITextureClip", "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", "AssetPath": "Resources/unity_builtin_extra",
"ShaderName": "Hidden/Internal-UIRAtlasBlitCopy", "ShaderName": "Hidden/Internal-UIRAtlasBlitCopy",
@ -113,80 +215,37 @@
}, },
{ {
"AssetPath": "Resources/unity_builtin_extra", "AssetPath": "Resources/unity_builtin_extra",
"ShaderName": "Hidden/UIElements/EditorUIE", "ShaderName": "Hidden/Internal-GUIRoundedRectWithColorPerBorder",
"ShaderVariantElements": [ "ShaderVariantElements": [
{ {
"PassType": 0, "PassType": 0,
"Keywords": [ "Keywords": [
"BILLBOARD_FACE_CAMERA_POS" ""
] ]
} }
] ]
}, },
{ {
"AssetPath": "Resources/unity_builtin_extra", "AssetPath": "Resources/unity_builtin_extra",
"ShaderName": "Mobile/Diffuse", "ShaderName": "Mobile/Particles/Additive",
"ShaderVariantElements": [ "ShaderVariantElements": [
{ {
"PassType": 4, "PassType": 0,
"Keywords": [
"BILLBOARD_FACE_CAMERA_POS",
"DIRECTIONAL",
"LIGHTPROBE_SH",
"SHADOWS_SCREEN",
"SHADOWS_SOFT",
"SHADOWS_SPLIT_SPHERES"
]
},
{
"PassType": 8,
"Keywords": [ "Keywords": [
"" ""
] ]
},
{
"PassType": 8,
"Keywords": [
"SHADOWS_DEPTH",
"SHADOWS_SOFT",
"SHADOWS_SPLIT_SPHERES"
]
} }
] ]
}, },
{ {
"AssetPath": "Assets/Samples/Basic Sample/GameArt/Shaders/StandardMobile.shader", "AssetPath": "Resources/unity_builtin_extra",
"ShaderName": "Mobile/Standard", "ShaderName": "Unlit/Texture",
"ShaderVariantElements": [ "ShaderVariantElements": [
{ {
"PassType": 1, "PassType": 0,
"Keywords": [ "Keywords": [
"BILLBOARD_FACE_CAMERA_POS",
"SHADOWS_SCREEN", "SHADOWS_SCREEN",
"SHADOWS_SOFT", "SHADOWS_SPLIT_SPHERES"
"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"
] ]
} }
] ]

View File

@ -13,10 +13,45 @@ ShaderVariantCollection:
variants: variants:
- keywords: - keywords:
passType: 0 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} - first: {fileID: 106, guid: 0000000000000000f000000000000000, type: 0}
second: second:
variants: 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 passType: 0
- first: {fileID: 9000, guid: 0000000000000000f000000000000000, type: 0} - first: {fileID: 9000, guid: 0000000000000000f000000000000000, type: 0}
second: second:
@ -43,39 +78,23 @@ ShaderVariantCollection:
variants: variants:
- keywords: - keywords:
passType: 0 passType: 0
- first: {fileID: 9006, guid: 0000000000000000f000000000000000, type: 0}
second:
variants:
- keywords:
passType: 0
- first: {fileID: 9007, guid: 0000000000000000f000000000000000, type: 0} - first: {fileID: 9007, guid: 0000000000000000f000000000000000, type: 0}
second: second:
variants: variants:
- keywords: - keywords:
passType: 0 passType: 0
- first: {fileID: 9101, guid: 0000000000000000f000000000000000, type: 0} - first: {fileID: 10720, guid: 0000000000000000f000000000000000, type: 0}
second: second:
variants: variants:
- keywords: - keywords:
passType: 0 passType: 0
- first: {fileID: 9103, guid: 0000000000000000f000000000000000, type: 0} - first: {fileID: 10752, guid: 0000000000000000f000000000000000, type: 0}
second: second:
variants: variants:
- keywords: BILLBOARD_FACE_CAMERA_POS - keywords: SHADOWS_SCREEN SHADOWS_SPLIT_SPHERES
passType: 0 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

View File

@ -18,9 +18,6 @@ public class Boot : MonoBehaviour
} }
void Start() void Start()
{ {
// 初始化BetterStreaming
BetterStreamingAssets.Initialize();
// 初始化事件系统 // 初始化事件系统
UniEvent.Initalize(); UniEvent.Initalize();

View File

@ -122,9 +122,8 @@ internal class FsmInitialize : IStateNode
{ {
public bool QueryStreamingAssets(string fileName) public bool QueryStreamingAssets(string fileName)
{ {
// 注意使用了BetterStreamingAssets插件使用前需要初始化该插件
string buildinFolderName = YooAssets.GetStreamingAssetBuildinFolderName(); string buildinFolderName = YooAssets.GetStreamingAssetBuildinFolderName();
return BetterStreamingAssets.FileExists($"{buildinFolderName}/{fileName}"); return StreamingAssetsHelper.FileExists($"{buildinFolderName}/{fileName}");
} }
} }

View File

@ -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

View File

@ -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.

View File

@ -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;
}
}
}
```

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: c59a049ac5cdb4a48996edcefd9ac9c7
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 23985058976064b4aa47f955e25b32fb
timeCreated: 1506207467
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 4ae0668083250b649a64c7836ec20f11
timeCreated: 1506207467
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +0,0 @@
{
"name": "BetterStreamingAssets",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false
}

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 5e90fee04fdf7164fa71eeef34c6a431
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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"
}
]
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 89357359fd1b3f74fa7cb048a1ffa2b6 guid: 0dc8cc2d1f4fd7149a0a1f13f8f8e54b
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -1,5 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: f3d9e435137e34d34a54f181bf3dc7c2 guid: 4b1fa97e8bb8c5c46ad030b9554e1b5c
folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

@ -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);
}
}

View File

@ -1,8 +1,7 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 12bc032fc47a6c34fb90f0f4a48f4441 guid: 7bfd05221983858429246096617dff1d
timeCreated: 1506516969
licenseType: Pro
MonoImporter: MonoImporter:
externalObjects: {}
serializedVersion: 2 serializedVersion: 2
defaultReferences: [] defaultReferences: []
executionOrder: 0 executionOrder: 0

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 89bc753a71211472587f0baa7a08d0e7 guid: b19a7385dee2d0141901167dbfda9300
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -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
}

View File

@ -1,8 +1,7 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 34f6b4de9962c114299b3b9bfdd590d5 guid: 358145d67e230b34883002b08b23cba3
timeCreated: 1506546248
licenseType: Free
MonoImporter: MonoImporter:
externalObjects: {}
serializedVersion: 2 serializedVersion: 2
defaultReferences: [] defaultReferences: []
executionOrder: 0 executionOrder: 0

View File

@ -1,7 +1,7 @@
{ {
"name": "com.tuyoogame.yooasset", "name": "com.tuyoogame.yooasset",
"displayName": "YooAsset", "displayName": "YooAsset",
"version": "1.4.2-preview", "version": "1.4.4-preview",
"unity": "2019.4", "unity": "2019.4",
"description": "unity3d resources management system.", "description": "unity3d resources management system.",
"author": { "author": {