Homebrew [W.I.P] VN3DS Visual Novel Interpreter for 3DS Homebrew

dfsa3fdvc1

Well-Known Member
OP
Member
Joined
Jan 3, 2015
Messages
226
Trophies
0
XP
214
Country
Albania
VNDS is a syntax created for the purpose of playing visual novel games on multiple devices. Dozens of popular visual novels such as Fate Stay Night, Ever 17, Saya no Uta have been converted to be VNDS compatible and thus, when this is completed, will be 3DS compatible.

Here's a GIF of the interpreter fast forwarding though Saya no Uta
CitraDemo.gif

What's done:
Text, cleartext, label/goto, delay, jump, setvar, gsetvar, save/load, "CPU bgload" "CPU setimg", choice selection, IF/FI, random

what's not done
Music/sound (ogg loading in LPP is a W.I.P. Crashes quite often right now and been disabled)
Fadetime
Start/End colored text

This is what if/fi conditionals look like in a very conditional heavy scene of Ever 17. Can't even imagine how someone managed to do this in VNDS. Looks like nonsense.
IF/FI said:
text I was trying to decide which area of the park to visit first.

setvar v_d0 = 1
setvar v_d1 = 1
setvar v_d2 = 1
label L00000221
if v_d0 != 0
goto L00000260
fi
if v_d1 != 0
goto L00000260
fi
if v_d2 == 0
goto L000006a3
fi
label L00000260
if v_d0 != 0
if v_d1 != 0
if v_d2 != 0
choice The Dolphin Merry-go-round|The so++uvenir shop|The Security Office
if selected == 1
setvar selected2 = 0
fi
if selected == 2
setvar selected2 = 1
fi
if selected == 3
setvar selected2 = 2
fi
goto CL1
fi
fi
fi
if v_d0 == 0
if v_d1 != 0
if v_d2 != 0
choice The souvenir shop|The Security Office
if selected == 1
setvar selected2 = 1
fi
if selected == 2
setvar selected2 = 2
fi
goto CL1
fi
fi
fi
if v_d0 != 0
if v_d1 == 0
if v_d2 != 0
choice The Dolphin Merry-go-round|The Security Office
if selected == 1
setvar selected2 = 0
fi
if selected == 2
setvar selected2 = 2
fi
goto CL1
fi
fi
fi
if v_d0 == 0
if v_d1 == 0
if v_d2 != 0
choice The Security Office
if selected == 1
setvar selected2 = 2
fi
goto CL1
fi
fi
fi
if v_d0 != 0
if v_d1 != 0
if v_d2 == 0
choice The Dolphin Merry-go-round|The souvenir shop
if selected == 1
setvar selected2 = 0
fi
if selected == 2
setvar selected2 = 1
fi
goto CL1
fi
fi
fi
if v_d0 == 0
if v_d1 != 0
if v_d2 == 0
choice The souvenir shop
if selected == 1
setvar selected2 = 1
fi
goto CL1
fi
fi
fi
if v_d0 != 0
if v_d1 == 0
if v_d2 == 0
choice The Dolphin Merry-go-round
if selected == 1
setvar selected2 = 0
fi
goto CL1
fi
fi
fi
if v_d0 == 0
if v_d1 == 0
if v_d2 == 0
text ERROR: All choice options are deactivated

setvar selected = 0
goto CL1
fi
fi
fi
label CL1
setvar v_b3 = selected2
if v_b3 == 0
goto L0000028a
fi
if v_b3 == 1
goto L00000508
fi
if v_b3 == 2
goto L0000062a
fi
label L0000028a
setvar v_d0 = 0
bgload bg02a2.jpg
text I headed to the room with the attractions.

Future plans.
Automatic unzipping of .novel and .zip assets
Clean up all the awful programming.
GPU rendering to allow imagescaling. VNDS has been widely adopted on Android and other devices with higher res screens than the DS's 256x192. What this means is that newer VNDS ports in some cases aren't actually compatible with DS. This would fix that. This is actually a feature of later LPP commites, only problem is that LPP has problems with GPU rendering JPGs currently. Thinking of on the fly converting the JPGs via CPU into BMP and then GPU rendering and storing the newly converted BMP version. Wonder if that would work... Or just wait for LPP to support GPU JPGS.

Attached ZIP of latest build of LPP 3ds/3dsx/elf, and the index.lua script which goes on the root of the SD. Obviously I can't distribute the VNs with it so you'd need one of those and it's hardcoded to open /vnds/Saya/script.s02.txt so you'd need to change that. Because there's no GPU scaling you'd need to batch resize all the assets down to 3DS size (mine are at 320x24)
IDK. Maybe someone smart could help me with the IF/FI thing. This really isn't meant to be playable. Without if/fi support it actually can't get past the main menu. It can display pretty much the whole game, just not make it through the main menu and conditionally move through the script.
 

Attachments

  • VNDS.zip
    1.7 MB · Views: 2,386
Last edited by dfsa3fdvc1,

Konno Ryo

gbatemp's clueless butler?
Member
Joined
Aug 5, 2015
Messages
158
Trophies
0
Location
Somewhere?
XP
176
Country
Canada
Good job:yay:, and keep up the good work, I remember playing Ballad of an Evening Butterfly on my DSTwo, doing it in 3ds mode will be much easer (and less glitchy) than the original NDS version. Btw did you try and use any of the code from the VNDS git?
 

dfsa3fdvc1

Well-Known Member
OP
Member
Joined
Jan 3, 2015
Messages
226
Trophies
0
XP
214
Country
Albania
Good job:yay:, and keep up the good work, I remember playing Ballad of an Evening Butterfly on my DSTwo, doing it in 3ds mode will be much easer (and less glitchy) than the original NDS version. Btw did you try and use any of the code from the VNDS git?

I did take a peek at the source contained in 1.50 of VNDS for DS.
But really, the core of VNDS is so simple that I didn't really see much point in reading it. It was just a matter of understand the end result of the few built-in commands and accomplishing that end result.
I may read up on it for SAV compatibility. Currently my sav format is totally incompatible with other versions.

Actually reading up on it again definitely helps with IF/FI. I had pretty much the same idea where IF condition evaluated to be false would add +1 to a variable and while that variable was greater than 0, all lines would be not processed but every encounter of FI would subtract -1, which would eventually hit 0 again, signaling the end of nested conditionals. For some reason I thought I'd have to do some sort of recursion as well if the condition was positive but it looks like I can just process as normal which makes a lot more sense. So, this might work :3
 

Arras

Well-Known Member
Member
Joined
Sep 14, 2010
Messages
6,319
Trophies
2
XP
5,595
Country
Netherlands
I did take a peek at the source contained in 1.50 of VNDS for DS.
But really, the core of VNDS is so simple that I didn't really see much point in reading it. It was just a matter of understand the end result of the few built-in commands and accomplishing that end result.
I may read up on it for SAV compatibility. Currently my sav format is totally incompatible with other versions.

Actually reading up on it again definitely helps with IF/FI. I had pretty much the same idea where IF condition evaluated to be false would add +1 to a variable and while that variable was greater than 0, all lines would be not processed but every encounter of FI would subtract -1, which would eventually hit 0 again, signaling the end of nested conditionals. For some reason I thought I'd have to do some sort of recursion as well if the condition was positive but it looks like I can just process as normal which makes a lot more sense. So, this might work :3
if you do the false = +1, fi = -1 method, how will you handle something like this?
if false
if true
do something
fi
do something else
fi

If you just do a naive -1 on every fi (except if value is already 0, obviously), you'd decrease the counter before the "do something else". The easiest solution I can think of right now (there's probably better ones, but hey) is to keep a stack of false/true (0/1) for each respective if, and remove the top one for every fi. As long as there's a false somewhere in the stack, don't execute anything. If you already know a way from reading the VNDS source, you can just ignore this :P
 

dfsa3fdvc1

Well-Known Member
OP
Member
Joined
Jan 3, 2015
Messages
226
Trophies
0
XP
214
Country
Albania
if you do the false = +1, fi = -1 method, how will you handle something like this?
if false
if true
do something​
fi
do something else​
fi
I may have worded my explanation poorly but here's how I think it would work with your example
line 1: if false (a false condition) nesting += 1
line 2: would be irrelevant because nesting>0 but since it's IF, nesting += 1
line 3: Irrelevant because nesting > 0
line 4: would be irrelevant because nesting>0 but since it's FI, nesting -= 1
line 5: Irrelevant because nesting > 0
line 6: would be irrelevant because nesting>0 but since it's FI, nesting -= 1. Nesting is now 0 again, script processing returns to normal.

and if the initial condition were true it would processes the nesting condition normally and something else line normally as well.

I'll keep your suggestion in mind but the source of VNDS for DS seems to confirm that it would work.
VNDS SOURCE said:
void ScriptInterpreter::cmd_if(Command* cmd, bool quickread) {
if (!EvaluateIf(cmd->vif.expr1, cmd->vif.op, cmd->vif.expr2)) {​
//If condition evaluates to false, skip to matching fi
int nesting = 1;​

Command cmd;
do {
cmd = vnds->scriptEngine->GetCommand(0);
if (cmd.id == IF) {
nesting++;​
} else if (cmd.id == FI) {
nesting--;​
}
vnds->scriptEngine->SkipCommands(1);​
} while (nesting > 0 && cmd.id != END_OF_FILE);​

if (nesting > 0) {​
vnLog(EL_warning, COM_SCRIPT, "Invalid nesting of if's. Reached the end of the file before encountering the required number of fi's");
}
}
}
 
Last edited by dfsa3fdvc1,

Arras

Well-Known Member
Member
Joined
Sep 14, 2010
Messages
6,319
Trophies
2
XP
5,595
Country
Netherlands
I may have worded my explanation poorly but here's how I think it would work with your example
line 1: if false (a false condition) nesting += 1
line 2: would be irrelevant because nesting>0 but since it's IF, nesting += 1
line 3: Irrelevant because nesting > 0
line 4: would be irrelevant because nesting>0 but since it's FI, nesting -= 1
line 5: Irrelevant because nesting > 0
line 6: would be irrelevant because nesting>0 but since it's FI, nesting -= 1. Nesting is now 0 again, script processing returns to normal.

and if the initial condition were true it would processes the nesting condition normally and something else line normally as well.

I'll keep your suggestion in mind but the source of VNDS for DS seems to confirm that it would work.
Oh, I see what you meant now. I didn't realize you would increment the counter for every if after a false, even the ones that evaluated to true. You're right, that works just fine.
 

Rinnegatamante

Well-Known Member
Member
Joined
Nov 24, 2014
Messages
3,162
Trophies
2
Age
29
Location
Bologna
Website
rinnegatamante.it
XP
4,858
Country
Italy
Nice to see some new project developed using lpp-3ds.

Music/sound (ogg loading in LPP is a W.I.P. Crashes quite often right now and been disabled)

OGG loading is not a W.i.P. in lpp-3ds.
Don't know why you get crashes (maybe you try to allocate too big files, remember that 3DS has only 64MB RAM available for homebrews).
If you need to use big files, you have to use streaming feature but currently if you need loop feature or you write your own loop feature (something which kills the song when it ends and reload it) or you have to temporary use WAV/AIFF files.
I'm working to fix looping streaming issues with OGG cause is a big issue and also i need to fix it for my projects (TriaAl and RPG Maker for example).
 

dfsa3fdvc1

Well-Known Member
OP
Member
Joined
Jan 3, 2015
Messages
226
Trophies
0
XP
214
Country
Albania
Nice to see some new project developed using lpp-3ds.



OGG loading is not a W.i.P. in lpp-3ds.
Don't know why you get crashes (maybe you try to allocate too big files, remember that 3DS has only 64MB RAM available for homebrews).
If you need to use big files, you have to use streaming feature but currently if you need loop feature or you write your own loop feature (something which kills the song when it ends and reload it) or you have to temporary use WAV/AIFF files.
I'm working to fix looping streaming issues with OGG cause is a big issue and also i need to fix it for my projects (TriaAl and RPG Maker for example).

Ah, it might be just user error. It's weird because I've actually gotten some OGGs to play and other OGGs that will totally crash it.

So here's what I've got.
Code said:
test = Sound.openOgg("/blood.ogg", true)
Sound.init()
Sound.play(test,NO_LOOP,0x09)
It worked with "blood.ogg" which a 3 minute song 2MB in size
Crashes with "blood2.ogg" which is a 1 second 22kb audio clip

You're right, I probably am doing something but I'm just not sure what. Attached the 2 Oggs in zip
 

Attachments

  • oggs.zip
    1.9 MB · Views: 387

jurassicplayer

Completionist Themer
Member
Joined
Mar 7, 2009
Messages
4,488
Trophies
2
Location
Pantsuland
Website
www.youtube.com
XP
3,065
Country
United States
Well...I mean the actually dev might make the project himself...
As for if/fi conditionals it's kind of just doing shit one line at a time. There really isn't much to them. If statement works, continue, otherwise just skip everything until you hit if/fi. Then keep a variable somewhere for the number of fi you still need to skip. The syntax was made to be deadbeat stupid though which is why if/fi conditionals look so funny.

- Edit -
From the dev of VNDS:
<jake`> hahahaha
<jake`> amazing
<jake`> now I don't have to do anything
Congrats. All my efforts of keeping up with the 3DS scene is now wasted.
 
Last edited by jurassicplayer,

Rinnegatamante

Well-Known Member
Member
Joined
Nov 24, 2014
Messages
3,162
Trophies
2
Age
29
Location
Bologna
Website
rinnegatamante.it
XP
4,858
Country
Italy
Ah, it might be just user error. It's weird because I've actually gotten some OGGs to play and other OGGs that will totally crash it.

So here's what I've got.

It worked with "blood.ogg" which a 3 minute song 2MB in size
Crashes with "blood2.ogg" which is a 1 second 22kb audio clip

You're right, I probably am doing something but I'm just not sure what. Attached the 2 Oggs in zip

Are your audio files both mono files?
 

dfsa3fdvc1

Well-Known Member
OP
Member
Joined
Jan 3, 2015
Messages
226
Trophies
0
XP
214
Country
Albania
Are your audio files both mono files?

Here's all the info on them. Also attached them in my previous post if you want to replicate the problem I'm having.
Blood.ogg said:
File size : 1.89 MiB
Duration : 3mn 30s
Overall bit rate mode : Variable
Overall bit rate : 75.6 Kbps
Track name : s01
Length : 000000210326

Audio
ID : 11083 (0x2B4B)
Format : Vorbis
Format settings, Floor : 1
Duration : 3mn 30s
Bit rate mode : Variable
Bit rate : 88.0 Kbps
Channel(s) : 2 channels
Sampling rate : 22.05 KHz
Compression mode : Lossy
Stream size : 2.21 MiB
Writing library : libVorbis (Omnipresent) (20120203 (Omnipresent))

Blood2.ogg said:
File size : 21.3 KiB
Duration : 1s 148ms
Overall bit rate mode : Variable
Overall bit rate : 152 Kbps
Writing application : Lavc52.27.0

Audio
ID : 0 (0x0)
Format : Vorbis
Format settings, Floor : 1
Duration : 1s 148ms
Bit rate mode : Variable
Bit rate : 160 Kbps
Channel(s) : 2 channels
Sampling rate : 44.1 KHz
Compression mode : Lossy
Stream size : 22.4 KiB
Writing library : libVorbis 1.2 (UTC 2007-06-22)
 

Rinnegatamante

Well-Known Member
Member
Joined
Nov 24, 2014
Messages
3,162
Trophies
2
Age
29
Location
Bologna
Website
rinnegatamante.it
XP
4,858
Country
Italy
They are both stereo files and you give only one channel :/
You have to use double channels (like 0x08, 0x09) for stereo files.

Code:
void Sound.play(wav_id wav_file, int loop, u32 channel, [u32 channel2]) - Play a loaded sound.

PS for very little file DON'T use streaming feature (like 1s audio files).
 
  • Like
Reactions: dfsa3fdvc1

dfsa3fdvc1

Well-Known Member
OP
Member
Joined
Jan 3, 2015
Messages
226
Trophies
0
XP
214
Country
Albania
Oh wow missed that. Lol. Appreciate the help.
The other stereo file worked fine so I totally glossed over the documentation thinking it'd work.
 

dfsa3fdvc1

Well-Known Member
OP
Member
Joined
Jan 3, 2015
Messages
226
Trophies
0
XP
214
Country
Albania
As for if/fi conditionals it's kind of just doing shit one line at a time. There really isn't much to them.

I finally got it working. Cleaned up a bunch of problems with earlier stuff that was hindering the very basic solution.
Working_IFFI.png
And built some menus which accommodate as many choices as are present
menus.png

They are both stereo files and you give only one channel
So I tried changing it and I still get crashes when playing that blood2.ogg
index.lua said:
test = Sound.openOgg("/blood2.ogg", false)
Sound.init()
Sound.play(test,NO_LOOP,0x08,0x09)

while true do
Screen.waitVblankStart()
Screen.refresh()
Screen.clear(TOP_SCREEN)
pad = Controls.read()
if (Controls.check(pad,KEY_A)) then
Sound.close(test)
Sound.term()
System.exit()
end
if (Controls.check(pad,KEY_B)) and not (Controls.check(oldpad,KEY_B)) then
if (Sound.isPlaying(test)) then
Sound.pause(test)
else
Sound.resume(test)
end
end
Screen.flip()
oldpad = pad
end
 
Last edited by dfsa3fdvc1,

Site & Scene News

Popular threads in this forum

General chit-chat
Help Users
    realtimesave @ realtimesave: got pretty much all my money back