go语言实战之解密ons脚本

作于: 2018 年 12 月 16 日,预计阅读时间 6 分钟

Intro

ons 是一个开放源代码的视觉小说引擎,以简单实用出名。本博用 golang 来解密 ons 引擎的.dat.nt2脚本,主要实践目标是异步解密输出。

算法

.dat的加密非常简单,一次异或。密码是0x84

可以用 go 非常简单粗暴地写出以下代码。

for i := 0; i < len(buf); i++ {
    buf[i] ^= 0x84
}

.nt2的加密同样简单,一次异或,密码是0x85 & 0x97

可以用 go 非常粗暴地写出以下代码。

for i := 0; i < len(buf); i++ {
    buf[i] = (buf[i] ^ (0x85 & 0x97)) - 1
}

异步读文件

go 方式比较多,ioutil或者bufio或者os都有文件模块。这里采用bufioos.Open的方式读文件。

func readFile(p string, outChannel chan []byte) {
    // 只读方式打开文件
	file, err := os.OpenFile(p, os.O_RDONLY, 0644)
	if err != nil {
		panic(err)
	}

    // 包装一层 bufio
    reader := bufio.NewReader(file)
    // 准备一个保存读取结果的buf
	var buf = new([1024000]byte)

	for {
        // 循环读
		n, err := reader.Read(buf[:])

        // 没有内容了就退出循环
		if n == 0 {
			break
		}

		if err != nil {
			panic(err)
        }

        // 把读到的结果用 channel 传递给下一道处理工序
		outChannel <- buf[:n]
	}

	defer func() {
		close(outChannel)
		err := file.Close()
		if err != nil {
			panic(err)
		}
	}()
}

异步写文件

写文件的方式和读文件的方式差不多,由那几个包提供。

func writeFile(p string, outChannel chan []byte, done chan bool) {
    // 只读方式打开文件,已存在文件则清空内容,未存在文件则创建,文件权限 rw-r--r--
	file, err := os.OpenFile(p, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
	if err != nil {
		panic(err)
	}

	writer := bufio.NewWriter(file)

	for {
        // 从上一道工序取得解密后的数据
        buf, more := <-outChannel
        // 如果所有数据全部取得,则结束写入
		if !more {
			break
		}

        // 写入
		_, err := writer.Write(buf)
		if err != nil {
			panic(err)
		}
	}

	defer func() {
		err := writer.Flush()
		if err != nil {
			panic(err)
		}

		err = file.Close()
		if err != nil {
			panic(err)
		}

		done <- true
	}()
}

异步解密

解密过程就像是水管上的过滤器,水流进来处理好,流出去。内容乏善可陈,就直接丢代码好了。

func decodeDat(inChannel chan []byte, outChannel chan []byte) {
	for {
		buf, more := <-inChannel
		if !more {
			break
		}

		for i := 0; i < len(buf); i++ {
			buf[i] ^= 0x84
		}
		outChannel <- buf
	}

	defer func() {
		close(outChannel)
	}()
}

func decodeNt2(inChannel chan []byte, outChannel chan []byte) {
	for {
		buf, more := <-inChannel
		if !more {
			break
		}

		for i := 0; i < len(buf); i++ {
			buf[i] = (buf[i] ^ (0x85 & 0x97)) - 1
		}
		outChannel <- buf
	}

	defer func() {
		close(outChannel)
	}()
}

调度

严肃地说,我认为这种调度模式是显然不对的。正确的调度方式应该是这样。

graph TB;
	A[cli] --> |启动|B[read worker];
	A --> |启动| C[write worker];
	A --> |启动| D[decode worker];
	A --> |启动| E[scheduler];
	E --> |发出读指令| B;
	B --> |发送来源标识符+内容| D;
	D --> |发送来源标识符+处理后的内容| C;

对于有多个 worker 的情况,也需要调度器协调才行,不过直觉上来说硬盘读写性能会是先一步的瓶颈。

func main() {
	for i := 1; i < len(os.Args); i++ {
		decodeChannel, outChannel, done := make(chan []byte), make(chan []byte), make(chan bool)
		p := os.Args[i]
		go readFile(p, decodeChannel)

		switch path.Ext(p) {
		case ".dat":
			go decodeDat(decodeChannel, outChannel)
		case ".nt2":
			go decodeNt2(decodeChannel, outChannel)
		default:
			log.Println(path.Ext(p))
			panic("Input file should be .dat or .nt2 encrypted script!")
		}

		go writeFile(path.Base(p)+".txt", outChannel, done)
		<-done
	}
}

完整代码:

package main

import (
	"bufio"
	"log"
	"os"
	"path"
)

func main() {
	for i := 1; i < len(os.Args); i++ {
		decodeChannel, outChannel, done := make(chan []byte), make(chan []byte), make(chan bool)
		p := os.Args[i]
		go readFile(p, decodeChannel)

		switch path.Ext(p) {
		case ".dat":
			go decodeDat(decodeChannel, outChannel)
		case ".nt2":
			go decodeNt2(decodeChannel, outChannel)
		default:
			log.Println(path.Ext(p))
			panic("Input file should be .dat or .nt2 encrypted script!")
		}

		go writeFile(path.Base(p)+".txt", outChannel, done)
		<-done
	}
}

func readFile(p string, outChannel chan []byte) {
	file, err := os.OpenFile(p, os.O_RDONLY, 0644)
	if err != nil {
		panic(err)
	}

	reader := bufio.NewReader(file)
	var buf = new([1024000]byte)

	for {
		n, err := reader.Read(buf[:])

		if n == 0 {
			break
		}

		if err != nil {
			panic(err)
		}
		outChannel <- buf[:n]
	}

	defer func() {
		close(outChannel)
		err := file.Close()
		if err != nil {
			panic(err)
		}
	}()
}

func writeFile(p string, outChannel chan []byte, done chan bool) {
	file, err := os.OpenFile(p, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
	if err != nil {
		panic(err)
	}

	writer := bufio.NewWriter(file)

	for {
		buf, more := <-outChannel
		if !more {
			break
		}

		_, err := writer.Write(buf)
		if err != nil {
			panic(err)
		}
	}

	defer func() {
		err := writer.Flush()
		if err != nil {
			panic(err)
		}

		err = file.Close()
		if err != nil {
			panic(err)
		}

		done <- true
	}()
}

func decodeDat(inChannel chan []byte, outChannel chan []byte) {
	for {
		buf, more := <-inChannel
		if !more {
			break
		}

		for i := 0; i < len(buf); i++ {
			buf[i] ^= 0x84
		}
		outChannel <- buf
	}

	defer func() {
		close(outChannel)
	}()
}

func decodeNt2(inChannel chan []byte, outChannel chan []byte) {
	for {
		buf, more := <-inChannel
		if !more {
			break
		}

		for i := 0; i < len(buf); i++ {
			buf[i] = (buf[i] ^ (0x85 & 0x97)) - 1
		}
		outChannel <- buf
	}

	defer func() {
		close(outChannel)
	}()
}

/golang/ /ons/