It is currently Sat Sep 21, 2019 3:51 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Thu Apr 04, 2019 6:52 am 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1524
I'm having one heck of a time trying to emulate this thing.

How in the world does the disk drive transfer bytes to and from the RAM adapter?

Starting with the .fds format, we'll first lop off the optional (not in No-Intro) 16-byte fwNES header, for a 65500 byte file.

We get numbered, ... blocks? 01 {info}, 02 {file count}, 03 {file header}, 04 {file data}, 03 {file header}, 04 {file data}, ...

The wiki further says that the actual raw disks have large gaps between blocks. ~28300 bits on the first block, then ~976 bytes on the rest. All gap bytes are 0x0, and each gap ends with a 0x80 byte. Yet when I look at Hubz' .bin FDS dumps, I see 0x709 bytes of pre-gap before the first track, which is half of the minimum bits before the first track per NESdev wiki.

Further, the wiki says that even WITH the gaps, the actual capacity is 65500 bytes. Yet Hubz' .bin dumps are larger than 65500 bytes. Usually 67000 bytes. It varies per game.

The .raw dumps are over 400KiB and kind of terrify me. I think that's too low-level for me right now ...

I am told it takes ~6 seconds for the motor to scan across the entire disk side. So at 21477272hz crystal clock, that's ~1967 clock cycles per byte read.

The BIOS starts up and starts writing to $4025:
#$2e
#$2e
#$2f
#$2d
#$2e
#$2f
#$2d
#$6d
#$ed
At this point, it expects an interrupt to fire on each byte read, and if it doesn't start reading the first track (01 2a 4e 49 ...), it will return disk error 22.

So from this, I gather than $4025.d0 must be set to transfer data, and that $4025.d6 must also be set. It doesn't actually put a read byte into $4031 and set the byte transferred flag in $4030 unless it's a real byte and not a gap byte (0x00 or 0x80).

The BIOS then happily continues reading along data, accepting IRQs, until reading one past the end of the first block, eg it reads (01 2a ... ff ff ff ff ff 00 00 00 00 02). After that it writes #$fd to $4205, clearing the CRC counter. And now it writes #$2d, #$6d, #$ed back to $4205. And now it expects the next IRQ transfer byte to be #$02 that it already read before, otherwise you get disk error 23.

My thinking is ... we have to emulate the gaps. The last byte of track 1 should be a gap byte, and get discarded. Yet it somehow must fire an interrupt as a transferred byte to stop the BIOS from waiting on the next valid byte, #$02 ...

So as a cheap hack, I made it so writing $4205.d7=1 will subtract the .fds disk data by 1 if it's not zero. Then we get this:
$4205 = fd
read 02
$4205 = 2d, 6d, ed
read 02
read 07
read 03
<okay, it read block two to get the total number of files>
$4205 = fd
read 03
$4205 = 2d, 6d, ed
read 03, 00, 00, 4b, 59, 4f, 44, 41, 4b, 55, 2d
<reads the file name and then stops>
$4205 = ed
reads 2d
$4205 = ed
reads 2d
$4205 = ed
reads 2d, 00, 28
$4205 = fd
reads 28
$4205 = 2d, 6d, ed
reads 28 <it's expecting 04 here most likely>

It resets $4205.d0 (restarting the drive motor), re-reads the disk error, and then now I get disk error 25.

I'm at a loss. Does anyone emulate the FDS at a lower level than HLE BIOS hooks?

Does the motor actually pause and stop reading more disk data when $4205.d6 is clear?

If you turn the motor off and on again, does this reset the motor head to the outer edge (start) of the disk, or does it still have to wait for the entire disk to be scanned before starting over again?

Once the entire disk is scanned, does it disable the motor at that point?

Are the checksums relevant in any way? I see Mesen hooks bus address reads in the BIOS to simulate things via HLE, and performs CRC calculations in there ...

Anything else I should know, any insights anyone could share? :/


Top
 Profile  
 
PostPosted: Thu Apr 04, 2019 9:01 am 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 976
byuu wrote:
Further, the wiki says that even WITH the gaps, the actual capacity is 65500 bytes. Yet Hubz' .bin dumps are larger than 65500 bytes. Usually 67000 bytes. It varies per game.
The 65500 byte limit seems to have been entirely made up by Fan Wen Yang, the author of the original .FDS file format, and seems to be unrelated to actual hardware limitations. There are unlicensed disks (for Bung/Venus Game Doctor and Front Fareast Magicard RAM cartridges) that hold more than 65500 bytes of actual data.
byuu wrote:
I'm at a loss. Does anyone emulate the FDS at a lower level than HLE BIOS hooks?
I don't think anybody emulates the FDS entirely using HLE BIOS hooks. All emulators that I have seen do try to emulate the FDS hardware registers, but usually only barely enough to work with BIOS and gapless/CRC-less images.

In NintendulatorNRS, which is my fork of Nintendulator, when loading a gap- and CRC-less FDS file, I internally convert it to one that has gap and CRC fields, which allows for more low-level emulation. It can also use .RAW format files, though I decode them to a more usable format (with gaps and CRC fields) when loading.
byuu wrote:
If you turn the motor off and on again, does this reset the motor head to the outer edge (start) of the disk, or does it still have to wait for the entire disk to be scanned before starting over again?
If I recall correctly, the actual motor status only changes at the outer edge of the disk, so whatever you do with the bits, the motor cannot be turned off and on again mid-disk.
byuu wrote:
Anything else I should know, any insights anyone could share? :/
Tonkachi Editor does not use BIOS to read disks, and as a result of its odd method of skipping CRC bytes --- basically just spending time in a delay loop until the CRC field is past the magnetic head ---, does not read disks properly in any other emulator that I have tried.

Another interesting problem is switch bounce. Basically, when inserting a disk into the FDS disk drive, the "disk inserted" bit does not cleanly change from one state to the other, but jumps around for a short time. Not emulating that causes the games Aspic (from Bothtec) and Titanic Mystery (from Activision/Gakken) to unreliably recognize disk changes.


Last edited by NewRisingSun on Thu Apr 04, 2019 10:41 am, edited 2 times in total.

Top
 Profile  
 
PostPosted: Thu Apr 04, 2019 10:03 am 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1524
Quote:
The 65500 byte limit seems to have been entirely made up by Fan Wen Yang


Oh boy. The wiki needs some serious updates, then. I'll also add:

$4033.d7:
Quote:
+--------- Battery status (0: Good; 1: Voltage is low).


This is backward. 0 = low voltage, 1 = good.

Quote:
All emulators that I have seen do try to emulate the FDS hardware registers, but usually only barely enough to work with BIOS and gapless/CRC-less images.


Yeah that's not gonna work for me ^-^

Okay then ... I managed to get some games loading. It doesn't work well, though. Metroid gives error 24 when starting a save game, and I'm sure the details are wrong.

Code dump below. But so far:

The BIOS definitely reads past the end of each track, grabbing several 0x00 bytes. If the image is packed without gaps, then the extra reads will end up going past the next block start.

$4025.d0 seems to be a master enable for the entire drive motor and has to always be on to work.
$4025.d1 I am not sure on ... how does it differ in functionality from just using $4025.d0?
$4025.d6 being clear seems to stop the motor from reading more bytes off of the disk? I would have thought the head would keep moving, but I guess not. Maybe $4025.d1 is needed to to reset the head position back to the start without powering off the motor. In that case, I wonder if it can just seek the head backwards to the start, or if it has to finish a full 6-second scan of the entire disk before it'll return.
There's probably an extra delay required for the head to move from the end (inner) part of the disk back to the start (outer) part, but since it's not specified, I don't emulate that.

The big mystery right now is how the drive knows we want to stop sending IRQs for bytes until we get to the next block. Eg telling the controller, "hey we're now in a gap section." I would have thought that'd be $4025.d4, but apparently it's not. The only way I could get my emulator to load all the tracks correctly was to start a new gap skipping section after writing #$6d to $4025. I know that's definitely wrong, though.

I am rebuilding the 65500-byte track sides using the relative gaps I saw in Hubz' images, which are shorter than NESdev wiki says is possible. But it works fine. I've heard that some games use copy protection by storing data after the last file ... I presume that works by having a long stream of 0x00s, then a 0x80 byte, then the actual copy protection check data. I wonder how that works in existing .fds dumps, though.

I didn't emulate writes at all yet, but I hear that they act kind of wonky and tend to not save in the same spot on disk each time. Terrifying.

The last note I have is that the mirroring bit seems to be reversed from how it operates in other mappers. Which is most likely because I chose poorly in my conversion of iNES to manifests back to mirroring flag bits. A mirroring value of 1 transforms the address as : (address&0x800)>>1|address&0x3ff. That's needed to write the Nintendo copyright text into CIRAM properly, otherwise you get a disk error 20.

Code dump:

Code:
  struct Timer {
    uint16 counter;
    uint16 period;
     uint1 repeat;
     uint1 irq;
     uint1 triggered;
  } timer;

  struct Disk {
     uint1 reset;
     uint1 enable;
     uint1 mode;  //0 = write, 1 = read
     uint1 scan;
     uint1 irq;
    uint16 counter;
    uint32 offset;
     uint1 gap;
     uint8 data;
     uint1 transferred;
     uint1 completed;
  } disk;

  struct IO {
    uint1 enableDisk;
    uint1 enableAudio;
    uint1 enableMotor;
    uint1 mirroring;
  } io;


Code:
auto FDS::main() -> void {
  if(timer.irq && !--timer.counter) {
    if(timer.repeat) timer.counter = timer.period;
    timer.irq = timer.repeat;
    timer.triggered = 1;
    cpu.irqLine(1);
  }

  if(!disk.reset && disk.enable && disk.scan && !--disk.counter) {
    if(disk.offset == sideA.size()) {
      disk.enable = 0;
      disk.completed = 1;
    } else {
      disk.counter = 2000;
      disk.data = sideA[disk.offset++];
      if(disk.gap) {
        disk.gap = !disk.data.bit(7);
      } else {
        disk.transferred = 1;
        if(disk.irq) cpu.irqLine(1);
      }
    }
  }

  step(1);
}


Code:
auto FDS::rebuild(Memory::Writable<uint8>& memory) -> void {
  if(memory.size() != 65500) return;
  if(memory[0x00] != 0x01) return;
  if(memory[0x38] != 0x02) return;
  if(memory[0x3a] != 0x03) return;
  if(memory[0x4a] != 0x04) return;

  vector<uint8> data;
  uint offset = 0;

  //pregap
  for(uint n : range(0x0708)) data.append(0x00);
  data.append(0x80);

  //block 1
  for(uint n : range(0x38)) data.append(memory[offset++]);

  //gap
  for(uint n : range(0x80)) data.append(0x00);
  data.append(0x80);

  //block 2
  for(uint n : range(0x02)) data.append(memory[offset++]);

  for(uint file : range(memory[0x39])) {
    //gap
    for(uint n : range(0x80)) data.append(0x00);
    data.append(0x80);

    //block 3
    uint16 size;
    size.byte(0) = memory[offset + 0x0d];
    size.byte(1) = memory[offset + 0x0e];

    for(uint n : range(0x10)) data.append(memory[offset++]);

    //gap
    for(uint n : range(0x80)) data.append(0x00);
    data.append(0x80);

    //block 4
    for(uint n : range(1 + size)) data.append(memory[offset++]);
  }

  while(data.size() < 65500) data.append(0x00);

  memory.reset();
  memory.allocate(data.size());
  for(uint index : range(data.size())) memory[index] = data[index];
}


Code:
auto FDS::read(uint16 address) -> uint8 {
  uint8 data = 0x00;

  switch(address) {

  case 0x4030:
    data.bit(0) = timer.triggered;
    data.bit(1) = disk.transferred;
    data.bit(6) = disk.completed;
    timer.triggered = 0;
    disk.transferred = 0;
    cpu.irqLine(0);
    return data;

  case 0x4031:
    data = disk.data;
    disk.transferred = 0;
    cpu.irqLine(0);
    return data;

  case 0x4032:
    data.bit(0) = !(bool)sideA;  //0 = disk inserted, 1 = disk not inserted
    data.bit(1) = !(bool)sideA;  //0 = disk ready, 1 = disk not ready
    data.bit(2) = 0;             //0 = not write protected, 1 = write protected
    if(disk.reset) data.bit(1) = 1;
    return data;

  case 0x4033:
    data.bit(7) = 1;  //0 = battery voltage low, 1 = battery good
    return data;

  }

  return data;
}

auto FDS::write(uint16 address, uint8 data) -> void {
  switch(address) {

  case 0x4020:
    timer.period.byte(0) = data;
    return;

  case 0x4021:
    timer.period.byte(1) = data;
    return;

  case 0x4022:
    if(!io.enableDisk) return;
    timer.repeat = data.bit(0);
    timer.irq = data.bit(1);
    if(timer.irq) {
      timer.counter = timer.period;
    } else {
      timer.triggered = 0;
      cpu.irqLine(0);
    }
    return;

  case 0x4023:
    io.enableDisk  = data.bit(0);
    io.enableAudio = data.bit(1);
    if(!io.enableDisk) {
      timer.triggered = 0;
      cpu.irqLine(0);
    }
    return;

  case 0x4024:
    disk.data = data;
    disk.transferred = 0;
    cpu.irqLine(0);
    return;

  case 0x4025:
  //print("*", hex(data,2), " ", binary(data,8), "\n");
    if(!data.bit(0) || !disk.enable || data.bit(1)) {
      disk.counter = 2000;
      disk.offset = 0;
      disk.gap = 1;
      disk.transferred = 0;
      disk.completed = 0;
    }
    disk.enable   = data.bit(0);
    disk.reset    = data.bit(1);
    disk.mode     = data.bit(2);
    io.mirroring  = data.bit(3);
    disk.gap     |= data == 0x6d;
    disk.scan     = data.bit(6);
    disk.irq      = data.bit(7);
    cpu.irqLine(0);
    return;

  }
}


Last edited by byuu on Thu Apr 04, 2019 10:09 am, edited 1 time in total.

Top
 Profile  
 
PostPosted: Thu Apr 04, 2019 10:09 am 
Offline

Joined: Sun Feb 07, 2016 6:16 pm
Posts: 725
It's been a very long time since I implemented FDS, so I don't recall most of the details when it comes to emulating the drive itself, but it doesn't use any HLE. The hooks you might have seen are probably the ones used to auto-switch disks instead of making the user pick them or to detect whether or not the game is loading and needs to be fast forwarded to save time, etc.

The FDS file is loaded, gaps are added to it and the result is loaded through LLE (I think it also calculates the CRC while loading from the disk, but stops short of actually setting the CRC validity flag to false).

But yea, I recall struggling with FDS drive emulation too, I only came up with what I have after looking at how other emulators did it. In particular I recall bisqwit had a thread here with information on how he did it, but it seems like the partial code snippets he had posted are dead links now: viewtopic.php?f=3&t=8712

Edit: Just realized that thread has a post by bisqwit at the end with his pseudo-code for emulating the drive, which may be helpful?


Top
 Profile  
 
PostPosted: Thu Apr 04, 2019 10:24 am 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 976
byuu wrote:
I didn't emulate writes at all yet, but I hear that they act kind of wonky and tend to not save in the same spot on disk each time. Terrifying.
That is the case with any floppy disk controller, including the IBM PC's, that does not write an entire track at a time (such as the Commodore Amiga's).
byuu wrote:
The BIOS definitely reads past the end of each track
I was confused by this statement until I realized that you use "track" to mean "file". The Famicom Disk System/Mitsumi Quickdisk only has a single spiral track.
byuu wrote:
The last note I have is that the mirroring bit seems to be reversed from how it operates in other mappers.
$4025's mirroring bit is exactly in the same direction as on the Nintendo MMC3 and most mappers, with 0 meaning vertical mirroring and 1 meaning horizontal mirroring. Pretty much only the iNES header operates the other way.


Top
 Profile  
 
PostPosted: Thu Apr 04, 2019 10:26 am 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1524
Lots of good info in Bisqwit's thread, thank you! Didn't realize CRC information was in there on the .bin files as well, thought that was only part of the .raw dumps. Good to know.

Sorry for assuming Mesen was using HLE. That's pretty darn cool that you do low level emulation *and* allow user conveniences like auto flipping disks. It's gonna be a challenge for my GUI how to select disks/sides for sure.

Quote:
That is the case with any floppy disk controller, including the IBM PC's, that does not write an entire track at a time (such as the Commodore Amiga's).


Scary. I'm sure I will have questions when I get to disk writing, hahah.

Quote:
I was confused by this statement until I realized that you use "track" to mean "file". The Famicom Disk System/Mitsumi Quickdisk only has a single spiral track.


Whoops, sorry, yeah. Still don't have a good name for it since only the 04 blocks are technically files. But I meant to say block there, yeah.


Top
 Profile  
 
PostPosted: Thu Apr 04, 2019 10:29 am 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 976
byuu wrote:
Scary. I'm sure I will have questions when I get to disk writing, hahah.
The nice thing about the write splices that this creates is that one can use their presence to detect whether a disk was modified or had something saved to it by the user. Because factory mass disk duplicators write entire tracks, "untouched" disks will have no write splices.


Top
 Profile  
 
PostPosted: Thu Apr 04, 2019 10:42 am 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 976
byuu wrote:
I've heard that some games use copy protection by storing data after the last file ... I presume that works by having a long stream of 0x00s, then a 0x80 byte, then the actual copy protection check data. I wonder how that works in existing .fds dumps, though.
The copy-protected file is just another pair of blocks 3 and 4. Such a file can be created by writing a whole disk normally, then decreasing the block count in block type 2 by one. When parsing such a disk to create gaps and CRC fields, assume that the block count in block type 2 is not valid and just keep parsing blocks as long as valid blocks are still ahead. The best game to check correct emulation of this copy protection method is Yume Kojo: Doki Doki Panic, which will display a "PLEASE USE OFFICIAL DISK WRITER SHOP" message if the hidden file is missing.

Another means of copy protection is used by several games from Irem, namely Kinetic Connection 1+2 and Super Lode Runner 1+2. The main game code is located in a file that is larger than the 32 KiB of PRG-RAM that the FDS RAM Adapter has. Copy utilities of the time apparently could not handle that and would cut off the last part of the file where the all-important NMI and Reset vectors are located. The game runs by booting into a small stub that just skips the amount that exceeds 32768 bytes from the beginning of the file.


Last edited by NewRisingSun on Fri Apr 05, 2019 1:03 am, edited 4 times in total.

Top
 Profile  
 
PostPosted: Thu Apr 04, 2019 12:17 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:52 pm
Posts: 393
Location: UT
byuu wrote:
Oh boy. The wiki needs some serious updates, then. I'll also add:

The wiki is crap. Brad Taylor's .txt file (linked at the bottom of the wiki) is much more useful.


Top
 Profile  
 
PostPosted: Thu Apr 04, 2019 1:32 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 7582
Location: Canada
byuu wrote:
Quote:
The 65500 byte limit seems to have been entirely made up by Fan Wen Yang

Oh boy. The wiki needs some serious updates

The 65500 byte limit is part of the .FDS file format, so that does exist as a limit in that form, even though it's not an actual hardware limit. The Wiki unfortunately does not have a separate page for FDS and .FDS file format, and sometimes isn't clear about which of the two it's referring to. It should probably have the article split in two, at least. (Even the BIOS might deserve its own article? FDS hardware, FDS BIOS, FDS disk format, .FDS file format, FDS audio... each of these could be big enough to warrant separating? Maybe the disk and file formats are correlated enough to keep together.)

Feel free to do some editing there, I know it's a page people have complained about a few times. I worked on the corresponding audio article for it a bit, but I haven't really attempted to improve the main FDS article much.


Edit: I made the article splits I suggested. I think this might help a lot with comprehension, the problem I always had trying to read it or edit it was that it was hard to see lines between these related but very separate things (without already knowing what they are). If you can see further ways to improve it, though, please help edit the wiki FDS article, or make suggestions.


Last edited by rainwarrior on Thu Apr 04, 2019 3:49 pm, edited 3 times in total.

Top
 Profile  
 
PostPosted: Thu Apr 04, 2019 2:28 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21595
Location: NE Indiana, USA (NTSC)
Sour wrote:
The hooks you might have seen are probably the ones used to auto-switch disks instead of making the user pick them or to detect whether or not the game is loading and needs to be fast forwarded to save time, etc.

I'm curious about the rules for automatically switching sides. But would just "drive motor turned on in past 7 seconds + no audio or controller writes in past 3 frames" be enough as a heuristic for fast forward?

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Fri Apr 05, 2019 9:22 pm 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1524
Alright, thank you everyone for the help ^-^
Especially NewRisingSun for the very helpful code.

I'm sure there are lots of errors still, but I was able to get this functioning.

https://pastebin.com/raw/pHjeaY52

The way I'm doing it is taking the original input .fds file, removing the fwNES/iNES header if present, and then splitting it into a collection of sides. I haven't heard of 3+-disk games, so for now it's hard-coded to only support up to two disks with two sides each.

For each side, I add 0xe00 bytes of pregap padding before the first block, plus 0x80 bytes of per-block gap padding. Then a 0x80 byte after each pregap, then the block data, then the 16-bit checksum after that.

I was not able to find any confirmation of the absolute maximum size of a Famicom Disk, so for now I'm going with 0x12000 bytes, or 72KiB.

Whenever a disk is changed, I start a 500ms delay where $4032.d0 is force set, that way games have time to pick up that the disk was ejected before the new side is inserted.

Next, when powering on the drive motor via $4025.d0=0->1, I add a ~10ms wait. This is just a guess that a real motor won't turn on instantaneously.

Let's say you power off the motor halfway through a disk scan, or that the disk is completely scanned. It's going to take the head time to seek back to the start of the disk, so to simulate that, I'm stalling for 1% of the time it takes to read the disk (so ~60ms) before indicating that the disk is ready, by counting the read position backward (so if you stop the motor quickly, the rewind will be quicker.)

I am treating $4025.d1 as being latched after the motor powers up and the bit is set, and relatching it at the end of each disk scan, so clearing the bit won't stop the disk from continuing to scan, but it will set $4032.d1 to indicate that the disk is not ready.

To signal to the drive when the disk is in a gap section, I'm having each actual read off of the disk check for $4025.d6=0, and if so, forcing the drive into gap scanning mode (eg not sending IRQs until after the next 0x80 byte is found.) I'm not sure if the gap terminator is really 0x80 or just d7 of the byte, but I went with d7=1 for now.

I wasn't able to devise a way to perform the CRC checking, so I just return $4030.d4=0 always to say the CRC is good. I would like to support this, though. But I don't see the BIOS changing the drive control registers in an interesting way immediately before or after the CRC bytes are read off of the disk. I can only think of one way the drive itself would know to automatically compare the CRC, and that's if it hard-codes the lengths of blocks 1-3, and records the size of block 4 when read. But that ... doesn't seem likely.

I didn't emulate the disk inserted bounce just yet, but I will. And I haven't *yet* added write protection. Is that just a physical switch on the floppy disk like regular 3.5" disks have?

I can't see how Hubz' images with 0x708 bytes of pregap would work on hardware. It take about 0xbc0 bytes before the BIOS starts actually actively parsing the bytes coming in from the disk drive, and that amount of time is irrespective of how long we make the motor wait to turn on. With 0x708 bytes, it shoots right past the first two blocks before the first read.


Top
 Profile  
 
PostPosted: Sat Apr 06, 2019 2:30 am 
Offline

Joined: Thu May 19, 2005 11:30 am
Posts: 976
byuu wrote:
But I don't see the BIOS changing the drive control registers in an interesting way immediately before or after the CRC bytes are read off of the disk. I can only think of one way the drive itself would know to automatically compare the CRC, and that's if it hard-codes the lengths of blocks 1-3,
Does not need to. The RAM adapter resets the Current CRC Register to zero on a rising edge of $4025.6, and then just feeds all bytes it sees to the CRC calculator. The CRC field on the disk is such that reading all data bytes and then reading the two CRC bytes of the CRC field results in the Current CRC Register becoming zero, so all that $4030.4 needs to signal is whether the Current CRC Register value is non-zero or not.
byuu wrote:
I can't see how Hubz' images with 0x708 bytes of pregap would work on hardware. It take about 0xbc0 bytes before the BIOS starts actually actively parsing the bytes coming in from the disk drive, and that amount of time is irrespective of how long we make the motor wait to turn on. With 0x708 bytes, it shoots right past the first two blocks before the first read.
I do not know, but could imagine that these images are read with an offset from the beginning and will work when written with the same offset from the beginning.


Top
 Profile  
 
PostPosted: Sat Apr 06, 2019 12:06 pm 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1524
Quote:
The CRC field on the disk is such that reading all data bytes and then reading the two CRC bytes of the CRC field results in the Current CRC Register becoming zero


... wow. I knew leading 0x00s would be ignored when the CRC sum starts at 0, but I didn't know the CRC16 fed into the CRC16 algorithm would zero it out. That's really cool, thanks! In that case, I've got a bug in the calculation because the bit=crc!=0 thing was throwing CRC errors, but now I know to fix that, so thank you!

Quote:
I do not know, but could imagine that these images are read with an offset from the beginning and will work when written with the same offset from the beginning.


This whole system is kind of a nightmare for preservation. No such thing as a truly bit-perfect image. Much as I hate to say it, I think that the FDS format, aside from being too small, is probably the way to go, even though unlicensed games can store literally anything they want on side B, as long as they perform their own disk I/O.

...

On the off chance anyone ran into this before me ...

I can play Zelda on the NES just fine, but on the FDS Zelda, screen transitions do not work correctly and the scrolling seems to be wrong and glitchy until the entire transition has completed, then it looks okay again. I thought it would be the mirroring control, but it doesn't seem to be. If I try and invert the meaning of the mirroring bit, then games don't load because it doesn't populate CIRAM correctly.

Anyone else ever run into this?

Code:
  auto readPRG(uint address) -> uint8 {
    if(address >= 0x4020 && address <= 0x409f) return fds.read(address, cpu.mdr());
    if(address >= 0x6000 && address <= 0xdfff) return prgram.read(address - 0x6000);
    if(address >= 0xe000 && address <= 0xffff) return prgrom.read(address);
    return cpu.mdr();
  }

  auto writePRG(uint address, uint8 data) -> void {
    if(address == 0x4025) mirror = data.bit(3);
    if(address >= 0x4020 && address <= 0x409f) return fds.write(address, data);
    if(address >= 0x6000 && address <= 0xdfff) return prgram.write(address - 0x6000, data);
  }

  auto readCHR(uint address) -> uint8 {
    if(address & 0x2000) {
      if(mirror == 1) address = ((address & 0x0800) >> 1) | (address & 0x03ff);
      return ppu.readCIRAM(address & 0x07ff);
    }
    return chrram.read(address);
  }

  auto writeCHR(uint address, uint8 data) -> void {
    if(address & 0x2000) {
      if(mirror == 1) address = ((address & 0x0800) >> 1) | (address & 0x03ff);
      return ppu.writeCIRAM(address & 0x07ff, data);
    }
    return chrram.write(address, data);
  }


Top
 Profile  
 
PostPosted: Mon Apr 08, 2019 9:53 am 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1524
Oh indeed, just setting the invalid CRC bit to (bool)crc16; (eg crc16!=0) was all that was needed. Guess I already fixed the calculation issue. Thanks again!

The Zelda issue turned out to be a silly oversight. My mappers always stepped by CPU.rate() (which was 12 on NTSC, 16 on PAL, divisible against the 21MHz base clock), but the FDS was just stepping by 1, so the timer was running 12x too fast. Everything works great now.

Thanks again to everyone for the help! I don't know that I'm the most qualified, but I'd be willing to write up a doc on this to return the favor for helping me with this, if people would want that.

Not for the audio portion, though. I had to just reuse the Wiki formulas for that. I suck at FM synthesis. I do have 6-bit signed integers in higan though, so I would like to try and rework that code sometime to use those instead of the strange over/underflow manual checks to work with 8-bit integers that's there now.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 18 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group