diff --git a/README.md b/README.md index 584d873..36f092f 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,4 @@ Collection of tools used by administrators from VN Telegram Channel * `FFMpeg-Compressor` - Python utility uses ffmpeg to compress Visual Novel Resources * `RenPy-Android-Unpack` - A simple Python script for unpacking Ren'Py based .apk and .obb files to ready to use Ren'Py SDK's Project * `RenPy-Unpacker` - Simple .rpy script that will make any RenPy game unpack itself +* `VNDS-to-RenPy` - Simple script for converting vnds scripts to rpy diff --git a/VNDS-to-RenPy/convert.py b/VNDS-to-RenPy/convert.py new file mode 100644 index 0000000..3a7cc54 --- /dev/null +++ b/VNDS-to-RenPy/convert.py @@ -0,0 +1,352 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python +# +# Automatically converts VNDS to Ren'Py. + +import os +import zipfile +from io import open + +# Sets of variables found in the game. +global_variables = set() +game_variables = set() + +def unjp(s): + print(s) + white = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890" + for a in s: + if a not in white: + s = s.replace(a, str(ord(a))) + if s[0].isdigit(): + s = "var" + s + print(s) + return s + +def scan_gsetvar(l): + l = l.split()[0] + l = unjp(l) + global_variables.add(l) + +def scan_setvar(l): + if l[0] == "~": + return + + l = l.split()[0] + l = unjp(l) + game_variables.add(l) + +with zipfile.ZipFile("script.zip", 'r') as zip_ref: + zip_ref.extractall() + +def scan_script(infn): + inf = open("script/" + infn, encoding="utf-8") + + for l in inf: + l = l.replace("\xef\xbb\xbf", "") + l = l.strip() + + if not l: + continue + + if " " in l: + command, l = l.split(' ', 1) + else: + command = l + l = "" + + if "scan_" + command in globals(): + globals()["scan_" + command](l) + + inf.close() + + +class ConvertState(object): + + def __init__(self, outf, shortfn=""): + self.outf = outf + self.depth = 0 + self.shortfn = shortfn + self.textbuffer = "" + self.empty_block = False + self.imindex = 0 + + def write(self, s, *args): + + + self.outf.write(" " * self.depth + "\n") + + line = " " * self.depth + s % args + "\n" + + if isinstance(line, str): + line = line.encode("utf-8") + + self.outf.write(line.decode("utf-8")) + + self.empty_block = False + + def indent(self): + self.depth += 1 + + def outdent(self): + self.depth -= 1 + +def convert_endscript(cs, l): + cs.write("return") + +def convert_label(cs, l): + l = l.replace("*", "_star_") + l = l.replace("-", "_") + l = cs.shortfn + "_" + l + + cs.outdent() + cs.write("label %s:", l) + cs.indent() + +def convert_goto(cs, l): + l = l.replace("*", "_star_") + l = cs.shortfn + "_" + l + l = l.replace("-", "_") + + cs.write("jump %s", l) + +def convert_choice(cs, l): + choices = l.split("|") + + cs.write("menu:") + cs.indent() + + for i, c in enumerate(choices): + cs.write("%r:", c) + cs.indent() + cs.write("$ selected = %d", i + 1) + cs.outdent() + + cs.outdent() + +def convert_setvar(cs, l): + if l[0] == "~": + return + + print(f"l = {l}") + if l == 'chain = "main.scr start"': + var, op, val = "chain", "=", "main.scr start" + else: + var, op, val = l.split() + var = unjp(var) + + if op == "=": + cs.write("$ %s = %s", var, val) + elif op == "+": + cs.write("$ %s += %s", var, val) + elif op == "-": + cs.write("$ %s -= %s", var, val) + else: + raise Exception("Unknown operation " + op) + +def convert_gsetvar(cs, l): + if l[0] == "~": + return + + var, op, val = l.split() + var = unjp(var) + + if op == "=": + cs.write("$ persistent.%s = %s", var, val) + elif op == "+": + cs.write("$ persistent.%s += %s", var, val) + elif op == "-": + cs.write("$ persistent.%s -= %s", var, val) + else: + raise Exception("Unknown operation " + op) + +def convert_if(cs, l): + var, rest = l.strip().split(' ', 1) + var = unjp(var) + + if var in global_variables: + var = "persistent." + var + + cs.write("if %s %s:", var, rest) + cs.indent() + + cs.empty_block = True + +def convert_fi(cs, l): + if cs.empty_block: + cs.write("pass") + + cs.outdent() + +def convert_sound(cs, l): + if l == "~": + cs.write("stop sound") + else: + l = "sound/" + l + cs.write("play sound \"%s\"", l) + +def convert_setimg(cs, l): + fn, x, y = l.split() + x = int(x) + y = int(y) + fn = "foreground/" + fn + + cs.write("show expression %r as i%d at fgpos(%d, %d)", fn, cs.imindex, x, y) + cs.imindex += 1 + +def convert_delay(cs, l): + try: + t = int(l) / 60.0 + cs.write("pause %f", t) + except: + pass + + + +def convert_bgload(cs, fn): + + if " " in fn: + fn, delay = fn.split(" ", 1) + delay = int(delay) / 60.0 + else: + delay = 0.5 + + assert " " not in fn + + cs.write("nvl clear") + fn = "background/" + fn + cs.write("scene expression %r", fn) + cs.imindex = 0 + + if delay: + cs.write("with Dissolve(%f)", delay) + +def convert_jump(cs, fn): + if " " in fn: + fn, l = fn.split(" ", 1) + l = l.replace("*", "_star_") + l = l.replace("-", "_") + fn = fn.replace(".scr", "").replace("-", "_") + cs.write("jump %s_%s", fn, l) + return + + fn = fn.replace(".scr", "").replace("-", "_") + cs.write("jump %s", fn) + +def convert_music(cs, fn): + if " " in fn: + fn, loops = fn.split(" ", 1) + else: + loops = 0 + + if int(loops) > 0: + noloop = " noloop" + else: + noloop = "" + + if fn == "~": + cs.write("stop music") + else: + fn = "sound/" + fn + cs.write("play music %r%s", fn, noloop) + +def convert_text(cs, text): + + while text and (text[0] == "~" or text[0] == "!"): + text = text[1:] + + if not text: + return + + if text[0] == "@": + cs.textbuffer += text[1:] + "\n" + return + + text = cs.textbuffer + text + text = text.replace("\\", "\\\\") + text = text.replace("\"", "\\\"") + text = text.replace("\n", "\\n") + + cs.write('"%s"', text) + cs.textbuffer = "" + +def convert_script(infn): + dir_rp = "rpy/" + if not os.path.exists(dir_rp): + os.mkdir(dir_rp) + + if os.path.exists(dir_rp): + shortfn = infn.replace(".scr", "") + shortfn = shortfn.replace("-", "_") + inf = open("script/" + infn, encoding="utf-8") + outf = open (dir_rp + shortfn + ".rpy", "w", encoding = "utf-8") + cs = ConvertState(outf, shortfn) + cs.write("label %s:", shortfn) + cs.indent() + + for l in inf: + l = l.replace("\xef\xbb\xbf", "") + l = l.strip() + + if not l: + continue + + if l[0] == "#": + continue + + if " " in l: + command, l = l.split(' ', 1) + else: + command = l + l = "" + + if "convert_" + command in globals(): + globals()["convert_" + command](cs, l) + else: + print("Unknown command", repr(command), repr(l), repr(infn)) + + outf.close() + +def main(): + for i in os.listdir("script"): + if not i.endswith(".scr"): + continue + + scan_script(i) + + for i in os.listdir("script"): + if not i.endswith(".scr"): + continue + + convert_script(i) + + outf = open("rpy/_start.rpy", "w", encoding="utf-8") + cs = ConvertState(outf) + cs.write("init python:") + cs.indent() + + for i in global_variables: + i = unjp(i) + cs.write("if persistent.%s is None:", i) + cs.indent() + cs.write("persistent.%s = 0", i) + cs.outdent() + + # prevent non-empty block. + cs.write("pass") + + cs.outdent() + cs.write("label start:") + cs.indent() + + for i in game_variables: + i = unjp(i) + cs.write("$ %s = 0", i) + + cs.write("window show") + cs.write("jump main") + + cs.outdent() + outf.close() + +if __name__ == "__main__": + main()