├── Plugins.meta ├── WebGLFileSaver.cs.meta ├── Plugins ├── WebGLFileSaver.jslib.meta └── WebGLFileSaver.jslib ├── LICENSE ├── README.md └── WebGLFileSaver.cs /Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dde7cf05d3675dc46a1e54f8c6c9ed0b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /WebGLFileSaver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a66000d7141b0dd41becd24af5c242ea 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Plugins/WebGLFileSaver.jslib.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 216d526c0588d3e46b63191ced978401 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | defineConstraints: [] 9 | isPreloaded: 0 10 | isOverridable: 0 11 | isExplicitlyReferenced: 0 12 | validateReferences: 1 13 | platformData: 14 | - first: 15 | Any: 16 | second: 17 | enabled: 0 18 | settings: {} 19 | - first: 20 | Editor: Editor 21 | second: 22 | enabled: 0 23 | settings: 24 | DefaultValueInitialized: true 25 | - first: 26 | Facebook: WebGL 27 | second: 28 | enabled: 1 29 | settings: {} 30 | - first: 31 | WebGL: WebGL 32 | second: 33 | enabled: 1 34 | settings: {} 35 | userData: 36 | assetBundleName: 37 | assetBundleVariant: 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 nateonusapps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Nat's WebGL FileSaver 3 | ============ 4 | 5 | Use this plugin to download/save files from inside a WebGL Build. 6 | Built using the (MIT-licensed) FileSaver.js library, made by Eli Grey. (http://purl.eligrey.com/github/FileSaver.js ) 7 | 8 | This was created for https://nateonus.itch.io/natvox , an online Voxel Editor for Game Development. 9 | https://twitter.com/nateonus for updates. 10 | 11 | 12 | Usage 13 | ------------------ 14 | 15 | The WebGLFileSaver class has two static functions to use: 16 | 1) `public static bool IsSavingSupported()` 17 | This returns a boolean value of whether saving is supported on the current OS and Browser or not. 18 | 19 | 2) `public static void SaveFile(string content, string filename, string MIMEType)` 20 | `public static void SaveFile(byte[] content, string filename, string MIMEType)` 21 | This prompts the user to download a file named 'filename', with the content 'content'. 22 | The MIMEType is the file type that will allow the browser to open the file with a default program. 23 | It can be set to any values in the IANA Media Types (https://www.iana.org/assignments/media-types/media-types.xhtml ) 24 | It's default is set to "text/plain;charset=utf-8" (a plain-text file). 25 | SaveFile will not run if saving is not supported. 26 | 27 | 28 | Browser Support 29 | ------------------ 30 | 31 | To view which browsers are supported, visit https://github.com/eligrey/FileSaver.js#supported-browsers 32 | 33 | -------------------------------------------------------------------------------- /WebGLFileSaver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using System.Runtime.InteropServices; 5 | 6 | public class WebGLFileSaver 7 | { 8 | 9 | [DllImport("__Internal")] 10 | private static extern void UNITY_SAVE(string content, string name, string MIMEType); 11 | 12 | [DllImport ("__Internal")] 13 | private static extern void UNITY_SAVE_BYTEARRAY(byte[] array, int byteLength, string name, string MIMEType); 14 | 15 | [DllImport("__Internal")] 16 | private static extern void init(); 17 | 18 | [DllImport("__Internal")] 19 | private static extern bool UNITY_IS_SUPPORTED(); 20 | 21 | static bool hasinit = false; 22 | 23 | public static void SaveFile(string content, string fileName, string MIMEType = "text/plain;charset=utf-8") 24 | { 25 | if (!CheckSupportAndInit()) return; 26 | 27 | UNITY_SAVE (content, fileName, MIMEType); 28 | } 29 | 30 | public static void SaveFile(byte[] content, string fileName, string MIMEType = "text/plain;charset=utf-8") 31 | { 32 | if (content == null) 33 | { 34 | Debug.LogError("null parameter passed for content byte array"); 35 | return; 36 | } 37 | if (!CheckSupportAndInit()) return; 38 | 39 | UNITY_SAVE_BYTEARRAY (content, content.Length, fileName, MIMEType); 40 | } 41 | 42 | static bool CheckSupportAndInit() 43 | { 44 | if (Application.isEditor) 45 | { 46 | Debug.Log("Saving will not work in editor."); 47 | return false; 48 | } 49 | if (Application.platform != RuntimePlatform.WebGLPlayer) 50 | { 51 | Debug.Log("Saving must be on a WebGL build."); 52 | return false; 53 | } 54 | 55 | CheckInit(); 56 | 57 | if (!IsSavingSupported()) 58 | { 59 | Debug.LogWarning("Saving is not supported on this device."); 60 | return false; 61 | } 62 | return true; 63 | } 64 | 65 | static void CheckInit() 66 | { 67 | if (!hasinit) 68 | { 69 | init(); 70 | hasinit = true; 71 | } 72 | } 73 | 74 | public static bool IsSavingSupported() 75 | { 76 | if (Application.isEditor) 77 | { 78 | Debug.Log("Saving will not work in editor."); 79 | return false; 80 | } 81 | if (Application.platform != RuntimePlatform.WebGLPlayer) 82 | { 83 | Debug.Log("Saving must be on a WebGL build."); 84 | return false; 85 | } 86 | CheckInit(); 87 | return UNITY_IS_SUPPORTED(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Plugins/WebGLFileSaver.jslib: -------------------------------------------------------------------------------- 1 | /* 2 | * FileSaver.js 3 | * A saveAs() FileSaver implementation. 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * 7 | * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT) 8 | * source : http://purl.eligrey.com/github/FileSaver.js 9 | */ 10 | 11 | /* 12 | * Edited by Nat for Unity Support. https://twitter.com/nateonus 13 | * License : Still MIT 14 | * Source : Somewhere yet to be uploaded 15 | */ 16 | 17 | mergeInto(LibraryManager.library, { 18 | _global: null, 19 | init__deps: [ 20 | '_global', 21 | 'saveAs', 22 | 'corsEnabled', 23 | 'download', 24 | 'click', 25 | 'bom' 26 | ], 27 | init: function () { 28 | __global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof global === 'object' && global.global === global ? global : this; 29 | _saveAs = __global.saveAs || (typeof window !== 'object' || window !== __global ? function saveAs() { 30 | } : 'download' in HTMLAnchorElement.prototype ? function saveAs(blob, name, opts) { 31 | var URL = __global.URL || __global.webkitURL; 32 | var a = document.createElement('a'); 33 | name = name || blob.name || 'download'; 34 | a.download = name; 35 | a.rel = 'noopener'; 36 | if (typeof blob === 'string') { 37 | a.href = blob; 38 | if (a.origin !== location.origin) { 39 | _corsEnabled(a.href) ? _download(blob, name, opts) : _click(a, a.target = '_blank'); 40 | } else { 41 | _click(a); 42 | } 43 | } else { 44 | a.href = URL.createObjectURL(blob); 45 | setTimeout(function () { 46 | URL.revokeObjectURL(a.href); 47 | }, 40000); 48 | setTimeout(function () { 49 | _click(a); 50 | }, 0); 51 | } 52 | } : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) { 53 | name = name || blob.name || 'download'; 54 | if (typeof blob === 'string') { 55 | if (_corsEnabled(blob)) { 56 | _download(blob, name, opts); 57 | } else { 58 | var a = document.createElement('a'); 59 | a.href = blob; 60 | a.target = '_blank'; 61 | setTimeout(function () { 62 | _click(a); 63 | }); 64 | } 65 | } else { 66 | navigator.msSaveOrOpenBlob(_bom(blob, opts), name); 67 | } 68 | } : function saveAs(blob, name, opts, popup) { 69 | popup = popup || open('', '_blank'); 70 | if (popup) { 71 | popup.document.title = popup.document.body.innerText = 'downloading...'; 72 | } 73 | if (typeof blob === 'string') 74 | return _download(blob, name, opts); 75 | var force = blob.type === 'application/octet-stream'; 76 | var isSafari = /constructor/i.test(__global.HTMLElement) || __global.safari; 77 | var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent); 78 | if ((isChromeIOS || force && isSafari) && typeof FileReader === 'object') { 79 | var reader = new FileReader(); 80 | reader.onloadend = function () { 81 | var url = reader.result; 82 | url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;'); 83 | if (popup) 84 | popup.location.href = url; 85 | else 86 | location = url; 87 | popup = null; 88 | }; 89 | reader.readAsDataURL(blob); 90 | } else { 91 | var URL = __global.URL || __global.webkitURL; 92 | var url = URL.createObjectURL(blob); 93 | if (popup) 94 | popup.location = url; 95 | else 96 | location.href = url; 97 | popup = null; 98 | setTimeout(function () { 99 | URL.revokeObjectURL(url); 100 | }, 40000); 101 | } 102 | }); 103 | __global.saveAs = _saveAs.saveAs = _saveAs; 104 | if (typeof module !== 'undefined') { 105 | module.exports = _saveAs; 106 | } 107 | ; 108 | }, 109 | bom: function (blob, opts) { 110 | if (typeof opts === 'undefined') 111 | opts = { autoBom: false }; 112 | else if (typeof opts !== 'object') { 113 | console.warn('Deprecated: Expected third argument to be a object'); 114 | opts = { autoBom: !opts }; 115 | } 116 | if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { 117 | return new Blob([ 118 | String.fromCharCode(65279), 119 | blob 120 | ], { type: blob.type }); 121 | } 122 | return blob; 123 | }, 124 | download__deps: ['saveAs'], 125 | download: function (url, name, opts) { 126 | var xhr = new XMLHttpRequest(); 127 | xhr.open('GET', url); 128 | xhr.responseType = 'blob'; 129 | xhr.onload = function () { 130 | _saveAs(xhr.response, name, opts); 131 | }; 132 | xhr.onerror = function () { 133 | console.error('could not download file'); 134 | }; 135 | xhr.send(); 136 | }, 137 | corsEnabled: function (url) { 138 | var xhr = new XMLHttpRequest(); 139 | xhr.open('HEAD', url, false); 140 | try { 141 | xhr.send(); 142 | } catch (e) { 143 | } 144 | return xhr.status >= 200 && xhr.status <= 299; 145 | }, 146 | click: function (node) { 147 | try { 148 | node.dispatchEvent(new MouseEvent('click')); 149 | } catch (e) { 150 | var evt = document.createEvent('MouseEvents'); 151 | evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); 152 | node.dispatchEvent(evt); 153 | } 154 | }, 155 | UNITY_SAVE: function (content, name, mimetype) 156 | { 157 | var blob = new Blob([Pointer_stringify(content)], { type: Pointer_stringify(mimetype) }); 158 | saveAs(blob, Pointer_stringify(name)); 159 | }, 160 | UNITY_SAVE_BYTEARRAY: function (arr, size, name, mimetype) 161 | { 162 | var bytes = new Uint8Array(size); 163 | for (var i = 0; i < size; i++) bytes[i] = HEAPU8[arr + i]; 164 | var blob = new Blob([bytes], { type: Pointer_stringify(mimetype) }); 165 | saveAs(blob, Pointer_stringify(name)); 166 | }, 167 | UNITY_IS_SUPPORTED: function () 168 | { 169 | try 170 | { 171 | var isFileSaverSupported = !!new Blob; 172 | return isFileSaverSupported; 173 | } 174 | catch (e) {return false;} 175 | return false; 176 | }, 177 | saveAs: null 178 | }); 179 | --------------------------------------------------------------------------------