mirror of https://github.com/Mabbs/mabbs.github.io
				
				
				
			Update 4 files
- /llama.html - /_posts/2024-01-20-renpy.md - /index.html - /proxylist.mdpull/171/head
							parent
							
								
									d74fe7b4b8
								
							
						
					
					
						commit
						74cb7d028c
					
				|  | @ -0,0 +1,25 @@ | |||
| --- | ||||
| layout: post | ||||
| title: 如何在macOS上玩基于Ren'Py的视觉小说 | ||||
| tags: [Apple, macOS, 视觉小说] | ||||
| --- | ||||
| 
 | ||||
|   跨平台的游戏移植起来就是简单啊<!--more-->     | ||||
| 
 | ||||
| # 起因 | ||||
|   最近我在玩[Winged Cloud](https://store.steampowered.com/developer/WingedCloud/)出的视觉小说,他们家出的视觉小说画风都很不错,比很多其他同行画的好看,另外长度一般都很短,大概1-2个小时就能看完,很适合下班之后闲了看一部,不过我现在已经换了MacBook,要怎么玩呢?他们家的视觉小说基本上都是用的Ren'Py引擎开发的。Ren'Py引擎的游戏本身其实原生就是跨平台的,但是也许是因为我是直接从互联网上下载的,macOS会有些验证之类的?直接运行.app结尾的文件是没办法打开游戏的,双击会显示应用程序无法打开……那该怎么运行呢? | ||||
|    | ||||
| # 玩法研究 | ||||
| ## 使用iOS版的RenPyViewer | ||||
|   不过看看之前[我在MacOS上玩游戏的经验](/2023/10/21/game.html),对于Apple芯片的Mac来说,可以下载[RenPyViewer](https://apps.apple.com/us/app/renpyviewer/id1547796767)来玩。只是经过我的测试发现,RenPyViewer能玩的游戏很有限,因为它内置的Ren'Py引擎版本是7.5.3的,如果游戏用的Ren'Py引擎和这个版本相差不大,或者没有用到新版的特性之类的倒是能正常运行,我试了一下Sakura MMO系列、Sakura Gamer系列等都能正常运行,但只要运行游戏Ren'Py的版本过高或者过低的游戏都会报错,尤其像新出的基本上都是8.0以上的版本了,Python的版本也从2换成3了,显然用RenPyViewer肯定是没法运行的。    | ||||
| ## 使用Intel macOS版的RenPyViewer | ||||
|   其实在我发现iOS版的RenpyViewer不能运行一部分Ren'Py游戏之后,我又去搜了一下,在知乎上找到了iOS版的RenPyViewer作者发的文章,介绍了[macOS如何游玩Ren'Py引擎游戏](https://zhuanlan.zhihu.com/p/477696534),其中包含了他为macOS做的RenPyViewer,不过我下载看了一下是Intel版的……不过也许这个里面用的引擎更新一点,一部分iOS版不能玩的这个版本就可以玩。    | ||||
| ## 使用终端运行 | ||||
|   但毕竟前面两个方法内置的引擎版本是固定的,能玩的游戏也很少,看来得想个通用的办法,毕竟Ren'Py游戏在发行的时候是支持macOS的啊。所以玩又看了看,Ren'Py开发的游戏发行之后一般在游戏文件夹里有一个.sh的文件,看起来应该是给Linux系统运行使用的,但是macOS也可以运行.sh的文件啊,所以我就直接在终端运行了它,结果macOS和Linux不一样的地方是所有从网上下载的可执行文件都必须签名,不然就会报移到废纸篓之类的错,关于这个问题,我看网上说的好像是执行`xattr -r -d com.apple.quarantine <path>`就可以,不过后来我也忘了是出什么问题,最后是手动一个一个给每个可执行文件加的权限,最终倒是也运行起来了。    | ||||
| ## 手动修改.app文件 | ||||
|   不过每次运行要是用终端那不是很麻烦嘛,另外既然游戏里面明明有.app的文件,为什么会运行不了呢?后来我看了看,发现Contents/MacOS文件夹下的文件并没有可执行权限,我猜可能是和这个有关系?加了可执行权限之后倒是没有报应用程序无法打开的错了,但是还是不能运行,点开之后在Dock栏跳了几下就消失了……然后我就去看了下那个可执行文件,发现就是一个Shell脚本(后面的版本换成可执行文件了)里面定义了几个ROOT变量,一个是和脚本同级的目录,一个是和.app同级的目录,还有一个是Contents/Resources/autorun目录,这么看来正常情况下因为游戏是跨平台的,游戏肯定不会在.app里面,在外面的话……看现在macOS权限管的这么严格,让它读取.app外面的文件估计不太行,肯定只能读取.app里面的文件,至于Intel macOS版的RenPyViewer我看了一下好像原理差不多,是把游戏目录用软链接映射过去的,所以才能在不直接获取.app外面的文件下运行。之后我又参考了一下其他直接在macOS发行的Ren'Py游戏,感觉也差不多。所以解决方法也很简单,要么把游戏文件放到Contents/Resources/autorun目录下,要么做个软链接放过去,我觉得单个.app管理起来会方便一些,所以就直接把游戏文件全部移动进去了。试了一下,总算可以正常运行了。而且多试了几个,基本上都没有问题。    | ||||
|   但有些Ren'Py游戏连.app都没提供,我不知道SDK默认生成分发版的时候会不会包含macOS上用的.app文件,不过也有可能是发行的时候只针对Windows所以删掉了,对于一些非官方汉化版很有可能是汉化的人给删掉了。对于这种情况,可以先搞清楚这个游戏使用的Ren'Py版本,然后去Ren'Py官网下载对应版本的SDK,把SDK中的renpy.app复制出来,然后按照上面的方法把游戏拷进去就可以正常运行了。    | ||||
|   另外macOS上还有一些坑,比如说Windows的文件名是不区分大小写的,但是macOS是区分的,有时候他们写脚本的时候文件名和程序里可能有些比如CG之类的大小写不一致,结果图片不能正常加载,这种情况就只能用unrpa解包然后把对应的图片名改成正确的才能运行了,当然Ren'Py提供了忽略错误的功能,但是不知道为什么只有英文模式下有,中文下就没有……这种情况还得先切换到英文才行。    | ||||
| 
 | ||||
| # 总结 | ||||
|   总的来看,以后如果想在macOS上玩Ren'Py游戏,优先应该用游戏自带的.app最好,把Contents/MacOS下的文件添加可执行权限,然后把文件全部移动到Contents/Resources/autorun下。不过旧版的Ren'Py基本上都是只有x86_64的可执行文件,新的才有两种都支持的,如果是用的Apple芯片的Mac,最好先看看可执行文件是不是通用的,如果不是优先应该先试试iOS版的RenPyViewer,毕竟原生运行肯定要更省电一些,如果不能运行再用上面的办法。    | ||||
|  | @ -58,8 +58,6 @@ title: 首页 - 我的文章 | |||
|   | ||||
|  <a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/pixiv-index/">Pixiv图片索引API</a><br> | ||||
| 
 | ||||
|  <a href="/llama.html">Cloudflare LLaMA 2</a><br> | ||||
| 
 | ||||
|  <a href="/message.html">留言板</a><br> | ||||
| 
 | ||||
|  <a href="/links.html">Links</a><br> | ||||
|  |  | |||
							
								
								
									
										385
									
								
								llama.html
								
								
								
								
							
							
						
						
									
										385
									
								
								llama.html
								
								
								
								
							|  | @ -1,385 +0,0 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
| 
 | ||||
| <head> | ||||
|   <title>Cloudflare AI</title> | ||||
|   <meta charset="utf-8"> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|   <link rel="icon" | ||||
|     href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%3E%3Ctext%20x='0'%20y='14'%20style='filter:%20invert(100%);'%3E☁️%3C/text%3E%3C/svg%3E" | ||||
|     type="image/svg+xml" /> | ||||
|   <style> | ||||
|     body { | ||||
|       font-family: 'Raleway', sans-serif; | ||||
|       margin: 0; | ||||
|       padding: 20px; | ||||
|       background-color: #222; | ||||
|       color: #fff; | ||||
|       backdrop-filter: blur(10px); | ||||
|       overflow: hidden; | ||||
|       height: 100vh; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     h1 { | ||||
|       text-align: center; | ||||
|       font-weight: 900; | ||||
|       text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); | ||||
|       font-size: 64px; | ||||
|     } | ||||
| 
 | ||||
|     h2 { | ||||
|       text-align: center; | ||||
|       font-weight: bold; | ||||
|       text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); | ||||
|       font-size: 20px; | ||||
|     } | ||||
| 
 | ||||
|     form { | ||||
|       margin-bottom: 20px; | ||||
|       display: flex; | ||||
|       justify-content: center; | ||||
|       /* Center items horizontally */ | ||||
|       align-items: center; | ||||
|       /* Center items vertically */ | ||||
|       position: fixed; | ||||
|       bottom: 12px; | ||||
|       width: 100vw; | ||||
|       border-radius: 10px; | ||||
|       padding: 10px; | ||||
|       /* Add some padding to give space around the items */ | ||||
|     } | ||||
| 
 | ||||
|     label { | ||||
|       font-weight: bold; | ||||
|       color: #ccc; | ||||
|     } | ||||
| 
 | ||||
|     input[type="text"] { | ||||
|       width: 50%; | ||||
|       padding: 10px; | ||||
|       background-color: rgba(0, 0, 0, 0.3); | ||||
|       color: #fff; | ||||
|       border: none; | ||||
|       border-radius: 5px; | ||||
|     } | ||||
| 
 | ||||
|     textarea { | ||||
|       width: 50%; | ||||
|       padding: 9px; | ||||
|       color: whitesmoke; | ||||
| 
 | ||||
|       border: none; | ||||
|       border-radius: 5px; | ||||
|       font-size: 18px; | ||||
|       background-color: #222222; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     textarea::-webkit-scrollbar { | ||||
|       display: none; | ||||
|     } | ||||
| 
 | ||||
|     textarea:focus { | ||||
|       outline: none; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     #response { | ||||
|       max-width: 80%; | ||||
|       height: calc(100% - 250px); | ||||
|       margin-left: 10%; | ||||
|       overflow-y: scroll; | ||||
|     } | ||||
| 
 | ||||
|     #response::-webkit-scrollbar { | ||||
|       width: 10px; | ||||
|       border-radius: 50%; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     .user-message { | ||||
|       background-color: rgba(71, 71, 71, 0.3); | ||||
|       padding: 10px; | ||||
|       margin-bottom: 10px; | ||||
|       margin-left: 40%; | ||||
|       max-width: 60%; | ||||
|       border-radius: 10px; | ||||
|       box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); | ||||
|       backdrop-filter: blur(4px); | ||||
|       -webkit-backdrop-filter: blur(4px); | ||||
|       border-radius: 10px; | ||||
|       border: 1px solid rgba(255, 255, 255, 0.18); | ||||
|       color: whitesmoke; | ||||
|     } | ||||
| 
 | ||||
|     .ai-message { | ||||
|       background-color: rgba(71, 71, 71, 0.3); | ||||
|       padding: 10px; | ||||
|       margin-bottom: 10px; | ||||
|       margin-right: 40%; | ||||
|       max-width: 60%; | ||||
|       border-radius: 10px; | ||||
|       box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); | ||||
|       color: whitesmoke; | ||||
|     } | ||||
| 
 | ||||
|     p { | ||||
|       margin: 0; | ||||
|       padding: 0; | ||||
|     } | ||||
| 
 | ||||
|     .smaller { | ||||
|       font-size: 15; | ||||
|     } | ||||
| 
 | ||||
|     .fa-github { | ||||
|       text-decoration: none; | ||||
|       color: inherit; | ||||
|       position: fixed; | ||||
|       bottom: 0; | ||||
|       right: 0; | ||||
|       padding: 15px; | ||||
|     } | ||||
| 
 | ||||
|     .dropbtn { | ||||
|       background-color: #191b1c; | ||||
|       color: white; | ||||
|       padding: 16px; | ||||
|       font-size: 16px; | ||||
|       border: none; | ||||
|       cursor: pointer; | ||||
|     } | ||||
| 
 | ||||
|     .dropdown { | ||||
|       position: relative; | ||||
|       display: inline-block; | ||||
|       position: absolute; | ||||
|       top: 5px; | ||||
|       left: 5px; | ||||
|       padding: 10px; | ||||
|       color: #fff; | ||||
|       border: none; | ||||
|       border-radius: 5px; | ||||
|       cursor: pointer; | ||||
|     } | ||||
| 
 | ||||
|     .dropdown-content { | ||||
|       display: none; | ||||
|       position: absolute; | ||||
|       background-color: #191b1c; | ||||
|       min-width: 160px; | ||||
|       box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); | ||||
|       z-index: 1; | ||||
|     } | ||||
| 
 | ||||
|     .dropdown-content a { | ||||
|       color: #fff; | ||||
|       padding: 12px 16px; | ||||
|       text-decoration: none; | ||||
|       display: block; | ||||
|     } | ||||
| 
 | ||||
|     .dropdown-content a:hover { | ||||
|       background-color: black | ||||
|     } | ||||
| 
 | ||||
|     .dropdown:hover .dropdown-content { | ||||
|       display: block; | ||||
|     } | ||||
| 
 | ||||
|     .dropdown:hover .dropbtn { | ||||
|       background-color: #444; | ||||
|     } | ||||
| 
 | ||||
|     button { | ||||
|       font-size: 18px; | ||||
|       color: #e1e1e1; | ||||
|       font-family: inherit; | ||||
|       font-weight: 800; | ||||
|       cursor: pointer; | ||||
|       position: relative; | ||||
|       border: none; | ||||
|       background: none; | ||||
|       text-transform: uppercase; | ||||
|       transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); | ||||
|       transition-duration: 400ms; | ||||
|       transition-property: color; | ||||
|     } | ||||
| 
 | ||||
|     button:focus, | ||||
|     button:hover { | ||||
|       color: #fff; | ||||
|     } | ||||
| 
 | ||||
|     button:focus:after, | ||||
|     button:hover:after { | ||||
|       width: 100%; | ||||
|       left: 0%; | ||||
|     } | ||||
| 
 | ||||
|     button:after { | ||||
|       content: ""; | ||||
|       pointer-events: none; | ||||
|       bottom: -2px; | ||||
|       left: 50%; | ||||
|       position: absolute; | ||||
|       width: 0%; | ||||
|       height: 2px; | ||||
|       background-color: #fff; | ||||
|       transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); | ||||
|       transition-duration: 400ms; | ||||
|       transition-property: width, left; | ||||
|     } | ||||
| 
 | ||||
|     .github-icon { | ||||
|       position: fixed; | ||||
|       right: 50px; | ||||
|       bottom: 50px; | ||||
|     } | ||||
| 
 | ||||
|     /* if screen width<1000 then max -width=100% */ | ||||
|     @media screen and (max-width: 1000px) { | ||||
|       #response { | ||||
|         max-width: 100%; | ||||
|         margin-left: 0; | ||||
|         height: calc(100% - 280px); | ||||
|       } | ||||
| 
 | ||||
|       textarea { | ||||
|         width: 80%; | ||||
|       } | ||||
| 
 | ||||
|       .github-icon { | ||||
|         visibility: hidden; | ||||
|       } | ||||
| 
 | ||||
|       form { | ||||
|         bottom: 0; | ||||
|       } | ||||
| 
 | ||||
|       .ai-message { | ||||
|         max-width: 80%; | ||||
|         min-width: 80%; | ||||
|       } | ||||
|     } | ||||
|   </style> | ||||
|   <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> | ||||
|   <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | ||||
|   <link rel="preconnect" href="https://fonts.googleapis.com"> | ||||
|   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | ||||
|   <link href="https://fonts.googleapis.com/css2?family=Raleway:wght@700&display=swap" rel="stylesheet"> | ||||
|   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> | ||||
| </head> | ||||
| 
 | ||||
| <body> | ||||
|   <h1>Cloudflare AI</h1> | ||||
|   <div class="dropdown"> | ||||
|     <button class="dropbtn">Settings</button> | ||||
|     <div class="dropdown-content"> | ||||
|       <a href="#" onclick='setApiUrl()'>Custom API URL</a> | ||||
|       <a href="#" onclick='setIDsession()'>Custom ID Session</a> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div id="response"> | ||||
|     <div class="ai-message">Hey there, how can i assist you?</div> | ||||
|   </div> | ||||
|   <form id="chatForm"> | ||||
|     <textarea id="input" type="text" placeholder="Send a message"></textarea> | ||||
|     <button type="submit">➤</button> | ||||
|   </form> | ||||
|   <a class="fa fa-github github-icon" href="https://github.com/localuser-isback/Cloudflare-AI" style="font-size:36px"> | ||||
|     <script> | ||||
| 
 | ||||
|       // --------------- CONFIG --------------- // | ||||
|       // modify URL to your API | ||||
|       let apiUrl = "https://llama.mayx.eu.org" | ||||
| 
 | ||||
|       // ----------- END OF CONFIG ------------ // | ||||
|       let uuid = ''; | ||||
| 
 | ||||
|       function setApiUrl() { | ||||
|         const customUrl = prompt("Enter custom API URL: "); | ||||
|         if (customUrl) { | ||||
|           apiUrl = customUrl; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       function setIDsession() { | ||||
|         const customUUID = prompt("Enter custom session ID: "); | ||||
|         if (customUUID) { | ||||
|           uuid = customUUID; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       function generateUUID() { | ||||
|         let newUUID = ''; | ||||
|         const chars = 'abcdef0123456789'; | ||||
|         for (let i = 0; i < 32; i++) { | ||||
|           const charIndex = Math.floor(Math.random() * chars.length); | ||||
|           newUUID += chars[charIndex]; | ||||
|           if (i === 7 || i === 11 || i === 15 || i === 19) { | ||||
|             newUUID += '-'; | ||||
|           } | ||||
|         } | ||||
|         return newUUID; | ||||
|       } | ||||
|       const chatForm = document.getElementById('chatForm'); | ||||
|       const inputField = document.getElementById('input'); | ||||
|       const responseDiv = document.getElementById('response'); | ||||
| 
 | ||||
|       function chat() { | ||||
|         var input = inputField.value; | ||||
|         input = encodeURIComponent(input) | ||||
| 
 | ||||
|         const userMessageDiv = document.createElement('div'); | ||||
|         userMessageDiv.className = 'user-message'; | ||||
|         userMessageDiv.innerText = decodeURIComponent(input); | ||||
|         responseDiv.appendChild(userMessageDiv); | ||||
| 
 | ||||
|         const loadingDiv = document.createElement('div'); | ||||
|         loadingDiv.className = 'loading'; | ||||
|         loadingDiv.innerHTML = `<svg width="30" height="30" viewBox="0 0 50 50"><circle cx="25" cy="25" r="20" stroke="black" stroke-width="4" fill="none" /><circle cx="25" cy="25" r="20" stroke="#3498db" stroke-width="4" fill="none" stroke-dasharray="90" stroke-dashoffset="0"><animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="1s" repeatCount="indefinite"/></circle></svg><center class="smaller">If you recive an error than wait 1-2 minutes refresh and try again.</center>`; | ||||
|         responseDiv.appendChild(loadingDiv); | ||||
| 
 | ||||
|         axios.get(`${apiUrl}/${uuid}?q=${encodeURIComponent(input)}`) | ||||
|           .then((response) => { | ||||
|             responseDiv.removeChild(loadingDiv); | ||||
| 
 | ||||
|             const aiMessages = response.data[0].response.filter(message => message.role === 'system' && message.content.response); | ||||
|             if (aiMessages.length > 0) { | ||||
|               const lastAiMessage = aiMessages[aiMessages.length - 1]; | ||||
|               const aiMessageDiv = document.createElement('div'); | ||||
|               aiMessageDiv.className = 'ai-message'; | ||||
|               aiMessageDiv.innerHTML = marked.parse(lastAiMessage.content.response); | ||||
|               responseDiv.appendChild(aiMessageDiv); | ||||
|             } | ||||
| 
 | ||||
|             inputField.value = ''; | ||||
|           }) | ||||
|           .catch((error) => { | ||||
|             console.log("Error receiving response:", error); | ||||
|           }); | ||||
|       } | ||||
| 
 | ||||
|       chatForm.addEventListener('submit', (event) => { | ||||
|         event.preventDefault(); | ||||
|         chat() | ||||
|       }); | ||||
| 
 | ||||
|       inputField.addEventListener('keydown', (event) => { | ||||
|         if (event.key === 'Enter') { | ||||
|           if (!event.shiftKey) { | ||||
|             event.preventDefault(); | ||||
|             chat() | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       if (!uuid) { | ||||
|         uuid = generateUUID(); | ||||
|       } | ||||
|     </script> | ||||
| </body> | ||||
| 
 | ||||
| </html> | ||||
|  | @ -35,5 +35,3 @@ title: 代理列表 | |||
| - <https://unmayx.medium.com/>    | ||||
| - <https://mayx.cnblogs.com/>    | ||||
| - <https://mayx.xlog.app/>    | ||||
| - <https://mayx.proselog.com/>    | ||||
| - <https://mayx.substack.com/>    | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue