Making Tyranoscript VNs text hookable
TL;DR: edit the pushBackLog function in kag.js to copy to clipboard. Use javascript to filter the output. Various issues discused in full text
This post is intended for programmer-like people. If you’re not a programmer good luck but I will not be taking any questions at this time.
Some tyranoscript VNs hook fine. Some hook… okay, if you’re willing to filter their curséd html tags. Some work on specific versions using custom hook codes. Some work if you’re willing to use LunaHook. Some just don’t work.
But what if there was a way to get the text you want without ramming a spy program into its functions at all? Tyrano is just your average Electron monstrosity, and a lot of VNs have exposed javascript files. Just edit the code lol.
It turns out that this impulse is mostly right, but it takes some time to find the right point for a surgical strike. Also, archived files make it more annoying to patch, but there’s some imperfect workarounds at least. VNs I’ve done this with so far include:
雨にして人を外れ
腐った果実-Rotten Fruit-
背徳シレネ – あなたの知らない奈落の底 (en prepatched release, depatched)
接触 (https://vndb.org/v30010)
So what’s the right place to strike? It turns out that the script parser might have some Kirikiri ancestry, as it uses a KAG plugin (also .ks script files lol). The path to this is usually something like (resources/ app or app.asar archive)/tyrano/plugins/kag/
. In here we find kag.js
, which has a function pushBackLog
that, well, pushes things to backlog. It will probably be minified and unreadable, but a swift google finds an unminifier easily. The backlog push is good for our purposes (until you hit a denpa VN that decides to fuck with the backlog, I guess), as we can easily extract whole script lines without having to worry about how the game parsed the line originally (what if it’s symbol by symbol?). We can also deal with an issue that happens with text hookers in the engine: multi-line text boxes getting split. It’s not perfect with my current solution as you will copy first part of the line and then the whole line to clipboard, which is a problem if you do obsessive character tracking. This can be solved by not doing obsessive character tracking. I didn’t find any easy way to not copy half-lines after a moderate amount of thinking, but maybe I’m just being dumb.
Anyway, here’s an example of how to modify the function. Additions marked by asterisks:
pushBackLog: function (t, a) {
if (0 != this.stat.log_write) {
a = a || "add";
var e = parseInt(this.kag.config.maxBackLogNum);
if (!(e < 1)) {
if (1 == this.kag.stat.log_clear && (a = "add", this.kag.stat.log_clear = !1), "join" == a) {
var s = this.variable.tf.system.backlog.length - 1;
if (s >= 0) {
var i = this.variable.tf.system.backlog[s];
this.variable.tf.system.backlog[this.variable.tf.system.backlog.length - 1] = i + t;
*navigator.clipboard.writeText(this.stripHtml(i + t));
} else { this.variable.tf.system.backlog.push(t);
*navigator.clipboard.writeText(this.stripHtml(t)); }
} else { this.variable.tf.system.backlog.push(t);
*navigator.clipboard.writeText(this.stripHtml(t)); }
this.stat.current_save_str = this.variable.tf.system.backlog[this.variable.tf.system.backlog.length - 1], e < this.variable.tf.system.backlog.length && this.variable.tf.system.backlog.shift()
}
}
},
*this whole function is new
stripHtml: function(html) {
let tmp = document.createElement("DIV");
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || "";
},
Basically, any time we would add text to the backlog by changing this.variable.tf.system.backlog
, it’s reasonable to assume that’s a line we want to capture. The easiest way to do this is to use the navigator.clipboard.writeText()
function to write that same string to clipboard.stripHtml
is a utility function that strips the html from it by just… making it an html element and using some browser functions, because fuck me we’re in Electron land. I’ll be honest, I don’t particularly know or like javascript, and this is just based on a casual google. But it works so who cares I guess. Does the element leak? Who knows! Not my problem.
However, navigator.clipboard.writeText()
isn’t available in earlier tyranoscript. Version 500 as seen around the top of the kag.js file has worked for me, but 450 fails. What then? read on.
Vexing case one: what the heck are you doing, Haitoku Sirene?
This is a weird release. First of all, the resource files are actually embedded in the .exe. My memories are murky, but I think I got them out using arc_unpacker on that same exe. This got an archive I could open that then had the game data within alongside other weird stuff. Once I hit that, I was able to make a patch using the Tyrano Rider’s patch generator, though after experimenting this is actually just zipping something containing the patch folder maintaining folder structure with the name tyranoproject.tpatch
. You can even compress it if you want (level 0 and 5 in 7zip tested). I’m not entirely sure if this works on anything but the prepatched english version, but the great thing is we can both remove the english script and use the work of the tl team by changing out the patch archive to only contain the tyrano/
and below folders into kag.js
. Okay pog we did it reddit.
This minimally-invasive patch option probably works on other things. Unfortunately it has not for me for things using .asar archives… but that’s for the next vexation. Okay, we can actually patch the game. Problem: the engine is too old and we can’t copy to clipboard the same way. What now? Well, I descended upon stackoverflow and copied some other dude’s method. Does it really need the error logging? Probably not, but imagine thinking about how to improve javashit. It’s basically the same except we need a whole ass own function to copy to clipboard:
*this whole function is new copyToClipboard: function(text) { var textArea = document.createElement("textarea"); textArea.value = text; // copied near verbatim from https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript // Avoid scrolling to bottom textArea.style.top = "0"; textArea.style.left = "0"; textArea.style.position = "fixed"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { var successful = document.execCommand('copy'); var msg = successful ? 'successful' : 'unsuccessful'; console.log('Fallback: Copying text command was ' + msg); } catch (err) { console.error('Fallback: Oops, unable to copy', err); } document.body.removeChild(textArea); }, pushBackLog: function (str, type) { if (this.stat.log_write == false) return; type = type || "add"; var max_back_log = parseInt(this.kag.config["maxBackLogNum"]); if (max_back_log < 1) return; if (this.kag.stat.log_clear == true) { type = "add"; this.kag.stat.log_clear = false; } if (type == "join") { var index = this.variable.tf.system.backlog.length - 1; if (index >= 0) { var tmp = this.variable.tf["system"]["backlog"][index]; this.variable.tf["system"]["backlog"][this.variable.tf.system.backlog.length - 1] = tmp + str; *this.copyToClipboard(this.stripHtml(tmp +str)); } else this.variable.tf["system"]["backlog"].push(str); *this.copyToClipboard(this.stripHtml(str)); } else this.variable.tf["system"]["backlog"].push(str); *this.copyToClipboard(this.stripHtml(str)); this.stat.current_save_str = this.variable.tf["system"]["backlog"][this.variable.tf.system.backlog.length - 1]; if (max_back_log < this.variable.tf["system"]["backlog"].length) this.variable.tf["system"]["backlog"].shift(); }, *whole function is new stripHtml: function(html) { var tempo = document.createElement("DIV"); tempo.innerHTML = html; return tempo.textContent || tempo.innerText || ""; return(html); },
Oh yeah, I found running a vn with Tyrano Rider whatever let you actually see errors to debug and stuff. Usually you get no feedback at all on script errors, just the program not starting or a black screen. Tyrano is truly a great engine.
Vexing case two: the asar archive annoyance
Some Tyrano vns have something like app.asar
under resources/ instead of the usual app/ folder. IME This archive is easily extractable using the .asar opening extension to 7zip. But how do we patch? I tried using normal tyrano patches, but it just crashes. The only option I’ve found is to replace the whole archive, so you’re unfortunately going to have to serve a patch of the full game data just to change one little script, unless you’re just better and figure out a different way, or use some program that edits the archive directly. But otherwise you just do the same shit. Of note: some VNs have prioritized resources/app the folder over resources/app.asar the archive for me, others have not. In any case renaming the archive made the engine search the folder instead for me in one case, so you can try that (less annoying to have an open file tree than having to rearchive every time).
Anyway this post is written like shit but at least it actually exists. Yay you can make tyranoscript vns copy to clipboard which makes them effectively hookable. For the hipsters that have an allergy to PoLlUtInG tHe ClipBoArD feel free to write some web sockets shit instead, electron is literally a browser I’m sure you can figure it out KEKL
haitoku shirene patch
amehazu new kag.js script
For sesshoku and kusatta kajitsu I actually just substituted the amehazu script. I tried to edit the kusatta kajitsu one and it didn’t work, I have no idea if it was some dumb syntax error I made or the swap was actually crucial. They both used the same tyrano version of 500 as amehazu. Have fun!
2 Comments
Recommended Comments