Giter VIP home page Giter VIP logo

Comments (6)

gottyduke avatar gottyduke commented on July 18, 2024 2

大量文件的硬链接是否会影响性能

@Scighost
不需要考虑符号链接/硬链接对于性能的影响, 至少游戏内其他所有的性能开销都会比解析符号链接高, 具体可以参考Nexusmods的mod管理器Vortex, 其使用符号链接部署mod文件.

from starward.

Scighost avatar Scighost commented on July 18, 2024

坏消息是原的 StreamAssets\20527480.blk 文件在两服之间存在差异,和账号登录有关;铁也有部分文件存在差异,并且分布在不同文件夹中,所以上述仅链接 StreamAssets 的方案不可行。

考虑另一种方案,把首次安装的游戏客户端称为主客户端,同游戏不同服务器的其他客户端称为从客户端(暂且这样命名)。主客户端的安装更新流程都按照正常流程处理,从客户端安装时仅下载差异文件,其他的相同文件使用单文件硬链接的方式链接到主客户端对应的文件;从客户端禁用预下载、更新流程,在主客户端更新期间完成从客户端的更新。此方案不影响单客户端的正常使用和更新,也能和官方启动器兼容。

同样的,也有一些问题需要考虑:

  • 同上
  • 大量文件的硬链接是否会影响性能
  • 如何解决两客户端同时更新时国际服资源的网络连接不稳定的问题

铁道的本地化音频资源没有 pkg_version 给出校验信息,暂时不能实现此功能。

from starward.

starward-bot avatar starward-bot commented on July 18, 2024

@AbyssMorax deleted the following comment
published at 2024-04-04 15:16:29 +00:00
updated at 2024-04-04 15:16:29 +00:00

using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json.Linq;
using System.Diagnostics;
using System.IO.Compression;

namespace GenshinImapctStarter
{
internal class Program
{

    static void Main(string[] args)
    {
        //mklink需要管理员权限
        System.Security.Principal.WindowsIdentity identity = System.Security.Principal.WindowsIdentity.GetCurrent();
        System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(identity);
        if (!principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator))
        {
            System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
            startInfo.UseShellExecute = true;
            startInfo.WorkingDirectory = Environment.CurrentDirectory;
            startInfo.FileName = System.AppDomain.CurrentDomain.BaseDirectory + "GenshinImapctStarter.exe";
            startInfo.Verb = "runas";
            System.Diagnostics.Process.Start(startInfo);
            Environment.Exit(0);
            return;
        }

        Starter starter = new Starter();
        starter.Init().Wait();
        starter.CreateGame("E:\\ABCD\\", "global450").Wait();//测试,
    }

    public class Starter
    {
        private readonly string datapath = AppDomain.CurrentDomain.BaseDirectory + "data\\";
        private JObject jconfig = new JObject();
        public async Task Init()
        {
            if (!File.Exists(datapath + ".\\config.json"))
            {
                ChangeDefaultGamePath();
                await File.WriteAllTextAsync(datapath + ".\\config.json", this.jconfig.ToString());
            }
            this.jconfig = JObject.Parse(await File.ReadAllTextAsync(datapath + ".\\config.json"));
        }
        public void ChangeDefaultGamePath()
        {
            try
            {
                do
                {
                    Console.WriteLine("Input default game path:");
                    jconfig["game_default_path"] = Console.ReadLine();
                }
                while (!Directory.Exists((string)jconfig["game_default_path"]!));
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

        }
        public async Task CreateGame(string path, string version)
        {
            Console.WriteLine($"Create {version} Game to {path}");
            string versionpath = this.datapath + $"GameData\\{version}\\";
            JObject jversionconfig = JObject.Parse(await File.ReadAllTextAsync(versionpath + "config.json"));

            string target, link;
            if ((string)jversionconfig["game_type"]! == "China")
            {
                target = path + "YuanShen_Data\\StreamingAssets\\";
            }
            else
            {
                target = path + "GenshinImpact_Data\\StreamingAssets\\";
            }
            if ((string)this.jconfig["game_default_type"]! == "China")
            {
                link = this.jconfig["game_default_path"] + "YuanShen_Data\\StreamingAssets\\";
            }
            else
            {
                link = this.jconfig["game_default_path"] + "GenshinImpact_Data\\StreamingAssets\\";
            }
            await Ziper.Decompress(versionpath + "gamedata.zip", path, 1024 * 1024);
            this.Link(target, link);
            Console.WriteLine($"Task succeeded");
        }
        public void Link(string target, string link)
        {
            CMD cmd = new CMD();
            string[] dirlinkpaths = File.ReadAllLines(this.datapath + "dirlink.txt");
            string[] filelinkpaths = File.ReadAllLines(this.datapath + "filelink.txt");
            for (int i = 0; i < filelinkpaths.Length; i++)
            {
                string a = $"mklink \"{target + filelinkpaths[i]}\" \"{link + filelinkpaths[i]}\"";
                cmd.Run(a);
            }
            for (int i = 0; i < dirlinkpaths.Length; i++)
            {
                cmd.Run($"mklink /d \"{target + dirlinkpaths[i]}\" \"{link + dirlinkpaths[i]}\"");
            }

            cmd.Exit();
        }
        public async Task<string> GetVersion(string path)
        {
            string content = await File.ReadAllTextAsync(path + ".\\StreamingAssets\\asb_settings.json");
            JObject jsondata = JObject.Parse(content);
            return (string)jsondata["variance"]!;
        }

        public class CMD
        {
            private Process process;
            public CMD()
            {
                process = new Process();
                process.StartInfo.FileName = "cmd.exe";
                process.StartInfo.CreateNoWindow = true;
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardInput = true;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.StartInfo.Verb = "runas";
                process.Start();
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
                process.StandardInput.AutoFlush = true;
                
            }
            public void Exit()
            {
                process.StandardInput.WriteLine("exit");
                process.StandardInput.Flush();
                process.WaitForExit();
            }
            public void Run(string command)
            {
                process.StandardInput.WriteLine(command);
            }
        }

        public class Ziper
        {
            public static async Task Decompress(string zipFile, string targetPath, int bufferSize)
            {
                if (!Directory.Exists(targetPath))
                {
                    Directory.CreateDirectory(targetPath);
                }
                string currentpath = targetPath;

                ZipEntry entry;
                ZipInputStream zipinputstream = new ZipInputStream(File.OpenRead(zipFile));
                while ((entry = zipinputstream.GetNextEntry()) != null)
                {
                    if (entry.Name.Contains("/"))
                    {
                        string parentpath = entry.Name.Remove(entry.Name.LastIndexOf("/"));
                        if (!Directory.Exists(parentpath))
                        {
                            Directory.CreateDirectory(currentpath + parentpath);
                        }
                    }
                    if (entry.IsDirectory)
                    {
                        continue;
                    }
                    FileStream outputfilestream = File.Create(currentpath + entry.Name);
                    int readlength;
                    byte[] buffer = new byte[bufferSize];
                    while ((readlength = await zipinputstream.ReadAsync(buffer, 0, bufferSize)) > 0)
                    {
                        await outputfilestream.WriteAsync(buffer, 0, readlength);
                    }
                    outputfilestream.Close();
                    zipinputstream.CloseEntry();
                }
                zipinputstream.Close();
            }
        }

    }

}

}

/*已实现基本创建功能,可以正常运行,这仅是代码部分(c#测试版,还有许多其他功能未添加),本代码不是最优解,基本验证了符号链接方案的可行性
需要开发人员自制区服初始化文件,预下载仅下载主游戏区服即可,其他区服更新需要开发人员自制更新包(类似之前的资源替换包)
*/

from starward.

starward-bot avatar starward-bot commented on July 18, 2024

@AbyssMorax deleted the following comment
published at 2024-04-04 21:19:34 +00:00
updated at 2024-04-05 09:56:06 +00:00

https://github.com/AbyssMorax/GenshinImapctStarter/tree/master
已实现基本功能及命令行交互

from starward.

starward-bot avatar starward-bot commented on July 18, 2024

@AbyssMorax deleted the following comment
published at 2024-04-04 15:43:48 +00:00
updated at 2024-04-05 04:42:49 +00:00

@Scighost
问题说明(在完全使用starward情况下,不包含官方启动器):
1.StreamingAssets建议放在主游戏目录,更新主游戏时可以像原来一样,不需要额外的代码
2.游戏预下载如上一次所述,仅下载主游戏预更新包即可,其他子区服游戏更新须由开发人员自制更新包(如资源替换一样)
3.版本记录可选,建议starward一起更新主游戏和其他子区服游戏,保证所有资源版本统一
4.符号链接基于路径,所以StreamingAssets移动会影响符号链接
5.若玩家删除了主游戏,则应当选择另一个区服为主游戏,将原先的符号链接删除,并把StreamingAssets移动至后者
6.因为链接的只是相同的文件,所以每个游戏区的文件都为自己本身并与其他区服共存,StreamingAssets只占用一份,因此,若用户想要完整安装多个客户端,可以通过简单的复制粘贴完成,可以大幅缩小因国际服网络问题而下载速度慢的问题
7.在我的代码中,dirlink和filelink包含了StreamingAssets下各文件夹和文件的路径,除blk文件之外,所以blk事实上为每个区服自己的文件,blk也应该包含在开发人员制作的更新包中
8.符号链接性能探究,若符号链接不多层链接,在我的方案中(链接了StreamingAssets下多个文件),对比了多次场景加载时长,没有差别,本人电脑配置不高,内存仅8G,运行时占用率高达90%
9.国际服更新已给出方案,须由开发人员自制更新包(差异文件,不包括主体文件),国际服不需要下载资源(若本地已有国服客户端),可以直接复制

from starward.

starward-bot avatar starward-bot commented on July 18, 2024

@AbyssMorax deleted the following comment
published at 2024-04-04 06:54:25 +00:00
updated at 2024-04-04 06:54:25 +00:00

1.StreamAssets下仅有20527480.blk和APMConfig.json不同,后者的不同仅在区服说明,那么是否可以链接除这两个文件之外的文件和文件夹
2. .blk是打包文件,类似于unity中的assetbundle,我是在最近才仔细查看原神目录结构,不知道20527480.blk是否会变换名字,若不变换名字,则可以采用1点所说的方法
3. 要了解StreamAssets的游戏版本,可以查看其asb_settings.json文件来确定(此文件明文说明了版本,游戏增量包中更新了此文件)
5. 因本人没有之前游戏版本文件,以上所述仅根据已有情况概括和猜测,但希望有所帮助

from starward.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.