diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..46fd90e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "AxibugEmuOnline.Client/Assets/AlienUICore"] + path = AxibugEmuOnline.Client/Assets/AlienUICore + url = https://github.com/AlienJack/AlienUICore.git diff --git a/AxibugEmuOnline.Client/Assets/AlienUICore b/AxibugEmuOnline.Client/Assets/AlienUICore new file mode 160000 index 0000000..66425c7 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/AlienUICore @@ -0,0 +1 @@ +Subproject commit 66425c710fc5fee9206fb82e6f0e60da31fa2733 diff --git a/AxibugEmuOnline.Client/Assets/Plugins/ICSharpCode.SharpZipLib.dll b/AxibugEmuOnline.Client/Assets/Plugins/ICSharpCode.SharpZipLib.dll new file mode 100644 index 0000000..11a8f2f Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Plugins/ICSharpCode.SharpZipLib.dll differ diff --git a/AxibugEmuOnline.Client/Assets/Plugins/SevenZipSharp.dll.meta b/AxibugEmuOnline.Client/Assets/Plugins/ICSharpCode.SharpZipLib.dll.meta similarity index 93% rename from AxibugEmuOnline.Client/Assets/Plugins/SevenZipSharp.dll.meta rename to AxibugEmuOnline.Client/Assets/Plugins/ICSharpCode.SharpZipLib.dll.meta index 3031b1e..ebefec2 100644 --- a/AxibugEmuOnline.Client/Assets/Plugins/SevenZipSharp.dll.meta +++ b/AxibugEmuOnline.Client/Assets/Plugins/ICSharpCode.SharpZipLib.dll.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: cda51a5b513e24a4db500829511ef182 +guid: 0f9e31e301641e94eb97c78b5c04d45b PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/AxibugEmuOnline.Client/Assets/Plugins/SevenZipSharp.dll b/AxibugEmuOnline.Client/Assets/Plugins/SevenZipSharp.dll deleted file mode 100644 index f588f8a..0000000 Binary files a/AxibugEmuOnline.Client/Assets/Plugins/SevenZipSharp.dll and /dev/null differ diff --git a/AxibugEmuOnline.Client/Assets/Script/AppAxibugEmuOnline.cs b/AxibugEmuOnline.Client/Assets/Script/AppAxibugEmuOnline.cs index 1f45603..7fdf35f 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppAxibugEmuOnline.cs +++ b/AxibugEmuOnline.Client/Assets/Script/AppAxibugEmuOnline.cs @@ -1,6 +1,7 @@ using AxibugEmuOnline.Client.Manager; using AxibugEmuOnline.Client.Network; using System; +using System.Collections; using UnityEngine; using static AxibugEmuOnline.Client.Manager.LogManager; @@ -19,8 +20,13 @@ namespace AxibugEmuOnline.Client.ClientCore public static UserDataManager user; public static AppNetGame netgame; public static AppEmu emu; - - public static void Init() + public static RomLib romLib; + public static HttpAPI httpAPI; + + private static CoroutineRunner coRunner; + + [RuntimeInitializeOnLoadMethod] + static void Init() { log = new LogManager(); LogManager.OnLog += OnNoSugarNetLog; @@ -30,6 +36,22 @@ namespace AxibugEmuOnline.Client.ClientCore user = new UserDataManager(); emu = new AppEmu(); netgame = new AppNetGame(); + romLib = new RomLib(); + httpAPI = new HttpAPI(); + + var go = new GameObject("[AppAxibugEmuOnline]"); + GameObject.DontDestroyOnLoad(go); + coRunner = go.AddComponent(); + } + + public static Coroutine StartCoroutine(IEnumerator itor) + { + return coRunner.StartCoroutine(itor); + } + + public static void StopCoroutine(Coroutine cor) + { + coRunner.StopCoroutine(cor); } public static bool Connect(string IP, int port) @@ -43,7 +65,7 @@ namespace AxibugEmuOnline.Client.ClientCore } static void OnNoSugarNetLog(int LogLevel, string msg) { - Debug.Log("[AxibugEmuOnline]:"+msg); + Debug.Log("[AxibugEmuOnline]:" + msg); } } } \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/CoroutineRunner.cs b/AxibugEmuOnline.Client/Assets/Script/Manager/CoroutineRunner.cs new file mode 100644 index 0000000..c6541b8 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/Manager/CoroutineRunner.cs @@ -0,0 +1,8 @@ +using UnityEngine; + +namespace AxibugEmuOnline.Client +{ + public class CoroutineRunner : MonoBehaviour + { + } +} diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/CoroutineRunner.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Manager/CoroutineRunner.cs.meta new file mode 100644 index 0000000..7ae66ee --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/Manager/CoroutineRunner.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 26e0101eb56737b4f97fcaa3bd319d43 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/HttpAPI.cs b/AxibugEmuOnline.Client/Assets/Script/Manager/HttpAPI.cs new file mode 100644 index 0000000..a1593d9 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/Manager/HttpAPI.cs @@ -0,0 +1,79 @@ +using AxibugEmuOnline.Client.ClientCore; +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Networking; + +namespace AxibugEmuOnline.Client +{ + public class HttpAPI + { + public string WebSite = "http://emu.axibug.com/api"; + public string DownSite = "http://emu.axibug.com"; + + public void GetNesRomList(Action callback, int page, int pageSize = 10) + { + AppAxibugEmuOnline.StartCoroutine(GetNesRomListFlow(page, pageSize, callback)); + } + + private IEnumerator GetNesRomListFlow(int page, int pageSize, Action callback) + { + UnityWebRequest request = UnityWebRequest.Get($"{WebSite}/NesRomList?Page={page}&PageSize={pageSize}"); + yield return request.SendWebRequest(); + + if (request.result != UnityWebRequest.Result.Success) + { + callback.Invoke(null); + yield break; + } + + var resp = JsonUtility.FromJson(request.downloadHandler.text); + callback.Invoke(resp); + } + + enum GameType : byte + { + NONE = 0, + ACT, + ARPG, + AVG, + ETC, + FTG, + PUZ, + RAC, + RPG, + SLG, + SPG, + SRPG, + STG, + TAB, + /// + /// 合卡 + /// + ALLINONE, + } + + [Serializable] + public class Resp_GameList + { + public int page; + public int maxPage; + public int resultAllCount; + public List gameList; + } + + [Serializable] + public class Resp_RomInfo + { + public int id; + public string romName; + public string gType; + public string desc; + public string url; + public string imgUrl; + public string hash; + public int stars; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/HttpAPI.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Manager/HttpAPI.cs.meta new file mode 100644 index 0000000..078f309 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/Manager/HttpAPI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 81bb136dc2c85304db4715953f751aa6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib.meta b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib.meta new file mode 100644 index 0000000..a783f44 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 006d5604a1d73de4e8d057974313dbaf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/EnumPlatform.cs b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/EnumPlatform.cs new file mode 100644 index 0000000..d2c8aa5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/EnumPlatform.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AxibugEmuOnline.Client +{ + public enum EnumPlatform + { + NES + } +} diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/EnumPlatform.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/EnumPlatform.cs.meta new file mode 100644 index 0000000..8984029 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/EnumPlatform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 355fa00c125158f4ba90003b0fd5d788 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomFile.cs b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomFile.cs new file mode 100644 index 0000000..e334da0 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomFile.cs @@ -0,0 +1,124 @@ +using AxibugEmuOnline.Client.ClientCore; +using ICSharpCode.SharpZipLib.Zip; +using System; +using System.Collections; +using System.IO; +using System.Linq; +using UnityEngine; +using UnityEngine.Networking; + +namespace AxibugEmuOnline.Client +{ + public class RomFile + { + private HttpAPI.Resp_RomInfo webData; + private bool hasLocalFile; + private string fileName; + private EnumPlatform platform; + + public string LocalFilePath => $"{Application.persistentDataPath}/RemoteRoms/{platform}/{fileName}"; + public bool FileReady => hasLocalFile; + public int ID => webData != null ? webData.id : -1; + public bool IsDownloading { get; private set; } + public string Alias => webData.romName; + public string FileName => fileName; + + public event Action OnDownloadOver; + + public RomFile(EnumPlatform platform) + { + this.platform = platform; + } + + public void BeginDownload() + { + if (FileReady) return; + if (IsDownloading) return; + + IsDownloading = true; + AppAxibugEmuOnline.StartCoroutine(DownloadRemoteRom((bytes) => + { + IsDownloading = false; + + if (bytes != null) + { + var directPath = Path.GetDirectoryName(LocalFilePath); + Directory.CreateDirectory(directPath); + + File.WriteAllBytes(LocalFilePath, bytes); + hasLocalFile = true; + } + OnDownloadOver?.Invoke(); + })); + } + + public byte[] GetRomFileData() + { + if (webData == null) throw new Exception("Not Valid Rom"); + if (!FileReady) throw new Exception("Rom File Not Downloaded"); + + var bytes = File.ReadAllBytes(LocalFilePath); + if (Path.GetExtension(LocalFilePath).ToLower() == ".zip") + { + var zip = new ZipInputStream(new MemoryStream(bytes)); + while (zip.GetNextEntry() is ZipEntry entry) + { + if (!entry.Name.ToLower().EndsWith(".nes")) continue; + + var buffer = new byte[1024]; + MemoryStream output = new MemoryStream(); + while (true) + { + var size = zip.Read(buffer, 0, buffer.Length); + if (size == 0) break; + else output.Write(buffer, 0, size); + } + output.Flush(); + return output.ToArray(); + } + } + else + { + return bytes; + } + + throw new Exception("Not Valid Rom Data"); + } + + private IEnumerator DownloadRemoteRom(Action callback) + { + UnityWebRequest uwr = UnityWebRequest.Get($"{AppAxibugEmuOnline.httpAPI.DownSite}/{webData.url}"); + yield return uwr.SendWebRequest(); + + if (uwr.result != UnityWebRequest.Result.Success) + { + callback(null); + } + else + { + callback(uwr.downloadHandler.data); + } + } + + public void SetWebData(HttpAPI.Resp_RomInfo resp_RomInfo) + { + webData = resp_RomInfo; + fileName = Path.GetFileName(webData.url); + fileName = System.Net.WebUtility.UrlDecode(fileName); + + if (File.Exists(LocalFilePath)) + { + var fileBytes = File.ReadAllBytes(LocalFilePath); + var localHash = RomLib.CalcHash(fileBytes); + + hasLocalFile = localHash == webData.hash; + if (!hasLocalFile) + File.Delete(LocalFilePath); + } + else + { + hasLocalFile = false; + } + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomFile.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomFile.cs.meta new file mode 100644 index 0000000..f8eb032 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomFile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a876afb939308548a7b4bdc53a3f6df +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomLib.cs b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomLib.cs new file mode 100644 index 0000000..94047f2 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomLib.cs @@ -0,0 +1,64 @@ +using AxibugEmuOnline.Client.ClientCore; +using System; +using System.Collections.Generic; + +namespace AxibugEmuOnline.Client +{ + public class RomLib + { + private Dictionary nesRomFileIdMapper = new Dictionary(); + private Dictionary nesRomFileNameMapper = new Dictionary(); + + public RomFile GetNesRomFile(string romFileName) + { + nesRomFileNameMapper.TryGetValue(romFileName, out RomFile romFile); + return romFile; + } + + public void GetNesRomFile(int page, int pageSize, Action> callback) + { + AppAxibugEmuOnline.httpAPI.GetNesRomList((romList) => + { + if (romList == null) + { + callback.Invoke(null); + } + else + { + List result = new List(); + for (int i = 0; i < romList.gameList.Count; i++) + { + var webData = romList.gameList[i]; + + nesRomFileIdMapper.TryGetValue(webData.id, out var targetRomFile); + if (targetRomFile == null) + { + targetRomFile = new RomFile(EnumPlatform.NES); + targetRomFile.SetWebData(webData); + nesRomFileIdMapper[webData.id] = targetRomFile; + + nesRomFileNameMapper[targetRomFile.FileName] = targetRomFile; + } + + result.Add(targetRomFile); + } + + callback(result); + } + }, page, pageSize); + } + + public static string CalcHash(byte[] data) + { + return string.Empty; //todo : 等待远程仓库敲定hash算法 + //var hashBytes = MD5.Create().ComputeHash(data); + //StringBuilder sb = new StringBuilder(); + //foreach (byte b in hashBytes) + //{ + // sb.Append(b.ToString("x2")); + //} + + //return sb.ToString(); + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomLib.cs.meta b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomLib.cs.meta new file mode 100644 index 0000000..b4a2ea5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/Manager/RomLib/RomLib.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8b1860dd3aacece40a93ae8273fc71b1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreSupporter.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreSupporter.cs index ae766d6..99e195b 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreSupporter.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreSupporter.cs @@ -26,8 +26,10 @@ namespace AxibugEmuOnline.Client { try { - var stream = File.Open($"{RomDirectoryPath}/{fname}", FileMode.Open); - return stream; + var romFile = AppAxibugEmuOnline.romLib.GetNesRomFile(fname); + var bytes = romFile.GetRomFileData(); + Debug.Log($"Open {romFile.Alias}"); + return new MemoryStream(bytes); } catch (Exception ex) { diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs index 1c8d76d..5341fbb 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs @@ -1,6 +1,7 @@ using AxibugEmuOnline.Client.ClientCore; using System; using System.IO; +using System.Linq; using System.Xml.Linq; using UnityEngine; using VirtualNes.Core; @@ -15,22 +16,14 @@ namespace AxibugEmuOnline.Client public VideoProvider VideoProvider; public AudioProvider AudioProvider; -#if UNITY_EDITOR - public string RomName; -#endif - private void Start() { Application.targetFrameRate = 60; VideoProvider.NesEmu = this; AudioProvider.NesEmu = this; - -#if UNITY_EDITOR - StartGame(RomName); -#endif } - public void StartGame(string romName) + public void StartGame(RomFile rom) { StopGame(); @@ -39,7 +32,7 @@ namespace AxibugEmuOnline.Client try { - NesCore = new NES(romName); + NesCore = new NES(rom.FileName); } catch (Exception ex) { @@ -92,6 +85,38 @@ namespace AxibugEmuOnline.Client UnityEditor.EditorUtility.SetDirty(db); UnityEditor.AssetDatabase.SaveAssets(); } + + [ContextMenu("LoadRom")] + public void LoadRom() + { + AppAxibugEmuOnline.romLib.GetNesRomFile(0, 10, (romFiles) => + { + if (romFiles == null) return; + + var file = romFiles[2]; + + if (file.FileReady) + { + StartGame(file); + } + else + { + file.BeginDownload(); + Action action = null; + action = () => + { + file.OnDownloadOver -= action; + if (!file.FileReady) + { + throw new Exception("Download Failed"); + } + StartGame(file); + }; + file.OnDownloadOver += action; + } + + }); + } #endif } } diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs index 4fa0d3e..70ade97 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VideoProvider.cs @@ -2,7 +2,6 @@ using System; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.UI; -using VirtualNes.Core; namespace AxibugEmuOnline.Client { @@ -30,7 +29,8 @@ namespace AxibugEmuOnline.Client GCHandle handle = GCHandle.Alloc(wrapTexBuffer, GCHandleType.Pinned); // ȡָ wrapTexBufferPointer = handle.AddrOfPinnedObject(); - + + Image.texture = wrapTex; Image.material.SetTexture("_MainTex", wrapTex); TexBufferSize = wrapTexBuffer.Length * 4; @@ -43,9 +43,9 @@ namespace AxibugEmuOnline.Client uint colorRaw = palRaw[i]; var argbColor = BitConverter.GetBytes(colorRaw); Color temp = Color.white; - temp.r = argbColor[2]/255f; - temp.g = argbColor[1]/255f; - temp.b = argbColor[0]/255f; + temp.r = argbColor[2] / 255f; + temp.g = argbColor[1] / 255f; + temp.b = argbColor[0] / 255f; temp.a = 1; pPal.SetPixel(i, 0, temp); } diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VirtuaNesDraw.shader b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VirtuaNesDraw.shader index 99400dc..63fad39 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VirtuaNesDraw.shader +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/VirtuaNesDraw.shader @@ -105,13 +105,13 @@ float start= 8.0/272.0; float end = (272.0-8.0)/272.0; - //mapUV.x = lerp(start,end, mapUV.x); + mapUV.x = lerp(start,end, mapUV.x); half4 color = tex2D(_MainTex,mapUV); float rawIndex = color.b; - color = tex2D(_PalTex,float2(rawIndex,0.5)); + color = tex2D(_PalTex,float2(rawIndex,0.5)); #ifdef UNITY_UI_CLIP_RECT color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_FME7.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_FME7.cs index 2871744..a7d59ed 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_FME7.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_FME7.cs @@ -1,28 +1,401 @@ using System; +using static VirtualNes.Core.APU_INTERNAL; +using System.Net; namespace VirtualNes.Core { public class APU_FME7 : APU_INTERFACE { + // Envelope tables + byte[] envelope_pulse0 = { + 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00 + }; + byte[] envelope_pulse1 = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x00 + }; + byte[] envelope_pulse2 = { + 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x1F + }; + byte[] envelope_pulse3 = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x1F + }; + sbyte[] envstep_pulse = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0 + }; + + byte[] envelope_sawtooth0 = { + 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00 + }; + byte[] envelope_sawtooth1 = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + sbyte[] envstep_sawtooth = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, -15 + }; + + byte[] envelope_triangle0 = { + 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + byte[] envelope_triangle1 = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00 + }; + sbyte[] envstep_triangle = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, -31 + }; + + byte[][] envelope_table; + sbyte[][] envstep_table; + + ENVELOPE envelope = new ENVELOPE(); + NOISE noise = new NOISE(); + CHANNEL[] op = new CHANNEL[3] { new CHANNEL(), new CHANNEL(), new CHANNEL() }; + byte address; + int[] vol_table = new int[0x20]; + int cycle_rate; + + float cpu_clock; + + public APU_FME7() + { + envelope_table = new byte[16][] + { + envelope_pulse0, envelope_pulse0, envelope_pulse0, envelope_pulse0, + envelope_pulse1, envelope_pulse1, envelope_pulse1, envelope_pulse1, + envelope_sawtooth0, envelope_pulse0, envelope_triangle0, envelope_pulse2, + envelope_sawtooth1, envelope_pulse3, envelope_triangle1, envelope_pulse1 + }; + + envstep_table = new sbyte[16][] + { + envstep_pulse, envstep_pulse, envstep_pulse, envstep_pulse, + envstep_pulse, envstep_pulse, envstep_pulse, envstep_pulse, + envstep_sawtooth, envstep_pulse, envstep_triangle, envstep_pulse, + envstep_sawtooth, envstep_pulse, envstep_triangle, envstep_pulse + }; + + Reset(APU_CLOCK, 22050); + } + public override void Reset(float fClock, int nRate) { - //todo : 实现 + int i; + + envelope.ZeroMemory(); + noise.ZeroMemory(); + + foreach (var item in op) + { + item.ZeroMemory(); + } + + envelope.envtbl = envelope_table[0]; + envelope.envstep = envstep_table[0]; + + noise.noiserange = 1; + noise.noiseout = 0xFF; + + address = 0; + + // Volume to voltage + double @out = 0x1FFF; + for (i = 31; i > 1; i--) + { + vol_table[i] = (int)(@out + 0.5); + @out /= 1.188502227; /* = 10 ^ (1.5/20) = 1.5dB */ + } + vol_table[1] = 0; + vol_table[0] = 0; + + Setup(fClock, nRate); } public override void Setup(float fClock, int nRate) { - //todo : 实现 + cpu_clock = fClock; + cycle_rate = (int)((fClock / 16.0f) * (1 << 16) / nRate); } public override void Write(ushort addr, byte data) { - //todo : 实现 + if (addr == 0xC000) + { + address = data; + } + else if (addr == 0xE000) + { + byte chaddr = address; + switch (chaddr) + { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + { + CHANNEL ch = op[chaddr >> 1]; + ch.reg[chaddr & 0x01] = data; + ch.freq = INT2FIX(((ch.reg[1] & 0x0F) << 8) + ch.reg[0] + 1); + } + break; + case 0x06: + noise.freq = INT2FIX((data & 0x1F) + 1); + break; + case 0x07: + { + for (byte i = 0; i < 3; i++) + { + op[i].enable = (byte)(data & (1 << i)); + op[i].noise_on = (byte)(data & (8 << i)); + } + } + break; + case 0x08: + case 0x09: + case 0x0A: + { + CHANNEL ch = op[chaddr & 3]; + ch.reg[2] = data; + ch.env_on = (byte)(data & 0x10); + ch.volume = (byte)((data & 0x0F) * 2); + } + break; + case 0x0B: + case 0x0C: + envelope.reg[chaddr - 0x0B] = data; + envelope.freq = INT2FIX(((envelope.reg[1] & 0x0F) << 8) + envelope.reg[0] + 1); + break; + case 0x0D: + envelope.envtbl = envelope_table[data & 0x0F]; + envelope.envstep = envstep_table[data & 0x0F]; + envelope.envadr = 0; + break; + } + } } public override int Process(int channel) { - //todo : 实现 + if (channel < 3) + { + return ChannelRender(op[channel]); + } + else if (channel == 3) + { + // 必ずch3を1回呼んでからch0-2を呼ぶ事 + EnvelopeRender(); + NoiseRender(); + } + return 0; } + + public override int GetFreq(int channel) + { + if (channel < 3) + { + CHANNEL ch = op[channel]; + + if (ch.enable != 0 || ch.freq == 0) + return 0; + if (ch.env_on != 0) + { + if (envelope.volume == 0) + return 0; + } + else + { + if (ch.volume == 0) + return 0; + } + + return (int)(256.0f * cpu_clock / (FIX2INT(ch.freq) * 16.0f)); + } + + return 0; + } + + void EnvelopeRender() + { + if (envelope.freq == 0) + return; + envelope.phaseacc -= cycle_rate; + if (envelope.phaseacc >= 0) + return; + while (envelope.phaseacc < 0) + { + envelope.phaseacc += envelope.freq; + envelope.envadr += envelope.envstep[envelope.envadr]; + } + envelope.volume = envelope.envtbl[envelope.envadr]; + } + void NoiseRender() + { + if (noise.freq == 0) + return; + noise.phaseacc -= cycle_rate; + if (noise.phaseacc >= 0) + return; + while (noise.phaseacc < 0) + { + noise.phaseacc += noise.freq; + if (((noise.noiserange + 1) & 0x02) != 0) + noise.noiseout = (byte)(~noise.noiseout); + if ((noise.noiserange & 0x01) != 0) + noise.noiserange ^= 0x28000; + noise.noiserange >>= 1; + } + } + + int ChannelRender(CHANNEL ch) + { + int output, volume; + + if (ch.enable != 0) + return 0; + if (ch.freq == 0) + return 0; + + ch.phaseacc -= cycle_rate; + while (ch.phaseacc < 0) + { + ch.phaseacc += ch.freq; + ch.adder++; + } + + output = volume = 0; + volume = ch.env_on != 0 ? vol_table[envelope.volume] : vol_table[ch.volume + 1]; + + if ((ch.adder & 0x01) != 0) + { + output += volume; + } + else + { + output -= volume; + } + if (ch.noise_on == 0) + { + if (noise.noiseout != 0) + output += volume; + else + output -= volume; + } + + ch.output_vol = output; + + return ch.output_vol; + } + + public class ENVELOPE + { + public byte[] reg = new byte[3]; + public byte volume; + + public int freq; + public int phaseacc; + public int envadr; + + public byte[] envtbl; + public sbyte[] envstep; + + public void ZeroMemory() + { + Array.Clear(reg, 0, 3); + volume = 0; + freq = 0; + phaseacc = 0; + envadr = 0; + envtbl = null; + envstep = null; + } + } + + public class NOISE + { + public int freq; + public int phaseacc; + public int noiserange; + public byte noiseout; + + public void ZeroMemory() + { + freq = 0; phaseacc = 0; noiserange = 0; noiseout = 0; + } + } + + public class CHANNEL + { + public byte[] reg = new byte[3]; + public byte enable; + public byte env_on; + public byte noise_on; + public byte adder; + public byte volume; + + public int freq; + public int phaseacc; + + public int output_vol; + + public void ZeroMemory() + { + Array.Clear(reg, 0, reg.Length); + enable = 0; + env_on = 0; + noise_on = 0; + adder = 0; + volume = 0; freq = 0; + phaseacc = 0; + output_vol = 0; + } + } } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs index e718c66..63a37f9 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs @@ -470,49 +470,6 @@ namespace VirtualNes.Core // Start if (state.HasButton(no, EnumButtonType.START)) bit |= 1 << 3; - // A rapid setup - if ((bit & (1 << 8)) != 0) - { - int spd = Supporter.Config.controller.nRapid[no][0]; - if (spd >= 3) spd = 3; - - int[] tbl = rentbl[spd]; - - if (padcnt[no][0] >= renmask[spd]) - padcnt[no][0] = 0; - - if ((tbl[padcnt[no][0]]) != 0) - bit |= (1 << 0); - else - bit = (byte)(bit & ~(1 << 0)); - - padcnt[no][0]++; - } - else - { - padcnt[no][0] = 0; - } - // B rapid setup - if ((bit & (1 << 9)) != 0) - { - int spd = Supporter.Config.controller.nRapid[no][1]; - if (spd >= 3) spd = 3; - int[] tbl = rentbl[spd]; - - if (padcnt[no][1] >= renmask[spd]) - padcnt[no][1] = 0; - - if ((tbl[padcnt[no][1]]) != 0) - bit |= (1 << 1); - else - bit = (byte)(bit & ~(1 << 1)); - - padcnt[no][1]++; - } - else - { - padcnt[no][1] = 0; - } return (byte)(bit & 0xFF); }