From 4de4291a248451e09750c041322ea253120e1946 Mon Sep 17 00:00:00 2001 From: "ALIENJACK\\alien" Date: Tue, 30 Jul 2024 18:53:36 +0800 Subject: [PATCH] 123 --- .gitignore | 1 + .../Assets/Script/NesEmulator/CoreDebuger.cs | 1 + .../Assets/Script/NesEmulator/NesEmulator.cs | 12 +- .../Script/NesEmulator/PaletteDefine.cs | 242 ++++++++++++++++++ .../Script/NesEmulator/PaletteDefine.cs.meta | 11 + .../Script/NesEmulator/VideoProvider.cs | 57 ++++- .../Assets/VirtualNes.Core/CPU.cs | 10 +- .../VirtualNes.Core/CoreLibs/ByteArrayRef.cs | 28 ++ .../CoreLibs/ByteArrayRef.cs.meta | 11 + .../Assets/VirtualNes.Core/Debuger.cs | 7 +- .../Assets/VirtualNes.Core/MMU.cs | 19 +- .../Assets/VirtualNes.Core/Mapper/Mapper.cs | 4 +- .../Assets/VirtualNes.Core/NES.cs | 114 ++++++++- .../Assets/VirtualNes.Core/PAD.cs | 2 +- .../Assets/VirtualNes.Core/PPU.cs | 49 ++-- .../Assets/VirtualNes.Core/ROM.cs | 4 +- .../EmulatorConfig/EmulatorConfig.cs | 25 ++ .../Assets/VirtualNes.Core/VsUnisystem.cs | 49 ++++ .../VirtualNes.Core/VsUnisystem.cs.meta | 11 + virtuanessrc097-master/Render32bpp.h | Bin 39262 -> 41142 bytes 20 files changed, 600 insertions(+), 57 deletions(-) create mode 100644 AxibugEmuOnline.Client/Assets/Script/NesEmulator/PaletteDefine.cs create mode 100644 AxibugEmuOnline.Client/Assets/Script/NesEmulator/PaletteDefine.cs.meta create mode 100644 AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs create mode 100644 AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs.meta create mode 100644 AxibugEmuOnline.Client/Assets/VirtualNes.Core/VsUnisystem.cs create mode 100644 AxibugEmuOnline.Client/Assets/VirtualNes.Core/VsUnisystem.cs.meta diff --git a/.gitignore b/.gitignore index 44e618d..41a8bcd 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ /AxibugEmuOnline.Client/ProjectSettings/ProjectVersion.txt /AxibugEmuOnline.Client/ProjectSettings/AutoStreamingSettings.asset /AxibugEmuOnline.Client/Logs +/virtuanessrc097-master/save diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreDebuger.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreDebuger.cs index d031095..cdf1191 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreDebuger.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreDebuger.cs @@ -7,6 +7,7 @@ namespace AxibugEmuOnline.Client { public class CoreDebuger : IDebugerImpl { + public void Log(string message) { Debug.Log(message); diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs index 411c3c0..a7c7010 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using UnityEngine; using VirtualNes.Core; using VirtualNes.Core.Debug; @@ -26,7 +27,6 @@ namespace AxibugEmuOnline.Client try { m_nesIns = new NES(romName); - m_nesIns.Command(NESCOMMAND.NESCMD_HWRESET); } catch (Exception ex) { @@ -46,9 +46,17 @@ namespace AxibugEmuOnline.Client if (m_nesIns != null) { m_nesIns.EmulateFrame(true); + var screenBuffer = m_nesIns.ppu.GetScreenPtr(); - VideoProvider.SetDrawData(screenBuffer, PPU.SCREEN_WIDTH, PPU.SCREEN_HEIGHT); + var lineColorMode = m_nesIns.ppu.GetLineColorMode(); + + VideoProvider.SetDrawData(screenBuffer, lineColorMode, 256, 240); } } + + private void OnDestroy() + { + File.WriteAllLines("E:/log.txt", Debuger.logRecords); + } } } diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/PaletteDefine.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/PaletteDefine.cs new file mode 100644 index 0000000..4cb4fd3 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/PaletteDefine.cs @@ -0,0 +1,242 @@ +using Codice.CM.Client.Differences; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using VirtualNes.Core; + +namespace AxibugEmuOnline.Client.Assets.Script.NesEmulator +{ + public static class PaletteDefine + { + public class PALBUF + { + public byte r; + public byte g; + public byte b; + + public PALBUF(byte r, byte g, byte b) + { + this.r = r; + this.g = g; + this.b = b; + } + } + + // スキャンラインカラー + private static int m_nScanlineColor => Supporter.Config.graphics.nScanlineColor; + + public static float[][] PalConvTbl = new float[8][] + { + new float[3]{1.00f, 1.00f, 1.00f}, + new float[3]{1.00f, 0.80f, 0.73f}, + new float[3]{0.73f, 1.00f, 0.70f}, + new float[3]{0.76f, 0.78f, 0.58f}, + new float[3]{0.86f, 0.80f, 1.00f}, + new float[3]{0.83f, 0.68f, 0.85f}, + new float[3]{0.67f, 0.77f, 0.83f}, + new float[3]{0.68f, 0.68f, 0.68f}, + }; + + public static PALBUF[] m_PaletteBuf = new PALBUF[64] + { + new PALBUF(0x7F, 0x7F, 0x7F), + new PALBUF(0x20, 0x00, 0xB0), + new PALBUF(0x28, 0x00, 0xB8), + new PALBUF(0x60, 0x10, 0xA0), + new PALBUF(0x98, 0x20, 0x78), + new PALBUF(0xB0, 0x10, 0x30), + new PALBUF(0xA0, 0x30, 0x00), + new PALBUF(0x78, 0x40, 0x00), + new PALBUF(0x48, 0x58, 0x00), + new PALBUF(0x38, 0x68, 0x00), + new PALBUF(0x38, 0x6C, 0x00), + new PALBUF(0x30, 0x60, 0x40), + new PALBUF(0x30, 0x50, 0x80), + new PALBUF(0x00, 0x00, 0x00), + new PALBUF(0x00, 0x00, 0x00), + new PALBUF(0x00, 0x00, 0x00), + new PALBUF(0xBC, 0xBC, 0xBC), + new PALBUF(0x40, 0x60, 0xF8), + new PALBUF(0x40, 0x40, 0xFF), + new PALBUF(0x90, 0x40, 0xF0), + new PALBUF(0xD8, 0x40, 0xC0), + new PALBUF(0xD8, 0x40, 0x60), + new PALBUF(0xE0, 0x50, 0x00), + new PALBUF(0xC0, 0x70, 0x00), + new PALBUF(0x88, 0x88, 0x00), + new PALBUF(0x50, 0xA0, 0x00), + new PALBUF(0x48, 0xA8, 0x10), + new PALBUF(0x48, 0xA0, 0x68), + new PALBUF(0x40, 0x90, 0xC0), + new PALBUF(0x00, 0x00, 0x00), + new PALBUF(0x00, 0x00, 0x00), + new PALBUF(0x00, 0x00, 0x00), + new PALBUF(0xFF, 0xFF, 0xFF), + new PALBUF(0x60, 0xA0, 0xFF), + new PALBUF(0x50, 0x80, 0xFF), + new PALBUF(0xA0, 0x70, 0xFF), + new PALBUF(0xF0, 0x60, 0xFF), + new PALBUF(0xFF, 0x60, 0xB0), + new PALBUF(0xFF, 0x78, 0x30), + new PALBUF(0xFF, 0xA0, 0x00), + new PALBUF(0xE8, 0xD0, 0x20), + new PALBUF(0x98, 0xE8, 0x00), + new PALBUF(0x70, 0xF0, 0x40), + new PALBUF(0x70, 0xE0, 0x90), + new PALBUF(0x60, 0xD0, 0xE0), + new PALBUF(0x60, 0x60, 0x60), + new PALBUF(0x00, 0x00, 0x00), + new PALBUF(0x00, 0x00, 0x00), + new PALBUF(0xFF, 0xFF, 0xFF), + new PALBUF(0x90, 0xD0, 0xFF), + new PALBUF(0xA0, 0xB8, 0xFF), + new PALBUF(0xC0, 0xB0, 0xFF), + new PALBUF(0xE0, 0xB0, 0xFF), + new PALBUF(0xFF, 0xB8, 0xE8), + new PALBUF(0xFF, 0xC8, 0xB8), + new PALBUF(0xFF, 0xD8, 0xA0), + new PALBUF(0xFF, 0xF0, 0x90), + new PALBUF(0xC8, 0xF0, 0x80), + new PALBUF(0xA0, 0xF0, 0xA0), + new PALBUF(0xA0, 0xFF, 0xC8), + new PALBUF(0xA0, 0xFF, 0xF0), + new PALBUF(0xA0, 0xA0, 0xA0), + new PALBUF(0x00, 0x00, 0x00), + new PALBUF(0x00, 0x00, 0x00), + }; + + #region ピクセルフォーマットに変換したパレット + // Color + public static uint[][] m_cnPalette = new uint[8][] + { + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + }; + // Color/Scanline + public static uint[][] m_csPalette = new uint[8][] + { + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + }; + + // Monochrome + public static uint[][] m_mnPalette = new uint[8][] + { + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + }; + + // Monochrome/Scanline + public static uint[][] m_msPalette = new uint[8][] + { + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + new uint[256], + }; + #endregion + + static PaletteDefine() + { + int Rbit = 0, Gbit = 0, Bbit = 0; + int Rsft = 0, Gsft = 0, Bsft = 0; + + GetBitMask(0xFF0000, ref Rsft, ref Rbit); + GetBitMask(0x00FF00, ref Gsft, ref Gbit); + GetBitMask(0x0000FF, ref Bsft, ref Bbit); + + for (int j = 0; j < 8; j++) + { + for (int i = 0; i < 64; i++) + { + uint Rn, Gn, Bn; + uint Rs, Gs, Bs; + + // Normal + Rn = (uint)(PalConvTbl[j][0] * m_PaletteBuf[i].r); + Gn = (uint)(PalConvTbl[j][1] * m_PaletteBuf[i].g); + Bn = (uint)(PalConvTbl[j][2] * m_PaletteBuf[i].b); + // Scanline + Rs = (uint)(PalConvTbl[j][0] * m_PaletteBuf[i].r * m_nScanlineColor / 100.0f); + Gs = (uint)(PalConvTbl[j][1] * m_PaletteBuf[i].g * m_nScanlineColor / 100.0f); + Bs = (uint)(PalConvTbl[j][2] * m_PaletteBuf[i].b * m_nScanlineColor / 100.0f); + + m_cnPalette[j][i] = ((Rn >> (8 - Rbit)) << Rsft) | ((Gn >> (8 - Gbit)) << Gsft) | ((Bn >> (8 - Bbit)) << Bsft); + m_csPalette[j][i] = ((Rs >> (8 - Rbit)) << Rsft) | ((Gs >> (8 - Gbit)) << Gsft) | ((Bs >> (8 - Bbit)) << Bsft); + + // Monochrome + Rn = (uint)(m_PaletteBuf[i & 0x30].r); + Gn = (uint)(m_PaletteBuf[i & 0x30].g); + Bn = (uint)(m_PaletteBuf[i & 0x30].b); + Rn = + Gn = + Bn = (uint)(0.299f * Rn + 0.587f * Gn + 0.114f * Bn); + Rn = (uint)(PalConvTbl[j][0] * Rn); + Gn = (uint)(PalConvTbl[j][1] * Gn); + Bn = (uint)(PalConvTbl[j][2] * Bn); + if (Rn > 0xFF) Rs = 0xFF; + if (Gn > 0xFF) Gs = 0xFF; + if (Bn > 0xFF) Bs = 0xFF; + // Scanline + Rs = (uint)(m_PaletteBuf[i & 0x30].r * m_nScanlineColor / 100.0f); + Gs = (uint)(m_PaletteBuf[i & 0x30].g * m_nScanlineColor / 100.0f); + Bs = (uint)(m_PaletteBuf[i & 0x30].b * m_nScanlineColor / 100.0f); + Rs = + Gs = + Bs = (uint)(0.299f * Rs + 0.587f * Gs + 0.114f * Bs); + Rs = (uint)(PalConvTbl[j][0] * Rs); + Gs = (uint)(PalConvTbl[j][1] * Gs); + Bs = (uint)(PalConvTbl[j][2] * Bs); + if (Rs > 0xFF) Rs = 0xFF; + if (Gs > 0xFF) Gs = 0xFF; + if (Bs > 0xFF) Bs = 0xFF; + + m_mnPalette[j][i] = ((Rn >> (8 - Rbit)) << Rsft) | ((Gn >> (8 - Gbit)) << Gsft) | ((Bn >> (8 - Bbit)) << Bsft); + m_msPalette[j][i] = ((Rs >> (8 - Rbit)) << Rsft) | ((Gs >> (8 - Gbit)) << Gsft) | ((Bs >> (8 - Bbit)) << Bsft); + } + } + } + + // ビット位置の取得 + static void GetBitMask(uint val, ref int shift, ref int bits) + { + shift = 0; + while (((val & (1 << shift)) == 0) && (shift < 32)) + { + shift++; + } + + bits = 32; + while (((val & (1 << (bits - 1))) == 0) && (bits > 0)) + { + bits--; + } + bits = bits - shift; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/PaletteDefine.cs.meta b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/PaletteDefine.cs.meta new file mode 100644 index 0000000..d93c9c2 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/PaletteDefine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bbd3f54279eb4ae45831a914b13d1cec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs index f077271..662bac7 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs @@ -1,8 +1,13 @@ +using AxibugEmuOnline.Client.Assets.Script.NesEmulator; +using Codice.CM.Client.Differences; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text; using UnityEngine; using UnityEngine.UI; +using VirtualNes.Core; namespace AxibugEmuOnline.Client { @@ -12,14 +17,56 @@ namespace AxibugEmuOnline.Client private Texture2D wrapTex; - public void SetDrawData(byte[] data, int width, int height) + public void SetDrawData(byte[] screenData, byte[] lineColorMode, int screenWidth, int screenHeight) { - if (wrapTex == null) wrapTex = new Texture2D(width, height); + if (wrapTex == null) wrapTex = new Texture2D(screenWidth, screenHeight); - var colors = data.Select(d => new Color((d / 255f), (d / 255f), (d / 255f), 1)).ToArray(); - wrapTex.SetPixels(colors); + var str = Encoding.ASCII.GetString(screenData, 0, screenData.Length); + + uint[] pPal; + int pScn = 0; + int width; + + var Dst = wrapTex.GetPixels32(); + var pDst = 0; + + for (int line = 0; line < screenHeight; line++) + { + if ((lineColorMode[line] & 0x80) != 0) + { + pPal = PaletteDefine.m_cnPalette[lineColorMode[line] & 0x07]; + } + else + { + pPal = PaletteDefine.m_mnPalette[lineColorMode[line] & 0x07]; + } + + width = screenWidth; + + while (width > 0) + { + Color32 temp = new Color32(255, 255, 255, 255); + var edx = screenData[pScn]; + + byte index = (byte)(edx & 0xFF); + var colorData = pPal[index]; + var rawData = BitConverter.GetBytes(colorData); + Dst[pDst] = new Color32(rawData[0], rawData[1], rawData[2], 255); + + pScn += 1; + pDst += 1; + width -= 1; + } + + pScn += PPU.SCREEN_WIDTH - screenWidth; + } + + wrapTex.SetPixels32(Dst); wrapTex.Apply(); - Graphics.Blit(wrapTex, Image.texture as RenderTexture); + + Graphics.Blit(wrapTex, Image.mainTexture as RenderTexture); } + + } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs index 2b5fad9..4594c8b 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs @@ -1169,7 +1169,7 @@ namespace VirtualNes.Core return (ushort)(nes.Read(addr) + nes.Read((ushort)(addr + 1)) * 0x100); } - var temp = MMU.CPU_MEM_BANK[addr >> 13].Span; + var temp = MMU.CPU_MEM_BANK[addr >> 13]; shortTemp[0] = temp[addr & 0x1FFF]; shortTemp[1] = temp[(addr & 0x1FFF) + 1]; return BitConverter.ToUInt16(shortTemp, 0); @@ -1716,14 +1716,14 @@ namespace VirtualNes.Core internal byte OP6502(ushort addr) { - return MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF]; + return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF]; } private byte[] shortTemp = new byte[2]; internal ushort OP6502W(ushort addr) { var bytePage = MMU.CPU_MEM_BANK[addr >> 13]; - var spanByte = bytePage.Span; + var spanByte = bytePage; shortTemp[0] = spanByte[addr & 0x1FFF]; shortTemp[1] = spanByte[(addr & 0x1FFF) + 1]; return BitConverter.ToUInt16(shortTemp, 0); @@ -1744,11 +1744,11 @@ namespace VirtualNes.Core else { // Dummy access - mapper.Read(addr, MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF]); + mapper.Read(addr, MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF]); } // Quick bank read - return MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF]; + return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF]; } private void AND() diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs new file mode 100644 index 0000000..4966557 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs @@ -0,0 +1,28 @@ +namespace VirtualNes.Core +{ + public class ByteArrayRef + { + private byte[] m_rawArray; + private int m_offset; + private int m_length; + + public ByteArrayRef(byte[] array, int offset, int length) + { + m_rawArray = array; + m_offset = offset; + m_length = length; + } + + public byte this[int index] + { + get + { + return m_rawArray[m_offset + index]; + } + set + { + m_rawArray[(m_offset + index)] = value; + } + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs.meta b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs.meta new file mode 100644 index 0000000..eba7e42 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/ByteArrayRef.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe59f85b299db6f498a7e87a5125df58 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Debuger.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Debuger.cs index 8845558..526ef75 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Debuger.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Debuger.cs @@ -1,7 +1,11 @@ -namespace VirtualNes.Core.Debug +using System.Collections.Generic; + +namespace VirtualNes.Core.Debug { public static class Debuger { + public static List logRecords = new List(); + private static IDebugerImpl s_debuger; public static void Setup(IDebugerImpl debuger) { @@ -10,6 +14,7 @@ public static void Log(string message) { s_debuger.Log(message); + logRecords.Add(message); } public static void LogError(string message) diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs index 790f5ed..2ce14c8 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs @@ -1,15 +1,16 @@ using System; +using VirtualNes.Core; namespace VirtualNes { public static class MMU { // CPU 儊儌儕僶儞僋 - public static Memory[] CPU_MEM_BANK = new Memory[8]; // 8K扨埵 + public static ByteArrayRef[] CPU_MEM_BANK = new ByteArrayRef[8]; // 8K扨埵 public static byte[] CPU_MEM_TYPE = new byte[8]; public static int[] CPU_MEM_PAGE = new int[8]; // 僗僥乕僩僙乕僽梡 // PPU 儊儌儕僶儞僋 - public static Memory[] PPU_MEM_BANK = new Memory[12]; // 1K扨埵 + public static ByteArrayRef[] PPU_MEM_BANK = new ByteArrayRef[12]; // 1K扨埵 public static byte[] PPU_MEM_TYPE = new byte[12]; public static int[] PPU_MEM_PAGE = new int[12]; // 僗僥乕僩僙乕僽梡 public static byte[] CRAM_USED = new byte[16]; // 僗僥乕僩僙乕僽梡 @@ -70,9 +71,9 @@ namespace VirtualNes // Frame-IRQ儗僕僗僞($4017) public static int FrameIRQ; - internal static void SetPROM_Bank(byte page, Memory ptr, byte type) + internal static void SetPROM_Bank(byte page, byte[] ptr, byte type) { - CPU_MEM_BANK[page] = ptr; + CPU_MEM_BANK[page] = new ByteArrayRef(ptr, 0, ptr.Length); CPU_MEM_TYPE[page] = type; CPU_MEM_PAGE[page] = 0; } @@ -80,7 +81,7 @@ namespace VirtualNes internal static void SetPROM_8K_Bank(byte page, int bank) { bank %= PROM_8K_SIZE; - CPU_MEM_BANK[page] = new Memory(MMU.PROM, 0x2000 * bank, MMU.PROM.Length - 0x2000 * bank); + CPU_MEM_BANK[page] = new ByteArrayRef(MMU.PROM, 0x2000 * bank, MMU.PROM.Length - 0x2000 * bank); CPU_MEM_TYPE[page] = BANKTYPE_ROM; CPU_MEM_PAGE[page] = bank; } @@ -108,7 +109,7 @@ namespace VirtualNes } // PPU VROM bank - internal static void SetVROM_Bank(byte page, Memory ptr, byte type) + internal static void SetVROM_Bank(byte page, ByteArrayRef ptr, byte type) { PPU_MEM_BANK[page] = ptr; PPU_MEM_TYPE[page] = type; @@ -118,7 +119,7 @@ namespace VirtualNes internal static void SetVROM_1K_Bank(byte page, int bank) { bank %= VROM_1K_SIZE; - PPU_MEM_BANK[page] = new Memory(VROM, 0x0400 * bank, VROM.Length - (0x0400 * bank)); + PPU_MEM_BANK[page] = new ByteArrayRef(VROM, 0x0400 * bank, VROM.Length - (0x0400 * bank)); PPU_MEM_TYPE[page] = BANKTYPE_VROM; PPU_MEM_PAGE[page] = bank; } @@ -161,7 +162,7 @@ namespace VirtualNes internal static void SetCRAM_1K_Bank(byte page, int bank) { bank &= 0x1F; - PPU_MEM_BANK[page] = new Memory(MMU.CRAM, 0x0400 * bank, MMU.CRAM.Length - 0x0400 * bank); + PPU_MEM_BANK[page] = new ByteArrayRef(MMU.CRAM, 0x0400 * bank, MMU.CRAM.Length - 0x0400 * bank); PPU_MEM_TYPE[page] = BANKTYPE_CRAM; PPU_MEM_PAGE[page] = bank; @@ -193,7 +194,7 @@ namespace VirtualNes internal static void SetVRAM_1K_Bank(byte page, int bank) { bank &= 3; - PPU_MEM_BANK[page] = new Memory(VRAM, 0x0400 * bank, VRAM.Length - 0x0400 * bank); + PPU_MEM_BANK[page] = new ByteArrayRef(VRAM, 0x0400 * bank, VRAM.Length - 0x0400 * bank); PPU_MEM_TYPE[page] = BANKTYPE_VRAM; PPU_MEM_PAGE[page] = bank; } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Mapper/Mapper.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Mapper/Mapper.cs index 542201a..c61fae7 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Mapper/Mapper.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Mapper/Mapper.cs @@ -26,7 +26,7 @@ namespace VirtualNes.Core // $6000-$7FFF WRAM if (addr >= 0x6000 && addr <= 0x7FFF) { - return MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF]; + return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF]; } return (byte)(addr >> 8); @@ -35,7 +35,7 @@ namespace VirtualNes.Core { if (addr >= 0x6000 && addr <= 0x7FFF) { - MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF] = data; + MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF] = data; } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs index d6359e2..aad3066 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs @@ -75,6 +75,10 @@ namespace VirtualNes.Core private long base_cycles; private long emul_cycles; + // For VS-Unisystem + byte m_VSDipValue; + VSDIPSWITCH[] m_VSDipTable; + private byte[] m_PadImg = new byte[226] { 28, 8, @@ -253,6 +257,65 @@ namespace VirtualNes.Core NesSub_MemoryInitial(); LoadSRAM(); LoadDISK(); + + { + // Pad饹ڤȳڻߥ󥰤WΤǤ + uint crc = rom.GetPROM_CRC(); + if ( + crc == 0xe792de94 // Best Play - Pro Yakyuu (New) (J) + || crc == 0xf79d684a // Best Play - Pro Yakyuu (Old) (J) + || crc == 0xc2ef3422 // Best Play - Pro Yakyuu 2 (J) + || crc == 0x974e8840 // Best Play - Pro Yakyuu '90 (J) + || crc == 0xb8747abf // Best Play - Pro Yakyuu Special (J) + || crc == 0x9fa1c11f // Castle Excellent (J) + || crc == 0x0b0d4d1b // Derby Stallion - Zenkoku Ban (J) + || crc == 0x728c3d98 // Downtown - Nekketsu Monogatari (J) + || crc == 0xd68a6f33 // Dungeon Kid (J) + || crc == 0x3a51eb04 // Fleet Commander (J) + || crc == 0x7c46998b // Haja no Fuuin (J) + || crc == 0x7e5d2f1a // Itadaki Street - Watashi no Mise ni Yottette (J) + || crc == 0xcee5857b // Ninjara Hoi! (J) + || crc == 0x50ec5e8b // Wizardry - Legacy of Llylgamyn (J) + || crc == 0x343e9146 // Wizardry - Proving Grounds of the Mad Overlord (J) + || crc == 0x33d07e45) + { // Wizardry - The Knight of Diamonds (J) + pad.SetExController(EXCONTROLLER.EXCONTROLLER_TURBOFILE); + } + } + + LoadTurboFile(); + + // VS-UnisystemΥǥեO + if (rom.IsVSUNISYSTEM()) + { + uint crc = rom.GetPROM_CRC(); + + m_VSDipValue = 0; + m_VSDipTable = VsUnisystem.vsdip_default; + } + + Reset(); + + // `ФΥǥեȥץO(Orʹ) + GameOption.defRenderMethod = (int)GetRenderMethod(); + GameOption.defIRQtype = GetIrqType(); + GameOption.defFrameIRQ = GetFrameIRQmode(); + GameOption.defVideoMode = GetVideoMode(); + + // O`ɤO(ȥ꤬oХǥեȤ) + if (rom.GetMapperNo() != 20) + { + GameOption.Load(rom.GetPROM_CRC()); + } + else + { + GameOption.Load(rom.GetGameID(), rom.GetMakerID()); + } + + SetRenderMethod((EnumRenderMethod)GameOption.nRenderMethod); + SetIrqType((IRQMETHOD)GameOption.nIRQtype); + SetFrameIRQmode(GameOption.bFrameIRQ); + SetVideoMode(GameOption.bVideoMode); } catch (Exception ex) { @@ -261,6 +324,44 @@ namespace VirtualNes.Core } } + private int GetIrqType() + { + return nIRQtype; + } + + private void LoadTurboFile() + { + MemoryUtility.ZEROMEMORY(MMU.ERAM, MMU.ERAM.Length); + + if (pad.GetExController() != (int)EXCONTROLLER.EXCONTROLLER_TURBOFILE) + return; + + var fp = Supporter.OpenFile(Supporter.Config.path.szSavePath, "TurboFile.vtf"); + try + { + if (fp == null) + { + // xxx ե_ޤ + throw new Exception($"Can Not Open File [TurboFile.vtf]"); + } + + long size = fp.Length; + // ե륵ȡ + if (size > 32 * 1024) + { + size = 32 * 1024; + } + + fp.Read(MMU.ERAM, 0, MMU.ERAM.Length); + fp.Close(); + } + catch (Exception ex) + { + fp?.Close(); + Debuger.LogError($"Loading TurboFile Error.\n{ex}"); + } + } + private void LoadDISK() { //todo : ŵȡ֧ @@ -1308,7 +1409,7 @@ namespace VirtualNes.Core case 0x05: // $A000-$BFFF case 0x06: // $C000-$DFFF case 0x07: // $E000-$FFFF - return MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF]; + return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF]; } return 0x00; // Warning\h @@ -1414,8 +1515,13 @@ namespace VirtualNes.Core return ret; } + static int NESWRITECOUNT = 0; internal void Write(ushort addr, byte data) { + NESWRITECOUNT++; + + Debuger.Log($"[{NESWRITECOUNT}] addr:{addr},data:{data}"); + switch (addr >> 13) { case 0x00: // $0000-$1FFF @@ -1461,16 +1567,16 @@ namespace VirtualNes.Core if ((addr & 0x8000) != 0) { // 8character codes - if (MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF] == m_GenieCode[i].cmp) + if (MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF] == m_GenieCode[i].cmp) { - MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF] = m_GenieCode[i].data; + MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF] = m_GenieCode[i].data; } } else { // 6character codes addr |= 0x8000; - MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF] = m_GenieCode[i].data; + MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF] = m_GenieCode[i].data; } } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs index df417bb..01f5df1 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs @@ -330,7 +330,7 @@ namespace VirtualNes.Core } } - private void SetExController(EXCONTROLLER type) + internal void SetExController(EXCONTROLLER type) { excontroller_select = (int)type; diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs index fff8fe7..ffbeb54 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs @@ -1,12 +1,4 @@ -using Codice.CM.Client.Differences; -using Microsoft.Win32; -using System; -using System.IO; -using Unity.VisualScripting.Antlr3.Runtime.Tree; -using UnityEngine; -using static VirtualNes.Core.PPU; - -namespace VirtualNes.Core +namespace VirtualNes.Core { public class PPU { @@ -195,7 +187,7 @@ namespace VirtualNes.Core } addr &= 0xEFFF; } - MMU.PPU7_Temp = MMU.PPU_MEM_BANK[addr >> 10].Span[addr & 0x03FF]; + MMU.PPU7_Temp = MMU.PPU_MEM_BANK[addr >> 10][addr & 0x03FF]; break; } @@ -331,7 +323,7 @@ namespace VirtualNes.Core } if (MMU.PPU_MEM_TYPE[vaddr >> 10] != MMU.BANKTYPE_VROM) { - MMU.PPU_MEM_BANK[vaddr >> 10].Span[vaddr & 0x03FF] = data; + MMU.PPU_MEM_BANK[vaddr >> 10][vaddr & 0x03FF] = data; } break; } @@ -473,7 +465,7 @@ namespace VirtualNes.Core int attradr = 0x23C0 + (MMU.loopy_v & 0x0C00) + ((MMU.loopy_v & 0x0380) >> 4); int ntbl_x = ntbladr & 0x001F; int attrsft = (ntbladr & 0x0040) >> 4; - var pNTBL = MMU.PPU_MEM_BANK[ntbladr >> 10].Span; + var pNTBL = MMU.PPU_MEM_BANK[ntbladr >> 10]; int tileadr; int cache_tile = unchecked((int)(0xFFFF0000)); @@ -486,7 +478,6 @@ namespace VirtualNes.Core for (int i = 0; i < 33; i++) { - tileadr = tileofs + pNTBL[ntbladr & 0x03FF] * 0x10 + loopy_y; attr = (byte)(((pNTBL[attradr + (ntbl_x >> 2)] >> ((ntbl_x & 2) + attrsft)) & 3) << 2); @@ -508,8 +499,8 @@ namespace VirtualNes.Core { cache_tile = tileadr; cache_attr = attr; - chr_l = MMU.PPU_MEM_BANK[tileadr >> 10].Span[tileadr & 0x03FF]; - chr_h = MMU.PPU_MEM_BANK[tileadr >> 10].Span[(tileadr & 0x03FF) + 8]; + chr_l = MMU.PPU_MEM_BANK[tileadr >> 10][tileadr & 0x03FF]; + chr_h = MMU.PPU_MEM_BANK[tileadr >> 10][(tileadr & 0x03FF) + 8]; BGwrite[pBGw] = (byte)(chr_h | chr_l); int pBGPAL = attr; @@ -540,7 +531,7 @@ namespace VirtualNes.Core ntbl_x = 0; ntbladr ^= 0x41F; attradr = 0x03C0 + ((ntbladr & 0x0380) >> 4); - pNTBL = MMU.PPU_MEM_BANK[ntbladr >> 10].Span; + pNTBL = MMU.PPU_MEM_BANK[ntbladr >> 10]; } else { @@ -632,7 +623,7 @@ namespace VirtualNes.Core int attradr = 0x03C0 + ((MMU.loopy_v & 0x0380) >> 4); int ntbl_x = ntbladr & 0x001F; int attrsft = (ntbladr & 0x0040) >> 4; - var pNTBL = MMU.PPU_MEM_BANK[ntbladr >> 10].Span; + var pNTBL = MMU.PPU_MEM_BANK[ntbladr >> 10]; int tileadr = 0; int cache_tile = unchecked((int)(0xFFFF0000)); @@ -656,8 +647,8 @@ namespace VirtualNes.Core cache_tile = tileadr; cache_attr = attr; - chr_l = MMU.PPU_MEM_BANK[tileadr >> 10].Span[tileadr & 0x03FF]; - chr_h = MMU.PPU_MEM_BANK[tileadr >> 10].Span[(tileadr & 0x03FF) + 8]; + chr_l = MMU.PPU_MEM_BANK[tileadr >> 10][tileadr & 0x03FF]; + chr_h = MMU.PPU_MEM_BANK[tileadr >> 10][(tileadr & 0x03FF) + 8]; lpScreen[pBGw] = (byte)(chr_l | chr_h); int pBGPAL = attr; @@ -702,7 +693,7 @@ namespace VirtualNes.Core ntbl_x = 0; ntbladr ^= 0x41F; attradr = 0x03C0 + ((ntbladr & 0x0380) >> 4); - pNTBL = MMU.PPU_MEM_BANK[ntbladr >> 10].Span; + pNTBL = MMU.PPU_MEM_BANK[ntbladr >> 10]; } else { @@ -732,16 +723,16 @@ namespace VirtualNes.Core } ntbladr = 0x2000 + (MMU.loopy_v & 0x0FFF); - tileadr = ((MMU.PPUREG[0] & PPU_BGTBL_BIT) << 8) + MMU.PPU_MEM_BANK[ntbladr >> 10].Span[ntbladr & 0x03FF] * 0x10 + ((MMU.loopy_v & 0x7000) >> 12); - attr = (byte)(((MMU.PPU_MEM_BANK[ntbladr >> 10].Span[0x03C0 + ((ntbladr & 0x0380) >> 4) + ((ntbladr & 0x001C) >> 2)] >> (((ntbladr & 0x40) >> 4) + (ntbladr & 0x02))) & 3) << 2); + tileadr = ((MMU.PPUREG[0] & PPU_BGTBL_BIT) << 8) + MMU.PPU_MEM_BANK[ntbladr >> 10][ntbladr & 0x03FF] * 0x10 + ((MMU.loopy_v & 0x7000) >> 12); + attr = (byte)(((MMU.PPU_MEM_BANK[ntbladr >> 10][0x03C0 + ((ntbladr & 0x0380) >> 4) + ((ntbladr & 0x001C) >> 2)] >> (((ntbladr & 0x40) >> 4) + (ntbladr & 0x02))) & 3) << 2); if (cache_tile != tileadr || cache_attr != attr) { cache_tile = tileadr; cache_attr = attr; - chr_l = MMU.PPU_MEM_BANK[tileadr >> 10].Span[tileadr & 0x03FF]; - chr_h = MMU.PPU_MEM_BANK[tileadr >> 10].Span[(tileadr & 0x03FF) + 8]; + chr_l = MMU.PPU_MEM_BANK[tileadr >> 10][tileadr & 0x03FF]; + chr_h = MMU.PPU_MEM_BANK[tileadr >> 10][(tileadr & 0x03FF) + 8]; BGwrite[pBGw] = (byte)(chr_l | chr_h); int pBGPAL = attr; @@ -937,8 +928,8 @@ namespace VirtualNes.Core spraddr += ((~sp_y & 8) << 1) + (7 - (sp_y & 7)); } // Character pattern - chr_l = MMU.PPU_MEM_BANK[spraddr >> 10].Span[spraddr & 0x3FF]; - chr_h = MMU.PPU_MEM_BANK[spraddr >> 10].Span[(spraddr & 0x3FF) + 8]; + chr_l = MMU.PPU_MEM_BANK[spraddr >> 10][spraddr & 0x3FF]; + chr_h = MMU.PPU_MEM_BANK[spraddr >> 10][(spraddr & 0x3FF) + 8]; // Character latch(For MMC2/MMC4) if (bChrLatch) @@ -1117,12 +1108,18 @@ namespace VirtualNes.Core return lpScreen; } + public byte[] GetLineColorMode() + { + return lpColormode; + } + internal void SetScreenPtr(byte[] screenBuffer, byte[] colormode) { lpScreen = screenBuffer; lpColormode = colormode; } + internal bool IsDispON() { return (MMU.PPUREG[1] & (PPU_BGDISP_BIT | PPU_SPDISP_BIT)) != 0; diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ROM.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ROM.cs index b7aacbd..d6cdd26 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ROM.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ROM.cs @@ -347,12 +347,12 @@ namespace VirtualNes.Core return diskno; } - internal ulong GetGameID() + internal uint GetGameID() { return fdsgameID; } - internal ulong GetMakerID() + internal uint GetMakerID() { return fdsmakerID; } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/EmulatorConfig/EmulatorConfig.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/EmulatorConfig/EmulatorConfig.cs index 961e389..77f78a8 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/EmulatorConfig/EmulatorConfig.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/EmulatorConfig/EmulatorConfig.cs @@ -17,4 +17,29 @@ public CfgExtraSound extsound { get; private set; } = new CfgExtraSound(); public CfgNetPlay netplay { get; private set; } = new CfgNetPlay(); } + + public static class GameOption + { + // Default保存 + public static int defRenderMethod; + public static int defIRQtype; + public static bool defFrameIRQ; + public static bool defVideoMode; + + // データ + public static int nRenderMethod; + public static int nIRQtype; + public static bool bFrameIRQ; + public static bool bVideoMode; + + public static void Load(uint crc) + { + + } + + public static void Load(uint gid, uint mid) + { + + } + } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/VsUnisystem.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/VsUnisystem.cs new file mode 100644 index 0000000..18f995c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/VsUnisystem.cs @@ -0,0 +1,49 @@ +namespace VirtualNes.Core +{ + public class VSDIPSWITCH + { + public string name; + public ushort value; + } + + + public static class VsUnisystem + { + public static VSDIPSWITCH[] vsdip_default = new VSDIPSWITCH[] + { + new VSDIPSWITCH{name="Unknown", value= 0x0100}, + new VSDIPSWITCH{name="Off", value= 0x00}, + new VSDIPSWITCH{name="On", value= 0x01}, + new VSDIPSWITCH{name=null, value= 0xFF}, + new VSDIPSWITCH{name="Unknown", value= 0x0200}, + new VSDIPSWITCH{name="Off", value= 0x00}, + new VSDIPSWITCH{name="On", value= 0x02}, + new VSDIPSWITCH{name=null, value= 0xFF}, + new VSDIPSWITCH{name="Unknown", value= 0x0400}, + new VSDIPSWITCH{name="Off", value= 0x00}, + new VSDIPSWITCH{name="On", value= 0x04}, + new VSDIPSWITCH{name=null, value= 0xFF}, + new VSDIPSWITCH{name="Unknown", value= 0x0800}, + new VSDIPSWITCH{name="Off", value= 0x00}, + new VSDIPSWITCH{name="On", value= 0x08}, + new VSDIPSWITCH{name=null, value= 0xFF}, + new VSDIPSWITCH{name="Unknown", value= 0x1000}, + new VSDIPSWITCH{name="Off", value= 0x00}, + new VSDIPSWITCH{name="On", value= 0x10}, + new VSDIPSWITCH{name=null, value= 0xFF}, + new VSDIPSWITCH{name="Unknown", value= 0x2000}, + new VSDIPSWITCH{name="Off", value= 0x00}, + new VSDIPSWITCH{name="On", value= 0x20}, + new VSDIPSWITCH{name=null, value= 0xFF}, + new VSDIPSWITCH{name="Unknown", value= 0x4000}, + new VSDIPSWITCH{name="Off", value= 0x00}, + new VSDIPSWITCH{name="On", value= 0x40}, + new VSDIPSWITCH{name=null, value= 0xFF}, + new VSDIPSWITCH{name="Unknown", value= 0x8000}, + new VSDIPSWITCH{name="Off", value= 0x00}, + new VSDIPSWITCH{name="On", value= 0x80}, + new VSDIPSWITCH{name=null, value= 0xFF}, + new VSDIPSWITCH{name=null, value= 0 }, + }; + } +} diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/VsUnisystem.cs.meta b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/VsUnisystem.cs.meta new file mode 100644 index 0000000..3d06276 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/VsUnisystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db82d3b2d60f2c14fa3c5582acc439c6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/virtuanessrc097-master/Render32bpp.h b/virtuanessrc097-master/Render32bpp.h index 118bad721245ca27ba85c94f1355a14150b720e2..9ecb5b6eff53a841bddd51ea70110b9c1a8f6ce2 100644 GIT binary patch literal 41142 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