Register - Login
Views: 99409482
Main - Memberlist - Active users - Calendar - Wiki - IRC Chat - Online users
Ranks - Rules/FAQ - Stats - Latest Posts - Color Chart - Smilies
04-24-22 10:46:31 PM
Jul - General Game/ROM Hacking - Food For Thought... (NES Emulation) Black and Red 3DChat New poll - New thread - New reply
Pages: 1 2 3 Next newer thread | Next older thread
GuyPerfect
Catgirl
Level: 68


Posts: 704/1096
EXP: 2663610
For next: 65190

Since: 07-23-07


Since last post: 1.6 years
Last activity: 211 days

Posted on 05-10-12 10:49:45 PM (last edited by GuyPerfect at 05-10-12 11:03:51 PM) Link | Quote
So I'm doing some work hacking NES games and, while FCEUX's debugger has been sufficient for what I'm doing, I've found myself wanting to do a few things that are way beyond its scope... Things like:
  • Function extracts, where addresses jumped to by JSR are automatically parsed (marking all of the code for the given function) and added to a list. This can be helpful to isolate code and assign meaning to it given just the address, which could take the form of a symbol name used in the disassembly either as a comment or a label name.
  • Complex breakpoint conditions, like... only break on read for this address if the value of a given register is N and the value at a given RAM address isn't in a certain range... for instance. More than once I've thought to myself, "I'd like to break when this RAM address is read, but NOT for any of these known instructions that read from it."
  • Automatic notation of I/O operations, probably in the form of comments in the disassembly. For example, if you see the instruction LDA $2002, you know the only reason it exists is to reset the VRAM address latch. There's no reason an emulator can't also know that. Similarly, it could auto-document mapper control writes in the upper address range.
  • Analysis of VRAM writes that can pinpoint where in the ROM data palettes and tiles came from. This should be doable in the event there's, in the case of tiles, 16 consecutive reads from ROM and the same values are immediately written to VRAM. Tile viewer programs tend to be a good way to hunt these down, but if it's easy, why not?
And then there's another thing... For many years I've had the idea kicking around in the back of my head for making an NES emulator of my own. Part of it is just for kicks, since the experience gained by doing something like this is a reward in and of itself. But I always back away because, frankly, it's been done a million times over and reinventing the wheel one more time really won't make the world a better place.

But even so, I could be suckered into making an emulator if only to gain access to advanced debugging functions like the ones I listed above. And I'd also write it in Java, since I've always wanted to write an emulator of some sort in Java. And heck, if I do it right, it can be compiled for Android too, come to think of it...

So hey, as long as I'm thinking out loud, what thoughts does everyone else have on this? What features have you always wanted to see? What kind of interest is there in seeing something like this actually exist?
Kazinsal

Level: 53


Posts: 271/674
EXP: 1122312
For next: 34807

Since: 01-19-11

Pronouns: he/him
From: Vancouver, Canada

Since last post: 233 days
Last activity: 192 days

Posted on 05-11-12 01:20:58 AM Link | Quote
Don't write it in Java. Java has no 8-bit unsigned types. Or 32-bit unsigned types. And the 16-bit one is only there because Unicode.

____________________
Tears fall from the shameless
Shelter me, guide me to the edge of the water
Selfless are the righteous
Burden me, lead me like a lamb to the slaughter
GuyPerfect
Catgirl
Level: 68


Posts: 705/1096
EXP: 2663610
For next: 65190

Since: 07-23-07


Since last post: 1.6 years
Last activity: 211 days

Posted on 05-11-12 03:03:23 AM Link | Quote
Fortunately, it works just the same using Java's signed data types: 0x7F + 0x03 = 0x82 no matter how you slice it. Such is the beauty of two's complement.

Even in contexts where unsigned bytes are needed--such as color channels (in general) or the iNES file header fields--it's trivial to convert values by doing something like this:

byte sVar = -64; // Signed 0xC0
int uVar = (int) sVar & 0xFF; // Unsigned 0xC0 (192)
sVar = (byte) (uVar & -1); // -64 again

There are very few aspects of NES that necessitate a distinction between signed and unsigned byte values. The most common of which--relative addressing via branch instructions--happens to require a signed interpretation, so Java would do just fine for something like this.

So while I appreciate the heads-up, it turns out it's not actually a problem.
Joe
Common spammer
🍬
Level: 111


Posts: 2544/3392
EXP: 14489926
For next: 378434

Since: 08-02-07

From: Pororoca

Since last post: 4 days
Last activity: 3 hours

Posted on 05-11-12 03:36:05 AM Link | Quote
You're going to spend a lot of time playing Battletoads.

I'd appreciate FCEUX-like debugging tools in a more accurate emulator, if only so that I can play with the color emphasis bits.

____________________
GuyPerfect
Catgirl
Level: 68


Posts: 706/1096
EXP: 2663610
For next: 65190

Since: 07-23-07


Since last post: 1.6 years
Last activity: 211 days

Posted on 05-11-12 04:23:15 AM Link | Quote
I wouldn't worry about the so-called "hard to emulate" games. It's a long-established fact that some NES games rely on the scanline:cpu cycles ratio, so an emulator should be designed with that in mind. Something like "this last instruction was 2 cycles, so render 2 cycles' worth of video and process 2 cycles' worth of audio."

It's not so much an issue in newer systems, where the CPU halts itself and then wakes on a VBlank interrupt--a behavior that allows for such tricks as just-in-time compilation--but it's most definitely an issue on NES.

You're spot-on with FCEUX's accuracy, though. It seems to work pretty well for the most part, though I've had some run-ins with it simply not supporting things correctly. You can't execute out of MMC5 PRG-RAM. Bank numbers in the disassembler are fixed to 16KB indexes, which can make it difficult to locate code in the ROM data (like MMC3's 8KB banks). It doesn't even support selecting 4 CHR-ROM banks into table 0 in MMC mappers whatsoever. Bank selects in UxROM require writing bank number N to address 0xC00N, rather than any address at or above 0x8000... Etc. etc. etc.

To be fair, many of these things (especially the PRG-RAM one) aren't just shortcomings of FCEUX--lots of emulators fail spectacularly to implement things that should already work out of the box. And that's something I don't understand. I mean, is someone paying them to emulate games so only the end result matters? If you're not going to do it right, why do it at all?
GuyPerfect
Catgirl
Level: 68


Posts: 708/1096
EXP: 2663610
For next: 65190

Since: 07-23-07


Since last post: 1.6 years
Last activity: 211 days

Posted on 05-11-12 05:49:24 PM (last edited by GuyPerfect at 05-11-12 05:59:28 PM) Link | Quote
Just jotting down some ideas here, in regards to the construction of an NES emulator.
  • A Processor interface, which establishes a memory address range that can be mapped to.
  • A Mapper interface, which maps addresses in a Processor's memory map and supplies Read and Write operations.
    • In the case of a cartridge mapper, this would handle bank switching among other things.
  • A CPU class, which implements Processor and handles the execution of program code.
    • Other Processors would be synchronized with the CPU on a per-cycle basis.
  • A PPU class, which implements both Processor and Mapper and takes care of rendering pictures.
    • It can have its memory addresses mapped by cartridge mappers, and it itself maps a few I/O ports in the CPU's address space.
  • An APU class, which implements Mapper (not Processor) and handles generation of audio.
    • Needs to connect with cartridge mappers somehow for sound expansions.
  • A Controller class, which implements Mapper and handles joypad input.
The main attraction here is CPU, which will handle all the juicy stuff like breakpoints and disassembly.

EDIT:
Thinking on it, there should also be a Cartridge class that implements Mapper and explicitly requires CPU, PPU and APU in its constructor. The output of the APU would be passed to the cartridge, and then cartridge's output is what winds up at the speakers.
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: 4746/5390
EXP: 29053074
For next: 281931

Since: 07-22-07

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

Since last post: 334 days
Last activity: 334 days

Posted on 05-12-12 07:13:27 AM Link | Quote
Post #4746 · 05-12-12 02:13:27 AM
Originally posted by GuyPerfect
There are very few aspects of NES that necessitate a distinction between signed and unsigned byte values. The most common of which--relative addressing via branch instructions--happens to require a signed interpretation, so Java would do just fine for something like this.

So while I appreciate the heads-up, it turns out it's not actually a problem.
True, but Java is also terrible.

I've been working on a Game Boy emulator lately (C++ modules with Lua to glue them together). Probably will put the source up on Github sometime relatively Soon™. It tries to mimic how a real system is designed - the CPU is one module, the memory another, the GPU another, etc. CPU asks memory for a byte, executes it, tells the other modules how many cycles it just executed, providing the master "clock signal". GPU draws a pixel for every n cycles. CPU knows nothing about bank switching or video or etc, it just sends an address/data to the memory controller, which passes them on to whatever is mapped to that address. ROM bankswitching is done just like in reality - a memory controller sits between ROM and CPU, and maps different areas of ROM into the address space when bank numbers are written to certain ROM addresses. Even, the "display image on screen" logic is separate from the "turn bits into image" logic (Display module gives GPU a pixel buffer to write into), the "get input from keyboard/joypad/etc" logic is separate from the "handle joybutton register" logic, etc, so the frontend can be switched out as well.

To me that felt like the best way to write an emulator, since it closely resembles the construction of the real system, and it's easy to add support for new things as needed. (New cartridge type? Just need to write a module to emulate its memory controller. Color support? Just some changes to the GPU module.) Using Lua to bind things together also introduces a ton of interesting possibilities for user scripting, while not hampering speed - Lua is already quite fast, and 99% of runtime is pure C code; you set up the CPU and modules and tell it to go and call back into the script if something unusual happens (a breakpoint hit, user pressed the stop button, invalid opcode, etc).

The one major downside is that you have potentially a lot of code running for every memory access, and every cycle (4 million per second) is doing at least one memory access, usually more (CPU instruction fetch 1-3 bytes, GPU pixel data fetch, etc), so it can be slow... needs a lot of optimization.


____________________
GuyPerfect
Catgirl
Level: 68


Posts: 712/1096
EXP: 2663610
For next: 65190

Since: 07-23-07


Since last post: 1.6 years
Last activity: 211 days

Posted on 05-12-12 03:24:23 PM Link | Quote
Sounds like we're taking an identical approach. I assume it works really freaking totally awesomely well?

I've given some thought into managing memory access. Because of the way it works, every read and write has to pass through the mapping controller since there might be further processing involved than simply reads and writes. And you want to enforce integrity when attempting to access un-mapped addresses, like the $6000-$7FFF range when WRAM is not present. And then of course there's breakpoints and whatever else that would be injected on top by the emulator.

For the sake of efficiency, all of this needs to be implemented in as few steps as possible: Is this mapped? Is there a breakpoint on it? Does it require any mapping behavior? If not, it shouldn't waste time thinking about it. Preferably, it won't even run an if statement in the event nothing needs to be done. Optimization can be an interesting field.

So there's not a lot of love for Java, eh? Sure, it routinely loads a good 40MB of runtime for simple programs, but it runs on nigh-on everything! It's not slow, it's got good hardware support, it abstracts all I/O into a handy API... And it can embed into web pages!

But it's also not compatible with anything. If you write something in Java, it runs on Java and only Java. Maybe I should do this in C after all.
GuyPerfect
Catgirl
Level: 68


Posts: 713/1096
EXP: 2663610
For next: 65190

Since: 07-23-07


Since last post: 1.6 years
Last activity: 211 days

Posted on 05-12-12 04:22:59 PM (last edited by GuyPerfect at 05-12-12 04:36:04 PM) Link | Quote
Further thinking on memory mapping and optimization... !

(I've switched into C mode now, so instead of saying "method of an instance" I will now say "function pointer")

A Processor has a memory range, of which individual addresses can be mapped on the byte level. The CPU's address range is a full 16 bits, so 0x0000 through 0xFFFF. Every individual byte therein may or may not be mapped at all and may or may not have have a read/write/execute breakpoint on it. And mapped addresses may or may not have side effects on read/write, such as the I/O ports and cartridge mapping control registers. You really don't want to waste time with things like "is there a Mapper active for a range containing this address, and if so what is it?"

This can become downright villainous on larger systems, but NES has all of 64KB to worry about so I think we can get away with it... Every individual address can have some values associated with it. A few come to mind:
  • The mapper (if any) handling the address
  • The read breakpoint(s) (if any)
  • The write breakpoint(s) (if any)
  • The execute breakpoint(s) (if any)
And the mapper itself would know a few things, like:
  • The location in the ROM data this address occurs (based on bank number)
  • Whether the address can be written to (such as with PRG-RAM)
  • Whether the address controls mapping in some way
So in the case of the CPU list, there's going to be four pointers per address, where the mapper points to the mapper object that can process the address, and the breakpoints point to lists of emulator breakpoints currently active on the address. Reads and writes can trigger exceptions when the address is not mapped, and the breakpoints can simply return true if a break occurs.

Pointers take up sizeof (void *) bytes in memory, so 64KB of addresses * 4 pointers * 8 bytes for 64-bit systems, we're looking at 2MB of data sitting around in memory at all times. And honestly, that's not bad at all. It's half that for 32-bit systems. When the emulation core needs to check memory addresses for auxiliary behavior, it doesn't have to go hunting through fancy dynamic lists or anything--it just has to look over to the side and immediately get its answers.

In the case of more advanced systems like Game Boy Advance or Nintendo 64, we can generally rely on the fact that every commercial game was made with compilers and not hand-programmed in assembly. They also don't have mappers, save for a few regions reserved for expansion hardware, so dynamic compilation is a very attractive solution when emulating those systems. SNES might get sticky, though. It's assembly like NES (quite literally), but it has a 16MB address range... Oh well, I'll burn that bridge when I come to it.

In other news, just some notes for possible types of exceptions the emulator can trigger:
  • Attempting to access unmapped addresses
  • Attempting to execute, for instance, a 3-byte instruction at $FFFE
  • Execution of invalid opcodes, possibly with an option to execute them as "undocumented"
  • Specifying more than one color emphasis bit
  • Attempting to switch to an unavailable cartridge bank
  • A stack overflow occurs
  • Attempting to write to video memory while the frame is rendering
  • Writing a palette value with either of the upper two bits set
devin

Yoshi
i'm mima irl
Level: 112


Posts: 2549/3519
EXP: 14919769
For next: 418436

Since: 04-29-08

Pronouns: any
From: FL

Since last post: 298 days
Last activity: 2 days

Posted on 05-12-12 05:05:54 PM (last edited by Don Que Hooty at 05-12-12 05:08:17 PM) Link | Quote
Originally posted by GuyPerfect
  • Execution of invalid opcodes, possibly with an option to execute them as "undocumented"


These are rather well documented here should you decide to implement them. I don't know how many NES emulators actually do (I can only think of VICE, which is a Commodore 64 emulator) - FCEUX's debugger just shows them as undefined, I'm not sure what it does when attempting to actually execute any illegal opcodes.

Of course this is probably a pretty minor endeavor anyway, since I don't know how many NES ROMs (commercial or otherwise) actually use the undocumented opcodes; a lot of them are just slower NOPs or halt the CPU, and very few of the rest are even remotely useful (LAX being the main exception, I think.)

The same page also details some obscure addressing bugs in the 6502, as well as a quirk in the way arithmetic works in decimal mode (although if I remember right, the NES doesn't even support the 6502's decimal mode to begin with, so you can probably ignore this part...)

____________________
Photo by Luc Viatour
Joe
Common spammer
🍬
Level: 111


Posts: 2546/3392
EXP: 14489926
For next: 378434

Since: 08-02-07

From: Pororoca

Since last post: 4 days
Last activity: 3 hours

Posted on 05-12-12 06:07:48 PM Link | Quote
Originally posted by Rena
CPU asks memory for a byte, executes it, tells the other modules how many cycles it just executed,
At least on the NES, there are a couple of corner cases that rely on events affecting the CPU (or PPU) mid-instruction. I can't recall if any games rely on this behavior, but it's there.
Originally posted by GuyPerfect
Specifying more than one color emphasis bit
There's nothing wrong with specifying multiple color emphasis bits; a few games use all three at once to darken the screen.
Originally posted by GuyPerfect
Attempting to switch to an unavailable cartridge bank
I don't know if it counts, but Atlantis no Nazo uses invalid values for all of its bank switches. It just happens to work due to mirroring.
Originally posted by Don Que Hooty
Originally posted by GuyPerfect
Execution of invalid opcodes, possibly with an option to execute them as "undocumented"
These are rather well documented here should you decide to implement them. I don't know how many NES emulators actually do (I can only think of VICE, which is a Commodore 64 emulator) - FCEUX's debugger just shows them as undefined, I'm not sure what it does when attempting to actually execute any illegal opcodes.
FCEUX supports at least one illegal opcode for Puzznic, and I would assume the more accuracy-oriented emulators support more. How about an exception that only catches opcodes that are non-deterministic or guaranteed to lock the CPU?

____________________
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: 4750/5390
EXP: 29053074
For next: 281931

Since: 07-22-07

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

Since last post: 334 days
Last activity: 334 days

Posted on 05-13-12 03:42:43 AM (last edited by Rena at 05-13-12 03:44:47 AM) Link | Quote
Post #4750 · 05-12-12 10:42:43 PM
Originally posted by GuyPerfect
In the case of more advanced systems like Game Boy Advance or Nintendo 64, we can generally rely on the fact that every commercial game was made with compilers and not hand-programmed in assembly. They also don't have mappers, save for a few regions reserved for expansion hardware, so dynamic compilation is a very attractive solution when emulating those systems.
Ehh, an emulator should emulate the machine itself, not rely on quirks of some software. That kind of thing is why N64 emulation sucks so much today. You might find a shortcut that works well for the majority of games compiled using version X of program Y, but then you get a few games compiled differently, or using assembly, or ROM hacks or homebrew or unlicensed games, and it all falls apart. Look at how poorly supported any game not using Nintendo's standard microcodes is... IIRC there are some N64 games that still don't work in most emulators because they used a custom microcode.


My approach to breakpoints was to just add another module to the memory chain, having an array of flags telling whether each byte has a breakpoint set. Multiple modules can be mapped to a region of memory, and each is called in series. The breakpoint module simply tells the CPU to pause when an access breakpoint hits. Of course this module can be unmapped when not in use to save some cycles. Another example of this system in use is that the GPU is mapped to almost the entire memory space, so that during OAM DMA it can block access to everything but the upper page, just like on the real system.

That means every memory access goes through a list of modules (though that list might contain only one item), so I divide the memory space into blocks, with each block having its own list. Right now it's set to 32-byte blocks (2048 blocks); this can be changed at compile time. (I tried 1-byte blocks just for kicks. No noticeable improvement.) So e.g. accesses to ROM only go through Debug (if enabled), GPU (for DMA blocking, which might get optimized into something different later), and memory mapper/ROM emulator; they don't have to get bogged down passing through the modules for input, sound, I/O etc.


Originally posted by GuyPerfect
In other news, just some notes for possible types of exceptions the emulator can trigger:
  • Attempting to access unmapped addresses
  • Attempting to execute, for instance, a 3-byte instruction at $FFFE
  • Execution of invalid opcodes, possibly with an option to execute them as "undocumented"
  • Attempting to switch to an unavailable cartridge bank
  • A stack overflow occurs
  • Attempting to write to video memory while the frame is rendering
  • Writing a palette value with either of the upper two bits set

I'm not sure why you want to trap these events or treat them as unusual? Again, it's an emulator, so it should emulate what the machine does, even if that means the game crashes. How do you tell if the stack has overflowed? By SP being set to some particular value, or an uneven number of push/pop instructions? But maybe some game does such things deliberately. Pokémon for example uses it as a 16-bit auto-incrementing pointer to copy tilemaps to VRAM:

ld (old_sp),sp
ld sp,tilemap_src
ld hl,tilemap_dst
pop bc
ldi (hl),b
ldi (hl),c
pop bc
ldi (hl),b
ldi (hl),c
...
ld sp,(old_sp)
So for some time, SP is pointing into some arbitrary RAM and doing a lot of pops without a corresponding push. Whether that means the game is just updating VRAM, or is broken and corrupting itself, is of no concern to the CPU. Heck, maybe we want it to corrupt itself! Maybe that corruption is something the player wanted to happen! Even if the game does collapse in on itself, the machine is still chugging away executing code; it just isn't producing a result that seems very useful.

One thing I had to make sure of in designing my memory interface is that accesses at the edges of boundaries (like a 16-bit read from 0x7FFF) work correctly, because they do happen. This means every 16-bit access is actually handled as two 8-bit accesses (just like the real thing!). This does bug me a bit, because it seems like something that could be made much faster, but I'm not sure how to improve it...

____________________
Joe
Common spammer
🍬
Level: 111


Posts: 2547/3392
EXP: 14489926
For next: 378434

Since: 08-02-07

From: Pororoca

Since last post: 4 days
Last activity: 3 hours

Posted on 05-13-12 05:36:21 AM Link | Quote
Originally posted by Rena
That means every memory access goes through a list of modules (though that list might contain only one item), so I divide the memory space into blocks, with each block having its own list. Right now it's set to 32-byte blocks (2048 blocks); this can be changed at compile time. (I tried 1-byte blocks just for kicks. No noticeable improvement.) So e.g. accesses to ROM only go through Debug (if enabled), GPU (for DMA blocking, which might get optimized into something different later), and memory mapper/ROM emulator; they don't have to get bogged down passing through the modules for input, sound, I/O etc.
As modular and convenient as this sounds, I can see why you'd be looking for ways to optimize it for speed.

How do memory accesses usually compare to memory-mapped hardware? It might be better to design the interface around RAM pages that can be hooked by modules rather than modules that act like RAM. I don't know how big switched banks of memory on the Gameboy usually are, but I'd imagine a page size between 256 bytes and 4096 bytes would be good enough. Each of these would be interleaved RAM data and flags indicating extra operations to be performed per byte or per group of bytes. Additionally, each page might have a master flag set indicating if it should be handled as RAM at all or simply passed on to some other module. In this way, bank switching could be accomplished by simply repointing these banks; this could then allow for easy breakpoints based on the unique location of a byte, rather than its address in RAM. RAM address breakpoints could be handled by a large lookup table.

...I'm starting to prefer the simplicity of your method now.

____________________
GuyPerfect
Catgirl
Level: 68


Posts: 715/1096
EXP: 2663610
For next: 65190

Since: 07-23-07


Since last post: 1.6 years
Last activity: 211 days

Posted on 05-13-12 03:00:31 PM Link | Quote
Originally posted by Rena
Ehh, an emulator should emulate the machine itself, not rely on quirks of some software. That kind of thing is why N64 emulation sucks so much today.

All emulators should support an interpreter core for accuracy; I don't mean to suggest otherwise. Sometimes accuracy matters, and failing to implement the specification just because you "don't need to" leads to... well, all those things I was complaining about FCEUX over a few posts ago.

But if you want to, you know, play games, especially on newer systems, interpreters are notorious for being unplayably slow. Look at Dolphin, for instance. Thanks to its recompiler, games play at their native speeds most of the time. And if you switch to the interpreter for accuracy, you get no noticeable improvement in most cases and it becomes too slow to enjoy.

Originally posted by Rena
I'm not sure why you want to trap these events or treat them as unusual? Again, it's an emulator, so it should emulate what the machine does, even if that means the game crashes.

Remember, the chief reason I'm even considering this is for debugging purposes. If my code crashes, I want to know why. A check box in the debugger that says "break on exceptions" would be a fantastic feature to have.

Originally posted by Rena
How do you tell if the stack has overflowed? By SP being set to some particular value, or an uneven number of push/pop instructions? But maybe some game does such things deliberately.

If the stack pointer is 00 and you try to push, it's an overflow. If it's FF and you try to pop, it's an "underflow." I don't know how it is on Game Boy, but the NES's stack is fixed to the $0100-$01FF range and the stack pointer is 8-bit only. If the program does that on purpose for whatever reason, you're under no obligation to enable breaking on exceptions.
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: 4752/5390
EXP: 29053074
For next: 281931

Since: 07-22-07

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

Since last post: 334 days
Last activity: 334 days

Posted on 05-14-12 05:35:45 AM Link | Quote
Post #4752 · 05-14-12 12:35:45 AM
Ahh, then yes, I can see why you want to trap such things.

Joe, you're pretty much describing how the modules themselves work. The memory mappers handle bankswitching by changing a pointer to a block of RAM; the debug module uses a set of flags like you described.

____________________
GuyPerfect
Catgirl
Level: 68


Posts: 716/1096
EXP: 2663610
For next: 65190

Since: 07-23-07


Since last post: 1.6 years
Last activity: 211 days

Posted on 05-14-12 05:06:49 PM Link | Quote
Originally posted by Hironobu Takeshita, in an interview with Gamasutra
[Mega Man 9] couldn't fit on a Famicom cartridge. It's too big. It's too much for that.

The more I researched exactly what the NES could do and how much could fit, my curiosity got the better of me. I played through most of Mega Man 9 today (blew both my Shock Guards at the beginning of the first Dr. Wily level), and I'll be darned if this can't be made to work with Nintendo's original hardware.

Where can I find me an MMC5 board? I'm not much of an electronics engineer, but solder on a flash ROM chip and I really think I can make this a reality.
Joe
Common spammer
🍬
Level: 111


Posts: 2548/3392
EXP: 14489926
For next: 378434

Since: 08-02-07

From: Pororoca

Since last post: 4 days
Last activity: 3 hours

Posted on 05-15-12 05:17:48 AM Link | Quote
Originally posted by GuyPerfect
Where can I find me an MMC5 board?
You can find a MMC5 in any of these games. There are no substitute chips or pre-made development boards: the MMC5 has not been completely reverse-engineered.

____________________
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: 4754/5390
EXP: 29053074
For next: 281931

Since: 07-22-07

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

Since last post: 334 days
Last activity: 334 days

Posted on 05-15-12 06:44:29 AM Link | Quote
Post #4754 · 05-15-12 01:44:29 AM
One thing I've never been sure of is how rewinding works in emulators. Are they just making save states very frequently? Some do it so smoothly, I find it hard to imagine they're saving that often and still running at a decent speed.

I thought about ways to make state saving fast... one thought was that every time you make a save state, you need to make a copy of RAM. That could be eliminated if you made the copy at power-on, then kept it in sync with actual RAM (any write to RAM, also write to the save state's copy). Then you don't need to make a copy when you save the state, because it's already made for you.
But of course then, you need to make a copy anyway for the next save state you want to make. Eh, making a copy of a 32K buffer isn't exactly a tall order anyway..

____________________
GuyPerfect
Catgirl
Level: 68


Posts: 718/1096
EXP: 2663610
For next: 65190

Since: 07-23-07


Since last post: 1.6 years
Last activity: 211 days

Posted on 05-15-12 02:18:09 PM Link | Quote
Whenever I've considered the idea of rewind for games such as Lemmings, the solution I always wind up back at (get it?) is to keep a list of commands not unlike an undo history and just have the engine literally run backwards.

But for an emulator, that's however many megahertz of undo history you'll have to keep track of... Frankly, a temporary archive of once-per-frame save states seems like a better bargain.

No tellin'. Maybe someone came up with a smart way to do it. Do you have an example of an emulator that pulls it off really well?
Darkdata
Ruins!? ♥
Level: 103


Posts: 2635/2892
EXP: 11437233
For next: 34173

Since: 07-04-07


Since last post: 194 days
Last activity: 2 days

Posted on 05-15-12 05:59:52 PM Link | Quote
Originally posted by GuyPerfect
Whenever I've considered the idea of rewind for games such as Lemmings, the solution I always wind up back at (get it?) is to keep a list of commands not unlike an undo history and just have the engine literally run backwards.

But for an emulator, that's however many megahertz of undo history you'll have to keep track of... Frankly, a temporary archive of once-per-frame save states seems like a better bargain.

No tellin'. Maybe someone came up with a smart way to do it. Do you have an example of an emulator that pulls it off really well?


ZSNES does it well. I think.

____________________
Pages: 1 2 3 Next newer thread | Next older thread
Jul - General Game/ROM Hacking - Food For Thought... (NES Emulation) Black and Red 3DChat New poll - New thread - New reply


Rusted Logic

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

28 database queries, 11 query cache hits.
Query execution time:  0.087341 seconds
Script execution time:  0.052029 seconds
Total render time:  0.139370 seconds


TidyHTML vomit below
line 1 column 1 - Warning: missing <!DOCTYPE> declaration
line 119 column 11 - Warning: <form> isn't allowed in <table> elements
line 118 column 10 - Info: <table> previously mentioned
line 120 column 11 - Warning: missing <tr>
line 120 column 119 - Warning: missing </font> before </td>
line 124 column 16 - Warning: plain text isn't allowed in <tr> elements
line 120 column 11 - Info: <tr> previously mentioned
line 125 column 68 - Warning: missing </nobr> before </td>
line 141 column 68 - Warning: missing </nobr> before <tr>
line 147 column 35 - Warning: missing <tr>
line 147 column 50 - Warning: missing </font> before </td>
line 148 column 37 - Warning: unescaped & or unknown entity "&id"
line 147 column 229 - Warning: missing </font> before </table>
line 149 column 35 - Warning: missing <tr>
line 149 column 97 - Warning: unescaped & or unknown entity "&page"
line 149 column 130 - Warning: unescaped & or unknown entity "&page"
line 149 column 50 - Warning: missing </font> before </td>
line 149 column 165 - Warning: missing </font> before </table>
line 156 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 158 column 9 - Warning: missing <tr>
line 176 column 13 - Warning: missing <tr>
line 177 column 102 - Warning: unescaped & or unknown entity "&postid"
line 187 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 189 column 9 - Warning: missing <tr>
line 207 column 13 - Warning: missing <tr>
line 208 column 102 - Warning: unescaped & or unknown entity "&postid"
line 216 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 218 column 9 - Warning: missing <tr>
line 236 column 13 - Warning: missing <tr>
line 237 column 102 - Warning: unescaped & or unknown entity "&postid"
line 251 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 253 column 9 - Warning: missing <tr>
line 271 column 13 - Warning: missing <tr>
line 272 column 102 - Warning: unescaped & or unknown entity "&postid"
line 274 column 74 - Warning: <style> isn't allowed in <td> elements
line 274 column 9 - Info: <td> previously mentioned
line 279 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 281 column 9 - Warning: missing <tr>
line 299 column 13 - Warning: missing <tr>
line 300 column 102 - Warning: unescaped & or unknown entity "&postid"
line 311 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 313 column 9 - Warning: missing <tr>
line 331 column 13 - Warning: missing <tr>
line 332 column 102 - Warning: unescaped & or unknown entity "&postid"
line 342 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 344 column 9 - Warning: missing <tr>
line 362 column 13 - Warning: missing <tr>
line 363 column 102 - Warning: unescaped & or unknown entity "&postid"
line 374 column 7072 - Warning: replacing unexpected input with </input>
line 374 column 7386 - Warning: discarding unexpected </span>
line 377 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 379 column 9 - Warning: missing <tr>
line 397 column 13 - Warning: missing <tr>
line 398 column 102 - Warning: unescaped & or unknown entity "&postid"
line 411 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 413 column 9 - Warning: missing <tr>
line 431 column 13 - Warning: missing <tr>
line 432 column 102 - Warning: unescaped & or unknown entity "&postid"
line 452 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 454 column 9 - Warning: missing <tr>
line 472 column 13 - Warning: missing <tr>
line 473 column 102 - Warning: unescaped & or unknown entity "&postid"
line 475 column 546 - Warning: missing </span> before <blockquote>
line 475 column 626 - Warning: inserting implicit <span>
line 475 column 626 - Warning: missing </span> before <hr>
line 475 column 701 - Warning: inserting implicit <span>
line 476 column 1 - Warning: inserting implicit <span>
line 483 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 485 column 9 - Warning: missing <tr>
line 503 column 13 - Warning: missing <tr>
line 504 column 102 - Warning: unescaped & or unknown entity "&postid"
line 506 column 74 - Warning: <style> isn't allowed in <td> elements
line 506 column 9 - Info: <td> previously mentioned
line 511 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 513 column 9 - Warning: missing <tr>
line 531 column 13 - Warning: missing <tr>
line 532 column 102 - Warning: unescaped & or unknown entity "&postid"
line 555 column 8196 - Warning: unescaped & or unknown entity "&feature"
line 557 column 9069 - Warning: replacing unexpected input with </input>
line 557 column 9383 - Warning: discarding unexpected </span>
line 560 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 562 column 9 - Warning: missing <tr>
line 580 column 13 - Warning: missing <tr>
line 581 column 102 - Warning: unescaped & or unknown entity "&postid"
line 583 column 74 - Warning: <style> isn't allowed in <td> elements
line 583 column 9 - Info: <td> previously mentioned
line 590 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 592 column 9 - Warning: missing <tr>
line 610 column 13 - Warning: missing <tr>
line 611 column 102 - Warning: unescaped & or unknown entity "&postid"
line 625 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 627 column 9 - Warning: missing <tr>
line 645 column 13 - Warning: missing <tr>
line 646 column 102 - Warning: unescaped & or unknown entity "&postid"
line 650 column 4719 - Warning: replacing unexpected input with </input>
line 650 column 5033 - Warning: discarding unexpected </span>
line 653 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 655 column 9 - Warning: missing <tr>
line 673 column 13 - Warning: missing <tr>
line 674 column 102 - Warning: unescaped & or unknown entity "&postid"
line 682 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 684 column 9 - Warning: missing <tr>
line 702 column 13 - Warning: missing <tr>
line 703 column 102 - Warning: unescaped & or unknown entity "&postid"
line 705 column 74 - Warning: <style> isn't allowed in <td> elements
line 705 column 9 - Info: <td> previously mentioned
line 705 column 1002 - Warning: unescaped & or unknown entity "&hardware"
line 705 column 1022 - Warning: unescaped & or unknown entity "&hardware"
line 705 column 1043 - Warning: unescaped & or unknown entity "&field"
line 705 column 1051 - Warning: unescaped & or unknown entity "&order"
line 705 column 1061 - Warning: unescaped & or unknown entity "&rfa"
line 708 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 710 column 9 - Warning: missing <tr>
line 728 column 13 - Warning: missing <tr>
line 729 column 102 - Warning: unescaped & or unknown entity "&postid"
line 734 column 5299 - Warning: replacing unexpected input with </input>
line 734 column 5613 - Warning: discarding unexpected </span>
line 737 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 739 column 9 - Warning: missing <tr>
line 757 column 13 - Warning: missing <tr>
line 758 column 102 - Warning: unescaped & or unknown entity "&postid"
line 767 column 9 - Warning: <div> isn't allowed in <table> elements
line 152 column 17 - Info: <table> previously mentioned
line 769 column 9 - Warning: missing <tr>
line 787 column 13 - Warning: missing <tr>
line 788 column 102 - Warning: unescaped & or unknown entity "&postid"
line 790 column 74 - Warning: <style> isn't allowed in <td> elements
line 790 column 9 - Info: <td> previously mentioned
line 799 column 17 - Warning: missing <tr>
line 799 column 17 - Warning: discarding unexpected <table>
line 802 column 35 - Warning: missing <tr>
line 802 column 97 - Warning: unescaped & or unknown entity "&page"
line 802 column 130 - Warning: unescaped & or unknown entity "&page"
line 802 column 50 - Warning: missing </font> before </td>
line 802 column 165 - Warning: missing </font> before </table>
line 804 column 35 - Warning: missing <tr>
line 804 column 50 - Warning: missing </font> before </td>
line 805 column 37 - Warning: unescaped & or unknown entity "&id"
line 804 column 229 - Warning: missing </font> before </table>
line 806 column 17 - Warning: discarding unexpected </textarea>
line 806 column 28 - Warning: discarding unexpected </form>
line 806 column 35 - Warning: discarding unexpected </embed>
line 806 column 43 - Warning: discarding unexpected </noembed>
line 806 column 53 - Warning: discarding unexpected </noscript>
line 806 column 64 - Warning: discarding unexpected </noembed>
line 806 column 74 - Warning: discarding unexpected </embed>
line 806 column 82 - Warning: discarding unexpected </table>
line 806 column 90 - Warning: discarding unexpected </table>
line 808 column 9 - Warning: missing </font> before <table>
line 820 column 25 - Warning: discarding unexpected </font>
line 829 column 58 - Warning: discarding unexpected </font>
line 807 column 1 - Warning: missing </center>
line 120 column 63 - Warning: <img> lacks "alt" attribute
line 125 column 19 - Warning: <td> attribute "width" has invalid value "120px"
line 125 column 93 - Warning: <img> lacks "alt" attribute
line 141 column 19 - Warning: <td> attribute "width" has invalid value "120px"
line 141 column 98 - Warning: <img> lacks "alt" attribute
line 148 column 44 - Warning: <img> proprietary attribute value "absmiddle"
line 148 column 142 - Warning: <img> proprietary attribute value "absmiddle"
line 148 column 246 - Warning: <img> proprietary attribute value "absmiddle"
line 161 column 22 - Warning: <img> lacks "alt" attribute
line 161 column 63 - Warning: <img> lacks "alt" attribute
line 161 column 112 - Warning: <img> lacks "alt" attribute
line 161 column 162 - Warning: <img> lacks "alt" attribute
line 172 column 15 - Warning: <img> lacks "alt" attribute
line 176 column 90 - Warning: <font> attribute "color" had invalid value "7C60B0" and has been replaced
line 192 column 22 - Warning: <img> lacks "alt" attribute
line 192 column 63 - Warning: <img> lacks "alt" attribute
line 192 column 112 - Warning: <img> lacks "alt" attribute
line 192 column 162 - Warning: <img> lacks "alt" attribute
line 193 column 11 - Warning: <img> lacks "alt" attribute
line 203 column 15 - Warning: <img> lacks "alt" attribute
line 221 column 22 - Warning: <img> lacks "alt" attribute
line 221 column 63 - Warning: <img> lacks "alt" attribute
line 221 column 112 - Warning: <img> lacks "alt" attribute
line 221 column 162 - Warning: <img> lacks "alt" attribute
line 232 column 15 - Warning: <img> lacks "alt" attribute
line 256 column 23 - Warning: <img> lacks "alt" attribute
line 256 column 64 - Warning: <img> lacks "alt" attribute
line 256 column 113 - Warning: <img> lacks "alt" attribute
line 256 column 163 - Warning: <img> lacks "alt" attribute
line 257 column 11 - Warning: <img> lacks "alt" attribute
line 267 column 15 - Warning: <img> lacks "alt" attribute
line 274 column 905 - Warning: <img> proprietary attribute value "absmiddle"
line 274 column 905 - Warning: <img> lacks "alt" attribute
line 276 column 1092 - Warning: <img> proprietary attribute value "absmiddle"
line 276 column 1092 - Warning: <img> lacks "alt" attribute
line 284 column 22 - Warning: <img> lacks "alt" attribute
line 284 column 63 - Warning: <img> lacks "alt" attribute
line 284 column 112 - Warning: <img> lacks "alt" attribute
line 284 column 162 - Warning: <img> lacks "alt" attribute
line 295 column 15 - Warning: <img> lacks "alt" attribute
line 316 column 22 - Warning: <img> lacks "alt" attribute
line 316 column 63 - Warning: <img> lacks "alt" attribute
line 316 column 112 - Warning: <img> lacks "alt" attribute
line 316 column 162 - Warning: <img> lacks "alt" attribute
line 327 column 15 - Warning: <img> lacks "alt" attribute
line 331 column 90 - Warning: <font> attribute "color" had invalid value "7C60B0" and has been replaced
line 347 column 23 - Warning: <img> lacks "alt" attribute
line 347 column 64 - Warning: <img> lacks "alt" attribute
line 347 column 113 - Warning: <img> lacks "alt" attribute
line 347 column 163 - Warning: <img> lacks "alt" attribute
line 358 column 15 - Warning: <img> lacks "alt" attribute
line 367 column 4810 - Warning: <img> proprietary attribute value "absmiddle"
line 367 column 4810 - Warning: <img> lacks "alt" attribute
line 382 column 22 - Warning: <img> lacks "alt" attribute
line 382 column 63 - Warning: <img> lacks "alt" attribute
line 382 column 112 - Warning: <img> lacks "alt" attribute
line 382 column 162 - Warning: <img> lacks "alt" attribute
line 393 column 15 - Warning: <img> lacks "alt" attribute
line 400 column 180 - Warning: <img> proprietary attribute value "absmiddle"
line 400 column 180 - Warning: <img> lacks "alt" attribute
line 416 column 22 - Warning: <img> lacks "alt" attribute
line 416 column 63 - Warning: <img> lacks "alt" attribute
line 416 column 112 - Warning: <img> lacks "alt" attribute
line 416 column 162 - Warning: <img> lacks "alt" attribute
line 427 column 15 - Warning: <img> lacks "alt" attribute
line 431 column 90 - Warning: <font> attribute "color" had invalid value "7C60B0" and has been replaced
line 446 column 2904 - Warning: <img> proprietary attribute value "absmiddle"
line 446 column 2904 - Warning: <img> lacks "alt" attribute
line 456 column 11 - Warning: <img> lacks "alt" attribute
line 457 column 23 - Warning: <img> lacks "alt" attribute
line 457 column 64 - Warning: <img> lacks "alt" attribute
line 457 column 113 - Warning: <img> lacks "alt" attribute
line 457 column 163 - Warning: <img> lacks "alt" attribute
line 458 column 11 - Warning: <img> lacks "alt" attribute
line 468 column 15 - Warning: <img> lacks "alt" attribute
line 472 column 91 - Warning: <font> attribute "color" had invalid value "7C60B0" and has been replaced
line 488 column 23 - Warning: <img> lacks "alt" attribute
line 488 column 64 - Warning: <img> lacks "alt" attribute
line 488 column 113 - Warning: <img> lacks "alt" attribute
line 488 column 163 - Warning: <img> lacks "alt" attribute
line 489 column 11 - Warning: <img> lacks "alt" attribute
line 499 column 15 - Warning: <img> lacks "alt" attribute
line 516 column 23 - Warning: <img> lacks "alt" attribute
line 516 column 64 - Warning: <img> lacks "alt" attribute
line 516 column 113 - Warning: <img> lacks "alt" attribute
line 516 column 163 - Warning: <img> lacks "alt" attribute
line 527 column 15 - Warning: <img> lacks "alt" attribute
line 531 column 90 - Warning: <font> attribute "color" had invalid value "77ECFF" and has been replaced
line 565 column 23 - Warning: <img> lacks "alt" attribute
line 565 column 64 - Warning: <img> lacks "alt" attribute
line 565 column 113 - Warning: <img> lacks "alt" attribute
line 565 column 163 - Warning: <img> lacks "alt" attribute
line 566 column 11 - Warning: <img> lacks "alt" attribute
line 576 column 15 - Warning: <img> lacks "alt" attribute
line 587 column 2556 - Warning: <img> proprietary attribute value "absmiddle"
line 587 column 2556 - Warning: <img> lacks "alt" attribute
line 595 column 22 - Warning: <img> lacks "alt" attribute
line 595 column 63 - Warning: <img> lacks "alt" attribute
line 595 column 112 - Warning: <img> lacks "alt" attribute
line 595 column 162 - Warning: <img> lacks "alt" attribute
line 606 column 15 - Warning: <img> lacks "alt" attribute
line 614 column 606 - Warning: <img> proprietary attribute value "absmiddle"
line 614 column 606 - Warning: <img> lacks "alt" attribute
line 630 column 23 - Warning: <img> lacks "alt" attribute
line 630 column 64 - Warning: <img> lacks "alt" attribute
line 630 column 113 - Warning: <img> lacks "alt" attribute
line 630 column 163 - Warning: <img> lacks "alt" attribute
line 641 column 15 - Warning: <img> lacks "alt" attribute
line 648 column 4382 - Warning: <img> proprietary attribute value "absmiddle"
line 648 column 4382 - Warning: <img> lacks "alt" attribute
line 658 column 22 - Warning: <img> lacks "alt" attribute
line 658 column 63 - Warning: <img> lacks "alt" attribute
line 658 column 112 - Warning: <img> lacks "alt" attribute
line 658 column 162 - Warning: <img> lacks "alt" attribute
line 669 column 15 - Warning: <img> lacks "alt" attribute
line 687 column 23 - Warning: <img> lacks "alt" attribute
line 687 column 64 - Warning: <img> lacks "alt" attribute
line 687 column 113 - Warning: <img> lacks "alt" attribute
line 687 column 163 - Warning: <img> lacks "alt" attribute
line 688 column 11 - Warning: <img> lacks "alt" attribute
line 698 column 15 - Warning: <img> lacks "alt" attribute
line 713 column 23 - Warning: <img> lacks "alt" attribute
line 713 column 64 - Warning: <img> lacks "alt" attribute
line 713 column 113 - Warning: <img> lacks "alt" attribute
line 713 column 163 - Warning: <img> lacks "alt" attribute
line 724 column 15 - Warning: <img> lacks "alt" attribute
line 731 column 4557 - Warning: <img> proprietary attribute value "absmiddle"
line 731 column 4557 - Warning: <img> lacks "alt" attribute
line 734 column 5105 - Warning: <img> proprietary attribute value "absmiddle"
line 734 column 5105 - Warning: <img> lacks "alt" attribute
line 742 column 22 - Warning: <img> lacks "alt" attribute
line 742 column 63 - Warning: <img> lacks "alt" attribute
line 742 column 112 - Warning: <img> lacks "alt" attribute
line 742 column 162 - Warning: <img> lacks "alt" attribute
line 753 column 15 - Warning: <img> lacks "alt" attribute
line 771 column 11 - Warning: <font> attribute "color" has invalid value "orange"
line 772 column 23 - Warning: <img> lacks "alt" attribute
line 772 column 64 - Warning: <img> lacks "alt" attribute
line 772 column 113 - Warning: <img> lacks "alt" attribute
line 772 column 162 - Warning: <img> lacks "alt" attribute
line 773 column 11 - Warning: <img> lacks "alt" attribute
line 783 column 15 - Warning: <img> lacks "alt" attribute
line 805 column 44 - Warning: <img> proprietary attribute value "absmiddle"
line 805 column 142 - Warning: <img> proprietary attribute value "absmiddle"
line 805 column 246 - Warning: <img> proprietary attribute value "absmiddle"
line 814 column 25 - Warning: <img> lacks "alt" attribute
line 819 column 267 - Warning: <img> lacks "alt" attribute
line 374 column 7139 - Warning: trimming empty <label>
line 475 column 546 - Warning: trimming empty <span>
line 557 column 9136 - Warning: trimming empty <label>
line 650 column 4786 - Warning: trimming empty <label>
line 734 column 5366 - Warning: trimming empty <label>
line 799 column 17 - Warning: trimming empty <tr>
line 125 column 68 - Warning: <nobr> is not approved by W3C
line 141 column 68 - Warning: <nobr> is not approved by W3C
line 177 column 27 - Warning: <nobr> is not approved by W3C
line 208 column 27 - Warning: <nobr> is not approved by W3C
line 237 column 27 - Warning: <nobr> is not approved by W3C
line 272 column 27 - Warning: <nobr> is not approved by W3C
line 300 column 27 - Warning: <nobr> is not approved by W3C
line 332 column 27 - Warning: <nobr> is not approved by W3C
line 363 column 27 - Warning: <nobr> is not approved by W3C
line 398 column 27 - Warning: <nobr> is not approved by W3C
line 432 column 27 - Warning: <nobr> is not approved by W3C
line 473 column 27 - Warning: <nobr> is not approved by W3C
line 504 column 27 - Warning: <nobr> is not approved by W3C
line 532 column 27 - Warning: <nobr> is not approved by W3C
line 581 column 27 - Warning: <nobr> is not approved by W3C
line 611 column 27 - Warning: <nobr> is not approved by W3C
line 646 column 27 - Warning: <nobr> is not approved by W3C
line 674 column 27 - Warning: <nobr> is not approved by W3C
line 703 column 27 - Warning: <nobr> is not approved by W3C
line 729 column 27 - Warning: <nobr> is not approved by W3C
line 758 column 27 - Warning: <nobr> is not approved by W3C
line 788 column 27 - Warning: <nobr> is not approved by W3C
Info: Document content looks like HTML5
Info: No system identifier in emitted doctype
Tidy found 319 warnings and 0 errors!


The alt attribute should be used to give a short description
of an image; longer descriptions should be given with the
longdesc attribute which takes a URL linked to the description.
These measures are needed for people using non-graphical browsers.

For further advice on how to make your pages accessible
see http://www.w3.org/WAI/GL.
You are recommended to use CSS to specify the font and
properties such as its size and color. This will reduce
the size of HTML files and make them easier to maintain
compared with using <FONT> elements.

You are recommended to use CSS to control line wrapping.
Use "white-space: nowrap" to inhibit wrapping in place
of inserting <NOBR>...</NOBR> into the markup.

About HTML Tidy: https://github.com/htacg/tidy-html5
Bug reports and comments: https://github.com/htacg/tidy-html5/issues
Official mailing list: https://lists.w3.org/Archives/Public/public-htacg/
Latest HTML specification: http://dev.w3.org/html5/spec-author-view/
Validate your HTML documents: http://validator.w3.org/nu/
Lobby your company to join the W3C: http://www.w3.org/Consortium

Do you speak a language other than English, or a different variant of
English? Consider helping us to localize HTML Tidy. For details please see
https://github.com/htacg/tidy-html5/blob/master/README/LOCALIZE.md