(function () {
  // #region 通用变量和方法
  let isOPPO = () => {
    const result = window.qg.getProvider().toLowerCase() === 'oppo'
    isOPPO = () => result
    return isOPPO()
  }

  let isVivo = () => {
    const result = window.qg.getProvider().toLowerCase() === 'vivo'
    isVivo = () => result
    return isVivo()
  }

  let isMi = () => {
    const result = window.qg.getProvider().toLowerCase().includes('mi')
    isMi = () => result
    return isMi()
  }

  let isHonor = () => {
    const result = window.qg.getProvider().toLowerCase() === 'honor'
    isHonor = () => result
    return isHonor()
  }
  // #endregion

  // ### 底层函数 Object 相关修改 ###
  // 保存原始的 Object.defineProperty 方法
  var originalDefineProperty = Object.defineProperty
  // 重定义 Object.defineProperty 方法
  Object.defineProperty = function (obj, prop, descriptor) {
    if (prop === 'HTMLCanvasElement') {
      try {
        // 调用原始的 Object.defineProperty 方法
        originalDefineProperty(obj, prop, descriptor)
      } catch (err) {
        console.warn(err)
      }
    } else {
      // 调用原始的 Object.defineProperty 方法
      originalDefineProperty(obj, prop, descriptor)
    }
  }
  // 将对象定义为不可更改
  const defineReadOnlyPropertyContain = (obj, name, value) => {
    try {
      Object.defineProperty(obj, name, {
        enumerable: true,
        get () {
          return value
        },
        set (newValue) {
          if (newValue !== value) {
            console.warn(`rewrite the property: ${name}, read only.`, newValue)
            return
          }
          value = newValue
        }
      })
    } catch (e) {
      console.warn('不影响正常运行：', e)
    }
  }

  // 将对象定义为不可更改
  const defineReadOnlyPropertyCanDefine = (obj, name, value) => {
    try {
      Object.defineProperty(obj, name, {
        enumerable: true,
        get () {
          return value
        },
        set (newValue) {
          if (newValue !== value) {
            console.warn(`rewrite the property: ${name}`, newValue)
          }
          value = newValue
        }
      })
    } catch (e) {
      console.warn('不影响正常运行：', e)
    }
  }
  try {
    // 兼容vivo旧的readFileSync接口的回调接口
    const stringPro = String.prototype
    Object.defineProperty(stringPro, 'data', {
      get: function () {
        return this.toString()
      },
      enumerable: false
    })
    Object.defineProperty(stringPro, 'text', {
      get: function () {
        return this.toString()
      },
      enumerable: false
    })
    if (!('replaceAll' in stringPro)) {
      Object.defineProperty(stringPro, 'replaceAll', {
        get: function (from, to) {
          from = from.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
          return this.replace(new RegExp(from, 'g'), to)
        },
        enumerable: false
      })
    }
    const arrayBufferPro = ArrayBuffer.prototype
    Object.defineProperty(arrayBufferPro, 'data', {
      get: function () {
        return this
      },
      enumerable: false
    })
    Object.defineProperty(arrayBufferPro, 'text', {
      get: function () {
        return this
      },
      enumerable: false
    })
    if (!('replaceAll' in stringPro)) {
      Object.defineProperty(stringPro, 'replaceAll', {
        get: function (from, to) {
          from = from.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
          return this.replace(new RegExp(from, 'g'), to)
        },
        enumerable: false
      })
    }
  } catch (err) {
  }

  // ### 渲染层相关修改 ###
  let descriptor = Object.getOwnPropertyDescriptor(window, 'TouchEvent')
  if (window.TouchEvent && descriptor && descriptor.writable && descriptor.configurable) {
    defineReadOnlyPropertyContain(window, 'TouchEvent', window.TouchEvent)
  }
  descriptor = Object.getOwnPropertyDescriptor(window, 'Image')
  if (window.Image && descriptor && descriptor.writable && descriptor.configurable) {
    defineReadOnlyPropertyContain(window, 'Image', window.Image)
  }
  descriptor = Object.getOwnPropertyDescriptor(window, 'HTMLImageElement')
  if (window.HTMLImageElement && descriptor && descriptor.writable && descriptor.configurable) {
    defineReadOnlyPropertyContain(window, 'HTMLImageElement', window.HTMLImageElement)
  }
  if (!window.qg.createImage && window.Image) {
    defineReadOnlyPropertyContain(window.qg, 'createImage', () => {
      return new window.Image()
    })
  }
  descriptor = Object.getOwnPropertyDescriptor(window, 'Blob')
  if (window.Blob && descriptor && descriptor.writable && descriptor.configurable) {
    defineReadOnlyPropertyContain(window, 'Blob', window.Blob)
  }
  descriptor = Object.getOwnPropertyDescriptor(window, 'URL')
  if (window.URL && descriptor && descriptor.writable && descriptor.configurable) {
    defineReadOnlyPropertyContain(window, 'URL', window.URL)
  }
  // 兼容 __cccanvas __canvas, cocos低版本会使用到
  // OPPO是__canvas，小米是canvas
  if (!isVivo()) {
    if (!window.canvas) {
      // 荣耀平台qg.createCanvas 无法获取gl，需要通过HTMLCanvasElement获取，而且只要调用一次webgl，否则会失败
      if (window.HTMLCanvasElement && !window.__canvas && isHonor()) {
        const canvas = new window.HTMLCanvasElement()
        if (canvas.getContext && canvas.toDataURL) {
          window.canvas = canvas
        }
      }

      if (!window.canvas) {
        window.canvas = window.__canvas ? window.__canvas : window.qg.createCanvas()
      }
    }
  }
  if (!window.mainCanvas) {
    window.mainCanvas = window.canvas ? window.canvas : window.qg.createCanvas()
  }
  if (!window.__cccanvas) {
    window.__cccanvas = window.canvas ? window.canvas : window.qg.createCanvas()
  }
  if (!window.__canvas) {
    window.__canvas = window.canvas ? window.canvas : window.qg.createCanvas()
  }

  // 针对unity runtime，小米的canvas只能获取2d或者webgl，如果先获取2d会导致webgl获取失败，所以这里先获取webgl，保证后续可以正常渲染上屏
  if (window.mainCanvas && window.__miEngineType === 'unity' && isMi()) {
    window.mainCanvas.getContext('webgl')
  }

  descriptor = Object.getOwnPropertyDescriptor(window, 'HTMLCanvasElement')
  if (window.HTMLCanvasElement && descriptor && descriptor.writable && descriptor.configurable) {
    const canvas = new window.HTMLCanvasElement()
    if (canvas.getContext && canvas.toDataURL) {
      let originCanvas = window.HTMLCanvasElement
      Object.defineProperty(window, 'HTMLCanvasElement', {
        enumerable: true,
        configurable: false,
        get () {
          return originCanvas
        },
        set (newValue) {
          if (newValue !== originCanvas) {
            console.warn(`rewrite the property: HTMLCanvasElement, read only.`, newValue)
            return
          }
          originCanvas = newValue
        }
      })
      // 兼容 qg.createCanvas().constructor 会导致荣耀崩溃
      if (isHonor()) {
        descriptor = Object.getOwnPropertyDescriptor(window.qg, 'createCanvas')
        if (window.qg.createCanvas && descriptor && descriptor.writable && descriptor.configurable) {
          const originalCreateCanvas = window.qg.createCanvas
          defineReadOnlyPropertyContain(window.qg, 'createCanvas', (params) => {
            const canvasObject = originalCreateCanvas.apply(this, params)
            // Modify the constructor property of the returned object
            Object.defineProperty(canvasObject, 'constructor', {
              value: window.HTMLCanvasElement,
              enumerable: false,
              writable: true,
              configurable: true
            })
            return canvasObject
          })
        }
      }
    } else {
      Object.defineProperty(window, 'HTMLCanvasElement', {
        set: function set (val) { },
        get: function get () {
          return window.__canvas.constructor
        }
      })

      // 兼容其他平台 getContext只能获取一个，先获取2d会导致webgl获取失败，所以这里先获取webgl，保证后续可以正常渲染上屏
      descriptor = Object.getOwnPropertyDescriptor(window.mainCanvas, 'getContext')
      if (window.mainCanvas.getContext && descriptor && descriptor.writable && descriptor.configurable) {
        const originalGetContext = window.mainCanvas.getContext
        defineReadOnlyPropertyContain(window.mainCanvas, 'getContext', (params) => {
          var contextObject = null
          try {
            contextObject = originalGetContext.apply(window.mainCanvas, Array.isArray(params) ? params : [params])
          } catch (err) {
            console.error(err)
          }

          if (!contextObject) {
            try {
              contextObject = new window.HTMLCanvasElement().getContext(params)
            } catch (err) {
              console.error(err)
            }
          }
          return contextObject
        })
      }
    }
  }

  // ##引擎插件适配，联盟暂时不支持引擎插件
  try {
    descriptor = window.requirePlugin && Object.getOwnPropertyDescriptor(window, 'requirePlugin')
    if (!window.requirePlugin || (descriptor && descriptor.writable && descriptor.configurable)) {
      const requirePluginOrigin = window.requirePlugin
      defineReadOnlyPropertyCanDefine(window, 'requirePlugin', (params) => {
        try {
          if (requirePluginOrigin && window.qg.getProvider().toLowerCase() === 'vivo') {
            return requirePluginOrigin.apply(this, params)
          }
        } catch (err) {
          console.error(err)
        }
        return window.require(params)
      })
    }
  } catch (err) { }

  // ### cocos或laya特殊适配层逻辑转换 ###
  // 兼容 loadRuntime
  if (!window.loadRuntime) {
    window.loadRuntime = () => {
      return window.qg
    }
  }
  // 兼容 __gl
  if (window.WebGLRenderingContext && !window.__gl) {
    window.__gl = window.WebGLRenderingContext.prototype
  }
  // 兼容 getCanvasRenderingContext2D
  if (window.CanvasRenderingContext2D && !window.qg.getCanvasRenderingContext2D) {
    defineReadOnlyPropertyContain(window.qg, 'getCanvasRenderingContext2D', () => {
      return window.CanvasRenderingContext2D
    })
  }
  // 给cocos 游戏使用 兼容适配
  window.scriptEngineType = ''
  if (!window.__getOS) {
    defineReadOnlyPropertyContain(window, '__getOS', () => 'Android')
  }
  if (!window.__getCurrentLanguage) {
    defineReadOnlyPropertyContain(window, '__getCurrentLanguage', () => 'zh')
  }
  if (!window.__getOSVersion) {
    defineReadOnlyPropertyContain(window, '__getOSVersion', () => 'android')
  }
  if (!window.__getPlatform) {
    defineReadOnlyPropertyContain(window, '__getPlatform', () => 3) // cocos 专用，写死的3
  }

  // 兼容 getUserAgent
  if (!window.qg.getUserAgent) {
    defineReadOnlyPropertyContain(window.qg, 'getUserAgent', () => {
      return 'Dalvik/2.1.0 (Linux; U; Build/RP1A.200720.012) hap/vivo com.vivo.hybrid/ VIVO VVGame '
    })
  }
  // 兼容laya引擎获取userAgent判断逻辑修改
  if (window.navigator) {
    window.navigator.userAgent = 'Dalvik/2.1.0 (Linux; U; Build/RP1A.200720.012) hap/vivo com.vivo.hybrid/ VIVO VVGame NetType/WIFI Language/zh_CN'
  } else {
    window.navigator = {}
    window.navigator.userAgent = 'Dalvik/2.1.0 (Linux; U; Build/RP1A.200720.012) hap/vivo com.vivo.hybrid/ VIVO VVGame NetType/WIFI Language/zh_CN'
  }

  // 兼容 getCurrentLanguage
  if (!window.qg.getCurrentLanguage) {
    defineReadOnlyPropertyContain(window.qg, 'getCurrentLanguage', () => {
      return 'zh'
    })
  }
  // 兼容 getOS
  if (!window.qg.getOS) {
    defineReadOnlyPropertyContain(window.qg, 'getOS', () => {
      return 'android'
    })
  }
  // 兼容jsb接口
  const systemInfo = window.qg.getSystemInfoSync()
  if (!window.jsb) {
    window.jsb = {}
  }
  if (!window.jsb.openURL) {
    window.jsb.openURL = () => { }
  }
  if (!window.jsb.AudioEngine) {
    window.jsb.AudioEngine = window.qg.createInnerAudioContext()
  }
  if (!window.jsb.loadFont) {
    window.jsb.loadFont = window.qg.loadFont
  }
  if (!window.jsb.setPreferredFramesPerSecond) {
    window.jsb.setPreferredFramesPerSecond = window.qg.setPreferredFramesPerSecond
  }
  if (!window.jsb.device) {
    window.jsb.device = {
      setMotionInterval: () => { },
      setMotionEnabled: () => { },
      dispatchDeviceMotionEvent: () => { }
    }
  }
  if (!window.jsb.reflection) {
    window.jsb.reflection = {
      callStaticMethod: function () {
        console.error('不支持jsb.reflection.callStaticMethod这种调用，请尽快删除这种调用')
      }
    }
  }
  if (!window.jsb.language) {
    window.jsb.language = systemInfo.language
  }
  if (!window.jsb.system) {
    window.jsb.system = systemInfo.system
  }
  if (!window.jsb.platform) {
    defineReadOnlyPropertyContain(window.jsb, 'platform', 'android')
  }

  // 字体相关baseLine适配到web标准，cocos3.6+才支持兼容逻辑
  if (!window.qg.getFeature) {
    defineReadOnlyPropertyContain(window.qg, 'getFeature', (name) => {
      if (name === 'canvas.context2d.textbaseline.alphabetic' || name === 'canvas.context2d.textbaseline.default') {
        return 1
      }
    })
  }
  if (!window.qg.setFeature) {
    defineReadOnlyPropertyContain(window.qg, 'setFeature', () => { })
  }

  // ### API1.0到2.0联盟接口的适配层 ###
  // ###### Network ######
  if (!window.qg.download && window.qg.downloadFile) {
    defineReadOnlyPropertyContain(window.qg, 'download', window.qg.downloadFile)
  }

  // ###### Storage ######
  // 兼容 getStorageSync 旧接口
  descriptor = Object.getOwnPropertyDescriptor(window.qg, 'getStorageSync')
  if (window.qg.getStorageSync && descriptor && descriptor.writable && descriptor.configurable) {
    const getStorageSyncOrigin = window.qg.getStorageSync
    defineReadOnlyPropertyContain(window.qg, 'getStorageSync', (data) => {
      if (data && typeof data !== 'string' && data.key) {
        try {
          return getStorageSyncOrigin(data.key)
        } catch (err) { }
        return ''
      }
      return getStorageSyncOrigin(data)
    })
  }
  // 兼容 deleteStorageSync
  descriptor = Object.getOwnPropertyDescriptor(window.qg, 'deleteStorageSync')
  if (window.qg.deleteStorageSync && descriptor && descriptor.writable && descriptor.configurable) {
    const deleteStorageSyncOrigin = window.qg.deleteStorageSync
    defineReadOnlyPropertyContain(window.qg, 'deleteStorageSync', (data) => {
      if (data && typeof data !== 'string' && data.key) {
        try {
          return deleteStorageSyncOrigin(data.key)
        } catch (err) { }
        return
      }
      return deleteStorageSyncOrigin(data)
    })
  } if (window.qg.removeStorageSync && !window.qg.deleteStorageSync && descriptor && descriptor.writable && descriptor.configurable) {
    defineReadOnlyPropertyContain(window.qg, 'deleteStorageSync', (data) => {
      if (data && typeof data !== 'string' && data.key) {
        try {
          return window.qg.removeStorageSync(data.key)
        } catch (err) { }
        return
      }
      return window.qg.removeStorageSync(data)
    })
  }
  descriptor = Object.getOwnPropertyDescriptor(window.qg, 'deleteStorage')
  if (window.qg.removeStorage && !window.qg.deleteStorage && descriptor && descriptor.writable && descriptor.configurable) {
    defineReadOnlyPropertyContain(window.qg, 'deleteStorage', window.qg.removeStorage)
  }
  // 兼容 setStorageSync
  descriptor = Object.getOwnPropertyDescriptor(window.qg, 'setStorageSync')
  if (window.qg.setStorageSync && descriptor && descriptor.writable && descriptor.configurable) {
    const setStorageSyncOrigin = window.qg.setStorageSync
    defineReadOnlyPropertyContain(window.qg, 'setStorageSync', (...params) => {
      let data
      let storageData = {}
      if (params.length === 1) {
        data = params[0].value || params[0].data
        storageData = {
          key: params[0].key || '',
          value: data || ''
        }
        try {
          return setStorageSyncOrigin(storageData.key, storageData.value)
        } catch (err) { }
        return 'key not define'
      } else {
        storageData = {
          key: params[0] || '',
          value: params[1] || ''
        }
        return setStorageSyncOrigin(storageData.key, storageData.value)
      }
    })
  }

  // ###### File ######
  var fs = window.qg.getFileSystemManager()
  var needUserReplace = false
  if (window.qg.env && window.qg.env.USER_DATA_PATH !== 'internal://files') {
    console.warn(`qg.env.USER_DATA_PATH is not alliance, ${window.qg.env.USER_DATA_PATH}`)
    needUserReplace = true
  }
  // 兼容 qg 旧的文件管理
  if (!window.qg.accessFile) {
    defineReadOnlyPropertyContain(window.qg, 'accessFile', (data) => {
      try {
        fs.accessSync(needUserReplace ? data.uri.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.uri)
        return 'true'
      } catch (err) { }
      return 'false'
    })
  }
  if (!window.qg.copyFile) {
    defineReadOnlyPropertyContain(window.qg, 'copyFile', (data) => {
      if (data.srcUri) {
        data.srcPath = data.srcUri
      }
      data.srcPath = needUserReplace ? data.srcPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.srcPath
      if (data.dstUri) {
        data.destPath = data.dstUri
      }
      data.destPath = needUserReplace ? data.destPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.destPath
      return fs.copyFile(data)
    })
  }
  if (!window.qg.moveFile) {
    defineReadOnlyPropertyContain(window.qg, 'moveFile', (data) => {
      if (data.srcUri) {
        data.oldPath = data.srcUri
      }
      data.oldPath = needUserReplace ? data.oldPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.oldPath
      if (data.dstUri) {
        data.newPath = data.dstUri
      }
      data.newPath = needUserReplace ? data.newPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.newPath
      return fs.rename(data)
    })
  }
  if (!window.qg.getFileInfo) {
    defineReadOnlyPropertyContain(window.qg, 'getFileInfo', (data) => {
      if (data.uri) {
        data.filePath = data.uri
      }
      data.filePath = needUserReplace ? data.filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.filePath
      const suc = data.success
      data.success = (res) => {
        res.uri = data.filePath
        res.length = res.size
        res.lastModifiedTime = 0
        if (suc && typeof suc === 'function') {
          suc(res)
        }
      }
      return fs.getFileInfo(data)
    })
  }
  if (!window.qg.listDir) {
    defineReadOnlyPropertyContain(window.qg, 'listDir', (data) => {
      if (data.uri) {
        data.path = data.uri
      }
      data.path = needUserReplace ? data.path.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.path
      data.recursive = true
      const suc = data.success
      data.success = (res) => {
        var files = []
        for (var i = 0; i < res.length; i++) {
          const file = {
            uri: res[i].path,
            length: res[i].stats.size,
            lastModifiedTime: res[i].stats.lastModifiedTime
          }
          files.push(file)
        }
        if (suc && typeof suc === 'function') {
          suc({
            fileList: files
          })
        }
      }
      return fs.stat(data)
    })
  }
  if (!window.qg.deleteFile) {
    defineReadOnlyPropertyContain(window.qg, 'deleteFile', (data) => {
      if (data.uri) {
        data.filePath = data.uri
      }
      data.filePath = needUserReplace ? data.filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.filePath
      return fs.unlink(data)
    })
  }
  if (!window.qg.readFile) {
    defineReadOnlyPropertyContain(window.qg, 'readFile', (data) => {
      if (data.uri) {
        data.filePath = data.uri
      }
      data.filePath = needUserReplace ? data.filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.filePath
      const suc = data.success
      data.success = (res) => {
        res.text = res.data
        if (suc && typeof suc === 'function') {
          suc(res)
        }
      }
      return fs.readFile(data)
    })
  }
  if (!window.qg.readFileSync) {
    defineReadOnlyPropertyContain(window.qg, 'readFileSync', (data) => {
      try {
        let res
        const filePath = needUserReplace ? data.uri.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.uri
        if (data.encoding && data.position && data.length) {
          res = fs.readFileSync(filePath, data.encoding, data.position, data.length)
        } else if (data.encoding && data.position) {
          res = fs.readFileSync(filePath, data.encoding, data.position)
        } else if (data.encoding) {
          res = fs.readFileSync(filePath, data.encoding)
        } else {
          res = fs.readFileSync(filePath)
        }

        if (res) {
          return {
            text: res
          }
        }
      } catch (err) {
        console.error(err)
      }
      return 'no such file or directory'
    })
  }
  if (!window.qg.writeFile) {
    defineReadOnlyPropertyContain(window.qg, 'writeFile', (data) => {
      if (data.uri) {
        data.filePath = data.uri
      }
      data.filePath = needUserReplace ? data.filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.filePath
      if (data.text) {
        data.data = data.text
      }
      return fs.writeFile(data)
    })
  }
  if (!window.qg.writeFileSync) {
    defineReadOnlyPropertyContain(window.qg, 'writeFileSync', (data) => {
      try {
        const filePath = needUserReplace ? data.uri.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.uri
        if (data.text && data.encoding) {
          return fs.writeFileSync(filePath, data.text, data.encoding)
        } else {
          return fs.writeFileSync(filePath, data.text)
        }
      } catch (err) {
        console.error(err)
      }
      return 'no such file or directory'
    })
  }
  if (!window.qg.appendFile) {
    defineReadOnlyPropertyContain(window.qg, 'appendFile', (data) => {
      if (data.uri) {
        data.filePath = data.uri
      }
      data.filePath = needUserReplace ? data.filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.filePath
      if (data.text) {
        data.data = data.text
      }
      return fs.appendFile(data)
    })
  }
  if (!window.qg.mkdir) {
    defineReadOnlyPropertyContain(window.qg, 'mkdir', (data) => {
      if (data.uri) {
        data.dirPath = data.uri + '/'
        data.recursive = true
      }
      data.dirPath = needUserReplace ? data.dirPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.dirPath
      return fs.mkdir(data)
    })
  }
  if (!window.qg.rmdir) {
    defineReadOnlyPropertyContain(window.qg, 'rmdir', (data) => {
      if (data.uri) {
        data.dirPath = data.uri
      }
      data.dirPath = needUserReplace ? data.dirPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.dirPath
      return fs.rmdir(data)
    })
  }
  if (!window.qg.isDirectory) {
    defineReadOnlyPropertyContain(window.qg, 'isDirectory', (data) => {
      try {
        const res = fs.statSync(needUserReplace ? data.uri.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.uri, false)
        return res.isDirectory()
      } catch (err) { }
      return false
    })
  }
  if (!window.qg.isFile) {
    defineReadOnlyPropertyContain(window.qg, 'isFile', (data) => {
      try {
        const res = fs.statSync(needUserReplace ? data.uri.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.uri, false)
        return res.isFile()
      } catch (err) { }
      return false
    })
  }
  if (!window.qg.renameFile) {
    defineReadOnlyPropertyContain(window.qg, 'renameFile', (data) => {
      if (data.srcUri) {
        data.oldPath = data.srcUri
      }
      data.oldPath = needUserReplace ? data.oldPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.oldPath
      if (data.dstUri) {
        data.newPath = data.dstUri
      }
      data.newPath = needUserReplace ? data.newPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.newPath
      return fs.rename(data)
    })
  }
  if (!window.qg.unzipFile) {
    defineReadOnlyPropertyContain(window.qg, 'unzipFile', (data) => {
      if (data.srcUri) {
        data.zipFilePath = data.srcUri
      }
      data.zipFilePath = needUserReplace ? data.zipFilePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.zipFilePath
      if (data.dstUri) {
        data.targetPath = data.dstUri
      }
      data.targetPath = needUserReplace ? data.targetPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : data.targetPath
      return fs.unzip(data)
    })
  }

  // 兼容 FileSystemManager
  const readInject = fs.readFileSync
  defineReadOnlyPropertyCanDefine(fs, 'readFileSync', (...params) => {
    if (params.length === 1 && (params[0].uri || params[0].filePath)) {
      try {
        let filePath = params[0].uri || params[0].filePath
        filePath = needUserReplace ? filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : filePath
        if (params[0].encoding && params[0].position && params[0].length) {
          const result = readInject.bind(fs)(filePath, params[0].encoding, params[0].position, params[0].length)
          return result
        } else if (params[0].encoding && params[0].position) {
          const result = readInject.bind(fs)(filePath, params[0].encoding, params[0].position)
          return result
        } else if (params[0].encoding) {
          const result = readInject.bind(fs)(filePath, params[0].encoding)
          return result
        } else {
          const result = readInject.bind(fs)(filePath)
          return result
        }
      } catch (err) {
        console.error(err)
        throw (err)
      }
    }
    params[0] = needUserReplace ? params[0].replace('internal://files', window.qg.env.USER_DATA_PATH) : params[0]
    return readInject.bind(fs)(...params)
  })
  const unlinkInject = fs.unlink
  defineReadOnlyPropertyCanDefine(fs, 'unlink', (params) => {
    params.filePath = needUserReplace ? params.filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.filePath
    return unlinkInject.bind(fs)(params)
  })
  const copyFileInject = fs.copyFile
  defineReadOnlyPropertyCanDefine(fs, 'copyFile', (params) => {
    params.srcPath = needUserReplace ? params.srcPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.srcPath
    params.destPath = needUserReplace ? params.destPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.destPath
    return copyFileInject.bind(fs)(params)
  })
  const writeFileInject = fs.writeFile
  defineReadOnlyPropertyCanDefine(fs, 'writeFile', (params) => {
    params.filePath = needUserReplace ? params.filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.filePath
    if (ArrayBuffer.isView(params.data) || params.data instanceof ArrayBuffer) {
      params.encoding = 'binary'
    }
    return writeFileInject.bind(fs)(params)
  })
  const writeFileSyncInject = fs.writeFileSync
  defineReadOnlyPropertyCanDefine(fs, 'writeFileSync', (path, data, encoding) => {
    path = needUserReplace ? path.replace('internal://files', window.qg.env.USER_DATA_PATH) : path
    if (ArrayBuffer.isView(data) || data instanceof ArrayBuffer) {
      return writeFileSyncInject.bind(fs)(path, data, 'binary')
    }
    return writeFileSyncInject.bind(fs)(path, data, encoding)
  })
  const readFileInject = fs.readFile
  defineReadOnlyPropertyCanDefine(fs, 'readFile', (params) => {
    params.filePath = needUserReplace ? params.filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.filePath
    // @note: OPPO 底层在路径不存在时未回调 fail，在此兼容一下，其他渠道保持不变
    if (isOPPO()) {
      try {
        fs.accessSync(params.filePath)
        return readFileInject.bind(fs)(params)
      } catch (err) {
        if (params.fail && typeof params.fail === 'function') {
          params.fail(err)
        }
        if (params.complete && typeof params.complete === 'function') {
          params.complete(err)
        }
      }
    } else {
      return readFileInject.bind(fs)(params)
    }
  })
  const readdirInject = fs.readdir
  defineReadOnlyPropertyCanDefine(fs, 'readdir', (params) => {
    params.dirPath = needUserReplace ? params.dirPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.dirPath
    return readdirInject.bind(fs)(params)
  })
  const mkdirSyncInject = fs.mkdirSync
  defineReadOnlyPropertyCanDefine(fs, 'mkdirSync', (path, recursive) => {
    path = needUserReplace ? path.replace('internal://files', window.qg.env.USER_DATA_PATH) : path
    return mkdirSyncInject.bind(fs)(path, recursive)
  })
  const rmdirSyncInject = fs.rmdirSync
  defineReadOnlyPropertyCanDefine(fs, 'rmdirSync', (dirPath, recursive) => {
    dirPath = needUserReplace ? dirPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : dirPath
    return rmdirSyncInject.bind(fs)(dirPath, recursive)
  })
  const accessInject = fs.access
  defineReadOnlyPropertyCanDefine(fs, 'access', (params) => {
    params.path = needUserReplace ? params.path.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.path
    return accessInject.bind(fs)(params)
  })
  const accessSyncInject = fs.accessSync
  defineReadOnlyPropertyCanDefine(fs, 'accessSync', (filePath) => {
    filePath = needUserReplace ? filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : filePath
    return accessSyncInject.bind(fs)(filePath)
  })
  const unzipInject = fs.unzip
  defineReadOnlyPropertyCanDefine(fs, 'unzip', (params) => {
    params.zipFilePath = needUserReplace ? params.zipFilePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.zipFilePath
    params.targetPath = needUserReplace ? params.targetPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.targetPath
    return unzipInject.bind(fs)(params)
  })

  if (isHonor()) {
    const saveFileInject = fs.saveFile
    defineReadOnlyPropertyCanDefine(fs, 'saveFile', (params) => {
      params.filePath = needUserReplace ? params.filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.filePath 
      return saveFileInject.bind(fs)(params)
    })
    const saveFileSyncInject = fs.saveFileSync
    defineReadOnlyPropertyCanDefine(fs, 'saveFileSync', (tempFilePath, filePath) => {
      filePath = needUserReplace ? filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : filePath 
      return saveFileSyncInject.bind(fs)(tempFilePath, filePath)
    })
    const mkdirInject = fs.mkdir
    defineReadOnlyPropertyCanDefine(fs, 'mkdir', (params) => {
      params.dirPath = needUserReplace ? params.dirPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.dirPath 
      return mkdirInject.bind(fs)(params)
    })
    const readdirSyncInject = fs.readdirSync
    defineReadOnlyPropertyCanDefine(fs, 'readdirSync', (dirPath) => {
      dirPath = needUserReplace ? dirPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : dirPath
      return readdirSyncInject.bind(fs)(dirPath)
    })
    const copyFileSyncInject = fs.copyFileSync
    defineReadOnlyPropertyCanDefine(fs, 'copyFileSync', (srcPath, destPath) => {
      srcPath = needUserReplace ? srcPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : srcPath
      destPath = needUserReplace ? destPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : destPath
      return copyFileSyncInject.bind(fs)(srcPath, destPath)
    })
    const getFileInfoInject = fs.getFileInfo
    defineReadOnlyPropertyCanDefine(fs, 'getFileInfo', (params) => {
      params.filePath = needUserReplace ? params.filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.filePath
      return getFileInfoInject.bind(fs)(params)
    })
    const rmdirInject = fs.rmdir
    defineReadOnlyPropertyCanDefine(fs, 'rmdir', (params) => {
      params.dirPath = needUserReplace ? params.dirPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.dirPath
      return rmdirInject.bind(fs)(params)
    })
    const unlinkSyncInject = fs.unlinkSync
    defineReadOnlyPropertyCanDefine(fs, 'unlinkSync', (filePath) => {
      filePath = needUserReplace ? filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : filePath
      return unlinkSyncInject.bind(fs)(filePath)
    })
    const appendFileInject = fs.appendFile
    defineReadOnlyPropertyCanDefine(fs, 'appendFile', (params) => { 
      params.filePath = needUserReplace ? params.filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.filePath
      return appendFileInject.bind(fs)(params)
    })
    const appendFileSyncInject = fs.appendFileSync
    defineReadOnlyPropertyCanDefine(fs, 'appendFileSync', (filePath, data, encoding) => {
      filePath = needUserReplace ? filePath.replace('internal://files', window.qg.env.USER_DATA_PATH) : filePath
      return appendFileSyncInject.bind(fs)(filePath, data, encoding)
    })
    const renameInject = fs.rename
    defineReadOnlyPropertyCanDefine(fs, 'rename', (params) => {
      params.newPath = needUserReplace ? params.newPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.newPath 
      params.oldPath = needUserReplace ? params.oldPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.oldPath
      return renameInject.bind(fs)(params)
    })
    const renameSyncInject = fs.renameSync
    defineReadOnlyPropertyCanDefine(fs, 'renameSync', (oldPath, newPath) => {
      oldPath = needUserReplace ? oldPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : oldPath
      newPath = needUserReplace ? newPath.replace('internal://files', window.qg.env.USER_DATA_PATH) : newPath
      return renameSyncInject.bind(fs)(oldPath, newPath)
    })
    const statInject = fs.stat
    defineReadOnlyPropertyCanDefine(fs, 'stat', (params) => {
      params.path = needUserReplace ? params.path.replace('internal://files', window.qg.env.USER_DATA_PATH) : params.path
      return statInject.bind(fs)(params)
    })
    const statSyncInject = fs.statSync
    defineReadOnlyPropertyCanDefine(fs, 'statSync', (path, recursive) => {
      path = needUserReplace ? path.replace('internal://files', window.qg.env.USER_DATA_PATH) : path
      return statSyncInject.bind(fs)(path, recursive)
    })
  }

  // 兼容 subscribeNetworkStatus
  if (window.qg.onNetworkStatusChange && !window.qg.subscribeNetworkStatus) {
    defineReadOnlyPropertyContain(window.qg, 'subscribeNetworkStatus', (params = {}) => {
      const suc = params.success
      params.success = (data) => {
        data.type = data.networkType
        if (suc && typeof suc === 'function') {
          suc(data)
        }
      }
      return window.qg.onNetworkStatusChange(params.success)
    })
  }

  // 兼容 loadFont
  descriptor = Object.getOwnPropertyDescriptor(window.qg, 'loadFont')
  if (window.qg.loadFont && descriptor && descriptor.writable && descriptor.configurable) {
    const loadFontOrigin = window.qg.loadFont
    defineReadOnlyPropertyContain(window.qg, 'loadFont', (...params) => {
      if (params.length === 1) {
        return loadFontOrigin(params[0])
      } else {
        if (params[1].startsWith('url(')) {
          return loadFontOrigin(params[1].slice(5, -2))
        }
        return loadFontOrigin(params[1])
      }
    })
  }

  // 兼容 getDeviceRotation
  if (!window.qg.getDeviceRotation) {
    defineReadOnlyPropertyContain(window.qg, 'getDeviceRotation', () => {
      const data = window.qg.getSystemInfoSync()
      if (data.deviceOrientation === 'portrait') {
        return {
          rotation: 0
        }
      } else {
        return {
          rotation: 1
        }
      }
    })
  }

  // 兼容 subscribeAccelerometer unsubscribeAccelerometer
  if (window.qg.startAccelerometer && !window.qg.subscribeAccelerometer) {
    defineReadOnlyPropertyContain(window.qg, 'subscribeAccelerometer', window.qg.startAccelerometer)
  }

  if (window.qg.stopAccelerometer && !window.qg.unsubscribeAccelerometer) {
    defineReadOnlyPropertyContain(window.qg, 'unsubscribeAccelerometer', window.qg.stopAccelerometer)
  }

  // #region 网络适配
  // @note: 暂时只针对 OPPO，其他渠道按需使用
  if (!isVivo()) {
    const xhrOpenProxy = window.XMLHttpRequest.prototype.open
    window.XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
      this.url = url
      return xhrOpenProxy.apply(this, arguments)
    }
  }
  // #endregion

  // ### 其他接口适配 ###
  // touch
  var _jsb = window.jsb
  if (!_jsb) {
    _jsb = {}
  }
  var _touches = []
  var _getTouchIndex = function _getTouchIndex (touch) {
    var element
    for (var index = 0; index < _touches.length; index++) {
      element = _touches[index]
      if (touch.identifier === element.identifier) {
        return index
      }
    }
    return -1
  }
  var _copyObject = function _copyObject (fromObj, toObject) {
    for (var key in fromObj) {
      if (fromObj.hasOwnProperty (key)) {
        toObject[key] = fromObj[key]
      }
    }
  }
  var _listenerMap = {}
  _listenerMap.touchstart = []
  _listenerMap.touchmove = []
  _listenerMap.touchend = []
  _listenerMap.touchcancel = []

  function _addListener (key, value) {
    var listenerArr = _listenerMap[key]
    for (var index = 0, length = listenerArr.length; index < length; index++) {
      if (value === listenerArr[index]) {
        return
      }
    }
    listenerArr.push(value)
  }

  function _removeListener (key, value) {
    var listenerArr = _listenerMap[key] || []
    var length = listenerArr.length
    for (var index = 0; index < length; ++index) {
      if (value === listenerArr[index]) {
        listenerArr.splice(index, 1)
        return
      }
    }
  }
  var _hasDellWith = false
  var _systemInfo = window.qg.getSystemInfoSync()
  if (window.innerWidth && _systemInfo.windowWidth !== window.innerWidth) {
    _hasDellWith = true
  }
  var _touchEventHandlerFactory = function _touchEventHandlerFactory (type) {
    return function (changedTouches) {
      if (typeof changedTouches === 'function') {
        _addListener(type, changedTouches)
        return
      }
      var touchEvent = new window.TouchEvent(type)
      var index
      if (type === 'touchstart') {
        changedTouches.forEach(function (touch) {
          index = _getTouchIndex(touch)
          if (index >= 0) {
            _copyObject(touch, _touches[index])
          } else {
            var tmp = {}
            _copyObject(touch, tmp)
            _touches.push(tmp)
          }
        })
      } else if (type === 'touchmove') {
        changedTouches.forEach(function (element) {
          index = _getTouchIndex(element)
          if (index >= 0) {
            _copyObject(element, _touches[index])
          }
        })
      } else if (type === 'touchend' || type === 'touchcancel') {
        changedTouches.forEach(function (element) {
          index = _getTouchIndex(element)
          if (index >= 0) {
            _touches.splice(index, 1)
          }
        })
      }
      var touches = [].concat(_touches)
      var _changedTouches = []
      changedTouches.forEach(function (touch) {
        var length = touches.length
        for (var _index = 0; _index < length; ++_index) {
          var _touch = touches[_index]
          if (touch.identifier === _touch.identifier) {
            _changedTouches.push(_touch)
            return
          }
        }
        _changedTouches.push(touch)
      })
      touchEvent.touches = touches
      touchEvent.targetTouches = touches
      touchEvent.changedTouches = _changedTouches
      if (_hasDellWith) {
        touches.forEach(function (touch) {
          touch.clientX /= window.devicePixelRatio
          touch.clientY /= window.devicePixelRatio
          touch.pageX /= window.devicePixelRatio
          touch.pageY /= window.devicePixelRatio
        })
        if (type === 'touchcancel' || type === 'touchend') {
          _changedTouches.forEach(function (touch) {
            touch.clientX /= window.devicePixelRatio
            touch.clientY /= window.devicePixelRatio
            touch.pageX /= window.devicePixelRatio
            touch.pageY /= window.devicePixelRatio
          })
        }
      }
      var listenerArr = _listenerMap[type]
      var length = listenerArr.length
      for (var _index2 = 0; _index2 < length; _index2++) {
        listenerArr[_index2](touchEvent)
      }
    }
  }

  if (!window.qg.onTouchStart) {
    _jsb.onTouchStart = _touchEventHandlerFactory('touchstart')
    _jsb.offTouchStart = function (callback) {
      _removeListener('touchstart', callback)
    }
    window.qg.onTouchStart = _jsb.onTouchStart.bind(_jsb)
    window.qg.offTouchStart = _jsb.offTouchStart.bind(_jsb)
  }
  if (!window.qg.onTouchMove) {
    _jsb.onTouchMove = _touchEventHandlerFactory('touchmove')
    _jsb.offTouchMove = function (callback) {
      _removeListener('touchmove', callback)
    }
    window.qg.onTouchMove = _jsb.onTouchMove.bind(_jsb)
    window.qg.offTouchMove = _jsb.offTouchMove.bind(_jsb)
  }
  if (!window.qg.onTouchCancel) {
    _jsb.onTouchCancel = _touchEventHandlerFactory('touchcancel')
    _jsb.offTouchCancel = function (callback) {
      _removeListener('touchcancel', callback)
    }
    window.qg.onTouchCancel = _jsb.onTouchCancel.bind(_jsb)
    window.qg.offTouchCancel = _jsb.offTouchCancel.bind(_jsb)
  }
  if (!window.qg.onTouchEnd) {
    _jsb.onTouchEnd = _touchEventHandlerFactory('touchend')
    _jsb.offTouchEnd = function (callback) {
      _removeListener('touchend', callback)
    }
    window.qg.onTouchEnd = _jsb.onTouchEnd.bind(_jsb)
    window.qg.offTouchEnd = _jsb.offTouchEnd.bind(_jsb)
  }

  // 小米适配=====================
  if (window.TouchEvent) {
    defineReadOnlyPropertyContain(window, 'TouchEvent', window.TouchEvent)
  }
  if (window.Event) {
    defineReadOnlyPropertyContain(window, 'Event', window.Event)
  }
  if (window.mainCanvas) {
    defineReadOnlyPropertyContain(window, 'mainCanvas', window.mainCanvas)
  }

  // #region 游戏引擎特殊逻辑
  if (isOPPO()) {
    // 引擎底层针对 unity 游戏有特殊处理
    try {
      const webglJSON = JSON.parse(fs.readFileSync('./buildUnity/webgl.json', 'utf8'))
      if (webglJSON.unityVersion) {
        window.qg.setIsUnityGame(true)
      }

      if (window.crypto && !window.crypto.getRandomValues) {
        window.crypto = null
      }
    } catch (ignore) { }
  }
  // #endregion
})()
