如何保护你的 Roblox 游戏公平/安全

注:文章仓促完成,如果有误请不吝指正。

众所周知,游戏都有可能存在漏洞,而作弊者常常出现,他们破坏游戏规则,干扰玩家体验,甚至窃取游戏代码。至此在游戏有一定社区规模后,我们就不得不考虑游戏安全和公平问题了。借由亲身体验过游戏执行器(Executor)(即可以从外部向 Roblox Player 内执行完整的核心脚本(Core Script)的等级 Lua 代码的工具)的人。我向大家介绍 Roblox 的游戏作弊社区,以及如何尽可能的保护游戏和公平。

什么是执行器(Executor)?

执行器(俗称作弊器)是一种通过访问 Roblox 进程,获取 Lua 句柄,并从外部向内执行任何完整 Lua 代码的工具,目前较为出名的执行器有 Synapse-X,Protosmasher 等,被注入的脚本通常拥有非常高的权限(等级同等 CoreScript)以及非常丰富的脚本模块,以至于脚本很难被侦测到。
但因为其执行器的特殊性,大部分黑客用其制作游戏作弊脚本。例如 ESP(玩家透视),TP(传送),Farm(自动刷取游戏币)等。

执行器的等级

在 Roblox 的黑客社区里,这类执行器的执行能力通常被划分为 7 个等级,通常代表了不同的脚本环境,1 - 2 级为 LocalScript 环境,3 - 4 级为 Roblox 核心脚本环境,5 级为 Roblox Studio 命令栏环境,而 6 级为的 Roblox Studio 的插件环境,最后一个 7 级等级为 Roblox 服务器脚本等级。(因为 2016 年 Roblox 推出的过滤启用功能,现已经没有这个等级的执行器了)(笔者入坑时,正值当时的 7 级执行器 RC7 泛滥的时候)

什么是混淆器(Obfuscator)?

混淆器和压缩器(Minifier)虽然看似相同,但是目的不一致。压缩器的目的是用于减少原代码体积,而混淆器是用于使代码转换成让普通程序员无法直接解读的形式:

一段简单的代码:

被混淆后变成了这样:

这类代码通常解读难度巨大,一般情况下很难判断代码内在执行什么,也正因为如此,部分黑客通过将脚本混淆,放在一些工具箱(ToolBox)的模型内,进而影响到玩家的正常游戏(例如被莫名其妙的跳转,要求购买通行证,甚至是要求输入账户密码)。更有甚者,在开发者的插件内植入混淆代码,从而通过 HttpService 直接从开发者处获取完整的原代码(历史上 木材大亨2(Lumber Tycoon 2) 幻影军队(Phantom Forces) 均因此被窃取原代码并在黑客论坛内流通)
目前已知的混淆器有闭源的 Synapse-Xen 和开源的 IronBrew,Github 也有不同种类的可以自行查询。

如何保证游戏公平?

虽然我们不一定能做到绝对无法破解(例如 ESP,Synapse-X 可以在 CoreGui 内输出透视结果,而局部脚本和服务器脚本均无法检测核心页面的变化),但如果能把作弊降低至辅助等级,我们的公平就能得到极大的保证了。

开启过滤启用(或关闭实验模式)

2016 年,Roblox 推出了过滤启用(Workspace.FilteringEnabled),使得客户端的绝大部分数据不会复制到服务器内,大大减少了执行器的涉及范围。如果你的游戏没有开启该设置,启用将可以抵制绝大部分的低等级执行器。

重要的事件放在服务器处理

拿 FPS 游戏举例(Phantom Forces),我们通常会使用枪械射击,这通常会需要触发事件,但某些游戏开发者会先检测是否击中玩家,然后向服务器发送该玩家的信息,这就给作弊可乘之机了,只要遍历玩家列表,逐个循环触发,就能做到全员击杀(没准队友也被杀了呢)如果手段高明一些,我们可以发送枪械的发射方向(Ray)然后让服务器计算弹道是否击中,虽然可能有些简陋,但是我们的确增加了作弊脚本的编写难度。

拿 Obby 游戏举例(举例 The Tower of Hell),玩家想瞬移到终点直接胜利,而如果玩家的移动速度是恒定不变的,我们便可以检测玩家的水平速度(垂直可能存在玩家掉落的情况),如果会超出,可能就是在作弊了。

拿 Tycoon 游戏举例(Lumber Tycoon 2),作弊者通过触发读档事件复制游戏物品,作者将保存和读取分开,而不是合并成一个处理,游戏中不得保存当前存档槽到别的存档槽上,但是借助作弊脚本即可复制存档,如果可以进行合并,也许可以解决复制方面的问题。

其他游戏可能不会出现上述情况,但是要领一定是尽可能减少客户端与服务器的交互,或者合并与客户端的访问都可以。

不用的游戏实例放在 ServerStorage 内

不光是事件,一些游戏模型如果可以就将其存储在 ServerStorage 内,绝大多数的执行器权限不足以访问 ServerStorage,所以这也可以确保游戏模型不被一下子全部窃取。

使用 ToolBox 的模型需要再三检查

正如上文说到,ToolBox 的部分模型被插入恶意代码,虽然一下子可能会让你束手无策,但是如果我们只需要一个纯模型(不需要脚本添加行为的)的话,完全可以把下下来的模型包含的脚本删除。这样就能避免恶意脚本影响玩家游玩了。

尽量不要开启或限制插件访问 HttpService

前面举了两个游戏被窃取原代码的例子,都是因为安装了留有后门的插件造成的,因此我们一定要选择可以信任的插件(最好是从开发者论坛内寻找),如果担心插件不安全,可以访问 Roblox 的安装目录下的 你的用户ID 文件夹下的 InstalledPlugins 找到插件对应的模型文件(rbxm)进行检查。

减少局部脚本的数量或者也进行混淆

我们无法确保客户端脚本(LocalScript)的完整安全,毕竟数据已经在客户端上,只要有时间都可以破除或者反编译。但是如果混淆器能正确使用,也可以保护我们的脚本不被直接解读。可以尝试制作插件,发布前将脚本进行混淆,这样能大大增加逆向工程的难度,这样即使被反编译,也需要大量的精力去逆向工程。

在表内使用元表进行访问检测

如果你的游戏价值已经大到作弊脚本已经要访问你的脚本来篡改数据的话,那么在需要的变量(多为 table)内使用元表进行检测访问便是必不可少的了。通过 __index__newindex 抓取数据被更改的情况,使用 getfenv 获取脚本环境是否是脚本自身,如果不一致那么就是作弊脚本正在访问了。
简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
local scriptEnv = getfenv()
local meta = {
__index = function(t, k)
for i = 0, 10 do
if getfenv(i) ~= scriptEnv then
warn("检测到作弊脚本读取数据!") -- 做你想做的,踢出玩家也可以
return
end
end
return t[k]
end,
__newindex = function(t, k, v)
for i = 0, 10 do
if getfenv(i) ~= scriptEnv then
warn("检测到作弊脚本写入数据!") -- 做你想做的,踢出玩家也可以
return
end
end
t[k] = v
end
}
local data = setmetatable({ a = 0 }, meta)
data.a = data.a + 1

在你能确认的情况下永久封禁用户

如果能确信用户的操作属于作弊,那么可以在云端数据上记录该玩家并阻止玩家进入游戏(进入则踢出),这也是一个简单粗暴的方式,而且在国服 Roblox 内,小号的创建难度远高于国际服,所以能加大脚本作者编写作弊脚本的难度。

结尾

我们不能完全阻止玩家做出不公平的事情,道高一尺魔高一丈,即便是像微软苹果这样的公司也会有泄露商业机密的问题。我们能做的就是尽可能增加难度,让作弊成本大于游戏价值

最后祝大家做出自己梦想的游戏,Powering Imagination

↓ 继续阅读 ↓

关于如何优化 Scratch 虚拟机的运行速度思路

开学一周了,也有一阵子没有写过博客了,正好最近在自制 ScratchScript ,脑袋里想到了些关于优化性能的思路,就写在这里留作记忆了。

注:下文仅为个人见解,如果有误请在评论区指出,谢谢!

本文仅在 SteveXMH 的个人博客 里发布,请勿复制本文!

短板在哪?

看过 Scratch 3.0 虚拟机源码 的都知道,官方 VM 为了扩展性和与 ScratchBlocks 的交互性整出了不少的接口,一环套一环,而且为了适应不严谨的 JavaScript 和难看的扩展文档在扩展调用方面做了大堆的语法糖,执行一个模块的逻辑都没有 Scratch 2.0 的虚拟机 来的简洁,尽管 3.0 相对于 2.0 性能快了不少,但是如果如此设计虚拟机,无疑会大大降低可以利用的性能。

也许是我的错觉,因为 3.0 的虚拟机代码附上了大量的注释,尽可能地解释了代码的功能(虽然我还是看不懂(巨雾))

立刻掀了 3.0 的调度器?

至少人力成本是不允许的,代码的耦合度太强,强行替换会出现连锁反应,所以我的想法是基于原有调度器添加类似预编译的功能,从我的思路想法上应该不会有太大的难度,难点在于如何进行预编译。

预编译?

我的思路来源于这个预编译型虚拟机 forkphorus 和它的原版 phosphorus,二者都是通过将模块预先转译成 JavaScript 后再执行,所以我有想把类似的转移方式塞进 3.0 运行时里(不是把 forkphorus 照搬进去!)。

举个栗子:

这是我们的运动模块中的 前进 ( )步 的模块实现:

1
2
3
4
5
6
7
8
9
10
11
class Scratch3MotionBlocks {
// ...
moveSteps (args, util) {
const steps = Cast.toNumber(args.STEPS);
const radians = MathUtil.degToRad(90 - util.target.direction);
const dx = steps * Math.cos(radians);
const dy = steps * Math.sin(radians);
util.target.setXY(util.target.x + dx, util.target.y + dy);
}
// ...
}

首先,因为我们的运行时在注册模块的时候,会给每个模块的实现使用 Function.bind 固定 this,所以导致了不能直接对执行模块实现使用 Function.toString 获取原代码,不然只会返回 [Native Code],所以为了能够在不修改原代码的前提下添加预编译的方案,也许只有添加一个新的属性用于描述用于嵌入的代码块了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Scratch3MotionBlocks {
// ...
get jitcodes () {
return {
// 对照模块实现的 moveSteps
motion_movesteps: `
const steps = Cast.toNumber(args.STEPS);
const radians = MathUtil.degToRad(90 - util.target.direction);
const dx = steps * Math.cos(radians);
const dy = steps * Math.sin(radians);
util.target.setXY(util.target.x + dx, util.target.y + dy);
`
}
}
// ...
}

当然也许我们还可以在注册前使用 Function.toString 获取到函数实现代码,获得了实现代码之后,我们就可以尝试进行组合然后使用 new Function(code) 生成新包装的函数了。但是如此直接复制的话难免会变得繁杂,因为原本的实现参数并不能直接用,那么我们可不可以用一种特殊的参数以暴露所需的接口呢?

答案是肯定的,每个模块实现里的第二个参数是一个抽象对象,可以让模块实现访问到很多的接口。我们也可以定义一个 util 参数来包装几个 JIT 代码会用到的东西,之后的实现代码都可以用得到。

顺带一提,我们的 JIT 代码会自深到浅进行预编译,类似于脏检查,如果有代码出现了变动,就只更新那一段的 JIT 代码,然后和原来已经生成过的 JIT 代码重新组合,减少函数生成消耗。

模块参数?

这个的难度还是有一点的,为了最大化性能肯定要做成内嵌形式的模块才可以正确获取参数,但是那么多的数据转换难免有点受不了,所以我想到了一个字符串的替换方案,用[=[InputName]=]用来表达要嵌入的参数,我们便可以把上面的 JIT 代码如此改动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Scratch3MotionBlocks {
// ...
get jitcodes () {
return {
// 对照模块实现的 moveSteps
motion_movesteps: `
const steps = util.Cast.toNumber([=[STEPS]=]);
const radians = util.MathUtil.degToRad(90 - util.target.direction);
const dx = steps * Math.cos(radians);
const dy = steps * Math.sin(radians);
util.target.setXY(util.target.x + dx, util.target.y + dy);
`
}
}
// ...
}

之后在生成的时候,根据模块的原型进行字符串转换,把常量或者嵌入的模块嵌入进去即可,但是部分地方可能需要注意一下优先级和语法问题,例如运算符的代码转换,还有数学函数的代码转换,如果遇到了同代码的不同模块实现,为了能更好的进行嵌入可以把 JIT 代码做成一个函数,传递模块参数并返回对应的 JIT 代码,可以更好的优化 JIT 代码量。

舞台刷新?

正常情况下我们的舞台角色通常会需要移动,等待,或者用画笔绘制图像,所以我们需要在一些模块里嵌入一种用于暂时停止的功能,以防止无止境的等待卡顿或不让出的问题。

我的想法是使用 ES2017 的 AsyncFunction 来做到这个效果(IE:我####),然后在 util 参数里加上一个 yield 异步函数,调用时把目前的状态(当前的模块ID)和是否需要等待舞台刷新传递给调度器,然后调度器返回一个 Promise 并保存 resolve 函数到当前线程里,等待调度器处理完其他线程并更新舞台后再调用,即可继续运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Scratch3ControlBlocks {
// ...
get jitcodes () {
return {
// 对照模块实现的 wait
control_wait: `
if (util.stackTimerNeedsInit()) {
const duration = Math.max(0, 1000 * util.Cast.toNumber([=[DURATION]=]));

util.startStackTimer(duration);
util.runtime.requestRedraw();
await util.yield({
currentBlockId: 'abcd',
waitStageUpdate: true
});
} else if (!util.stackTimerFinished()) {
await util.yield({
currentBlockId: 'abcd',
waitStageUpdate: true
});
}
`
}
}
// ...
}

代码块?

代码块例如 如果< >那么{ } 模块需要嵌入一段代码树,其实在 VM 里分支模块树本质也是一个参数,所以我们可以使用 SUBSTACK,SUBSTACK2 等参数进行嵌入,甚至我们可以自己定义一个 NEXT 参数用于连接下一个模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Scratch3ControlBlocks {
// ...
get jitcodes () {
return {
// 对照模块实现的 wait
control_if: `
const condition = Cast.toBoolean([=[CONDITION]=]);
if (condition) {
[=[SUBSTACK]=];
}
[=[NEXT]=];
`
}
}
// ...
}

那扩展作者也得一个个加 JIT 代码?

我们是基于原有的调度器进行改造,如果遇到没有 JIT 代码的模块,我们就按照原版的执行方式去执行,直到下一个模块包含了 JIT 代码才会继续调用。

热更新?

难免会有刁难人的事情,比如执行时代码被更换了(模块拖拽过了),但是介于 VM 是单线程的,且模块更新和调度器不会并行运行,所以这个问题不会有太大的影响。一般情况下我们的 JIT 代码会在让出和结束执行时记录下当前的模块ID,所以我们可以检测当前线程的当前模块是否存在,是否有可用的 JIT 代码供调用,还有后面是否有其他模块链接,等等,如果没有 JIT 代码,就按照原版的调度器来执行代码,如果有则继续工作,我们的 JIT 代码会在热更新时自动更新,不会有出现 JIT 代码没有完全生成就被执行的问题。

说的好听,做起来还是很累。。。

学生党依然是没有时间的,所以上面提供的思路,也许真正的大佬就可以做得出来,期待能有这样的新 VM 出现。

有什么意见建议就发在评论区吧!周末我会回来看的哦!

↓ 继续阅读 ↓

Libra 红名单 WebAPI v2 文档

众所周知,因为一些内部开发人员对于使用 Python 有争议,最后协商选择使用 NodeJS 重新编写,同时和维护团队与阿尔法营的守护者老师深度讨论之后重新制定了第二个 WebAPI 版本。

该文档的内容以通过审核,现已可用。

注:旧版本的 API 仍然会保留,但是不建议继续使用,请尽快将请求链接转移至 v2!
旧版 v1 API 链接

API 文档

所有链接均使用 https://www.scpo.top:1120/v2 为链接,调用方法为 POST。表单数据均为 JSON,FormData 或 URIEncodedForm。

所有表单以 method 确定调用功能类型和返回值,总共有以下值可以选择:

  • isInList 获取一个用户是否在红名单内
  • getList 获取以 JSON 数组为格式的在红名单内的所有用户名单
  • getImportableList 获取以 Scratch 链表文本为格式的在红名单内的所有用户名单

以下为对应功能的参数集:

isInList

参数名称 默认值 可选值 说明
platform kada,acamp 社区平台,如不填则为所有平台
format username username,id 输入的名称类型,是用户名还是用户ID编号
value 需要检测的用户名或用户ID

返回值:

参数名称 类型 说明
message string 返回 success 即成功,否则返回错误原因
status boolean 返回用户状态,如果为 true 则在红名单内,false 反之亦然
reason string/null 如果 status 为 true 时这里是被登记入红名单的原因,否则为 null

getList

参数名称 默认值 可选值 说明
platform kada,acamp 社区平台,如不填则为所有平台

返回值:

参数名称 类型 说明
message string 返回 success 即成功,否则返回错误原因
list 见下表 在红名单内的所有用户名单

数组成员类型结构:

成员名称 类型 说明
name string 用户的名称
reason string 用户被登记入红名单的原因

getImportableList

参数名称 默认值 可选值 说明
platform kada,acamp 社区平台,如不填则为所有平台
type username username,id,reason 返回用户名称还是用户ID

返回值:一个以 Scratch 链表文本为格式的在红名单内的所有用户名单,可以直接导入到 Scratch 的链表变量中。

使用范例

isInList

客户端:

1
2
3
4
5
6
7
8
9
10
POST /v2 HTTP/1.1
HOST: redlist.zerlight.top:1100
Content-Type: application/json

{
"method": "isInList",
"format": "username",
"platform": "acamp",
"value": "Zerdot"
}

服务端:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Content-Type: application/json

{
"message": "success",
"status": true,
"reason": "此为示例用户,当你看到这个永久名单,说明你的请求有问题或者你本来就是要测试才调用这个的,请更正你的请求数据后重试"
}

getList

客户端:

1
2
3
4
5
6
7
8
POST /v2 HTTP/1.1
HOST: redlist.zerlight.top:1100
Content-Type: application/json

{
"method": "getList",
"platform": "acamp"
}

服务端(因列表数据太多所以只列举一个):

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Content-Type: application/json

{
"message": "success",
"list": [{
"name": "Zerdot",
"reason": "此为示例用户,当你看到这个永久名单,说明你的请求有问题或者你本来就是要测试才调用这个的,请更正你的请求数据后重试"
}]
}

getImportableList

客户端:

1
2
3
4
5
6
7
8
9
POST /v2 HTTP/1.1
HOST: redlist.zerlight.top:1100
Content-Type: application/json

{
"method": "getImportableList",
"platform": "acamp",
"type": "username"
}

服务端:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Content-Type: text/plain

Umberto Unity
Carla Caring
Katie Kindness
Fred Friendship
↓ 继续阅读 ↓

小萧的网页三件套入门之第一套:HTML

2020/1/29,在 Scratch 群里人的要求下撰写此文,如果有误请在评论区指出,万分感谢!

本文仅在小萧的个人博客发布,禁止转载!

序言

网络教程千千万,来让小萧教教看(bushi),网络的发达让程序员们越来越关注于网页编程上,甚至网页三件套中的 JavaScript 已经可以通过 NodeJS 运行在服务器上,似乎三件套的运用已经扩展到了全栈开发上。

那么面对这股潮流,学习网页开发似乎是不可避免的了(如果你喜欢 C++ Java Kotlin 也可以现在关掉本文了)。那么作为三件套,每一样都不可或缺,本文我们主要关注的是 HTML(Hyper-Text Markup Language)

当然,如果你觉得这篇教程太蔡了,可以选择更有权威的 MDN 开发文档 然后关闭本教程,希望你学的开心!

什么是 HTML?

网页五花八门,单单文字显得十分单调。就好比现在你看到的本文,有大标题,引用,段落,代码,都是文字无法清晰展示的。因此,必须要有一个能“超越文本”的表达能力的语言,而且还要好写(其实越多越难看)。于是先人们就发明了超文本标记语言(Hyper-Text Markup Language)来作为网页的主用语言来装饰我们的网页。为了简单,这个语言有这些特点:

  • HTML 是可以阅读的,它不会变成机器码,也不会变成难懂的代码(Script 元素除外)。
  • 这是一种标记语言,不是编程语言,所以不用担心掉光头发学习门槛高。
  • 这种语言用标记的方式来描述我们的网页布局,层次会很清晰(除了那些迷惑网页布局)。
  • HTML 不会关心你的大小写(即不敏感大小写),未来的 XHTML 甚至要强制小写(元素内容除外)。
  • HTML 真的很简单!!!!!

介于 HTML 是采用了 XML 的标记方式,那么下面会从 XML 的基本语法开始教起:

基本概念

元素

首先,这是一个元素:

1
2
3
<p>
我是一个元素里的文本
</p>

<p>称作一个元素的开头,也叫作开始标签(Opening Tag)。这个标签会用<>两个符号括在一起,里面的开头会写上这个元素的名称(也就是p),通常名称后面还会写一些关于这个元素的一些属性,后面会说到。

</p>是这个元素的结尾,也叫作闭合标签(Closing Tag)。和开始标签类似,但是元素名称的开头需要带一个斜杠(/)表示这是个闭合标签,闭合标签里面除了名称什么都不能包含。

但是有这么一种特别的元素,它并不需要闭合标签,也就是这个元素里面不会有内容出现,它在一开始就关上了(误):

1
<br/> <hr/>

这样的元素称之为空元素,也就是没有内容的元素,他的行为依照下文的属性而定。

除去空元素,一个元素的两个标签之间可以写入任何你喜欢的文字,而且可以任意嵌套你想要的的各种元素:

1
2
3
4
5
6
7
8
9
10
11
12
<p>
<h2>
HTML 教程
</h2>
By
<a href="/">
SteveXMH
</a>
<p>
这是一个小萧写的 HTML 教程~
</p>
</p>

效果应该是这样的:

HTML教程

BySteveXMH

这是一个小萧写的HTML教程~

被一个元素所包含的元素称之为那个元素的子元素,反过来说,这个包含了某个元素的元素称作这个元素的父元素(禁止套娃)。一个元素可以有很多个子元素,但是每个子元素只有一个父元素。

有人说写网页难,是因为元素种类太多,但是我们都可以在 MDN(Mozilla Developer Network)网页找到任何有关网页开发(比这个教程好了不知道多少)的教程和文档(绝大多数都是社区翻译的简体中文)。那么我们继续基础概念:

属性

一般来说,元素的开始标签可能会有很多属性(Attribute)来修饰这个元素的一些特点,通常和 JavaScript 配套使用,效果最佳(√)。通常以 属性名=”属性值” (也有属性值用单引号来标注的,甚至引号也不用的,但是最好用双引号)来定义,如果有很多个属性就用空格分隔:

1
<a href="https://github.com/" id="github-link" style="color: white; background-color: red;">全球最大同性交友网站!点我约炮→</a>

实际展示效果是这样的:

全球最大同性交友网站!点我约炮→

这里用了点 CSS 风格,后面我们也会讲到,能够让你的网页风格更加突出。当然这些都是通过属性加以修饰之后才能实现的行为,比如你点击了上面的文字后,会跳转到这个同性交友网站,是因为这个 a 元素有了 href 这个属性后点击时会跳转到这个值对应的链接。

有的时候,属性不一定需要值,当不使用属性值的时候,这个属性则默认取 JavaScript 中的 true 值:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div>
<style scoped>
div {
color: red;
}
</style>
<div>
只有我的文字是红色的!
</div>
</div>
<div>
后面会讲到 CSS 是什么的,我们只要注意到上面的 style 元素的 scoped 这个属性没有特定的值就可以了。
</div>

注释

注释就很简单了,符合下面的格式即可:

1
2
3
<!-- 感叹号和两个横杠开头
然后中间全部是注释~
结尾再加两个横杆一个大于就OK了 -->

网页在外表是给人看的,写的时候也是给人看的,多加注释有助于帮自己回忆当时的做法,也方便别人理解的你的网页布局。

一些常见的元素

元素的基本概念了解了,那么我们来看下能用上的各种自带元素吧。

  • a 元素能创建超链接,点击会跳转到 href 属性所对应的链接,例:<a href="baidu.com">Baidu</a>
  • b 元素使文本粗体,例:<b>IMPORTANT!</b>
  • i 元素使文本斜体,例:<i>MDN</i>
  • br 元素能换行
    这是空元素,例:I'm in the first line.<br/>I'm in the second line.
  • p 元素构造一个自然段,每一段都会自动换行,例:<p>I'm the first paragraph.</p><p>I'm the second</p>
  • img 元素显示一个图片,可显示 src 属性指向的链接的图片,如果还在加载就会显示 alt 属性的文字,例:<img src="https://developer.mozilla.org/media/examples/grapefruit-slice-332-332.jpg" alt="Grapefruit slice atop a pile of other slices">
  • span 元素是一种行内容器,本身无特别效果,后面会详细说到用法和区别,例:I'm in a <span>span</span> container.
  • div 元素是一种块状容器,本身无特别效果,后面会详细说到用法和区别,例:<div>I'm in a div container</div>

让我们再深入一些

学会了基础,希望大家还是接受得了的,HTML 其实真的是元素种类多才看起来难。接下来的许多说明就要建立在基本概念之上了,所以请务必跟上~

网页的标准模板

我们接下来要开始了解网页的大概构造了,首先是现代网页的标准模板:

1
2
3
4
5
6
7
8
9
10
11
12
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>我是网页标题,显示在标签页上的那个</title>
</head>
<body>
<!-- 里面就是网页本体了 -->
<h1>我的一级标题,字很大很大的</h1>
<p>第一段,写些啥吧?</p>
</body>
</html>

首先开头的一个元素有些特别,当然就它特别点而已。<!doctype html>是用来注明这个文本是个 HTML 网页(我不是普通的文字!),方便某些老掉牙的浏览器识别网页。虽然现代浏览器都是通过 HTTP 的 Content-Type 头来判断文件类型的(扯远了,这不是三件套的内容,了解就好),但我们还是要养成写 doctype 的习惯。

接着的元素就是 Html 的本体了,Html 元素的内容除了注释还有两个必不可少的子元素:head 和 body。

Head 元素用来描述网页的基本属性,比如标题(title 元素),元数据(meta 元素),要加载的脚本,要加载的资源文件等等。

Body 元素就是浏览器上所显示的内容了,里面写的元素都会显示在我们的网页里。

一般来说只要按照上面的模板,网页都可以在大部分浏览器里正常显示。

块状元素和行内元素

你可能会发现一个问题,有些元素可以换行,但有些元素不会自己换行。通常我们把这类会自己换行的元素称作块状元素,反之称之为行内元素。这个特性我们后面学习了 CSS 之后是可以任意调整的,这里只是初步了解一下就好。目前有两个最常用的,只用作容器的元素是 divspan,两者代表的分别是块状容器和行内容器(当然也可以被 CSS 任意调教),通常用于布局:

1
2
3
4
5
6
<div>
Chunk 1
</div>
<div>
Chunk 2 with <span>span element</span>
</div>

表格

网页有时候是服务于办公的,表格是显示数据的绝佳方式,那么在网页里如何制作表格呢?

  • 表格的英文名是 Table,那么用于表达表格的元素自然是 table 元素了。
  • 我们数据的英文名是 Data,那么用于表示表格内数据的元素也自然是 td(Table Data) 元素了。
  • 表格由行拆分,行的英文名是 Row,所以每行的数据都要在 tr(Table Row) 元素内才会换行。
  • 表格的首行都会写上这一列代表什么数据,也就是表头单元格,即 th(Table Headline) 元素,标题的字会更粗一些,方便辨认。

综上所述,我们便可以按照如下格式创建表格了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<table>
<tr>
<!-- 第一行可以先写标题 -->
<th>Todo</th>
<th>Status</th>
</tr>
<tr>
<td>HTML Tutorial</td>
<td>Finished</td>
</tr>
<tr>
<td>CSS Tutorial</td>
<td>Working</td>
</tr>
<tr>
<td>JS Tutorial</td>
<td>Working</td>
</tr>
</table>

效果应该是这样的:

TodoStatus
HTML TutorialFinished
CSS TutorialWorking
JS TutorialWorking

结尾

其实 HTML 学会了概念,其余的元素的使用也都是套标准(其实俺就是一个字,懒)。绝大多数的 HTML 教程都可以从 MDN 里找到,本文也有部分内容借鉴了 MDN(来找找看?)。那么祝大家做出属于自己的网页!

啥?你还觉得不好看?

我也是这么觉得的!

为了装饰我们的网页,还得学习 CSS(Cascading Style Sheets,层叠样式表) 呢!

那么我们第二套再会!

顺带一提,有自己博客的群友,想要加友链的可以在下方回复哦~

↓ 继续阅读 ↓

如何二次开发 Scratch 3.0 第一篇

没错,我又来写二次开发了。
一个周末连肝三篇文章的我无所畏惧!
那么进入主题:

Scratch 3.0 源代码的大致情况

在着手二次开发 Scratch 3.0 之前,我们先要了解目前的 Scratch 3.0 的开发布局。
为了提升代码的可用性(其实是迎合谷歌的 Blocky),Scratch 3.0 被拆分成许多部分。
也就是说你看到的 Scratch 3.0 成品其实是由多个不同作用的模块组合而成的,而这些模块很大部分可以独立工作而不受影响。
目前肉眼可见的模块大致这样:

当然,本篇二次开发不会一一解释这些模块的具体用途,我们只需要了解一些地位比较高,比较重要的模块即可。

  • scratch-gui 是将所有模块组合在一起的 GUI 框架。
  • scratch-vm 是 Scratch 3.0 的虚拟机模块,是 Scratch 3.0 的核心。
  • scratch-render 是 Scratch 3.0 的渲染器模块,舞台画面由它负责。
  • scratch-blocks 是 Scratch 3.0 的代码编辑模块,通过它来拖拽模块编写代码。

(注意:scratch-blocks 因为需要编译非常多的模块,导致编译器参数命令行过长无法编译,可能不能在 Windows 平台编译!)

以上是我认为比较重要的模块,了解他们的具体用途对后面多模块开发有一定帮助。
一般只需要修改 UI(加自己的图标什么的)的话,你只需要使用 scratch-gui 即可。
但是如果你需要添加自定义模块,比如打印字符串的话,你可能还需要 scratch-render scratch-vm scratch-blocks 这三个。
一个负责处理文字显示,一个负责执行模块,一个负责给用户提供可操作的模块。
简言之,我一开始想尝试二次开发的时候,因为没有仔细看官方的简略文档所以总是认为:

“他们的虚拟机在哪啊?”
“这个语言是什么啊?根本不像 js 啊?”
“怎么加个模块路径还藏的这么深啊,什么 node_modules 什么的麻烦死了!”

那么同样是为了排除问题,快速进行开发,请继续往下看。。。

搭建开发环境

首先,十分建议大家使用 Linux 系统(Ubuntu, MacOS 之类的)来作为搭建环境的主操作系统,因为 Windows 的命令行单条命令长度最大只能达到 8192 个字符。
scratch-blocks 的预发行流程所需要执行的编译器参数(这些参数都是文件路径,而且文件非常多(模块化带来的缺陷啊))已经远远超过 8192 个字符,再加上 google 的奇葩编译参数尿性, scratch-blocks 不能在 Windows 正确编译。
(似乎可以使用 python 安装程序最后的一个选项来解锁命令行参数限制?)
倘若你真的是 Windows 党的话,你也可以使用 Windows Subsystem for Linux (WSL) 子系统来在 Windows 里运行 Ubuntu。
VS Code 也有可以方便与 WSL 系统交互的远程开发插件,详情戳这里前往插件下载
那么下面就假定大家已经克隆了源码库到一个文件夹里,进行多模块之间的链接。

如何进行多模块开发

我一开始想尝试二次开发 Scratch 3.0 的原因是为了将 SteveScratch 转移到 js 进行开发,但是因为过去有很多的自定义模块所以犯了不少困难。尤其是不清楚 nodejs 的模块链接功能,结果导致我不得不打消二次开发的念头,现在 2019 学业跟的紧,更没有时间继续开发了。

那么为了能够将模块相互链接,我们需要使用 npm 的链接功能,这个功能可以将某个 nodejs 工程链接到全局,然后再通过某个工程的引用链接来使用这个模块,这样你就不需要死盯着 node_modules 文件夹找模块写代码了。

那么下面以 scratch-vmscratch-gui 组合进行开发为例讲解如何

首先克隆 scratch-vm 源码库到一个文件夹,进入文件夹后在终端输入:

1
path/to/scratch-vm> npm i

等待一会后,再克隆 scratch-gui 到任意文件夹(也可以和 scratch-vm 同一个文件夹,但最好就放一起),在这个文件夹打开终端输入:

1
2
path/to/scratch-gui> npm i
path/to/scratch-gui> npm ln path/to/scratch-vm

这样,npm 就会在 scratch-gui 的模块文件夹中创建指向刚刚我们克隆的 scratch-vm 的快捷方式(或者叫链接),scratch-gui 调用到 scratch-vm 的模块时将会从这个文件夹中寻找所需的模块。如果你把两个源代码放在一个文件夹的话,使用相对目录也没有问题。
大功告成!你已经知道如何链接模块,如果你需要删除链接的话,只需要输入在终端输入:

1
path/to/scratch-gui> npm r scratch-vm

注意:模块的名称是以 package.json 中的 name 键值为准,和你的源码所在文件夹的名称无关,也就是说你可以在链接前重命名文件夹,只要你喜欢 :)

完成之后,现在你应该可以通过 scratch-guiwebpack-dev-server 来进行开发了,你在 scratch-vm 所做的更改都会作用在你的 scratch-gui 上。回到 scratch-gui 的工程目录,打开终端输入:

1
path/to/scratch-gui> npm start

就可以开始自由改造了~
后面会详细介绍各个模块的改造方式,所以呢,To be continued –>

↓ 继续阅读 ↓

如何二次开发 Scratch 2.0

本文原本是发在 Scratch 贴吧 里的,但是因为比较凌乱所以正好开了博客就稍作整理之后发在这里了。

前言

你还在为 Flash Builder 的破解验证而烦恼吗?
你还在为 Flash Builder 的超长加载而感到心烦吗?
你还在为找不到 FlashDevelop 二次开发 Scratch 的教程而感到心累吗?
一切都结束了!
小萧经历千辛万苦终于集合了各位大佬的教程,成功的使用 FlashDevelop (以下简称FD)进行编译和 AIR 签名打包操作!
那么既然炫耀了(划掉)成功了结果,那么这里就开始告诉大家我的二次开发方式吧!

第一步:安装独立版 Flex SDK

注意:此步骤可能需要科学上网
如果你不喜欢使用安装器下载 Flex SDK,你可以直接从此处下载离线归档包

Flex SDK 用于对 Flash 或者 ActionScript 的包装和组合调试,包括我们待会要进行的 AIR 签名和包装也要在 Flex SDK 完成。
那么我们先前往 Apache Flex 官网来下载 Flex SDK Installer:http://flex.apache.org/installer.html
当然,因为 Flash 和 AIR 引擎的更新换代,开发者需要自行选择安装的版本,所以我们下载的是安装器。
那么安装完毕后,你需要选择安装版本。
那么前往 Scratch 的 Github 源码库看看,最低支持 Flash 10.2 ,那么我们就选择这个版本,然后 Flex 和 AIR 的版本也会自动变更(这里小萧是选择了最新的版本来安装)。
那么下一步,你需要准备一个干净的文件夹(没有任何文件!)来存放 SDK,接着就开始下载了。
为什么我要先安装 Flex?因为这个玩意下的速度太##慢了

第二步:安装 Java

这个就不说了,链接在此
玩 MC 的就忽略此步

第三步:下载 FlashDevelop (FD)

在安装 Flex SDK 的期间,我们可以开始安装 FD 了
首先进入官网:点我进入
然后有个原谅色的下载按钮就可以进入论坛了
进入论坛往下翻就能找到“FlashDevelop *.*.* Setup”了
下载完之后,在安装组件步骤中选中中文语言(否则没有中文语言)

这下子你就可以选择好路径开始安装了~

第四步:配置 FlashDevelop

等待 SDK 安装完毕后,记住你存放 SDK 的文件夹,然后启动 FD,你可能会看到如下页面:

虽然我们在安装程序已经选中了中文组件,但是我们还是需要在设置中设置显示语言。
打开 Tools 菜单,打开 Program Settings:

在打开的设置里找到 Selected Locale 修改成 zh_CN 后重启即可
如果配置项太多,右上角有个过滤设置 Filter settings,搜索 locale 就找到了:

第五步:下载 Scratch 2.0 源代码

直接从 Scratch 2.0 源码库 下载即可(或者 直接点我下载压缩包
下载完毕后先放着,下面会用到。

第六步:新建和配置工程

那么回到 FD ,我们要开始新建在 FD 的 Scratch 工程了。
首先点击“项目”菜单栏,找到“新建项目”并打开:

那么你打开的时候页面应该是这样的:

首先还是选择你喜欢的位置,名称也是随意的,包名留空。项目类型的话选择 “AIR AS3 Projector” 然后确定创建工程:
第一次创建工程的话会出现一点点模板配置,按自己喜好填写即可~
那么我们看到右边的项目已经多出了一点东西,这个就是我们的工程目录了:

那么新建工程已经完毕,接下来我们需要进行部分的配置了。
首先还是在项目菜单栏里,找到底下的属性:

在里面一部分修改项是不能用的,需要在源码中修改才可以,那么首先打开 SDK 选项卡,找到“自定义 SDK”,把我们刚刚下载的独立 Flex SDK 路径填入:

那么再打开“编译器选项”选项卡,找到”Additional Compiler Options”(编译器参数选项),因为我们的 Scratch 源码有一部分常量需要在编译器启动时提供,源码内不提供,而且不提供这些常量的话 Scratch 反而还无法编译(WTH)。
所以点击省略号打开,填入以下内容:

1
2
-define+=SCRATCH::allow3d,false
-define+=SCRATCH::revision,'e267f37'

(注:如果你的 Flex SDK 中的 Flash Player 版本大于或等于 11.6 的话,你可以把 allow3d 的常量值修改为 true(虽然没啥用))
这两个是必要内容,其他的教程会填入其他内容,这里就不需要了,最后应该是这样的:

第七步:转移源码到工程

配置工程完毕后,我们就要把 Scratch 的源代码合并到我们的工程里。
那么首先看到 FD 的工程目录,右键我们的工程,选择“资源管理器”打开工程文件夹:

将第五步下好的源码包解压,将其中的 libs 文件夹和 src 文件夹解压到工程目录内:

完成之后可以看到 FD 内的工程目录被刷新了:

接下来,我们要设置主运行程序,FD 默认会创建 Main.as 作为第一个运行的程序。
但是在 Scratch 的源码中,Scratch.as 才是主程序。
所以我们右击 Scratch.as ,选择“设为文档类”(奇怪的翻译?),然后删除 Main.as 即可(如果你不需要了的话):

然后将 Scratch 2.0 需要使用的几个库添加到库中,在工程浏览器中展开 libs 文件夹,将内部的 .swc 文件选中,右键选择 “添加到库”

第八步:调试和编译

终于要见到自己亲手编译的 Scratch 了(大开心)
首先我们看一下是否可以进行编译,这里是各种编译的方法:

  1. 点击蓝色的小箭头直接测试程序:
  1. 在项目菜单栏里点击“运行项目”或“编译项目”(这里不能真正打包成 AIR 程序):
  1. 按下 F5 或 F8 快速测试或编译

如果无法编译的话,请确定你的 Flex SDK 是否下载完整,并前往第一步重新安装。

最终结果应该是这样的:

倘若你能够打开上面的页面,恭喜你!你可以立即开始二次开发了!
但是如果你在调试的时候出错了的话,比如说出现这个错误:

如果出现了这个错误,请继续往下看 ↓

第八步·2:修正标识符

大家如果都下载了最新版本那么肯定会遇到如同上面的错误,虽然不知道原因,但是为了可以顺利编译,我们需要对其稍作修改以进行调试
首先还是来到工程文件夹,找到 application.xml 文件(这个步骤可以在 FD 操作),然后第二行就可以看到如上错误中出现的那个链接
我们要修改的就是这个链接尾巴的那串数字,实际上那个是 AIR 的版本号
如果这个版本号不和我们所用的 AIR 版本号一样的话,就会出现这个问题
所以如果你还记得 AIR 的版本的话就替换这个版本,那就直接替换版本号即可
但是如果你忘记了的话,就随我继续往下做:
打开你的 Flex SDK 目录,找到 bin 文件夹,这里你就可以找到 AIR 的调试启动器了:

右键,打开属性,在详细信息选项卡里你就能看到产品版本号了:

那么替换版本号,保存之后尝试编译一下,应该就成功了~

最终步:打包工程为 AIR 程序

当然最终我们是需要把它编译成发行版本的,只是自己玩算啥呢
那么为了可以打包成 AIR,我们首先需要一个签名文件,这个签名文件只需要生成一次就可以了(实际上每隔一年就需要重新生成一份)
而且为了方便,FD 为我们生成了一些 .bat 批处理文件来帮助我们打包和生成签名
这些批处理存放在你的工程文件夹中的 bat 文件夹里:

而下面的那个 .p12 文件就是我们待会需要的签名文件了
需要获取新的签名文件,直接双击 CreateCertificate.bat 运行批处理即可
生成完毕后就剩下最终的打包文件了
双击 PackageApp.bat 来全自动打包文件,稍等片刻后,在工程文件夹中的 air 文件夹里就会出现我们需要的 .air 打包文件了
唯一剩下的就是你如何去发布了~

↓ 继续阅读 ↓

第一个博客!

虽然不知道要写什么,不过还是介绍一下自己吧:

我是谁?

大家好,我叫 SteveXMH (史蒂夫·萧),还是一个在校学生,也是一个编程蒟蒻(真)。

我特别喜欢玩 Minecraft (我的世界),但是流行的一些 MOBA、FPS 一类的都是去当快递的。
可能是 MC 快要在国内冷淡了,已经没有多少人在玩了,甚至是多模组生存也没有人愿意肝了,实在是遗憾啊。。。

我都有哪些作品?

SteveScratch

我的一个二次开发版本的 Scratch 2.0,现在早已无人问津(哭)

SteveScratch 首发贴
SteveScratch 第二弹
SteveScratch 源码库(已归档)
SteveScratch Github 源码库(非官方)

scratch-script

一个我自认为很优雅的玩具编程语言,用于将代码转换成 Scratch 作品,并且可以完全线下编写,不必拖拽模块。

scratch-judge

一个满足想拿 Scratch 3.0 做信息竞赛题目的骚人们的 judge 程序,用 js 编写。

好像就这么多了(我太难了)

我会发啥呢?

大多数情况我会想发一些我的编程灵感,也许我的能力有限,但是可以留在博客里参考,就当作是个数据库吧~
也许我会一个星期一个星期的写那么一两天,但是对于三分钟热度的我来说也许明天就忘了吧(笑)。
不管怎样,有想写的我都会往这里丢的,谢谢啦~

我的平台账户

Bilibili
Github
Tieba
Afdian
Scratch
Roblox
Aerfaying

↓ 继续阅读 ↓