Compare commits

...

153 Commits

Author SHA1 Message Date
a1aa816426 合并Switch版本修改 2025-08-21 10:34:57 +08:00
ccf2279947 AxiHttp规范 2025-08-21 10:32:38 +08:00
dd12a88d11 Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-08-21 02:28:20 +08:00
01da084b5a Switch初次游玩游戏归档 2025-08-21 02:28:12 +08:00
c09085354b Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-08-21 00:29:59 +08:00
b07972f66d inputsystem debug 2025-08-20 15:25:25 +08:00
51d638e116 Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-08-20 15:22:14 +08:00
224e0d1126 axihttp DNS线程安全 2025-08-20 15:22:02 +08:00
2356fd7448 inputsystem 输入日志 2025-08-20 15:21:41 +08:00
78f56831ae Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-08-20 14:55:43 +08:00
ab4496ebef 修复云存档相关bug 2025-08-20 14:39:59 +08:00
ca0880e256 web 用户处理推进 2025-08-20 12:46:01 +08:00
5d44174d01 fixed 2025-08-20 12:45:08 +08:00
a5642aef54 服务端存档上传问题修复 2025-08-20 12:44:23 +08:00
f8b8eb6213 web 代码注入存档.sav下载mame 2025-08-20 12:43:36 +08:00
94808a8b84 Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-08-20 10:51:10 +08:00
31ca1b9571 修复侧边栏UI的布局计算bug(当有子菜单存在时,任意菜单项发生visible变化重新布局的位置计算错误) 2025-08-20 10:49:49 +08:00
aba9d4fa41 移除AxibugEmuOnline.GameScreenConvert/ 2025-08-20 10:29:05 +08:00
4beea769b5 Switch 归档 2025-08-20 10:27:44 +08:00
f043a8323e Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-08-20 10:26:06 +08:00
15993b493f switch 归档 2025-08-20 10:25:32 +08:00
f03bb43bdf Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-08-20 10:21:54 +08:00
09407468c2 移除冗余命名空间 2025-08-20 10:19:07 +08:00
c864dceb5d 增加存档的重试和解决冲突的操作菜单选项 2025-08-20 01:27:47 +08:00
9270852fe0 修复存档同步状态机相关问题,修改同步状态UI表现 2025-08-20 01:06:16 +08:00
627faef387 添加一些存档同步状态相关的UI资源 2025-08-20 01:05:01 +08:00
8aa3fc2b55 移除nes 核心初始化时一些不必要的报错日志 2025-08-20 00:20:03 +08:00
f275fa79c7 Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-08-19 13:35:27 +08:00
1a526b10f6 projectsetting 2025-08-19 11:27:35 +08:00
a7807194ed 增加SwitchJoyCon手柄类型支持 2025-08-19 11:27:10 +08:00
cd4163d9fe 添加joycon手柄图标资源 2025-08-19 11:26:40 +08:00
c36adccbde using 2025-08-19 10:22:29 +08:00
e1d7ef2d13 Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-08-19 10:21:27 +08:00
0f27fa328c switch 2025-08-19 10:21:17 +08:00
33283a9a34 Merge pull request '修复状态机bug' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-08-19 09:59:24 +08:00
0a45c4fbbd inputsystem 高低版本兼容 2025-08-18 22:51:04 +08:00
a1db47ab4b 完善AxiPoject工具 2025-08-18 22:20:56 +08:00
ebb4cb401c 一些不必要的usering 2025-08-18 22:20:18 +08:00
57c4947623 引入最新版本的AxiNSApi 2025-08-18 22:12:40 +08:00
629cd16016 修复状态机bug 2025-08-18 18:27:28 +08:00
8afcbce6b1 Merge pull request '增加登录时自动存档同步功能' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-08-18 17:48:41 +08:00
15c64ae655 增加登录时自动存档同步功能 2025-08-18 17:20:45 +08:00
3c04d26c50 Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-07-15 14:56:44 +08:00
a1fe8dff21 增加RealisticCRT shader,来自godot 2025-06-25 18:57:26 +08:00
fbcaecf596 remove shader RLPro 因为效果不理想 2025-06-25 18:57:10 +08:00
c69340f7b9 Merge branch 'master' of http://git.axibug.com/alienjack/AxibugEmuOnline
# Conflicts:
#	AxibugEmuOnline.Client/Assets/Script/AppMain/AxiPlayerPrefs/AxiPlayerPrefsForFileSystem.cs.meta
2025-06-25 17:31:35 +08:00
f4af119cdb CRT_easymode format 2025-06-25 17:30:58 +08:00
45d075722b 帮皓月搽屁股 2025-06-25 17:30:31 +08:00
a596d199c2 Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline
# Conflicts:
#	AxibugEmuOnline.Client/Assets/Script/AppMain/AxiPlayerPrefs/AxiPlayerPrefsForFileSystem.cs.meta
2025-06-21 15:41:12 +08:00
de7340ee39 增加一个左上角状态栏UI,用于显示当前已连接的输入设备,修复独占性设备(手柄)被错误的binding到多个模拟器虚拟输入设备的问题 2025-06-21 15:39:52 +08:00
d1c150e1be 补充shader引用 2025-06-21 15:35:26 +08:00
570419e2cc 补全代码 2025-06-20 18:58:29 +08:00
caaad7a6bf 实现PSVita背面触摸板子,模拟虚拟按键的功能 2025-06-20 18:58:10 +08:00
9995a784aa 初始化提示 2025-06-16 11:20:52 +08:00
92169c2f87 axio 2025-06-16 11:15:21 +08:00
59968bbd1f Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-06-16 10:59:28 +08:00
20b3d2e8ef 完善AxiIO为Switch可用且健壮的代码 2025-06-16 10:58:49 +08:00
7b40e190d0 引入AxiPlayerPrefs,替换现有PlayerPrefs,使用文件系统,替代不同平台的PlayerPrefs读写 2025-06-16 10:56:21 +08:00
fe53dc4021 Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-06-16 10:53:21 +08:00
631d4bb0bc 移植retroarch中的crt-easymode shader效果 2025-06-16 10:52:33 +08:00
6193e57a99 Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-06-09 13:48:55 +08:00
3cc79ca730 将EssgeeKeyCore中所有按键绑定类的控制器槽位固定为4个 2025-06-06 13:47:21 +08:00
bd45706c00 Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-06-05 15:28:30 +08:00
7d2b80ab55 Merge branch 'master' of http://git.axibug.com/alienjack/AxibugEmuOnline 2025-06-05 15:28:16 +08:00
eaeb789513 什么鬼namespace 2025-06-05 15:28:09 +08:00
0e5421d6bd todo 2025-06-05 13:29:21 +08:00
7eed897474 todo 2025-06-05 13:25:51 +08:00
3783868bee Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-06-05 13:21:22 +08:00
7c3c025b7c todo 2025-06-05 13:20:42 +08:00
f192e748b4 Merge pull request '修复optionUI一系列表现bug' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-05-21 14:54:45 +08:00
235dc0ed39 修复optionUI一系列表现bug 2025-05-20 00:02:19 +08:00
c8f0d77b34 Client 存档功能 迭代 2025-05-14 22:54:35 +08:00
52e9f42184 Server 网络存档功能 修正保存逻辑 2025-05-14 22:53:33 +08:00
2bc6972ebe Server Savée 2025-05-14 19:32:46 +08:00
e6ef077f16 Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-05-14 14:42:39 +08:00
f3d246ab43 Switch 定时Commit到存储池 2025-05-14 14:08:19 +08:00
0d3024521c 网络建立连接断开连接,回到主线程处理 2025-05-14 14:06:45 +08:00
ef419928ed 调整EmuCore基类中获取输入数据和推送的逻辑 2025-05-06 12:00:47 +08:00
6c23bdc1c2 Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-05-06 10:43:33 +08:00
eb6b25256b 重构:AfterPushFrame修改为protected方法,原先跳帧逻辑从InGameUI移入EmuCore基类中实现,原先InGameUI.IsNetPlay移入EmuCore.IsNetPlay 2025-05-06 10:43:20 +08:00
6be11bdf42 IEmuCore rename to EmuCore 2025-05-06 10:41:25 +08:00
306199dba8 Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-04-30 16:54:05 +08:00
97c278b4ec Merge branch 'master' into master 2025-04-30 16:53:13 +08:00
cc57a1e0df emucore基类替换 2025-04-30 15:41:20 +08:00
ace77987e4 fix NesEmulator重构后处理输入数据bug 2025-04-29 19:43:02 +08:00
1ffa708e71 NesEmulator重构 2025-04-29 19:18:02 +08:00
c7a4f1467a RLPRO_CRT滤镜完善 2025-04-29 19:17:40 +08:00
992adc115b NS fixed 2025-04-29 16:42:39 +08:00
902dc95d19 IEmuCore从接口修改为抽象类 2025-04-29 16:19:09 +08:00
e5f497a163 存档云同步状态UI提交 2025-04-28 11:58:22 +08:00
8fc65c2f70 调整几个工具脚本的目录 2025-04-28 11:20:45 +08:00
f0b139e692 Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-04-27 15:09:27 +08:00
fcd64a54f6 完善RLPRO_CRT滤镜 2025-04-27 15:06:42 +08:00
e8fc45b006 RLPro_Bleed rename to RLPRO_CRT 2025-04-27 10:20:18 +08:00
9cfee768c3 ignore 2025-04-25 17:39:46 +08:00
e8bac390af Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-04-25 16:28:13 +08:00
412c9759df 滤镜添加 2025-04-25 16:27:57 +08:00
74bde1d94a 存档序号 追加到服务端 2025-04-25 14:42:12 +08:00
213ed7163c 补全Switch的Directory逻辑 2025-04-25 14:39:11 +08:00
24036739a7 整个项目和核心都不再强依赖System.IO进行文件读写,为Switch坐准备 2025-04-25 10:30:52 +08:00
1f33d35394 essgee fixed 2025-04-24 19:53:55 +08:00
e66740b8a9 mame essgee 不强依赖System的文件读写 2025-04-24 19:50:55 +08:00
36b614c4a7 主项目和VirtualNES.Core核心 NS下不再依赖system.io进行文件读写 2025-04-24 16:34:09 +08:00
d7fe849bb1 除了核心之外的,已经包装了区分Switch和普通的IO 2025-04-24 11:27:35 +08:00
ee9d54386b Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-04-22 19:02:09 +08:00
eb2f5d268c 存档支持云同步 2025-04-18 13:38:45 +08:00
13cddd8a3b 添加几个存档状态的图标资源 2025-04-18 13:38:21 +08:00
16a7acee61 存档文件改为每个独立用户各自拥有 2025-04-16 13:09:25 +08:00
959956706f 获取EmuCore的OutputTex时,还会根据DrawCanvas的localscale,来纠正存档图片Y轴翻转的处理 2025-04-15 15:41:50 +08:00
8e24f556af 存档图片UI元素的localscale改回1,1,1 不再在UI上去适应存档图片的Y轴翻转问题 2025-04-15 15:40:27 +08:00
424e139692 Nes DrawCanvas修改缩放系数 y=-1 而不是x轴翻转180,以实现Y轴偏转 2025-04-15 15:36:21 +08:00
49d0a9aa86 Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-04-14 09:47:20 +08:00
a079947bc4 gitignore添加对于AxibugEmuOnline.Client/ProjectSettings/PackageManagerSettings.asset的忽略 2025-04-13 19:06:29 +08:00
a21be185ce 移除PackageManagerSettings.asset文件的版本管理,因为国际版和国内版共用实在是太恶心了 2025-04-13 19:05:36 +08:00
3dc7e9acd1 实现存档以及读取存档功能 2025-04-13 19:03:43 +08:00
f325e63e26 存档界面的存档日期,日月数字使用0补全到2位 2025-04-13 18:16:36 +08:00
017026f706 Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline 2025-04-10 17:00:47 +08:00
4116a05b6c 存档UI功能完成 2025-04-10 17:00:36 +08:00
209c82da80 侧边栏存档菜单UI实现中 2025-04-10 15:39:44 +08:00
8bd3b2247b 输出模块增加独占机制,避免一个设备绑定到多个手柄映射配置 2025-04-09 14:54:21 +08:00
5aabda7c37 Switch文件操作异步实现 2025-04-09 00:48:15 +08:00
6111319ad4 引入 AxiNSApi 以及 补完Switch自动打包 2025-04-08 10:36:07 +08:00
279483c14f Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-04-08 01:36:02 +08:00
c89b6b1ab5 存档模块提交(未完成) 2025-04-07 14:47:02 +08:00
8b2b66a940 存档相关proto,新增Sequence字段 2025-04-07 14:46:41 +08:00
902a8f1360 修复程序集定义错误语法 2025-04-07 14:45:24 +08:00
569e4e44fe cn 2025-04-07 11:07:07 +08:00
c77cac4812 fixed 2025-04-07 11:01:07 +08:00
5f9d67b36f AxiNSApi 2025-04-07 10:59:03 +08:00
5014c81539 引入 AxiNSApi 方便操作Switch的API 做一些安全封装 2025-04-07 10:56:09 +08:00
4b0a781957 更新忽略规则 2025-04-04 21:34:31 +08:00
10ac17c18b Merge pull request 'master' () from Alienjack/AxibugEmuOnline:master into master
Reviewed-on: 
2025-04-01 15:50:13 +08:00
591204e044 Merge branch 'master' of http://git.axibug.com/sin365/AxibugEmuOnline
# Conflicts:
#	AxibugEmuOnline.Client/Assets/Script/AppMain/AxiInputSP.Settings/NESMultiKeysSetting.cs
#	AxibugEmuOnline.Client/Assets/Script/AppMain/AxibugEmuOnline.Client.asmdef
2025-04-01 15:37:43 +08:00
cfb3bc8b6a Input机制修改已完成 2025-04-01 15:35:17 +08:00
34cf5487e8 fix bug 2025-03-31 18:35:38 +08:00
6cc7b2fb4b 引入IDeviceBinder接口,以利用编译报错补充应该编写的代码,避免漏掉 2025-03-31 18:25:07 +08:00
fbe47bce1b 添加DS和Xbox手柄支持 2025-03-31 16:20:39 +08:00
3797bba442 完善GamePad支持 2025-03-27 20:13:00 +08:00
4e87cfe9ee Input代码迭代 2025-03-27 20:08:31 +08:00
e463c23c4c Input逻辑结构调整 2025-03-27 19:19:05 +08:00
50e3a30f31 将所有抽象层Input的类加上后缀,D代表设备,C代表控件,避免和InputSystem中的设备重名 2025-03-27 15:02:41 +08:00
8007af0bc5 InputControl的实现类挪入基本类型,不再每个设备单独实现 2025-03-27 11:54:10 +08:00
924727eb7c Input系统代码迭代 2025-03-26 19:07:46 +08:00
cf8127c553 修复GetBinding返回的InputControl对象并不完整的问题 2025-03-18 11:23:08 +08:00
49c893fdab GetBinding方法修改,在没有绑定设备时返回空迭代器而不是null 2025-03-18 10:56:24 +08:00
667089891a KeyMapper根据每种设备单独配置 2025-03-17 20:32:34 +08:00
811b0b2a48 InputManager迭代 2025-03-17 19:50:41 +08:00
a93f6449c8 键盘设备键值字段接口调整 2025-03-17 10:50:17 +08:00
3257e15dda 键盘映射配置加载机制修改,现在在初始化以及任何键盘设备断开和连接时,会检查可用键盘设备并且调用配置加载方法 2025-03-17 10:33:23 +08:00
1c20707751 输入系统迭代 2025-03-14 15:27:26 +08:00
4e1f8fd045 InputDevice中的keybase基类改为inputcontrol,突出基础类型作为一个输入控件的概念,以免和按钮类控件混淆 2025-03-14 11:02:38 +08:00
955a35659a 引入InputSystem,输入设备管理系统迭代中 2025-03-13 17:38:12 +08:00
3c10873180 输入设备管理器,以及按键映射设置基本框架搭建 2025-03-12 20:09:11 +08:00
3039 changed files with 429904 additions and 11153 deletions
.gitignore
AxibugEmuOnline.Client.Switch/Assets
AxiProjectTools.meta
AxiProjectTools
Editors.meta
Editors
New Controls.inputactionsNew Controls.inputactions.metaPlugins.meta
Plugins

20
.gitignore vendored
View File

@ -9,13 +9,28 @@
/AxibugEmuOnline.Client/obj/
/AxibugEmuOnline.Client/log/
/AxibugEmuOnline.Client/ProjectSettings/Packages/
/AxibugEmuOnline.Client.Switch/*.vsconfig
/AxibugEmuOnline.Client.Switch/Library/
/AxibugEmuOnline.Client.Switch/Temp/
/AxibugEmuOnline.Client.Switch/UserSettings/
/AxibugEmuOnline.Client.Switch/.vs/
/AxibugEmuOnline.Client.Switch/*.csproj
/AxibugEmuOnline.Client.Switch/*.sln
/AxibugEmuOnline.Client.Switch/*.sln
/AxibugEmuOnline.Client.Switch/obj/
/AxibugEmuOnline.Client.Switch/Logs/
/AxibugEmuOnline.Client.Switch/ProjectSettings/Packages/
/AxibugEmuOnline.Web/config.cfg
/AxibugEmuOnline.Client/ProjectSettings/ProjectVersion.txt
/AxibugEmuOnline.Client/ProjectSettings/AutoStreamingSettings.asset
/AxibugEmuOnline.Client/Logs
/AxibugEmuOnline.Client/Assets/AxiProjectTools/AxiNSPack/switch_keys/*.keys
/AxibugEmuOnline.Client/Assets/AxiProjectTools/AxiNSPack/switch_keys/*.keys.meta
**/switch_keys/*.keys
**/switch_keys/*.keys.meta
**/NintendoSDKPlugin/
**/NintendoSDKPlugin.meta
/virtuanessrc097-master
/AxibugEmuOnline.Server/config.cfg
@ -49,3 +64,4 @@ AxibugEmuOnline.GameScreenConvert/bin/
AxibugEmuOnline.GameScreenConvert/obj/
/.vs/
AxibugEmuOnline.Client/ProjectSettings/PackageManagerSettings.asset

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 91746af636f351140a4796dc4e98be6d
guid: e745b95dbce117b48b4bd17df06ed302
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 04e926e140ae5bc4fa46bd64067261cf
guid: 164952f99969ca942b4761b200d7e381
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5fe26f58ab822c44888b86305c5326e0
guid: cbe37300d75dbd641be2e6dca83a913c
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -0,0 +1,369 @@
#if UNITY_EDITOR_WIN
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;
namespace AxibugEmuOnline.Editors
{
public class AxibugNSPTools : Editor
{
static string WorkRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "AxiProjectTools/AxiNSPack"));
static string switch_keys = Path.GetFullPath(Path.Combine(Application.dataPath, "AxiProjectTools/AxiNSPack/switch_keys"));
static string hacpack_root = Path.GetFullPath(Path.Combine(Application.dataPath, "AxiProjectTools/AxiNSPack/hacpack"));
static Dictionary<string, string> tools = new Dictionary<string, string>();
static string prodKeysPath;
static void InitToolPath()
{
#region
// 获取环境变量(需要添加环境变量检查)
string sdkRoot = Environment.GetEnvironmentVariable("NINTENDO_SDK_ROOT");
tools["authoringTool"] = Path.Combine(sdkRoot, "Tools/CommandLineTools/AuthoringTool/AuthoringTool.exe");
tools["hacPack"] = Path.Combine(hacpack_root, "hacpack");
#endregion
}
[MenuItem("Axibug移植工具/Switch/AxibugNSPTools/RepackNSP(仅重新构建NSP")]
static void RepackNSP()
{
if (!CheckEnvironmentVariable())
return;
string path = EditorUtility.OpenFilePanel(
title: "选择 .nsp 文件",
directory: Path.Combine(Application.dataPath, ".."), // 默认路径为项目 Assets 目录
extension: "nsp" // 限制文件类型为 .nsp
);
if (string.IsNullOrEmpty(path))
return;
RepackNSP(path);
}
//[MenuItem("Axibug移植工具/Switch/AxibugNSPTools/UnpackNSP(解包工具)")]
//static void UnpackNSP()
//{
// InitToolPath();
// if (!CheckEnvironmentVariable())
// return;
// string nspFilePath = EditorUtility.OpenFilePanel(
// title: "选择 .nsp 文件",
// directory: Path.Combine(Application.dataPath, ".."), // 默认路径为项目 Assets 目录
// extension: "nsp" // 限制文件类型为 .nsp
// );
// if (string.IsNullOrEmpty(nspFilePath))
// return;
// string nspParentDir = Path.GetDirectoryName(nspFilePath);
// string extractPath = Path.Combine(nspParentDir, "repacker_extract");
// ExecuteCommand($"{tools["authoringTool"]} extract -o \"{extractPath}\" \"{nspFilePath}\"", nspParentDir);
//}
[MenuItem("Axibug移植工具/Switch/AxibugNSPTools/Build With RepackNSP(打包NSP并重新构建NSP")]
public static void BuildWithRepackNSP()
{
if (!CheckEnvironmentVariable())
return;
if (!EditorUtility.DisplayDialog("打包", $"确认打包NSP?", "继续", "取消"))
return;
var levels = new List<string>();
foreach (var scene in EditorBuildSettings.scenes)
{
if (scene.enabled)
levels.Add(scene.path);
}
var buildOpt = EditorUserBuildSettings.development ? BuildOptions.Development : BuildOptions.None;
if (EditorUserBuildSettings.buildWithDeepProfilingSupport)
buildOpt |= BuildOptions.EnableDeepProfilingSupport;
if (EditorUserBuildSettings.allowDebugging)
buildOpt |= BuildOptions.AllowDebugging;
//勾选构建NSP包
EditorUserBuildSettings.switchCreateRomFile = true;
#if UNITY_2018_1_OR_NEWER && !UNITY_6000_0_OR_NEWER
string titleid = PlayerSettings.Switch.applicationID;
#else
string titleid = "null";
#endif
string targetName = $"{Application.productName}_{titleid}.nsp";
string _locationPathName = $"Output/NSPBuild/{DateTime.Now.ToString("yyyyMMddHHmmss")}/{targetName}";
var options = new BuildPlayerOptions
{
scenes = levels.ToArray(),
locationPathName = _locationPathName,
target = BuildTarget.Switch,
options = buildOpt
};
try
{
BuildReport report = BuildPipeline.BuildPlayer(options);
}
catch (Exception ex)
{
Debug.LogError($"[AxibugNSPTools] Unity Build NSP 错误:{ex.ToString()}");
return;
}
string NSPFullPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", _locationPathName));
RepackNSP(NSPFullPath);
}
static bool CheckEnvironmentVariable()
{
// 获取环境变量(需要添加环境变量检查)
string sdkRoot = Environment.GetEnvironmentVariable("NINTENDO_SDK_ROOT");
if (string.IsNullOrEmpty(sdkRoot))
{
Debug.LogError($"[AxibugNSPTools]请先正确配置环境变量:NINTENDO_SDK_ROOT,(若已配置则保证配置后彻底重启Unity Hub和Unity)");
return false;
}
#region prod.keys文件路径
prodKeysPath = Path.Combine(
switch_keys,
"prod.keys"
);
if (!File.Exists(prodKeysPath))
{
Debug.LogError($"[AxibugNSPTools]未找到 prod.keys! 请先准备文件path:{prodKeysPath}");
return false;
}
#endregion
return true;
}
static void RepackNSP(string nspFile)
{
InitToolPath();
#region NSP文件路径
string nspFilePath = nspFile;
string nspFileName = Path.GetFileName(nspFilePath);
string nspParentDir = Path.GetDirectoryName(nspFilePath);
#endregion
#region Title ID
string titleID = ExtractTitleID(nspFilePath);
if (string.IsNullOrEmpty(titleID))
{
Debug.LogError($"[AxibugNSPTools]NSP文件名一部分必须包含TitleID");
return;
}
Debug.Log($"[AxibugNSPTools]Using Title ID: {titleID}");
#endregion
EditorUtility.DisplayProgressBar("AxibugNSPTools", $"清理临时目录", 0);
#region
CleanDirectory(Path.Combine(nspParentDir, "repacker_extract"));
CleanDirectory(Path.Combine(Path.GetTempPath(), "NCA"));
CleanDirectory(Path.Combine(nspParentDir, "hacpack_backup"));
#endregion
EditorUtility.DisplayProgressBar("AxibugNSPTools", $"解包NSP文件", 0.2f);
#region NSP文件
string extractPath = Path.Combine(nspParentDir, "repacker_extract");
ExecuteCommand($"{tools["authoringTool"]} extract -o \"{extractPath}\" \"{nspFilePath}\"", nspParentDir);
string controlPath = null;
string programPath = null;
FindNACPAndNPDPaths(extractPath, ref controlPath, ref programPath);
if (controlPath == null || programPath == null)
{
Debug.LogError("[AxibugNSPTools] Critical directory structure not found!");
return;
}
#endregion
#region NCA/NSP
string tmpPath = Path.Combine(Path.GetTempPath(), "NCA");
EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建 Program NCA", 0.3f);
string programNCA = BuildProgramNCA(tmpPath, titleID, programPath, nspParentDir);
EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建 Control NCA", 0.4f);
string controlNCA = BuildControlNCA(tmpPath, titleID, controlPath, nspParentDir);
EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建 Meta NCA", 0.5f);
BuildMetaNCA(tmpPath, titleID, programNCA, controlNCA, nspParentDir);
EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建NSP", 0.6f);
string outputNSP = BuildFinalNSP(nspFilePath, nspParentDir, tmpPath, titleID, nspParentDir);
EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建NSP", 0.9f);
Debug.Log($"[AxibugNSPTools]Repacking completed: {outputNSP}");
#endregion
EditorUtility.DisplayProgressBar("AxibugNSPTools", $"清理临时目录", 1);
#region
CleanDirectory(Path.Combine(nspParentDir, "repacker_extract"));
CleanDirectory(Path.Combine(Path.GetTempPath(), "NCA"));
CleanDirectory(Path.Combine(nspParentDir, "hacpack_backup"));
#endregion
System.Diagnostics.Process.Start("explorer", "/select,\"" + outputNSP.Trim() + "\"");
EditorUtility.ClearProgressBar();
}
#region
static string GetUserInput()
{
Console.Write("Enter the NSP filepath: ");
return Console.ReadLine();
}
static string ExtractTitleID(string path)
{
var match = Regex.Match(path, @"0100[\dA-Fa-f]{12}");
return match.Success ? match.Value : null;
}
static void CleanDirectory(string path)
{
if (Directory.Exists(path))
{
Directory.Delete(path, true);
while (Directory.Exists(path)) ; // 等待删除完成
}
}
static void FindNACPAndNPDPaths(string basePath, ref string controlPath, ref string programPath)
{
foreach (var dir in Directory.GetDirectories(basePath))
{
if (File.Exists(Path.Combine(dir, "fs0/control.nacp")))
controlPath = dir;
if (File.Exists(Path.Combine(dir, "fs0/main.npdm")))
programPath = dir;
}
}
static string ExecuteCommand(string command, string workdir)
{
Debug.Log($"调用cmd=>{command}");
var process = new System.Diagnostics.Process()
{
StartInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/C {command}",
RedirectStandardOutput = true,
RedirectStandardError = true, // 增加错误流重定向
UseShellExecute = false,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8, // 明确指定编码
StandardErrorEncoding = Encoding.UTF8,
WorkingDirectory = workdir
}
};
var outputBuilder = new StringBuilder();
var errorBuilder = new StringBuilder();
// 使用事件处理程序捕获实时输出
process.OutputDataReceived += (sender, args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
outputBuilder.AppendLine(args.Data);
Debug.Log($"[AxibugNSPTools]{args.Data}");
}
};
process.ErrorDataReceived += (sender, args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
errorBuilder.AppendLine(args.Data);
Debug.LogError($"[AxibugNSPTools]{args.Data}");
}
};
process.Start();
// 开始异步读取输出
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// 等待进程退出(此时流已关闭)
process.WaitForExit();
// 将错误信息附加到主输出
if (errorBuilder.Length > 0)
{
outputBuilder.AppendLine("\nError Output:");
outputBuilder.Append(errorBuilder);
}
return outputBuilder.ToString();
}
#endregion
#region NCA构建逻辑
static string BuildProgramNCA(string tmpPath, string titleID, string programDir, string workdir)
{
string args = $"-k \"{prodKeysPath}\" -o \"{tmpPath}\" --titleid {titleID} " +
$"--type nca --ncatype program --exefsdir \"{programDir}/fs0\" " +
$"--romfsdir \"{programDir}/fs1\" --logodir \"{programDir}/fs2\"";
string output = ExecuteCommand($"{tools["hacPack"]} {args}", workdir);
return ParseNCAOutput(output, "Program");
}
static string BuildControlNCA(string tmpPath, string titleID, string controlDir, string workdir)
{
string args = $"-k \"{prodKeysPath}\" -o \"{tmpPath}\" --titleid {titleID} " +
$"--type nca --ncatype control --romfsdir \"{controlDir}/fs0\"";
string output = ExecuteCommand($"{tools["hacPack"]} {args}", workdir);
return ParseNCAOutput(output, "Control");
}
static void BuildMetaNCA(string tmpPath, string titleID, string programNCA, string controlNCA, string workdir)
{
string args = $"-k \"{prodKeysPath}\" -o \"{tmpPath}\" --titleid {titleID} " +
$"--type nca --ncatype meta --titletype application " +
$"--programnca \"{programNCA}\" --controlnca \"{controlNCA}\"";
ExecuteCommand($"{tools["hacPack"]} {args}", workdir);
}
static string BuildFinalNSP(string origPath, string parentDir, string tmpPath, string titleID, string workdir)
{
string outputPath = origPath.Replace(".nsp", "_repacked.nsp");
if (File.Exists(outputPath)) File.Delete(outputPath);
string args = $"-k \"{prodKeysPath}\" -o \"{parentDir}\" --titleid {titleID} " +
$"--type nsp --ncadir \"{tmpPath}\"";
ExecuteCommand($"{tools["hacPack"]} {args}", workdir);
File.Move(Path.Combine(parentDir, $"{titleID}.nsp"), outputPath);
return outputPath;
}
static string ParseNCAOutput(string output, string type)
{
var line = output.Split('\n')
.FirstOrDefault(l => l.Contains($"Created {type} NCA:"));
//return line?.Split(':').Last().Trim();
return line?.Substring(line.IndexOf("NCA:") + "NCA:".Length).Trim();
}
#endregion
}
}
#endif

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 55aa3f0466c30bc4683cdbdc4dd75940

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d90c85ddb14ad7e4e9a6242ba135da0b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b31e2ae7250c09548a777d4dcdfe2d1f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7be57cd4293e9dc4297ea9b83fe08b18
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
<Application>
<Title>
<Language>AmericanEnglish</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Title>
<Language>BritishEnglish</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Title>
<Language>Japanese</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Title>
<Language>French</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Title>
<Language>German</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Title>
<Language>LatinAmericanSpanish</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Title>
<Language>Spanish</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Title>
<Language>Italian</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Title>
<Language>Dutch</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Title>
<Language>CanadianFrench</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Title>
<Language>Portuguese</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Title>
<Language>Russian</Language>
<Name>Homebrew Menu</Name>
<Publisher>Yellows8</Publisher>
</Title>
<Isbn/>
<StartupUserAccount>Required</StartupUserAccount>
<UserAccountSwitchLock>Disable</UserAccountSwitchLock>
<ParentalControl>None</ParentalControl>
<SupportedLanguage>AmericanEnglish</SupportedLanguage>
<SupportedLanguage>BritishEnglish</SupportedLanguage>
<SupportedLanguage>Japanese</SupportedLanguage>
<SupportedLanguage>French</SupportedLanguage>
<SupportedLanguage>German</SupportedLanguage>
<SupportedLanguage>LatinAmericanSpanish</SupportedLanguage>
<SupportedLanguage>Spanish</SupportedLanguage>
<SupportedLanguage>Italian</SupportedLanguage>
<SupportedLanguage>Dutch</SupportedLanguage>
<SupportedLanguage>CanadianFrench</SupportedLanguage>
<SupportedLanguage>Russian</SupportedLanguage>
<Screenshot>Allow</Screenshot>
<VideoCapture>Disable</VideoCapture>
<PresenceGroupId>0x0104444444441001</PresenceGroupId>
<DisplayVersion>2.0</DisplayVersion>
<Rating>
<Organization>CERO</Organization>
<Age>12</Age>
</Rating>
<Rating>
<Organization>ESRB</Organization>
<Age>10</Age>
</Rating>
<Rating>
<Organization>USK</Organization>
<Age>12</Age>
</Rating>
<Rating>
<Organization>PEGI</Organization>
<Age>12</Age>
</Rating>
<Rating>
<Organization>PEGIPortugal</Organization>
<Age>12</Age>
</Rating>
<Rating>
<Organization>PEGIBBFC</Organization>
<Age>12</Age>
</Rating>
<Rating>
<Organization>Russian</Organization>
<Age>12</Age>
</Rating>
<Rating>
<Organization>ACB</Organization>
<Age>13</Age>
</Rating>
<Rating>
<Organization>OFLC</Organization>
<Age>13</Age>
</Rating>
<DataLossConfirmation>Required</DataLossConfirmation>
<PlayLogPolicy>All</PlayLogPolicy>
<SaveDataOwnerId>0x0104444444441001</SaveDataOwnerId>
<UserAccountSaveDataSize>0x0000000003e00000</UserAccountSaveDataSize>
<UserAccountSaveDataJournalSize>0x0000000000180000</UserAccountSaveDataJournalSize>
<DeviceSaveDataSize>0x0000000000000000</DeviceSaveDataSize>
<DeviceSaveDataJournalSize>0x0000000000000000</DeviceSaveDataJournalSize>
<BcatDeliveryCacheStorageSize>0x0000000000000000</BcatDeliveryCacheStorageSize>
<ApplicationErrorCodeCategory/>
<AddOnContentBaseId>0x0104444444442001</AddOnContentBaseId>
<LogoType>Nintendo</LogoType>
<LocalCommunicationId>0x0104444444441001</LocalCommunicationId>
<LogoHandling>Auto</LogoHandling>
<SeedForPseudoDeviceId>0x0000000000000000</SeedForPseudoDeviceId>
<BcatPassphrase/>
<AddOnContentRegistrationType>AllOnLaunch</AddOnContentRegistrationType>
<UserAccountSaveDataSizeMax>0x0000000000000000</UserAccountSaveDataSizeMax>
<UserAccountSaveDataJournalSizeMax>0x0000000000000000</UserAccountSaveDataJournalSizeMax>
<DeviceSaveDataSizeMax>0x0000000000000000</DeviceSaveDataSizeMax>
<DeviceSaveDataJournalSizeMax>0x0000000000000000</DeviceSaveDataJournalSizeMax>
<TemporaryStorageSize>0x0000000000000000</TemporaryStorageSize>
<CacheStorageSize>0x0000000000000000</CacheStorageSize>
<CacheStorageJournalSize>0x0000000000000000</CacheStorageJournalSize>
<CacheStorageDataAndJournalSizeMax>0x0000000000000000</CacheStorageDataAndJournalSizeMax>
<CacheStorageIndexMax>0x0000000000000000</CacheStorageIndexMax>
<Hdcp>None</Hdcp>
<CrashReport>Deny</CrashReport>
<RuntimeAddOnContentInstall>Deny</RuntimeAddOnContentInstall>
<PlayLogQueryableApplicationId>0x0000000000000000</PlayLogQueryableApplicationId>
<PlayLogQueryCapability>None</PlayLogQueryCapability>
<Repair>None</Repair>
<Attribute>None</Attribute>
<ProgramIndex>0</ProgramIndex>
<RequiredNetworkServiceLicenseOnLaunch>None</RequiredNetworkServiceLicenseOnLaunch>
</Application>

View File

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

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 409c6e8e5ead0ac4991ea6c243e407dd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 292b282919a768c4fa6b8adb858daa95
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,118 @@
#if UNITY_EDITOR
using AxibugEmuOnline.Editors;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;
public static class AxiAutoBuild
{
[MenuItem("Axibug移植工具/AutoBuild/Build ALL")]
public static void Build_Build_ALL()
{
Build_Global(BuildTarget.Android);
Build_Global(BuildTarget.iOS);
Build_Global(BuildTarget.StandaloneWindows);
Build_Global(BuildTarget.StandaloneLinux64);
Build_Global(BuildTarget.WSAPlayer);
AxibugNSPTools.BuildWithRepackNSP();
}
[MenuItem("Axibug移植工具/AutoBuild/Android")]
public static void Build_Android()
{
Build_Global(BuildTarget.Android);
}
[MenuItem("Axibug移植工具/AutoBuild/IOS")]
public static void Build_IOS()
{
Build_Global(BuildTarget.iOS);
}
[MenuItem("Axibug移植工具/AutoBuild/PC")]
public static void Build_PC()
{
Build_Global(BuildTarget.StandaloneWindows);
}
[MenuItem("Axibug移植工具/AutoBuild/Linux")]
public static void Build_Linux64()
{
Build_Global(BuildTarget.StandaloneLinux64);
}
[MenuItem("Axibug移植工具/AutoBuild/UWP")]
public static void Build_UWP()
{
Build_Global(BuildTarget.WSAPlayer);
}
[MenuItem("Axibug移植工具/AutoBuild/EmbeddedLinux")]
public static void Build_EmbeddedLinux()
{
Build_Global(BuildTarget.EmbeddedLinux);
}
[MenuItem("Axibug移植工具/AutoBuild/Switch")]
public static void Build_Switch()
{
AxibugNSPTools.BuildWithRepackNSP();
}
public static void Build_Global(BuildTarget target)
{
if (!EditorUtility.DisplayDialog("打包", $"确认打包{target}?", "继续", "取消"))
return;
var levels = new List<string>();
foreach (var scene in EditorBuildSettings.scenes)
{
if (scene.enabled)
levels.Add(scene.path);
}
var buildOpt = EditorUserBuildSettings.development ? BuildOptions.Development : BuildOptions.None;
if (EditorUserBuildSettings.buildWithDeepProfilingSupport)
buildOpt |= BuildOptions.EnableDeepProfilingSupport;
if (EditorUserBuildSettings.allowDebugging)
buildOpt |= BuildOptions.AllowDebugging;
string targetName = $"{Application.productName}_{DateTime.Now.ToString("yyyyMMddHHmmss")}";
targetName += target switch
{
BuildTarget.Android => ".apk",
BuildTarget.iOS => ".ipa",
BuildTarget.StandaloneWindows => ".exe",
_ => "",
};
string _locationPathName = $"Output/{target}/{targetName}";
string FullPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", _locationPathName));
string dirPath = Path.GetDirectoryName(FullPath);
if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
var options = new BuildPlayerOptions
{
scenes = levels.ToArray(),
locationPathName = _locationPathName,
target = target,
options = buildOpt
};
try
{
BuildReport report = BuildPipeline.BuildPlayer(options);
}
catch (Exception ex)
{
Debug.LogError($"[AutoBuild] Unity Build {target} 错误:{ex.ToString()}");
return;
}
}
}
#endif

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e3b8550d3919d1044b50eb20f0c50fb8

View File

@ -0,0 +1,24 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class AxiPrefabCache : ScriptableObject
{
public List<AxiPrefabCache_Com2GUID> caches = new List<AxiPrefabCache_Com2GUID>();
}
[Serializable]
public class AxiPrefabCache_Com2GUID
{
public string SrcFullName;
public string SrcName;
public string GUID;
public string ToName;
public string ToPATH;
public string ToGUID;
public MonoScript monoScript;
}
#endif

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 47ae96bfd5393694da28c22ab2ccfb7c

View File

@ -0,0 +1,42 @@
#if UNITY_EDITOR
using System.IO;
using UnityEditor;
using UnityEngine;
public class AxiProjectTools_AssetsAutoSetting : EditorWindow
{
[MenuItem("Axibug移植工具/AssetsAutoSetting/自动设置TextureMaxSize为图片大小1倍")]
public static void AutoSettTextureSize_1x() { SetTextureSite(1f); }
[MenuItem("Axibug移植工具/AssetsAutoSetting/自动设置TextureMaxSize为图片大小2分之1倍")]
public static void AutoSettTextureSize_1_2x() { SetTextureSite(1f / 2f); }
[MenuItem("Axibug移植工具/AssetsAutoSetting/自动设置TextureMaxSize为图片大小4分之1倍")]
public static void AutoSettTextureSize_1_4x() { SetTextureSite(1f / 4f); }
public static void SetTextureSite(float Scale)
{
Texture2D[] textures = Selection.GetFiltered<Texture2D>(SelectionMode.DeepAssets);
if (textures.Length == 0)
{
Debug.LogWarning("请先选择目录或者Texture资源");
return;
}
AssetDatabase.StartAssetEditing(); // 开启批量编辑模式
foreach (var texture in textures)
{
string path = AssetDatabase.GetAssetPath(texture);
TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer != null)
{
int size = Mathf.Max(texture.width, texture.height);
int maxsize = Mathf.ClosestPowerOfTwo((int)(size * Scale)); // Unity内置方法适配2的幂次方
importer.maxTextureSize = maxsize;
importer.SaveAndReimport();
}
}
AssetDatabase.StopAssetEditing(); // 结束批量编辑
Debug.Log($"Updated {textures.Length} textures.");
}
}
#endif

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: dc0741a79e74f96449a260406f239066

View File

@ -0,0 +1,840 @@
#if UNITY_EDITOR && UNITY_2020_1_OR_NEWER
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
public class AxiProjectToolsStatistics : EditorWindow
{
static string cachecfgPath = "Assets/AxiStatisticsDatas.asset";
static Dictionary<string, AxiStatisticsDatas> dictTempData = new Dictionary<string, AxiStatisticsDatas>();
static void ClearTempData()
{
dictTempData.Clear();
}
static string GetRootTempKey(int type, string rootName)
{
return type + "_" + rootName;
}
// 添加Hierarchy右键菜单项
[MenuItem("GameObject/AxiStatistics/GetAxiNodeHash", false, 10)]
public static void GetAxiNodeHash()
{
// 获取当前右键选中的Transform
Transform selectedTransform = Selection.activeTransform;
if (selectedTransform != null)
{
Debug.Log("选中的对象"+selectedTransform.name+",Hash=>"+ GetNodeDataHash(selectedTransform,true));
}
}
static int GetNodeDataHash(Transform trans,bool bLog = false)
{
long hashplus = 0;
hashplus += trans.position.GetHashCode();
hashplus += trans.localPosition.GetHashCode();
#if UNITY_2017_1_OR_NEWER
int count = trans.childCount;
#else
int count = trans.GetChildCount();
#endif
hashplus += count;
for (int i = 0; i < count; i++)
{
hashplus += trans.GetChild(i).name.GetHashCode();
}
if (bLog)
{
//Debug.Log("trans.position.GetHashCode()=>" + trans.position.GetHashCode());
//Debug.Log("trans.localPosition.GetHashCode()=>" + trans.localPosition.GetHashCode());
//Debug.Log("childCount =>" + count);
//Debug.Log("hashplus =>" + hashplus);
//Debug.Log("hashplus.GetHashCode() =>" + hashplus.GetHashCode());
}
return hashplus.GetHashCode();
}
static int GetNodeLinkListHash(List<AxiStatistics_Node_Link> nodes)
{
string hashplus = string.Empty;
foreach (var node in nodes)
{
hashplus += node.Name;
hashplus += node.Idx.ToString();
}
return hashplus.GetHashCode();
}
static string GetNodeLinkListStr(List<AxiStatistics_Node_Link> nodes)
{
string linkstr = string.Empty;
foreach (var node in nodes)
{
linkstr += "/";
linkstr += node.Name;
linkstr += "[" + node.Idx + "]";
}
return linkstr;
}
static void AddComponentData(int _type, string _rootPath, AxiStatistics_Node_Component _comdata, string _nodepath, Component lastcom)
{
string rootKey = GetRootTempKey(_type, _rootPath);
if (!dictTempData.ContainsKey(rootKey))
{
dictTempData[rootKey] = new AxiStatisticsDatas() { type = _type, FullPath = _rootPath, nodes = new List<AxiStatistics_Node>() };
}
AxiStatisticsDatas rootData = dictTempData[rootKey];
List<AxiStatistics_Node_Link> link = new List<AxiStatistics_Node_Link>();
if (lastcom.transform.parent != null)
{
Transform currNode = lastcom.transform;
while (currNode != null)
{
//最顶层了
if (currNode.parent == null)
{
link.Insert(0, new AxiStatistics_Node_Link()
{
NodeHash = GetNodeDataHash(currNode),
Idx = 0,
OnlyOne = true,
Name = currNode.gameObject.name
});
break;
}
int thisNameAllCount = 0;
int thisNodeIdx = -1;
#if UNITY_2017_1_OR_NEWER
int count = currNode.parent.childCount;
#else
int count = currNode.parent.GetChildCount();
#endif
bool bFind = false;
for (int i = 0; i < count; i++)
{
GameObject checkGobj = currNode.parent.GetChild(i).gameObject;
if (checkGobj.name == currNode.name)
{
thisNameAllCount++;
if (checkGobj == currNode.gameObject)
{
thisNodeIdx = thisNameAllCount - 1;
bFind = true;
}
}
}
if (bFind)
{
link.Insert(0, new AxiStatistics_Node_Link()
{
NodeHash = GetNodeDataHash(currNode),
Idx = thisNodeIdx,
OnlyOne = thisNameAllCount == 1,
Name = currNode.gameObject.name
});
currNode = currNode.parent;
}
else
break;
}
}
else
{
link.Insert(0, new AxiStatistics_Node_Link()
{
NodeHash = GetNodeDataHash(lastcom.transform),
Idx = 0,
OnlyOne = true,
Name = lastcom.gameObject.name
});
}
int linkhash = GetNodeLinkListHash(link);
AxiStatistics_Node nodeData = rootData.nodes.Where(w => w.LinkHash == linkhash).FirstOrDefault();
if (nodeData == null)
{
nodeData = new AxiStatistics_Node();
nodeData.Name = Path.GetFileName(_nodepath);
//nodeData.NodeFullPath = _nodepath;
nodeData.components = new List<AxiStatistics_Node_Component>();
//nodeData.NodeIdx = thisNodeIdx;
//nodeData.NodeIdxOnlyOne = bNodeIdxOnlyOne;
nodeData.link = link;
nodeData.LinkHash = linkhash;
nodeData.LinkFullStr = GetNodeLinkListStr(link);
rootData.nodes.Add(nodeData);
}
nodeData.components.Add(_comdata);
}
static bool CheckCom(Component[] allcoms, int comRealIdx, int _type, string _rootPath, Component com, string nodepath)
{
if (com is BoxCollider2D)
{
BoxCollider2D bc = com as BoxCollider2D;
#if UNITY_2017_1_OR_NEWER
Debug.Log(nodepath + "BoxCollider2D->center=>(" + bc.offset.x + "," + bc.offset.y + ") size=>(" + bc.size.x + "," + bc.size.y + "");
#else
Debug.Log(nodepath +"BoxCollider2D->center=>("+ bc.center.x+","+bc.center.y+") size=>("+ bc.size.x+","+bc.size.y+"");
#endif
AxiStatistics_Node_Component _com = new AxiStatistics_Node_Component();
_com.type = typeof(BoxCollider2D).ToString();
#if UNITY_2017_1_OR_NEWER
_com.center = bc.offset;
#else
_com.center = bc.center;
#endif
_com.size = bc.size;
SetCompnentIdxNum<BoxCollider2D>(_com, allcoms, comRealIdx, bc);
AddComponentData(_type, _rootPath, _com, nodepath, com);
}
if (com is Rigidbody2D)
{
Rigidbody2D rig2d = com as Rigidbody2D;
Debug.Log(_rootPath + "Rigidbody2D->simulated=>(" + rig2d.simulated + ")");
Debug.Log(_rootPath + "Rigidbody2D->IsSleeping=>(" + rig2d.isKinematic.ToString() + ")");
AxiStatistics_Node_Component _com = new AxiStatistics_Node_Component();
_com.type = typeof(Rigidbody2D).ToString();
_com.isKinematic = rig2d.isKinematic;
_com.simulated = rig2d.simulated;
_com.gravityScale = rig2d.gravityScale;
SetCompnentIdxNum<Rigidbody2D>(_com, allcoms, comRealIdx, rig2d);
AddComponentData(_type, _rootPath, _com, nodepath, com);
}
return true;
}
/// <summary>
/// 找出同类Idx
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="allcoms"></param>
/// <param name="comRealIdx"></param>
/// <param name="com"></param>
/// <returns></returns>
static void SetCompnentIdxNum<T>(AxiStatistics_Node_Component _comData, Component[] allcoms, int comRealIdx, T com) where T : Component
{
int ComIdxNum;
bool ComTypeIsOnlyOne = false;
int TCount = com.transform.GetComponents<T>().Length;
if (TCount == 1)
{
ComIdxNum = 0;
ComTypeIsOnlyOne = true;
}
else if (TCount < 1)
{
Debug.LogError("找不到,不应该");
ComIdxNum = -1;
}
ComIdxNum = -1;
for (int i = 0; i < allcoms.Length; i++)
{
//他自己自然是了
if (i == comRealIdx)
{
ComIdxNum++;
break;
}
if (allcoms[i] is T)
ComIdxNum++;
}
_comData.ComIdxNum = ComIdxNum;
_comData.ComTypeOnlyOne = ComTypeIsOnlyOne;
}
[MenuItem("Axibug移植工具/Statistics/[1]Collider和RigBody")]
public static void StatisticsCollider()
{
ClearTempData();
StatisticsCollider<BoxCollider2D>();
StatisticsCollider<Rigidbody2D>();
AxiStatisticsCache cache = ScriptableObject.CreateInstance<AxiStatisticsCache>();
foreach (var data in dictTempData)
cache.caches.Add(data.Value);
AssetDatabase.CreateAsset(cache, cachecfgPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
public static void StatisticsCollider<T>() where T : Component
{
AxiProjectTools.GoTAxiProjectToolsSence();
string[] sceneGuids = AssetDatabase.FindAssets("t:scene");
foreach (string guid in sceneGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (path.Contains(AxiProjectTools.toolSenceName))
continue;
#if UNITY_4_6
EditorApplication.OpenScene(path);
#else
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(path);
#endif
// 创建一个列表来存储根节点
List<GameObject> rootNodes = new List<GameObject>();
// 遍历场景中的所有对象
GameObject[] allObjects = FindObjectsOfType<GameObject>();
foreach (GameObject obj in allObjects)
{
// 检查对象是否有父对象
if (obj.transform.parent == null)
{
// 如果没有父对象,则它是一个根节点
rootNodes.Add(obj);
}
}
foreach (var node in rootNodes)
LoopPrefabNode<T>(0, path, path, node, 0);
}
string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab");
foreach (string guid in prefabGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
GetPrefab<T>(path);
}
AxiProjectTools.GoTAxiProjectToolsSence();
Debug.Log("<Color=#FFF333>处理完毕 统计所有预制体和场景下的" + typeof(T).FullName + "</color>");
}
static void GetPrefab<T>(string path) where T : Component
{
#if UNITY_4_6
GameObject prefab = AssetDatabase.LoadAssetAtPath(path,typeof(GameObject)) as GameObject;
#else
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
#endif
LoopPrefabNode<T>(1, path, path, prefab.gameObject, 0);
}
static void LoopPrefabNode<T>(int _type, string _rootPath, string noderootPath, GameObject trans, int depth) where T : Component
{
// #if UNITY_2018_4_OR_NEWER
string nodename = noderootPath + "/" + trans.name;
GameObject prefabRoot = trans.gameObject;
Component[] components = prefabRoot.GetComponents<Component>();
for (int i = 0; i < components.Length; i++)
{
var com = components[i];
if (com == null)
continue;
T comobj = com as T;
if (comobj == null)
continue;
if (CheckCom(components, i, _type, _rootPath, comobj, nodename))
continue;
}
//遍历
foreach (Transform child in trans.transform)
LoopPrefabNode<T>(_type, _rootPath, nodename, child.gameObject, depth + 1);
//#else
// Debug.Log("低版本不要执行本函数");
//#endif
}
#if UNITY_2017_1_OR_NEWER
[MenuItem("Axibug移植工具/Statistics/[2]")]
public static void RepairRigBodyByStatistics()
{
List<string> errLog = new List<string>();
List<string> doneLog = new List<string>();
List<ValueTuple<string, string>> NeedRepair = new List<ValueTuple<string, string>>();
List<ValueTuple<string, string>> FinishRepair = new List<ValueTuple<string, string>>();
string CurrScenePath = string.Empty;
AxiProjectTools.GoTAxiProjectToolsSence();
#if UNITY_4_6
AxiStatisticsCache data = AssetDatabase.LoadAssetAtPath(cachecfgPath,typeof(AxiStatisticsCache)) as AxiStatisticsCache;
#else
AxiStatisticsCache data = AssetDatabase.LoadAssetAtPath<AxiStatisticsCache>(cachecfgPath);
#endif
string[] sceneGuids = AssetDatabase.FindAssets("t:scene");
List<string> ScenePath = new List<string>();
List<string> SceneName = new List<string>();
foreach (string guid in sceneGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (path.Contains(AxiProjectTools.toolSenceName))
continue;
ScenePath.Add(path);
SceneName.Add(Path.GetFileName(path));
}
string[] prefabGuids = AssetDatabase.FindAssets("t:prefab");
List<string> prefabPath = new List<string>();
List<string> prefabName = new List<string>();
foreach (string guid in prefabGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
prefabPath.Add(path);
prefabName.Add(Path.GetFileName(path));
}
foreach (var cache in data.caches.OrderBy(w => w.type))
{
//场景
if (cache.type == 0)
{
#region
string targetName = Path.GetFileName(cache.FullPath);
int Idx = SceneName.IndexOf(targetName);
if (Idx < 0)
{
Debug.LogError(targetName + "[Repair]找不到对应资源");
continue;
}
string targetpath = ScenePath[Idx];
//保证场景切换
if (!string.Equals(CurrScenePath, targetpath))
{
#if UNITY_4_6
EditorApplication.OpenScene(targetpath);
#else
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(targetpath);
#endif
}
CurrScenePath = targetpath;
#endregion
int DirtyCount = 0;
foreach (var node in cache.nodes)
{
GameObject targetNodePathObj = GetNodeByLink(cache.FullPath, node.link, out string errStr);
if (targetNodePathObj == null)
{
errLog.Add(errStr);
continue;
}
/*
string targetNodePath = node.NodeFullPath.Substring(cache.FullPath.Length, node.NodeFullPath.Length - cache.FullPath.Length);
//GameObject targetNodePathObj = GameObject.Find(targetNodePath);
GameObject targetNodePathObj = GetNodeByIdx(node, targetNodePath);
if (targetNodePathObj == null)
{
string err = "[Repair]" + node.NodeFullPath + "找不到对应节点";
errLog.Add(err);
Debug.LogError(err);
continue;
}
*/
foreach (var com in node.components)
{
if (RepairComponent(node.LinkFullStr, targetNodePathObj, com, out var errlog))
{
NeedRepair.Add(new ValueTuple<string, string>($"{cache.FullPath}:{node.LinkFullStr}", $"{com.type}[{com.ComIdxNum}]"));
DirtyCount++;
}
errLog.AddRange(errlog);
}
}
if (DirtyCount > 0)
{
Debug.Log($"[Repair][场景处理]{cache.FullPath}共{DirtyCount}个需要处理");
// 获取当前打开的场景
var activeScene = UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene();
// 保存场景
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(activeScene);
Debug.Log("场景已保存: " + activeScene.path);
string donestr = $"[Repair][场景处理成功]{targetpath},共{DirtyCount}个";
doneLog.Add(donestr);
}
}
else if (cache.type == 1)
{
string targetpath = cache.FullPath;
//来到空场景
if (!string.IsNullOrEmpty(CurrScenePath))
{
AxiProjectTools.GoTAxiProjectToolsSence();
CurrScenePath = string.Empty;
}
GameObject prefabInstance = AssetDatabase.LoadAssetAtPath<GameObject>(targetpath);
if (prefabInstance == null)
{
Debug.LogError($"[Repair]Failed to load prefab at path: {prefabPath}");
return;
}
var obj = GameObject.Instantiate(prefabInstance, null);
int DirtyCount = 0;
foreach (var node in cache.nodes)
{
GameObject targetNodePathObj = GetNodeByLink(cache.FullPath, node.link, out string errStr, obj);
if (targetNodePathObj == null)
{
errLog.Add(errStr);
continue;
}
//if (node.NodeFullPath == targetpath + "/" + Path.GetFileNameWithoutExtension(targetpath))
//{
// //预制体自己就是目标
// targetNodePathObj = obj;
//}
//else
//{
// string targetNodePath = node.NodeFullPath.Substring(cache.FullPath.Length + prefabInstance.name.Length + 2, node.NodeFullPath.Length - cache.FullPath.Length - prefabInstance.name.Length - 2);
// //targetNodePathObj = obj.transform.Find(targetNodePath)?.gameObject;
// targetNodePathObj = GetNodeByIdx(node, targetNodePath, obj);
// if (targetNodePathObj == null)
// {
// Debug.LogError("[Repair]" + targetNodePath + "找不到对应节点");
// continue;
// }
//}
foreach (var com in node.components)
{
if (RepairComponent(node.LinkFullStr, targetNodePathObj, com, out var errlog))
{
NeedRepair.Add(new ValueTuple<string, string>($"{cache.FullPath}:{node.LinkFullStr}", $"{com.type}[{com.ComIdxNum}]"));
DirtyCount++;
}
errLog.AddRange(errlog);
}
}
if (DirtyCount > 0)
{
Debug.Log($"[Repair][预制体处理]{targetpath}共{DirtyCount}个需要处理");
PrefabUtility.SaveAsPrefabAsset(obj, targetpath);
string donestr = $"[Repair][预制体处理成功]{targetpath},共{DirtyCount}个";
doneLog.Add(donestr);
}
GameObject.DestroyImmediate(obj);
}
}
AxiProjectTools.GoTAxiProjectToolsSence();
StringBuilder sb = new StringBuilder();
sb.AppendLine("[Repair][统计]:");
sb.AppendLine("----处理成功----");
foreach (var val in doneLog.OrderBy(w => w))
{
sb.AppendLine(val);
}
sb.AppendLine("----异常统计----");
foreach (var val in errLog.OrderBy(w => w))
{
sb.AppendLine(val);
}
sb.AppendLine("----需要处理----");
foreach (var val in NeedRepair.OrderBy(w => w.Item1))
{
sb.AppendLine($"{val.Item1}=>{val.Item2}");
}
Debug.Log($"{sb}");
File.WriteAllText("Assets/AxiNeedRepair.txt", sb.ToString());
}
// static GameObject GetNodeByIdx(AxiStatistics_Node nodedata, string targetNodePath, GameObject root = null)
// {
// GameObject targetNodePathObj;
// if (root == null)
// targetNodePathObj = GameObject.Find(targetNodePath);
// else
// targetNodePathObj = root.transform.Find(targetNodePath)?.gameObject;
// if (targetNodePathObj == null)
// return null;
// string targetName = targetNodePathObj.name;
// int currIdx = -1;
// if (!nodedata.NodeIdxOnlyOne)
// {
// if (targetNodePathObj.transform.parent != null)
// {
//#if UNITY_2017_1_OR_NEWER
// int count = targetNodePathObj.transform.parent.childCount;
//#else
// int count = targetNodePathObj.transform.parent.GetChildCount();
//#endif
// for (int i = 0; i < count; i++)
// {
// GameObject checkGobj = targetNodePathObj.transform.parent.GetChild(i).gameObject;
// if (checkGobj.name == targetName)
// {
// currIdx++;
// if (nodedata.NodeIdx == currIdx)
// {
// targetNodePathObj = checkGobj;
// break;
// }
// }
// }
// }
// }
// return targetNodePathObj;
// }
static GameObject GetNodeByLink(string rootPath, List<AxiStatistics_Node_Link> linklist, out string errStr, GameObject PrefabRoot = null)
{
List<AxiStatistics_Node_Link> temp_useddlink = new List<AxiStatistics_Node_Link>();
if (linklist.Count < 1)
{
errStr = $"[Repair] Link 为空";
Debug.LogError(errStr);
return null;
}
temp_useddlink.Add(linklist[0]);
GameObject currRoot;
if (PrefabRoot == null)
currRoot = GameObject.Find(linklist[0].Name);
else
{
currRoot = PrefabRoot;
//currRoot = PrefabRoot.transform.Find(linklist[0].Name)?.gameObject;
}
if (currRoot == null)
{
errStr = $"[Repair] 根节点找不到{rootPath}:{GetNodeLinkListStr(linklist)} => null";
Debug.LogError(errStr);
return null;
}
for (int link_i = 1; link_i < linklist.Count; link_i++)
{
AxiStatistics_Node_Link targetLink = linklist[link_i];
temp_useddlink.Add(targetLink);
GameObject findNode = null;
#if UNITY_2017_1_OR_NEWER
int count = currRoot.transform.childCount;
#else
int count = currNode.transform.GetChildCount();
#endif
if (targetLink.OnlyOne)
{
for (int i = 0; i < count; i++)
{
GameObject checkGobj = currRoot.transform.GetChild(i).gameObject;
if (checkGobj.name == targetLink.Name)
{
findNode = checkGobj;
break;
}
}
}
else
{
Dictionary<int, GameObject> tempHash2Node = new Dictionary<int, GameObject>();
List<GameObject> tempGobjList = new List<GameObject>();
bool HashDrity = false;
for (int i = 0; i < count; i++)
{
GameObject checkGobj = currRoot.transform.GetChild(i).gameObject;
if (checkGobj.name == targetLink.Name)
{
int temphash = GetNodeDataHash(checkGobj.transform);
if (!tempHash2Node.ContainsKey(temphash))
{
tempHash2Node.Add(GetNodeDataHash(checkGobj.transform), checkGobj);
}
else
{
HashDrity = true;
}
tempGobjList.Add(checkGobj);
}
}
//Hash严格模式
if (!HashDrity && tempHash2Node.TryGetValue(targetLink.NodeHash, out var val))
{
findNode = val;
}
//下标模式
else
{
if (targetLink.Idx < 0 || tempGobjList.Count == 0 || (tempGobjList.Count != 0 && targetLink.Idx >= tempGobjList.Count))
{
errStr = $"[Repair]link 下标模式 找不到=>{rootPath}:{GetNodeLinkListStr(temp_useddlink)}[{targetLink.Idx}] => 完整链路{rootPath}:{GetNodeLinkListStr(linklist)}";
Debug.LogError(errStr);
return null;
}
else
{
findNode = tempGobjList[targetLink.Idx];
}
}
}
currRoot = findNode;
if (currRoot == null)
break;
}
if (currRoot == null)
{
errStr = $"[Repair]link 找不到[{rootPath}:{GetNodeLinkListStr(temp_useddlink)}] => 完整链路{rootPath}:{GetNodeLinkListStr(linklist)}";
Debug.LogError(errStr);
return null;
}
else
{
errStr = string.Empty;
return currRoot;
}
}
static bool RepairComponent(string NodePath, GameObject targetNodePathObj, AxiStatistics_Node_Component comdata, out List<string> Errlog)
{
Errlog = new List<string>();
string err;
bool Dirty = false;
if (comdata.type == typeof(Rigidbody2D).ToString())
{
Rigidbody2D rg2d = GetCompnentById<Rigidbody2D>(targetNodePathObj, comdata);
if (rg2d == null)
{
err = $"[Repair]{NodePath}=> Rigidbody2D[{comdata.ComIdxNum}] == null";
Debug.LogError(err);
Errlog.Add(err);
Dirty = false;
}
/*
Unity的差异
BodyType选项Unity 4.6.7Rigidbody2D默认等效于新版的Dynamic类型Static或Kinematic
Simulated选项Rigidbody2D组件且Gravity Scale > 0
gravityScale > 0,simulated = true;bodyType = RigidbodyType2D.Dynamic;
*/
if (rg2d.gravityScale != comdata.gravityScale)
{
Debug.Log($"[Repair]{NodePath}=> Rigidbody2D[{comdata.ComIdxNum}] simulated:{rg2d.gravityScale} != :{comdata.gravityScale} rg2d.bodyType => {rg2d.bodyType} ");
rg2d.gravityScale = comdata.gravityScale;
Dirty = true;
}
//if (rg2d.gravityScale > 0 && (!rg2d.simulated || rg2d.bodyType != RigidbodyType2D.Dynamic))
if (!rg2d.simulated || rg2d.bodyType != RigidbodyType2D.Dynamic)
{
Debug.Log($"[Repair]{NodePath}=> Rigidbody2D[{comdata.ComIdxNum}] simulated:{rg2d.simulated} != :{comdata.simulated} rg2d.bodyType => {rg2d.bodyType} ");
rg2d.simulated = true;
rg2d.bodyType = RigidbodyType2D.Dynamic;
Dirty = true;
}
}
else if (comdata.type == typeof(BoxCollider2D).ToString())
{
BoxCollider2D bc = GetCompnentById<BoxCollider2D>(targetNodePathObj, comdata);
if (bc == null)
{
err = $"[Repair]{NodePath}=> BoxCollider2D[{comdata.ComIdxNum}] == null";
Debug.LogError(err);
Errlog.Add(err);
Dirty = false;
}
else
{
if (bc.size != comdata.size)
{
Debug.Log($"[Repair]{NodePath} BoxCollider2D[{comdata.ComIdxNum}] => size:{bc.size} != {comdata.size} ");
bc.size = comdata.size;
Dirty = true;
}
if (bc.offset != comdata.center)
{
Debug.Log($"[Repair]{NodePath} BoxCollider2D[{comdata.ComIdxNum}] => offset:{bc.offset} != center{comdata.center} ");
bc.offset = comdata.center;
Dirty = true;
}
if (Dirty)
{
bc.size = comdata.size;
bc.offset = comdata.center;
}
}
}
return Dirty;
}
static T GetCompnentById<T>(GameObject gobj, AxiStatistics_Node_Component node) where T : Component
{
if (node.ComIdxNum == 0)
return gobj.GetComponent<T>();
else if (node.ComIdxNum > 0)
{
T[] coms = gobj.GetComponents<T>();
if (node.ComIdxNum < coms.Length)
return coms[node.ComIdxNum];
}
return null;
}
#endif
}
#endif

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 32aabdd304d2c4d47b8ef660f672ead1
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,506 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
public class AxiProjectTools : EditorWindow
{
static string cachecfgPath = "Assets/AxiComToolCache.asset";
public static string toolSenceName = "AxiProjectTools";
public static string outCsDir = Application.dataPath + "/AxiCom/";
public static Dictionary<string, AxiPrefabCache_Com2GUID> ComType2GUID = new Dictionary<string, AxiPrefabCache_Com2GUID>();
public static void GoTAxiProjectToolsSence()
{
string[] sceneGuids = AssetDatabase.FindAssets("t:scene");
foreach (string guid in sceneGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (path.Contains(toolSenceName))
{
#if UNITY_4_6
EditorApplication.OpenScene(path);
#else
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(path);
#endif
return;
}
}
}
[MenuItem("Axibug移植工具/ToLowVersionUnity/[1]UGUI组件")]
public static void Part1()
{
GoTAxiProjectToolsSence();
ComType2GUID.Clear();
string[] sceneGuids = AssetDatabase.FindAssets("t:scene");
foreach (string guid in sceneGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (path.Contains(toolSenceName))
continue;
#if UNITY_4_6
EditorApplication.OpenScene(path);
#else
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(path);
#endif
// 创建一个列表来存储根节点
List<GameObject> rootNodes = new List<GameObject>();
// 遍历场景中的所有对象
GameObject[] allObjects = FindObjectsOfType<GameObject>();
foreach (GameObject obj in allObjects)
{
// 检查对象是否有父对象
if (obj.transform.parent == null)
{
// 如果没有父对象,则它是一个根节点
rootNodes.Add(obj);
}
}
foreach (var node in rootNodes)
LoopPrefabNode(path, node, 0);
}
string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab");
foreach (string guid in prefabGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
GetPrefab(path);
}
AxiPrefabCache cache = ScriptableObject.CreateInstance<AxiPrefabCache>();
foreach (var data in ComType2GUID)
cache.caches.Add(data.Value);
AssetDatabase.CreateAsset(cache, cachecfgPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
GoTAxiProjectToolsSence();
Debug.Log("<Color=#FFF333>处理完毕 [1]采集所有预制体和场景下的UGUI组件</color>");
}
static void GetPrefab(string path)
{
#if UNITY_4_6
GameObject prefab = AssetDatabase.LoadAssetAtPath(path,typeof(GameObject)) as GameObject;
#else
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
#endif
LoopPrefabNode(path, prefab.gameObject, 0);
}
static void LoopPrefabNode(string rootPath, GameObject trans, int depth)
{
// #if UNITY_2018_4_OR_NEWER
string nodename = rootPath + trans.name;
GameObject prefabRoot = trans.gameObject;
Component[] components = prefabRoot.GetComponents<Component>();
for (int i = 0; i < components.Length; i++)
{
var com = components[i];
if (com == null)
continue;
MonoBehaviour monoCom = com as MonoBehaviour;
if (monoCom == null)
continue;
Type monoType = monoCom.GetType();
if (!monoType.Assembly.FullName.Contains("UnityEngine.UI"))
continue;
// 获取MonoScript资源
MonoScript monoScript = MonoScript.FromMonoBehaviour(monoCom);
if (monoScript != null)
{
// 获取MonoScript资源的GUID
string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(monoScript));
Debug.Log(nodename+" | <color=#FFF333>["+monoType.Name+"]</color> <color=#FF0000>"+guid+"</color><color=#00FF00>("+monoType.FullName+")</color>");
ComType2GUID[monoType.FullName] =
new AxiPrefabCache_Com2GUID()
{
SrcFullName = monoType.FullName,
SrcName = monoType.Name,
GUID = guid,
};
}
else
{
Debug.LogError("!!!! 没得");
}
}
//遍历
foreach (Transform child in trans.transform)
LoopPrefabNode(nodename, child.gameObject, depth + 1);
//#else
// Debug.Log("低版本不要执行本函数");
//#endif
}
[MenuItem("Axibug移植工具/ToLowVersionUnity/[2]")]
public static void Part2()
{
#if UNITY_4_6
if(System.IO.Directory.Exists(outCsDir))
System.IO.Directory.Delete(outCsDir);
#else
if (UnityEngine.Windows.Directory.Exists(outCsDir))
UnityEngine.Windows.Directory.Delete(outCsDir);
#endif
Directory.CreateDirectory(outCsDir);
#if UNITY_4_6
AxiPrefabCache cache = AssetDatabase.LoadAssetAtPath(cachecfgPath,typeof(AxiPrefabCache)) as AxiPrefabCache;
#else
AxiPrefabCache cache = AssetDatabase.LoadAssetAtPath<AxiPrefabCache>(cachecfgPath);
#endif
foreach (var data in cache.caches)
{
string toName = "Axi" + data.SrcName;
string toPath = outCsDir + toName + ".cs";
string codeStr = "namespace AxibugCom { public class " + toName + " : " + data.SrcFullName + " {} }";
try
{
System.IO.File.WriteAllText(toPath, codeStr);
data.ToName = toName;
data.ToPATH = toPath;
}
catch (Exception ex)
{
Debug.LogError("写入失败" + ex.ToString());
}
}
Debug.Log("写入完毕");
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("<Color=#FFF333>处理完毕 [2]生成中间脚本代码</color>");
}
[MenuItem("Axibug移植工具/ToLowVersionUnity/[3]")]
public static void Part3()
{
#if UNITY_4_6
AxiPrefabCache cache = AssetDatabase.LoadAssetAtPath(cachecfgPath,typeof(AxiPrefabCache)) as AxiPrefabCache;
MonoScript[] allMonoScripts = (MonoScript[])Resources.FindObjectsOfTypeAll(typeof(MonoScript));
#else
AxiPrefabCache cache = AssetDatabase.LoadAssetAtPath<AxiPrefabCache>(cachecfgPath);
List<MonoScript> allMonoScripts = FindAllAssetsOfType<MonoScript>();
#endif
foreach (var data in cache.caches)
{
MonoScript monoScript = allMonoScripts.FirstOrDefault(w => w.name == data.ToName);
if (monoScript == null)
{
Debug.LogError("没找到" + data.ToName);
continue;
}
string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(monoScript));
data.ToGUID = guid;
data.monoScript = monoScript;
}
Debug.Log("写入完毕");
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("<Color=#FFF333>处理完毕 [3]收集生成的脚本</color>");
}
static List<T> FindAllAssetsOfType<T>() where T : UnityEngine.Object
{
List<T> assets = new List<T>();
string[] allGuids = AssetDatabase.FindAssets("");
foreach (string guid in allGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (path.EndsWith(".cs") || path.EndsWith(".js") || path.EndsWith(".boo")) // Unity支持多种脚本语言但现代Unity主要使用C#
{
#if UNITY_4_6
T asset = AssetDatabase.LoadAssetAtPath(cachecfgPath,typeof(T)) as T;
#else
T asset = AssetDatabase.LoadAssetAtPath<T>(path);
#endif
if (asset != null)
{
assets.Add(asset);
}
}
}
return assets;
}
[MenuItem("Axibug移植工具/ToLowVersionUnity/[4]")]
public static void Part4()
{
#if UNITY_4_6
AxiPrefabCache cache = AssetDatabase.LoadAssetAtPath(cachecfgPath,typeof(AxiPrefabCache)) as AxiPrefabCache;
#else
AxiPrefabCache cache = AssetDatabase.LoadAssetAtPath<AxiPrefabCache>(cachecfgPath);
#endif
Dictionary<string, string> tempReplaceDict = new Dictionary<string, string>();
foreach (var data in cache.caches)
{
tempReplaceDict[data.GUID] = data.ToGUID;
}
ProcessAllPrefabs("*.prefab", tempReplaceDict);
ProcessAllPrefabs("*.unity", tempReplaceDict);
ProcessAllPrefabs("*.anim", tempReplaceDict);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("<Color=#FFF333>处理完毕 [4]替换所有预制体和场景中的组件</color>");
}
static void ProcessAllPrefabs(string form, Dictionary<string, string> tempReplaceDict, bool reverse = false)
{
List<GameObject> prefabs = new List<GameObject>();
var resourcesPath = Application.dataPath;
var absolutePaths = Directory.GetFiles(resourcesPath, form, SearchOption.AllDirectories);
for (int i = 0; i < absolutePaths.Length; i++)
{
Debug.Log("prefab name: " + absolutePaths[i]);
foreach (var VARIABLE in tempReplaceDict)
{
string oldValue = reverse ? VARIABLE.Value : VARIABLE.Key;
string newValue = reverse ? VARIABLE.Key : VARIABLE.Value;
ReplaceValue(absolutePaths[i], oldValue, newValue);
}
EditorUtility.DisplayProgressBar("处理预制体……", "处理预制体中……", (float)i / absolutePaths.Length);
}
EditorUtility.ClearProgressBar();
}
/// <summary>
/// 替换值
/// </summary>
/// <param name="strFilePath">文件路径</param>
static void ReplaceValue(string strFilePath, string oldLine, string newLine)
{
if (File.Exists(strFilePath))
{
string[] lines = File.ReadAllLines(strFilePath);
for (int i = 0; i < lines.Length; i++)
{
lines[i] = lines[i].Replace(oldLine, newLine);
}
File.WriteAllLines(strFilePath, lines);
}
}
[MenuItem("Axibug移植工具/ToLowVersionUnity/[5]UnPack所有嵌套预制体和场景中的预制体")]
public static void UnpackPrefabs()
{
#if UNITY_2018_4_OR_NEWER
GoTAxiProjectToolsSence();
string[] allAssetPaths = AssetDatabase.GetAllAssetPaths();
int prefabCount = 0;
foreach (string path in allAssetPaths)
{
if (Path.GetExtension(path).Equals(".prefab"))
{
Debug.Log($"Unpacking {path}");
UnpackPrefab(path);
prefabCount++;
}
}
Debug.Log($"{prefabCount}个预制体Unpack");
string[] sceneGuids = AssetDatabase.FindAssets("t:scene");
foreach (string guid in sceneGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (path.Contains(toolSenceName))
continue;
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(path);
UnityEngine.SceneManagement.Scene currentScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
GameObject[] rootObjects = currentScene.GetRootGameObjects();
foreach (GameObject rootObj in rootObjects)
{
// 遍历场景中的所有对象
TraverseHierarchy(rootObj);
}
// Save the scene // 获取当前打开的场景
currentScene = UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene();
// 保存场景到文件(默认路径和名称)
bool success = UnityEditor.SceneManagement.EditorSceneManager.SaveScene(currentScene, currentScene.path);
Debug.Log($"{currentScene.name}场景中 所有物体Unpack");
}
GoTAxiProjectToolsSence();
Debug.Log("<Color=#FFF333>处理完毕 [5]UnPack所有预制体</color>");
#else
Debug.Log("低版本不要执行本函数");
#endif
}
static void UnpackPrefab(string prefabPath)
{
#if UNITY_2018_4_OR_NEWER
GameObject prefabInstance = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (prefabInstance == null)
{
Debug.LogError($"Failed to load prefab at path: {prefabPath}");
return;
}
var obj = GameObject.Instantiate(prefabInstance, null);
TraverseHierarchy(obj);
PrefabUtility.SaveAsPrefabAsset(obj, prefabPath);
GameObject.DestroyImmediate(obj);
#else
Debug.Log("低版本不要执行本函数");
#endif
}
static void TraverseHierarchy(GameObject obj)
{
#if UNITY_2018_4_OR_NEWER
// 检查该对象是否是预制体的实例
if (PrefabUtility.IsPartOfPrefabInstance(obj))
{
// 将预制体实例转换为普通游戏对象
PrefabUtility.UnpackPrefabInstance(obj, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction);
Debug.Log("Prefab instance converted to game object: " + obj.name);
}
// 递归遍历子对象
for (int i = 0; i < obj.transform.childCount; i++)
{
TraverseHierarchy(obj.transform.GetChild(i).gameObject);
}
#else
Debug.Log("低版本不要执行本函数");
#endif
}
[MenuItem("Axibug移植工具/ToLowVersionUnity/[6]Sprite")]
public static void FixMultipleMaterialSprites()
{
string[] guids = AssetDatabase.FindAssets("t:sprite");
List<Sprite> spritesToFix = new List<Sprite>();
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(path);
// 检查是否有多个材质
if (IsUsingMultipleMaterials(sprite))
{
spritesToFix.Add(sprite);
Debug.Log("Found sprite with multiple materials: " + path);
}
}
// 修复每个找到的Sprite
foreach (var sprite in spritesToFix)
{
FixSprite(sprite);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("<Color=#FFF333>处理完毕 [6]修复Sprite</color>");
}
private static bool IsUsingMultipleMaterials(Sprite sprite)
{
if (sprite == null) return false;
// 获取精灵的材质
var textureImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(sprite)) as TextureImporter;
return textureImporter != null && textureImporter.spriteImportMode == SpriteImportMode.Multiple;
}
private static void FixSprite(Sprite sprite)
{
// 获取Sprite的路径
string path = AssetDatabase.GetAssetPath(sprite);
var textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
if (textureImporter != null)
{
// 保存当前切割信息
SpriteMetaData[] originalMetaData = textureImporter.spritesheet;
// 临时禁用Sprite导入
textureImporter.spriteImportMode = SpriteImportMode.None;
textureImporter.SaveAndReimport();
// 重新启用Sprite导入并保持原样切割参数
textureImporter.spriteImportMode = SpriteImportMode.Multiple;
textureImporter.spritesheet = originalMetaData; // 恢复原来的切割信息
// 重新导入以应用更改
textureImporter.SaveAndReimport();
}
}
[MenuItem("Axibug移植工具/ToLowVersionUnity/[7]")]
static void FixPrefabRefs()
{
// 1. 扫描所有预制体
string[] prefabPaths = Directory.GetFiles("Assets", "*.prefab", SearchOption.AllDirectories);
foreach (var path in prefabPaths) FixRefTypeInFile(path);
// 2. 处理场景文件
string[] scenePaths = Directory.GetFiles("Assets", "*.unity", SearchOption.AllDirectories);
foreach (var path in scenePaths) FixRefTypeInFile(path);
AssetDatabase.Refresh();
Debug.Log("<Color=#FFF333>处理完毕 [5]导入低版本后:修复预制体依赖丢失</color>");
Debug.Log("修复完成!已处理" + prefabPaths.Length + "个预制体");
}
public static void FixRefTypeInFile(string filePath)
{
string content = File.ReadAllText(filePath);
// 匹配所有 {fileID: X, guid: Y, type: Z} 结构
string pattern = @"(\{[^}]*guid:\s*(\w+)[^}]*type:\s*)3(\s*[^}]*\})";
string newContent = Regex.Replace(content, pattern, match => {
string guid = match.Groups[2].Value;
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
// 仅当资源类型为 GameObject 时修改 type
if (AssetDatabase.GetMainAssetTypeAtPath(assetPath) == typeof(GameObject))
// if (assetPath.ToLower().EndsWith(".prefab")
////&& assetPath.Contains("/sound/")
//&& assetPath.Contains("/level")
// )
{
Debug.Log("已处理被引用项=>"+assetPath+" ,引用到=>"+ filePath);
Debug.Log("原值=>" + match.Value + " ,处理值=>"+ match.Groups[1].Value + "2" + match.Groups[3].Value);
//return match.Value;
return match.Groups[1].Value + "2" + match.Groups[3].Value; // type:3→2
}
return match.Value;
});
File.WriteAllText(filePath, newContent);
}
}
#endif

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 01c95f5e03a338749b54784eb6420d04

View File

@ -0,0 +1,73 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class AxiStatisticsCache : ScriptableObject
{
public List<AxiStatisticsDatas> caches = new List<AxiStatisticsDatas>();
}
[Serializable]
public class AxiStatisticsDatas
{
/// <summary>
/// [0]Sence [1]Prefab
/// </summary>
public int type;
public string FullPath;
public List<AxiStatistics_Node> nodes = new List<AxiStatistics_Node>();
}
[Serializable]
public class AxiStatistics_Node
{
public string Name;
public List<AxiStatistics_Node_Link> link = new List<AxiStatistics_Node_Link>();
public int LinkHash;
public string LinkFullStr;
//public string NodeFullPath;
// /// <summary>
// /// 表示相同路径只有一个
// /// </summary>
// public bool NodeIdxOnlyOne;
/// <summary>
/// 表示相同路径是第几个下标
/// </summary>
//public int NodeIdx;
public List<AxiStatistics_Node_Component> components = new List<AxiStatistics_Node_Component>();
}
[Serializable]
public class AxiStatistics_Node_Link
{
public string Name;
public bool OnlyOne;
public int Idx;
public int NodeHash;
}
[Serializable]
public class AxiStatistics_Node_Component
{
public string type;
/// <summary>
/// 表示相同组件只有一个
/// </summary>
public bool ComTypeOnlyOne;
/// <summary>
/// 表示相同组件是第几个下标
/// </summary>
public int ComIdxNum;
//Rigboody
public bool simulated;
public float gravityScale;
public bool isKinematic;
//BoxCollider2D
public Vector2 center;
public Vector2 size;
}
#endif

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e4cee4feffb506b4c833262e779424f6
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 11a59d59d152c214bb99a09f4d795c21
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,316 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 10
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 0
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 13
m_BakeOnSceneLoad: 0
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 1
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 256
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 1
m_PVRDenoiserTypeDirect: 1
m_PVRDenoiserTypeIndirect: 1
m_PVRDenoiserTypeAO: 1
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 1
m_PVRFilteringGaussRadiusAO: 1
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 3
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
buildHeightMesh: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &1760378052
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1760378055}
- component: {fileID: 1760378054}
- component: {fileID: 1760378053}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &1760378053
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1760378052}
m_Enabled: 1
--- !u!20 &1760378054
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1760378052}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_Iso: 200
m_ShutterSpeed: 0.005
m_Aperture: 16
m_FocusDistance: 10
m_FocalLength: 50
m_BladeCount: 5
m_Curvature: {x: 2, y: 11}
m_BarrelClipping: 0.25
m_Anamorphism: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &1760378055
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1760378052}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1948026923
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1948026925}
- component: {fileID: 1948026924}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!108 &1948026924
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1948026923}
m_Enabled: 1
serializedVersion: 11
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize: 10
m_Shadows:
m_Type: 2
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!4 &1948026925
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1948026923}
serializedVersion: 2
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- {fileID: 1760378055}
- {fileID: 1948026925}

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fd41663b74cbfcc45a028bc891a8c4fc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
{
"name": "AxibugEmuOnline.Editor",
"rootNamespace": "AxibugEmuOnline.Editors",
"references": [
"AxibugEmuOnline.Client"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

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

View File

@ -0,0 +1,50 @@
using AxibugEmuOnline.Client;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
namespace AxibugEmuOnline.Editors
{
[CustomEditor(typeof(CommandDispatcher))]
public class CommandDispatcherEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (!Application.isPlaying) return;
var dispacather = target as CommandDispatcher;
IReadOnlyList<CommandExecuter> normal; IReadOnlyList<CommandExecuter> solo;
dispacather.GetRegisters(out normal, out solo);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("NORMAL");
foreach (var item in normal)
{
Draw(item);
}
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("SOLO");
foreach (var item in solo)
{
Draw(item);
}
EditorGUILayout.EndVertical();
EditorGUILayout.LabelField(dispacather.Mode.ToString());
Repaint();
}
private void Draw(CommandExecuter item)
{
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
using (new EditorGUI.DisabledGroupScope(!item.Enable))
EditorGUILayout.ObjectField(item.gameObject, typeof(GameObject), false);
EditorGUILayout.EndHorizontal();
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b8790f215d873d044aa8d00bacdee237
guid: 57378be70cec95341aea522ad2d8e30d
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 1ad516ab7bf27b945a96f9c6eca54f10
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3}
generateWrapperCode: 0
wrapperCodePath:
wrapperClassName:
wrapperCodeNamespace:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 69eb869b06dab98439f07da4549fb7cb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4094856a5db24f142a14fda03f1215a2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,46 @@
#if UNITY_SWITCH
using nn.account;
#endif
public class AxiNS
{
static AxiNS _instance;
public static AxiNS instance
{
get
{
if (_instance == null)
_instance = new AxiNS();
return _instance;
}
}
/// <summary>
/// 延迟提交是否使用多线程
/// </summary>
public static bool usedmultithreading = false;
public AxiNSUser user;
public AxiNSMount mount;
public AxiNSIO io;
public AxiNSWaitHandle wait;
AxiNS()
{
user = new AxiNSUser();
mount = new AxiNSMount();
io = new AxiNSIO();
wait = new AxiNSWaitHandle();
}
/// <summary>
/// 初始化(最好在项目第一时间初始化,保证先初始化再使用某些东西,才不闪退)
/// </summary>
public void Init()
{
#if UNITY_SWITCH
if (!user.GetUserID(out Uid uid))
return;
mount.MountSave(uid);
#endif
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c29cb72b155d20a48a3a47a7a05160bd
guid: 52541c757d45c4c488726bcc39f73ba6
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,3 @@
{
"name": "AxiNSApi"
}

View File

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

View File

@ -0,0 +1,367 @@
public static class AxiNSErrCode
{
#if UNITY_SWITCH
public static string GetErrorInfo(this nn.Result result)
{
if (result.IsSuccess())
return "NoErr";
return GetErrorDetails(result.GetModule(), result.GetDescription());
}
#endif
/// <summary>
/// 根据模块 ID 和描述 ID 返回任天堂 Switch 错误码的含义、可能原因及解决办法。
/// </summary>
/// <param name="moduleId">模块 ID</param>
/// <param name="descriptionId">描述 ID</param>
/// <returns>包含错误码、含义、可能原因及解决办法的字符串</returns>
public static string GetErrorDetails(int moduleId, int descriptionId)
{
string errorCode = $"2{moduleId:D3}-{descriptionId:D4}"; // 格式化为 2XXX-YYYY
string meaning = "未知错误";
string causeAndSolution = "未知错误,请检查日志或联系任天堂支持。";
switch (moduleId)
{
case 2: // nn::fs (文件系统)
switch (descriptionId)
{
case 1:
meaning = "ResultPathNotFound";
causeAndSolution = "路径未找到。检查路径是否正确,确保父目录存在。使用 nn.fs.Directory.Create 创建父目录。";
break;
case 2:
meaning = "ResultPermissionDenied";
causeAndSolution = "权限被拒绝。可能是 Atmosphere 限制了 save:/ 的写权限。尝试调整 Atmosphere 配置或使用 sd:/ 挂载点。";
break;
case 3:
meaning = "ResultPathAlreadyExists";
causeAndSolution = "路径已存在。检查路径是否被占用,删除或重命名现有文件/目录。";
break;
case 5:
meaning = "ResultTargetLocked";
causeAndSolution = "目标被锁定。可能是文件正在使用中,关闭相关程序后重试。";
break;
case 7:
meaning = "ResultTargetNotFound";
causeAndSolution = "目标未找到。确认目标文件/目录是否存在,检查路径拼写。";
break;
}
break;
case 5: // nn::err (错误处理)
switch (descriptionId)
{
case 3:
meaning = "microSD 卡相关错误";
causeAndSolution = "无法下载软件,可能是 microSD 卡损坏。移除 microSD 卡,重新插入,或更换卡。";
break;
}
break;
case 16: // nn::oe (操作系统错误)
switch (descriptionId)
{
case 247:
meaning = "microSD 卡存储问题";
causeAndSolution = "microSD 卡损坏或不兼容。更换 microSD 卡,或将默认存储位置设置为系统内存。";
break;
case 390:
meaning = "不支持的 microSD 卡";
causeAndSolution = "microSD 卡格式不受支持。格式化为 exFAT 或 FAT32 后重试。";
break;
case 601:
meaning = "microSD 卡数据损坏";
causeAndSolution = "microSD 卡数据损坏。备份数据后格式化卡,或更换新卡。";
break;
}
break;
case 21: // nn::settings (设置)
switch (descriptionId)
{
case 3:
meaning = "系统软件未更新或损坏";
causeAndSolution = "系统固件版本过旧或损坏。更新系统固件,或重新安装系统。";
break;
}
break;
case 101: // nn::fssrv (文件系统服务)
switch (descriptionId)
{
case 1:
meaning = "系统错误";
causeAndSolution = "通用系统错误。重启 Switch若问题持续联系任天堂支持。";
break;
case 2:
meaning = "固件损坏";
causeAndSolution = "系统固件损坏。尝试更新系统,或恢复出厂设置(注意备份数据)。";
break;
}
break;
case 107: // nn::nim (网络安装管理)
switch (descriptionId)
{
case 427:
meaning = "固件损坏";
causeAndSolution = "系统固件损坏。更新系统固件,或重新安装系统。";
break;
case 445:
meaning = "硬件损坏或盗版内容";
causeAndSolution = "可能是硬件损坏或存在盗版内容。删除盗版内容,重启 Switch若无效联系任天堂支持。";
break;
}
break;
case 110: // nn::socket (网络套接字)
switch (descriptionId)
{
case 1000:
meaning = "网络连接失败";
causeAndSolution = "无法连接到网络。检查网络连接重启路由器尝试切换到其他网络频段2.4GHz/5GHz。";
break;
case 2003:
meaning = "无线网络连接失败";
causeAndSolution = "无线信号弱或不稳定。靠近路由器,移除干扰物,或重启路由器。";
break;
case 2004:
meaning = "网络设置不支持";
causeAndSolution = "网络安全类型不受支持。Switch 支持 WEP/WPA/WPA2调整路由器设置后重试。";
break;
case 2091:
meaning = "有线网络连接失败";
causeAndSolution = "有线连接问题。检查网线是否插好,重启 Switch 和路由器。";
break;
case 3127:
meaning = "网络连接失败";
causeAndSolution = "网络不稳定。测试网络连接,重启路由器,或联系网络提供商。";
break;
}
break;
case 115: // nn::mii (Mii 相关)
switch (descriptionId)
{
case 96:
meaning = "Mii 数据错误";
causeAndSolution = "Mii 数据损坏。删除并重新创建 Mii或更新系统。";
break;
}
break;
case 139: // nn::nfp (NFC/Amiibo)
switch (descriptionId)
{
case 6:
meaning = "Amiibo 读取错误";
causeAndSolution = "Amiibo 读取失败。检查 Amiibo 是否损坏,更新系统,或尝试其他 Amiibo。";
break;
}
break;
case 153: // nn::ir (红外摄像头)
switch (descriptionId)
{
case 321:
meaning = "红外摄像头读取错误";
causeAndSolution = "红外摄像头无法读取。检查摄像头是否被遮挡,清洁镜头,或联系任天堂支持。";
break;
case 1540:
meaning = "红外摄像头硬件错误";
causeAndSolution = "红外摄像头硬件故障。联系任天堂支持进行维修。";
break;
}
break;
case 155: // nn::account (账户服务)
switch (descriptionId)
{
case 8006:
meaning = "无法链接任天堂账户";
causeAndSolution = "网络问题或服务中断。检查网络连接,稍后重试,或查看任天堂网络状态页面。";
break;
}
break;
case 160: // nn::online (在线服务)
switch (descriptionId)
{
case 103:
meaning = "无法加入在线匹配";
causeAndSolution = "网络不稳定。重启 Switch检查网络连接或稍后重试。";
break;
case 202:
meaning = "无法连接到在线服务";
causeAndSolution = "服务可能正在维护。查看任天堂网络状态页面,稍后重试。";
break;
case 8006:
meaning = "连接测试失败";
causeAndSolution = "网络问题。重启路由器,检查网络设置,或尝试其他网络。";
break;
}
break;
case 162: // nn::application (应用程序)
switch (descriptionId)
{
case 2:
meaning = "软件崩溃";
causeAndSolution = "软件崩溃,可能是盗版内容或固件问题。删除盗版内容,更新系统,或重新安装软件。";
break;
case 101:
meaning = "软件需要更新";
causeAndSolution = "游戏或软件需要更新。检查软件更新并安装。";
break;
}
break;
case 168: // nn::sys (系统)
switch (descriptionId)
{
case 0:
meaning = "需要软件更新";
causeAndSolution = "软件需要更新。更新游戏或系统固件。";
break;
case 2:
meaning = "系统崩溃";
causeAndSolution = "系统崩溃,可能是硬件问题。重启 Switch若无效联系任天堂支持。";
break;
}
break;
case 205: // nn::camera (摄像头)
switch (descriptionId)
{
case 123:
meaning = "摄像头读取错误";
causeAndSolution = "摄像头无法读取。检查摄像头是否被遮挡,清洁镜头,或联系任天堂支持。";
break;
}
break;
case 306: // nn::ngc (网络游戏连接)
switch (descriptionId)
{
case 501:
meaning = "无法加入在线匹配";
causeAndSolution = "网络连接中断。重启 Switch检查网络连接或稍后重试。";
break;
case 502:
meaning = "匹配过程失败";
causeAndSolution = "网络不稳定。测试网络连接,重启路由器,或联系网络提供商。";
break;
case 820:
meaning = "在线服务不可用";
causeAndSolution = "服务可能正在维护。查看任天堂网络状态页面,稍后重试。";
break;
}
break;
case 613: // nn::eShop (eShop)
switch (descriptionId)
{
case 1400:
meaning = "无法使用信用卡购买";
causeAndSolution = "信用卡信息错误或 eShop 服务问题。检查信用卡信息,稍后重试,或联系任天堂支持。";
break;
case 6838:
meaning = "eShop 连接失败";
causeAndSolution = "网络问题或 eShop 维护。检查网络连接,查看任天堂网络状态页面,稍后重试。";
break;
}
break;
case 618: // nn::ngc (网络游戏连接)
switch (descriptionId)
{
case 6:
meaning = "无法加入在线匹配";
causeAndSolution = "网络不稳定。重启 Switch检查网络连接或稍后重试。";
break;
case 201:
meaning = "匹配已满";
causeAndSolution = "尝试加入的匹配已满。稍后重试,或加入其他匹配。";
break;
case 501:
meaning = "匹配过程失败";
causeAndSolution = "网络连接中断。重启 Switch检查网络连接或稍后重试。";
break;
}
break;
case 801: // nn::sns (社交网络服务)
switch (descriptionId)
{
case 7002:
meaning = "无法上传截图到 Twitter";
causeAndSolution = "服务可能正在维护。查看任天堂网络状态页面,稍后重试。";
break;
case 7199:
meaning = "无法上传照片到 Facebook";
causeAndSolution = "服务可能正在维护。查看任天堂网络状态页面,稍后重试。";
break;
}
break;
case 810: // nn::account (账户服务)
switch (descriptionId)
{
case 1224:
meaning = "无法登录任天堂账户";
causeAndSolution = "网络问题或服务中断。检查网络连接,稍后重试,或查看任天堂网络状态页面。";
break;
case 1500:
meaning = "无法登录 Facebook 账户";
causeAndSolution = "服务可能正在维护。重启 Switch稍后重试。";
break;
}
break;
case 811: // nn::account (账户服务)
switch (descriptionId)
{
case 1006:
meaning = "无法链接任天堂账户";
causeAndSolution = "网络问题或 DNS 错误。检查网络连接,尝试更换 DNS如 8.8.8.8),或稍后重试。";
break;
case 5001:
meaning = "无法访问 eShop";
causeAndSolution = "eShop 服务中断。查看任天堂网络状态页面,稍后重试。";
break;
}
break;
case 813: // nn::eShop (eShop)
switch (descriptionId)
{
case 0:
meaning = "eShop 访问失败";
causeAndSolution = "eShop 服务中断。查看任天堂网络状态页面,稍后重试。";
break;
case 2470:
meaning = "交易处理失败";
causeAndSolution = "信用卡信息错误或 eShop 服务问题。检查信用卡信息,稍后重试,或联系任天堂支持。";
break;
}
break;
case 819: // nn::online (在线服务)
switch (descriptionId)
{
case 3:
meaning = "软件被暂停";
causeAndSolution = "同一账户在另一台设备上使用。关闭其他设备上的软件,或使用不同账户。";
break;
}
break;
default:
meaning = "未知模块";
causeAndSolution = "未识别的模块 ID请检查日志或联系任天堂支持。";
break;
}
return $"错误码: {errorCode}\n含义: {meaning}\n可能原因及解决办法: {causeAndSolution}";
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c8339ffab20bfea4cbc7d3aa440c3fdb
guid: 0e28b69692cb1bb4a9d8ddb91274fa50
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,869 @@
#if UNITY_SWITCH
using nn.fs;
#endif
using System.Collections.Generic;
using System.Text.RegularExpressions;
public class AxiNSIO
{
string save_name => AxiNS.instance.mount.SaveMountName;
public string save_path => $"{save_name}:/";
#if UNITY_SWITCH
private FileHandle fileHandle = new nn.fs.FileHandle();
#endif
static object commitLock = new object();
static bool bDirty = false;
bool CommitSave()
{
lock (commitLock)
{
#if UNITY_SWITCH && !UNITY_EDITOR
// 阻止用户在保存时,退出游戏 Switch 条例 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
nn.Result ret = FileSystem.Commit(save_name);
if (!ret.IsSuccess())
{
UnityEngine.Debug.LogError($"FileSystem.Commit({save_name}) 失败: " + ret.GetErrorInfo());
return false;
}
// 停止阻止用户退出游戏
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
bDirty = false;
return true;
#else
return false;
#endif
}
}
void SetCommitDirty()
{
lock (commitLock)
{
bDirty = true;
}
}
public void ApplyAutoCommit()
{
bool temp;
lock (commitLock)
{
temp = bDirty;
}
if (temp)
{
CommitSave();
}
}
/// <summary>
/// 检查Path是否存在
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public bool CheckPathExists(string filePath)
{
#if !UNITY_SWITCH
return false;
#else
nn.fs.EntryType entryType = 0;
nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, filePath);
//result.abortUnlessSuccess();
//这个异常捕获。真的别扭
//日FileSystem.ResultPathAlreadyExists 貌似不太行
//return nn.fs.FileSystem.ResultPathAlreadyExists.Includes(result);
return !nn.fs.FileSystem.ResultPathNotFound.Includes(result);
#endif
}
/// <summary>
/// 检查Path是否不存在
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public bool CheckPathNotFound(string filePath)
{
#if !UNITY_SWITCH
return false;
#else
nn.fs.EntryType entryType = 0;
nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, filePath);
//这个异常捕获。真的别扭
return nn.fs.FileSystem.ResultPathNotFound.Includes(result);
#endif
}
/// <summary>
/// 创建目录目录存在也会返回true
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public bool CreateDir(string filePath)
{
lock (commitLock)
{
#if !UNITY_SWITCH
return false;
#else
// 使用封装函数检查和创建父目录
if (!EnsureParentDirectory(filePath, true))
{
UnityEngine.Debug.LogError($"无法确保父目录,文件写入取消: {filePath}");
return false;
}
return true;
#endif
}
}
/// <summary>
/// 保存并创建文件(如果目录不存在回先自动创建目录)
/// </summary>
/// <param name="filePath"></param>
/// <param name="bw"></param>
/// <returns></returns>
public bool FileToSaveWithCreate(string filePath, System.IO.MemoryStream ms)
{
return FileToSaveWithCreate(filePath, ms.ToArray());
}
/// <summary>
/// 保存并创建文件(如果目录不存在回先自动创建目录)
/// </summary>
/// <param name="filePath"></param>
/// <param name="data"></param>
/// <returns></returns>
public AxiNSWait_FileToSaveByMSWithCreate FileToSaveWithCreateAsync(string filePath, System.IO.MemoryStream ms)
{
var wait = new AxiNSWait_FileToSaveByMSWithCreate(filePath, ms);
AxiNS.instance.wait.AddWait(wait);
return wait;
}
/// <summary>
/// 保存并创建文件(如果目录不存在回先自动创建目录)
/// </summary>
/// <param name="filePath"></param>
/// <param name="data"></param>
/// <param name="immediatelyCommit">是否立即Commit到物理存储</param>
/// <returns></returns>
public bool FileToSaveWithCreate(string filePath, byte[] data, bool immediatelyCommit = true)
{
lock (commitLock)
{
#if !UNITY_SWITCH
return false;
#else
if (!AxiNS.instance.mount.SaveIsMount)
{
UnityEngine.Debug.LogError($"Save 尚未挂载,无法存储 {filePath}");
return false;
}
nn.Result result;
#if UNITY_SWITCH && !UNITY_EDITOR
// 阻止用户在保存时,退出游戏
// Switch 条例 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
// 使用封装函数检查和创建父目录
if (!EnsureParentDirectory(filePath, true))
{
UnityEngine.Debug.LogError($"无法确保父目录,文件写入取消: {filePath}");
return false;
}
//string directoryPath = System.IO.Path.GetDirectoryName(filePath.Replace(save_path, ""));
//string fullDirectoryPath = $"{save_path}{directoryPath}";
//UnityEngine.Debug.Log($"检查父目录: {fullDirectoryPath}");
//nn.fs.EntryType entryType = 0;
//result = nn.fs.FileSystem.GetEntryType(ref entryType, fullDirectoryPath);
//if (!result.IsSuccess() && nn.fs.FileSystem.ResultPathNotFound.Includes(result))
//{
// UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 不存在,尝试创建 (判断依据 result=>{result.ToString()})");
// result = nn.fs.Directory.Create(fullDirectoryPath);
// if (!result.IsSuccess())
// {
// UnityEngine.Debug.LogError($"创建父目录失败: {result.GetErrorInfo()}");
// return false;
// }
// UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 创建成功");
//}
//else if (result.IsSuccess() && entryType != nn.fs.EntryType.Directory)
//{
// UnityEngine.Debug.LogError($"路径 {fullDirectoryPath} 已存在,但不是目录");
// return false;
//}
//else if (!result.IsSuccess())
//{
// UnityEngine.Debug.LogError($"检查父目录失败: {result.GetErrorInfo()}");
// return false;
//}
if (CheckPathNotFound(filePath))
{
UnityEngine.Debug.Log($"文件({filePath})不存在需要创建");
result = nn.fs.File.Create(filePath, data.Length); //this makes a file the size of your save journal. You may want to make a file smaller than this.
//result.abortUnlessSuccess();
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"创建文件失败 {filePath} : " + result.GetErrorInfo());
return false;
}
//读取文件Handle
result = File.Open(ref fileHandle, filePath, OpenFileMode.Write);
}
else
{
//读取文件Handle
result = File.Open(ref fileHandle, filePath, OpenFileMode.Write);
long currsize = 0;
File.GetSize(ref currsize, fileHandle);
if (currsize == data.Length)
{
UnityEngine.Debug.Log($"文件({filePath})存在,长度一致,不用重新创建");
}
else
{
UnityEngine.Debug.Log($"文件({filePath})存在,长度不一致,先删除再重建");
nn.fs.File.Close(fileHandle);
//删除
File.Delete(filePath);
//重新创建
result = nn.fs.File.Create(filePath, data.Length);
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"创建文件失败 {filePath} : " + result.GetErrorInfo());
return false;
}
//重新读取文件Handle
result = File.Open(ref fileHandle, filePath, OpenFileMode.Write);
}
}
// //OpenFileMode.AllowAppend 好像不可用
// //result = File.Open(ref fileHandle, filePath, OpenFileMode.AllowAppend);
// result = File.Open(ref fileHandle, filePath, OpenFileMode.Write);
//result.abortUnlessSuccess();
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"失败 File.Open(ref filehandle, {filePath}, OpenFileMode.Write): " + result.GetErrorInfo());
return false;
}
UnityEngine.Debug.Log($"成功 File.Open(ref filehandle, {filePath}, OpenFileMode.Write)");
//nn.fs.WriteOption.Flush 应该就是覆盖写入
result = nn.fs.File.Write(fileHandle, 0, data, data.Length, nn.fs.WriteOption.Flush); // Writes and flushes the write at the same time
//result.abortUnlessSuccess();
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError("写入文件失败: " + result.GetErrorInfo());
return false;
}
UnityEngine.Debug.Log("写入文件成功: " + filePath);
nn.fs.File.Close(fileHandle);
#if UNITY_SWITCH && !UNITY_EDITOR
// 停止阻止用户退出游戏
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
if (immediatelyCommit)
{
//必须得提交,否则没有真实写入
return CommitSave();
}
else
{
SetCommitDirty();
return true;
}
#endif
}
}
/// <summary>
/// 保存并创建文件(如果目录不存在回先自动创建目录)
/// </summary>
/// <param name="filePath"></param>
/// <param name="data"></param>
/// <returns></returns>
public AxiNSWait_FileToSaveWithCreate FileToSaveWithCreateAsync(string filePath, byte[] data)
{
var wait = new AxiNSWait_FileToSaveWithCreate(filePath, data);
AxiNS.instance.wait.AddWait(wait);
return wait;
}
public byte[] LoadSwitchDataFile(string filename)
{
byte[] outputData;
LoadSwitchDataFile(filename, out outputData);
return outputData;
}
public bool LoadSwitchDataFile(string filename, ref System.IO.MemoryStream ms)
{
byte[] outputData;
if (LoadSwitchDataFile(filename, out outputData))
{
using (System.IO.BinaryWriter writer = new System.IO.BinaryWriter(ms))
{
writer.Write(outputData);
}
return true;
}
return false;
}
public bool LoadSwitchDataFile(string filename, out byte[] outputData)
{
#if !UNITY_SWITCH || UNITY_EDITOR
outputData = null;
return false;
#else
outputData = null;
if (!AxiNS.instance.mount.SaveIsMount)
{
UnityEngine.Debug.LogError($"Save 尚未挂载,无法读取 {filename}");
return false;
}
if (CheckPathNotFound(filename))
return false;
nn.Result result;
result = nn.fs.File.Open(ref fileHandle, filename, nn.fs.OpenFileMode.Read);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.Open 失败 {filename} : result=>{result.GetErrorInfo()}");
return false; // Could not open file. This can be used to detect if this is the first time a user has launched your game.
// (However, be sure you are not getting this error due to your file being locked by another process, etc.)
}
UnityEngine.Debug.Log($"nn.fs.File.Open 成功 {filename}");
long iFileSize = 0;
result = nn.fs.File.GetSize(ref iFileSize, fileHandle);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.GetSize 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
UnityEngine.Debug.Log($"nn.fs.File.GetSize 成功 {filename},size=>{iFileSize}");
byte[] loadedData = new byte[iFileSize];
result = nn.fs.File.Read(fileHandle, 0, loadedData, iFileSize);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.Read 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
UnityEngine.Debug.Log($"nn.fs.File.Read 成功 {filename}");
nn.fs.File.Close(fileHandle);
//for (int i = 0; i < loadedData.Length; i++)
//{
// UnityEngine.Debug.Log($"data[{i}]:{loadedData[i]}");
//}
outputData = loadedData;
return true;
#endif
}
public AxiNSWait_LoadSwitchDataFile LoadSwitchDataFileAsync(string filename)
{
var wait = new AxiNSWait_LoadSwitchDataFile(filename);
AxiNS.instance.wait.AddWait(wait);
return wait;
}
public bool GetDirectoryFiles(string path, out string[] entrys)
{
#if !UNITY_SWITCH || UNITY_EDITOR
entrys = null;
return false;
#else
return GetDirectoryEntrys(path,nn.fs.OpenDirectoryMode.File,out entrys);
#endif
}
public bool GetDirectoryDirs(string path, out string[] entrys)
{
#if !UNITY_SWITCH || UNITY_EDITOR
entrys = null;
return false;
#else
return GetDirectoryEntrys(path, nn.fs.OpenDirectoryMode.Directory, out entrys);
#endif
}
#if UNITY_SWITCH
public bool GetDirectoryEntrys(string path, nn.fs.OpenDirectoryMode type, out string[] entrys)
{
nn.fs.DirectoryHandle eHandle = new nn.fs.DirectoryHandle();
nn.Result result = nn.fs.Directory.Open(ref eHandle, path, type);
if (nn.fs.FileSystem.ResultPathNotFound.Includes(result))
{
UnityEngine.Debug.Log($"目录 {path} 不存在");
entrys = null;
return false;
}
long entryCount = 0;
nn.fs.Directory.GetEntryCount(ref entryCount, eHandle);
nn.fs.DirectoryEntry[] entries = new nn.fs.DirectoryEntry[entryCount];
long actualEntries = 0;
nn.fs.Directory.Read(ref actualEntries, entries, eHandle, entryCount);
entrys = new string[actualEntries];
for (int i = 0; i < actualEntries; i++)
{
entrys[i] = System.IO.Path.Combine(path, entries[i].name);
}
nn.fs.Directory.Close(eHandle);
return true;
}
#endif
public bool GetDirectoryEntrysFullRecursion(string path, out string[] entrys)
{
#if UNITY_SWITCH
nn.fs.DirectoryHandle eHandle = new nn.fs.DirectoryHandle();
nn.Result result = nn.fs.Directory.Open(ref eHandle, path, OpenDirectoryMode.All);
if (nn.fs.FileSystem.ResultPathNotFound.Includes(result))
{
UnityEngine.Debug.Log($"目录 {path} 不存在");
entrys = null;
return false;
}
long entryCount = 0;
nn.fs.Directory.GetEntryCount(ref entryCount, eHandle);
nn.fs.DirectoryEntry[] entries = new nn.fs.DirectoryEntry[entryCount];
long actualEntries = 0;
nn.fs.Directory.Read(ref actualEntries, entries, eHandle, entryCount);
List<string> temp = new List<string>();
for (int i = 0; i < actualEntries; i++)
{
string singlePath = System.IO.Path.Combine(path, entries[i].name);
temp.Add(singlePath);
if (entries[i].entryType == EntryType.Directory && GetDirectoryEntrysFullRecursion(singlePath, out string[] singleEntryList))
{
temp.AddRange(singleEntryList);
}
}
nn.fs.Directory.Close(eHandle);
entrys = temp.ToArray();
return true;
#else
entrys = null;
return false;
#endif
}
public IEnumerable<string> EnumerateFiles(string path, string searchPattern)
{
#if !UNITY_SWITCH || UNITY_EDITOR
yield break;
#else
// 将通配符转换为正则表达式(支持*和?
var regexPattern = "^" +
Regex.Escape(searchPattern)
.Replace("\\*", ".*")
.Replace("\\?", ".")
+ "$";
var regex = new Regex(regexPattern, RegexOptions.IgnoreCase);
if (!GetDirectoryEntrys(path, nn.fs.OpenDirectoryMode.File, out string[] entrys))
{
yield break;
}
for (int i = 0; i < entrys.Length; i++)
{
if (regex.IsMatch(System.IO.Path.GetFileName(entrys[i])))
{
yield return entrys[i];
}
}
#endif
}
public bool DeletePathFile(string filename)
{
#if !UNITY_SWITCH
return false;
#else
#if UNITY_SWITCH && !UNITY_EDITOR
// This next line prevents the user from quitting the game while saving.
// This is required for Nintendo Switch Guideline 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
if (CheckPathNotFound(filename))
return false;
nn.Result result;
result = nn.fs.File.Delete(filename);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.Delete 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
#if UNITY_SWITCH && !UNITY_EDITOR
// End preventing the user from quitting the game while saving.
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
return CommitSave();
#endif
}
public AxiNSWait_DeletePathFile DeletePathFileAsync(string filename)
{
var wait = new AxiNSWait_DeletePathFile(filename);
AxiNS.instance.wait.AddWait(wait);
return wait;
}
public bool DeletePathDir(string filename)
{
#if !UNITY_SWITCH
return false;
#else
#if UNITY_SWITCH && !UNITY_EDITOR
// This next line prevents the user from quitting the game while saving.
// This is required for Nintendo Switch Guideline 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
if (CheckPathNotFound(filename))
return false;
nn.Result result;
result = nn.fs.Directory.Delete(filename);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.Delete 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
#if UNITY_SWITCH && !UNITY_EDITOR
// End preventing the user from quitting the game while saving.
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
return CommitSave();
#endif
}
public AxiNSWait_DeletePathDir DeletePathDirAsync(string filename)
{
var wait = new AxiNSWait_DeletePathDir(filename);
AxiNS.instance.wait.AddWait(wait);
return wait;
}
public bool DeletePathDirRecursively(string filename)
{
#if !UNITY_SWITCH
return false;
#else
#if UNITY_SWITCH && !UNITY_EDITOR
// This next line prevents the user from quitting the game while saving.
// This is required for Nintendo Switch Guideline 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
if (CheckPathNotFound(filename))
return false;
nn.Result result;
result = nn.fs.Directory.DeleteRecursively(filename);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.Recursively 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
#if UNITY_SWITCH && !UNITY_EDITOR
// End preventing the user from quitting the game while saving.
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
return CommitSave();
#endif
}
public AxiNSWait_DeletePathDirRecursively DeletePathDirRecursivelyAsync(string filename)
{
var wait = new AxiNSWait_DeletePathDirRecursively(filename);
AxiNS.instance.wait.AddWait(wait);
return wait;
}
/// <summary>
/// 递归删除目录
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
public bool DeleteRecursivelyPathDir(string filename)
{
#if !UNITY_SWITCH
return false;
#else
#if UNITY_SWITCH && !UNITY_EDITOR
// This next line prevents the user from quitting the game while saving.
// This is required for Nintendo Switch Guideline 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
if (CheckPathNotFound(filename))
return false;
nn.Result result;
result = nn.fs.Directory.DeleteRecursively(filename);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.DeleteRecursively 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
#if UNITY_SWITCH && !UNITY_EDITOR
// End preventing the user from quitting the game while saving.
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
return CommitSave();
#endif
}
/// <summary>
/// 递归删除情况
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
public bool CleanRecursivelyPathDir(string filename)
{
#if !UNITY_SWITCH
return false;
#else
#if UNITY_SWITCH && !UNITY_EDITOR
// This next line prevents the user from quitting the game while saving.
// This is required for Nintendo Switch Guideline 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
if (CheckPathNotFound(filename))
return false;
nn.Result result;
result = nn.fs.Directory.CleanRecursively(filename);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.DeleteRecursively 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
#if UNITY_SWITCH && !UNITY_EDITOR
// End preventing the user from quitting the game while saving.
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
return CommitSave();
#endif
}
public bool RenameDir(string oldpath, string newpath)
{
#if !UNITY_SWITCH
return false;
#else
#if UNITY_SWITCH && !UNITY_EDITOR
// This next line prevents the user from quitting the game while saving.
// This is required for Nintendo Switch Guideline 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
if (CheckPathNotFound(oldpath))
return false;
nn.Result result;
result = nn.fs.Directory.Rename(oldpath, newpath);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.Rename 失败 {oldpath} to {newpath} : result=>{result.GetErrorInfo()}");
return false;
}
#if UNITY_SWITCH && !UNITY_EDITOR
// End preventing the user from quitting the game while saving.
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
return CommitSave();
#endif
}
bool EnsureParentDirectory(string filePath, bool bAutoCreateDir = true)
{
#if !UNITY_SWITCH
return false;
#else
// 参数校验
if (string.IsNullOrEmpty(filePath))
{
UnityEngine.Debug.LogError($"无效参数filePath={filePath}");
return false;
}
// 提取路径前缀(如 save:/、sd:/
int prefixEndIndex = filePath.IndexOf(":/");
if (prefixEndIndex == -1)
{
UnityEngine.Debug.LogError($"文件路径 {filePath} 格式无效,未找到 ':/' 前缀");
return false;
}
string pathPrefix = filePath.Substring(0, prefixEndIndex + 2); // 提取前缀,例如 "save:/"
string relativePath = filePath.Substring(prefixEndIndex + 2); // 移除前缀,得到相对路径
// 检查挂载状态
if (!IsMountPointAccessible(pathPrefix))
{
UnityEngine.Debug.LogError($"挂载点 {pathPrefix} 未挂载,无法操作路径 {filePath}");
return false;
}
// 提取父目录路径
string directoryPath = System.IO.Path.GetDirectoryName(relativePath); // 获取父目录相对路径
if (string.IsNullOrEmpty(directoryPath))
{
UnityEngine.Debug.Log($"文件路径 {filePath} 无需创建父目录(位于根目录)");
return true; // 根目录无需创建
}
string fullDirectoryPath = $"{pathPrefix}{directoryPath}"; // 拼接完整父目录路径
UnityEngine.Debug.Log($"检查父目录: {fullDirectoryPath}");
// 检查路径是否存在及其类型
nn.fs.EntryType entryType = 0;
nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, fullDirectoryPath);
if (!result.IsSuccess() && nn.fs.FileSystem.ResultPathNotFound.Includes(result))
{
if (bAutoCreateDir)
{
//List<string> NeedCreateList = new List<string>();
//NeedCreateList.Add(fullDirectoryPath);
//nn.fs.EntryType entryTypeLoop = 0;
//nn.Result resultloop;
//string NodeLoop = fullDirectoryPath;
//while (!NodeLoop.EndsWith(":/"))
//{
// NodeLoop = System.IO.Path.GetDirectoryName(NodeLoop);
// if (NodeLoop.EndsWith(":/"))
// break;
// resultloop = nn.fs.FileSystem.GetEntryType(ref entryTypeLoop, NodeLoop);
// if (!resultloop.IsSuccess() && nn.fs.FileSystem.ResultPathNotFound.Includes(resultloop))
// {
// NeedCreateList.Add(NodeLoop);
// }
//}
//for(int i = NeedCreateList.Count - 1; i >= 0; i--)
//{
// string dirToCreate = NeedCreateList[i];
// // 路径不存在,尝试创建
// UnityEngine.Debug.Log($"父目录 {dirToCreate} 不存在,尝试创建 (判断依据 result=>{result.ToString()})");
// resultloop = nn.fs.Directory.Create(dirToCreate);
// if (!resultloop.IsSuccess())
// {
// UnityEngine.Debug.LogError($"创建父目录失败: {resultloop.GetErrorInfo()}");
// return false;
// }
// UnityEngine.Debug.Log($"父目录 {dirToCreate} 创建成功");
//}
//return true;
// 路径不存在,尝试创建
UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 不存在,尝试创建 (判断依据 result=>{result.ToString()})");
result = nn.fs.Directory.Create(fullDirectoryPath);
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"创建父目录失败: {result.GetErrorInfo()}");
return false;
}
UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 创建成功");
return true;
}
return false;
}
else if (result.IsSuccess() && entryType != nn.fs.EntryType.Directory)
{
// 路径存在,但不是目录
UnityEngine.Debug.LogError($"路径 {fullDirectoryPath} 已存在,但不是目录");
return false;
}
else if (!result.IsSuccess())
{
// 其他错误
UnityEngine.Debug.LogError($"检查父目录失败: {result.GetErrorInfo()}");
return false;
}
// 路径存在且是目录
UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 已存在且有效");
return true;
#endif
}
/// <summary>
/// 检查指定挂载点是否可访问
/// </summary>
/// <param name="pathPrefix">路径前缀,例如 "save:/" 或 "sd:/"</param>
/// <returns>挂载点是否可访问</returns>
bool IsMountPointAccessible(string pathPrefix)
{
#if !UNITY_SWITCH
return false;
#else
if (string.IsNullOrEmpty(pathPrefix))
{
UnityEngine.Debug.LogError($"无效挂载点: {pathPrefix}");
return false;
}
// 根据前缀判断挂载点类型并检查挂载状态
if (pathPrefix == $"{save_name}:/")
{
if (!AxiNS.instance.mount.SaveIsMount)
{
UnityEngine.Debug.LogError($"{save_name}:/ 未挂载");
return false;
}
return true;
}
else if (pathPrefix == "sd:/")
{
long freeSpace = 0;
// 检查 SD 卡挂载状态(示例,需根据实际实现调整)
nn.Result result = nn.fs.FileSystem.GetFreeSpaceSize(ref freeSpace, "sd:/");
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"sd:/ 未挂载或无法访问: {result.GetErrorInfo()}");
return false;
}
return true;
}
else
{
UnityEngine.Debug.LogWarning($"未知挂载点 {pathPrefix},假定已挂载");
return true; // 其他挂载点需根据实际需求实现
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d910a015a6b6561418bdff7f2c48cffa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,41 @@
using System;
using UnityEngine;
public class AxiNSMono : MonoBehaviour
{
Action act;
float waittime;
float lastinvokeTime;
public static void SetInvoke(Action _act, int _waitsec)
{
GameObject gobj = GameObject.Find($"[{nameof(AxiNSMono)}]");
if (gobj == null)
{
gobj = new GameObject();
gobj.name = $"[{nameof(AxiNSMono)}]";
GameObject.DontDestroyOnLoad(gobj);
}
AxiNSMono com = gobj.GetComponent<AxiNSMono>();
if (com == null)
{
com = gobj.AddComponent<AxiNSMono>();
}
com.act = _act;
com.waittime = _waitsec;
}
public void OnEnable()
{
Debug.Log("AxiNSMono Enable");
}
public void Update()
{
if (Time.time - lastinvokeTime < waittime)
return;
lastinvokeTime = Time.time;
if (act != null)
act.Invoke();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 23d5745b8989af04d8a871b5c7b65d50
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,134 @@
#if UNITY_SWITCH
using nn.account;
#endif
public class AxiNSMount
{
static bool bInMount = false;
internal static string m_SaveMountName;
static bool bInSdCardMount = false;
internal static string m_SdCardMountName;
static bool bInSdCardDebugMount = false;
internal static string m_SdCardDebugMountName;
public bool SaveIsMount => bInMount;
public string SaveMountName
{
get
{
if (!bInMount)
return string.Empty;
else
return m_SaveMountName;
}
}
#if UNITY_SWITCH
public bool MountSave(Uid userId, string mountName = "save")
{
if (bInMount)
return true;
if (!nn.fs.SaveData.IsExisting(userId))
{
UnityEngine.Debug.LogError($"{userId.ToString()}存档不存在!");
return false;
}
UnityEngine.Debug.Log($"{userId.ToString()}存档确保存在!");
nn.Result result;
result = nn.fs.SaveData.Mount(mountName, userId);
//result.abortUnlessSuccess();
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"MountSave->挂载{mountName}:/ 失败: " + result.ToString());
return false;
}
UnityEngine.Debug.Log($"MountSave->挂载{mountName}:/ 成功 ");
m_SaveMountName = mountName;
bInMount = true;
return true;
}
#endif
public bool MountSDForDebug(string mountName = "dbgsd")
{
#if !UNITY_SWITCH
return false;
#else
if (bInSdCardDebugMount)
return true;
nn.Result result;
result = nn.fs.SdCard.MountForDebug(mountName);
//result.abortUnlessSuccess();
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"nn_fs_MountSdCardForDebug->挂载{mountName}:/ 失败: " + result.ToString());
return false;
}
UnityEngine.Debug.Log($"nn_fs_MountSdCardForDebug->挂载{mountName}:/ 成功 ");
m_SdCardDebugMountName = mountName;
bInSdCardDebugMount = true;
return true;
#endif
}
public bool MountSD(string mountName = "sd")
{
#if !UNITY_SWITCH
return false;
#else
if (bInSdCardMount)
return true;
nn.Result result;
result = AxiNSSDCard.Mount(mountName);
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"nn_fs_MountSdCard->挂载{mountName}:/ 失败: " + result.ToString());
return false;
}
UnityEngine.Debug.Log($"nn_fs_MountSdCard->挂载{mountName}:/ 成功 ");
m_SdCardMountName = mountName;
bInSdCardMount = true;
return true;
#endif
}
public void UnmountSave()
{
#if UNITY_SWITCH
if (!bInMount)
{
UnityEngine.Debug.LogError($"{m_SaveMountName}:/ 没有被挂载,无需卸载");
return;
}
nn.fs.FileSystem.Unmount(m_SaveMountName);
UnityEngine.Debug.LogError($"UnmountSaveForDebufa->已卸载{m_SaveMountName}:/ ");
bInMount = false;
#endif
}
public void UnmountSDCardForDebug()
{
#if UNITY_SWITCH
if (!bInSdCardDebugMount)
{
UnityEngine.Debug.LogError($"{m_SdCardDebugMountName}:/ 没有被挂载,无需卸载");
return;
}
nn.fs.FileSystem.Unmount(m_SdCardDebugMountName);
UnityEngine.Debug.LogError($"UnmountSDCardForDebug->已卸载{m_SdCardDebugMountName}:/ ");
bInSdCardDebugMount = false;
#endif
}
public void UnmountSDCard()
{
#if UNITY_SWITCH
if (!bInSdCardMount)
{
UnityEngine.Debug.LogError($"{m_SdCardMountName}:/ 没有被挂载,无需卸载");
return;
}
nn.fs.FileSystem.Unmount(m_SdCardMountName);
UnityEngine.Debug.LogError($"UnmountSDCard->已卸载{m_SdCardMountName}:/ ");
bInSdCardMount = false;
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 48826c5dc8959ff4db8c6a51b6568bb7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,23 @@
#if UNITY_SWITCH
using nn.account;
#endif
public class AxiNSSDCard
{
#if UNITY_SWITCH
#if DEVELOPMENT_BUILD || NN_FS_SD_CARD_FOR_DEBUG_ENABLE
[DllImport(Nn.DllName,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "nn_fs_MountSdCard")]
public static extern nn.Result Mount(string name);
#else
public static nn.Result Mount(string name)
{
return new nn.Result();
}
#endif
#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 21fa04ba4da10d74aafd65dd138478b7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,106 @@
#if UNITY_SWITCH
using nn.account;
#endif
public class AxiNSUser
{
bool m_bInit = false;
bool m_bGotOpenPreselectedUser = false;
#if UNITY_SWITCH
Uid m_UserId;
nn.account.UserHandle mUserHandle;
nn.account.Nickname m_NickName;
#endif
#region
#if UNITY_SWITCH
public bool GetUserID(out Uid uid)
{
InitPreselectedUserInfo();
if (!m_bGotOpenPreselectedUser)
{
uid = Uid.Invalid;
return false;
}
uid = m_UserId;
return true;
}
#endif
public bool GetNickName(out string NickName)
{
#if !UNITY_SWITCH
NickName = "";
return true;
#else
InitPreselectedUserInfo();
if (!m_bGotOpenPreselectedUser)
{
NickName = string.Empty;
return false;
}
NickName = m_NickName.ToString();
return true;
#endif
}
#endregion
/// <summary>
/// 初始化Account模块儿
/// </summary>
void InitNSAccount()
{
#if UNITY_SWITCH
if (m_bInit)
return;
//必须先初始化NS的Account 不然调用即崩
nn.account.Account.Initialize();
m_bInit = true;
#endif
}
/// <summary>
/// 获取预选用户
/// </summary>
void InitPreselectedUserInfo()
{
#if UNITY_SWITCH
if (m_bGotOpenPreselectedUser)
return;
InitNSAccount();
nn.Result result;
mUserHandle = new nn.account.UserHandle();
if (!nn.account.Account.TryOpenPreselectedUser(ref mUserHandle))
{
UnityEngine.Debug.LogError("打开预选的用户失败.");
return;
}
UnityEngine.Debug.Log("打开预选用户成功.");
result = nn.account.Account.GetUserId(ref m_UserId, mUserHandle);
//result.abortUnlessSuccess();
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"GetUserId失败: {result.ToString()}");
return;
}
if (m_UserId == Uid.Invalid)
{
UnityEngine.Debug.LogError("无法获取用户 ID");
return;
}
UnityEngine.Debug.Log($"获取用户 ID:{m_UserId.ToString()}");
result = nn.account.Account.GetNickname(ref m_NickName, m_UserId);
//result.abortUnlessSuccess();
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"GetNickname失败: {result.ToString()}");
return;
}
UnityEngine.Debug.Log($"获取用户 NickName ID:{m_NickName.ToString()}");
m_bGotOpenPreselectedUser = true;
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 95c2e164c69c6cc4887a194d6eba0cc2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e28ed9d2fb16c7f42b28cafb6a2ce0ac
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,134 @@
public abstract class AxiNSWaitBase : UnityEngine.CustomYieldInstruction
{
protected bool IsDone;
public abstract void Invoke();
public string errmsg = string.Empty;
public AxiNSWaitBase()
{
this.IsDone = false;
}
public void SetDone()
{
this.IsDone = true;
}
~AxiNSWaitBase()
{
}
public override bool keepWaiting
{
get { return !IsDone; }
}
}
public struct S_NSWAIT_PathWithBytes
{
public string filePath;
public byte[] data;
}
public class AxiNSWait_FileToSaveWithCreate : AxiNSWaitBase
{
S_NSWAIT_PathWithBytes req;
public bool result;
public AxiNSWait_FileToSaveWithCreate(string filePath, byte[] data)
{
req = new S_NSWAIT_PathWithBytes() { filePath = filePath, data = data };
}
public override void Invoke()
{
result = AxiNS.instance.io.FileToSaveWithCreate(req.filePath, req.data);
}
}
public struct S_NSWAIT_PathWithMS
{
public string filePath;
public System.IO.MemoryStream ms;
}
public class AxiNSWait_FileToSaveByMSWithCreate : AxiNSWaitBase
{
S_NSWAIT_PathWithMS req;
public bool result;
public AxiNSWait_FileToSaveByMSWithCreate(string filePath, System.IO.MemoryStream ms)
{
req = new S_NSWAIT_PathWithMS() { filePath = filePath, ms = ms };
}
public override void Invoke()
{
result = AxiNS.instance.io.FileToSaveWithCreate(req.filePath, req.ms);
}
}
public struct S_NSWAIT_Path
{
public string filePath;
}
public class AxiNSWait_LoadSwitchDataFile : AxiNSWaitBase
{
S_NSWAIT_Path req;
public bool result;
public byte[] outputData;
public AxiNSWait_LoadSwitchDataFile(string filePath)
{
req = new S_NSWAIT_Path() { filePath = filePath};
}
public override void Invoke()
{
result = AxiNS.instance.io.LoadSwitchDataFile(req.filePath, out outputData);
}
}
public class AxiNSWait_DeletePathFile : AxiNSWaitBase
{
S_NSWAIT_Path req;
public bool result;
public AxiNSWait_DeletePathFile(string filePath)
{
req = new S_NSWAIT_Path() { filePath = filePath };
}
public override void Invoke()
{
result = AxiNS.instance.io.DeletePathFile(req.filePath);
}
}
public class AxiNSWait_DeletePathDir : AxiNSWaitBase
{
S_NSWAIT_Path req;
public bool result;
public AxiNSWait_DeletePathDir(string filePath)
{
req = new S_NSWAIT_Path() { filePath = filePath };
}
public override void Invoke()
{
result = AxiNS.instance.io.DeletePathDir(req.filePath);
}
}
public class AxiNSWait_DeletePathDirRecursively : AxiNSWaitBase
{
S_NSWAIT_Path req;
public bool result;
public AxiNSWait_DeletePathDirRecursively(string filePath)
{
req = new S_NSWAIT_Path() { filePath = filePath };
}
public override void Invoke()
{
result = AxiNS.instance.io.DeletePathDirRecursively(req.filePath);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46a1a776d2f9dba49b9641d8e0976861
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Threading;
public class AxiNSWaitHandle
{
static Queue<AxiNSWaitBase> m_QueueReady = new Queue<AxiNSWaitBase>();
static Queue<AxiNSWaitBase> m_QueueWork = new Queue<AxiNSWaitBase>();
public void AddWait(AxiNSWaitBase wait)
{
lock (m_QueueReady)
{
m_QueueReady.Enqueue(wait);
}
if (AxiNS.usedmultithreading)
{
InitInternalThread();
autoEvent.Set();
}
else
{
InitMonoInit();
}
}
#region 线
static AutoResetEvent autoEvent = new AutoResetEvent(false);
static Thread waitThread = new Thread(Loop);
static bool bSingleInit = false;
static void InitInternalThread()
{
if (bSingleInit) return;
waitThread.Start();
bSingleInit = true;
}
static void Loop()
{
while (autoEvent.WaitOne())
{
Do();
}
}
#endregion
#region 线
static bool bMonoInit = false;
static void InitMonoInit()
{
if (bMonoInit) return;
AxiNSMono.SetInvoke(Do,15);
bMonoInit = true;
}
#endregion
static void Do()
{
lock (m_QueueReady)
{
while (m_QueueReady.Count > 0)
{
m_QueueWork.Enqueue(m_QueueReady.Dequeue());
}
}
while (m_QueueWork.Count > 0)
{
AxiNSWaitBase wait = m_QueueWork.Dequeue();
try
{
wait.Invoke();
}
catch (Exception ex)
{
wait.errmsg = ex.ToString();
UnityEngine.Debug.Log(ex.ToString());
}
wait.SetDone();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5e36180ba1c4a8f4db3ceed533a43999
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 86a02c697fd26264cb5ee552b582449b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3 @@
{
"name": "AxiReplay"
}

View File

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

View File

@ -0,0 +1,71 @@
using System;
using System.Diagnostics;
namespace AxiReplay
{
public partial class FrameProfiler
{
private int m_headFrame;
private int m_cacheCount;
private int m_targetFrameRate;
private RingBuffer<double> m_timePoints;
private double m_lastTime;
private Stopwatch sw;
public void InputHead(int headFrame)
{
m_headFrame = headFrame;
var currentTimeMs = GetCurrTime();
if (m_timePoints.Available() == 60)
CalcCacheCount();
m_timePoints.Write(currentTimeMs - m_lastTime);
m_lastTime = currentTimeMs;
}
public void Reset(int targetFrameRate = 60)
{
if (sw != null) sw.Stop();
sw = Stopwatch.StartNew();
m_timePoints = new RingBuffer<double>(targetFrameRate);
m_lastTime = 0;
m_targetFrameRate = targetFrameRate;
}
/// <summary>
/// 临时方法(暂行)
/// </summary>
/// <param name="mRemoteForwardCount"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public int TempFrameCount(int mRemoteForwardCount)
{
if (mRemoteForwardCount == 0)
return 0;
if (mRemoteForwardCount < 5)
return 1;
else
return (int)Math.Ceiling(mRemoteForwardCount / 5f);
}
void CalcCacheCount()
{
double deltaMax = 0;
while (m_timePoints.TryRead(out double delta))
{
deltaMax = Math.Max(deltaMax, delta);
}
int minCacheCount = (int)Math.Ceiling(deltaMax * m_targetFrameRate);
m_cacheCount = minCacheCount;
}
double GetCurrTime()
{
if (sw == null) return 0;
return sw.Elapsed.TotalMilliseconds;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cfd4511a83ff0bf4ea7615b87e7d09aa

View File

@ -0,0 +1,11 @@
using System;
namespace AxiReplay
{
internal interface IReplayReader : IDisposable
{
bool NextFrame(out ReplayStep data);
bool TakeFrame(int addFrame, out ReplayStep data);
bool NextFramebyFrameIdx(int FrameID, out ReplayStep data);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 896ff07370157db46b612575616020ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,12 @@
using System;
namespace AxiReplay
{
internal interface IReplayWriter : IDisposable
{
void NextFrame(UInt64 frameInput);
void NextFramebyFrameIdx(int FrameID, UInt64 frameInput);
void TakeFrame(int addFrame, UInt64 frameInput);
void SaveData(string path, bool bNeedDump = false, string dumpFilePath = null);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6a8fcda365e5a7f428f88bc130eb913b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,78 @@
using System.Threading;
namespace AxiReplay
{
public partial class FrameProfiler
{
internal class RingBuffer<T>
{
private readonly T[] buffer;
private readonly int capacity;
private int writePos;
private int readPos;
private int count;
public RingBuffer(int capacity)
{
this.capacity = capacity;
this.buffer = new T[capacity];
this.writePos = 0;
this.readPos = 0;
this.count = 0;
}
public void Write(T item)
{
int localWritePos;
int localReadPos;
do
{
localWritePos = Volatile.Read(ref writePos);
localReadPos = Volatile.Read(ref readPos);
int nextWritePos = (localWritePos + 1) % capacity;
if (nextWritePos == localReadPos)
{
// 缓冲区已满,覆盖最旧的未读数据
Interlocked.CompareExchange(ref readPos, (localReadPos + 1) % capacity, localReadPos);
}
}
while (Interlocked.CompareExchange(ref writePos, (localWritePos + 1) % capacity, localWritePos) != localWritePos);
buffer[localWritePos] = item;
Interlocked.Increment(ref count);
}
public bool TryRead(out T item)
{
item = default(T);
int localReadPos;
int localWritePos;
do
{
localReadPos = Volatile.Read(ref readPos);
localWritePos = Volatile.Read(ref writePos);
if (localReadPos == localWritePos)
{
return false; // 缓冲区为空
}
}
while (Interlocked.CompareExchange(ref readPos, (localReadPos + 1) % capacity, localReadPos) != localReadPos);
item = buffer[localReadPos];
Interlocked.Decrement(ref count);
return true;
}
public int Available()
{
return Volatile.Read(ref count);
}
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b17d83b69bd47094594c32fcff9715f4

View File

@ -0,0 +1,161 @@
using System.Collections.Generic;
using UnityEngine;
namespace AxiReplay
{
public class NetReplay
{
/// <summary>
/// 客户端当前帧
/// </summary>
public int mCurrClientFrameIdx = 0;
/// <summary>
/// 服务器远端当前帧
/// </summary>
public int mRemoteFrameIdx { get; private set; }
/// <summary>
/// 服务器远端当前提前量
/// </summary>
public int mRemoteForwardCount { get; private set; }
/// <summary>
/// Remote 2 Client Frame Gap
/// </summary>
public int mDiffFrameCount => mRemoteFrameIdx - mCurrClientFrameIdx;
/// <summary>
/// 网络数据队列
/// </summary>
public Queue<ReplayStep> mNetReplayQueue { get; private set; } = new Queue<ReplayStep>();
/// <summary>
/// 当前数据
/// </summary>
ReplayStep mCurrReplay;
/// <summary>
/// 下一个数据数据
/// </summary>
ReplayStep mNextReplay;
FrameProfiler frameProfiler = new FrameProfiler();
bool bNetInit = false;
public NetReplay()
{
ResetData();
}
public void ResetData()
{
mNetReplayQueue.Clear();
mCurrReplay = default(ReplayStep);
mCurrReplay.FrameStartID = int.MinValue;
bNetInit = false;
frameProfiler.Reset();
}
public void InData(ReplayStep inputData, int ServerFrameIdx, uint ServerForwardCount)
{
mRemoteForwardCount = (int)ServerForwardCount;
mNetReplayQueue.Enqueue(inputData);
Debug.Log($"InData=>{inputData.FrameStartID} QCount = >{mNetReplayQueue.Count}");
mRemoteFrameIdx = inputData.FrameStartID;
if (!bNetInit)
{
bNetInit = true;
mNextReplay = mNetReplayQueue.Dequeue();
}
frameProfiler.InputHead(inputData.FrameStartID);
}
public bool TryGetNextFrame(int targetFrame, bool indirectGet, out ReplayStep data, out int frameDiff, out bool inputDiff)
{
if (!bNetInit)
{
data = default(ReplayStep);
frameDiff = default(int);
inputDiff = false;
return false;
}
return TakeFrameToTargetFrame(targetFrame, indirectGet, out data, out frameDiff, out inputDiff);
}
bool checkCanGetFrame(int targetFrame, bool indirectGet)
{
if (indirectGet)
{
return targetFrame == mNextReplay.FrameStartID && targetFrame <= mRemoteFrameIdx;
}
else
{
return targetFrame == mNextReplay.FrameStartID && targetFrame <= mRemoteFrameIdx && mNetReplayQueue.Count >= frameProfiler.TempFrameCount(mRemoteForwardCount);
}
}
bool TakeFrameToTargetFrame(int targetFrame, bool indirectGet, out ReplayStep data, out int bFrameDiff, out bool inputDiff)
{
bool result;
inputDiff = false;
if (checkCanGetFrame(targetFrame, indirectGet))
{
//当前帧追加
mCurrClientFrameIdx = targetFrame;
ulong oldInput = mCurrReplay.InPut;
mCurrReplay = mNextReplay;
if (oldInput != mCurrReplay.InPut)
inputDiff = true;
mNextReplay = mNetReplayQueue.Dequeue();
result = true;
}
else
result = false;
bFrameDiff = mRemoteFrameIdx - mCurrClientFrameIdx;
data = mCurrReplay;
return result;
}
public int GetSkipFrameCount()
{
if (!bNetInit)
return 0;
//本地队列差异高于服务器提前量的值
int moreNum = mDiffFrameCount - mRemoteForwardCount;
//if (mDiffFrameCount < 0 || mDiffFrameCount > 10000)
// return 0;
////游戏刚开始的一小段时间,直接追满
//if (mCurrClientFrameIdx < 60)
// return moreNum;
int skip = 0;
if (mDiffFrameCount > short.MaxValue) skip = 0;
else if (moreNum <= mRemoteForwardCount) skip = 0;
else if (moreNum <= mRemoteForwardCount + 2) skip = 0;
else if (moreNum <= mRemoteForwardCount + 5) skip = 1;
else if (moreNum <= mRemoteForwardCount + 6) skip = 2;
else if (moreNum <= mRemoteForwardCount + 20) skip = moreNum / 2; //20帧以内平滑跳帧数
else skip = moreNum;//完全追上
return skip;
//int skip = 0;
//if (mDiffFrameCount > short.MaxValue) skip = 0;
//else if (moreNum <= 1) skip = 0;
//else if (moreNum <= 3) skip = 2;
//else if (moreNum <= 6) skip = 2;
//else if (moreNum <= 20) skip = moreNum / 2; //20帧以内平滑跳帧数
//else skip = moreNum;//完全追上
//return skip;
//var frameGap = mDiffFrameCount;
//if (frameGap > 10000) return 0;
//if (frameGap <= 2) skip = 0;
//if (frameGap > 2 && frameGap < 6) skip = 1 + 1;
//else if (frameGap > 7 && frameGap < 12) skip = 2 + 1;
//else if (frameGap > 13 && frameGap < 20) skip = 3 + 1;
//else skip = frameGap - 2;
//return skip;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 452b58ff73a0853449845fd9e1134cc2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,101 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace AxiReplay
{
[StructLayout(LayoutKind.Explicit, Size = 44)]
public struct ReplayHandler
{
[FieldOffset(0)]
public int Format;
[FieldOffset(sizeof(int) * 1)]
public int RomID;
[FieldOffset(sizeof(int) * 2)]
public int RomType;
[FieldOffset(sizeof(int) * 3)]
public int DataOffset;
[FieldOffset(sizeof(int) * 4)]
public int TitleOffset;
[FieldOffset(sizeof(int) * 5)]
public int NoteOffset;
[FieldOffset(sizeof(int) * 6)]
public int AllFrame;
[FieldOffset(sizeof(int) * 7)]
public int AllTime;
[FieldOffset(sizeof(int) * 8)]
public int SingleLenght;
[FieldOffset(sizeof(int) * 9)]
public long CreateTime;
}
[StructLayout(LayoutKind.Explicit)]
public struct ReplayStep
{
[FieldOffset(0)]
public UInt64 All64Data;
[FieldOffset(0)]
public Int32 FrameStartID;
[FieldOffset(4)]
public UInt64 InPut;
}
public static class ReplayData
{
public static int HandlerLenght = sizeof(int) * 9 + sizeof(long);
public enum ReplayFormat : byte
{
None = 0,
FM32IPBYTE,
FM32IP16,
FM32IP32,
FM32IP64,
}
public static void GetStringByteData(string str, out byte[] data, out int lenghtWithEnd, Encoding encoding)
{
data = encoding.GetBytes(str);
lenghtWithEnd = data.Length + 1;
}
public static byte[] GetHandlerData(ReplayHandler replayhandler)
{
int size = Marshal.SizeOf(typeof(ReplayHandler));
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(replayhandler, ptr, false);
Marshal.Copy(ptr, arr, 0, size);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return arr;
}
public static ReplayHandler GetReplayHandlerFromData(byte[] data)
{
if (data == null || data.Length < ReplayData.HandlerLenght)
{
throw new ArgumentException("Invalid data length or null data.");
}
IntPtr ptr = Marshal.AllocHGlobal(ReplayData.HandlerLenght);
try
{
// 将byte数组的内容复制到非托管内存中
Marshal.Copy(data, 0, ptr, ReplayData.HandlerLenght);
// 从非托管内存将内容转换回ReplayHandler结构体
return (ReplayHandler)Marshal.PtrToStructure(ptr, typeof(ReplayHandler));
}
finally
{
// 释放非托管内存
Marshal.FreeHGlobal(ptr);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 42df5a138f4f4ae488815f35d8e748da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using static AxiReplay.ReplayData;
namespace AxiReplay
{
public class ReplayReader : IReplayReader
{
public ReplayData.ReplayFormat mFormat { get; private set; }
public Encoding TexEncoding { get; private set; }
ReplayHandler handler;
string mTitle;
string mNote;
int mAllFrame;
int mAllTime;
long mData;
int mSingleInputLenght;
int mSingleDataLenght;
FileStream mStream;
BinaryReader mBinaryReader;
int mCurrFrame = -1;
byte[] mNextOutbytes;
public ReplayStep currStep;
public ReplayStep nextStep;
bool bEnd;
List<string> dbgList = new List<string>();
bool bdbg = false;
string dumpPath;
public ReplayReader(string path, bool bWithDump = false, string dumppath = null)
{
dbgList.Clear();
bdbg = bWithDump;
dumpPath = dumppath;
mStream = new FileStream(path, FileMode.Open, FileAccess.Read);
mBinaryReader = new BinaryReader(mStream);
byte[] Outbytes;
Outbytes = mBinaryReader.ReadBytes(ReplayData.HandlerLenght);
handler = ReplayData.GetReplayHandlerFromData(Outbytes);
mFormat = (ReplayFormat)handler.Format;
switch (mFormat)
{
case ReplayData.ReplayFormat.FM32IP64: mSingleInputLenght = sizeof(UInt64); break;
case ReplayData.ReplayFormat.FM32IP32: mSingleInputLenght = sizeof(UInt32); break;
case ReplayData.ReplayFormat.FM32IP16: mSingleInputLenght = sizeof(UInt16); break;
case ReplayData.ReplayFormat.FM32IPBYTE: mSingleInputLenght = sizeof(byte); break;
}
//Frame+Lenght
mSingleDataLenght = (sizeof(UInt32)) + mSingleInputLenght;
nextStep = new ReplayStep();
nextStep.FrameStartID = -1;
bEnd = false;
dbgList.Add($"Format => {handler.Format}");
dbgList.Add($"DataOffset => {handler.DataOffset}");
dbgList.Add($"CreateTime => {handler.CreateTime}");
dbgList.Add($"AllFrame => {handler.AllFrame}");
dbgList.Add($"SingleLenght => {handler.SingleLenght}");
mNextOutbytes = new byte[mSingleDataLenght];
if (bWithDump)
{
int TestFrameIdx = -1;
while (!bEnd)
{
UpdateNextFrame(TestFrameIdx++);
}
File.WriteAllLines(dumppath, dbgList);
}
else
{
UpdateNextFrame(0);
}
}
void UpdateNextFrame(int targetFrame)
{
//如果已经超过
while (targetFrame >= nextStep.FrameStartID)
{
if (nextStep.FrameStartID >= handler.AllFrame)
{
bEnd = true;
break;
}
mBinaryReader.Read(mNextOutbytes, 0, mSingleDataLenght);
switch (mFormat)
{
case ReplayFormat.FM32IP64:
{
nextStep.FrameStartID = BitConverter.ToInt32(mNextOutbytes, 0);
nextStep.InPut = BitConverter.ToUInt64(mNextOutbytes, sizeof(UInt32));
}
break;
case ReplayFormat.FM32IP32:
{
nextStep.All64Data = BitConverter.ToUInt64(mNextOutbytes, 0);
}
break;
case ReplayFormat.FM32IP16:
{
nextStep.All64Data = BitConverter.ToUInt64(mNextOutbytes, 0);
}
break;
case ReplayFormat.FM32IPBYTE:
{
nextStep.All64Data = BitConverter.ToUInt64(mNextOutbytes, 0);
}
break;
}
dbgList.Add($"{nextStep.FrameStartID} | {nextStep.InPut}");
targetFrame++;
}
}
int byFrameIdx = 0;
/// <summary>
/// 往前推进1帧的Input(返回是否变化)
/// </summary>
public bool NextFrame(out ReplayStep data)
{
return TakeFrame(1, out data);
}
/// <summary>
/// 往前推进指定帧数量的Input (返回是否变化)
/// </summary>
/// <param name="addFrame"></param>
public bool TakeFrame(int addFrame, out ReplayStep data)
{
bool Changed = false;
mCurrFrame += addFrame;
if (mCurrFrame >= nextStep.FrameStartID)
{
Changed = currStep.InPut != nextStep.InPut;
currStep = nextStep;
data = currStep;
UpdateNextFrame(mCurrFrame);
}
else
{
data = currStep;
}
return Changed;
}
/// <summary>
/// 往前推进帧的,指定帧下标
/// </summary>
public bool NextFramebyFrameIdx(int FrameID, out ReplayStep data)
{
bool res = TakeFrame(FrameID - byFrameIdx, out data);
byFrameIdx = FrameID;
return res;
}
public void Dispose()
{
mStream.Dispose();
mBinaryReader.Dispose();
//TODO
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 66e0e18d1f5981745a3078e8460cb0e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace AxiReplay
{
public class ReplayWriter : IReplayWriter
{
public ReplayData.ReplayFormat mFormat { get; private set; }
public Encoding TexEncoding { get; private set; }
ReplayHandler handler;
string mTitle;
string mNote;
int mAllFrame;
int mAllTime;
long mData;
int mSingleInputLenght;
int mSingleDataLenght;
MemoryStream mStream;
BinaryWriter mBinaryWriter;
int mCurrFrame;
UInt64 mCurrInput;
ReplayStep wirteStep;
List<string> dbgList = new List<string>();
public ReplayWriter(string Title, string Note, ReplayData.ReplayFormat format, Encoding encoding)
{
mTitle = Title;
mNote = Note;
TexEncoding = encoding;
mFormat = format;
switch (mFormat)
{
case ReplayData.ReplayFormat.FM32IP64: mSingleInputLenght = sizeof(UInt64); break;
case ReplayData.ReplayFormat.FM32IP32: mSingleInputLenght = sizeof(UInt32); break;
case ReplayData.ReplayFormat.FM32IP16: mSingleInputLenght = sizeof(UInt16); break;
case ReplayData.ReplayFormat.FM32IPBYTE: mSingleInputLenght = sizeof(byte); break;
}
mSingleDataLenght = (sizeof(UInt32)) + mSingleInputLenght;
mStream = new MemoryStream();
mBinaryWriter = new BinaryWriter(mStream);
mCurrFrame = -1;
mCurrInput = int.MaxValue;
wirteStep = new ReplayStep();
dbgList.Clear();
}
int byFrameIdx = 0;
/// <summary>
/// 往前推进帧的,指定帧下标
/// </summary>
/// <param name="frameInput"></param>
public void NextFramebyFrameIdx(int FrameID, UInt64 frameInput)
{
TakeFrame(FrameID - byFrameIdx, frameInput);
byFrameIdx = FrameID;
}
/// <summary>
/// 往前推进1帧的Input
/// </summary>
/// <param name="frameInput"></param>
public void NextFrame(UInt64 frameInput)
{
TakeFrame(1, frameInput);
}
/// <summary>
/// 往前推进指定帧数量的Input
/// </summary>
/// <param name="frameInput"></param>
public void TakeFrame(int addFrame, UInt64 frameInput)
{
if (addFrame < 0)
{
}
mCurrFrame += addFrame;
if (mCurrInput == frameInput)
return;
mCurrInput = frameInput;
wirteStep.FrameStartID = mCurrFrame;
wirteStep.InPut = mCurrInput;
dbgList.Add($"{mCurrFrame} | {mCurrInput}");
switch (mFormat)
{
case ReplayData.ReplayFormat.FM32IP64:
mBinaryWriter.Write(wirteStep.FrameStartID);
mBinaryWriter.Write(wirteStep.InPut);
break;
case ReplayData.ReplayFormat.FM32IP32:
mBinaryWriter.Write(BitConverter.GetBytes(wirteStep.All64Data), 0, 4 + 4);
break;
case ReplayData.ReplayFormat.FM32IP16:
mBinaryWriter.Write(BitConverter.GetBytes(wirteStep.All64Data), 0, 4 + 2);
break;
case ReplayData.ReplayFormat.FM32IPBYTE:
mBinaryWriter.Write(BitConverter.GetBytes(wirteStep.All64Data), 0, 4 + 1);
break;
}
}
public void SaveData(string path, bool bWithDump = false, string dumppath = null)
{
byte[] titleData; int titleLenghtWithEnd;
ReplayData.GetStringByteData(mTitle, out titleData, out titleLenghtWithEnd, TexEncoding);
byte[] noteData; int noteLenghtWithEnd;
ReplayData.GetStringByteData(mNote, out noteData, out noteLenghtWithEnd, TexEncoding);
ReplayHandler handler = new ReplayHandler();
handler.Format = (int)this.mFormat;
handler.DataOffset = ReplayData.HandlerLenght;
handler.CreateTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
handler.AllFrame = wirteStep.FrameStartID;
handler.SingleLenght = mSingleDataLenght;
using (FileStream fs = new FileStream(path, FileMode.Create))
{
using (BinaryWriter bw = new BinaryWriter(fs))
{
//写入Handler
bw.Write(ReplayData.GetHandlerData(handler));
//写入Data
bw.Write(mStream.ToArray());
}
}
if (bWithDump)
{
List<string> temp = new List<string>();
temp.Add($"Format => {handler.Format}");
temp.Add($"DataOffset => {handler.DataOffset}");
temp.Add($"CreateTime => {handler.CreateTime}");
temp.Add($"AllFrame => {handler.AllFrame}");
temp.Add($"SingleLenght => {handler.SingleLenght}");
dbgList.InsertRange(0, temp);
File.WriteAllLines(dumppath, dbgList);
}
}
public void Dispose()
{
mStream.Dispose();
mBinaryWriter.Dispose();
//TODO
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc53a3d9a3e1749438b6ad1cef7b39bc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 38ad9fa7ab0e4e04788cecad771d86a1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: a50bd9a009c8dfc4ebd88cc8101225a7
labels:
- Tween
- Tweening
- Animation
- HOTween
- Paths
- iTween
- DFTween
- LeanTween
- Ease
- Easing
- Shake
- Punch
- 2DToolkit
- TextMeshPro
- Text
folderAsset: yes
DefaultImporter:
userData:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 34192c5e0d14aee43a0e86cc4823268a
TextScriptImporter:
userData:

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 4f007001a22b3d24dae350342c4d19c8
DefaultImporter:
userData:

View File

@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: a811bde74b26b53498b4f6d872b09b6d
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: b27f58ae5d5c33a4bb2d1f4f34bd036d
folderAsset: yes
DefaultImporter:
userData:

View File

@ -0,0 +1,144 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>DOTweenEditor</name>
</assembly>
<members>
<member name="T:DG.DOTweenEditor.EditorCompatibilityUtils">
<summary>
Contains compatibility methods taken from DemiEditor (for when DOTween is without it)
</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorCompatibilityUtils.FindObjectOfType``1(System.Boolean)">
<summary>
Warning: some versions of this method don't have the includeInactive parameter so it won't be taken into account
</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorCompatibilityUtils.FindObjectOfType(System.Type,System.Boolean)">
<summary>
Warning: some versions of this method don't have the includeInactive parameter so it won't be taken into account
</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorCompatibilityUtils.FindObjectsOfType``1(System.Boolean)">
<summary>
Warning: some versions of this method don't have the includeInactive parameter so it won't be taken into account
</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorCompatibilityUtils.FindObjectsOfType(System.Type,System.Boolean)">
<summary>
Warning: some versions of this method don't have the includeInactive parameter so it won't be taken into account
</summary>
</member>
<member name="M:DG.DOTweenEditor.DOTweenEditorPreview.Start(System.Action)">
<summary>
Starts the update loop of tween in the editor. Has no effect during playMode.
</summary>
<param name="onPreviewUpdated">Eventual callback to call after every update</param>
</member>
<member name="M:DG.DOTweenEditor.DOTweenEditorPreview.Stop(System.Boolean,System.Boolean)">
<summary>
Stops the update loop and clears the onPreviewUpdated callback.
</summary>
<param name="resetTweenTargets">If TRUE also resets the tweened objects to their original state.
Note that this works by calling Rewind on all tweens, so it will work correctly
only if you have a single tween type per object and it wasn't killed</param>
<param name="clearTweens">If TRUE also kills any cached tween</param>
</member>
<member name="M:DG.DOTweenEditor.DOTweenEditorPreview.PrepareTweenForPreview(DG.Tweening.Tween,System.Boolean,System.Boolean,System.Boolean)">
<summary>
Readies the tween for editor preview by setting its UpdateType to Manual plus eventual extra settings.
</summary>
<param name="t">The tween to ready</param>
<param name="clearCallbacks">If TRUE (recommended) removes all callbacks (OnComplete/Rewind/etc)</param>
<param name="preventAutoKill">If TRUE prevents the tween from being auto-killed at completion</param>
<param name="andPlay">If TRUE starts playing the tween immediately</param>
</member>
<member name="F:DG.DOTweenEditor.EditorVersion.Version">
<summary>Full major version + first minor version (ex: 2018.1f)</summary>
</member>
<member name="F:DG.DOTweenEditor.EditorVersion.MajorVersion">
<summary>Major version</summary>
</member>
<member name="F:DG.DOTweenEditor.EditorVersion.MinorVersion">
<summary>First minor version (ex: in 2018.1 it would be 1)</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorUtils.SetEditorTexture(UnityEngine.Texture2D,UnityEngine.FilterMode,System.Int32)">
<summary>
Checks that the given editor texture use the correct import settings,
and applies them if they're incorrect.
</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorUtils.DOTweenSetupRequired">
<summary>
Returns TRUE if setup is required
</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorUtils.AssetExists(System.String)">
<summary>
Returns TRUE if the file/directory at the given path exists.
</summary>
<param name="adbPath">Path, relative to Unity's project folder</param>
<returns></returns>
</member>
<member name="M:DG.DOTweenEditor.EditorUtils.ADBPathToFullPath(System.String)">
<summary>
Converts the given project-relative path to a full path,
with backward (\) slashes).
</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorUtils.FullPathToADBPath(System.String)">
<summary>
Converts the given full path to a path usable with AssetDatabase methods
(relative to Unity's project folder, and with the correct Unity forward (/) slashes).
</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorUtils.ConnectToSourceAsset``1(System.String,System.Boolean)">
<summary>
Connects to a <see cref="T:UnityEngine.ScriptableObject"/> asset.
If the asset already exists at the given path, loads it and returns it.
Otherwise, either returns NULL or automatically creates it before loading and returning it
(depending on the given parameters).
</summary>
<typeparam name="T">Asset type</typeparam>
<param name="adbFilePath">File path (relative to Unity's project folder)</param>
<param name="createIfMissing">If TRUE and the requested asset doesn't exist, forces its creation</param>
</member>
<member name="M:DG.DOTweenEditor.EditorUtils.GetAssemblyFilePath(System.Reflection.Assembly)">
<summary>
Full path for the given loaded assembly, assembly file included
</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorUtils.AddGlobalDefine(System.String)">
<summary>
Adds the given global define if it's not already present
</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorUtils.RemoveGlobalDefine(System.String)">
<summary>
Removes the given global define if it's present
</summary>
</member>
<member name="M:DG.DOTweenEditor.EditorUtils.HasGlobalDefine(System.String,System.Nullable{UnityEditor.BuildTargetGroup})">
<summary>
Returns TRUE if the given global define is present in all the <see cref="T:UnityEditor.BuildTargetGroup"/>
or only in the given <see cref="T:UnityEditor.BuildTargetGroup"/>, depending on passed parameters.<para/>
</summary>
<param name="id"></param>
<param name="buildTargetGroup"><see cref="T:UnityEditor.BuildTargetGroup"/>to use. Leave NULL to check in all of them.</param>
</member>
<member name="T:DG.DOTweenEditor.DOTweenDefines">
<summary>
Not used as menu item anymore, but as a utility function
</summary>
</member>
<member name="F:DG.DOTweenEditor.UnityEditorVersion.Version">
<summary>Full major version + first minor version (ex: 2018.1f)</summary>
</member>
<member name="F:DG.DOTweenEditor.UnityEditorVersion.MajorVersion">
<summary>Major version</summary>
</member>
<member name="F:DG.DOTweenEditor.UnityEditorVersion.MinorVersion">
<summary>First minor version (ex: in 2018.1 it would be 1)</summary>
</member>
</members>
</doc>

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 2e2c6224d345d9249acfa6e8ef40bb2d
TextScriptImporter:
userData:

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 8f46310a8b0a8f04a92993c37c713243
DefaultImporter:
userData:

View File

@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 45d5034162d6cf04dbe46da84fc7d074
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: 0034ebae0c2a9344e897db1160d71b6d
folderAsset: yes
DefaultImporter:
userData:

Binary file not shown.

After

(image error) Size: 1.5 KiB

Some files were not shown because too many files have changed in this diff Show More