mirror of
https://github.com/ares-emulator/ares.git
synced 2026-10-12 02:11:04 +00:00
## Overview Adds Nintendo Vs. UniSystem support to the ares FC core. Games remain in MAME ZIP format and load through the existing Arcade path, without going through the iNES detection path used by regular Famicom software. The implementation covers MAME metadata generation, parent ZIP lookup, Vs. PPU and palette behavior, cartridge boards, protection logic, cabinet I/O, DIP switches, controller wiring, Zapper input, and save states. A total of 41 Vs. UniSystem sets have been validated. Vs. DualSystem, non-standard bootleg hardware, and other configurations that have not yet been implemented are explicitly marked as unsupported instead of being started with the UniSystem model. ## Why `vsnes.py` is separate MAME groups three substantially different hardware families in `nintendo/vsnes.cpp`: - Vs. UniSystem with one CPU and one PPU; - Vs. DualSystem with two CPUs and two PPUs; - non-standard bootleg hardware built around a Z80 and SN76489. The generic MAME parser reads common data such as machine entries, ROM regions, and DIP switches. Those fields alone are not enough to determine which Vs. hardware model a game requires. The three families have different runtime structures and cannot share a single emulation path. Configurations also differ between Vs. UniSystem games. The PPU, palette, cartridge board, protection logic, controller wiring, Zapper support, and DIP settings may all vary by set. The core needs explicit configuration data rather than inferring hardware from a game title. `vsnes.py` reuses the MAME XML and ROM-region information produced by the generic parser, then adds hardware classification and per-game configuration. It generates a deterministic `VsSystem.bml` database. At runtime, supported sets select the corresponding FC hardware model from this metadata, while DualSystem and bootleg sets are rejected with a clear explanation. ## Video The combined video shows the attract modes of all 41 supported games. Each game is shown for approximately ten seconds, with its name in the upper-right corner. https://github.com/user-attachments/assets/46a59b2b-f64a-40ce-a870-1351d5df3948 https://github.com/user-attachments/assets/7927d1d0-4349-4f4b-9092-feeb7b0ac127 https://github.com/user-attachments/assets/d162c834-b227-497b-afe7-2f766f3ab42a https://github.com/user-attachments/assets/12d7b58e-7cd5-4bbf-b040-9d615e654e0c https://github.com/user-attachments/assets/a8708059-4369-48e1-be1f-42d15099d7a8 ## Screenshots The contact sheet contains one attract-mode screenshot for each game, composed at the original capture resolution. <img width="3920" height="5328" alt="contact-sheet" src="https://github.com/user-attachments/assets/e111cf05-4a49-4b0d-9719-082b2b925342" /> ## Supported sets The following 41 Vs. UniSystem MAME sets have been validated for loading and attract-mode operation: | No. | MAME short name | Game | | ---: | --- | --- | | 1 | `btlecity` | Vs. Battle City | | 2 | `cluclu` | Vs. Clu Clu Land | | 3 | `cstlevna` | Vs. Castlevania | | 4 | `drmario` | Vs. Dr. Mario | | 5 | `duckhunt` | Vs. Duck Hunt (set DH3 E) | | 6 | `excitebk` | Vs. Excitebike (set EB4-3 E) | | 7 | `excitebkj` | Vs. Excitebike (set EB4-4 A) | | 8 | `excitebko` | Vs. Excitebike (set EB4-3 ?) | | 9 | `goonies` | Vs. The Goonies (set E) | | 10 | `hogalley` | Vs. Hogan's Alley (set HA4-1 E-1) | | 11 | `iceclimb` | Vs. Ice Climber (set IC4-4 B-1) | | 12 | `iceclimba` | Vs. Ice Climber (set IC4-4 ?) | | 13 | `jajamaru` | Vs. Ninja Jajamaru Kun (Japan) | | 14 | `ladygolf` | Vs. Stroke & Match Golf (Ladies Version, set LG4 ?) | | 15 | `ladygolfe` | Vs. Stroke & Match Golf (Ladies Version, set LG4 E) | | 16 | `nvs_machrider` | Vs. Mach Rider (Endurance Course Version) | | 17 | `nvs_machridera` | Vs. Mach Rider (Fighting Course Version, set MR4-1 A) | | 18 | `nvs_mightybj` | Vs. Mighty Bomb Jack (Japan) | | 19 | `nvs_platoon` | Vs. Platoon | | 20 | `rbibb` | Vs. Atari R.B.I. Baseball (set 1) | | 21 | `rbibba` | Vs. Atari R.B.I. Baseball (set 2) | | 22 | `skatekds` | Vs. Skate Kids. (Graphic hack of Super Mario Bros.) | | 23 | `smgolf` | Vs. Stroke & Match Golf (Men Version, set GF4-2 F) | | 24 | `smgolfb` | Vs. Stroke & Match Golf (Men Version, set GF4-2 ?) | | 25 | `smgolfj` | Vs. Stroke & Match Golf (Men Version) (Japan, set GF3 B) | | 26 | `starlstr` | Vs. Star Luster | | 27 | `suprmrio` | Vs. Super Mario Bros. (set SM4-4 E) | | 28 | `suprmrioa` | Vs. Super Mario Bros. (set ?, harder) | | 29 | `supxevs` | Vs. Super Xevious | | 30 | `tkoboxng` | Vs. T.K.O. Boxing | | 31 | `topgun` | Vs. Top Gun | | 32 | `vsfdf` | Vs. Freedom Force | | 33 | `vsgradus` | Vs. Gradius (US, set GR E) | | 34 | `vsgshoe` | Vs. Gumshoe (set GM5) | | 35 | `vspinbal` | Vs. Pinball (US, set PN4 E-1) | | 36 | `vspinbalj` | Vs. Pinball (Japan, set PN3 B) | | 37 | `vsskykid` | Vs. Super SkyKid | | 38 | `vsslalom` | Vs. Slalom | | 39 | `vssoccer` | Vs. Soccer (set SC4-2 A) | | 40 | `vssoccera` | Vs. Soccer (set SC4-3 ?) | | 41 | `vstetris` | Vs. Tetris |
129 lines
2.7 KiB
C++
129 lines
2.7 KiB
C++
Zapper::Zapper(Node::Port port) {
|
|
node = port->append<Node::Peripheral>("Zapper");
|
|
|
|
x = node->append<Node::Input::Axis>("X");
|
|
y = node->append<Node::Input::Axis>("Y");
|
|
trigger = node->append<Node::Input::Button>("Trigger");
|
|
|
|
lightGun.load(node, x, y, trigger);
|
|
}
|
|
|
|
Zapper::~Zapper() {
|
|
lightGun.unload();
|
|
}
|
|
|
|
auto LightGun::load(Node::Object parent, Node::Input::Axis x, Node::Input::Axis y,
|
|
Node::Input::Button trigger) -> void {
|
|
unload();
|
|
this->parent = parent;
|
|
this->x = x;
|
|
this->y = y;
|
|
this->trigger = trigger;
|
|
cx = 256 / 2;
|
|
cy = 240 / 2;
|
|
px = 0;
|
|
py = 0;
|
|
nx = 256 / 2;
|
|
ny = 240 / 2;
|
|
|
|
sprite = parent->append<Node::Video::Sprite>("Crosshair");
|
|
sprite->setImage(Resource::Sprite::Famicom::Crosshair);
|
|
ppu.screen->attach(sprite);
|
|
}
|
|
|
|
auto LightGun::unload() -> void {
|
|
if(ppu.screen && sprite) ppu.screen->detach(sprite);
|
|
if(parent && sprite) parent->remove(sprite);
|
|
parent.reset();
|
|
x.reset();
|
|
y.reset();
|
|
trigger.reset();
|
|
sprite.reset();
|
|
}
|
|
|
|
auto LightGun::frame() -> void {
|
|
platform->input(x); //-n = left, 0 = center, +n = right
|
|
platform->input(y); //-n = up, 0 = center, +n = down
|
|
|
|
px = x->value();
|
|
py = y->value();
|
|
if(!px && !py) return;
|
|
|
|
cx = max(-8, min(256 + 8, px + cx));
|
|
cy = max(-8, min(240 + 8, py + cy));
|
|
|
|
sprite->setPosition(cx, cy);
|
|
sprite->setVisible(true);
|
|
|
|
nx = cx + 8;
|
|
ny = cy + 8;
|
|
}
|
|
|
|
auto LightGun::data() -> n3 {
|
|
n3 result = 0b000;
|
|
|
|
platform->input(trigger);
|
|
if(trigger->value()) result.bit(2) = 1;
|
|
|
|
bool offscreen = nx < 0 || ny < 0 || nx >= 256 || ny >= 240;
|
|
if(offscreen) {
|
|
result.bit(1) = 1;
|
|
return result;
|
|
}
|
|
|
|
u32* target = ppu.screen->pixels().data();
|
|
u32 count = 0;
|
|
for(int y = ny - 8; y <= ny + 8; y++) {
|
|
if(y < 0) continue;
|
|
if(y <= ppu.io.ly - 18) continue;
|
|
if(y > ppu.io.ly) break;
|
|
|
|
for(int x = nx - 8; x <= nx + 8; x++) {
|
|
if(x < 0) continue;
|
|
if(x >= 256) break;
|
|
|
|
n32 offset = y * 283 + x; //y * 283 + x + Region::PAL() ? 18 : 16;
|
|
n64 color = ppu.color(target[offset]);
|
|
f64 brightness = 0;
|
|
|
|
brightness += ((color >> 32) & 0xffff) * 0.299;
|
|
brightness += ((color >> 16) & 0xffff) * 0.587;
|
|
brightness += ((color >> 0) & 0xffff) * 0.114;
|
|
if(brightness > 0x80) count++;
|
|
}
|
|
}
|
|
|
|
if(count < 0x40) result.bit(1) = 1;
|
|
return result;
|
|
}
|
|
|
|
auto LightGun::serialize(serializer& s) -> void {
|
|
s(cx);
|
|
s(cy);
|
|
s(px);
|
|
s(py);
|
|
s(nx);
|
|
s(ny);
|
|
u32 legacyRaster = 0;
|
|
s(legacyRaster);
|
|
|
|
if(s.reading() && sprite) {
|
|
sprite->setPosition(cx, cy);
|
|
sprite->setVisible(true);
|
|
}
|
|
}
|
|
|
|
auto Zapper::frame() -> void {
|
|
lightGun.frame();
|
|
}
|
|
|
|
auto Zapper::data() -> n3 {
|
|
return lightGun.data();
|
|
}
|
|
|
|
auto Zapper::latch(n1 data) -> void {
|
|
}
|
|
|
|
auto Zapper::serialize(serializer& s) -> void {
|
|
}
|