Register - Login
Views: 99794528
Main - Memberlist - Active users - Calendar - Wiki - IRC Chat - Online users
Ranks - Rules/FAQ - Stats - Latest Posts - Color Chart - Smilies
05-03-22 05:15:35 AM
Jul - General Game/ROM Hacking - Virtual Boy Wario Land New poll - New thread - New reply
Next newer thread | Next older thread
GuyPerfect
Catgirl
Level: 68


Posts: 798/1096
EXP: 2665653
For next: 63147

Since: 07-23-07


Since last post: 1.7 years
Last activity: 219 days

Posted on 12-09-12 04:05:54 AM (last edited by GuyPerfect at 12-09-12 04:07:09 AM) Link | Quote
I finally got ahold of a Virtual Boy, for those who can remember I that was wanting one. Today, I took a quick glance at Virtual Boy Wario Land's level data. And here's what I found!

My target was the main area of the first level, after the room with the swinging spiky balls. I found two bits of pertinent data for that room. The room is composed of 32x32 pixel "blocks", and there are 176 columns and 7 rows of blocks for that room. At least on the front layer. The back layer of course is smaller, and may count for more "blocks", but I haven't done any hacking on that yet.

At 0x0725EC in the ROM file is the start of the main foreground terrain layer. Data is encoded as a simple RLE format specifying block IDs for each cell in the "block" canvas, starting at the top-left. The stream is broken up into data chunks that start with a control byte. This byte is an 8-bit, signed integer in two's complement format. For raw and repeated data alike, the value is largely used as a block counter.

• If the count is negative, the data chunk represents a repeating value. The negative of the count value (its absolute value, etc.) is the number of times to repeat the next block, and the following byte is the block ID. This means a maximum of 128 repetitions in a single data chunk.
• Otherwise, the count value is used directly, and that many additional bytes are read from the stream and used as block IDs. The maximum number of un-compressed block IDs in a single data chunk is 127.

I couldn't get this image hosted anywhere where it wasn't unceremoniously shrunken, so I've included it directly into this post. For full-size, you'll have to right-click it and "view image":



Those faint spikes are always present in the level. On a normal game, they're empty space, but in the extra game, they're full-on spikes. I dimmed them with an image editor to indicate that they're special pieces.

Before this data, at 0x071E5C, is the graphical foreground layer. However, unlike the terrain, it's not compressed. It's just a big grid of 176 x 7 block IDs (one byte each) that appear in front of the action, but scroll with the terrain.

Compared to Game Boy Wario Land's level data, this one is delightfully straightforward. I expect the background layers are similarly encoded and function in the same manner... From what I've done so far, though, all items are built into the blocks themselves. So instead of defining a level with a block, and then an item at the same location as the block, you just need to specify "a block with an item in it" at whatever location.

One of the joys of doing stuff like this is that you find secrets you never knew about, and Virtual Boy Wario Land in general is chock-full of secret areas. In particular, just right of the center of the room is the platform where the key is located. You can see in the image a non-descript block just floating there. I made that graphic to represent one of those kinds of blocks that is invisible until you jump up under it, but didn't go through to find out what was actually inside it. Well, like the image shows, there happens to be one there on that platform, and in-game testing reveals the block has Garlic in it. So many times I've played through the game, and I always just grabbed the key and jumped back to the background without ever knowing that Garlic was there.

Well, bed time for me now. But I'm itching to see what kinds of leftovers might be tucked away in this game.
Peardian

  
Magikoopa

16/3/1: KvSG #479 is up!

Level: 157


Posts: 6518/7597
EXP: 48602577
For next: 976656

Since: 08-02-07

From: Isle Delfino

Since last post: 10 days
Last activity: 11 hours

Posted on 12-09-12 02:22:09 PM Link | Quote
Very cool! I tried poking around in the game once, but found absolutely nothing.

____________________
-Peardian-

"Kindness is the language which the deaf can hear and the blind can see." -Mark Twain


GuyPerfect
Catgirl
Level: 68


Posts: 799/1096
EXP: 2665653
For next: 63147

Since: 07-23-07


Since last post: 1.7 years
Last activity: 219 days

Posted on 12-09-12 04:49:21 PM (last edited by GuyPerfect at 12-09-12 05:44:58 PM) Link | Quote
The hacking technique I like to use has served me well for many years, even as far back as when I made the level editor for F-Zero X. So far, it's never failed me!

I wrote a program that accepts an input file and two offsets. Every byte from the first offset to the second offset is simply set to 0x00 and an output file is produced. By selecting regions of a ROM to clear out in this manner, the output files can be loaded in emulators to see what effects they have. Byte regions are then narrowed down until they only modify desired parts of the game, such as level or sound data, and storage formats are subsequently derived by tampering with a hex editor.

This doesn't work in all cases, however. Some systems, like the Wii, run an operating system and potentially map game programs into virtual memory regions (though not necessarily). This holds true for any desktop computer game, as well as more sophisticated handheld devices like Android or Apple phones and tablets. For other systems, generally those loaded in cartridge form, program data is mapped straight onto the system bus and the game program has executive control over the entire system. These are the games where this hacking technique really shines.

Generally speaking, games store program code first, followed by regions of resource data like images, text and sounds. At times, there will be data mixed in with programming, especially on older systems like NES and Game Boy where PRG-ROM bank mapping would make program code from other banks inaccessible after a switch. Still, in the grand scheme of things, you have program code first, and resource data second.

On Virtual Boy, the reverse seems to be true, for the most part. Due to how the hardware works, the last 16 bytes of a Virtual Boy ROM image express the program code for the reset interrupt handler. This is enough space for 1 to 8 instructions, meaning you can populate a register with a 32-bit address and then jump to that address. In Wario Land, the following is at that address:
FFFFFFF0  20 BC FC FF  MOVHI   FFFCh, r0, r1

FFFFFFF4 21 A0 56 E9 MOVEA E956h, r1, r1
FFFFFFF8 01 18 JMP r1
The result of this code is that program execution is transferred to system bus address 0xFFFBE956. This corresponds with 0x07FBE956 on the bus after discarding the upper 5 bits, which is in cartridge ROM address space, in turn corresponding with 0x1BE956 in the ROM file. At the end of the day it could have jumped directly to 0x071BE956, but it didn't. This indicates that the compiler used for commercial Virtual Boy games started at the end and worked backwards.

In the case of Wario Land's level data, I kinda lucked out.

I actually wrote a program to preview 8x8 pixel tile patterns and was browsing through the file when I reached 0x070000 and noticed some pretty big regions of emptiness; it looked like uncompressed data with a lot of space. This is exactly the kind of thing you'd see in level data, where most of a map is empty air with some obstacles thrown in. By clearing out portions of the ROM near that address and loading the resulting mods in an emulator, I found that they did in fact alter the data for the first level. It was a matter of narrowing it down to the first observable change, which was part of the graphical foreground layer.

Fortunately enough for me, the main terrain data occurred right after the foreground graphics. However, counter-intuitively, THAT data was compressed, so I had to work out the compression format (which seems to be a curse of mine). After hammering that out, I modified the level data a bit to get the graphics for each of the 256 blocks, then put those graphics in an image renderer that produced the full image posted in the OP.

So the moral of the story is, blow away whole regions of ROM files and test them in an emulator to try and mess up the data you're interested in.
__________

EDIT:

On further inspection, it would seem that the reset interrupt handler code is woefully excessive. While it's true that you'll need two instructions to populate a register with 32 bits of immediate data (MOVHI and MOVEA do not affect the status flags), you don't actually need to do that for jumps. The jump in question is a distance that can be represented in 19 bits: 0xFFFFFFF0 - 0xFFFBE956 = 0x0004169A. The V810 has an instruction JR that is a 26-bit signed jump, meaning you can make that hop in just 4 bytes:
FFFFFFF0  FB AB 66 E9  JR      FFFBE956
Hooray for compilers making fat code!
Raccoon Sam
Member
free speech disabled
Level: 32


Posts: 137/187
EXP: 187838
For next: 18604

Since: 07-25-07

From: Somewhat

Since last post: 4.5 years
Last activity: 1.1 years

Posted on 12-10-12 06:23:43 AM Link | Quote
That is exactly what I did back a few times ago. The RLE-ish compression was pretty straightforward, but the ViBE Emulator can't play ROMs that have a bad checksum, so it was a tedious project. Needless to say, I didn't find anything level-related, but my lousy (and incomplete) documentation and visual representation of how the sprites work is readable here.

But aside from that, fantastic job yet again. Your work never ceases to amaze me and this time is no exception. VBWL is one of my favorite games ever and I would love to think this evolved into something great.

(Y)
GuyPerfect
Catgirl
Level: 68


Posts: 801/1096
EXP: 2665653
For next: 63147

Since: 07-23-07


Since last post: 1.7 years
Last activity: 219 days

Posted on 12-10-12 08:54:54 PM (last edited by GuyPerfect at 12-10-12 09:03:13 PM) Link | Quote
My curiosity got the better of me, so I checked out what the reset handler was jumping to--that code at 0xFFFBE956. It looks like a simple system initializer routine that is established and runs before main(). I suppose there's a chance it is main(), but I can't say for sure right now.

Here's a C code representation of what I picked apart in the disassembly:
// Code executed by reset interrupt handler

void OnReset() {

// Inert loop. Probably a delay to let system components start up.
// The whole thing runs for 262136 cycles, which is ~0.0125 seconds.
for (temp = 0xFFFF; --temp; );

// Configure system registers
StatusRegister = 0; // LDSR 0 into PSW
InterruptsEnable = true; // SEI
CacheEnable = true; // LDSR 2 into CHCW

// Configure control registers -- Need more info on these
*(char *)0x02000024 = 1; // "WROM 1-wait", whatever that is
*(char *)0x01000580 = 1; // SMREG control register, also unknown

// Initialize memory regions -- Actual code uses an alternative to memset()
memset((void *) 0x00020000, 0xFF, 0x20000); // BG, param, window, OAM
memset((void *) 0x00078000, 0, 0x8000); // Character memory
memset((void *) 0x05000000, 0, 0x10000); // System RAM (64k)

// Preparations for program execution
*(char *)0x02000020 = 8; // I/O timer interrupt enable

// Code of unknown significance
call_FFFE4000(); // Unknown function -- Possibly main()
InterruptsEnable = false; // CLI
call_FFFBED4E(); // Unknown function -- Why uninitialize?

// System dealock. With interrupts disabled, HALT will never wake.
sleep(forever); // HALT
StatusRegister = 0; // LDSR 0 into PSW
return; // JMP r31 -- I wish I was kidding.
}
All graphics are drawn into windows, which are rectangular regions on the screens. Even when you're dealing with sprites, they go inside a window (the window's display mode is configured to draw sprites). Therefore, if no windows are enabled, nothing gets drawn. Windows have a flag that is used to indicate "no more windows", as in, "this window and all windows after this will not be drawn." Therefore, setting every byte of window memory to 0xFF will initialize the system to draw no windows as output.

Additionally, in the event you do configure a window to display objects (sprites), you'll still get nothing on the screen because the X and Y coordinates for objects are 16 bits each. This places their position at -32768 on both axes, well out of the visible range (espeically since all objects consist of single 8x8 pixel characters).

Character (tile) memory, on the other hand, is initialized to 0x00, which represents nothing but transparent pixels. It's technially stored at four memory locations on the system bus: 0x00006000, 0x0000E000, 0x00016000 and 0x0001E000 and are 0x2000 bytes each, meaning these regions are non-sequential. However, character memory is linearly mirrored at 0x00078000 through 0x0007FFFF, which IS sequential, and is where this code is writing to.

System RAM is also initialized to 0x00 in this code, indicating that the contents of system RAM on reset is undefined. See, we can infer some good info by looking at code! You don't want to initialize cartridge RAM in this manner, though, since that might be battery backed and doing so will erase your save data.

The function used in place of memset() in my pseudo-code differs slightly in that it writes 32-bit units to memory and it counts those instead of individual bytes. The disassembly is as follows:
FFFC2A94  40 01        MOV     r0, r10

FFFC2A96 00 A8 0C 00 JR FFFC2AA2
FFFC2A9A 06 DD 00 00 ST.W r8, 0h[r6]
FFFC2A9E C4 44 ADD 4h, r6
FFFC2AA0 41 45 ADD 1h, r10
FFFC2AA2 47 0D CMP r7, r10
FFFC2AA4 F6 83 BL FFFC2A9A
FFFC2AA6 1F 18 JMP r31
Going in, r6 contains the starting address, r7 contains the number of words to write (32 bits on V810, meaning the number of bytes written is 4 times this), and r8 contains the value to store. The values passed to this function are -1, 0 and 0 (respectively), meaning the memset() code I made is an accurate alternative. All other things being equal, the code that exists in the game is faster than your standard memset(). But part of me wonders if it could be done even faster using the MOVBSU instruction, which I don't reckon any compiler actually supports.

EDIT: Holy crap, why is it using the 32-bit JR instruction to jump 12 bytes forward? What's wrong with the 16-bit BR instruction? Man, I can tell already that working on this game is going to make me very sad about the efficiency of compilers in the mid 90s.
Rena
I had one (1) message in Discord deleted and proceeded to make a huge, huge mess about how it was a violation of free speech and how moderators are supposed to be spam janitors and nobody should have the right to tell me not to talk about school shootings
Level: 135


Posts: 4937/5390
EXP: 29075353
For next: 259652

Since: 07-22-07

Pronouns: he/him/whatever
From: RSP Segment 6

Since last post: 342 days
Last activity: 342 days

Posted on 12-11-12 12:17:49 AM Link | Quote
Post #4937 · Mon 121210 201749
Originally posted by GuyPerfect
*(char *)0x02000024 = 1; // "WROM 1-wait", whatever that is
I'd guess wait state control, though I'm not entirely sure what that means.


call_FFFBED4E(); // Unknown function -- Why uninitialize?
Unless it's compiler boilerplate (freeing up all allocated objects?), my first guess would be that this function's purpose is to report a message somewhere, along the lines of "game_main() returned, which should never happen".

I think the actual entry point for most compilers is _start(), which is usually part of a standard library that sets up things like command-line arguments and calls main(). But this compiler probably doesn't obey standards that strictly... I'd guess OnReset() is actually _start(), call_FFFE4000() is main(), and everything after that is what would be "tell the OS to terminate the program", if there were an OS... but since there isn't, and actually returning from _start() would be nonsensical, I'm not sure what to think of that.

It's pretty safe to say the compiler is not that clever, given it bothers to return from a function that contains an unbreakable infinite loop, and to where? The reset handler isn't a loop, is it?

____________________
GuyPerfect
Catgirl
Level: 68


Posts: 802/1096
EXP: 2665653
For next: 63147

Since: 07-23-07


Since last post: 1.7 years
Last activity: 219 days

Posted on 12-11-12 02:36:48 AM (last edited by GuyPerfect at 12-11-12 02:39:06 AM) Link | Quote
Originally posted by Rena
It's pretty safe to say the compiler is not that clever, given it bothers to return from a function that contains an unbreakable infinite loop, and to where? The reset handler isn't a loop, is it?
This function, which I've called OnReset(), is jumped to directly from the reset interrupt handler. OnReset() is located at 0xFFFBE956, and the only code at 0xFFFFFFF0 sets up a register with that address and then jumps to it. That is to say, it's not a function being called, but is rather a self-contained procedure in and of itself.

OnReset() does not implement a stack (V810 reserves r3--and to a lesser extent, r2--for this purpose, but does not natively support a stack in the hardware). r31 is therefore not pulled from a stack, so its value will be whatever was in the register at the end of the last function called. In this case, that ends up being the address of the HALT instruction. Hardly a "return" in the typical sense, but I suppose at the end of the day it can't possibly malfunction: the system will be in deadlock until a new reset occurs.

The end of the function looks like this:
FFFBE9D4  02 AC 2C 56  JAL     FFFE4000

FFFBE9D8 00 58 CLI
FFFBE9DA 00 AC 74 03 JAL FFFBED4E
FFFBE9DE 00 68 HALT
FFFBE9E0 05 70 LDSR r0, PSW
FFFBE9E2 1F 18 JMP r31
My money's on FFFE4000 being main(), and with such a perfect address, how can you go wrong!? Though FFFBED4E is still a mystery until further analysis can be done.

I just ran a disassembly at 0xFFFE4000 and the function is surprisingly short. Bear in mind that I'm examining it while typing up this post:
FFFE4000  40 BE 00 01  MOVHI   0100h, r0, r18

FFFE4004 60 BE 00 05 MOVHI 0500h, r0, r19
FFFE4008 7C 44 ADD -4h, r3
FFFE400A E3 DF 00 00 ST.W r31, 0h[r3]
FFFE400E 00 AC AE AF JAL FFFEEFBC
FFFE4012 00 AC 72 02 JAL FFFE4284
FFFE4016 00 AC 26 02 JAL FFFE423C
FFFE401A 00 AC 46 02 JAL FFFE4260
FFFE401E 00 AC FA 01 JAL FFFE4218
FFFE4022 E3 CF 00 00 LD.W 0h[r3], r31
FFFE4026 64 44 ADD 4h, r3
FFFE4028 1F 18 JMP r31
Okay, I can see that this function does implement a stack. You can see it decrementing r3 (the reserved stack pointer) and then writing r31 (the JAL return address) to the new location. I didn't document it in my previous post, but the first thing done by OnReset() is to initialize r3 to 0x0500FFFC and r4 to 0x05008000. r4 is the register reserved for .data, or otherwise the address where global variables start. Virtual Boy has 64k of on-board RAM, so it looks like it's dedicating half to stack and half to static memory.

I took a look at all five of those functions and they're each fairly discreet, setting values in memory and returning. I don't think this is main().

Looking at the other function from OnReset(), however, the one at 0xFFFBED4E... Yeah, that's gotta be main(). It's pretty large, calls lots of functions, and has a fair amount of conditional logic. The first thing it does is back up r3 a ways, stores some values relative to it, then calls a function. That's standard issue behavior for a function establishing local variables and function parameters.

So this begs the question... Why would the program enable interrupts, call a bunch of initialization routines, then disable interrupts before calling main()?
Rena
I had one (1) message in Discord deleted and proceeded to make a huge, huge mess about how it was a violation of free speech and how moderators are supposed to be spam janitors and nobody should have the right to tell me not to talk about school shootings
Level: 135


Posts: 4939/5390
EXP: 29075353
For next: 259652

Since: 07-22-07

Pronouns: he/him/whatever
From: RSP Segment 6

Since last post: 342 days
Last activity: 342 days

Posted on 12-12-12 12:54:31 AM Link | Quote
Post #4939 · Tue 121211 205431
I wonder if that's some debug menu, or the leftover stub of one. Enables interrupts so that it could show some configuration menu, disables them before finishing because it's good practice to put things back the way they were, then main() would enable them again and use those settings.

____________________
GuyPerfect
Catgirl
Level: 68


Posts: 803/1096
EXP: 2665653
For next: 63147

Since: 07-23-07


Since last post: 1.7 years
Last activity: 219 days

Posted on 12-13-12 09:40:31 PM (last edited by GuyPerfect at 12-14-12 03:00:16 AM) Link | Quote
I re-wired my disassembler to record forward jumps, function calls and return statements in the form of JMP r31. Barring manual programming in assembly and linker templates, it should be a pretty good indication of all of the code accessible through normal compiled source.

Running the new disassembler on 0xFFFBE956 yielded 121 functions, each neatly disassembled to its own text file. Assuming I've done everything right, this should account for most of the game, excluding interrupt handlers. But does 121 functions sound like enough for Wario Land? I mean, it might be, but I don't know. It's a small enough number, though, so poking through them to derive their true meaning won't take too long.


EDIT:
I've picked through the code of that initializer function and found that it is indeed just prepping the system for use by the game program. This is what the constituent functions do:

• FFFEEFBC - Sets the sequence of values 01 CC 00 -- to all bytes from 0x05000400 to 0x05000436 (14 times). The -- bytes are not written.
• FFFE4284 - Sets 0x050000F5 to 0xFF and "SMREG" to 1. Need to find out what that register does.
• FFFE423C - Copies 44 words (32-bit values) from 0xFFFE673C (0x1E673C in the ROM file) to 0x05000010.
• FFFE4260 - Resets sound registers. Copies 192 sequential bytes from 0xFFFE667C to every fourth byte at 0x01000000.
• FFFE4218 - Resets controller registers. Sets the clock rate to 200 something (probably hertz), enables key interrupts and resets key clock.

The first three functions all initialize values in the 0x05000000-0x0500FFFF range, which is the on-board system memory. The first one appears to be writing template values for debugging purposes, the second one only sets one value, and the third one appears to be initializing static variables via .data.


EDIT 2:
Not surprisingly, there isn't a whole lot of good documentation out there for Virtual Boy's hardware registers. From what I can tell, "SMREG" is the master sound switch. When it's set to 1, the audio subsystem is enabled.
GuyPerfect
Catgirl
Level: 68


Posts: 805/1096
EXP: 2665653
For next: 63147

Since: 07-23-07


Since last post: 1.7 years
Last activity: 219 days

Posted on 12-21-12 05:23:37 PM Link | Quote
While documenting some stuff on the Virtual Boy's CPU, I came across the interrupt bit in status register. Turns out that bit is set to disable interrupts, not enable them. This was likewise the case on NES and SNES. Therefore, SEI doesn't enable maskable interrupts; it prevents them from occurring.

The startup code is in fact ensuring no interrupts occur while it initializes, and then re-enables them before calling main.
GuyPerfect
Catgirl
Level: 68


Posts: 806/1096
EXP: 2665653
For next: 63147

Since: 07-23-07


Since last post: 1.7 years
Last activity: 219 days

Posted on 12-23-12 09:46:30 PM (last edited by GuyPerfect at 12-23-12 09:47:12 PM) Link | Quote
Well, connections got me a copy of the official development manual, so my attention will be turned elsewhere. But at least I have some explanations now.
  • The reason for the dummy loop that occurs first thing when the system starts up is to allow the working memory of the control unit to "warm up" prior to use. The document specifies a 200 microsecond delay, but the Wario Land code lasts easily 12000 times that. The document also encourages 8 dummy reads on the bus.

  • 0x02000024 is the Wait Control register (WCR), and bit 0 is the wait configuration for cartridge ROM. The reset default of 0 specifies 2 waits for ROM reads, where setting the bit to 1 speeds it up to only 1 wait. The only other significant bit of this register is bit 1, which behaves the same way for the unused-in-any-commercial-games cartridge expansion capability.

  • 0x01000580 is the Stop All Sound Output register (SSTOP), and only bit 0 is significant. When set, no audio output is produced.

Next newer thread | Next older thread
Jul - General Game/ROM Hacking - Virtual Boy Wario Land New poll - New thread - New reply


Rusted Logic

Acmlmboard - commit 47be4dc [2021-08-23]
©2000-2022 Acmlm, Xkeeper, Kaito Sinclaire, et al.

29 database queries, 6 query cache hits.
Query execution time: 0.089829 seconds
Script execution time: 0.036336 seconds
Total render time: 0.126165 seconds