Typora Go 版补丁工具完整使用指南
本文档记录如何使用 Go 源码对 Windows 平台的 Typora 进行本地补丁处理、授权注入以及后续的官方原版回滚还原。
一、核心工作原理
该 Go 补丁工具主要执行以下自动化处理流程:
- 解包前端资源:读取并解包 Typora 目录下的
resources/app.asar,提取出独立的前端脚本目录(resources/app/)。 - 修改 Electron Fuses:修改
Typora.exe二进制文件中的特定标记位(将OnlyLoadAppFromAsar选项禁用为0x30),允许主程序直接加载解包后的目录代码。 - 注入网络与解密 Hook:在
launch.dist.js中注入逻辑,拦截客户端api/client/activate请求与更新检测,并在底层解密异常时返回本地构造的有效授权实体。 - 写入注册表状态:向
HKCU\Software\Typora写入SLicense和IDate注册表标记。 - 双向备份机制:自动生成
app.asar.bak与Typora.exe.bak,支持随时无损一键还原官方状态。
二、准备工作与环境配置
1. 验证 Go 编译环境
- 确认本地电脑已安装 Go(建议 Go 1.18 及以上版本)。
- 按
Win + R输入cmd或powershell,运行以下命令验证:go version
2. 获取 Typora 机器码
- 启动未激活的 Typora。
- 点击菜单栏 “帮助” -> “我的许可证”(或 “激活”)。
- 选择输入序列号界面下方的 “离线激活”(或从其他设备激活)入口。
- 复制弹窗中显示的 机器码(Machine Code) 保存备用。
三、保存源码与编译二进制文件
1. 获取源码文件
- 可以直接下载文末附件中的
main.go,或者直接复制文末附录中的完整源码。 - 将其保存在本地新建目录(如
D:\typora_patch\main.go),确保为 UTF-8 编码。
2. 编译可执行程序
以管理员身份打开终端(PowerShell 或 CMD),进入该目录执行编译:
cd /d D:\typora_patch
go build -o typora_patch.exe main.go
编译成功后,当前目录下会生成 typora_patch.exe。
四、执行补丁交互与授权验证
以管理员身份运行终端,执行生成的程序:
.\typora_patch.exe
程序启动后会进行控制台交互,按步骤操作:
- 确认安装路径:程序会自动检索注册表与缓存中的 Typora 路径。若正确直接回车;若未检测到,在弹出窗口中选中 Typora 根目录。
- 输入激活凭据:
- 机器码:粘贴刚才在 Typora 复制出来的机器码。
- 邮箱:输入任意有效格式的邮箱(例如
user@example.com)。
- 等待自动化注入:程序会自动备份、解包、修改 Fuses 并写入注册表。完成后控制台会打印生成的激活码
+XXXXXXXX#。 - 验证激活状态:启动 Typora,如未自动生效,使用生成的激活码与输入的邮箱提交确认。
五、一键回滚还原(恢复官方原版)
若后续需要升级官方版本或彻底清理修改:
- 再次以管理员身份运行
.\typora_patch.exe。 - 脚本检测到备份后会自动提示:
是否执行回滚还原?[Y/N]: - 输入
Y并回车,脚本会自动恢复原版app.asar、Typora.exe并清理注册表键值。
六、附件下载与完整源码
1. 源码附件直接下载
2. 完整源码备忘(附录)
package main
import (
"bufio"
"bytes"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"math/rand"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"syscall"
"time"
)
// ANSI color codes
const (
cReset = "\x1b[0m"
cCyan = "\x1b[36m"
cYellow = "\x1b[33m"
cGreen = "\x1b[32m"
cRed = "\x1b[31m"
cMagenta = "\x1b[35m"
cBlue = "\x1b[34m"
cCyanB = "\x1b[96m"
cYellowB = "\x1b[93m"
cGreenB = "\x1b[92m"
cRedB = "\x1b[91m"
cMagentaB = "\x1b[95m"
cBlueB = "\x1b[94m"
)
func main() {
if runtime.GOOS == "windows" {
runHidden("cmd.exe", "/c", "chcp", "65001")
}
typoraPath := ""
for {
typoraPath = chooseTyporaDir()
exePath := filepath.Join(typoraPath, "Typora.exe")
resourcesPath := filepath.Join(typoraPath, "resources")
asarPath := filepath.Join(resourcesPath, "app.asar")
if _, err := os.Stat(exePath); os.IsNotExist(err) {
fmt.Printf("%s❌ 错误:该目录下没有找到 Typora.exe!%s\n", cRed, cReset)
fmt.Printf("%s请确保选择的是 Typora 的根安装目录。%s\n\n", cYellow, cReset)
continue
}
if _, err := os.Stat(asarPath); os.IsNotExist(err) {
appDirCheck := filepath.Join(resourcesPath, "app")
asarBakCheck := filepath.Join(resourcesPath, "app.asar.bak")
_, errBak := os.Stat(asarBakCheck)
hasBak := errBak == nil
info, errAppDir := os.Stat(appDirCheck)
hasAppDir := errAppDir == nil && info.IsDir()
if hasBak || hasAppDir {
fmt.Printf("%s\n⚠️ 检测到 Typora 已经被激活过!%s\n", cYellowB, cReset)
fmt.Printf("%s是否执行回滚还原?[Y/N]:%s", cCyan, cReset)
choice := strings.TrimSpace(strings.ToUpper(readLine()))
if choice == "Y" {
rollbackTypora(typoraPath)
} else {
fmt.Printf("%s已取消回滚,退出脚本。%s\n", cGreen, cReset)
quit(0)
}
} else {
fmt.Printf("%s❌ 错误:未找到 resources/app.asar 文件!%s\n", cRed, cReset)
fmt.Printf("%s请确认这是完整的 Typora 安装目录。%s\n\n", cYellow, cReset)
continue
}
}
fmt.Printf("%s✅ 路径验证成功!准备开始激活...%s\n", cGreen, cReset)
break
}
machineCode, email := getMachineCodeAndEmail()
atobMachineCode := map[string]string{}
decoded, err := base64.StdEncoding.DecodeString(machineCode)
if err != nil {
fmt.Printf("%s💥 机器码解码失败:%s%s\n", cRed, err, cReset)
quit(1)
}
json.Unmarshal(decoded, &atobMachineCode)
fmt.Printf("%sdeviceId: %s%s\n", cBlueB, atobMachineCode["l"], cReset)
fmt.Printf("%sfingerprint: %s%s\n", cBlueB, atobMachineCode["i"], cReset)
fmt.Printf("%sversion: %s%s\n", cBlueB, atobMachineCode["v"], cReset)
nowDateStr := getNowDateStr()
enableHookDebug := false
closeTyporaProcesses()
time.Sleep(300 * time.Millisecond)
resourcesPath := filepath.Join(typoraPath, "resources")
asarPath := filepath.Join(resourcesPath, "app.asar")
appDir := filepath.Join(resourcesPath, "app")
appBakDir := filepath.Join(resourcesPath, "app.bak")
asarBakPath := filepath.Join(resourcesPath, "app.asar.bak")
typoraExe := filepath.Join(typoraPath, "Typora.exe")
launchDistJS := filepath.Join(appDir, "launch.dist.js")
err = func() error {
clearScreen()
fmt.Printf("%s\n✨ 正在启动 Typora 激活脚本...%s\n", cYellowB, cReset)
fmt.Printf("%s\n==== 开始处理... ====%s\n", cGreenB, cReset)
fmt.Printf("%s\n一、环境配置...%s\n", cBlueB, cReset)
if _, err := os.Stat(appDir); err == nil {
os.RemoveAll(appDir)
}
fmt.Printf("%s解包 app.asar%s\n", cBlue, cReset)
if err := extractAsar(asarPath, appDir); err != nil {
return fmt.Errorf("解包 app.asar 失败: %w", err)
}
fmt.Printf("%s备份 app 目录%s\n", cBlue, cReset)
if err := copyDir(appDir, appBakDir); err != nil {
return fmt.Errorf("备份 app 目录失败: %w", err)
}
fmt.Printf("%s备份原始 app.asar%s\n", cBlue, cReset)
if err := os.Rename(asarPath, asarBakPath); err != nil {
return fmt.Errorf("备份 app.asar 失败: %w", err)
}
fmt.Printf("%s备份原始 Typora.exe%s\n", cBlue, cReset)
if err := copyFile(typoraExe, typoraExe+".bak"); err != nil {
return fmt.Errorf("备份 Typora.exe 失败: %w", err)
}
fmt.Printf("%s配置 Electron Fuses...%s\n", cBlue, cReset)
if err := flipElectronFuse(typoraExe); err != nil {
return fmt.Errorf("配置 Electron Fuses 失败: %w", err)
}
fmt.Printf("%s✅ 环境配置完成!%s\n", cGreenB, cReset)
fmt.Printf("%s\n二、注入授权模块...%s\n", cBlueB, cReset)
contentBytes, err := os.ReadFile(launchDistJS)
if err != nil {
return fmt.Errorf("读取 launch.dist.js 失败: %w", err)
}
content := string(contentBytes)
injectCode := getInsertCode(enableHookDebug, atobMachineCode, email, nowDateStr)
re := regexp.MustCompile(`require\s*\((?:[^()]|\([^()]*\))*\)\s*;`)
match := re.FindStringIndex(content)
if match != nil {
content = content[:match[1]] + injectCode + content[match[1]:]
} else {
content = injectCode + content
}
if err := os.WriteFile(launchDistJS, []byte(content), 0644); err != nil {
return fmt.Errorf("写入 launch.dist.js 失败: %w", err)
}
fmt.Printf("%s✅ 注入完成!%s\n", cGreenB, cReset)
fmt.Printf("%s\n三、写入授权...%s\n", cBlueB, cReset)
writeRegValue("HKCU\\Software\\Typora", "SLicense", "RHJlYW1OeWE=#0#1/1/2059")
writeRegValue("HKCU\\Software\\Typora", "IDate", nowDateStr)
fmt.Printf("%s✅ 授权写入成功!%s\n", cGreenB, cReset)
fmt.Printf("%s\n🎉 全部完成!使用愉快!%s\n", cGreenB, cReset)
fmt.Printf("%s🔑 激活码:%s%s\n", cMagentaB, generateRegCode(), cReset)
return nil
}()
if err != nil {
fmt.Printf("%s\n💥 错误:%s%s\n", cRed, err, cReset)
fmt.Printf("%s\n🔄 正在执行自动回滚...%s\n", cYellowB, cReset)
rollbackTypora(typoraPath)
}
quit(0)
}
func extractAsar(asarPath, destDir string) error {
data, err := os.ReadFile(asarPath)
if err != nil {
return err
}
if len(data) < 8 {
return fmt.Errorf("asar 文件太小")
}
headerPayloadSize := int(binary.LittleEndian.Uint32(data[0:4]))
if headerPayloadSize != 4 {
return fmt.Errorf("非预期的 asar header 格式:payload size = %d", headerPayloadSize)
}
headerSize := int(binary.LittleEndian.Uint32(data[4:8]))
if 8+headerSize > len(data) {
return fmt.Errorf("asar header 超出文件范围")
}
headerPickle := data[8 : 8+headerSize]
if len(headerPickle) < 8 {
return fmt.Errorf("header pickle 太小")
}
strLen := int(int32(binary.LittleEndian.Uint32(headerPickle[4:8])))
if strLen < 0 || 8+strLen > len(headerPickle) {
return fmt.Errorf("非预期的 JSON 字符串长度: %d", strLen)
}
jsonStr := string(headerPickle[8 : 8+strLen])
var headerJSON map[string]interface{}
if err := json.Unmarshal([]byte(jsonStr), &headerJSON); err != nil {
return fmt.Errorf("解析 ASAR header JSON 失败: %w", err)
}
filesObj, ok := headerJSON["files"].(map[string]interface{})
if !ok {
return fmt.Errorf("ASAR header 缺少 files 对象")
}
if err := os.MkdirAll(destDir, 0755); err != nil {
return fmt.Errorf("创建目标目录失败: %w", err)
}
dataStart := int64(8 + headerSize)
var walk func(basePath string, files map[string]interface{}) error
walk = func(basePath string, files map[string]interface{}) error {
for name, v := range files {
info, ok := v.(map[string]interface{})
if !ok {
continue
}
fullPath := filepath.Join(basePath, name)
if subFiles, ok := info["files"]; ok {
subFilesMap, _ := subFiles.(map[string]interface{})
if err := os.MkdirAll(fullPath, 0755); err != nil {
return err
}
if err := walk(fullPath, subFilesMap); err != nil {
return err
}
continue
}
if link, ok := info["link"]; ok {
linkStr, _ := link.(string)
os.Remove(fullPath)
os.Symlink(linkStr, fullPath)
continue
}
sizeVal, ok1 := info["size"]
offsetStr, _ := info["offset"].(string)
if !ok1 || offsetStr == "" {
continue
}
intSize := int(sizeVal.(float64))
if intSize == 0 {
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
return err
}
os.WriteFile(fullPath, []byte{}, 0644)
continue
}
offset := int64(0)
fmt.Sscanf(offsetStr, "%d", &offset)
dataOffset := dataStart + offset
if dataOffset+int64(intSize) > int64(len(data)) {
return fmt.Errorf("文件 %s 数据超出范围", fullPath)
}
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
return err
}
if err := os.WriteFile(fullPath, data[dataOffset:dataOffset+int64(intSize)], 0644); err != nil {
return err
}
if execOk, _ := info["executable"].(bool); execOk {
os.Chmod(fullPath, 0755)
}
}
return nil
}
return walk(destDir, filesObj)
}
func copyFile(src, dst string) error {
s, err := os.Open(src)
if err != nil {
return err
}
defer s.Close()
d, err := os.Create(dst)
if err != nil {
return err
}
defer d.Close()
_, err = io.Copy(d, s)
return err
}
func copyDir(src, dest string) error {
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, _ := filepath.Rel(src, path)
destPath := filepath.Join(dest, relPath)
if info.IsDir() {
return os.MkdirAll(destPath, info.Mode())
}
return copyFile(path, destPath)
})
}
const fuseSentinel = "dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX"
func flipElectronFuse(exePath string) error {
data, err := os.ReadFile(exePath)
if err != nil {
return fmt.Errorf("读取 EXE 失败: %w", err)
}
sentinelIdx := bytes.Index(data, []byte(fuseSentinel))
if sentinelIdx == -1 {
return fmt.Errorf("未找到 Electron fuse sentinel")
}
fuseOffset := sentinelIdx + len(fuseSentinel)
if fuseOffset+2 >= len(data) {
return fmt.Errorf("fuse 数据不完整")
}
version := data[fuseOffset]
length := int(data[fuseOffset+1])
const OnlyLoadAppFromAsar = 5
if version == 1 && length > OnlyLoadAppFromAsar {
if fuseOffset+2+length > len(data) {
return fmt.Errorf("fuse wire 数据超出范围")
}
data[fuseOffset+2+OnlyLoadAppFromAsar] = 0x30
} else {
return fmt.Errorf("不支持的 fuse 版本: %d, length: %d", version, length)
}
if err := os.WriteFile(exePath, data, 0644); err != nil {
return fmt.Errorf("写入 EXE 失败: %w", err)
}
return nil
}
func writeRegValue(key, name, value string) {
if runtime.GOOS != "windows" {
return
}
cmd := exec.Command("reg", "add", key, "/v", name, "/t", "REG_SZ", "/d", value, "/f")
cmd.Stdout = nil
cmd.Stderr = nil
cmd.Stdin = nil
cmd.SysProcAttr = windowsHide()
if err := cmd.Run(); err != nil {
panic(fmt.Errorf("reg add %s\\%s 失败", key, name))
}
}
func deleteRegValue(key, name string) {
if runtime.GOOS != "windows" {
return
}
cmd := exec.Command("reg", "delete", key, "/v", name, "/f")
cmd.Stdout = nil
stderr := &strings.Builder{}
cmd.Stderr = stderr
cmd.Stdin = nil
cmd.SysProcAttr = windowsHide()
if err := cmd.Run(); err != nil {
stderrStr := stderr.String()
if strings.Contains(stderrStr, "unable to find") {
return
}
fmt.Printf("%s⚠ 删除注册表值 %s 失败(可忽略): %s%s\n", cYellow, name, strings.TrimSpace(stderrStr), cReset)
}
}
func windowsHide() *syscall.SysProcAttr {
return &syscall.SysProcAttr{HideWindow: true}
}
func runHidden(name string, args ...string) {
cmd := exec.Command(name, args...)
cmd.Stdout = nil
cmd.Stderr = nil
cmd.Stdin = nil
cmd.SysProcAttr = windowsHide()
cmd.Run()
}
func clearScreen() {
fmt.Print("\x1b[2J\x1b[H")
}
func quit(code int) {
fmt.Printf("%s\n按任意键退出...%s", cCyan, cReset)
bufio.NewReader(os.Stdin).ReadBytes('\n')
os.Exit(code)
}
func readLine() string {
reader := bufio.NewReader(os.Stdin)
line, _ := reader.ReadString('\n')
return strings.TrimRight(line, "\r\n")
}
func generateRegCode() string {
chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
code := "+"
for i := 0; i < 8; i++ {
code += string(chars[rand.Intn(len(chars))])
}
return code + "#"
}
func getNowDateStr() string {
now := time.Now()
return fmt.Sprintf("%02d/%02d/%d", now.Month(), now.Day(), now.Year())
}
func closeTyporaProcesses() {
fmt.Printf("%s正在关闭 Typora 进程...%s\n", cBlue, cReset)
runHidden("taskkill", "/IM", "Typora.exe")
time.Sleep(500 * time.Millisecond)
cmd := exec.Command("taskkill", "/F", "/IM", "Typora.exe")
cmd.Stdout = nil
cmd.Stderr = nil
cmd.Stdin = nil
cmd.SysProcAttr = windowsHide()
if cmd.Run() == nil {
fmt.Printf("%s✅ 已关闭所有 Typora 进程%s\n", cGreen, cReset)
} else {
fmt.Printf("%s⚠ 未检测到运行中的 Typora.exe%s\n", cYellow, cReset)
}
}
type cacheConfig struct {
Path string `json:"path"`
MachineCode string `json:"machineCode"`
Email string `json:"email"`
}
func configPath() string {
home, _ := os.UserHomeDir()
return filepath.Join(home, ".typora_crack_config.json")
}
func readCache() cacheConfig {
data, err := os.ReadFile(configPath())
if err != nil {
return cacheConfig{}
}
var cfg cacheConfig
json.Unmarshal(data, &cfg)
if cfg.Path != "" {
if _, err := os.Stat(filepath.Join(cfg.Path, "Typora.exe")); os.IsNotExist(err) {
cfg.Path = ""
}
}
return cfg
}
func saveCache(cfg cacheConfig) {
data, _ := json.MarshalIndent(cfg, "", " ")
os.WriteFile(configPath(), data, 0644)
}
func readRegPath() string {
if runtime.GOOS != "windows" {
return ""
}
cmd := exec.Command("reg", "query",
`HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\Typora.exe`,
"/ve")
cmd.Stdin = nil
cmd.SysProcAttr = windowsHide()
out, err := cmd.Output()
if err != nil {
return ""
}
output := string(out)
lines := strings.Split(output, "\n")
for _, line := range lines {
idx := strings.Index(line, "REG_SZ")
if idx == -1 {
idx = strings.Index(line, "REG_EXPAND_SZ")
if idx == -1 {
continue
}
}
rest := strings.TrimSpace(line[idx:])
parts := strings.SplitN(rest, " ", 2)
if len(parts) < 2 {
continue
}
p := strings.TrimSpace(parts[1])
if strings.HasSuffix(strings.ToLower(p), "typora.exe") {
p = filepath.Dir(p)
}
p = filepath.Clean(p)
if _, err := os.Stat(filepath.Join(p, "Typora.exe")); err == nil {
return p
}
}
return ""
}
func chooseTyporaDir() string {
clearScreen()
fmt.Printf("%s=================================%s\n", cCyanB, cReset)
fmt.Printf("%s Typora 激活脚本 %s\n", cMagenta, cReset)
fmt.Printf("%s=================================%s\n", cCyanB, cReset)
cache := readCache()
if cache.Path != "" {
fmt.Printf("%s✅ 检测到缓存路径: %s%s\n", cGreen, cache.Path, cReset)
fmt.Printf("%s是否使用此路径?[Y/n]:%s", cCyan, cReset)
use := strings.TrimSpace(strings.ToLower(readLine()))
if use != "n" && use != "no" {
fmt.Printf("%s✅ 已选择:%s%s\n", cGreenB, cache.Path, cReset)
return cache.Path
}
fmt.Printf("%s已跳过缓存路径,正在尝试从注册表读取...%s\n", cYellow, cReset)
}
regPath := readRegPath()
if regPath != "" {
fmt.Printf("%s✅ 从注册表检测到路径: %s%s\n", cGreen, regPath, cReset)
fmt.Printf("%s是否使用此路径?[Y/n]:%s", cCyan, cReset)
use := strings.TrimSpace(strings.ToLower(readLine()))
if use != "n" && use != "no" {
fmt.Printf("%s✅ 已选择:%s%s\n", cGreenB, regPath, cReset)
saveCache(cacheConfig{Path: regPath, MachineCode: cache.MachineCode, Email: cache.Email})
return regPath
}
fmt.Printf("%s已跳过注册表路径,进入文件夹选择窗口...%s\n", cYellow, cReset)
} else {
fmt.Printf("%s未在注册表中找到 Typora,准备打开文件夹选择窗口...%s\n", cYellow, cReset)
}
selected, err := folderBrowserDialog()
if err == nil && selected != "" {
fmt.Printf("%s✅ 已选择:%s%s\n", cGreenB, selected, cReset)
saveCache(cacheConfig{Path: selected, MachineCode: cache.MachineCode, Email: cache.Email})
return selected
}
fmt.Printf("%s\n❌ 图形窗口不可用,切换手动输入%s\n", cRedB, cReset)
return manualInputPath(cache)
}
func manualInputPath(cache cacheConfig) string {
for {
fmt.Printf("%s请输入 Typora 安装目录路径(例如 C:\\Program Files\\Typora): %s", cCyan, cReset)
input := strings.TrimSpace(readLine())
if input == "" {
fmt.Printf("%s路径不能为空,请重新输入!%s\n", cRed, cReset)
continue
}
res := filepath.Clean(input)
if _, err := os.Stat(filepath.Join(res, "Typora.exe")); err == nil {
saveCache(cacheConfig{Path: res, MachineCode: cache.MachineCode, Email: cache.Email})
return res
}
fmt.Printf("%s❌ 该路径下无效,请重新输入!%s\n", cRed, cReset)
}
}
func folderBrowserDialog() (string, error) {
psScript := `
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;
$OutputEncoding = [System.Text.Encoding]::UTF8;
Add-Type -AssemblyName System.Windows.Forms;
$fbd=New-Object System.Windows.Forms.FolderBrowserDialog;
$fbd.Description='请选择 Typora 安装目录';
$fbd.ShowNewFolderButton=$false;
if($fbd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){
Write-Output $fbd.SelectedPath
}
`
cmd := exec.Command("powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psScript)
cmd.Stdin = nil
cmd.Stderr = nil
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: false}
out, err := cmd.Output()
if err != nil {
return "", err
}
result := strings.TrimSpace(string(out))
if result == "" {
return "", fmt.Errorf("未选择路径")
}
return filepath.Clean(result), nil
}
func getMachineCodeAndEmail() (string, string) {
cache := readCache()
if cache.MachineCode != "" && cache.Email != "" {
fmt.Printf("%s\n✅ 检测到缓存的激活信息:%s\n", cGreen, cReset)
fmt.Printf("%s 机器码: %s%s\n", cGreen, cache.MachineCode, cReset)
fmt.Printf("%s 邮箱: %s%s\n", cGreen, cache.Email, cReset)
fmt.Printf("%s是否使用此信息?[Y/n]:%s", cCyan, cReset)
use := strings.TrimSpace(strings.ToLower(readLine()))
if use != "n" && use != "no" {
fmt.Printf("%s✅ 已使用缓存的激活信息!%s\n", cGreenB, cReset)
return cache.MachineCode, cache.Email
}
fmt.Printf("%s已跳过缓存的激活信息,进入手动输入...%s\n", cYellow, cReset)
}
var machineCode, email string
for {
fmt.Printf("%s\n请输入机器码: %s", cCyanB, cReset)
machineCode = strings.TrimSpace(readLine())
fmt.Printf("%s请输入邮箱: %s", cCyanB, cReset)
email = strings.TrimSpace(readLine())
if machineCode == "" {
fmt.Printf("%s❌ 机器码不能为空,请重新输入!%s\n", cRed, cReset)
continue
}
if email == "" {
fmt.Printf("%s❌ 邮箱不能为空,请重新输入!%s\n", cRed, cReset)
continue
}
break
}
saveCache(cacheConfig{
Path: cache.Path,
MachineCode: machineCode,
Email: email,
})
return machineCode, email
}
func rollbackTypora(typoraPath string) {
fmt.Printf("%s\n🔄 开始执行回滚还原...%s\n", cYellowB, cReset)
resourcesPath := filepath.Join(typoraPath, "resources")
asarPath := filepath.Join(resourcesPath, "app.asar")
asarBakPath := filepath.Join(resourcesPath, "app.asar.bak")
appDir := filepath.Join(resourcesPath, "app")
appBakDir := filepath.Join(resourcesPath, "app.bak")
typoraExe := filepath.Join(typoraPath, "Typora.exe")
exeBakPath := typoraExe + ".bak"
closeTyporaProcesses()
time.Sleep(300 * time.Millisecond)
if _, err := os.Stat(asarBakPath); err == nil {
os.Remove(asarPath)
os.Rename(asarBakPath, asarPath)
fmt.Printf("%s✅ 已还原 app.asar%s\n", cGreen, cReset)
}
if _, err := os.Stat(exeBakPath); err == nil {
os.Remove(typoraExe)
os.Rename(exeBakPath, typoraExe)
fmt.Printf("%s✅ 已还原 Typora.exe%s\n", cGreen, cReset)
}
if _, err := os.Stat(appDir); err == nil {
os.RemoveAll(appDir)
fmt.Printf("%s✅ 已删除解包 app 目录%s\n", cGreen, cReset)
}
if _, err := os.Stat(appBakDir); err == nil {
os.RemoveAll(appBakDir)
}
deleteRegValue("HKCU\\Software\\Typora", "SLicense")
deleteRegValue("HKCU\\Software\\Typora", "IDate")
fmt.Printf("%s✅ 已清理授权信息%s\n", cGreen, cReset)
fmt.Printf("%s\n🎉 回滚完成!已恢复为官方原版!%s\n", cGreenB, cReset)
quit(0)
}
func getInsertCode(enableHookDebug bool, atobMachineCode map[string]string, email, nowDateStr string) string {
actEntity := map[string]string{
"deviceId": atobMachineCode["l"],
"fingerprint": atobMachineCode["i"],
"email": email,
"license": "Cracked_By_DreamNya",
"version": atobMachineCode["v"],
"date": nowDateStr,
"type": "DreamNya",
}
actEntityJSON, _ := json.Marshal(actEntity)
standardLicenseMsg := base64.StdEncoding.EncodeToString(actEntityJSON)
actEntityJSONStr := string(actEntityJSON)
enableDebugStr := "false"
if enableHookDebug {
enableDebugStr = "true"
}
return fmt.Sprintf(`
/** Hook破解开始 */
const electron = require("electron");
if (%s) {
Object.defineProperty(electron.app, "quit", {
value: function () {},
writable: true,
configurable: true,
});
}
electron.app.on("browser-window-created", (_event, win) => {
win.webContents.once("dom-ready", () => {
if (%s) win.webContents.openDevTools({ mode: "detach" });
});
});
const fsPathFrom = /resources[\\/]app[\\/]/i;
const fsPathTo = "resources\\app.bak\\";
const fsHook = {};
[
"readFileSync","readFile","statSync","stat","Stats","StatsFs","open","openSync"
].forEach((property) => {
fsHook[property] = fs[property];
fs[property] = function (filePath, ...args) {
if (typeof filePath == "string" && fsPathFrom.test(filePath)) {
return fsHook[property].call(this, filePath.replace(fsPathFrom, fsPathTo), ...args);
}
return fsHook[property].call(this, filePath, ...args);
};
});
const fsPromisesHook = {};
["readFile","open","stat"].forEach((property) => {
fsPromisesHook[property] = fs.promises[property];
fs.promises[property] = async function (filePath, ...args) {
if (typeof filePath == "string" && fsPathFrom.test(filePath)) {
return fsPromisesHook[property].call(this, filePath.replace(fsPathFrom, fsPathTo), ...args);
}
return fsPromisesHook[property].call(this, filePath, ...args);
};
});
const crypto = require("crypto");
const originalPublicDecrypt = crypto.publicDecrypt;
crypto.publicDecrypt = function (key, buffer) {
try {
return originalPublicDecrypt.call(this, key, buffer);
} catch (e) {
return Buffer.from(JSON.stringify(%s));
}
};
(function registerProtocolHandler() {
try {
electron.protocol.handle("https", async (request) => {
if (request.url.includes('releases/windows_64.json')) {
return new Response(JSON.stringify({ name: "typora", version: "0.0.0", releaseNoteLink: "", download: "", downloadCN: "", alternatives: {} }), { status: 200 });
}
if (request.url.includes('api/client/activate') || request.url.includes('api/client/renew')) {
return new Response(JSON.stringify({
success: true, code: 0, retry: true, msg: "%s"
}), { status: 200 });
}
return electron.net.fetch(request, { bypassCustomProtocolHandlers: true });
});
} catch (_) {
electron.app.whenReady().then(() => {
try {
electron.protocol.handle("https", async (request) => {
if (request.url.includes('releases/windows_64.json')) {
return new Response(JSON.stringify({ name: "typora", version: "0.0.0", releaseNoteLink: "", download: "", downloadCN: "", alternatives: {} }), { status: 200 });
}
if (request.url.includes('api/client/activate') || request.url.includes('api/client/renew')) {
return new Response(JSON.stringify({
success: true, code: 0, retry: true, msg: "%s"
}), { status: 200 });
}
return electron.net.fetch(request, { bypassCustomProtocolHandlers: true });
});
} catch (_2) {}
});
}
})();
/** Hook破解结束 */
`, enableDebugStr, enableDebugStr, actEntityJSONStr, standardLicenseMsg, standardLicenseMsg)
}