summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--LICENSE4
-rw-r--r--unrpyc/LICENSE17
-rw-r--r--unrpyc/Makefile16
-rw-r--r--unrpyc/README15
-rw-r--r--unrpyc/decompiler.py499
-rwxr-xr-xunrpyc/fix.js6
-rw-r--r--unrpyc/unrpyc.py140
-rw-r--r--www/js/api.js121
-rw-r--r--www/js/html5ks.js39
-rw-r--r--www/js/menu.js4
11 files changed, 790 insertions, 73 deletions
diff --git a/.gitignore b/.gitignore
index ce5fbff..4a9a913 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+/unrpyc/renpy
# ignore 'copyrighted' contents similar to emulators
+/www/scripts
/www/dump
/rpy
diff --git a/LICENSE b/LICENSE
index 8ea27f6..c3bb2aa 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,6 @@
Copyright © 2013 Alex Xu (Hello71)
All Rights Reserved
-I haven't decided on an open-source license; contact me to discuss.
+I haven't decided on an open-source license; contact me [0] to discuss.
+
+[0] mailto:alex.hello71@gmail.com
diff --git a/unrpyc/LICENSE b/unrpyc/LICENSE
new file mode 100644
index 0000000..8a8ff9b
--- /dev/null
+++ b/unrpyc/LICENSE
@@ -0,0 +1,17 @@
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/unrpyc/Makefile b/unrpyc/Makefile
new file mode 100644
index 0000000..86dcac3
--- /dev/null
+++ b/unrpyc/Makefile
@@ -0,0 +1,16 @@
+all: $(patsubst %.rpyc,%.json,$(wildcard *.rpyc))
+
+%.json.o: %.rpyc *.py
+ python unrpyc.py --clobber $< $@
+
+%.json: %.json.o
+ node fix.js $< $@
+
+clean:
+ rm -f *.json.o *.json
+
+test: all
+ jshint --show-non-errors *.json
+
+.PHONY: all clean test
+.PRECIOUS: %.json.o
diff --git a/unrpyc/README b/unrpyc/README
new file mode 100644
index 0000000..c046fe8
--- /dev/null
+++ b/unrpyc/README
@@ -0,0 +1,15 @@
+This is a !@#$ed up version of unrpyc to generate sort-of JSON from Ren'Py .rpyc files.
+
+It's made for Katawa Shoujo script files; trying to use it on anything else will probably break horribly.
+
+How to use:
+
+1. Copy Ren'Py distribution into "renpy" folder.
+2. Execute "2to3 -w renpy/*.py".
+3. Put the rpyc files from the official KS distribution in here.
+4. Run "make" with appropriate -j options (yay auto-parallelization)
+5. Copy the resulting .json files to /www/scripts.
+
+If you want to hack on the code, have fun. This is all the documentation you get.
+
+https://github.com/yuriks/unrpyc
diff --git a/unrpyc/decompiler.py b/unrpyc/decompiler.py
new file mode 100644
index 0000000..c6046f6
--- /dev/null
+++ b/unrpyc/decompiler.py
@@ -0,0 +1,499 @@
+# Copyright (c) 2012 Yuri K. Schlesner
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import ast as python_ast
+import renpy.ast as ast
+import renpy.atl as atl
+
+DECOMPILE_SCREENS = False
+firstLabel = True
+warnedATL = False
+
+def pretty_print_ast(out_file, ast):
+ out_file.write('{')
+ for stmt in ast:
+ print_statement(out_file, stmt, 0)
+ out_file.write(']}')
+
+def indent(f, level):
+ # Print indentation
+ f.write(' ' * level)
+
+def print_statement(f, statement, indent_level=0):
+ indent(f, indent_level)
+
+ func = statement_printer_dict.get(type(statement), print_Unknown)
+ func(f, statement, indent_level)
+
+def escape_string(s):
+ s = s.replace('"', '\\"')
+ s = s.replace('\n', '\\n')
+ s = s.replace('\t', '\\t')
+ return s
+
+# TODO "choice" and "parallel" blocks are greedily combined
+# so we need a "pass" statement to separate them if
+# multiple of the same block are immediately after
+# each other.
+def print_atl(f, atl_block, indent_level):
+ if not warnedATL:
+ global warnedATL
+ warnedATL = True
+ print("ATL not yet implemented")
+ return
+ if not atl_block.statements:
+ indent(f, indent_level)
+ f.write('pass\n')
+ for stmt in atl_block.statements:
+ indent(f, indent_level)
+
+ if type(stmt) is atl.RawMultipurpose:
+ # warper
+ if stmt.warp_function:
+ f.write("warp %s" % (stmt.warp_function.strip(), ))
+ f.write(" %s " % (stmt.duration.strip(), ))
+ elif stmt.warper:
+ f.write(stmt.warper)
+ f.write(" %s " % (stmt.duration.strip(), ))
+ elif stmt.duration.strip() != '0':
+ f.write('pause')
+ f.write(" %s" % (stmt.duration.strip(), ))
+
+ # revolution
+ if stmt.revolution:
+ f.write("%s " % (stmt.revolution, ))
+
+ # circles
+ if stmt.circles != "0":
+ f.write("circles %s " % (stmt.circles.strip(), ))
+
+ # splines
+ for (name, exprs) in stmt.splines:
+ f.write("%s " % (name, ))
+ for expr in exprs:
+ f.write("knot %s " % (expr.strip(), ))
+
+ # properties
+ for (k, v) in stmt.properties:
+ f.write("%s %s " % (k, v.strip()))
+
+ # with
+ for (expr, with_expr) in stmt.expressions:
+ f.write("%s " % (expr.strip(), ))
+ if with_expr:
+ f.write("with %s " % (with_expr, ))
+
+ f.write("\n")
+
+ elif type(stmt) is atl.RawBlock:
+ # what does stmt.animation do?
+ f.write("block:\n")
+ print_atl(f, stmt, indent_level + 1)
+
+ elif type(stmt) is atl.RawChoice:
+ first = True
+ for (chance, block) in stmt.choices:
+ if first:
+ first = False
+ else:
+ indent(f, indent_level)
+
+ f.write("choice")
+ if chance != "1.0":
+ f.write(" %s" % (chance, ))
+ f.write(":\n")
+ print_atl(f, block, indent_level + 1)
+
+ elif type(stmt) is atl.RawContainsExpr:
+ f.write("contains %s\n" % (stmt.expression, ))
+
+ elif type(stmt) is atl.RawEvent:
+ f.write("event %s\n" % (stmt.name, ))
+
+ elif type(stmt) is atl.RawFunction:
+ f.write("function %s\n" % (stmt.expr, ))
+
+ elif type(stmt) is atl.RawOn:
+ first = True
+ for name, block in list(stmt.handlers.items()):
+ if first:
+ first = False
+ else:
+ indent(f, indent_level)
+
+ f.write("on %s:\n" % (name, ))
+ print_atl(f, block, indent_level + 1)
+
+ elif type(stmt) is atl.RawParallel:
+ first = True
+ for block in stmt.blocks:
+ if first:
+ first = False
+ else:
+ indent(f, indent_level)
+
+ f.write("parallel:\n")
+ print_atl(f, block, indent_level + 1)
+
+ elif type(stmt) is atl.RawRepeat:
+ f.write("repeat")
+ if stmt.repeats:
+ f.write(" %s" % (stmt.repeats, )) # not sure if this is even a string
+ f.write("\n")
+
+ elif type(stmt) is atl.RawTime:
+ f.write("time %s\n" % (stmt.time, ))
+
+ else:
+ f.write("TODO atl.%s\n" % type(stmt).__name__)
+
+def print_imspec(f, imspec):
+ if imspec[1] is not None: # Expression
+ f.write('expression ')
+ f.write(escape_string(imspec[1]))
+ else: # Image name
+ delim = ''
+ for s in imspec[0]:
+ f.write(delim + escape_string(s))
+ delim = '", "'
+
+ # at
+ if len(imspec[3]) > 0:
+ f.write(' at ')
+ delim = ''
+ for s in imspec[3]:
+ f.write(delim + escape_string(s))
+ delim = ', '
+
+ # as
+ if imspec[2] is not None:
+ f.write(" as %s" % (escape_string(imspec[2]), ))
+
+ # behind
+ if len(imspec[6]) > 0:
+ f.write('", "behind", "')
+ delim = ''
+ for s in imspec[6]:
+ f.write(delim + escape_string(s))
+ delim = ', '
+
+ f.write('"],')
+
+def print_Label(f, stmt, indent_level):
+ if firstLabel:
+ global firstLabel
+ firstLabel = False
+ else:
+ f.write("],\n")
+ f.write("\"%s\": [\n" % (stmt.name, ))
+ if stmt.parameters is not None:
+ print("Error: label params")
+
+ for sub_stmt in stmt.block:
+ print_statement(f, sub_stmt, indent_level + 1)
+
+def print_Say(f, stmt, indent_level):
+ if stmt.who is not None:
+ f.write('["%s", ' % (escape_string(stmt.who), ))
+ else:
+ f.write("[")
+ f.write("\"%s\"]," % (escape_string(stmt.what), ))
+ if stmt.with_ is not None:
+ pass
+ #f.write(" with %s" % (stmt.with_, ))
+ f.write('\n')
+
+def print_Jump(f, stmt, indent_level):
+ f.write("jump ")
+ if stmt.expression:
+ # TODO expression
+ f.write("expression TODO")
+ else:
+ f.write(stmt.target)
+ f.write('\n')
+
+def print_Scene(f, stmt, indent_level):
+ f.write('["scene", "')
+ print_imspec(f, stmt.imspec)
+
+ # with isn't handled here, but split in several statements
+
+ f.write('\n')
+ if stmt.atl is not None:
+ print_atl(f, stmt.atl, indent_level+1)
+
+def print_With(f, stmt, indent_level):
+ f.seek(-3, 1)
+ f.write(', "%s"],\n' % (escape_string(stmt.expr), ))
+
+def print_Show(f, stmt, indent_level):
+ f.write('["show", "')
+ print_imspec(f, stmt.imspec)
+
+ # with isn't handled here, but split in several statements
+
+ if stmt.atl is not None:
+ f.write('\n')
+ print_atl(f, stmt.atl, indent_level+1)
+ else:
+ f.write('\n')
+
+def print_Hide(f, stmt, indent_level):
+ f.write('["hide", "')
+ print_imspec(f, stmt.imspec)
+
+ # with isn't handled here, but split in several statements
+
+ f.write('\n')
+
+class PrintRenPython(python_ast.NodeVisitor):
+ def __init__(self, f):
+ self.f = f
+
+ def visit_Attribute(self, node):
+ return '"%s"' % node.attr
+
+ def visit_Call(self, node):
+ self.f.write('[')
+ self.f.write(self.visit(node.func))
+ self.f.write(', ')
+ self.f.write(', '.join(map(self.visit, node.args)))
+ self.f.write('],\n')
+
+ def quote(self, string):
+ return '"%s"' % string
+
+ def visit_Dict(self, node):
+ return self.quote(python_ast.dump(node))
+
+ def visit_Num(self, node):
+ return str(node.n)
+
+ def visit_Name(self, node):
+ return self.quote(node.id)
+
+ def visit_Str(self, node):
+ return self.quote(escape_string(node.s))
+
+def print_Python(f, stmt, indent_level, early=False):
+ code_src = stmt.code.source
+
+ stripped_code = code_src.strip()
+
+ if stripped_code.count('\n') == 0:
+ stmt = compile(code_src, '<unknown>', 'exec', python_ast.PyCF_ONLY_AST).body[0]
+ PrintRenPython(f).visit(stmt)
+ else:
+ f.write("python")
+ if early:
+ f.write(" early")
+ if stmt.hide:
+ f.write(" hide")
+ f.write(":\n")
+
+ for line in code_src.splitlines(True):
+ indent(f, indent_level + 1)
+ f.write(line)
+
+def print_Return(f, stmt, indent_level):
+ if stmt.expression is not None:
+ f.write(' "%s",' % (stmt.expression, ))
+
+ f.write('\n')
+
+def print_UserStatement(f, stmt, indent_level):
+ f.write('["%s"],\n' % (escape_string(stmt.line).replace(' ', '", "'), ))
+
+def print_Init(f, stmt, indent_level):
+ f.write("init")
+ if stmt.priority != 0:
+ f.write(" %d" % (stmt.priority, ))
+ f.write(":\n")
+ for s in stmt.block:
+ print_statement(f, s, indent_level + 1)
+
+def print_Image(f, stmt, indent_level):
+ f.write("image %s" % (' '.join(stmt. imgname), ))
+ if stmt.code is not None:
+ f.write(" = %s\n" % (stmt.code.source, ))
+ else:
+ f.write("\n")
+ print_atl(f, stmt.atl, indent_level + 1)
+
+def print_Transform(f, stmt, indent_level):
+ f.write("transform %s" % (stmt.varname, ))
+ if stmt.parameters is not None:
+ print_params(f, stmt.parameters)
+
+ f.write("\n")
+ print_atl(f, stmt.atl, indent_level + 1)
+
+def print_Menu(f, stmt, indent_level):
+ f.write('["menu", ')
+
+ first = True
+
+ for item in stmt.items:
+ indent(f, indent_level + 1)
+
+ if first and item[2] is not None:
+ first = False
+ f.write(' {\n')
+
+ # caption
+ f.write("\"%s\"" % (escape_string(item[0]), ))
+
+ if first and item[2] is None:
+ first = False
+ f.write(', {')
+
+ if item[2] is not None:
+ f.write(':')
+ for inner_stmt in item[2]:
+ print_statement(f, inner_stmt, indent_level + 2)
+
+ f.write('}]\n')
+
+def print_Pass(f, stmt, indent_level):
+ f.write("pass\n")
+
+def print_Call(f, stmt, indent_level):
+ f.write("call ")
+ if stmt.expression:
+ f.write("expression %s" % (stmt.label, ))
+ else:
+ f.write(stmt.label)
+
+ if stmt.arguments is not None:
+ if stmt.expression:
+ f.write("pass ")
+ print_args(f, stmt.arguments)
+
+ f.write('\n')
+
+def print_If(f, stmt, indent_level):
+ f.write("if %s:\n" % (stmt.entries[0][0], ))
+ for inner_stmt in stmt.entries[0][1]:
+ print_statement(f, inner_stmt, indent_level + 1)
+
+ if len(stmt.entries) >= 2:
+ if stmt.entries[-1][0].strip() == 'True':
+ else_entry = stmt.entries[-1]
+ elif_entries = stmt.entries[1:-1]
+ else:
+ else_entry = None
+ elif_entries = stmt.entries
+
+ for case in elif_entries:
+ indent(f, indent_level)
+ f.write("elif %s:\n" % (case[0], ))
+ for inner_stmt in case[1]:
+ print_statement(f, inner_stmt, indent_level + 1)
+
+ if else_entry is not None:
+ indent(f, indent_level)
+ f.write("else:\n")
+ for inner_stmt in else_entry[1]:
+ print_statement(f, inner_stmt, indent_level + 1)
+
+def print_EarlyPython(f, stmt, indent_level):
+ print_Python(f, stmt, indent_level, early=True)
+
+# TODO extrapos, extrakw?
+def print_args(f, arginfo):
+ if arginfo is None:
+ return
+
+ f.write("(")
+
+ first = True
+ for (name, val) in arginfo.arguments:
+ if first:
+ first = False
+ else:
+ f.write(", ")
+
+ if name is not None:
+ f.write("%s = " % (name, ))
+ f.write(val)
+
+ f.write(")")
+
+# TODO positional?
+def print_params(f, paraminfo):
+ f.write("(")
+
+ first = True
+ for param in paraminfo.parameters:
+ if first:
+ first = False
+ else:
+ f.write(", ")
+
+ f.write(param[0])
+
+ if (param[1] is not None) and ('None' not in param[1]):
+ f.write(" = %s" % param[1])
+ if paraminfo.extrapos:
+ f.write(", ")
+ f.write("*%s" % paraminfo.extrapos)
+ if paraminfo.extrakw:
+ f.write(", ")
+ f.write("**%s" % paraminfo.extrakw)
+
+ f.write(")")
+
+# Print while command, from http://forum.cheatengine.org/viewtopic.php?p=5377683
+def print_While(f, stmt, indent_level):
+ f.write("while %s:\n" % (stmt.condition, ))
+ for inner_stmt in stmt.block:
+ print_statement(f, inner_stmt, indent_level + 1)
+
+# Print define command, by iAmGhost
+def print_Define(f, stmt, indent_level):
+ f.write("define %s = %s\n" % (stmt.varname, stmt.code.source,))
+
+
+statement_printer_dict = {
+ ast.Label: print_Label,
+ ast.Say: print_Say,
+ ast.Jump: print_Jump,
+ ast.Scene: print_Scene,
+ ast.With: print_With,
+ ast.Show: print_Show,
+ ast.Hide: print_Hide,
+ ast.Python: print_Python,
+ ast.Return: print_Return,
+ ast.UserStatement: print_UserStatement,
+ ast.Init: print_Init,
+ ast.Image: print_Image,
+ ast.Transform: print_Transform,
+ ast.Menu: print_Menu,
+ ast.Pass: print_Pass,
+ ast.Call: print_Call,
+ ast.If: print_If,
+ ast.While: print_While,
+ ast.Define: print_Define,
+ ast.EarlyPython: print_EarlyPython,
+ }
+
+def print_Unknown(f, stmt, indent_level):
+ print(("Unknown AST node: %s" % (type(stmt).__name__, )))
+ f.write("<<<UNKNOWN NODE %s>>>\n" % (type(stmt).__name__, ))
diff --git a/unrpyc/fix.js b/unrpyc/fix.js
new file mode 100755
index 0000000..88a9646
--- /dev/null
+++ b/unrpyc/fix.js
@@ -0,0 +1,6 @@
+var fs = require('fs');
+
+fs.readFile(process.argv[2], function (err, data) {
+ if (err) throw err;
+ fs.writeFile(process.argv[3], JSON.stringify(eval('(' + data + ')')));
+});
diff --git a/unrpyc/unrpyc.py b/unrpyc/unrpyc.py
new file mode 100644
index 0000000..f423cc8
--- /dev/null
+++ b/unrpyc/unrpyc.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python2
+
+# Copyright (c) 2012 Yuri K. Schlesner
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import optparse
+import os.path
+import sys
+import pickle as pickle
+import codecs
+import glob
+import itertools
+import zlib
+
+class Dummy:
+ def record_pycode(self,*args,**kwargs):
+ return
+ all_pycode = []
+
+def import_renpy(basedir=None):
+ #import renpy from another location.
+ if basedir:
+ sys.path.append(basedir)
+ global renpy
+ global decompiler
+
+ # Needed for pickle to read the AST
+ try:
+ import renpy
+ except ImportError:
+ print("\nFailed at importing renpy. Are you sure that the renpy directory can be found in sys.path or the current working directory?\n")
+ raise
+ # try to import as much renpy modules as possible, but some modules might not exist
+ # in older ren'py versions.
+ try: import renpy.log
+ except: pass
+ try: import renpy.display
+ except: pass
+ try: import renpy.object
+ except: pass
+ try:
+ import renpy.game
+ renpy.game.script = Dummy()
+ except: pass
+ try: import renpy.loader
+ except: pass
+ try: import renpy.ast
+ except: pass
+ try: import renpy.atl
+ except: pass
+ try: import renpy.curry
+ except: pass
+ try: import renpy.easy
+ except: pass
+ try: import renpy.execution
+ except: pass
+ try: import renpy.loadsave
+ except: pass
+ try: import renpy.parser
+ except: pass
+ try: import renpy.python
+ except: pass
+ try: import renpy.script
+ except: pass
+ try: import renpy.statements
+ except: pass
+ try: import renpy.style
+ except: pass
+
+ import decompiler
+ if basedir:
+ sys.path.remove(basedir)
+
+
+def read_ast_from_file(in_file):
+ raw_contents = zlib.decompress(in_file.read())
+ data, stmts = pickle.loads(raw_contents)
+ return stmts
+
+def decompile_rpyc(input_filename, out_filename, overwrite=False):
+ # Output filename is input filename but with .rpy extension
+ path, ext = os.path.splitext(input_filename)
+
+ print(("Decompiling %s to %s..." % (input_filename, out_filename)))
+
+ if not overwrite and os.path.exists(out_filename):
+ print("Output file already exists. Pass --clobber to overwrite.")
+ return False # Don't stop decompiling if one file already exists
+
+ with open(input_filename, 'rb') as in_file:
+ ast = read_ast_from_file(in_file)
+
+ with codecs.open(out_filename, 'w', encoding='utf-8') as out_file:
+ decompiler.pretty_print_ast(out_file, ast)
+ return True
+
+if __name__ == "__main__":
+ parser = optparse.OptionParser(
+ usage="usage: %prog [options] input output",
+ version="%prog 0.1")
+
+ parser.add_option('-c', '--clobber', action='store_true', dest='clobber',
+ default=False, help="overwrites existing output files")
+
+ parser.add_option('-b', '--basedir', action='store', dest='basedir',
+ help="specify the game base directory in which the 'renpy' directory is located")
+
+ options, args = parser.parse_args()
+
+
+ if options.basedir:
+ import_renpy(options.basedir)
+ else:
+ import_renpy()
+
+ if len(args) != 2:
+ parser.print_help();
+ parser.error("Incorrect number of arguments: expected 2, got %d." % (len(args)))
+
+ decompile_rpyc(args[0], args[1], options.clobber)
+else:
+ import_renpy()
+
diff --git a/www/js/api.js b/www/js/api.js
index 91a28d7..1cf9fbe 100644
--- a/www/js/api.js
+++ b/www/js/api.js
@@ -9,55 +9,55 @@ window.html5ks.api = {
}
}
},
- set_volume: function (volume, delay, channel) {
+
+
+ set_volume: function (target, delay, channel) {
var deferred = when.defer(),
audio = html5ks.elements.audio[channel],
- chg = volume - audio.volume,
- step = chg / (delay * 10);
+ step = (target - audio.volume) / (delay * 20);
var f = setInterval(function () {
- var newv = audio.volume + step;
- if (newv > 1) {
- audio.volume = 1;
+ // clamp new volume 0-1
+ audio.volume = Math.min(Math.max(audio.volume + step, 0), 1);
+ if (audio.volume === 0 || audio.volume === 1) {
clearInterval(f);
- } else if (newv < 0) {
- audio.volume = 0;
- clearInterval(f);
- } else {
- audio.volume = newv;
}
- }, 100);
+ }, 50);
deferred.resolve();
return deferred.promise;
},
- play: function (channel, name, fade) {
+
+ play: function (channel, name, ignore, fade) {
+ this.stop(channel);
var deferred = when.defer(),
- audio = html5ks.elements.audio[channel];
+ audio = new Audio();
+ if (channel === "music" || channel === "ambient") {
+ audio.loop = true;
+ }
+ html5ks.elements.audio[channel] = audio;
audio.src = "dump/" + (channel === "music" ? "bgm/" + html5ks.data.music[name] + ".ogg" : html5ks.data.sfx[name]);
audio.load();
audio.volume = fade ? 0 : 1;
audio.play();
audio.addEventListener("playing", function playing() {
- audio.removeEventListener("playing", playing, false);
deferred.resolve();
if (fade) {
html5ks.api.set_volume(1, fade, channel);
}
}, false);
audio.addEventListener("error", function error() {
- audio.removeEventListener("error", error, false);
deferred.reject(this.error);
}, false);
return deferred.promise;
},
- stop: function (channel, fade) {
+
+ stop: function (channel, ignore, fade) {
if (channel === "all") {
- this.stop("music");
- this.stop("sound");
- return this.stop("ambient");
+ this.stop("music", ignore, fade);
+ this.stop("sound", ignore, fade);
+ return this.stop("ambient", ignore, fade);
}
var deferred = when.defer(),
- audio = html5ks.elements.audio[channel],
- fadeSet = html5ks.persistent.settings.fade;
+ audio = html5ks.elements.audio[channel];
if (fade) {
this.set_volume(0, fade, channel);
} else {
@@ -67,6 +67,7 @@ window.html5ks.api = {
return deferred.promise;
},
+
movie_cutscene: function (vid_src, skippable) {
var deferred = when.defer(),
video = html5ks.elements.video,
@@ -113,13 +114,16 @@ window.html5ks.api = {
}, false);
return deferred.promise;
},
+
act_op: function (this_video) {
// strip off extension
return this.movie_cutscene(this_video.slice(0,-4));
},
+
+
iscene: function (target, is_h, is_end) {
var deferred = when.defer(),
- label = html5ks.data.script[target],
+ label = html5ks.data.script[html5ks.persistent.settings.language + "_" + target],
i = 0;
(function run(ret) {
if (label[i]) {
@@ -132,18 +136,18 @@ window.html5ks.api = {
return deferred.promise;
},
- with: function (transition, action) {
- return this.runInst(action);
- },
runInst: function (inst) {
- var cmd = inst[0],
+ var cmd = inst[0].replace(/"/g, ''),
args = inst.slice(1);
if (html5ks.data.characters[cmd]) {
return this.character(cmd, args[0]);
} else {
if (this[cmd]) {
return this[cmd].apply(this, args);
+ } else if (inst.length === 1) {
+ console.log("hopefully this is dialogue");
+ return this.character("name_only", cmd);
} else if (/^[A-Z]/.test(cmd)) {
console.log("cmd starts with caps, probably character");
return this.character(cmd, args[0]);
@@ -156,6 +160,7 @@ window.html5ks.api = {
}
},
+
window: function (action, transition) {
var windw = html5ks.elements.window,
deferred = when.defer();
@@ -167,12 +172,14 @@ window.html5ks.api = {
deferred.resolve(action);
return deferred.promise;
},
- // NOT iscene
+
+
scene: function (type, name) {
html5ks.elements.show.innerHTML = "";
return this.show.apply(this, arguments);
},
+
show: function (name, type, location) {
var deferred = when.defer();
var lookup = document.getElementById(name),
@@ -200,7 +207,7 @@ window.html5ks.api = {
bgleft: { xpos: 0.4, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 },
bgright: { xpos: 0.6, xanchor: 0.5, ypos: 1.0, yanchor: 1.0 }
};
- var pos = positions[location] || positions["center"];
+ var pos = positions[location] || positions.center;
// TODO: implement transitions
if (pos) {
el.style.left = pos.xpos * 800 + "px";
@@ -213,38 +220,37 @@ window.html5ks.api = {
deferred.resolve();
};
el.onerror = function () {
- // TODO: check if img is really in images.js
- deferred.resolve();
+ deferred.reject();
};
var nom = name;
if (type) {
nom = name + "_" + type;
}
var image = html5ks.data.images[nom];
- if (typeof image == "undefined") {
- switch (name) {
- case "bg":
- image = "bgs/" + type + ".jpg";
- break;
- case "url":
- name = type;
- image = type;
- break;
- default:
- image = "sprites/" + name + "/" + (type && type.indexOf("_close") > -1 ? "close/" : "") + name + "_" + type + ".png";
- }
- }
- if (typeof image == "string") {
- if (image.substring(0,1) == "#") {
+ switch (typeof image) {
+ case "string":
+ el = document.createElement("div");
el.style.backgroundColor = image;
el.style.width = "100%";
el.style.height = "100%";
el.src = "";
deferred.resolve();
return deferred.promise;
- } else {
- image = {image: image};
- }
+ case "undefined":
+ switch (name) {
+ case "bg":
+ image = "bgs/" + type + ".jpg";
+ break;
+ case "url":
+ name = type;
+ image = type;
+ break;
+ default:
+ image = "sprites/" + name + "/" + (type && type.indexOf("_close") > -1 ? "close/" : "") + name + "_" + type + ".png";
+ }
+ }
+ if (typeof image === "string") {
+ image = {image: image};
}
var src = "";
if (html5ks.persistent.settings.useWebP) {
@@ -429,10 +435,10 @@ window.html5ks.api = {
menu: function (label) {
var deferred = when.defer(),
- menu = html5ks.data.script[label],
- char = menu[1],
- str = menu[2],
- choices = menu[3];
+ imenu = html5ks.data.script[label],
+ char = imenu[1],
+ str = imenu[2],
+ choices = imenu[3];
this.character(char, str, null, true);
var menu = html5ks.elements.choices,
frag = document.createDocumentFragment(),
@@ -442,14 +448,15 @@ window.html5ks.api = {
for (var i in choices) {
choice.innerHTML = i;
- choice.addEventListener("click", function () {
- html5ks.elements.choices.innerHTML = "";
- deferred.resolve(choices[this.innerHTML]);
- }, false);
frag.appendChild(choice);
choice = choice.cloneNode(false);
}
+ menu.addEventListener("click", function (e) {
+ html5ks.elements.choices.innerHTML = "";
+ deferred.resolve(choices[e.target.innerHTML]);
+ }, false);
+
html5ks.elements.choices.appendChild(frag);
return deferred.promise;
}
diff --git a/www/js/html5ks.js b/www/js/html5ks.js
index 8a174dc..182d08a 100644
--- a/www/js/html5ks.js
+++ b/www/js/html5ks.js
@@ -1,5 +1,4 @@
"use strict";
-console.log("HELP")
window.html5ks = {
data: {
script: {}
@@ -13,12 +12,14 @@ window.html5ks = {
skipUnread: false,
skipAfterChoices: false,
useWebP: null,
- fullscreen: true,
+ fullscreen: false,
+ scaleAll: true,
scaleVideo: true,
textSpeed: 0.5,
autoModeDelay: 0.2,
musicVolume: 1,
- sfxVolume: 1
+ sfxVolume: 1,
+ language: "en"
}
},
store: {
@@ -30,8 +31,10 @@ window.html5ks = {
},
},
state: {},
+ next: function () {},
initElements: function () {
this.elements = {
+ all: document.getElementById("all"),
container: document.getElementById("container"),
video: document.getElementById("vid"),
audio: {
@@ -49,8 +52,6 @@ window.html5ks = {
choices: document.getElementById("choices"),
show: document.getElementById("show")
};
- this.elements.audio.music.loop = true;
- this.elements.audio.ambient.loop = true;
},
load: function () {
if (localStorage.persistent) {
@@ -62,7 +63,7 @@ window.html5ks = {
localStorage.persistent = JSON.stringify(this.persistent);
},
scale: function () {
- if (html5ks.persistent.settings.fullscreen) {
+ if (html5ks.persistent.settings.scaleAll) {
var newScale = 1;
var height = document.documentElement.clientHeight,
width = document.documentElement.clientWidth;
@@ -95,18 +96,24 @@ window.html5ks = {
}
}
},
- next: function () {},
+ fullscreen: function () {
+ var all = html5ks.elements.all;
+ if (all.requestFullscreen) {
+ all.requestFullscreen();
+ } else if (all.mozRequestFullScreen) {
+ all.mozRequestFullScreen();
+ } else if (all.webkitRequestFullscreen) {
+ all.webkitRequestFullscreen();
+ }
+ },
initEvents: function () {
window.addEventListener("resize", html5ks.scale, false);
this.elements.container.addEventListener("mouseup", function () {
html5ks.next();
}, false);
- var deselect = function () {
- window.getSelection().collapse(this, 0);
- };
- document.body.addEventListener("mousemove", deselect, true);
- document.body.addEventListener("mouseup", deselect, true);
- document.body.addEventListener("keyup", deselect, true);
+ window.addEventListener("dragstart", function (e) {
+ e.preventDefault();
+ }, false);
},
warnUnsupported: function () {
if (!html5ks.persistent.settings.gotit) {
@@ -141,6 +148,12 @@ window.html5ks = {
}
this.api.init();
this.menu.init();
+ if (this.persistent.settings.fullscreen) {
+ document.body.addEventListener("click", function onclick() {
+ this.removeEventListener("click", onclick, false);
+ html5ks.fullscreen();
+ }, false);
+ }
},
winload: function () {
if (!this.loaded) {
diff --git a/www/js/menu.js b/www/js/menu.js
index 4259ce1..43d98c4 100644
--- a/www/js/menu.js
+++ b/www/js/menu.js
@@ -45,13 +45,13 @@
case "checkbox":
option.checked = values[option.id];
option.addEventListener("change", function () {
- values[this.id] = this.checked;
+ values[this.id] = this.checked;
}, false);
break;
case "range":
option.value = values[option.id];
option.addEventListener("change", function () {
- values[this.id] = this.value;
+ values[this.id] = this.value;
}, false);
break;
default: