commit 1686838e03d51bab0a2f81b0149d3f87a294ff57 Author: Alex Date: Tue Sep 15 14:59:29 2026 +0300 Add OPL4 playback and analyzer support diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ed8e2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +src_sdcc/build +Debuger/obj +Debuger/bin +Debuger/*.vgz +Debuger/*.vgm +Debuger/dumps +**/*.img diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..491b864 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.errorSquiggles": "disabled" +} diff --git a/Debuger/Cpu/CpuFlags.cs b/Debuger/Cpu/CpuFlags.cs new file mode 100644 index 0000000..e0d59d9 --- /dev/null +++ b/Debuger/Cpu/CpuFlags.cs @@ -0,0 +1,119 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * + */ + +namespace ZXMAK2.Engine.Cpu +{ + /// + /// Represent masks for the Z80 flags register + /// + public static class CpuFlags + { + #region Flags + + /// + /// Sign Flag + /// [Set if the 2-complement value is negative (copy of MSB)] + /// + public const byte S = 0x80; + + /// + /// Zero Flag + /// [Set if the value is zero] + /// + public const byte Z = 0x40; + + /// + /// Undocumented flag F5 + /// + public const byte F5 = 0x20; + + /// + /// Half Carry Flag + /// [Carry from bit 3 to bit 4] + /// + public const byte H = 0x10; + + /// + /// Undocumented flag F3 + /// + public const byte F3 = 0x08; + + /// + /// P/V - Parity/Overflow Flag + /// [Parity set if even number of bits set. + /// Overflow set if the 2-complement result does not fit in the register] + /// + public const byte P = 0x04; + + /// + /// Add/Subtract Flag + /// [Set if the last operation was a subtraction] + /// + public const byte N = 0x02; + + /// + /// Carry Flag + /// [Set if the result did not fit in the register] + /// + public const byte C = 0x01; + + #endregion Flags + + + #region Inverted Masks + + public const byte NotS = S ^ 0xFF; + public const byte NotZ = Z ^ 0xFF; + public const byte NotF5 = F5 ^ 0xFF; + public const byte NotH = H ^ 0xFF; + public const byte NotF3 = F3 ^ 0xFF; + public const byte NotP = P ^ 0xFF; + public const byte NotN = N ^ 0xFF; + public const byte NotC = C ^ 0xFF; + + #endregion Inverted Masks + + + #region Internal flag sets (used by engine) + + internal const byte F3F5 = F3 | F5; + internal const byte SF3F5 = S | F3 | F5; + internal const byte PCF3F5 = P | C | F3 | F5; + internal const byte HNCF3F5 = H | N | C | F3 | F5; + internal const byte HPNF3F5 = H | P | N | F3 | F5; + internal const byte HC = H | C; + internal const byte SZP = S | Z | P; + internal const byte SZPC = S | Z | P | C; + internal const byte HN = H | N; + + internal const byte NotF3F5 = F3F5 ^ 0xFF; + internal const byte NotSF3F5 = SF3F5 ^ 0xFF; + internal const byte NotPCF3F5 = PCF3F5 ^ 0xFF; + internal const byte NotHNCF3F5 = HNCF3F5 ^ 0xFF; + internal const byte NotHPNF3F5 = HPNF3F5 ^ 0xFF; + internal const byte NotHC = HC ^ 0xFF; + internal const byte NotSZP = SZP ^ 0xFF; + internal const byte NotSZPC = SZPC ^ 0xFF; + internal const byte NotHN = HN ^ 0xFF; + + #endregion Internal flag sets (used by engine) + } +} diff --git a/Debuger/Cpu/CpuModeEx.cs b/Debuger/Cpu/CpuModeEx.cs new file mode 100644 index 0000000..0281c44 --- /dev/null +++ b/Debuger/Cpu/CpuModeEx.cs @@ -0,0 +1,31 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * + */ + + +namespace ZXMAK2.Engine.Cpu +{ + public enum CpuModeEx + { + None = 0, + Cb, + Ed, + } +} diff --git a/Debuger/Cpu/CpuModeIndex.cs b/Debuger/Cpu/CpuModeIndex.cs new file mode 100644 index 0000000..80cd444 --- /dev/null +++ b/Debuger/Cpu/CpuModeIndex.cs @@ -0,0 +1,31 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * + */ + + +namespace ZXMAK2.Engine.Cpu +{ + public enum CpuModeIndex + { + None = 0, + Ix, + Iy, + } +} diff --git a/Debuger/Cpu/CpuRegs.cs b/Debuger/Cpu/CpuRegs.cs new file mode 100644 index 0000000..355d8ff --- /dev/null +++ b/Debuger/Cpu/CpuRegs.cs @@ -0,0 +1,168 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * Description: Z80 CPU Emulator [registers part] + * Date: 18.03.2007 + * + */ +using System; +using System.Runtime.InteropServices; +using ZXMAK2.Engine.Cpu.Processor; + + +namespace ZXMAK2.Engine.Cpu +{ + [StructLayout(LayoutKind.Explicit)] + public sealed class CpuRegs + { + [FieldOffset(0)] + public ushort AF = 0; + [FieldOffset(2)] + public ushort BC = 0; + [FieldOffset(4)] + public ushort DE = 0; + [FieldOffset(6)] + public ushort HL = 0; + [FieldOffset(8)] + public ushort _AF = 0; + [FieldOffset(10)] + public ushort _BC = 0; + [FieldOffset(12)] + public ushort _DE = 0; + [FieldOffset(14)] + public ushort _HL = 0; + [FieldOffset(16)] + public ushort IX = 0; + [FieldOffset(18)] + public ushort IY = 0; + [FieldOffset(20)] + public ushort IR = 0; + [FieldOffset(22)] + public ushort PC = 0; + [FieldOffset(24)] + public ushort SP = 0; + [FieldOffset(26)] + public ushort MW = 0; // MEMPTR + [FieldOffset(28)] + public byte Q = 0; // TODO: Q flags + + + [FieldOffset(1)] + public byte A; + [FieldOffset(0)] + public byte F; + [FieldOffset(3)] + public byte B; + [FieldOffset(2)] + public byte C; + [FieldOffset(5)] + public byte D; + [FieldOffset(4)] + public byte E; + [FieldOffset(7)] + public byte H; + [FieldOffset(6)] + public byte L; + [FieldOffset(17)] + public byte XH; + [FieldOffset(16)] + public byte XL; + [FieldOffset(19)] + public byte YH; + [FieldOffset(18)] + public byte YL; + [FieldOffset(21)] + public byte I; + [FieldOffset(20)] + public byte R; + + [FieldOffset(27)] + public byte MH; + [FieldOffset(26)] + public byte ML; + + [FieldOffset(23)] + public byte PCH = 0; + [FieldOffset(22)] + public byte PCL = 0; + + + #region Access Lamda Generators + + // Access field from an object context + // makes one native instruction lesser + + internal Func CreateRegGetter(int r) + { + return r switch + { + CpuRegId.B => () => B, + CpuRegId.C => () => C, + CpuRegId.D => () => D, + CpuRegId.E => () => E, + CpuRegId.H => () => H, + CpuRegId.L => () => L, + CpuRegId.A => () => A, + CpuRegId.F => () => F, + _ => throw new ArgumentOutOfRangeException(nameof(r)), + }; + } + + internal Action CreateRegSetter(int r) + { + return r switch + { + CpuRegId.B => arg => B = arg, + CpuRegId.C => arg => C = arg, + CpuRegId.D => arg => D = arg, + CpuRegId.E => arg => E = arg, + CpuRegId.H => arg => H = arg, + CpuRegId.L => arg => L = arg, + CpuRegId.A => arg => A = arg, + CpuRegId.F => arg => F = arg, + _ => throw new ArgumentOutOfRangeException(nameof(r)), + }; + } + + internal Func CreatePairGetter(int rr) + { + return rr switch + { + CpuRegId.Bc => () => BC, + CpuRegId.De => () => DE, + CpuRegId.Hl => () => HL, + CpuRegId.Sp => () => SP, + _ => throw new ArgumentOutOfRangeException(nameof(rr)), + }; + } + + internal Action CreatePairSetter(int rr) + { + return rr switch + { + CpuRegId.Bc => arg => BC = arg, + CpuRegId.De => arg => DE = arg, + CpuRegId.Hl => arg => HL = arg, + CpuRegId.Sp => arg => SP = arg, + _ => throw new ArgumentOutOfRangeException(nameof(rr)), + }; + } + + #endregion Access Lamda Generators + } +} diff --git a/Debuger/Cpu/CpuType.cs b/Debuger/Cpu/CpuType.cs new file mode 100644 index 0000000..37fd61c --- /dev/null +++ b/Debuger/Cpu/CpuType.cs @@ -0,0 +1,32 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * + */ + + +namespace ZXMAK2.Engine.Cpu +{ + public enum CpuType + { + ZILOG_NMOS = 0, + ZILOG_CMOS = 1, + NEC_NMOS = 2, // NEC + ST_CMOS = 3, // Z84? + } +} diff --git a/Debuger/Cpu/CpuUnit.cs b/Debuger/Cpu/CpuUnit.cs new file mode 100644 index 0000000..49b8856 --- /dev/null +++ b/Debuger/Cpu/CpuUnit.cs @@ -0,0 +1,29 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * + */ +using ZXMAK2.Engine.Cpu.Processor; + + +namespace ZXMAK2.Engine.Cpu +{ + public sealed class CpuUnit : Z80Cpu + { + } +} diff --git a/Debuger/Cpu/Processor/CpuRegId.cs b/Debuger/Cpu/Processor/CpuRegId.cs new file mode 100644 index 0000000..8734584 --- /dev/null +++ b/Debuger/Cpu/Processor/CpuRegId.cs @@ -0,0 +1,41 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * + */ + + +namespace ZXMAK2.Engine.Cpu.Processor +{ + internal static class CpuRegId + { + public const int Bc = 0; + public const int De = 1; + public const int Hl = 2; + public const int Sp = 3; + + public const int B = 0; + public const int C = 1; + public const int D = 2; + public const int E = 3; + public const int H = 4; + public const int L = 5; + public const int F = 6; + public const int A = 7; + } +} diff --git a/Debuger/Cpu/Processor/CpuTables.cs b/Debuger/Cpu/Processor/CpuTables.cs new file mode 100644 index 0000000..4a9dace --- /dev/null +++ b/Debuger/Cpu/Processor/CpuTables.cs @@ -0,0 +1,681 @@ +/* + * Copyright 2015 Alex Makeev + * Copyright 2009 SMT (ALU tables) + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * -------------------------------------------------------------------- + * Most of the tables based on the source code of UnrealSpeccy emulator + * + */ + + +namespace ZXMAK2.Engine.Cpu.Processor +{ + public static class CpuTables + { + #region .ctor + + static CpuTables() + { + Logf = CreateLogf(); + Adcf = CreateAdcf(); + + // === make_rot === credits to SMT (Unreal) + Rlcaf = new byte[0x100]; + Rrcaf = new byte[0x100]; + for (var i = 0; i < 0x100; i++) + { + Rlcaf[i] = (byte)(Rlcf[i] & 0x3B); // rra,rla uses same tables + } + for (var i = 0; i < 0x100; i++) + { + Rrcaf[i] = (byte)(Rrcf[i] & 0x3B); + } + + // === make_sbc === credits to SMT (Unreal) + Sbcf = new byte[0x20000]; + Cpf = new byte[0x10000]; + Cpf8b = new byte[0x10000]; + for (var c = 0; c < 2; c++) + { + for (var x = 0; x < 0x100; x++) + { + for (var y = 0; y < 0x100; y++) + { + var res = x - y - c; + var fl = res & CpuFlags.SF3F5; + if ((res & 0xFF) == 0) fl |= CpuFlags.Z; + if ((res & 0x10000) != 0) fl |= CpuFlags.C; + var r = (sbyte)x - (sbyte)y - c; + if (r >= 0x80 || r < -0x80) fl |= CpuFlags.P; + if ((((x & 0x0F) - (res & 0x0F) - c) & 0x10) != 0) fl |= CpuFlags.H; + fl |= CpuFlags.N; + Sbcf[c * 0x10000 + x * 0x100 + y] = (byte)fl; + } + } + } + for (var i = 0; i < 0x10000; i++) + { + Cpf[i] = (byte)((Sbcf[i] & CpuFlags.NotF3F5) | (i & CpuFlags.F3F5)); + var val = (byte)((i >> 8) - (i & 0xFF) - ((Sbcf[i] & CpuFlags.H) >> 4)); + Cpf8b[i] = (byte)((Sbcf[i] & CpuFlags.NotPCF3F5) + (val & CpuFlags.F3) + ((val << 4) & CpuFlags.F5)); + } + + Parity = new byte[256]; + for (var i = 0; i < Parity.Length; i++) + { + // Bit Twiddling Hacks: Compute parity in parallel, 8-bit + var p = (0x6996 >> ((i ^ (i >> 4)) & 0xf)) & 1; + Parity[i] = p == 0 ? CpuFlags.P : (byte)0; + } + } + + private static byte[] CreateLogf() + { + // === make_log === credits to SMT (Unreal) + var log_f = new byte[0x100]; + for (var x = 0; x < 0x100; x++) + { + var fl = x & CpuFlags.SF3F5; + var p = CpuFlags.P; + for (var i = 0x80; i != 0; i /= 2) + { + if ((x & i) == i) p ^= CpuFlags.P; + } + log_f[x] = (byte)(fl | p); + } + log_f[0] |= CpuFlags.Z; + return log_f; + } + + private static byte[] CreateAdcf() + { + // === make_adc === credits to SMT (Unreal) + var adcf = new byte[0x20000]; + for (var c = 0; c < 2; c++) + { + for (var x = 0; x < 0x100; x++) + { + for (var y = 0; y < 0x100; y++) + { + uint res = (uint)(x + y + c); + byte fl = 0; + if ((res & 0xFF) == 0) fl |= CpuFlags.Z; + fl |= (byte)(res & CpuFlags.SF3F5); + if (res >= 0x100) fl |= CpuFlags.C; + if ((((x & 0x0F) + (y & 0x0F) + c) & 0x10) != 0) fl |= CpuFlags.H; + int ri = (sbyte)x + (sbyte)y + c; + if (ri >= 0x80 || ri <= -0x81) fl |= CpuFlags.P; + adcf[c * 0x10000 + x * 0x100 + y] = fl; + } + } + } + return adcf; + } + + #endregion .ctor + + + public static readonly byte[] Logf; + public static readonly byte[] Adcf; // flags for adc and add + public static readonly byte[] Rlcaf; // flags for RLCA + public static readonly byte[] Rrcaf; // flags for RRCA + public static readonly byte[] Sbcf; // flags for sub and sbc + public static readonly byte[] Cpf; // flags for cp + public static readonly byte[] Cpf8b; // flags for cpi(r)/cpd(r) + public static readonly byte[] Parity; // parity calculator + + + public static readonly byte[] Rlcf = + // credits to SMT (Unreal) + [ // for rlc r. may be for rlca (0x3B mask) + 0x44,0x00,0x00,0x04,0x08,0x0c,0x0c,0x08 + ,0x00,0x04,0x04,0x00,0x0c,0x08,0x08,0x0c + ,0x20,0x24,0x24,0x20,0x2c,0x28,0x28,0x2c + ,0x24,0x20,0x20,0x24,0x28,0x2c,0x2c,0x28 + ,0x00,0x04,0x04,0x00,0x0c,0x08,0x08,0x0c + ,0x04,0x00,0x00,0x04,0x08,0x0c,0x0c,0x08 + ,0x24,0x20,0x20,0x24,0x28,0x2c,0x2c,0x28 + ,0x20,0x24,0x24,0x20,0x2c,0x28,0x28,0x2c + ,0x80,0x84,0x84,0x80,0x8c,0x88,0x88,0x8c + ,0x84,0x80,0x80,0x84,0x88,0x8c,0x8c,0x88 + ,0xa4,0xa0,0xa0,0xa4,0xa8,0xac,0xac,0xa8 + ,0xa0,0xa4,0xa4,0xa0,0xac,0xa8,0xa8,0xac + ,0x84,0x80,0x80,0x84,0x88,0x8c,0x8c,0x88 + ,0x80,0x84,0x84,0x80,0x8c,0x88,0x88,0x8c + ,0xa0,0xa4,0xa4,0xa0,0xac,0xa8,0xa8,0xac + ,0xa4,0xa0,0xa0,0xa4,0xa8,0xac,0xac,0xa8 + ,0x01,0x05,0x05,0x01,0x0d,0x09,0x09,0x0d + ,0x05,0x01,0x01,0x05,0x09,0x0d,0x0d,0x09 + ,0x25,0x21,0x21,0x25,0x29,0x2d,0x2d,0x29 + ,0x21,0x25,0x25,0x21,0x2d,0x29,0x29,0x2d + ,0x05,0x01,0x01,0x05,0x09,0x0d,0x0d,0x09 + ,0x01,0x05,0x05,0x01,0x0d,0x09,0x09,0x0d + ,0x21,0x25,0x25,0x21,0x2d,0x29,0x29,0x2d + ,0x25,0x21,0x21,0x25,0x29,0x2d,0x2d,0x29 + ,0x85,0x81,0x81,0x85,0x89,0x8d,0x8d,0x89 + ,0x81,0x85,0x85,0x81,0x8d,0x89,0x89,0x8d + ,0xa1,0xa5,0xa5,0xa1,0xad,0xa9,0xa9,0xad + ,0xa5,0xa1,0xa1,0xa5,0xa9,0xad,0xad,0xa9 + ,0x81,0x85,0x85,0x81,0x8d,0x89,0x89,0x8d + ,0x85,0x81,0x81,0x85,0x89,0x8d,0x8d,0x89 + ,0xa5,0xa1,0xa1,0xa5,0xa9,0xad,0xad,0xa9 + ,0xa1,0xa5,0xa5,0xa1,0xad,0xa9,0xa9,0xad + ]; + + public static readonly byte[] Rrcf = + // credits to SMT (Unreal) + [ // rrc r. may be for rlca (0x3B mask) + 0x44,0x81,0x00,0x85,0x00,0x85,0x04,0x81 + ,0x00,0x85,0x04,0x81,0x04,0x81,0x00,0x85 + ,0x08,0x8d,0x0c,0x89,0x0c,0x89,0x08,0x8d + ,0x0c,0x89,0x08,0x8d,0x08,0x8d,0x0c,0x89 + ,0x00,0x85,0x04,0x81,0x04,0x81,0x00,0x85 + ,0x04,0x81,0x00,0x85,0x00,0x85,0x04,0x81 + ,0x0c,0x89,0x08,0x8d,0x08,0x8d,0x0c,0x89 + ,0x08,0x8d,0x0c,0x89,0x0c,0x89,0x08,0x8d + ,0x20,0xa5,0x24,0xa1,0x24,0xa1,0x20,0xa5 + ,0x24,0xa1,0x20,0xa5,0x20,0xa5,0x24,0xa1 + ,0x2c,0xa9,0x28,0xad,0x28,0xad,0x2c,0xa9 + ,0x28,0xad,0x2c,0xa9,0x2c,0xa9,0x28,0xad + ,0x24,0xa1,0x20,0xa5,0x20,0xa5,0x24,0xa1 + ,0x20,0xa5,0x24,0xa1,0x24,0xa1,0x20,0xa5 + ,0x28,0xad,0x2c,0xa9,0x2c,0xa9,0x28,0xad + ,0x2c,0xa9,0x28,0xad,0x28,0xad,0x2c,0xa9 + ,0x00,0x85,0x04,0x81,0x04,0x81,0x00,0x85 + ,0x04,0x81,0x00,0x85,0x00,0x85,0x04,0x81 + ,0x0c,0x89,0x08,0x8d,0x08,0x8d,0x0c,0x89 + ,0x08,0x8d,0x0c,0x89,0x0c,0x89,0x08,0x8d + ,0x04,0x81,0x00,0x85,0x00,0x85,0x04,0x81 + ,0x00,0x85,0x04,0x81,0x04,0x81,0x00,0x85 + ,0x08,0x8d,0x0c,0x89,0x0c,0x89,0x08,0x8d + ,0x0c,0x89,0x08,0x8d,0x08,0x8d,0x0c,0x89 + ,0x24,0xa1,0x20,0xa5,0x20,0xa5,0x24,0xa1 + ,0x20,0xa5,0x24,0xa1,0x24,0xa1,0x20,0xa5 + ,0x28,0xad,0x2c,0xa9,0x2c,0xa9,0x28,0xad + ,0x2c,0xa9,0x28,0xad,0x28,0xad,0x2c,0xa9 + ,0x20,0xa5,0x24,0xa1,0x24,0xa1,0x20,0xa5 + ,0x24,0xa1,0x20,0xa5,0x20,0xa5,0x24,0xa1 + ,0x2c,0xa9,0x28,0xad,0x28,0xad,0x2c,0xa9 + ,0x28,0xad,0x2c,0xa9,0x2c,0xa9,0x28,0xad + ]; + + public static readonly byte[] Incf = + // credits to SMT (Unreal) + [ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08 + ,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x10 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08 + ,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x30 + ,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x28 + ,0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x30 + ,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x28 + ,0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x10 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08 + ,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x10 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08 + ,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x30 + ,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x28 + ,0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x30 + ,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x28 + ,0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x94 + ,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x88 + ,0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x90 + ,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x88 + ,0x88,0x88,0x88,0x88,0x88,0x88,0x88,0xb0 + ,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa8 + ,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xb0 + ,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa8 + ,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0x90 + ,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x88 + ,0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x90 + ,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x88 + ,0x88,0x88,0x88,0x88,0x88,0x88,0x88,0xb0 + ,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa8 + ,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xb0 + ,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa8 + ,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0x50 + ]; + + public static readonly byte[] Decf = + // credits to SMT (Unreal) + [ + 0xba,0x42,0x02,0x02,0x02,0x02,0x02,0x02 + ,0x02,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a + ,0x1a,0x02,0x02,0x02,0x02,0x02,0x02,0x02 + ,0x02,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a + ,0x1a,0x22,0x22,0x22,0x22,0x22,0x22,0x22 + ,0x22,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a + ,0x3a,0x22,0x22,0x22,0x22,0x22,0x22,0x22 + ,0x22,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a + ,0x3a,0x02,0x02,0x02,0x02,0x02,0x02,0x02 + ,0x02,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a + ,0x1a,0x02,0x02,0x02,0x02,0x02,0x02,0x02 + ,0x02,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a + ,0x1a,0x22,0x22,0x22,0x22,0x22,0x22,0x22 + ,0x22,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a + ,0x3a,0x22,0x22,0x22,0x22,0x22,0x22,0x22 + ,0x22,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a + ,0x3e,0x82,0x82,0x82,0x82,0x82,0x82,0x82 + ,0x82,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a + ,0x9a,0x82,0x82,0x82,0x82,0x82,0x82,0x82 + ,0x82,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a + ,0x9a,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2 + ,0xa2,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa + ,0xba,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2 + ,0xa2,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa + ,0xba,0x82,0x82,0x82,0x82,0x82,0x82,0x82 + ,0x82,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a + ,0x9a,0x82,0x82,0x82,0x82,0x82,0x82,0x82 + ,0x82,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a + ,0x9a,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2 + ,0xa2,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa + ,0xba,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2 + ,0xa2,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa + ]; + + public static readonly ushort[] Daaf = + // credits to SMT (converted from Unreal) + [ + 0x0044,0x0100,0x0200,0x0304,0x0400,0x0504,0x0604,0x0700,0x0808,0x090C, + 0x1010,0x1114,0x1214,0x1310,0x1414,0x1510,0x1000,0x1104,0x1204,0x1300, + 0x1404,0x1500,0x1600,0x1704,0x180C,0x1908,0x2030,0x2134,0x2234,0x2330, + 0x2434,0x2530,0x2020,0x2124,0x2224,0x2320,0x2424,0x2520,0x2620,0x2724, + 0x282C,0x2928,0x3034,0x3130,0x3230,0x3334,0x3430,0x3534,0x3024,0x3120, + 0x3220,0x3324,0x3420,0x3524,0x3624,0x3720,0x3828,0x392C,0x4010,0x4114, + 0x4214,0x4310,0x4414,0x4510,0x4000,0x4104,0x4204,0x4300,0x4404,0x4500, + 0x4600,0x4704,0x480C,0x4908,0x5014,0x5110,0x5210,0x5314,0x5410,0x5514, + 0x5004,0x5100,0x5200,0x5304,0x5400,0x5504,0x5604,0x5700,0x5808,0x590C, + 0x6034,0x6130,0x6230,0x6334,0x6430,0x6534,0x6024,0x6120,0x6220,0x6324, + 0x6420,0x6524,0x6624,0x6720,0x6828,0x692C,0x7030,0x7134,0x7234,0x7330, + 0x7434,0x7530,0x7020,0x7124,0x7224,0x7320,0x7424,0x7520,0x7620,0x7724, + 0x782C,0x7928,0x8090,0x8194,0x8294,0x8390,0x8494,0x8590,0x8080,0x8184, + 0x8284,0x8380,0x8484,0x8580,0x8680,0x8784,0x888C,0x8988,0x9094,0x9190, + 0x9290,0x9394,0x9490,0x9594,0x9084,0x9180,0x9280,0x9384,0x9480,0x9584, + 0x9684,0x9780,0x9888,0x998C,0x0055,0x0111,0x0211,0x0315,0x0411,0x0515, + 0x0045,0x0101,0x0201,0x0305,0x0401,0x0505,0x0605,0x0701,0x0809,0x090D, + 0x1011,0x1115,0x1215,0x1311,0x1415,0x1511,0x1001,0x1105,0x1205,0x1301, + 0x1405,0x1501,0x1601,0x1705,0x180D,0x1909,0x2031,0x2135,0x2235,0x2331, + 0x2435,0x2531,0x2021,0x2125,0x2225,0x2321,0x2425,0x2521,0x2621,0x2725, + 0x282D,0x2929,0x3035,0x3131,0x3231,0x3335,0x3431,0x3535,0x3025,0x3121, + 0x3221,0x3325,0x3421,0x3525,0x3625,0x3721,0x3829,0x392D,0x4011,0x4115, + 0x4215,0x4311,0x4415,0x4511,0x4001,0x4105,0x4205,0x4301,0x4405,0x4501, + 0x4601,0x4705,0x480D,0x4909,0x5015,0x5111,0x5211,0x5315,0x5411,0x5515, + 0x5005,0x5101,0x5201,0x5305,0x5401,0x5505,0x5605,0x5701,0x5809,0x590D, + 0x6035,0x6131,0x6231,0x6335,0x6431,0x6535,0x6025,0x6121,0x6221,0x6325, + 0x6421,0x6525,0x6625,0x6721,0x6829,0x692D,0x7031,0x7135,0x7235,0x7331, + 0x7435,0x7531,0x7021,0x7125,0x7225,0x7321,0x7425,0x7521,0x7621,0x7725, + 0x782D,0x7929,0x8091,0x8195,0x8295,0x8391,0x8495,0x8591,0x8081,0x8185, + 0x8285,0x8381,0x8485,0x8581,0x8681,0x8785,0x888D,0x8989,0x9095,0x9191, + 0x9291,0x9395,0x9491,0x9595,0x9085,0x9181,0x9281,0x9385,0x9481,0x9585, + 0x9685,0x9781,0x9889,0x998D,0xA0B5,0xA1B1,0xA2B1,0xA3B5,0xA4B1,0xA5B5, + 0xA0A5,0xA1A1,0xA2A1,0xA3A5,0xA4A1,0xA5A5,0xA6A5,0xA7A1,0xA8A9,0xA9AD, + 0xB0B1,0xB1B5,0xB2B5,0xB3B1,0xB4B5,0xB5B1,0xB0A1,0xB1A5,0xB2A5,0xB3A1, + 0xB4A5,0xB5A1,0xB6A1,0xB7A5,0xB8AD,0xB9A9,0xC095,0xC191,0xC291,0xC395, + 0xC491,0xC595,0xC085,0xC181,0xC281,0xC385,0xC481,0xC585,0xC685,0xC781, + 0xC889,0xC98D,0xD091,0xD195,0xD295,0xD391,0xD495,0xD591,0xD081,0xD185, + 0xD285,0xD381,0xD485,0xD581,0xD681,0xD785,0xD88D,0xD989,0xE0B1,0xE1B5, + 0xE2B5,0xE3B1,0xE4B5,0xE5B1,0xE0A1,0xE1A5,0xE2A5,0xE3A1,0xE4A5,0xE5A1, + 0xE6A1,0xE7A5,0xE8AD,0xE9A9,0xF0B5,0xF1B1,0xF2B1,0xF3B5,0xF4B1,0xF5B5, + 0xF0A5,0xF1A1,0xF2A1,0xF3A5,0xF4A1,0xF5A5,0xF6A5,0xF7A1,0xF8A9,0xF9AD, + 0x0055,0x0111,0x0211,0x0315,0x0411,0x0515,0x0045,0x0101,0x0201,0x0305, + 0x0401,0x0505,0x0605,0x0701,0x0809,0x090D,0x1011,0x1115,0x1215,0x1311, + 0x1415,0x1511,0x1001,0x1105,0x1205,0x1301,0x1405,0x1501,0x1601,0x1705, + 0x180D,0x1909,0x2031,0x2135,0x2235,0x2331,0x2435,0x2531,0x2021,0x2125, + 0x2225,0x2321,0x2425,0x2521,0x2621,0x2725,0x282D,0x2929,0x3035,0x3131, + 0x3231,0x3335,0x3431,0x3535,0x3025,0x3121,0x3221,0x3325,0x3421,0x3525, + 0x3625,0x3721,0x3829,0x392D,0x4011,0x4115,0x4215,0x4311,0x4415,0x4511, + 0x4001,0x4105,0x4205,0x4301,0x4405,0x4501,0x4601,0x4705,0x480D,0x4909, + 0x5015,0x5111,0x5211,0x5315,0x5411,0x5515,0x5005,0x5101,0x5201,0x5305, + 0x5401,0x5505,0x5605,0x5701,0x5809,0x590D,0x6035,0x6131,0x6231,0x6335, + 0x6431,0x6535,0x0046,0x0102,0x0202,0x0306,0x0402,0x0506,0x0606,0x0702, + 0x080A,0x090E,0x0402,0x0506,0x0606,0x0702,0x080A,0x090E,0x1002,0x1106, + 0x1206,0x1302,0x1406,0x1502,0x1602,0x1706,0x180E,0x190A,0x1406,0x1502, + 0x1602,0x1706,0x180E,0x190A,0x2022,0x2126,0x2226,0x2322,0x2426,0x2522, + 0x2622,0x2726,0x282E,0x292A,0x2426,0x2522,0x2622,0x2726,0x282E,0x292A, + 0x3026,0x3122,0x3222,0x3326,0x3422,0x3526,0x3626,0x3722,0x382A,0x392E, + 0x3422,0x3526,0x3626,0x3722,0x382A,0x392E,0x4002,0x4106,0x4206,0x4302, + 0x4406,0x4502,0x4602,0x4706,0x480E,0x490A,0x4406,0x4502,0x4602,0x4706, + 0x480E,0x490A,0x5006,0x5102,0x5202,0x5306,0x5402,0x5506,0x5606,0x5702, + 0x580A,0x590E,0x5402,0x5506,0x5606,0x5702,0x580A,0x590E,0x6026,0x6122, + 0x6222,0x6326,0x6422,0x6526,0x6626,0x6722,0x682A,0x692E,0x6422,0x6526, + 0x6626,0x6722,0x682A,0x692E,0x7022,0x7126,0x7226,0x7322,0x7426,0x7522, + 0x7622,0x7726,0x782E,0x792A,0x7426,0x7522,0x7622,0x7726,0x782E,0x792A, + 0x8082,0x8186,0x8286,0x8382,0x8486,0x8582,0x8682,0x8786,0x888E,0x898A, + 0x8486,0x8582,0x8682,0x8786,0x888E,0x898A,0x9086,0x9182,0x9282,0x9386, + 0x9482,0x9586,0x9686,0x9782,0x988A,0x998E,0x3423,0x3527,0x3627,0x3723, + 0x382B,0x392F,0x4003,0x4107,0x4207,0x4303,0x4407,0x4503,0x4603,0x4707, + 0x480F,0x490B,0x4407,0x4503,0x4603,0x4707,0x480F,0x490B,0x5007,0x5103, + 0x5203,0x5307,0x5403,0x5507,0x5607,0x5703,0x580B,0x590F,0x5403,0x5507, + 0x5607,0x5703,0x580B,0x590F,0x6027,0x6123,0x6223,0x6327,0x6423,0x6527, + 0x6627,0x6723,0x682B,0x692F,0x6423,0x6527,0x6627,0x6723,0x682B,0x692F, + 0x7023,0x7127,0x7227,0x7323,0x7427,0x7523,0x7623,0x7727,0x782F,0x792B, + 0x7427,0x7523,0x7623,0x7727,0x782F,0x792B,0x8083,0x8187,0x8287,0x8383, + 0x8487,0x8583,0x8683,0x8787,0x888F,0x898B,0x8487,0x8583,0x8683,0x8787, + 0x888F,0x898B,0x9087,0x9183,0x9283,0x9387,0x9483,0x9587,0x9687,0x9783, + 0x988B,0x998F,0x9483,0x9587,0x9687,0x9783,0x988B,0x998F,0xA0A7,0xA1A3, + 0xA2A3,0xA3A7,0xA4A3,0xA5A7,0xA6A7,0xA7A3,0xA8AB,0xA9AF,0xA4A3,0xA5A7, + 0xA6A7,0xA7A3,0xA8AB,0xA9AF,0xB0A3,0xB1A7,0xB2A7,0xB3A3,0xB4A7,0xB5A3, + 0xB6A3,0xB7A7,0xB8AF,0xB9AB,0xB4A7,0xB5A3,0xB6A3,0xB7A7,0xB8AF,0xB9AB, + 0xC087,0xC183,0xC283,0xC387,0xC483,0xC587,0xC687,0xC783,0xC88B,0xC98F, + 0xC483,0xC587,0xC687,0xC783,0xC88B,0xC98F,0xD083,0xD187,0xD287,0xD383, + 0xD487,0xD583,0xD683,0xD787,0xD88F,0xD98B,0xD487,0xD583,0xD683,0xD787, + 0xD88F,0xD98B,0xE0A3,0xE1A7,0xE2A7,0xE3A3,0xE4A7,0xE5A3,0xE6A3,0xE7A7, + 0xE8AF,0xE9AB,0xE4A7,0xE5A3,0xE6A3,0xE7A7,0xE8AF,0xE9AB,0xF0A7,0xF1A3, + 0xF2A3,0xF3A7,0xF4A3,0xF5A7,0xF6A7,0xF7A3,0xF8AB,0xF9AF,0xF4A3,0xF5A7, + 0xF6A7,0xF7A3,0xF8AB,0xF9AF,0x0047,0x0103,0x0203,0x0307,0x0403,0x0507, + 0x0607,0x0703,0x080B,0x090F,0x0403,0x0507,0x0607,0x0703,0x080B,0x090F, + 0x1003,0x1107,0x1207,0x1303,0x1407,0x1503,0x1603,0x1707,0x180F,0x190B, + 0x1407,0x1503,0x1603,0x1707,0x180F,0x190B,0x2023,0x2127,0x2227,0x2323, + 0x2427,0x2523,0x2623,0x2727,0x282F,0x292B,0x2427,0x2523,0x2623,0x2727, + 0x282F,0x292B,0x3027,0x3123,0x3223,0x3327,0x3423,0x3527,0x3627,0x3723, + 0x382B,0x392F,0x3423,0x3527,0x3627,0x3723,0x382B,0x392F,0x4003,0x4107, + 0x4207,0x4303,0x4407,0x4503,0x4603,0x4707,0x480F,0x490B,0x4407,0x4503, + 0x4603,0x4707,0x480F,0x490B,0x5007,0x5103,0x5203,0x5307,0x5403,0x5507, + 0x5607,0x5703,0x580B,0x590F,0x5403,0x5507,0x5607,0x5703,0x580B,0x590F, + 0x6027,0x6123,0x6223,0x6327,0x6423,0x6527,0x6627,0x6723,0x682B,0x692F, + 0x6423,0x6527,0x6627,0x6723,0x682B,0x692F,0x7023,0x7127,0x7227,0x7323, + 0x7427,0x7523,0x7623,0x7727,0x782F,0x792B,0x7427,0x7523,0x7623,0x7727, + 0x782F,0x792B,0x8083,0x8187,0x8287,0x8383,0x8487,0x8583,0x8683,0x8787, + 0x888F,0x898B,0x8487,0x8583,0x8683,0x8787,0x888F,0x898B,0x9087,0x9183, + 0x9283,0x9387,0x9483,0x9587,0x9687,0x9783,0x988B,0x998F,0x9483,0x9587, + 0x9687,0x9783,0x988B,0x998F,0x0604,0x0700,0x0808,0x090C,0x0A0C,0x0B08, + 0x0C0C,0x0D08,0x0E08,0x0F0C,0x1010,0x1114,0x1214,0x1310,0x1414,0x1510, + 0x1600,0x1704,0x180C,0x1908,0x1A08,0x1B0C,0x1C08,0x1D0C,0x1E0C,0x1F08, + 0x2030,0x2134,0x2234,0x2330,0x2434,0x2530,0x2620,0x2724,0x282C,0x2928, + 0x2A28,0x2B2C,0x2C28,0x2D2C,0x2E2C,0x2F28,0x3034,0x3130,0x3230,0x3334, + 0x3430,0x3534,0x3624,0x3720,0x3828,0x392C,0x3A2C,0x3B28,0x3C2C,0x3D28, + 0x3E28,0x3F2C,0x4010,0x4114,0x4214,0x4310,0x4414,0x4510,0x4600,0x4704, + 0x480C,0x4908,0x4A08,0x4B0C,0x4C08,0x4D0C,0x4E0C,0x4F08,0x5014,0x5110, + 0x5210,0x5314,0x5410,0x5514,0x5604,0x5700,0x5808,0x590C,0x5A0C,0x5B08, + 0x5C0C,0x5D08,0x5E08,0x5F0C,0x6034,0x6130,0x6230,0x6334,0x6430,0x6534, + 0x6624,0x6720,0x6828,0x692C,0x6A2C,0x6B28,0x6C2C,0x6D28,0x6E28,0x6F2C, + 0x7030,0x7134,0x7234,0x7330,0x7434,0x7530,0x7620,0x7724,0x782C,0x7928, + 0x7A28,0x7B2C,0x7C28,0x7D2C,0x7E2C,0x7F28,0x8090,0x8194,0x8294,0x8390, + 0x8494,0x8590,0x8680,0x8784,0x888C,0x8988,0x8A88,0x8B8C,0x8C88,0x8D8C, + 0x8E8C,0x8F88,0x9094,0x9190,0x9290,0x9394,0x9490,0x9594,0x9684,0x9780, + 0x9888,0x998C,0x9A8C,0x9B88,0x9C8C,0x9D88,0x9E88,0x9F8C,0x0055,0x0111, + 0x0211,0x0315,0x0411,0x0515,0x0605,0x0701,0x0809,0x090D,0x0A0D,0x0B09, + 0x0C0D,0x0D09,0x0E09,0x0F0D,0x1011,0x1115,0x1215,0x1311,0x1415,0x1511, + 0x1601,0x1705,0x180D,0x1909,0x1A09,0x1B0D,0x1C09,0x1D0D,0x1E0D,0x1F09, + 0x2031,0x2135,0x2235,0x2331,0x2435,0x2531,0x2621,0x2725,0x282D,0x2929, + 0x2A29,0x2B2D,0x2C29,0x2D2D,0x2E2D,0x2F29,0x3035,0x3131,0x3231,0x3335, + 0x3431,0x3535,0x3625,0x3721,0x3829,0x392D,0x3A2D,0x3B29,0x3C2D,0x3D29, + 0x3E29,0x3F2D,0x4011,0x4115,0x4215,0x4311,0x4415,0x4511,0x4601,0x4705, + 0x480D,0x4909,0x4A09,0x4B0D,0x4C09,0x4D0D,0x4E0D,0x4F09,0x5015,0x5111, + 0x5211,0x5315,0x5411,0x5515,0x5605,0x5701,0x5809,0x590D,0x5A0D,0x5B09, + 0x5C0D,0x5D09,0x5E09,0x5F0D,0x6035,0x6131,0x6231,0x6335,0x6431,0x6535, + 0x6625,0x6721,0x6829,0x692D,0x6A2D,0x6B29,0x6C2D,0x6D29,0x6E29,0x6F2D, + 0x7031,0x7135,0x7235,0x7331,0x7435,0x7531,0x7621,0x7725,0x782D,0x7929, + 0x7A29,0x7B2D,0x7C29,0x7D2D,0x7E2D,0x7F29,0x8091,0x8195,0x8295,0x8391, + 0x8495,0x8591,0x8681,0x8785,0x888D,0x8989,0x8A89,0x8B8D,0x8C89,0x8D8D, + 0x8E8D,0x8F89,0x9095,0x9191,0x9291,0x9395,0x9491,0x9595,0x9685,0x9781, + 0x9889,0x998D,0x9A8D,0x9B89,0x9C8D,0x9D89,0x9E89,0x9F8D,0xA0B5,0xA1B1, + 0xA2B1,0xA3B5,0xA4B1,0xA5B5,0xA6A5,0xA7A1,0xA8A9,0xA9AD,0xAAAD,0xABA9, + 0xACAD,0xADA9,0xAEA9,0xAFAD,0xB0B1,0xB1B5,0xB2B5,0xB3B1,0xB4B5,0xB5B1, + 0xB6A1,0xB7A5,0xB8AD,0xB9A9,0xBAA9,0xBBAD,0xBCA9,0xBDAD,0xBEAD,0xBFA9, + 0xC095,0xC191,0xC291,0xC395,0xC491,0xC595,0xC685,0xC781,0xC889,0xC98D, + 0xCA8D,0xCB89,0xCC8D,0xCD89,0xCE89,0xCF8D,0xD091,0xD195,0xD295,0xD391, + 0xD495,0xD591,0xD681,0xD785,0xD88D,0xD989,0xDA89,0xDB8D,0xDC89,0xDD8D, + 0xDE8D,0xDF89,0xE0B1,0xE1B5,0xE2B5,0xE3B1,0xE4B5,0xE5B1,0xE6A1,0xE7A5, + 0xE8AD,0xE9A9,0xEAA9,0xEBAD,0xECA9,0xEDAD,0xEEAD,0xEFA9,0xF0B5,0xF1B1, + 0xF2B1,0xF3B5,0xF4B1,0xF5B5,0xF6A5,0xF7A1,0xF8A9,0xF9AD,0xFAAD,0xFBA9, + 0xFCAD,0xFDA9,0xFEA9,0xFFAD,0x0055,0x0111,0x0211,0x0315,0x0411,0x0515, + 0x0605,0x0701,0x0809,0x090D,0x0A0D,0x0B09,0x0C0D,0x0D09,0x0E09,0x0F0D, + 0x1011,0x1115,0x1215,0x1311,0x1415,0x1511,0x1601,0x1705,0x180D,0x1909, + 0x1A09,0x1B0D,0x1C09,0x1D0D,0x1E0D,0x1F09,0x2031,0x2135,0x2235,0x2331, + 0x2435,0x2531,0x2621,0x2725,0x282D,0x2929,0x2A29,0x2B2D,0x2C29,0x2D2D, + 0x2E2D,0x2F29,0x3035,0x3131,0x3231,0x3335,0x3431,0x3535,0x3625,0x3721, + 0x3829,0x392D,0x3A2D,0x3B29,0x3C2D,0x3D29,0x3E29,0x3F2D,0x4011,0x4115, + 0x4215,0x4311,0x4415,0x4511,0x4601,0x4705,0x480D,0x4909,0x4A09,0x4B0D, + 0x4C09,0x4D0D,0x4E0D,0x4F09,0x5015,0x5111,0x5211,0x5315,0x5411,0x5515, + 0x5605,0x5701,0x5809,0x590D,0x5A0D,0x5B09,0x5C0D,0x5D09,0x5E09,0x5F0D, + 0x6035,0x6131,0x6231,0x6335,0x6431,0x6535,0xFABE,0xFBBA,0xFCBE,0xFDBA, + 0xFEBA,0xFFBE,0x0046,0x0102,0x0202,0x0306,0x0402,0x0506,0x0606,0x0702, + 0x080A,0x090E,0x0A1E,0x0B1A,0x0C1E,0x0D1A,0x0E1A,0x0F1E,0x1002,0x1106, + 0x1206,0x1302,0x1406,0x1502,0x1602,0x1706,0x180E,0x190A,0x1A1A,0x1B1E, + 0x1C1A,0x1D1E,0x1E1E,0x1F1A,0x2022,0x2126,0x2226,0x2322,0x2426,0x2522, + 0x2622,0x2726,0x282E,0x292A,0x2A3A,0x2B3E,0x2C3A,0x2D3E,0x2E3E,0x2F3A, + 0x3026,0x3122,0x3222,0x3326,0x3422,0x3526,0x3626,0x3722,0x382A,0x392E, + 0x3A3E,0x3B3A,0x3C3E,0x3D3A,0x3E3A,0x3F3E,0x4002,0x4106,0x4206,0x4302, + 0x4406,0x4502,0x4602,0x4706,0x480E,0x490A,0x4A1A,0x4B1E,0x4C1A,0x4D1E, + 0x4E1E,0x4F1A,0x5006,0x5102,0x5202,0x5306,0x5402,0x5506,0x5606,0x5702, + 0x580A,0x590E,0x5A1E,0x5B1A,0x5C1E,0x5D1A,0x5E1A,0x5F1E,0x6026,0x6122, + 0x6222,0x6326,0x6422,0x6526,0x6626,0x6722,0x682A,0x692E,0x6A3E,0x6B3A, + 0x6C3E,0x6D3A,0x6E3A,0x6F3E,0x7022,0x7126,0x7226,0x7322,0x7426,0x7522, + 0x7622,0x7726,0x782E,0x792A,0x7A3A,0x7B3E,0x7C3A,0x7D3E,0x7E3E,0x7F3A, + 0x8082,0x8186,0x8286,0x8382,0x8486,0x8582,0x8682,0x8786,0x888E,0x898A, + 0x8A9A,0x8B9E,0x8C9A,0x8D9E,0x8E9E,0x8F9A,0x9086,0x9182,0x9282,0x9386, + 0x3423,0x3527,0x3627,0x3723,0x382B,0x392F,0x3A3F,0x3B3B,0x3C3F,0x3D3B, + 0x3E3B,0x3F3F,0x4003,0x4107,0x4207,0x4303,0x4407,0x4503,0x4603,0x4707, + 0x480F,0x490B,0x4A1B,0x4B1F,0x4C1B,0x4D1F,0x4E1F,0x4F1B,0x5007,0x5103, + 0x5203,0x5307,0x5403,0x5507,0x5607,0x5703,0x580B,0x590F,0x5A1F,0x5B1B, + 0x5C1F,0x5D1B,0x5E1B,0x5F1F,0x6027,0x6123,0x6223,0x6327,0x6423,0x6527, + 0x6627,0x6723,0x682B,0x692F,0x6A3F,0x6B3B,0x6C3F,0x6D3B,0x6E3B,0x6F3F, + 0x7023,0x7127,0x7227,0x7323,0x7427,0x7523,0x7623,0x7727,0x782F,0x792B, + 0x7A3B,0x7B3F,0x7C3B,0x7D3F,0x7E3F,0x7F3B,0x8083,0x8187,0x8287,0x8383, + 0x8487,0x8583,0x8683,0x8787,0x888F,0x898B,0x8A9B,0x8B9F,0x8C9B,0x8D9F, + 0x8E9F,0x8F9B,0x9087,0x9183,0x9283,0x9387,0x9483,0x9587,0x9687,0x9783, + 0x988B,0x998F,0x9A9F,0x9B9B,0x9C9F,0x9D9B,0x9E9B,0x9F9F,0xA0A7,0xA1A3, + 0xA2A3,0xA3A7,0xA4A3,0xA5A7,0xA6A7,0xA7A3,0xA8AB,0xA9AF,0xAABF,0xABBB, + 0xACBF,0xADBB,0xAEBB,0xAFBF,0xB0A3,0xB1A7,0xB2A7,0xB3A3,0xB4A7,0xB5A3, + 0xB6A3,0xB7A7,0xB8AF,0xB9AB,0xBABB,0xBBBF,0xBCBB,0xBDBF,0xBEBF,0xBFBB, + 0xC087,0xC183,0xC283,0xC387,0xC483,0xC587,0xC687,0xC783,0xC88B,0xC98F, + 0xCA9F,0xCB9B,0xCC9F,0xCD9B,0xCE9B,0xCF9F,0xD083,0xD187,0xD287,0xD383, + 0xD487,0xD583,0xD683,0xD787,0xD88F,0xD98B,0xDA9B,0xDB9F,0xDC9B,0xDD9F, + 0xDE9F,0xDF9B,0xE0A3,0xE1A7,0xE2A7,0xE3A3,0xE4A7,0xE5A3,0xE6A3,0xE7A7, + 0xE8AF,0xE9AB,0xEABB,0xEBBF,0xECBB,0xEDBF,0xEEBF,0xEFBB,0xF0A7,0xF1A3, + 0xF2A3,0xF3A7,0xF4A3,0xF5A7,0xF6A7,0xF7A3,0xF8AB,0xF9AF,0xFABF,0xFBBB, + 0xFCBF,0xFDBB,0xFEBB,0xFFBF,0x0047,0x0103,0x0203,0x0307,0x0403,0x0507, + 0x0607,0x0703,0x080B,0x090F,0x0A1F,0x0B1B,0x0C1F,0x0D1B,0x0E1B,0x0F1F, + 0x1003,0x1107,0x1207,0x1303,0x1407,0x1503,0x1603,0x1707,0x180F,0x190B, + 0x1A1B,0x1B1F,0x1C1B,0x1D1F,0x1E1F,0x1F1B,0x2023,0x2127,0x2227,0x2323, + 0x2427,0x2523,0x2623,0x2727,0x282F,0x292B,0x2A3B,0x2B3F,0x2C3B,0x2D3F, + 0x2E3F,0x2F3B,0x3027,0x3123,0x3223,0x3327,0x3423,0x3527,0x3627,0x3723, + 0x382B,0x392F,0x3A3F,0x3B3B,0x3C3F,0x3D3B,0x3E3B,0x3F3F,0x4003,0x4107, + 0x4207,0x4303,0x4407,0x4503,0x4603,0x4707,0x480F,0x490B,0x4A1B,0x4B1F, + 0x4C1B,0x4D1F,0x4E1F,0x4F1B,0x5007,0x5103,0x5203,0x5307,0x5403,0x5507, + 0x5607,0x5703,0x580B,0x590F,0x5A1F,0x5B1B,0x5C1F,0x5D1B,0x5E1B,0x5F1F, + 0x6027,0x6123,0x6223,0x6327,0x6423,0x6527,0x6627,0x6723,0x682B,0x692F, + 0x6A3F,0x6B3B,0x6C3F,0x6D3B,0x6E3B,0x6F3F,0x7023,0x7127,0x7227,0x7323, + 0x7427,0x7523,0x7623,0x7727,0x782F,0x792B,0x7A3B,0x7B3F,0x7C3B,0x7D3F, + 0x7E3F,0x7F3B,0x8083,0x8187,0x8287,0x8383,0x8487,0x8583,0x8683,0x8787, + 0x888F,0x898B,0x8A9B,0x8B9F,0x8C9B,0x8D9F,0x8E9F,0x8F9B,0x9087,0x9183, + 0x9283,0x9387,0x9483,0x9587,0x9687,0x9783,0x988B,0x998F, + ]; + + public static readonly byte[] Rl0 = + [ + 0x44,0x00,0x00,0x04,0x08,0x0c,0x0c,0x08 + ,0x00,0x04,0x04,0x00,0x0c,0x08,0x08,0x0c + ,0x20,0x24,0x24,0x20,0x2c,0x28,0x28,0x2c + ,0x24,0x20,0x20,0x24,0x28,0x2c,0x2c,0x28 + ,0x00,0x04,0x04,0x00,0x0c,0x08,0x08,0x0c + ,0x04,0x00,0x00,0x04,0x08,0x0c,0x0c,0x08 + ,0x24,0x20,0x20,0x24,0x28,0x2c,0x2c,0x28 + ,0x20,0x24,0x24,0x20,0x2c,0x28,0x28,0x2c + ,0x80,0x84,0x84,0x80,0x8c,0x88,0x88,0x8c + ,0x84,0x80,0x80,0x84,0x88,0x8c,0x8c,0x88 + ,0xa4,0xa0,0xa0,0xa4,0xa8,0xac,0xac,0xa8 + ,0xa0,0xa4,0xa4,0xa0,0xac,0xa8,0xa8,0xac + ,0x84,0x80,0x80,0x84,0x88,0x8c,0x8c,0x88 + ,0x80,0x84,0x84,0x80,0x8c,0x88,0x88,0x8c + ,0xa0,0xa4,0xa4,0xa0,0xac,0xa8,0xa8,0xac + ,0xa4,0xa0,0xa0,0xa4,0xa8,0xac,0xac,0xa8 + ,0x45,0x01,0x01,0x05,0x09,0x0d,0x0d,0x09 + ,0x01,0x05,0x05,0x01,0x0d,0x09,0x09,0x0d + ,0x21,0x25,0x25,0x21,0x2d,0x29,0x29,0x2d + ,0x25,0x21,0x21,0x25,0x29,0x2d,0x2d,0x29 + ,0x01,0x05,0x05,0x01,0x0d,0x09,0x09,0x0d + ,0x05,0x01,0x01,0x05,0x09,0x0d,0x0d,0x09 + ,0x25,0x21,0x21,0x25,0x29,0x2d,0x2d,0x29 + ,0x21,0x25,0x25,0x21,0x2d,0x29,0x29,0x2d + ,0x81,0x85,0x85,0x81,0x8d,0x89,0x89,0x8d + ,0x85,0x81,0x81,0x85,0x89,0x8d,0x8d,0x89 + ,0xa5,0xa1,0xa1,0xa5,0xa9,0xad,0xad,0xa9 + ,0xa1,0xa5,0xa5,0xa1,0xad,0xa9,0xa9,0xad + ,0x85,0x81,0x81,0x85,0x89,0x8d,0x8d,0x89 + ,0x81,0x85,0x85,0x81,0x8d,0x89,0x89,0x8d + ,0xa1,0xa5,0xa5,0xa1,0xad,0xa9,0xa9,0xad + ,0xa5,0xa1,0xa1,0xa5,0xa9,0xad,0xad,0xa9 + ]; + + public static readonly byte[] Rl1 = + [ + 0x00,0x04,0x04,0x00,0x0c,0x08,0x08,0x0c + ,0x04,0x00,0x00,0x04,0x08,0x0c,0x0c,0x08 + ,0x24,0x20,0x20,0x24,0x28,0x2c,0x2c,0x28 + ,0x20,0x24,0x24,0x20,0x2c,0x28,0x28,0x2c + ,0x04,0x00,0x00,0x04,0x08,0x0c,0x0c,0x08 + ,0x00,0x04,0x04,0x00,0x0c,0x08,0x08,0x0c + ,0x20,0x24,0x24,0x20,0x2c,0x28,0x28,0x2c + ,0x24,0x20,0x20,0x24,0x28,0x2c,0x2c,0x28 + ,0x84,0x80,0x80,0x84,0x88,0x8c,0x8c,0x88 + ,0x80,0x84,0x84,0x80,0x8c,0x88,0x88,0x8c + ,0xa0,0xa4,0xa4,0xa0,0xac,0xa8,0xa8,0xac + ,0xa4,0xa0,0xa0,0xa4,0xa8,0xac,0xac,0xa8 + ,0x80,0x84,0x84,0x80,0x8c,0x88,0x88,0x8c + ,0x84,0x80,0x80,0x84,0x88,0x8c,0x8c,0x88 + ,0xa4,0xa0,0xa0,0xa4,0xa8,0xac,0xac,0xa8 + ,0xa0,0xa4,0xa4,0xa0,0xac,0xa8,0xa8,0xac + ,0x01,0x05,0x05,0x01,0x0d,0x09,0x09,0x0d + ,0x05,0x01,0x01,0x05,0x09,0x0d,0x0d,0x09 + ,0x25,0x21,0x21,0x25,0x29,0x2d,0x2d,0x29 + ,0x21,0x25,0x25,0x21,0x2d,0x29,0x29,0x2d + ,0x05,0x01,0x01,0x05,0x09,0x0d,0x0d,0x09 + ,0x01,0x05,0x05,0x01,0x0d,0x09,0x09,0x0d + ,0x21,0x25,0x25,0x21,0x2d,0x29,0x29,0x2d + ,0x25,0x21,0x21,0x25,0x29,0x2d,0x2d,0x29 + ,0x85,0x81,0x81,0x85,0x89,0x8d,0x8d,0x89 + ,0x81,0x85,0x85,0x81,0x8d,0x89,0x89,0x8d + ,0xa1,0xa5,0xa5,0xa1,0xad,0xa9,0xa9,0xad + ,0xa5,0xa1,0xa1,0xa5,0xa9,0xad,0xad,0xa9 + ,0x81,0x85,0x85,0x81,0x8d,0x89,0x89,0x8d + ,0x85,0x81,0x81,0x85,0x89,0x8d,0x8d,0x89 + ,0xa5,0xa1,0xa1,0xa5,0xa9,0xad,0xad,0xa9 + ,0xa1,0xa5,0xa5,0xa1,0xad,0xa9,0xa9,0xad + ]; + + public static readonly byte[] Rr0 = + [ + 0x44,0x45,0x00,0x01,0x00,0x01,0x04,0x05 + ,0x00,0x01,0x04,0x05,0x04,0x05,0x00,0x01 + ,0x08,0x09,0x0c,0x0d,0x0c,0x0d,0x08,0x09 + ,0x0c,0x0d,0x08,0x09,0x08,0x09,0x0c,0x0d + ,0x00,0x01,0x04,0x05,0x04,0x05,0x00,0x01 + ,0x04,0x05,0x00,0x01,0x00,0x01,0x04,0x05 + ,0x0c,0x0d,0x08,0x09,0x08,0x09,0x0c,0x0d + ,0x08,0x09,0x0c,0x0d,0x0c,0x0d,0x08,0x09 + ,0x20,0x21,0x24,0x25,0x24,0x25,0x20,0x21 + ,0x24,0x25,0x20,0x21,0x20,0x21,0x24,0x25 + ,0x2c,0x2d,0x28,0x29,0x28,0x29,0x2c,0x2d + ,0x28,0x29,0x2c,0x2d,0x2c,0x2d,0x28,0x29 + ,0x24,0x25,0x20,0x21,0x20,0x21,0x24,0x25 + ,0x20,0x21,0x24,0x25,0x24,0x25,0x20,0x21 + ,0x28,0x29,0x2c,0x2d,0x2c,0x2d,0x28,0x29 + ,0x2c,0x2d,0x28,0x29,0x28,0x29,0x2c,0x2d + ,0x00,0x01,0x04,0x05,0x04,0x05,0x00,0x01 + ,0x04,0x05,0x00,0x01,0x00,0x01,0x04,0x05 + ,0x0c,0x0d,0x08,0x09,0x08,0x09,0x0c,0x0d + ,0x08,0x09,0x0c,0x0d,0x0c,0x0d,0x08,0x09 + ,0x04,0x05,0x00,0x01,0x00,0x01,0x04,0x05 + ,0x00,0x01,0x04,0x05,0x04,0x05,0x00,0x01 + ,0x08,0x09,0x0c,0x0d,0x0c,0x0d,0x08,0x09 + ,0x0c,0x0d,0x08,0x09,0x08,0x09,0x0c,0x0d + ,0x24,0x25,0x20,0x21,0x20,0x21,0x24,0x25 + ,0x20,0x21,0x24,0x25,0x24,0x25,0x20,0x21 + ,0x28,0x29,0x2c,0x2d,0x2c,0x2d,0x28,0x29 + ,0x2c,0x2d,0x28,0x29,0x28,0x29,0x2c,0x2d + ,0x20,0x21,0x24,0x25,0x24,0x25,0x20,0x21 + ,0x24,0x25,0x20,0x21,0x20,0x21,0x24,0x25 + ,0x2c,0x2d,0x28,0x29,0x28,0x29,0x2c,0x2d + ,0x28,0x29,0x2c,0x2d,0x2c,0x2d,0x28,0x29 + ]; + + public static readonly byte[] Rr1 = + [ + 0x80,0x81,0x84,0x85,0x84,0x85,0x80,0x81 + ,0x84,0x85,0x80,0x81,0x80,0x81,0x84,0x85 + ,0x8c,0x8d,0x88,0x89,0x88,0x89,0x8c,0x8d + ,0x88,0x89,0x8c,0x8d,0x8c,0x8d,0x88,0x89 + ,0x84,0x85,0x80,0x81,0x80,0x81,0x84,0x85 + ,0x80,0x81,0x84,0x85,0x84,0x85,0x80,0x81 + ,0x88,0x89,0x8c,0x8d,0x8c,0x8d,0x88,0x89 + ,0x8c,0x8d,0x88,0x89,0x88,0x89,0x8c,0x8d + ,0xa4,0xa5,0xa0,0xa1,0xa0,0xa1,0xa4,0xa5 + ,0xa0,0xa1,0xa4,0xa5,0xa4,0xa5,0xa0,0xa1 + ,0xa8,0xa9,0xac,0xad,0xac,0xad,0xa8,0xa9 + ,0xac,0xad,0xa8,0xa9,0xa8,0xa9,0xac,0xad + ,0xa0,0xa1,0xa4,0xa5,0xa4,0xa5,0xa0,0xa1 + ,0xa4,0xa5,0xa0,0xa1,0xa0,0xa1,0xa4,0xa5 + ,0xac,0xad,0xa8,0xa9,0xa8,0xa9,0xac,0xad + ,0xa8,0xa9,0xac,0xad,0xac,0xad,0xa8,0xa9 + ,0x84,0x85,0x80,0x81,0x80,0x81,0x84,0x85 + ,0x80,0x81,0x84,0x85,0x84,0x85,0x80,0x81 + ,0x88,0x89,0x8c,0x8d,0x8c,0x8d,0x88,0x89 + ,0x8c,0x8d,0x88,0x89,0x88,0x89,0x8c,0x8d + ,0x80,0x81,0x84,0x85,0x84,0x85,0x80,0x81 + ,0x84,0x85,0x80,0x81,0x80,0x81,0x84,0x85 + ,0x8c,0x8d,0x88,0x89,0x88,0x89,0x8c,0x8d + ,0x88,0x89,0x8c,0x8d,0x8c,0x8d,0x88,0x89 + ,0xa0,0xa1,0xa4,0xa5,0xa4,0xa5,0xa0,0xa1 + ,0xa4,0xa5,0xa0,0xa1,0xa0,0xa1,0xa4,0xa5 + ,0xac,0xad,0xa8,0xa9,0xa8,0xa9,0xac,0xad + ,0xa8,0xa9,0xac,0xad,0xac,0xad,0xa8,0xa9 + ,0xa4,0xa5,0xa0,0xa1,0xa0,0xa1,0xa4,0xa5 + ,0xa0,0xa1,0xa4,0xa5,0xa4,0xa5,0xa0,0xa1 + ,0xa8,0xa9,0xac,0xad,0xac,0xad,0xa8,0xa9 + ,0xac,0xad,0xa8,0xa9,0xa8,0xa9,0xac,0xad + ]; + + public static readonly byte[] Sraf = + [ + 0x44,0x45,0x00,0x01,0x00,0x01,0x04,0x05 + ,0x00,0x01,0x04,0x05,0x04,0x05,0x00,0x01 + ,0x08,0x09,0x0c,0x0d,0x0c,0x0d,0x08,0x09 + ,0x0c,0x0d,0x08,0x09,0x08,0x09,0x0c,0x0d + ,0x00,0x01,0x04,0x05,0x04,0x05,0x00,0x01 + ,0x04,0x05,0x00,0x01,0x00,0x01,0x04,0x05 + ,0x0c,0x0d,0x08,0x09,0x08,0x09,0x0c,0x0d + ,0x08,0x09,0x0c,0x0d,0x0c,0x0d,0x08,0x09 + ,0x20,0x21,0x24,0x25,0x24,0x25,0x20,0x21 + ,0x24,0x25,0x20,0x21,0x20,0x21,0x24,0x25 + ,0x2c,0x2d,0x28,0x29,0x28,0x29,0x2c,0x2d + ,0x28,0x29,0x2c,0x2d,0x2c,0x2d,0x28,0x29 + ,0x24,0x25,0x20,0x21,0x20,0x21,0x24,0x25 + ,0x20,0x21,0x24,0x25,0x24,0x25,0x20,0x21 + ,0x28,0x29,0x2c,0x2d,0x2c,0x2d,0x28,0x29 + ,0x2c,0x2d,0x28,0x29,0x28,0x29,0x2c,0x2d + ,0x84,0x85,0x80,0x81,0x80,0x81,0x84,0x85 + ,0x80,0x81,0x84,0x85,0x84,0x85,0x80,0x81 + ,0x88,0x89,0x8c,0x8d,0x8c,0x8d,0x88,0x89 + ,0x8c,0x8d,0x88,0x89,0x88,0x89,0x8c,0x8d + ,0x80,0x81,0x84,0x85,0x84,0x85,0x80,0x81 + ,0x84,0x85,0x80,0x81,0x80,0x81,0x84,0x85 + ,0x8c,0x8d,0x88,0x89,0x88,0x89,0x8c,0x8d + ,0x88,0x89,0x8c,0x8d,0x8c,0x8d,0x88,0x89 + ,0xa0,0xa1,0xa4,0xa5,0xa4,0xa5,0xa0,0xa1 + ,0xa4,0xa5,0xa0,0xa1,0xa0,0xa1,0xa4,0xa5 + ,0xac,0xad,0xa8,0xa9,0xa8,0xa9,0xac,0xad + ,0xa8,0xa9,0xac,0xad,0xac,0xad,0xa8,0xa9 + ,0xa4,0xa5,0xa0,0xa1,0xa0,0xa1,0xa4,0xa5 + ,0xa0,0xa1,0xa4,0xa5,0xa4,0xa5,0xa0,0xa1 + ,0xa8,0xa9,0xac,0xad,0xac,0xad,0xa8,0xa9 + ,0xac,0xad,0xa8,0xa9,0xa8,0xa9,0xac,0xad + ]; + } +} diff --git a/Debuger/Cpu/Processor/Z80Cpu.Alu.cs b/Debuger/Cpu/Processor/Z80Cpu.Alu.cs new file mode 100644 index 0000000..8fc3e19 --- /dev/null +++ b/Debuger/Cpu/Processor/Z80Cpu.Alu.cs @@ -0,0 +1,199 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * Description: Z80 CPU Emulator [ALU part] + * Date: 13.04.2007 + * + */ + + +namespace ZXMAK2.Engine.Cpu.Processor +{ + public partial class Z80Cpu + { + #region ALU + + private void ALU_ADDR(byte src) + { + regs.F = CpuTables.Adcf[regs.A + src * 0x100]; + regs.A += src; + } + + private void ALU_ADCR(byte src) + { + var carry = regs.F & CpuFlags.C; + regs.F = CpuTables.Adcf[regs.A + src * 0x100 + 0x10000 * carry]; + regs.A += (byte)(src + carry); + } + + private void ALU_SUBR(byte src) + { + regs.F = CpuTables.Sbcf[regs.A * 0x100 + src]; + regs.A -= src; + } + + private void ALU_SBCR(byte src) + { + var carry = regs.F & CpuFlags.C; + regs.F = CpuTables.Sbcf[regs.A * 0x100 + src + 0x10000 * carry]; + regs.A -= (byte)(src + carry); + } + + private void ALU_ANDR(byte src) + { + regs.A &= src; + regs.F = (byte)(CpuTables.Logf[regs.A] | CpuFlags.H); + } + + private void ALU_XORR(byte src) + { + regs.A ^= src; + regs.F = CpuTables.Logf[regs.A]; + } + + private void ALU_ORR(byte src) + { + regs.A |= src; + regs.F = CpuTables.Logf[regs.A]; + } + + private void ALU_CPR(byte src) + { + regs.F = CpuTables.Cpf[regs.A * 0x100 + src]; + } + + private byte ALU_INCR(byte x) + { + regs.F = (byte)(CpuTables.Incf[x] | (regs.F & CpuFlags.C)); + x++; + return x; + } + + private byte ALU_DECR(byte x) + { + regs.F = (byte)(CpuTables.Decf[x] | (regs.F & CpuFlags.C)); + x--; + return x; + } + + private ushort ALU_ADDHLRR(ushort rhl, ushort rde) + { + regs.F = (byte)(regs.F & CpuFlags.NotHNCF3F5); + regs.F |= (byte)((((rhl & 0x0FFF) + (rde & 0x0FFF)) >> 8) & CpuFlags.H); + uint res = (uint)((rhl & 0xFFFF) + (rde & 0xFFFF)); + + if ((res & 0x10000) != 0) regs.F |= CpuFlags.C; + regs.F |= (byte)((res >> 8) & CpuFlags.F3F5); + return (ushort)res; + } + + #endregion + + #region ALU #CB + + private byte ALU_RLC(int x) + { + regs.F = CpuTables.Rlcf[x]; + x <<= 1; + if ((x & 0x100) != 0) x |= 0x01; + return (byte)x; + } + + private byte ALU_RRC(int x) + { + regs.F = CpuTables.Rrcf[x]; + if ((x & 0x01) != 0) x = (x >> 1) | 0x80; + else x >>= 1; + return (byte)x; + } + + private byte ALU_RL(int x) + { + if ((regs.F & CpuFlags.C) != 0) + { + regs.F = CpuTables.Rl1[x]; + x <<= 1; + x++; + } + else + { + regs.F = CpuTables.Rl0[x]; + x <<= 1; + } + return (byte)x; + } + + private byte ALU_RR(int x) + { + if ((regs.F & CpuFlags.C) != 0) + { + regs.F = CpuTables.Rr1[x]; + x >>= 1; + x += 0x80; + } + else + { + regs.F = CpuTables.Rr0[x]; + x >>= 1; + } + return (byte)x; + } + + private byte ALU_SLA(int x) + { + regs.F = CpuTables.Rl0[x]; + x <<= 1; + return (byte)x; + } + + private byte ALU_SRA(int x) + { + regs.F = CpuTables.Sraf[x]; + x = (x >> 1) + (x & 0x80); + return (byte)x; + } + + private byte ALU_SLL(int x) + { + regs.F = CpuTables.Rl1[x]; + x <<= 1; + x++; + return (byte)x; + } + + private byte ALU_SRL(int x) + { + regs.F = CpuTables.Rr0[x]; + x >>= 1; + return (byte)x; + } + + private void ALU_BIT(byte src, int bit) + { + regs.F = (byte)(CpuTables.Logf[src & (1 << bit)] | CpuFlags.H | (regs.F & CpuFlags.C) | (src & CpuFlags.F3F5)); + } + + private void ALU_BITMEM(byte src, int bit) + { + regs.F = (byte)(CpuTables.Logf[src & (1 << bit)] | CpuFlags.H | (regs.F & CpuFlags.C)); + regs.F = (byte)((regs.F & CpuFlags.NotF3F5) | (regs.MH & CpuFlags.F3F5)); + } + + #endregion + } +} diff --git a/Debuger/Cpu/Processor/Z80Cpu.Opcodes.cs b/Debuger/Cpu/Processor/Z80Cpu.Opcodes.cs new file mode 100644 index 0000000..ef36899 --- /dev/null +++ b/Debuger/Cpu/Processor/Z80Cpu.Opcodes.cs @@ -0,0 +1,722 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * Description: Z80 CPU Emulator [direct opcode part] + * Date: 13.04.2007 + * + */ + + +namespace ZXMAK2.Engine.Cpu.Processor +{ + public partial class Z80Cpu + { + #region direct/DD/FD + + private void INA_NN_(byte cmd) // IN A,(N) [11T] + { + // 11T (4, 3, 4) + + regs.MW = RDMEM(regs.PC++); Tact += 3; + regs.MW += (ushort)(regs.A << 8); + + regs.A = RDPORT(regs.MW); Tact += 4; + regs.MW++; + } + + private void OUT_NN_A(byte cmd) // OUT (N),A [11T]+ + { + // 11T (4, 3, 4) + + regs.MW = RDMEM(regs.PC++); Tact += 3; + regs.MW += (ushort)(regs.A << 8); + + WRPORT(regs.MW, regs.A); Tact += 4; + regs.ML++; + } + + private void DI(byte cmd) + { + IFF1 = false; + IFF2 = false; + } + + private void EI(byte cmd) + { + IFF1 = true; + IFF2 = true; + BINT = true; + } + + private void LDSPHL(byte cmd) // LD SP,HL + { + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + regs.SP = regs.HL; + } + + private void EX_SP_HL(byte cmd) // EX (SP),HL + { + // 19T (4, 3, 4, 3, 5) + + ushort tmpsp = regs.SP; + regs.MW = RDMEM(tmpsp); Tact += 3; + tmpsp++; + + regs.MW += (ushort)(RDMEM(tmpsp) * 0x100); Tact += 3; + RDNOMREQ(tmpsp); Tact += 1; + + WRMEM(tmpsp, regs.H); Tact += 3; + tmpsp--; + + WRMEM(tmpsp, regs.L); Tact += 3; + WRNOMREQ(tmpsp); Tact++; + WRNOMREQ(tmpsp); Tact++; + regs.HL = regs.MW; + } + + private void JP_HL_(byte cmd) // JP (HL) + { + regs.PC = regs.HL; + } + + private void EXDEHL(byte cmd) // EX DE,HL + { + (regs.DE, regs.HL) = (regs.HL, regs.DE); + } + + private void EXAFAF(byte cmd) // EX AF,AF' + { + (regs._AF, regs.AF) = (regs.AF, regs._AF); + } + + private void EXX(byte cmd) // EXX + { + (regs._BC, regs.BC) = (regs.BC, regs._BC); + (regs._DE, regs.DE) = (regs.DE, regs._DE); + (regs._HL, regs.HL) = (regs.HL, regs._HL); + } + + #endregion + + + #region logical + + private void RLCA(byte cmd) + { + regs.F = (byte)(CpuTables.Rlcaf[regs.A] | (regs.F & CpuFlags.SZP)); + int x = regs.A; + x <<= 1; + if ((x & 0x100) != 0) x |= 0x01; + regs.A = (byte)x; + } + + private void RRCA(byte cmd) + { + regs.F = (byte)(CpuTables.Rrcaf[regs.A] | (regs.F & CpuFlags.SZP)); + int x = regs.A; + if ((x & 0x01) != 0) x = (x >> 1) | 0x80; + else x >>= 1; + regs.A = (byte)x; + } + + private void RLA(byte cmd) + { + var carry = (regs.F & CpuFlags.C) != 0; + regs.F = (byte)(CpuTables.Rlcaf[regs.A] | (regs.F & CpuFlags.SZP)); // use same table with rlca + regs.A = (byte)(regs.A << 1); + if (carry) regs.A |= 0x01; + } + + private void RRA(byte cmd) + { + var carry = (regs.F & CpuFlags.C) != 0; + regs.F = (byte)(CpuTables.Rrcaf[regs.A] | (regs.F & CpuFlags.SZP)); // use same table with rrca + regs.A = (byte)(regs.A >> 1); + if (carry) regs.A |= 0x80; + } + + private void DAA(byte cmd) + { + regs.AF = CpuTables.Daaf[regs.A + 0x100 * ((regs.F & 3) + ((regs.F >> 2) & 4))]; + } + + private void CPL(byte cmd) + { + regs.A = (byte)~regs.A; + regs.F = (byte)((regs.F & CpuFlags.NotF3F5) | CpuFlags.HN | (regs.A & CpuFlags.F3F5)); + } + + private void SCF_ZILOG(byte cmd) + { + regs.F = (byte)((regs.F & CpuFlags.SZP) | + (((regs.F ^ regs.Q) | regs.A) & CpuFlags.F3F5) | + CpuFlags.C); + } + + private void SCF_ST_CMOS(byte cmd) + { + regs.F = (byte)((regs.F & CpuFlags.SZP) | + ((((regs.F ^ regs.Q) & CpuFlags.F5) | regs.A) & CpuFlags.F3F5) | + CpuFlags.C); + } + + private void SCF_NEC_NMOS(byte cmd) + { + regs.F = (byte)((regs.F & CpuFlags.SZP) | + (regs.A & CpuFlags.F3F5) | + CpuFlags.C); + } + + private void CCF_ZILOG(byte cmd) + { + regs.F = (byte)(((regs.F & CpuFlags.SZPC) ^ CpuFlags.C) | + (((regs.F ^ regs.Q) | regs.A) & CpuFlags.F3F5) | + ((regs.F & CpuFlags.C) << 4)); + } + + private void CCF_ST_CMOS(byte cmd) + { + regs.F = (byte)(((regs.F & CpuFlags.SZPC) ^ CpuFlags.C) | + ((((regs.F ^ regs.Q) & CpuFlags.F5) | regs.A) & CpuFlags.F3F5) | + ((regs.F & CpuFlags.C) << 4)); + } + + private void CCF_NEC_NMOS(byte cmd) + { + regs.F = (byte)(((regs.F & CpuFlags.SZPC) ^ CpuFlags.C) | + (regs.A & CpuFlags.F3F5) | + ((regs.F & CpuFlags.C) << 4)); + } + + #endregion + + #region jmp/call/ret/jr + + private static readonly byte[] s_conds = + [ + CpuFlags.Z, + CpuFlags.C, + CpuFlags.P, + CpuFlags.S + ]; + + private void DJNZ(byte cmd) // DJNZ nn + { + // B==0 => 8T (5, 3) + // B!=0 => 13T (5, 3, 5) + + RDNOMREQ(regs.IR); Tact++; + + int drel = (sbyte)RDMEM(regs.PC); Tact += 3; + regs.PC++; + + if (--regs.B != 0) + { + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + regs.MW = (ushort)(regs.PC + drel); + regs.PC = regs.MW; + } + } + + private void JRNN(byte cmd) // JR nn + { + // 12T (4, 3, 5) + + int drel = (sbyte)RDMEM(regs.PC); Tact += 3; + regs.PC++; + + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + regs.MW = (ushort)(regs.PC + drel); + regs.PC = regs.MW; + } + + private void JRXNN(byte cmd) // JR x,nn + { + // false => 7T (4, 3) + // true => 12 (4, 3, 5) + var cond = (cmd & 0x18) >> 3; + var mask = s_conds[cond >> 1]; + var f = regs.AF & mask; + if ((cond & 1) != 0) f ^= mask; + + int drel = (sbyte)RDMEM(regs.PC); Tact += 3; + regs.PC++; + + if (f == 0) + { + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + regs.MW = (ushort)(regs.PC + drel); + regs.PC = regs.MW; + } + } + + private void CALLNNNN(byte cmd) // CALL + { + // 17T (4, 3, 4, 3, 3) + + regs.MW = RDMEM(regs.PC); Tact += 3; + regs.PC++; + + regs.MW += (ushort)(RDMEM(regs.PC) * 0x100); Tact += 3; + RDNOMREQ(regs.PC); Tact++; + regs.PC++; + regs.SP--; + + WRMEM(regs.SP, (byte)(regs.PC >> 8)); Tact += 3; + regs.SP--; + + WRMEM(regs.SP, (byte)regs.PC); Tact += 3; + regs.PC = regs.MW; + } + + private void CALLXNNNN(byte cmd) // CALL x,#nn + { + // false => 10T (4, 3, 3) + // true => 17T (4, 3, 4, 3, 3) + var cond = (cmd & 0x38) >> 3; + var mask = s_conds[cond >> 1]; + var f = regs.AF & mask; + if ((cond & 1) != 0) f ^= mask; + + regs.MW = RDMEM(regs.PC); Tact += 3; + regs.PC++; + + regs.MW += (ushort)(RDMEM(regs.PC) * 0x100); Tact += 3; + if (f == 0) + { + RDNOMREQ(regs.PC); Tact++; + } + regs.PC++; + + if (f == 0) + { + regs.SP--; + + WRMEM(regs.SP, (byte)(regs.PC >> 8)); Tact += 3; + regs.SP--; + + WRMEM(regs.SP, (byte)regs.PC); Tact += 3; + regs.PC = regs.MW; + } + } + + private void RET(byte cmd) // RET + { + // 10T (4, 3, 3) + + regs.MW = RDMEM(regs.SP); Tact += 3; + regs.SP++; + + regs.MW += (ushort)(RDMEM(regs.SP) * 0x100); Tact += 3; + regs.SP++; + regs.PC = regs.MW; + } + + private void RETX(byte cmd) // RET x + { + // false => 5T (5) + // true => 11T (5, 3, 3) + var cond = (cmd & 0x38) >> 3; + var mask = s_conds[cond >> 1]; + var f = regs.AF & mask; + if ((cond & 1) != 0) f ^= mask; + + RDNOMREQ(regs.IR); Tact++; + + if (f == 0) + { + regs.MW = RDMEM(regs.SP); Tact += 3; + regs.SP++; + + regs.MW += (ushort)(RDMEM(regs.SP) * 0x100); Tact += 3; + regs.SP++; + regs.PC = regs.MW; + } + } + + private void JPNNNN(byte cmd) // JP nnnn + { + // 10T (4, 3, 3) + + regs.MW = RDMEM(regs.PC); Tact += 3; + regs.PC++; + + regs.MW += (ushort)(RDMEM(regs.PC) * 0x100); Tact += 3; + regs.PC = regs.MW; + } + + private void JPXNN(byte cmd) // JP x,#nn ??? + { + // 10T (4, 3, 3) + var cond = (cmd & 0x38) >> 3; + var mask = s_conds[cond >> 1]; + var f = regs.AF & mask; + if ((cond & 1) != 0) f ^= mask; + + regs.MW = RDMEM(regs.PC); Tact += 3; + regs.PC++; + + regs.MW += (ushort)(RDMEM(regs.PC) * 0x100); Tact += 3; + regs.PC++; + + if (f == 0) + regs.PC = regs.MW; + } + + private void RSTNN(byte cmd) // RST #nn ?TIME? + { + // 11T (5, 3, 3) + var rst = (ushort)(cmd & 0x38); + + RDNOMREQ(regs.IR); Tact++; + regs.SP--; + + WRMEM(regs.SP, (byte)(regs.PC >> 8)); Tact += 3; + regs.SP--; + + WRMEM(regs.SP, (byte)regs.PC); Tact += 3; + regs.MW = rst; + regs.PC = regs.MW; + } + + #endregion + + #region push/pop + + private void PUSHRR(byte cmd) // PUSH RR ?TIME? + { + // 11T (5, 3, 3) + var rr = (cmd & 0x30) >> 4; + + RDNOMREQ(regs.IR); Tact += 1; + ushort val = rr == CpuRegId.Sp ? regs.AF : _pairGetters[rr](); + + regs.SP--; + WRMEM(regs.SP, (byte)(val >> 8)); Tact += 3; + + regs.SP--; + WRMEM(regs.SP, (byte)val); Tact += 3; + } + + private void POPRR(byte cmd) // POP RR + { + // 10T (4, 3, 3) + var rr = (cmd & 0x30) >> 4; + + var val = (ushort)RDMEM(regs.SP); + regs.SP++; + Tact += 3; + + val |= (ushort)(RDMEM(regs.SP) << 8); + regs.SP++; + if (rr == CpuRegId.Sp) regs.AF = val; + else _pairSetters[rr](val); + Tact += 3; + } + + #endregion + + #region ALU + + private void ALUAN(byte cmd) + { + // 7T (4, 3) + var op = (cmd & 0x38) >> 3; + + var val = RDMEM(regs.PC); + regs.PC++; + _alualg[op](val); + Tact += 3; + } + + private void ALUAR(byte cmd) // ADD/ADC/SUB/SBC/AND/XOR/OR/CP A,R + { + var r = cmd & 0x07; + var op = (cmd & 0x38) >> 3; + + _alualg[op](_regGetters[r]()); + } + + private void ALUA_HL_(byte cmd) // ADD/ADC/SUB/SBC/AND/XOR/OR/CP A,(HL) + { + // 7T (4, 3) + var op = (cmd & 0x38) >> 3; + + var val = RDMEM(regs.HL); + _alualg[op](val); + Tact += 3; + } + + private void ADDHLRR(byte cmd) // ADD HL,RR + { + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + regs.MW = (ushort)(regs.HL + 1); + regs.HL = ALU_ADDHLRR(regs.HL, _pairGetters[(cmd & 0x30) >> 4]()); + } + + #endregion + + #region loads + + private void LDA_RR_(byte cmd) // LD A,(RR) + { + // 7T (4, 3) + var rr = (cmd & 0x30) >> 4; + + var rrValue = _pairGetters[rr](); + regs.A = RDMEM(rrValue); + regs.MW = (ushort)(rrValue + 1); + Tact += 3; + } + + private void LDA_NN_(byte cmd) // LD A,(nnnn) + { + // 13T (4, 3, 3, 3) + + ushort adr = RDMEM(regs.PC); + regs.PC++; + Tact += 3; + + adr += (ushort)(RDMEM(regs.PC) * 0x100); + regs.PC++; + Tact += 3; + + regs.A = RDMEM(adr); + regs.MW = (ushort)(adr + 1); + Tact += 3; + } + + private void LDHL_NN_(byte cmd) // LD HL,(nnnn) + { + // 16T (4, 3, 3, 3, 3) + + var adr = (ushort)RDMEM(regs.PC); + regs.PC++; + Tact += 3; + + adr += (ushort)(RDMEM(regs.PC) * 0x100); + regs.PC++; + Tact += 3; + + var val = (ushort)RDMEM(adr); + regs.MW = (ushort)(adr + 1); + Tact += 3; + + val += (ushort)(RDMEM(regs.MW) * 0x100); + regs.HL = val; + Tact += 3; + } + + private void LD_RR_A(byte cmd) // LD (RR),A + { + // 7T (4, 3) + var rr = (cmd & 0x30) >> 4; + + var rrValue = _pairGetters[rr](); + WRMEM(rrValue, regs.A); + regs.MH = regs.A; + regs.ML = (byte)(rrValue + 1); + Tact += 3; + } + + private void LD_NN_A(byte cmd) // LD (nnnn),A + { + // 13T (4, 3, 3, 3) + + ushort adr = RDMEM(regs.PC); + regs.PC++; + Tact += 3; + + adr += (ushort)(RDMEM(regs.PC) * 0x100); + regs.PC++; + Tact += 3; + + WRMEM(adr, regs.A); + regs.MH = regs.A; + regs.ML = (byte)(adr + 1); + Tact += 3; + } + + private void LD_NN_HL(byte cmd) // LD (nnnn),HL + { + // 16T (4, 3, 3, 3, 3) + + ushort adr = RDMEM(regs.PC); + regs.PC++; + Tact += 3; + + adr += (ushort)(RDMEM(regs.PC) * 0x100); + regs.PC++; + Tact += 3; + + WRMEM(adr, regs.L); + regs.MW = (ushort)(adr + 1); + Tact += 3; + + WRMEM(regs.MW, regs.H); + Tact += 3; + } + + private void LDRRNNNN(byte cmd) // LD RR,nnnn + { + // 10T (4, 3, 3) + var rr = (cmd & 0x30) >> 4; + + ushort val = RDMEM(regs.PC); + regs.PC++; + Tact += 3; + + val |= (ushort)(RDMEM(regs.PC) << 8); + regs.PC++; + _pairSetters[rr](val); + Tact += 3; + } + + private void LDRNN(byte cmd) // LD R,nn + { + // 7T (4, 3) + var r = (cmd & 0x38) >> 3; + + _regSetters[r](RDMEM(regs.PC)); + regs.PC++; + Tact += 3; + } + + private void LD_HL_NN(byte cmd) // LD (HL),nn + { + // 10T (4, 3, 3) + + var val = RDMEM(regs.PC); + regs.PC++; + Tact += 3; + + WRMEM(regs.HL, val); + Tact += 3; + } + + private void LDRdRs(byte cmd) // LD R1,R2 + { + var rsrc = cmd & 0x07; + var rdst = (cmd & 0x38) >> 3; + _regSetters[rdst](_regGetters[rsrc]()); + } + + private void LD_HL_R(byte cmd) // LD (HL),R + { + // 7T (4, 3) + var r = cmd & 0x07; + + WRMEM(regs.HL, _regGetters[r]()); + Tact += 3; + } + + private void LDR_HL_(byte cmd) // LD R,(HL) + { + // 7T (4, 3) + var r = (cmd & 0x38) >> 3; + + _regSetters[r](RDMEM(regs.HL)); + Tact += 3; + } + + #endregion + + #region INC/DEC + + private void DECRR(byte cmd) // DEC RR + { + var rr = (cmd & 0x30) >> 4; + + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + _pairSetters[rr]((ushort)(_pairGetters[rr]() - 1)); + } + + private void INCRR(byte cmd) // INC RR + { + var rr = (cmd & 0x30) >> 4; + + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + _pairSetters[rr]((ushort)(_pairGetters[rr]() + 1)); + } + + private void DECR(byte cmd) // DEC R + { + var r = (cmd & 0x38) >> 3; + + _regSetters[r](ALU_DECR(_regGetters[r]())); + } + + private void INCR(byte cmd) // INC R + { + var r = (cmd & 0x38) >> 3; + + _regSetters[r](ALU_INCR(_regGetters[r]())); + } + + private void DEC_HL_(byte cmd) // DEC (HL) + { + // 11T (4, 4, 3) + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val = ALU_DECR(val); + + WRMEM(regs.HL, val); Tact += 3; + } + + private void INC_HL_(byte cmd) // INC (HL) + { + // 11T (4, 4, 3) + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val = ALU_INCR(val); + + WRMEM(regs.HL, val); Tact += 3; + } + + #endregion + + private void HALT(byte cmd) + { + HALTED = true; + regs.PC--; // workaround for Z80 snapshot halt issue + comfortable debugging + } + } +} diff --git a/Debuger/Cpu/Processor/Z80Cpu.OpcodesCb.cs b/Debuger/Cpu/Processor/Z80Cpu.OpcodesCb.cs new file mode 100644 index 0000000..31c4bfe --- /dev/null +++ b/Debuger/Cpu/Processor/Z80Cpu.OpcodesCb.cs @@ -0,0 +1,212 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * Description: Z80 CPU Emulator [CB prefixed opcodes part] + * Date: 13.04.2007 + * + */ + + +namespace ZXMAK2.Engine.Cpu.Processor +{ + public partial class Z80Cpu + { + private void CB_RLC(byte cmd) // RLC r + { + var r = cmd & 7; + _regSetters[r](ALU_RLC(_regGetters[r]())); + } + + private void CB_RRC(byte cmd) // RRC r + { + var r = cmd & 7; + _regSetters[r](ALU_RRC(_regGetters[r]())); + } + + private void CB_RL(byte cmd) // RL r + { + var r = cmd & 7; + _regSetters[r](ALU_RL(_regGetters[r]())); + } + + private void CB_RR(byte cmd) // RR r + { + var r = cmd & 7; + _regSetters[r](ALU_RR(_regGetters[r]())); + } + + private void CB_SLA(byte cmd) // SLA r + { + var r = cmd & 7; + _regSetters[r](ALU_SLA(_regGetters[r]())); + } + + private void CB_SRA(byte cmd) // SRA r + { + var r = cmd & 7; + _regSetters[r](ALU_SRA(_regGetters[r]())); + } + + private void CB_SLL(byte cmd) // *SLL r + { + var r = cmd & 7; + _regSetters[r](ALU_SLL(_regGetters[r]())); + } + + private void CB_SRL(byte cmd) // SRL r + { + var r = cmd & 7; + _regSetters[r](ALU_SRL(_regGetters[r]())); + } + + private void CB_BIT(byte cmd) // BIT r + { + var r = cmd & 7; + var b = (cmd & 0x38) >> 3; + + ALU_BIT(_regGetters[r](), b); + } + + private void CB_RES(byte cmd) // RES r + { + var r = cmd & 7; + var m = ~(1 << ((cmd & 0x38) >> 3)); + + _regSetters[r]((byte)(_regGetters[r]() & m)); + } + + private void CB_SET(byte cmd) // SET r + { + var r = cmd & 7; + var m = 1 << ((cmd & 0x38) >> 3); + + _regSetters[r]((byte)(_regGetters[r]() | m)); + } + + private void CB_RLCHL(byte cmd) // RLC (HL) + { + // 15T (4, 4, 4, 3) + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val = ALU_RLC(val); + WRMEM(regs.HL, val); Tact += 3; + } + + private void CB_RRCHL(byte cmd) // RRC (HL) + { + // 15T (4, 4, 4, 3) + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val = ALU_RRC(val); + WRMEM(regs.HL, val); Tact += 3; + } + + private void CB_RLHL(byte cmd) // RL (HL) + { + // 15T (4, 4, 4, 3) + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val = ALU_RL(val); + WRMEM(regs.HL, val); Tact += 3; + } + + private void CB_RRHL(byte cmd) // RR (HL) [15T] + { + // 15T (4, 4, 4, 3) + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val = ALU_RR(val); + WRMEM(regs.HL, val); Tact += 3; + } + + private void CB_SLAHL(byte cmd) // SLA (HL) [15T] + { + // 15T (4, 4, 4, 3) + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val = ALU_SLA(val); + WRMEM(regs.HL, val); Tact += 3; + } + + private void CB_SRAHL(byte cmd) // SRA (HL) [15T] + { + // 15T (4, 4, 4, 3) + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val = ALU_SRA(val); + WRMEM(regs.HL, val); Tact += 3; + } + + private void CB_SLLHL(byte cmd) // *SLL (HL) [15T] + { + // 15T (4, 4, 4, 3) + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val = ALU_SLL(val); + WRMEM(regs.HL, val); Tact += 3; + } + + private void CB_SRLHL(byte cmd) // SRL (HL) + { + // 15T (4, 4, 4, 3) + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val = ALU_SRL(val); + WRMEM(regs.HL, val); Tact += 3; + } + + private void CB_BITHL(byte cmd) // BIT (HL) [12T] + { + // 12T (4, 4, 4) + var b = (cmd & 0x38) >> 3; + + var val = RDMEM(regs.HL); Tact += 4; + ALU_BITMEM(val, b); + } + + private void CB_RESHL(byte cmd) // RES (HL) [15T] + { + // 15 (4, 4, 4, 3) + var m = (byte)~(1 << ((cmd & 0x38) >> 3)); + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val &= m; + WRMEM(regs.HL, val); Tact += 3; + } + + private void CB_SETHL(byte cmd) // SET (HL) [15T] + { + // 15T (4, 4, 4, 3) + var m = (byte)(1 << ((cmd & 0x38) >> 3)); + + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + val |= m; + WRMEM(regs.HL, val); Tact += 3; + } + } +} diff --git a/Debuger/Cpu/Processor/Z80Cpu.OpcodesEd.cs b/Debuger/Cpu/Processor/Z80Cpu.OpcodesEd.cs new file mode 100644 index 0000000..441ccc4 --- /dev/null +++ b/Debuger/Cpu/Processor/Z80Cpu.OpcodesEd.cs @@ -0,0 +1,880 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * Description: Z80 CPU Emulator [ED prefixed opcode part] + * Date: 13.04.2007 + * + */ + + +using System; +namespace ZXMAK2.Engine.Cpu.Processor +{ + public partial class Z80Cpu + { + private void ED_LDI(byte cmd) + { + // 16T (4, 4, 3, 5) + + var val = RDMEM(regs.HL); Tact += 3; + + WRMEM(regs.DE, val); Tact += 3; + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + + regs.HL++; + regs.DE++; + regs.BC--; + val += regs.A; + + regs.F = (byte)((regs.F & CpuFlags.NotHPNF3F5) | + (val & CpuFlags.F3) | ((val << 4) & CpuFlags.F5)); + if (regs.BC != 0) regs.F |= CpuFlags.P; + } + + private void ED_CPI(byte cmd) + { + // 16T (4, 4, 3, 5) + + var cf = regs.F & CpuFlags.C; + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + + regs.HL++; + regs.F = (byte)(CpuTables.Cpf8b[regs.A * 0x100 + val] + cf); + if (--regs.BC != 0) regs.F |= CpuFlags.P; + regs.MW++; + } + + private void ED_INI(byte cmd) // INI [16T] + { + // 16T (4, 5, 3, 4) + + RDNOMREQ(regs.IR); Tact++; + + var val = RDPORT(regs.BC); Tact += 3; + + WRMEM(regs.HL, val); Tact += 3; + + regs.MW = (ushort)(regs.BC + 1); + regs.HL++; + regs.B--; + + //FUSE + byte flgtmp = (byte)(val + regs.C + 1); + regs.F = (byte)(CpuTables.Logf[regs.B] & CpuFlags.NotP); + if ((CpuTables.Logf[(flgtmp & 0x07) ^ regs.B] & CpuFlags.P) != 0) regs.F |= CpuFlags.P; + if (flgtmp < val) regs.F |= CpuFlags.HC; + if ((val & 0x80) != 0) regs.F |= CpuFlags.N; + + Tact++; //?? really? + } + + private void ED_OUTI(byte cmd) // OUTI [16T] + { + // 16 (4, 5, 3, 4) + + RDNOMREQ(regs.IR); Tact++; + regs.B--; + + var val = RDMEM(regs.HL); Tact += 3; + + WRPORT(regs.BC, val); Tact += 3; + + regs.MW = (ushort)(regs.BC + 1); + regs.HL++; + + //FUSE + byte flgtmp = (byte)(val + regs.L); + regs.F = (byte)(CpuTables.Logf[regs.B] & CpuFlags.NotP); + if ((CpuTables.Logf[(flgtmp & 0x07) ^ regs.B] & CpuFlags.P) != 0) regs.F |= CpuFlags.P; + if (flgtmp < val) regs.F |= CpuFlags.HC; + if ((val & 0x80) != 0) regs.F |= CpuFlags.N; + + Tact++; //?? really? + } + + private void ED_LDD(byte cmd) + { + // 16T (4, 4, 3, 5) + + var val = RDMEM(regs.HL); Tact += 3; + + WRMEM(regs.DE, val); Tact += 3; + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + + regs.HL--; + regs.DE--; + regs.BC--; + val += regs.A; + + regs.F = (byte)((regs.F & CpuFlags.NotHPNF3F5) | + (val & CpuFlags.F3) | ((val << 4) & CpuFlags.F5)); + if (regs.BC != 0) regs.F |= CpuFlags.P; + } + + private void ED_CPD(byte cmd) + { + // 16T (4, 4, 3, 5) + + var cf = regs.F & CpuFlags.C; + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + + regs.HL--; + regs.BC--; + regs.MW--; + regs.F = (byte)(CpuTables.Cpf8b[regs.A * 0x100 + val] + cf); + if (regs.BC != 0) regs.F |= CpuFlags.P; + } + + private void ED_IND(byte cmd) // IND [16T] + { + // 16T (4, 5, 3, 4) + + RDNOMREQ(regs.IR); Tact++; + + var val = RDPORT(regs.BC); Tact += 3; + + WRMEM(regs.HL, val); Tact += 3; + + regs.MW = (ushort)(regs.BC - 1); + regs.HL--; + regs.B--; + + //FUSE + byte flgtmp = (byte)(val + regs.C - 1); + regs.F = (byte)(CpuTables.Logf[regs.B] & CpuFlags.NotP); + if ((CpuTables.Logf[(flgtmp & 0x07) ^ regs.B] & CpuFlags.P) != 0) regs.F |= CpuFlags.P; + if (flgtmp < val) regs.F |= CpuFlags.HC; + if ((val & 0x80) != 0) regs.F |= CpuFlags.N; + + Tact++; // ?? really? + } + + private void ED_OUTD(byte cmd) // OUTD [16T] + { + // 16T (4, 5, 3, 4) + + RDNOMREQ(regs.IR); Tact++; + + regs.B--; + var val = RDMEM(regs.HL); Tact += 3; + + WRPORT(regs.BC, val); Tact += 3; + + regs.MW = (ushort)(regs.BC - 1); + regs.HL--; + + //FUSE + byte flgtmp = (byte)(val + regs.L); + regs.F = (byte)(CpuTables.Logf[regs.B] & CpuFlags.NotP); + if ((CpuTables.Logf[(flgtmp & 0x07) ^ regs.B] & CpuFlags.P) != 0) regs.F |= CpuFlags.P; + if (flgtmp < val) regs.F |= CpuFlags.HC; + if ((val & 0x80) != 0) regs.F |= CpuFlags.N; + + Tact++; // ?? really? + } + + private void ED_LDIR(byte cmd) + { + //BC==0 => 16T (4, 4, 3, 5) + //BC!=0 => 21T (4, 4, 3, 5, 5) + + var val = RDMEM(regs.HL); Tact += 3; + + WRMEM(regs.DE, val); Tact += 3; + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + + regs.BC--; + val += regs.A; + + regs.F &= CpuFlags.NotHPNF3F5; + if (regs.BC != 0) + { + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + regs.PC--; + regs.MW = regs.PC; + regs.PC--; + + // YF = PCi.13; XF = PCi.11 + regs.F |= (byte)(regs.PCH & CpuFlags.F3F5); + regs.F |= CpuFlags.P; + } + else + { + // YF = PCi.13; XF = PCi.11 + regs.F |= (byte)((val & CpuFlags.F3) | ((val << 4) & CpuFlags.F5)); + } + regs.HL++; + regs.DE++; + } + + private void ED_CPIR(byte cmd) + { + //BC==0 => 16T (4, 4, 3, 5) + //BC!=0 => 21T (4, 4, 3, 5, 5) + + regs.MW++; + var cf = regs.F & CpuFlags.C; + var val = RDMEM(regs.HL); Tact += 3; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + + regs.BC--; + regs.F = (byte)(CpuTables.Cpf8b[regs.A * 0x100 + val] + cf); + + if (regs.BC != 0) + { + regs.F |= CpuFlags.P; + if ((regs.F & CpuFlags.Z) == 0) + { + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + regs.PC--; + regs.MW = regs.PC; + regs.PC--; + + // YF = PCi.13; XF = PCi.11 + regs.F &= CpuFlags.NotF3F5; + regs.F |= (byte)(regs.PCH & CpuFlags.F3F5); + } + } + regs.HL++; + } + + private void ED_INIR(byte cmd) // INIR [16T/21T] + { + // B==0 => 16T (4, 5, 3, 4) + // B!=0 => 21T (4, 5, 3, 4, 5) + + RDNOMREQ(regs.IR); Tact++; + + var val = RDPORT(regs.BC); Tact += 3; + + var nf = (byte)((val >> 6) & CpuFlags.N); + var t = val + ((regs.C + 1) & 0xff); + var hcf = t > 255 ? CpuFlags.HC : 0; + + WRMEM(regs.HL++, val); Tact += 3; + regs.MW = (ushort)(regs.BC + 1); + + regs.B--; Tact++; + var pf = CpuTables.Parity[(t & 7) ^ regs.B]; + + if (regs.B != 0) + { + WRNOMREQ(regs.HL); Tact++; + WRNOMREQ(regs.HL); Tact++; + WRNOMREQ(regs.HL); Tact++; + WRNOMREQ(regs.HL); Tact++; + WRNOMREQ(regs.HL); Tact++; + regs.PC -= 2; + + regs.F = (byte)( + /* ZF = 0 */ + (regs.B & CpuFlags.S) | /* SF = Bo.7 */ + (regs.PCH & CpuFlags.F3F5) | /* YF = PCi.13; XF = PCi.11 */ + nf); /* NF = IO.7 */ + if (hcf != 0) + { + regs.F |= CpuFlags.C; + if ((val & 0x80) != 0) + { + // PF = PF ^ Parity((B - 1) & 0x7) ^ 1; + // HF = (B & 0x0F) == 0x00; + regs.F |= (byte)(pf ^ CpuTables.Parity[(regs.B - 1) & 7] ^ CpuFlags.P); + if ((regs.B & 0x0f) == 0x00) + regs.F |= CpuFlags.H; + } + else + { + // PF = PF ^ Parity((B + 1) & 0x7) ^ 1; + // HF = (B & 0x0F) == 0x0F; + regs.F |= (byte)(pf ^ CpuTables.Parity[(regs.B + 1) & 7] ^ CpuFlags.P); + if ((regs.B & 0x0f) == 0x0f) + regs.F |= CpuFlags.H; + } + } + else + { + // PF = PF ^ Parity(B & 0x7) ^ 1; + regs.F |= (byte)(pf ^ CpuTables.Parity[regs.B & 7] ^ CpuFlags.P); + } + } + else + { + regs.F = (byte)(CpuFlags.Z | /* ZF = 1; SF, YF, XF = 0 */ + hcf | /* HF, CF = T > 255 */ + pf | /* PF = ((T & 7) ^ Bo).parity */ + nf); /* NF = IO.7 */ + } + } + + private void ED_OTIR(byte cmd) // OTIR [16T/21T] + { + // B==0 => 16T (4, 5, 3, 4) + // B!=0 => 21T (4, 5, 3, 4, 5) + + RDNOMREQ(regs.IR); Tact++; + + var val = RDMEM(regs.HL++); Tact += 3; + regs.MW = (ushort)(regs.BC + 1); + + var nf = (byte)((val >> 6) & CpuFlags.N); + var t = val + regs.L; + var hcf = t > 255 ? CpuFlags.HC : 0; + + regs.B--; + var pf = CpuTables.Parity[(t & 7) ^ regs.B]; + + WRPORT(regs.BC, val); Tact += 3; + Tact++; + + if (regs.B != 0) + { + RDNOMREQ(regs.BC); Tact++; + RDNOMREQ(regs.BC); Tact++; + RDNOMREQ(regs.BC); Tact++; + RDNOMREQ(regs.BC); Tact++; + RDNOMREQ(regs.BC); Tact++; + regs.PC -= 2; + + regs.F = (byte)( + /* ZF = 0 */ + (regs.B & CpuFlags.S) | /* SF = Bo.7 */ + (regs.PCH & CpuFlags.F3F5) |/* YF = PCi.13; XF = PCi.11 */ + nf); /* NF = IO.7 */ + if (hcf != 0) + { + regs.F |= CpuFlags.C; + if ((val & 0x80) != 0) + { + // PF = PF ^ Parity((B - 1) & 0x7) ^ 1; + // HF = (B & 0x0F) == 0x00; + regs.F |= (byte)(pf ^ CpuTables.Parity[(regs.B - 1) & 7] ^ CpuFlags.P); + if ((regs.B & 0x0f) == 0x00) + regs.F |= CpuFlags.H; + } + else + { + // PF = PF ^ Parity((B + 1) & 0x7) ^ 1; + // HF = (B & 0x0F) == 0x0F; + regs.F |= (byte)(pf ^ CpuTables.Parity[(regs.B + 1) & 7] ^ CpuFlags.P); + if ((regs.B & 0x0f) == 0x0f) + regs.F |= CpuFlags.H; + } + } + else + { + // PF = PF ^ Parity(B & 0x7) ^ 1; + regs.F |= (byte)(pf ^ CpuTables.Parity[regs.B & 7] ^ CpuFlags.P); + } + } + else + { + regs.F = (byte)(CpuFlags.Z | /* ZF = 1; SF, YF, XF = 0 */ + hcf | /* HF, CF = T > 255 */ + pf | /* PF = ((T & 7) ^ Bo).parity */ + nf); /* NF = IO.7 */ + } + } + + private void ED_LDDR(byte cmd) + { + //BC==0 => 16T (4, 4, 3, 5) + //BC!=0 => 21T (4, 4, 3, 5, 5) + + var val = RDMEM(regs.HL); Tact += 3; + + WRMEM(regs.DE, val); Tact += 3; + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + + regs.BC--; + val += regs.A; + + regs.F &= CpuFlags.NotHPNF3F5; + if (regs.BC != 0) + { + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + WRNOMREQ(regs.DE); Tact++; + regs.PC--; + regs.MW = regs.PC; + regs.PC--; + + // YF = PCi.13; XF = PCi.11 + regs.F |= (byte)(regs.PCH & CpuFlags.F3F5); + regs.F |= CpuFlags.P; + } + else + { + // YF = PCi.13; XF = PCi.11 + regs.F |= (byte)((val & CpuFlags.F3) | ((val << 4) & CpuFlags.F5)); + } + regs.HL--; + regs.DE--; + } + + private void ED_CPDR(byte cmd) + { + // BC==0 => 16T (4, 4, 3, 5) + // BC!=0 => 21T (4, 4, 3, 5, 5) + + regs.MW--; + var cf = regs.F & CpuFlags.C; + var val = RDMEM(regs.HL); Tact += 3; + + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + regs.BC--; + regs.F = (byte)(CpuTables.Cpf8b[regs.A * 0x100 + val] + cf); + + if (regs.BC != 0) + { + regs.F |= CpuFlags.P; + if ((regs.F & CpuFlags.Z) == 0) + { + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + regs.PC--; + regs.MW = regs.PC; + regs.PC--; + + // YF = PCi.13; XF = PCi.11 + regs.F &= CpuFlags.NotF3F5; + regs.F |= (byte)(regs.PCH & CpuFlags.F3F5); + } + } + regs.HL--; + } + + private void ED_INDR(byte cmd) // INDR [16T/21T] + { + // B==0 => 16 (4, 5, 3, 4) + // B!=0 => 21 (4, 5, 3, 4, 5) + + RDNOMREQ(regs.IR); Tact++; + + var val = RDPORT(regs.BC); Tact += 3; + + var nf = (byte)((val >> 6) & CpuFlags.N); + var t = val + ((regs.C - 1) & 0xff); + var hcf = t > 255 ? CpuFlags.HC : 0; + + WRMEM(regs.HL--, val); Tact += 3; + regs.MW = (ushort)(regs.BC - 1); + + regs.B--; Tact++; + var pf = CpuTables.Parity[(t & 7) ^ regs.B]; + + if (regs.B != 0) + { + WRNOMREQ(regs.HL); Tact++; + WRNOMREQ(regs.HL); Tact++; + WRNOMREQ(regs.HL); Tact++; + WRNOMREQ(regs.HL); Tact++; + WRNOMREQ(regs.HL); Tact++; + regs.PC -= 2; + + regs.F = (byte)( + /* ZF = 0 */ + (regs.B & CpuFlags.S) | /* SF = Bo.7 */ + (regs.PCH & CpuFlags.F3F5) |/* YF = PCi.13; XF = PCi.11 */ + nf); /* NF = IO.7 */ + if (hcf != 0) + { + regs.F |= CpuFlags.C; + if ((val & 0x80) != 0) + { + // PF = PF ^ Parity((B - 1) & 0x7) ^ 1; + // HF = (B & 0x0F) == 0x00; + regs.F |= (byte)(pf ^ CpuTables.Parity[(regs.B - 1) & 7] ^ CpuFlags.P); + if ((regs.B & 0x0f) == 0x00) + regs.F |= CpuFlags.H; + } + else + { + // PF = PF ^ Parity((B + 1) & 0x7) ^ 1; + // HF = (B & 0x0F) == 0x0F; + regs.F |= (byte)(pf ^ CpuTables.Parity[(regs.B + 1) & 7] ^ CpuFlags.P); + if ((regs.B & 0x0f) == 0x0f) + regs.F |= CpuFlags.H; + } + } + else + { + // PF = PF ^ Parity(B & 0x7) ^ 1; + regs.F |= (byte)(pf ^ CpuTables.Parity[regs.B & 7] ^ CpuFlags.P); + } + } + else + { + regs.F = (byte)(CpuFlags.Z | /* ZF = 1; SF, YF, XF = 0 */ + hcf | /* HF, CF = T > 255 */ + pf | /* PF = ((T & 7) ^ Bo).parity */ + nf); /* NF = IO.7 */ + } + } + + private void ED_OTDR(byte cmd) //OTDR [16T/21T] + { + // B==0 => 16T (4, 5, 3, 4) + // B!=0 => 21T (4, 5, 3, 4, 5) + + RDNOMREQ(regs.IR); Tact++; + + var val = RDMEM(regs.HL--); Tact += 3; + regs.MW = (ushort)(regs.BC - 1); + + var nf = (byte)((val >> 6) & CpuFlags.N); + var t = val + regs.L; + var hcf = t > 255 ? CpuFlags.HC : 0; + + regs.B--; + var pf = CpuTables.Parity[(t & 7) ^ regs.B]; + + WRPORT(regs.BC, val); Tact += 3; + Tact++; + + if (regs.B != 0) + { + RDNOMREQ(regs.BC); Tact++; + RDNOMREQ(regs.BC); Tact++; + RDNOMREQ(regs.BC); Tact++; + RDNOMREQ(regs.BC); Tact++; + RDNOMREQ(regs.BC); Tact++; + regs.PC -= 2; + + regs.F = (byte)( + /* ZF = 0 */ + (regs.B & CpuFlags.S) | /* SF = Bo.7 */ + (regs.PCH & CpuFlags.F3F5) |/* YF = PCi.13; XF = PCi.11 */ + nf); /* NF = IO.7 */ + if (hcf != 0) + { + regs.F |= CpuFlags.C; + if ((val & 0x80) != 0) + { + // PF = PF ^ Parity((B - 1) & 0x7) ^ 1; + // HF = (B & 0x0F) == 0x00; + regs.F |= (byte)(pf ^ CpuTables.Parity[(regs.B - 1) & 7] ^ CpuFlags.P); + if ((regs.B & 0x0f) == 0x00) + regs.F |= CpuFlags.H; + } + else + { + // PF = PF ^ Parity((B + 1) & 0x7) ^ 1; + // HF = (B & 0x0F) == 0x0F; + regs.F |= (byte)(pf ^ CpuTables.Parity[(regs.B + 1) & 7] ^ CpuFlags.P); + if ((regs.B & 0x0f) == 0x0f) + regs.F |= CpuFlags.H; + } + } + else + { + // PF = PF ^ Parity(B & 0x7) ^ 1; + regs.F |= (byte)(pf ^ CpuTables.Parity[regs.B & 7] ^ CpuFlags.P); + } + } + else + { + regs.F = (byte)(CpuFlags.Z | /* ZF = 1; SF, YF, XF = 0 */ + hcf | /* HF, CF = T > 255 */ + pf | /* PF = ((T & 7) ^ Bo).parity */ + nf); /* NF = IO.7 */ + } + } + + private void ED_INRC(byte cmd) // in R,(c) [12T] + { + // 12T (4, 4, 4) + var r = (cmd & 0x38) >> 3; + + regs.MW = regs.BC; + var pval = RDPORT(regs.BC); + regs.MW++; + if (r != CpuRegId.F) + _regSetters[r](pval); + regs.F = (byte)(CpuTables.Logf[pval] | (regs.F & CpuFlags.C)); + Tact += 4; + } + + private void ED_OUTCR_NMOS(byte cmd) // out (c),R [12T] + { + // 12T (4, 4, 4) + var r = (cmd & 0x38) >> 3; + + regs.MW = regs.BC; + if (r != CpuRegId.F) + WRPORT(regs.BC, _regGetters[r]()); + else + WRPORT(regs.BC, 0x00); // 0 for Z80 and 0xFF for Z84 + regs.MW++; + Tact += 4; + } + + private void ED_OUTCR_CMOS(byte cmd) // out (c),R [12T] + { + // 12T (4, 4, 4) + var r = (cmd & 0x38) >> 3; + + regs.MW = regs.BC; + if (r != CpuRegId.F) + WRPORT(regs.BC, _regGetters[r]()); + else + WRPORT(regs.BC, 0xFF); // 0 for Z80 and 0xFF for Z84 + regs.MW++; + Tact += 4; + } + + private void ED_ADCHLRR(byte cmd) // adc hl,RR + { + var rr = (cmd & 0x30) >> 4; + + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + + regs.MW = (ushort)(regs.HL + 1); + byte fl = (byte)((((regs.HL & 0x0FFF) + (_pairGetters[rr]() & 0x0FFF) + (regs.F & CpuFlags.C)) >> 8) & CpuFlags.H); + uint tmp = (uint)((regs.HL & 0xFFFF) + (_pairGetters[rr]() & 0xFFFF) + (regs.F & CpuFlags.C)); // AF??? + if ((tmp & 0x10000) != 0) fl |= CpuFlags.C; + if ((tmp & 0xFFFF) == 0) fl |= CpuFlags.Z; + int ri = (int)(short)regs.HL + (int)(short)_pairGetters[rr]() + (int)(regs.F & CpuFlags.C); + if (ri < -0x8000 || ri >= 0x8000) fl |= CpuFlags.P; + regs.HL = (ushort)tmp; + regs.F = (byte)(fl | (regs.H & CpuFlags.SF3F5)); + } + + private void ED_SBCHLRR(byte cmd) // sbc hl,RR + { + var rr = (cmd & 0x30) >> 4; + + RDNOMREQ(regs.IR); Tact += 1; + RDNOMREQ(regs.IR); Tact += 1; + RDNOMREQ(regs.IR); Tact += 1; + RDNOMREQ(regs.IR); Tact += 1; + RDNOMREQ(regs.IR); Tact += 1; + RDNOMREQ(regs.IR); Tact += 1; + RDNOMREQ(regs.IR); Tact += 1; + + regs.MW = (ushort)(regs.HL + 1); + byte fl = CpuFlags.N; + fl |= (byte)((((regs.HL & 0x0FFF) - (_pairGetters[rr]() & 0x0FFF) - (regs.F & CpuFlags.C)) >> 8) & CpuFlags.H); + uint tmp = (uint)((regs.HL & 0xFFFF) - (_pairGetters[rr]() & 0xFFFF) - (regs.F & CpuFlags.C)); // AF??? + if ((tmp & 0x10000) != 0) fl |= CpuFlags.C; + if ((tmp & 0xFFFF) == 0) fl |= CpuFlags.Z; + int ri = (int)(short)regs.HL - (int)(short)_pairGetters[rr]() - (int)(regs.F & CpuFlags.C); + if (ri < -0x8000 || ri >= 0x8000) fl |= CpuFlags.P; + regs.HL = (ushort)tmp; + regs.F = (byte)(fl | (regs.H & CpuFlags.SF3F5)); + } + + private void ED_LDRR_NN_(byte cmd) // ld RR,(NN) + { + // 20T (4, 4, 3, 3, 3, 3) + var rr = (cmd & 0x30) >> 4; + + var adr = (ushort)RDMEM(regs.PC); + regs.PC++; + Tact += 3; + + adr += (ushort)(RDMEM(regs.PC) * 0x100); + regs.PC++; + regs.MW = (ushort)(adr + 1); + Tact += 3; + + var val = (ushort)RDMEM(adr); + Tact += 3; + + val += (ushort)(RDMEM(regs.MW) * 0x100); + _pairSetters[rr](val); + Tact += 3; + } + + private void ED_LD_NN_RR(byte cmd) // ld (NN),RR + { + // 20 (4, 4, 3, 3, 3, 3) + var rr = (cmd & 0x30) >> 4; + + var adr = (ushort)RDMEM(regs.PC); + regs.PC++; + Tact += 3; + + adr += (ushort)(RDMEM(regs.PC) * 0x100); + regs.PC++; + regs.MW = (ushort)(adr + 1); + var val = _pairGetters[rr](); + Tact += 3; + + WRMEM(adr, (byte)val); + Tact += 3; + + WRMEM(regs.MW, (byte)(val >> 8)); + Tact += 3; + } + + private void ED_RETN(byte cmd) // reti/retn + { + // 14T (4, 4, 3, 3) + + IFF1 = IFF2; + var adr = (ushort)RDMEM(regs.SP); + Tact += 3; + + adr += (ushort)(RDMEM(++regs.SP) * 0x100); + ++regs.SP; + regs.PC = adr; + regs.MW = adr; + Tact += 3; + } + + private void ED_IM(byte cmd) // im X + { + var mode = (byte)((cmd & 0x18) >> 3); + if (mode < 2) mode = 1; + mode--; + + IM = mode; + } + + private void ED_LDXRA(byte cmd) // ld I/R,a + { + var ir = (cmd & 0x08) == 0; + + RDNOMREQ(regs.IR); Tact++; + + if (ir) // I + regs.I = regs.A; + else + regs.R = regs.A; + } + + private void ED_LDAXR(byte cmd) // ld a,I/R + { + var ir = (cmd & 0x08) == 0; + + RDNOMREQ(regs.IR); Tact++; + + if (ir) // I + regs.A = regs.I; + else + regs.A = regs.R; + + regs.F = (byte)(((regs.F & CpuFlags.C) | CpuTables.Logf[regs.A]) & CpuFlags.NotP); + switch (_type) + { + case CpuType.ZILOG_NMOS: + case CpuType.NEC_NMOS: + case CpuType.ST_CMOS: + SCANSIG?.Invoke(); + if (!(INT && IFF1) && IFF2) + { + regs.F |= CpuFlags.P; + } + break; + case CpuType.ZILOG_CMOS: + if (IFF2) + { + regs.F |= CpuFlags.P; + } + break; + default: + throw new NotImplementedException(); + } + } + + private void ED_RRD(byte cmd) // RRD + { + // 18T (4, 4, 3, 4, 3) + + var tmp = RDMEM(regs.HL); Tact += 3; + + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + + regs.MW = (ushort)(regs.HL + 1); + var val = (byte)((regs.A << 4) | (tmp >> 4)); + + WRMEM(regs.HL, val); Tact += 3; + regs.A = (byte)((regs.A & 0xF0) | (tmp & 0x0F)); + regs.F = (byte)(CpuTables.Logf[regs.A] | (regs.F & CpuFlags.C)); + } + + private void ED_RLD(byte cmd) // RLD + { + // 18T (4, 4, 3, 4, 3) + + var tmp = RDMEM(regs.HL); Tact += 3; + + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + RDNOMREQ(regs.HL); Tact++; + + regs.MW = (ushort)(regs.HL + 1); + var val = (byte)((regs.A & 0x0F) | (tmp << 4)); + + WRMEM(regs.HL, val); Tact += 3; + regs.A = (byte)((regs.A & 0xF0) | (tmp >> 4)); + regs.F = (byte)(CpuTables.Logf[regs.A] | (regs.F & CpuFlags.C)); + } + + private void ED_NEG(byte cmd) // NEG + { + regs.F = CpuTables.Sbcf[regs.A]; + regs.A = (byte)-regs.A; + } + } +} diff --git a/Debuger/Cpu/Processor/Z80Cpu.OpcodesFx.cs b/Debuger/Cpu/Processor/Z80Cpu.OpcodesFx.cs new file mode 100644 index 0000000..8fee32e --- /dev/null +++ b/Debuger/Cpu/Processor/Z80Cpu.OpcodesFx.cs @@ -0,0 +1,485 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * Description: Z80 CPU Emulator [DD/FD prefixed opcode part] + * Date: 13.04.2007 + * + */ +using System; + + +namespace ZXMAK2.Engine.Cpu.Processor +{ + public partial class Z80Cpu + { + #region FXxx ops... + + private void FX_LDSPHL(byte cmd) // LD SP,IX + { + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + if (FX == CpuModeIndex.Ix) + regs.SP = regs.IX; + else + regs.SP = regs.IY; + } + + private void FX_EX_SP_HL(byte cmd) // EX (SP),IX + { + // 23T (4, 4, 3, 4, 3, 5) + + var tmpsp = regs.SP; + regs.MW = RDMEM(tmpsp); Tact += 3; + tmpsp++; + + regs.MW += (ushort)(RDMEM(tmpsp) * 0x100); Tact += 3; + RDNOMREQ(tmpsp); Tact++; + + if (FX == CpuModeIndex.Ix) + { + WRMEM(tmpsp, regs.XH); Tact += 3; + tmpsp--; + + WRMEM(tmpsp, regs.XL); Tact += 3; + WRNOMREQ(tmpsp); Tact++; + WRNOMREQ(tmpsp); Tact++; + regs.IX = regs.MW; + } + else + { + WRMEM(tmpsp, regs.YH); Tact += 3; + tmpsp--; + + WRMEM(tmpsp, regs.YL); Tact += 3; + WRNOMREQ(tmpsp); Tact++; + WRNOMREQ(tmpsp); Tact++; + regs.IY = regs.MW; + } + } + + private void FX_JP_HL_(byte cmd) // JP (IX) + { + if (FX == CpuModeIndex.Ix) + regs.PC = regs.IX; + else + regs.PC = regs.IY; + } + + private void FX_PUSHIX(byte cmd) // PUSH IX + { + // 15 (4, 5, 3, 3) + + RDNOMREQ(regs.IR); Tact++; + var val = FX == CpuModeIndex.Ix ? regs.IX : regs.IY; + regs.SP--; + + WRMEM(regs.SP, (byte)(val >> 8)); Tact += 3; + regs.SP--; + + WRMEM(regs.SP, (byte)val); Tact += 3; + } + + private void FX_POPIX(byte cmd) // POP IX + { + // 14T (4, 4, 3, 3) + + var val = (ushort)RDMEM(regs.SP); + regs.SP++; + Tact += 3; + + val |= (ushort)(RDMEM(regs.SP) << 8); + regs.SP++; + if (FX == CpuModeIndex.Ix) + regs.IX = val; + else + regs.IY = val; + Tact += 3; + } + + private void FX_ALUAXH(byte cmd) // ADD/ADC/SUB/SBC/AND/XOR/OR/CP A,XH + { + byte val; + if (FX == CpuModeIndex.Ix) + val = (byte)(regs.IX >> 8); + else + val = (byte)(regs.IY >> 8); + _alualg[(cmd & 0x38) >> 3](val); + } + + private void FX_ALUAXL(byte cmd) // ADD/ADC/SUB/SBC/AND/XOR/OR/CP A,XL + { + byte val; + if (FX == CpuModeIndex.Ix) + val = (byte)regs.IX; + else + val = (byte)regs.IY; + _alualg[(cmd & 0x38) >> 3](val); + } + + private void FX_ALUA_IX_(byte cmd) // ADD/ADC/SUB/SBC/AND/XOR/OR/CP A,(IX) + { + // 19T (4, 4, 3, 5, 3) + var op = (cmd & 0x38) >> 3; + + int drel = (sbyte)RDMEM(regs.PC); Tact += 3; + + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + + regs.PC++; + regs.MW = FX == CpuModeIndex.Ix ? regs.IX : regs.IY; + regs.MW = (ushort)(regs.MW + drel); + + var val = RDMEM(regs.MW); Tact += 3; + _alualg[op](val); + } + + private void FX_ADDIXRR(byte cmd) // ADD IX,RR + { + var rr = (cmd & 0x30) >> 4; + + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + var rde = rr switch + { + 0 => regs.BC, + 1 => regs.DE, + 2 => FX == CpuModeIndex.Ix ? regs.IX : regs.IY, + 3 => regs.SP, + _ => throw new ArgumentOutOfRangeException(nameof(cmd), "(cmd & 0x30) >> 4 is out of range"), + }; + if (FX == CpuModeIndex.Ix) + { + regs.MW = (ushort)(regs.IX + 1); + regs.IX = ALU_ADDHLRR(regs.IX, rde); + } + else + { + regs.MW = (ushort)(regs.IY + 1); + regs.IY = ALU_ADDHLRR(regs.IY, rde); + } + } + + private void FX_DECIX(byte cmd) // DEC IX + { + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + if (FX == CpuModeIndex.Ix) + regs.IX--; + else + regs.IY--; + } + + private void FX_INCIX(byte cmd) // INC IX + { + RDNOMREQ(regs.IR); Tact++; + RDNOMREQ(regs.IR); Tact++; + if (FX == CpuModeIndex.Ix) + regs.IX++; + else + regs.IY++; + } + + private void FX_LDIX_N_(byte cmd) // LD IX,(nnnn) + { + // 20 (4, 4, 3, 3, 3, 3) + + var adr = (ushort)RDMEM(regs.PC); + regs.PC++; + Tact += 3; + + adr += (ushort)(RDMEM(regs.PC) * 0x100); + regs.PC++; + regs.MW = (ushort)(adr + 1); + Tact += 3; + + var val = (ushort)RDMEM(adr); + Tact += 3; + + val += (ushort)(RDMEM(regs.MW) * 0x100); + if (FX == CpuModeIndex.Ix) + regs.IX = val; + else + regs.IY = val; + Tact += 3; + } + + private void FX_LD_NN_IX(byte cmd) // LD (nnnn),IX + { + // 20 (4, 4, 3, 3, 3, 3) + + var hl = FX == CpuModeIndex.Ix ? regs.IX : regs.IY; + var adr = (ushort)RDMEM(regs.PC); + regs.PC++; + Tact += 3; + + adr += (ushort)(RDMEM(regs.PC) * 0x100); + regs.PC++; + regs.MW = (ushort)(adr + 1); + Tact += 3; + + WRMEM(adr, (byte)hl); + Tact += 3; + + WRMEM(regs.MW, (byte)(hl >> 8)); + Tact += 3; + } + + private void FX_LDIXNNNN(byte cmd) // LD IX,nnnn + { + // 14 (4, 4, 3, 3) + + var val = (ushort)RDMEM(regs.PC); + regs.PC++; + Tact += 3; + + val |= (ushort)(RDMEM(regs.PC) << 8); + regs.PC++; + if (FX == CpuModeIndex.Ix) + regs.IX = val; + else + regs.IY = val; + Tact += 3; + } + + private void FX_DEC_IX_(byte cmd) // DEC (IX) + { + // 23T (4, 4, 3, 5, 4, 3) + + int drel = (sbyte)RDMEM(regs.PC); Tact += 3; + + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + + regs.PC++; + regs.MW = FX == CpuModeIndex.Ix ? regs.IX : regs.IY; + regs.MW = (ushort)(regs.MW + drel); + + var val = RDMEM(regs.MW); Tact += 3; + RDNOMREQ(regs.MW); Tact++; + val = ALU_DECR(val); + WRMEM(regs.MW, val); Tact += 3; + } + + private void FX_INC_IX_(byte cmd) // INC (IX) + { + //23T (4, 4, 3, 5, 4, 3) + + int drel = (sbyte)RDMEM(regs.PC); Tact += 3; + + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + + regs.PC++; + regs.MW = FX == CpuModeIndex.Ix ? regs.IX : regs.IY; + regs.MW = (ushort)(regs.MW + drel); + + var val = RDMEM(regs.MW); Tact += 3; + RDNOMREQ(regs.MW); Tact++; + val = ALU_INCR(val); + WRMEM(regs.MW, val); Tact += 3; + } + + private void FX_LD_IX_NN(byte cmd) // LD (IX),nn + { + // 19 (4, 4, 3, 5, 3) + + int drel = (sbyte)RDMEM(regs.PC); Tact += 3; + regs.PC++; + + var val = RDMEM(regs.PC); Tact += 3; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + regs.PC++; + regs.MW = FX == CpuModeIndex.Ix ? regs.IX : regs.IY; + regs.MW = (ushort)(regs.MW + drel); + WRMEM(regs.MW, val); Tact += 3; + } + + private void FX_LD_IX_R(byte cmd) // LD (IX),R + { + // 19T (4, 4, 3, 5, 3) + var r = cmd & 0x07; + + int drel = (sbyte)RDMEM(regs.PC); Tact += 3; + + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + + regs.PC++; + regs.MW = FX == CpuModeIndex.Ix ? regs.IX : regs.IY; + regs.MW = (ushort)(regs.MW + drel); + WRMEM(regs.MW, _regGetters[r]()); Tact += 3; + } + + private void FX_LDR_IX_(byte cmd) // LD R,(IX) + { + // 19T (4, 4, 3, 5, 3) + var r = (cmd & 0x38) >> 3; + + int drel = (sbyte)RDMEM(regs.PC); Tact += 3; + + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + + regs.PC++; + regs.MW = FX == CpuModeIndex.Ix ? regs.IX : regs.IY; + regs.MW = (ushort)(regs.MW + drel); + + _regSetters[r](RDMEM(regs.MW)); Tact += 3; + } + + #endregion + + #region RdRs... + + private void FX_LDHL(byte cmd) + { + if (FX == CpuModeIndex.Ix) + regs.XH = regs.XL; + else + regs.YH = regs.YL; + } + + private void FX_LDLH(byte cmd) + { + if (FX == CpuModeIndex.Ix) + regs.XL = regs.XH; + else + regs.YL = regs.YH; + } + + private void FX_LDRL(byte cmd) + { + var r = (cmd & 0x38) >> 3; + + if (FX == CpuModeIndex.Ix) + _regSetters[r](regs.XL); + else + _regSetters[r](regs.YL); + } + + private void FX_LDRH(byte cmd) + { + var r = (cmd & 0x38) >> 3; + + if (FX == CpuModeIndex.Ix) + _regSetters[r](regs.XH); + else + _regSetters[r](regs.YH); + } + + private void FX_LDLR(byte cmd) + { + var r = cmd & 0x07; + + if (FX == CpuModeIndex.Ix) + regs.XL = _regGetters[r](); + else + regs.YL = _regGetters[r](); + } + + private void FX_LDHR(byte cmd) + { + var r = cmd & 0x07; + + if (FX == CpuModeIndex.Ix) + regs.XH = _regGetters[r](); + else + regs.YH = _regGetters[r](); + } + + private void FX_LDLNN(byte cmd) // LD XL,nn + { + // 11T (4, 4, 3) + + if (FX == CpuModeIndex.Ix) + regs.XL = RDMEM(regs.PC); + else + regs.YL = RDMEM(regs.PC); + regs.PC++; + Tact += 3; + } + + private void FX_LDHNN(byte cmd) // LD XH,nn + { + // 11T (4, 4, 3) + + if (FX == CpuModeIndex.Ix) + regs.XH = RDMEM(regs.PC); + else + regs.YH = RDMEM(regs.PC); + regs.PC++; + Tact += 3; + } + + private void FX_INCL(byte cmd) // INC XL + { + if (FX == CpuModeIndex.Ix) + regs.XL = ALU_INCR(regs.XL); + else + regs.YL = ALU_INCR(regs.YL); + } + + private void FX_INCH(byte cmd) // INC XH + { + if (FX == CpuModeIndex.Ix) + regs.XH = ALU_INCR(regs.XH); + else + regs.YH = ALU_INCR(regs.YH); + } + + private void FX_DECL(byte cmd) // DEC XL + { + if (FX == CpuModeIndex.Ix) + regs.XL = ALU_DECR(regs.XL); + else + regs.YL = ALU_DECR(regs.YL); + } + + private void FX_DECH(byte cmd) // DEC XH + { + if (FX == CpuModeIndex.Ix) + regs.XH = ALU_DECR(regs.XH); + else + regs.YH = ALU_DECR(regs.YH); + } + + #endregion + } +} diff --git a/Debuger/Cpu/Processor/Z80Cpu.OpcodesFxCb.cs b/Debuger/Cpu/Processor/Z80Cpu.OpcodesFxCb.cs new file mode 100644 index 0000000..159863a --- /dev/null +++ b/Debuger/Cpu/Processor/Z80Cpu.OpcodesFxCb.cs @@ -0,0 +1,263 @@ +/* + * Copyright 2007, 2011, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * Description: Z80 CPU Emulator [FXCB prefixed opcodes part] + * Date: 25.09.2011 + * + */ + + +namespace ZXMAK2.Engine.Cpu.Processor +{ + public partial class Z80Cpu + { + private void FXCB_RLC(byte cmd, ushort adr) // *RLC r + { + // 23T (4, 4, 3, 5, 4, 3) + var r = cmd & 7; + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_RLC(val); + _regSetters[r](val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_RRC(byte cmd, ushort adr) // *RRC r + { + // 23T (4, 4, 3, 5, 4, 3) + var r = cmd & 7; + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_RRC(val); + _regSetters[r](val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_RL(byte cmd, ushort adr) // *RL r + { + // 23T (4, 4, 3, 5, 4, 3) + var r = cmd & 7; + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_RL(val); + _regSetters[r](val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_RR(byte cmd, ushort adr) // *RR r + { + // 23T (4, 4, 3, 5, 4, 3) + var r = cmd & 7; + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_RR(val); + _regSetters[r](val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_SLA(byte cmd, ushort adr) // *SLA r + { + // 23T (4, 4, 3, 5, 4, 3) + var r = cmd & 7; + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_SLA(val); + _regSetters[r](val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_SRA(byte cmd, ushort adr) // *SRA r + { + // 23T (4, 4, 3, 5, 4, 3) + var r = cmd & 7; + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_SRA(val); + _regSetters[r](val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_SLL(byte cmd, ushort adr) // **SLL r + { + // 23T (4, 4, 3, 5, 4, 3) + var r = cmd & 7; + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_SLL(val); + _regSetters[r](val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_SRL(byte cmd, ushort adr) // *SRL r + { + // 23T (4, 4, 3, 5, 4, 3) + var r = cmd & 7; + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_SRL(val); + _regSetters[r](val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_RES(byte cmd, ushort adr) // *RES b,r,(IX+drel) + { + // 23T (4, 4, 3, 5, 4, 3) + var m = (byte)~(1 << ((cmd & 0x38) >> 3)); + var r = cmd & 7; + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val &= m; + _regSetters[r](val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_SET(byte cmd, ushort adr) // *SET b,r,(IX+drel) + { + // 23T (4, 4, 3, 5, 4, 3) + var m = (byte)(1 << ((cmd & 0x38) >> 3)); + var r = cmd & 7; + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val |= m; + _regSetters[r](val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_RLCIX(byte cmd, ushort adr) // RLC (IX+) [23T] + { + // 23T (4, 4, 3, 5, 4, 3) + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_RLC(val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_RRCIX(byte cmd, ushort adr) // RRC (IX+) + { + // 23T (4, 4, 3, 5, 4, 3) + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_RRC(val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_RLIX(byte cmd, ushort adr) // RL (IX+) + { + // 23T (4, 4, 3, 5, 4, 3) + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_RL(val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_RRIX(byte cmd, ushort adr) // RR (IX+) + { + // 23T (4, 4, 3, 5, 4, 3) + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_RR(val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_SLAIX(byte cmd, ushort adr) // SLA (IX+) + { + // 23T (4, 4, 3, 5, 4, 3) + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_SLA(val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_SRAIX(byte cmd, ushort adr) // SRA (IX+) + { + // 23T (4, 4, 3, 5, 4, 3) + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_SRA(val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_SLLIX(byte cmd, ushort adr) // *SLL (IX+) + { + // 23T (4, 4, 3, 5, 4, 3) + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_SLL(val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_SRLIX(byte cmd, ushort adr) // SRL (IX+) + { + // 23T (4, 4, 3, 5, 4, 3) + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val = ALU_SRL(val); + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_BITIX(byte cmd, ushort adr) // BIT (IX+) + { + // 20T (4, 4, 3, 5, 4) + var b = (cmd & 0x38) >> 3; + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + ALU_BITMEM(val, b); + } + + private void FXCB_RESIX(byte cmd, ushort adr) // RES (IX+) + { + // 23T (4, 4, 3, 5, 4, 3) + var m = (byte)~(1 << ((cmd & 0x38) >> 3)); + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val &= m; + WRMEM(adr, val); Tact += 3; + } + + private void FXCB_SETIX(byte cmd, ushort adr) // SET (IX+) + { + // 23T (4, 4, 3, 5, 4, 3) + var m = (byte)(1 << ((cmd & 0x38) >> 3)); + + var val = RDMEM(adr); Tact += 3; + RDNOMREQ(adr); Tact++; + val |= m; + WRMEM(adr, val); Tact += 3; + } + } +} diff --git a/Debuger/Cpu/Processor/Z80Cpu.Tables.cs b/Debuger/Cpu/Processor/Z80Cpu.Tables.cs new file mode 100644 index 0000000..67e24c4 --- /dev/null +++ b/Debuger/Cpu/Processor/Z80Cpu.Tables.cs @@ -0,0 +1,297 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + */ +using System; + + +namespace ZXMAK2.Engine.Cpu.Processor +{ + public partial class Z80Cpu + { + private readonly Action[] _opcodes; + private readonly Action[] _opcodesFx; + private readonly Action[] _opcodesEd; + private readonly Action[] _opcodesCb; + private readonly Action[] _opcodesFxCb; + private readonly Action[] _alualg; + private readonly Func[] _pairGetters; + private readonly Action[] _pairSetters; + private readonly Func[] _regGetters; + private readonly Action[] _regSetters; + + + + private Action[] CreateOpcodes() + { + Action scf; + Action ccf; + switch (_type) + { + case CpuType.ZILOG_NMOS: + case CpuType.ZILOG_CMOS: + scf = SCF_ZILOG; + ccf = CCF_ZILOG; + break; + case CpuType.NEC_NMOS: + scf = SCF_NEC_NMOS; + ccf = CCF_NEC_NMOS; + break; + case CpuType.ST_CMOS: + scf = SCF_ST_CMOS; + ccf = CCF_ST_CMOS; + break; + default: throw new NotImplementedException(); + } + + var opcodes = new Action[256] + { +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + null, LDRRNNNN, LD_RR_A, INCRR, INCR, DECR, LDRNN, RLCA, EXAFAF, ADDHLRR, LDA_RR_, DECRR, INCR, DECR, LDRNN, RRCA, // 00..0F + DJNZ, LDRRNNNN, LD_RR_A, INCRR, INCR, DECR, LDRNN, RLA, JRNN, ADDHLRR, LDA_RR_, DECRR, INCR, DECR, LDRNN, RRA, // 10..1F + JRXNN, LDRRNNNN, LD_NN_HL, INCRR, INCR, DECR, LDRNN, DAA, JRXNN, ADDHLRR, LDHL_NN_, DECRR, INCR, DECR, LDRNN, CPL, // 20..2F + JRXNN, LDRRNNNN, LD_NN_A, INCRR, INC_HL_, DEC_HL_, LD_HL_NN, scf, JRXNN, ADDHLRR, LDA_NN_, DECRR, INCR, DECR, LDRNN, ccf, // 30..3F + + null, LDRdRs, LDRdRs, LDRdRs, LDRdRs, LDRdRs, LDR_HL_, LDRdRs, LDRdRs, null, LDRdRs, LDRdRs, LDRdRs, LDRdRs, LDR_HL_, LDRdRs, // 40..4F + LDRdRs, LDRdRs, null, LDRdRs, LDRdRs, LDRdRs, LDR_HL_, LDRdRs, LDRdRs, LDRdRs, LDRdRs, null, LDRdRs, LDRdRs, LDR_HL_, LDRdRs, // 50..5F + LDRdRs, LDRdRs, LDRdRs, LDRdRs, null, LDRdRs, LDR_HL_, LDRdRs, LDRdRs, LDRdRs, LDRdRs, LDRdRs, LDRdRs, null, LDR_HL_, LDRdRs, // 60..6F + LD_HL_R, LD_HL_R, LD_HL_R, LD_HL_R, LD_HL_R, LD_HL_R, HALT, LD_HL_R, LDRdRs, LDRdRs, LDRdRs, LDRdRs, LDRdRs, LDRdRs, LDR_HL_, null, // 70..7F + + ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUA_HL_, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUA_HL_, ALUAR, // 80..8F + ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUA_HL_, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUA_HL_, ALUAR, // 90..9F + ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUA_HL_, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUA_HL_, ALUAR, // A0..AF + ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUA_HL_, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, ALUA_HL_, ALUAR, // B0..BF + + RETX, POPRR, JPXNN, JPNNNN, CALLXNNNN, PUSHRR, ALUAN, RSTNN, RETX, RET, JPXNN, null, CALLXNNNN, CALLNNNN, ALUAN, RSTNN, // C0..CF + RETX, POPRR, JPXNN, OUT_NN_A, CALLXNNNN, PUSHRR, ALUAN, RSTNN, RETX, EXX, JPXNN, INA_NN_, CALLXNNNN, null, ALUAN, RSTNN, // D0..DF + RETX, POPRR, JPXNN, EX_SP_HL, CALLXNNNN, PUSHRR, ALUAN, RSTNN, RETX, JP_HL_, JPXNN, EXDEHL, CALLXNNNN, null, ALUAN, RSTNN, // E0..EF + RETX, POPRR, JPXNN, DI, CALLXNNNN, PUSHRR, ALUAN, RSTNN, RETX, LDSPHL, JPXNN, EI, CALLXNNNN, null, ALUAN, RSTNN, // F0..FF + }; + return opcodes; + // patch opcodes with optimized version + //for (var cmd = 0; cmd < 0x100; cmd++) + //{ + // opcodes[cmd] = + // EmitOpcodeLdRegReg(cmd) ?? + // EmitOpcodeJrXdisp(cmd) ?? + // opcodes[cmd]; + //} + //return opcodes; + } + + private Action[] CreateOpcodesFx() + { + Action scf; + Action ccf; + switch (_type) + { + case CpuType.ZILOG_NMOS: + case CpuType.ZILOG_CMOS: + scf = SCF_ZILOG; + ccf = CCF_ZILOG; + break; + case CpuType.NEC_NMOS: + scf = SCF_NEC_NMOS; + ccf = CCF_NEC_NMOS; + break; + case CpuType.ST_CMOS: + scf = SCF_ST_CMOS; + ccf = CCF_ST_CMOS; + break; + default: throw new NotImplementedException(); + } + + return + [ +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + null, LDRRNNNN, LD_RR_A, INCRR, INCR, DECR, LDRNN, RLCA, EXAFAF, FX_ADDIXRR, LDA_RR_, DECRR, INCR, DECR, LDRNN, RRCA, // 00..0F + DJNZ, LDRRNNNN, LD_RR_A, INCRR, INCR, DECR, LDRNN, RLA, JRNN, FX_ADDIXRR, LDA_RR_, DECRR, INCR, DECR, LDRNN, RRA, // 10..1F + JRXNN, FX_LDIXNNNN, FX_LD_NN_IX, FX_INCIX, FX_INCH, FX_DECH, FX_LDHNN, DAA, JRXNN, FX_ADDIXRR, FX_LDIX_N_, FX_DECIX, FX_INCL, FX_DECL, FX_LDLNN, CPL, // 20..2F + JRXNN, LDRRNNNN, LD_NN_A, INCRR, FX_INC_IX_, FX_DEC_IX_, FX_LD_IX_NN, scf, JRXNN, FX_ADDIXRR, LDA_NN_, DECRR, INCR, DECR, LDRNN, ccf, // 30..3F + + null, LDRdRs, LDRdRs, LDRdRs, FX_LDRH, FX_LDRL, FX_LDR_IX_, LDRdRs, LDRdRs, null, LDRdRs, LDRdRs, FX_LDRH, FX_LDRL, FX_LDR_IX_, LDRdRs, // 40..4F + LDRdRs, LDRdRs, null, LDRdRs, FX_LDRH, FX_LDRL, FX_LDR_IX_, LDRdRs, LDRdRs, LDRdRs, LDRdRs, null, FX_LDRH, FX_LDRL, FX_LDR_IX_, LDRdRs, // 50..5F + FX_LDHR, FX_LDHR, FX_LDHR, FX_LDHR, null, FX_LDHL, FX_LDR_IX_, FX_LDHR, FX_LDLR, FX_LDLR, FX_LDLR, FX_LDLR, FX_LDLH, null, FX_LDR_IX_, FX_LDLR,// 60..6F + FX_LD_IX_R, FX_LD_IX_R, FX_LD_IX_R, FX_LD_IX_R, FX_LD_IX_R, FX_LD_IX_R, HALT, FX_LD_IX_R, LDRdRs, LDRdRs, LDRdRs, LDRdRs, FX_LDRH, FX_LDRL, FX_LDR_IX_, null, // 70..7F + + ALUAR, ALUAR, ALUAR, ALUAR, FX_ALUAXH, FX_ALUAXL, FX_ALUA_IX_, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, FX_ALUAXH, FX_ALUAXL, FX_ALUA_IX_, ALUAR, // 80..8F + ALUAR, ALUAR, ALUAR, ALUAR, FX_ALUAXH, FX_ALUAXL, FX_ALUA_IX_, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, FX_ALUAXH, FX_ALUAXL, FX_ALUA_IX_, ALUAR, // 90..9F + ALUAR, ALUAR, ALUAR, ALUAR, FX_ALUAXH, FX_ALUAXL, FX_ALUA_IX_, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, FX_ALUAXH, FX_ALUAXL, FX_ALUA_IX_, ALUAR, // A0..AF + ALUAR, ALUAR, ALUAR, ALUAR, FX_ALUAXH, FX_ALUAXL, FX_ALUA_IX_, ALUAR, ALUAR, ALUAR, ALUAR, ALUAR, FX_ALUAXH, FX_ALUAXL, FX_ALUA_IX_, ALUAR, // B0..BF + + RETX, POPRR, JPXNN, JPNNNN, CALLXNNNN, PUSHRR, ALUAN, RSTNN, RETX, RET, JPXNN, null, CALLXNNNN, CALLNNNN, ALUAN, RSTNN, // C0..CF + RETX, POPRR, JPXNN, OUT_NN_A, CALLXNNNN, PUSHRR, ALUAN, RSTNN, RETX, EXX, JPXNN, INA_NN_, CALLXNNNN, null, ALUAN, RSTNN, // D0..DF + RETX, FX_POPIX, JPXNN, FX_EX_SP_HL, CALLXNNNN, FX_PUSHIX, ALUAN, RSTNN, RETX, FX_JP_HL_, JPXNN, EXDEHL, CALLXNNNN, null, ALUAN, RSTNN, // E0..EF + RETX, POPRR, JPXNN, DI, CALLXNNNN, PUSHRR, ALUAN, RSTNN, RETX, FX_LDSPHL, JPXNN, EI, CALLXNNNN, null, ALUAN, RSTNN, // F0..FF + ]; + } + + private Action[] CreateOpcodesEd() + { + Action ed_outcr = _type switch + { + CpuType.ZILOG_NMOS or CpuType.NEC_NMOS => ED_OUTCR_NMOS, + CpuType.ST_CMOS or CpuType.ZILOG_CMOS => ED_OUTCR_CMOS, + _ => throw new NotImplementedException(), + }; + + return + [ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, // 00..0F + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, // 10..1F + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, // 20..2F + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, // 30..3F + ED_INRC, ed_outcr, ED_SBCHLRR, ED_LD_NN_RR, ED_NEG, ED_RETN, ED_IM, ED_LDXRA, ED_INRC, ed_outcr, ED_ADCHLRR, ED_LDRR_NN_, ED_NEG, ED_RETN, ED_IM, ED_LDXRA, // 40..4F + ED_INRC, ed_outcr, ED_SBCHLRR, ED_LD_NN_RR, ED_NEG, ED_RETN, ED_IM, ED_LDAXR, ED_INRC, ed_outcr, ED_ADCHLRR, ED_LDRR_NN_, ED_NEG, ED_RETN, ED_IM, ED_LDAXR, // 50..5F + ED_INRC, ed_outcr, ED_SBCHLRR, ED_LD_NN_RR, ED_NEG, ED_RETN, ED_IM, ED_RRD, ED_INRC, ed_outcr, ED_ADCHLRR, ED_LDRR_NN_, ED_NEG, ED_RETN, ED_IM, ED_RLD, // 60..6F + ED_INRC, ed_outcr, ED_SBCHLRR, ED_LD_NN_RR, ED_NEG, ED_RETN, ED_IM, null, ED_INRC, ed_outcr, ED_ADCHLRR, ED_LDRR_NN_, ED_NEG, ED_RETN, ED_IM, null, // 70..7F + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, // 80..8F + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, // 90..9F + ED_LDI, ED_CPI, ED_INI, ED_OUTI, null, null, null, null, ED_LDD, ED_CPD, ED_IND, ED_OUTD, null, null, null, null, // A0..AF + ED_LDIR, ED_CPIR, ED_INIR, ED_OTIR, null, null, null, null, ED_LDDR, ED_CPDR, ED_INDR, ED_OTDR, null, null, null, null, // B0..BF + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, // C0..CF + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, // D0..DF + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, // E0..EF + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null // F0..FF + ]; + } + + private Action[] CreateOpcodesCb() + { + return + [ +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + CB_RLC, CB_RLC, CB_RLC, CB_RLC, CB_RLC, CB_RLC, CB_RLCHL, CB_RLC, CB_RRC, CB_RRC, CB_RRC, CB_RRC, CB_RRC, CB_RRC, CB_RRCHL, CB_RRC, // 00..0F + CB_RL, CB_RL, CB_RL, CB_RL, CB_RL, CB_RL, CB_RLHL, CB_RL, CB_RR, CB_RR, CB_RR, CB_RR, CB_RR, CB_RR, CB_RRHL, CB_RR, // 10..1F + CB_SLA, CB_SLA, CB_SLA, CB_SLA, CB_SLA, CB_SLA, CB_SLAHL, CB_SLA, CB_SRA, CB_SRA, CB_SRA, CB_SRA, CB_SRA, CB_SRA, CB_SRAHL, CB_SRA, // 20..2F + CB_SLL, CB_SLL, CB_SLL, CB_SLL, CB_SLL, CB_SLL, CB_SLLHL, CB_SLL, CB_SRL, CB_SRL, CB_SRL, CB_SRL, CB_SRL, CB_SRL, CB_SRLHL, CB_SRL, // 30..3F + CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BITHL, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BITHL, CB_BIT, // 40..4F + CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BITHL, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BITHL, CB_BIT, // 50..5F + CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BITHL, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BITHL, CB_BIT, // 60..6F + CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BITHL, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BIT, CB_BITHL, CB_BIT, // 70..7F + CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RESHL, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RESHL, CB_RES, // 80..8F + CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RESHL, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RESHL, CB_RES, // 90..9F + CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RESHL, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RESHL, CB_RES, // A0..AF + CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RESHL, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RES, CB_RESHL, CB_RES, // B0..BF + CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SETHL, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SETHL, CB_SET, // C0..CF + CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SETHL, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SETHL, CB_SET, // D0..DF + CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SETHL, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SETHL, CB_SET, // E0..EF + CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SETHL, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SET, CB_SETHL, CB_SET, // F0..FF + ]; + } + + private Action[] CreateOpcodesFxCb() + { + return + [ + FXCB_RLC, FXCB_RLC, FXCB_RLC, FXCB_RLC, FXCB_RLC, FXCB_RLC, FXCB_RLCIX, FXCB_RLC, FXCB_RRC, FXCB_RRC, FXCB_RRC, FXCB_RRC, FXCB_RRC, FXCB_RRC, FXCB_RRCIX, FXCB_RRC, // 00..0F + FXCB_RL, FXCB_RL, FXCB_RL, FXCB_RL, FXCB_RL, FXCB_RL, FXCB_RLIX, FXCB_RL, FXCB_RR, FXCB_RR, FXCB_RR, FXCB_RR, FXCB_RR, FXCB_RR, FXCB_RRIX, FXCB_RR, // 10..1F + FXCB_SLA, FXCB_SLA, FXCB_SLA, FXCB_SLA, FXCB_SLA, FXCB_SLA, FXCB_SLAIX, FXCB_SLA, FXCB_SRA, FXCB_SRA, FXCB_SRA, FXCB_SRA, FXCB_SRA, FXCB_SRA, FXCB_SRAIX, FXCB_SRA, // 20..2F + FXCB_SLL, FXCB_SLL, FXCB_SLL, FXCB_SLL, FXCB_SLL, FXCB_SLL, FXCB_SLLIX, FXCB_SLL, FXCB_SRL, FXCB_SRL, FXCB_SRL, FXCB_SRL, FXCB_SRL, FXCB_SRL, FXCB_SRLIX, FXCB_SRL, // 30..3F + FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, // 40..4F + FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, // 50..5F + FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, // 60..6F + FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, FXCB_BITIX, // 70..7F + FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RESIX, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RESIX, FXCB_RES, // 80..8F + FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RESIX, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RESIX, FXCB_RES, // 90..9F + FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RESIX, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RESIX, FXCB_RES, // A0..AF + FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RESIX, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RES, FXCB_RESIX, FXCB_RES, // B0..BF + FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SETIX, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SETIX, FXCB_SET, // C0..CF + FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SETIX, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SETIX, FXCB_SET, // D0..DF + FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SETIX, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SETIX, FXCB_SET, // E0..EF + FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SETIX, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SET, FXCB_SETIX, FXCB_SET, // F0..FF + ]; + } + + private Action[] CreateAluAlg() + { + return + [ + ALU_ADDR, ALU_ADCR, ALU_SUBR, ALU_SBCR, + ALU_ANDR, ALU_XORR, ALU_ORR, ALU_CPR, + ]; + } + + + + #region Optimized emitters + + //private Action EmitOpcodeLdRegReg(int cmd) + //{ + // if (cmd < 0x40 || cmd >= 0x80) + // { + // return null; + // } + // var rsrc = cmd & 0x07; + // var rdst = (cmd & 0x38) >> 3; + // if (rsrc == rdst) + // { + // return rsrc == 6 ? HALT : (Action)null; + // } + // var getter = rsrc != 6 ? regs.CreateRegGetter(rsrc) : + // new Func(() => + // { + // // LD R,(HL) + // // 7T (4, 3) + // var value = RDMEM(regs.HL); Tact += 3; + // return value; + // }); + // var setter = rdst != 6 ? regs.CreateRegSetter(rdst) : + // new Action(arg => + // { + // // LD (HL),R + // // 7T (4, 3) + // WRMEM(regs.HL, arg); Tact += 3; + // }); + // return arg => setter(getter()); + //} + + //private Action EmitOpcodeJrXdisp(int cmd) + //{ + // if (cmd < 0x20 || cmd >= 0x40 || (cmd & 7) != 0) + // { + // return null; + // } + // // JR x,disp + // // false => 7T (4, 3) + // // true => 12 (4, 3, 5) + // var cond = (cmd & 0x18) >> 3; + // var mask = s_conds[cond >> 1]; + // var xor = (cond & 1) == 0 ? -1 : 0; + // return new Action(arg => + // { + // int drel = (sbyte)RDMEM(regs.PC); Tact += 3; + // regs.PC++; + // var f = (regs.AF ^ xor) & mask; + // if (f == 0) + // { + // return; + // } + // RDNOMREQ(regs.PC); Tact++; + // RDNOMREQ(regs.PC); Tact++; + // RDNOMREQ(regs.PC); Tact++; + // RDNOMREQ(regs.PC); Tact++; + // RDNOMREQ(regs.PC); Tact++; + // regs.MW = (ushort)(regs.PC + drel); + // regs.PC = regs.MW; + // }); + //} + + #endregion Optimized emitters + } +} diff --git a/Debuger/Cpu/Processor/Z80Cpu.cs b/Debuger/Cpu/Processor/Z80Cpu.cs new file mode 100644 index 0000000..b069938 --- /dev/null +++ b/Debuger/Cpu/Processor/Z80Cpu.cs @@ -0,0 +1,353 @@ +/* + * Copyright 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * Description: Z80 CPU Emulator + * Date: 13.04.2007 + * + */ +using System; +using System.Linq; + +namespace ZXMAK2.Engine.Cpu.Processor +{ + public partial class Z80Cpu + { + public readonly CpuRegs regs = new(); + public int RzxCounter; + public long Tact; + public bool HALTED; + public bool IFF1; + public bool IFF2; + public byte IM; + public bool BINT; // last opcode was EI or DD/FD prefix (to prevent INT handling) + public CpuModeIndex FX; + public CpuModeEx XFX; + public ushort LPC; // last opcode PC + + public bool INT; + public bool NMI; + public bool RST; + public byte BUS = 0xFF; // state of free data bus + + public Action RESET; + public Action NMIACK_M1; + public Action INTACK_M1; + public Func RDMEM_M1; + public Func RDMEM; + public Action WRMEM; + public Func RDPORT; + public Action WRPORT; + public Action RDNOMREQ; + public Action WRNOMREQ; + public Action SCANSIG; + + private CpuType _type = CpuType.NEC_NMOS; + public CpuType Type + { + get { return _type; } + set { _type = value; InitOpcodeTables(); } + } + + public Z80Cpu() + { + _pairGetters = [.. Enumerable.Range(0, 4).Select(regs.CreatePairGetter)]; + _pairSetters = [.. Enumerable.Range(0, 4).Select(regs.CreatePairSetter)]; + _regGetters = [.. Enumerable.Range(0, 8).Select(regs.CreateRegGetter)]; + _regSetters = [.. Enumerable.Range(0, 8).Select(regs.CreateRegSetter)]; + _alualg = CreateAluAlg(); + + _opcodes = new Action[256]; + _opcodesFx = new Action[256]; + _opcodesEd = new Action[256]; + _opcodesCb = new Action[256]; + _opcodesFxCb = new Action[256]; + InitOpcodeTables(); + + regs.AF = 0xFF; + regs.BC = 0xFF; + regs.DE = 0xFF; + regs.HL = 0xFF; + regs._AF = 0xFF; + regs._BC = 0xFF; + regs._DE = 0xFF; + regs._HL = 0xFF; + regs.IX = 0xFF; + regs.IY = 0xFF; + regs.IR = 0xFF; + regs.PC = 0xFF; + regs.SP = 0xFF; + regs.MW = 0xFF; + } + + private void InitOpcodeTables() + { + InitOpcodeTable(_opcodes, CreateOpcodes()); + InitOpcodeTable(_opcodesFx, CreateOpcodesFx()); + InitOpcodeTable(_opcodesEd, CreateOpcodesEd()); + InitOpcodeTable(_opcodesCb, CreateOpcodesCb()); + InitOpcodeTable(_opcodesFxCb, CreateOpcodesFxCb()); + } + + private static void InitOpcodeTable(T[] opcodes, T[] actions) + { + if (opcodes.Length != actions.Length) + throw new ArgumentOutOfRangeException(nameof(opcodes)); + + for (var i = 0; i < opcodes.Length; i++) + { + opcodes[i] = actions[i]; + } + } + + + public void ExecCycle() + { + byte cmd = 0; + if (XFX == CpuModeEx.None && FX == CpuModeIndex.None) + { + if (ProcessSignals()) + return; + LPC = regs.PC; + cmd = RDMEM_M1(LPC); + } + else + { + if (ProcessSignals()) + return; + cmd = RDMEM(regs.PC); + } + Tact += 3; + regs.PC++; + if (XFX == CpuModeEx.Cb) + { + BINT = false; + if (FX != CpuModeIndex.None) + { + // elapsed T: 4, 4, 3 + // will be T: 4, 4, 3, 5 + int drel = (sbyte)cmd; + + regs.MW = FX == CpuModeIndex.Ix ? (ushort)(regs.IX + drel) : (ushort)(regs.IY + drel); + cmd = RDMEM(regs.PC); Tact += 3; + RDNOMREQ(regs.PC); Tact++; + RDNOMREQ(regs.PC); Tact++; + + regs.PC++; + _opcodesFxCb[cmd](cmd, regs.MW); + } + else + { + //Refresh(); + regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80)); + Tact += 1; + RzxCounter++; + + _opcodesCb[cmd](cmd); + } + XFX = CpuModeEx.None; + FX = CpuModeIndex.None; + } + else if (XFX == CpuModeEx.Ed) + { + //Refresh(); + regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80)); + Tact += 1; + RzxCounter++; + + BINT = false; + _opcodesEd[cmd]?.Invoke(cmd); + + XFX = CpuModeEx.None; + FX = CpuModeIndex.None; + } + else if (cmd == 0xDD) + { + //Refresh(); + regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80)); + Tact += 1; + RzxCounter++; + + FX = CpuModeIndex.Ix; + BINT = true; + } + else if (cmd == 0xFD) + { + //Refresh(); + regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80)); + Tact += 1; + RzxCounter++; + + FX = CpuModeIndex.Iy; + BINT = true; + } + else if (cmd == 0xCB) + { + //Refresh(); + regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80)); + Tact += 1; + RzxCounter++; + + XFX = CpuModeEx.Cb; + BINT = true; + } + else if (cmd == 0xED) + { + //Refresh(); + regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80)); + Tact += 1; + RzxCounter++; + + XFX = CpuModeEx.Ed; + BINT = true; + } + else + { + //Refresh(); + regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80)); + Tact += 1; + RzxCounter++; + + BINT = false; + (FX == CpuModeIndex.None ? _opcodes[cmd] : _opcodesFx[cmd])?.Invoke(cmd); + FX = CpuModeIndex.None; + } + } + + private bool ProcessSignals() + { + if (RST) // RESET + { + // 3T + RESET(); + //Refresh(); //+1T + regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80)); + Tact += 1; + RzxCounter++; + + + FX = CpuModeIndex.None; + XFX = CpuModeEx.None; + HALTED = false; + + IFF1 = false; + IFF2 = false; + regs.PC = 0; + regs.IR = 0; + IM = 0; + //regs.SP = 0xFFFF; + //regs.AF = 0xFFFF; + + Tact += 2; // total should be 3T? + return true; + } + else if (NMI) + { + // 11T (5, 3, 3) + + if (HALTED) // workaround for Z80 snapshot halt issue + comfortable debugging + regs.PC++; + + // M1 + NMIACK_M1(); + Tact += 4; + //Refresh(); + regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80)); + Tact += 1; + RzxCounter++; + + + IFF2 = IFF1; + IFF1 = false; + HALTED = false; + regs.SP--; + + // M2 + WRMEM(regs.SP, (byte)(regs.PC >> 8)); + Tact += 3; + regs.SP--; + + // M3 + WRMEM(regs.SP, (byte)regs.PC); + regs.PC = 0x0066; + Tact += 3; + + return true; + } + else if (INT && (!BINT) && IFF1) + { + // http://www.z80.info/interrup.htm + // IM0: 13T (7,3,3) [RST] + // IM1: 13T (7,3,3) + // IM2: 19T (7,3,3,3,3) + + if (HALTED) // workaround for Z80 snapshot halt issue + comfortable debugging + regs.PC++; + + + INTACK_M1(); + // M1: 7T = interrupt acknowledgement; SP-- + regs.SP--; + //if (HALTED) ?? + // Tact += 2; + Tact += 4 + 2; + //Refresh(); + //RzxCounter--; // fix because INTAK should not be calculated + regs.R = (byte)(((regs.R + 1) & 0x7F) | (regs.R & 0x80)); + Tact += 1; + + + IFF1 = false; + IFF2 = false; // proof? + HALTED = false; + + // M2 + WRMEM(regs.SP, (byte)(regs.PC >> 8)); // M2: 3T write PCH; SP-- + regs.SP--; + Tact += 3; + + // M3 + WRMEM(regs.SP, (byte)regs.PC); // M3: 3T write PCL + Tact += 3; + + if (IM == 0) // IM 0: execute instruction taken from BUS with timing T+2??? + { + regs.MW = 0x0038; // workaround: just execute #FF + } + else if (IM == 1) // IM 1: execute #FF with timing T+2 (11+2=13T) + { + regs.MW = 0x0038; + } + else // IM 2: VH=reg.I; VL=BUS; PC=[V] + { + // M4 + ushort adr = (ushort)((regs.IR & 0xFF00) | BUS); + regs.MW = RDMEM(adr); // M4: 3T read VL + Tact += 3; + + // M5 + regs.MW += (ushort)(RDMEM(++adr) * 0x100); // M5: 3T read VH, PC=V + Tact += 3; + } + regs.PC = regs.MW; + + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/Debuger/Cpu/Tools/DasmTool.cs b/Debuger/Cpu/Tools/DasmTool.cs new file mode 100644 index 0000000..205534b --- /dev/null +++ b/Debuger/Cpu/Tools/DasmTool.cs @@ -0,0 +1,1714 @@ +/* + * Copyright 2001, 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + * Description: Z80 Disassembler + * Date: 13.04.2007 + * + */ +using System; + + +namespace ZXMAK2.Engine.Cpu.Tools +{ + public class DasmTool(Func memRead, bool isHex = true) + { + private readonly Func _memRead = memRead; + + public bool IsHex { get; set; } = isHex; + + public string GetMnemonic(int addr, out int length) + { + if (_memRead == null) + { + length = 1; + return "N/A"; + } + var curPtr = 0x0000; + var opCode = _memRead((ushort)(addr + curPtr)); + length = 1; + + var fxReg = "*prefix*"; + var plusMinusOffsetIndex = 0; + + string mnem; + if (opCode == 0xCB) + { + curPtr++; + length++; + var data = _memRead((ushort)(addr + curPtr)); + mnem = CBhZ80Code[data]; + } + else if (opCode == 0xED) + { + curPtr++; + length++; + var data = _memRead((ushort)(addr + curPtr)); + mnem = EDhZ80Code[data]; + if (mnem.Length == 0) + { + mnem = "*NOP"; + } + } + else if ((opCode == 0xDD) || (opCode == 0xFD)) + { + REPREFIX_DDFD: + opCode = _memRead((ushort)(addr + curPtr)); + fxReg = opCode == 0xDD ? "IX" : "IY"; + curPtr++; + plusMinusOffsetIndex = 1; + length++; + var fxCode = _memRead((ushort)(addr + curPtr)); + if (fxCode == 0xCB) + { + curPtr++; + length++; + plusMinusOffsetIndex = 0; + var data = _memRead((ushort)(addr + curPtr + 1)); + mnem = DDFDCBhZ80Code[data]; + if (mnem.Length == 0) + { + length++; // ??? for CBh + } + } + else if (fxCode == 0xED) + { + curPtr++; + length++; + var data = _memRead((ushort)(addr + curPtr)); + mnem = EDhZ80Code[data]; + if (mnem.Length == 0) + { + mnem = "*NOP"; + } + if (mnem[0] != '*') + { + mnem = "*" + mnem; // mark undocumented as "*"... + } + } + else if (fxCode == 0xDD || fxCode == 0xFD) // DD/FD, DD/FD + { + goto REPREFIX_DDFD; + } + else + { + var data = _memRead((ushort)(addr + curPtr)); + mnem = DDFDhZ80Code[data]; + } + + if (mnem.Length == 0) + { + var data = _memRead((ushort)(addr + curPtr)); + mnem = "*" + DirectZ80Code[data]; + } + } + else + { + var data = _memRead((ushort)(addr + curPtr)); + mnem = DirectZ80Code[data]; + } + + if (mnem.IndexOf('$') < 0) + { + return mnem; + } + + do + { + if (mnem.Contains("$R", StringComparison.CurrentCulture)) // Prefix register + { + var pos = mnem.IndexOf("$R"); + if (mnem.Length <= (pos + 1 + 1)) // ixl/ixh -> xl/xh !!!!! + { + mnem = mnem.Remove(pos, 2); + mnem = mnem.Insert(pos, fxReg); + } + else if ((mnem[pos + 2] == 'L') || (mnem[pos + 2] == 'H')) + { + mnem = mnem.Remove(pos, 2); + mnem = mnem.Insert(pos, "" + fxReg[1]); + } + else + { + mnem = mnem.Remove(pos, 2); + mnem = mnem.Insert(pos, fxReg); + } + } + if (mnem.Contains("$PLUS", StringComparison.CurrentCulture)) // PrefixReg+-offset + { + var data = (sbyte)_memRead((ushort)(addr + (curPtr + plusMinusOffsetIndex))); + var txtValue = FormatOffset(data); + + var pos = mnem.IndexOf("$PLUS"); + mnem = mnem.Remove(pos, 5); + mnem = mnem.Insert(pos, txtValue); + length++; + curPtr++; + } + if (mnem.Contains("$S", StringComparison.CurrentCulture)) // Internal bits value + { + var data = _memRead((ushort)(addr + curPtr)); + var bitadr = (data & 0x38) >> 3; + var txtValue = string.Format("{0}", bitadr); + + var pos = mnem.IndexOf("$S"); + mnem = mnem.Remove(pos, 2); + mnem = mnem.Insert(pos, txtValue); + } + if (mnem.Contains("$W", StringComparison.CurrentCulture)) // 2byte value + { + var data = (ushort)(_memRead((ushort)(addr + curPtr + 1)) + 256 * _memRead((ushort)(addr + curPtr + 2))); + var txtValue = FormatWord(data); + + var pos = mnem.IndexOf("$W"); + mnem = mnem.Remove(pos, 2); + mnem = mnem.Insert(pos, txtValue); + length += 2; + } + if (mnem.Contains("$N", StringComparison.CurrentCulture)) // 1byte value + { + var data = _memRead((ushort)(addr + curPtr + 1)); + var txtValue = FormatByte(data); + + var pos = mnem.IndexOf("$N"); + mnem = mnem.Remove(pos, 2); + mnem = mnem.Insert(pos, txtValue); + length++; + } + if (mnem.Contains("$T", StringComparison.CurrentCulture)) // Internal bits value ($S)*8 + { + var data = _memRead((ushort)(addr + curPtr)); + var rstadr = (byte)(((data & 0x38) >> 3) * 8); + var txtValue = FormatByte(rstadr); + + var pos = mnem.IndexOf("$T"); + mnem = mnem.Remove(pos, 2); + mnem = mnem.Insert(pos, txtValue); + } + if (mnem.Contains("$DIS", StringComparison.CurrentCulture)) // 1byte offset value + { + var data = (sbyte)_memRead((ushort)(addr + curPtr + 1)); + var adr = (ushort)((addr + 2 + curPtr) + data); + var txtValue = FormatWord(adr); + + var pos = mnem.IndexOf("$DIS"); + mnem = mnem.Remove(pos, 4); + mnem = mnem.Insert(pos, txtValue); + length++; + } + } while (mnem.Contains('$', StringComparison.CurrentCulture)); + return mnem; + } + + + #region Format + + private string FormatByte(byte val) + { + return IsHex ? + string.Format("#{0:X2}", val) : + string.Format("{0}", val); + } + + private string FormatWord(ushort val) + { + return IsHex ? + string.Format("#{0:X4}", val) : + string.Format("{0}", val); + } + + private string FormatOffset(sbyte val) + { + var absVal = val < 0 ? -val : val; + var result = IsHex ? + string.Format("#{0:X2}", absVal) : + string.Format("{0}", absVal); + var sign = val < 0 ? "-" : "+"; + return sign + result; + } + + #endregion Format + + + #region Table Direct + + private static readonly string[] DirectZ80Code = + [ + "NOP", // 0x00 + "LD BC,$W", + "LD (BC),A", + "INC BC", + "INC B", + "DEC B", + "LD B,$N", + "RLCA", + "EX AF,AF'", + "ADD HL,BC", + "LD A,(BC)", + "DEC BC", + "INC C", + "DEC C", + "LD C,$N", + "RRCA", + "DJNZ $DIS", // 0x10 + "LD DE,$W", + "LD (DE),A", + "INC DE", + "INC D", + "DEC D", + "LD D,$N", + "RLA", + "JR $DIS", + "ADD HL,DE", + "LD A,(DE)", + "DEC DE", + "INC E", + "DEC E", + "LD E,$N", + "RRA", + "JR NZ,$DIS", // 0x20 + "LD HL,$W", + "LD ($W),HL", + "INC HL", + "INC H", + "DEC H", + "LD H,$N", + "DAA", + "JR Z,$DIS", + "ADD HL,HL", + "LD HL,($W)", + "DEC HL", + "INC L", + "DEC L", + "LD L,$N", + "CPL", + "JR NC,$DIS", // 0x30 + "LD SP,$W", + "LD ($W),A", + "INC SP", + "INC (HL)", + "DEC (HL)", + "LD (HL),$N", + "SCF", + "JR C,$DIS", // 0x38 + "ADD HL,SP", + "LD A,($W)", + "DEC SP", + "INC A", + "DEC A", + "LD A,$N", + "CCF", // 0x3F + "LD B,B", // 0x40 + "LD B,C", + "LD B,D", + "LD B,E", + "LD B,H", + "LD B,L", + "LD B,(HL)", + "LD B,A", + "LD C,B", + "LD C,C", + "LD C,D", + "LD C,E", + "LD C,H", + "LD C,L", + "LD C,(HL)", + "LD C,A", + + "LD D,B", // 0x50 + "LD D,C", + "LD D,D", + "LD D,E", + "LD D,H", + "LD D,L", + "LD D,(HL)", + "LD D,A", + + "LD E,B", + "LD E,C", + "LD E,D", + "LD E,E", + "LD E,H", + "LD E,L", + "LD E,(HL)", + "LD E,A", + + "LD H,B", // 0x60 + "LD H,C", + "LD H,D", + "LD H,E", + "LD H,H", + "LD H,L", + "LD H,(HL)", + "LD H,A", + + "LD L,B", + "LD L,C", + "LD L,D", + "LD L,E", + "LD L,H", + "LD L,L", + "LD L,(HL)", + "LD L,A", + + "LD (HL),B", // 0x70 + "LD (HL),C", + "LD (HL),D", + "LD (HL),E", + "LD (HL),H", + "LD (HL),L", + "HALT", + "LD (HL),A", + + "LD A,B", + "LD A,C", + "LD A,D", + "LD A,E", + "LD A,H", + "LD A,L", + "LD A,(HL)", + "LD A,A", + + "ADD A,B", // 0x80 + "ADD A,C", + "ADD A,D", + "ADD A,E", + "ADD A,H", + "ADD A,L", + "ADD A,(HL)", + "ADD A,A", + + "ADC A,B", + "ADC A,C", + "ADC A,D", + "ADC A,E", + "ADC A,H", + "ADC A,L", + "ADC A,(HL)", + "ADC A,A", + + "SUB B", // 0x90 + "SUB C", + "SUB D", + "SUB E", + "SUB H", + "SUB L", + "SUB (HL)", + "SUB A", + + "SBC A,B", + "SBC A,C", + "SBC A,D", + "SBC A,E", + "SBC A,H", + "SBC A,L", + "SBC A,(HL)", + "SBC A,A", + + "AND B", // 0xA0 + "AND C", + "AND D", + "AND E", + "AND H", + "AND L", + "AND (HL)", + "AND A", + + "XOR B", + "XOR C", + "XOR D", + "XOR E", + "XOR H", + "XOR L", + "XOR (HL)", + "XOR A", + + "OR B", // 0xB0 + "OR C", + "OR D", + "OR E", + "OR H", + "OR L", + "OR (HL)", + "OR A", + + "CP B", + "CP C", + "CP D", + "CP E", + "CP H", + "CP L", + "CP (HL)", + "CP A", // 0xBF + + "RET NZ", // 0xC0 + "POP BC", + "JP NZ,$W", + "JP $W", + "CALL NZ,$W", + "PUSH BC", + "ADD A,$N", + "RST $T", + "RET Z", + "RET", + "JP Z,$W", + "*CB", // 0xCB + "CALL Z,$W", + "CALL $W", + "ADC A,$N", + "RST $T", + + "RET NC", // 0xD0 + "POP DE", + "JP NC,$W", + "OUT ($N),A", + "CALL NC,$W", + "PUSH DE", + "SUB $N", + "RST $T", + "RET C", + "EXX", + "JP C,$W", + "IN A,($N)", + "CALL C,$W", + "*IX", // 0xDD + "SBC A,$N", + "RST $T", + + "RET PO", // 0xE0 + "POP HL", + "JP PO,$W", + "EX (SP),HL", + "CALL PO,$W", + "PUSH HL", + "AND $N", + "RST $T", + "RET PE", + "JP (HL)", + "JP PE,$W", + "EX DE,HL", + "CALL PE,$W", + "*ED", // 0xED + "XOR $N", + "RST $T", + + "RET P", // 0xF0 + "POP AF", + "JP P,$W", + "DI", + "CALL P,$W", + "PUSH AF", + "OR $N", + "RST $T", + "RET M", + "LD SP,HL", + "JP M,$W", + "EI", + "CALL M,$W", + "*IY", // 0xFD + "CP $N", + "RST $T" + ]; + + #endregion Direct + + + #region Table #CB + + private static readonly string[] CBhZ80Code = + [ + "RLC B", + "RLC C", + "RLC D", + "RLC E", + "RLC H", + "RLC L", + "RLC (HL)", + "RLC A", + + "RRC B", + "RRC C", + "RRC D", + "RRC E", + "RRC H", + "RRC L", + "RRC (HL)", + "RRC A", + + "RL B", + "RL C", + "RL D", + "RL E", + "RL H", + "RL L", + "RL (HL)", + "RL A", + + "RR B", + "RR C", + "RR D", + "RR E", + "RR H", + "RR L", + "RR (HL)", + "RR A", + + "SLA B", + "SLA C", + "SLA D", + "SLA E", + "SLA H", + "SLA L", + "SLA (HL)", + "SLA A", + + "SRA B", + "SRA C", + "SRA D", + "SRA E", + "SRA H", + "SRA L", + "SRA (HL)", + "SRA A", + + "*SLL B", // SLL or SLI (UNDOCUMENTED) + "*SLL C", + "*SLL D", + "*SLL E", + "*SLL H", + "*SLL L", + "*SLL (HL)", + "*SLL A", + + "SRL B", + "SRL C", + "SRL D", + "SRL E", + "SRL H", + "SRL L", + "SRL (HL)", + "SRL A", + + "BIT $S,B", // Bit 0 + "BIT $S,C", + "BIT $S,D", + "BIT $S,E", + "BIT $S,H", + "BIT $S,L", + "BIT $S,(HL)", + "BIT $S,A", + + "BIT $S,B", // Bit 1 + "BIT $S,C", + "BIT $S,D", + "BIT $S,E", + "BIT $S,H", + "BIT $S,L", + "BIT $S,(HL)", + "BIT $S,A", + + "BIT $S,B", // Bit 2 + "BIT $S,C", + "BIT $S,D", + "BIT $S,E", + "BIT $S,H", + "BIT $S,L", + "BIT $S,(HL)", + "BIT $S,A", + + "BIT $S,B", // Bit 3 + "BIT $S,C", + "BIT $S,D", + "BIT $S,E", + "BIT $S,H", + "BIT $S,L", + "BIT $S,(HL)", + "BIT $S,A", + + "BIT $S,B", // Bit 4 + "BIT $S,C", + "BIT $S,D", + "BIT $S,E", + "BIT $S,H", + "BIT $S,L", + "BIT $S,(HL)", + "BIT $S,A", + + "BIT $S,B", // Bit 5 + "BIT $S,C", + "BIT $S,D", + "BIT $S,E", + "BIT $S,H", + "BIT $S,L", + "BIT $S,(HL)", + "BIT $S,A", + + "BIT $S,B", // Bit 6 + "BIT $S,C", + "BIT $S,D", + "BIT $S,E", + "BIT $S,H", + "BIT $S,L", + "BIT $S,(HL)", + "BIT $S,A", + + "BIT $S,B", // Bit 7 + "BIT $S,C", + "BIT $S,D", + "BIT $S,E", + "BIT $S,H", + "BIT $S,L", + "BIT $S,(HL)", + "BIT $S,A", + + + "RES $S,B", // Bit 0 + "RES $S,C", + "RES $S,D", + "RES $S,E", + "RES $S,H", + "RES $S,L", + "RES $S,(HL)", + "RES $S,A", + + "RES $S,B", // Bit 1 + "RES $S,C", + "RES $S,D", + "RES $S,E", + "RES $S,H", + "RES $S,L", + "RES $S,(HL)", + "RES $S,A", + + "RES $S,B", // Bit 2 + "RES $S,C", + "RES $S,D", + "RES $S,E", + "RES $S,H", + "RES $S,L", + "RES $S,(HL)", + "RES $S,A", + + "RES $S,B", // Bit 3 + "RES $S,C", + "RES $S,D", + "RES $S,E", + "RES $S,H", + "RES $S,L", + "RES $S,(HL)", + "RES $S,A", + + "RES $S,B", // Bit 4 + "RES $S,C", + "RES $S,D", + "RES $S,E", + "RES $S,H", + "RES $S,L", + "RES $S,(HL)", + "RES $S,A", + + "RES $S,B", // Bit 5 + "RES $S,C", + "RES $S,D", + "RES $S,E", + "RES $S,H", + "RES $S,L", + "RES $S,(HL)", + "RES $S,A", + + "RES $S,B", // Bit 6 + "RES $S,C", + "RES $S,D", + "RES $S,E", + "RES $S,H", + "RES $S,L", + "RES $S,(HL)", + "RES $S,A", + + "RES $S,B", // Bit 7 + "RES $S,C", + "RES $S,D", + "RES $S,E", + "RES $S,H", + "RES $S,L", + "RES $S,(HL)", + "RES $S,A", + + "SET $S,B", // Bit 0 + "SET $S,C", + "SET $S,D", + "SET $S,E", + "SET $S,H", + "SET $S,L", + "SET $S,(HL)", + "SET $S,A", + + "SET $S,B", // Bit 1 + "SET $S,C", + "SET $S,D", + "SET $S,E", + "SET $S,H", + "SET $S,L", + "SET $S,(HL)", + "SET $S,A", + + "SET $S,B", // Bit 2 + "SET $S,C", + "SET $S,D", + "SET $S,E", + "SET $S,H", + "SET $S,L", + "SET $S,(HL)", + "SET $S,A", + + "SET $S,B", // Bit 3 + "SET $S,C", + "SET $S,D", + "SET $S,E", + "SET $S,H", + "SET $S,L", + "SET $S,(HL)", + "SET $S,A", + + "SET $S,B", // Bit 4 + "SET $S,C", + "SET $S,D", + "SET $S,E", + "SET $S,H", + "SET $S,L", + "SET $S,(HL)", + "SET $S,A", + + "SET $S,B", // Bit 5 + "SET $S,C", + "SET $S,D", + "SET $S,E", + "SET $S,H", + "SET $S,L", + "SET $S,(HL)", + "SET $S,A", + + "SET $S,B", // Bit 6 + "SET $S,C", + "SET $S,D", + "SET $S,E", + "SET $S,H", + "SET $S,L", + "SET $S,(HL)", + "SET $S,A", + + "SET $S,B", // Bit 7 + "SET $S,C", + "SET $S,D", + "SET $S,E", + "SET $S,H", + "SET $S,L", + "SET $S,(HL)", + "SET $S,A", + ]; + + #endregion Table #CB + + + #region Table #ED + + private static readonly string[] EDhZ80Code = + [ + "", // 0x00 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x08 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x10 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x18 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x20 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x28 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x30 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x38 + "", + "", + "", + "", + "", + "", + "", + + "IN B,(C)", // 0x40 + "OUT (C),B", // 0x41 + "SBC HL,BC", // 0x42 + "LD ($W),BC", // 0x43 + "NEG", // 0x44 + "RETN", // 0x45 + "IM 0", // 0x46 + "LD I,A", // 0x47 + + "IN C,(C)", // 0x48 + "OUT (C),C", // 0x49 + "ADC HL,BC", // 0x4A + "LD BC,($W)", // 0x4B + "*NEG", // 0x4C undocumented + "RETI", // 0x4D + "*IM 0", // 0x4E + "LD R,A", // 0x4F + + "IN D,(C)", // 0x50 + "OUT (C),D", // 0x51 + "SBC HL,DE", // 0x52 + "LD ($W),DE", // 0x53 + "*NEG", // 0x54 + "*RETN", // 0x55 + "IM 1", // 0x56 + "LD A,I", // 0x57 + + "IN E,(C)", // 0x58 + "OUT (C),E", // 0x59 + "ADC HL,DE", // 0x5A + "LD DE,($W)", // 0x5B + "*NEG", // 0x5C + "*RETN", // 0x5D + "IM 2", // 0x5E + "LD A,R", // 0x5F + + "IN H,(C)", // 0x60 + "OUT (C),H", // 0x61 + "SBC HL,HL", // 0x62 + "LD ($W),HL", // 0x63 + "*NEG", // 0x64 + "*RETN", // 0x65 + "*IM 0", // 0x66 + "RRD", // 0x67 + + "IN L,(C)", // 0x68 + "OUT (C),L", // 0x69 + "ADC HL,HL", // 0x6A + "LD HL,($W)", // 0x6B + "*NEG", // 0x6C + "*RETN", // 0x6D + "*IM 0", // 0x6E + "RLD", // 0x6F + + "*IN (C)", // 0x70 NON STORED INPUT PORT! (Only flags) + "*OUT (C),0", // 0x71 + "SBC HL,SP", // 0x72 + "LD ($W),SP", // 0x73 + "*NEG", // 0x74 + "*RETN", // 0x75 + "*IM 1", // 0x76 + "*NOP", // 0x77 + + "IN A,(C)", // 0x78 + "OUT (C),A", // 0x79 + "ADC HL,SP", // 0x7A + "LD SP,($W)", // 0x7B + "*NEG", // 0x7C + "*RETN", // 0x7D + "*IM 2", // 0x7E + "*NOP", // 0x7F + + "", // 0x80 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x88 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x90 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x98 + "", + "", + "", + "", + "", + "", + "", + + "LDI", // 0xA0 + "CPI", // 0xA1 + "INI", // 0xA2 + "OUTI", // 0xA3 + "", + "", + "", + "", + + "LDD", // 0xA8 + "CPD", // 0xA9 + "IND", // 0xAA + "OUTD", // 0xAB + "", + "", + "", + "", + + "LDIR", // 0xB0 + "CPIR", // 0xB1 + "INIR", // 0xB2 + "OTIR", // 0xB3 + "", + "", + "", + "", + + "LDDR", // 0xB8 + "CPDR", // 0xB9 + "INDR", // 0xBA + "OTDR", // 0xBB + "", + "", + "", + "", + + "", // 0xC0 + "", + "", + "", + "", + "", + "", + "", + + "", // 0xC8 + "", + "", + "", + "", + "", + "", + "", + + "", // 0xD0 + "", + "", + "", + "", + "", + "", + "", + + "", // 0xD8 + "", + "", + "", + "", + "", + "", + "", + + "", // 0xE0 + "", + "", + "", + "", + "", + "", + "", + + "", // 0xE8 + "", + "", + "", + "", + "", + "", + "", + + "", // 0xF0 + "", + "", + "", + "", + "", + "", + "", + + "", // 0xF8 + "", + "", + "", + "", + "", + "", + "" + ]; + + #endregion Table #ED + + + #region Table #DD/#FD + + private static readonly string[] DDFDhZ80Code = + [ + "", // 0x00 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x08 + "ADD $R,BC", + "", + "", + "", + "", + "", + "", + + "", // 0x10 + "", + "", + "", + "", + "", + "", + "", + + "", // 0x18 + "ADD $R,DE", + "", + "", + "", + "", + "", + "", + + "", // 0x20 + "LD $R,$W", + "LD ($W),$R", + "INC $R", + "*INC $RH", + "*DEC $RH", + "*LD $RH,$N", + "", + + "", // 0x28 + "ADD $R,$R", + "LD $R,($W)", + "DEC $R", + "*INC $RL", + "*DEC $RL", + "*LD $RL,$N", + "", + + "", // 0x30 + "", + "", + "", + "INC ($R$PLUS)", + "DEC ($R$PLUS)", + "LD ($R$PLUS),$N", + "", + + "", // 0x38 + "ADD $R,SP", + "", + "", + "", + "", + "", + "", + + "", // 0x40 + "", + "", + "", + "*LD B,$RH", + "*LD B,$RL", + "LD B,($R$PLUS)", + "", + + "", // 0x48 + "", + "", + "", + "*LD C,$RH", + "*LD C,$RL", + "LD C,($R$PLUS)", + "", + + "", // 0x50 + "", + "", + "", + "*LD D,$RH", + "*LD D,$RL", + "LD D,($R$PLUS)", + "", + + "", // 0x58 + "", + "", + "", + "*LD E,$RH", + "*LD E,$RL", + "LD E,($R$PLUS)", + "", + + "*LD $RH,B", // 0x60 + "*LD $RH,C", + "*LD $RH,D", + "*LD $RH,E", + "*LD $RH,$RH", + "*LD $RH,$RL", + "LD H,($R$PLUS)", + "*LD $RH,A", + + "*LD $RL,B", // 0x68 + "*LD $RL,C", + "*LD $RL,D", + "*LD $RL,E", + "*LD $RL,$RH", + "*LD $RL,$RL", + "LD L,($R$PLUS)", + "*LD $RL,A", + + "LD ($R$PLUS),B", // 0x70 + "LD ($R$PLUS),C", + "LD ($R$PLUS),D", + "LD ($R$PLUS),E", + "LD ($R$PLUS),H", + "LD ($R$PLUS),L", + "", + "LD ($R$PLUS),A", + + "", // 0x78 + "", + "", + "", + "*LD A,$RH", + "*LD A,$RL", + "LD A,($R$PLUS)", + "", + + "", // 0x80 + "", + "", + "", + "*ADD A,$RH", + "*ADD A,$RL", + "ADD A,($R$PLUS)", + "", + + "", // 0x88 + "", + "", + "", + "*ADC A,$RH", + "*ADC A,$RL", + "ADC A,($R$PLUS)", + "", + + "", // 0x90 + "", + "", + "", + "*SUB $RH", + "*SUB $RL", + "SUB ($R$PLUS)", + "", + + "", // 0x98 + "", + "", + "", + "*SBC A,$RH", + "*SBC A,$RL", + "SBC A,($R$PLUS)", + "", + + "", // 0xA0 + "", + "", + "", + "*AND $RH", + "*AND $RL", + "AND ($R$PLUS)", + "", + + "", // 0xA8 + "", + "", + "", + "*XOR $RH", + "*XOR $RL", + "XOR ($R$PLUS)", + "", + + "", // 0xB0 + "", + "", + "", + "*OR $RH", + "*OR $RL", + "OR ($R$PLUS)", + "", + + "", // 0xB8 + "", + "", + "", + "*CP $RH", + "*CP $RL", + "CP ($R$PLUS)", + "", + + "", // 0xC0 + "", + "", + "", + "", + "", + "", + "", + + "", // 0xC8 + "", + "", + "*DD/FD,CB", + "", + "", + "", + "", + + "", // 0xD0 + "", + "", + "", + "", + "", + "", + "", + + "", // 0xD8 + "", + "", + "", + "", + "*DD/FD,DD", + "", + "", + + "", // 0xE0 + "POP $R", + "", + "EX (SP),$R", + "", + "PUSH $R", + "", + "", + + "", // 0xE8 + "JP ($R)", + "", + "", // EX DE,HL??? + "", + "*DD/FD,ED", + "", + "", + + "", // 0xF0 + "", + "", + "", + "", + "", + "", + "", + + "", // 0xF8 + "LD SP,$R", + "", + "", + "", + "*DD/FD,FD", + "", + "" + ]; + + #endregion Table #DD/#FD + + + #region Table #DD/#FD,#CB + + private static readonly string[] DDFDCBhZ80Code = + [ + "*RLC B,($R$PLUS)", // 0x00 + "*RLC C,($R$PLUS)", + "*RLC D,($R$PLUS)", + "*RLC E,($R$PLUS)", + "*RLC H,($R$PLUS)", + "*RLC L,($R$PLUS)", + "RLC ($R$PLUS)", + "*RLC A,($R$PLUS)", + + "*RRC B,($R$PLUS)", // 0x08 + "*RRC C,($R$PLUS)", + "*RRC D,($R$PLUS)", + "*RRC E,($R$PLUS)", + "*RRC H,($R$PLUS)", + "*RRC L,($R$PLUS)", + "RRC ($R$PLUS)", + "*RRC A,($R$PLUS)", + + "*RL B,($R$PLUS)", // 0x10 + "*RL C,($R$PLUS)", + "*RL D,($R$PLUS)", + "*RL E,($R$PLUS)", + "*RL H,($R$PLUS)", + "*RL L,($R$PLUS)", + "RL ($R$PLUS)", + "*RL A,($R$PLUS)", + + "*RR B,($R$PLUS)", // 0x18 + "*RR C,($R$PLUS)", + "*RR D,($R$PLUS)", + "*RR E,($R$PLUS)", + "*RR H,($R$PLUS)", + "*RR L,($R$PLUS)", + "RR ($R$PLUS)", + "*RR A,($R$PLUS)", + + "*SLA B,($R$PLUS)", // 0x20 + "*SLA C,($R$PLUS)", + "*SLA D,($R$PLUS)", + "*SLA E,($R$PLUS)", + "*SLA H,($R$PLUS)", + "*SLA L,($R$PLUS)", + "SLA ($R$PLUS)", + "*SLA A,($R$PLUS)", + + "*SRA B,($R$PLUS)", // 0x28 + "*SRA C,($R$PLUS)", + "*SRA D,($R$PLUS)", + "*SRA E,($R$PLUS)", + "*SRA H,($R$PLUS)", + "*SRA L,($R$PLUS)", + "SRA ($R$PLUS)", + "*SRA A,($R$PLUS)", + + "*SLL B,($R$PLUS)", // 0x30 + "*SLL C,($R$PLUS)", + "*SLL D,($R$PLUS)", + "*SLL E,($R$PLUS)", + "*SLL H,($R$PLUS)", + "*SLL L,($R$PLUS)", + "*SLL ($R$PLUS)", // SLI or SLL + "*SLL A,($R$PLUS)", + + "*SRL B,($R$PLUS)", // 0x38 + "*SRL C,($R$PLUS)", + "*SRL D,($R$PLUS)", + "*SRL E,($R$PLUS)", + "*SRL H,($R$PLUS)", + "*SRL L,($R$PLUS)", + "SRL ($R$PLUS)", + "*SRL A,($R$PLUS)", + + "*BIT $S,($R$PLUS)", // 0x40 + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "BIT $S,($R$PLUS)", // BIT 0 + "*BIT $S,($R$PLUS)", + + "*BIT $S,($R$PLUS)", // 0x48 + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "BIT $S,($R$PLUS)", // BIT 1 + "*BIT $S,($R$PLUS)", + + "*BIT $S,($R$PLUS)", // 0x50 + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "BIT $S,($R$PLUS)", // BIT 2 + "*BIT $S,($R$PLUS)", + + "*BIT $S,($R$PLUS)", // 0x58 + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "BIT $S,($R$PLUS)", // BIT 3 + "*BIT $S,($R$PLUS)", + + "*BIT $S,($R$PLUS)", // 0x60 + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "BIT $S,($R$PLUS)", // BIT 4 + "*BIT $S,($R$PLUS)", + + "*BIT $S,($R$PLUS)", // 0x68 + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "BIT $S,($R$PLUS)", // BIT 5 + "*BIT $S,($R$PLUS)", + + "*BIT $S,($R$PLUS)", // 0x70 + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "BIT $S,($R$PLUS)", // BIT 6 + "*BIT $S,($R$PLUS)", + + "*BIT $S,($R$PLUS)", // 0x78 + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "*BIT $S,($R$PLUS)", + "BIT $S,($R$PLUS)", // BIT 7 + "*BIT $S,($R$PLUS)", + + "*RES $S,B,($R$PLUS)", // 0x80 + "*RES $S,C,($R$PLUS)", + "*RES $S,D,($R$PLUS)", + "*RES $S,E,($R$PLUS)", + "*RES $S,H,($R$PLUS)", + "*RES $S,L,($R$PLUS)", + "RES $S,($R$PLUS)", // RES 0 + "*RES $S,A,($R$PLUS)", + + "*RES $S,B,($R$PLUS)", // 0x88 + "*RES $S,C,($R$PLUS)", + "*RES $S,D,($R$PLUS)", + "*RES $S,E,($R$PLUS)", + "*RES $S,H,($R$PLUS)", + "*RES $S,L,($R$PLUS)", + "RES $S,($R$PLUS)", // RES 1 + "*RES $S,A,($R$PLUS)", + + "*RES $S,B,($R$PLUS)", // 0x90 + "*RES $S,C,($R$PLUS)", + "*RES $S,D,($R$PLUS)", + "*RES $S,E,($R$PLUS)", + "*RES $S,H,($R$PLUS)", + "*RES $S,L,($R$PLUS)", + "RES $S,($R$PLUS)", // RES 2 + "*RES $S,A,($R$PLUS)", + + "*RES $S,B,($R$PLUS)", // 0x98 + "*RES $S,C,($R$PLUS)", + "*RES $S,D,($R$PLUS)", + "*RES $S,E,($R$PLUS)", + "*RES $S,H,($R$PLUS)", + "*RES $S,L,($R$PLUS)", + "RES $S,($R$PLUS)", // RES 3 + "*RES $S,A,($R$PLUS)", + + "*RES $S,B,($R$PLUS)", // 0xA0 + "*RES $S,C,($R$PLUS)", + "*RES $S,D,($R$PLUS)", + "*RES $S,E,($R$PLUS)", + "*RES $S,H,($R$PLUS)", + "*RES $S,L,($R$PLUS)", + "RES $S,($R$PLUS)", // RES 4 + "*RES $S,A,($R$PLUS)", + + "*RES $S,B,($R$PLUS)", // 0xA8 + "*RES $S,C,($R$PLUS)", + "*RES $S,D,($R$PLUS)", + "*RES $S,E,($R$PLUS)", + "*RES $S,H,($R$PLUS)", + "*RES $S,L,($R$PLUS)", + "RES $S,($R$PLUS)", // RES 5 + "*RES $S,A,($R$PLUS)", + + "*RES $S,B,($R$PLUS)", // 0xB0 + "*RES $S,C,($R$PLUS)", + "*RES $S,D,($R$PLUS)", + "*RES $S,E,($R$PLUS)", + "*RES $S,H,($R$PLUS)", + "*RES $S,L,($R$PLUS)", + "RES $S,($R$PLUS)", // RES 6 + "*RES $S,A,($R$PLUS)", + + "*RES $S,B,($R$PLUS)", // 0xB8 + "*RES $S,C,($R$PLUS)", + "*RES $S,D,($R$PLUS)", + "*RES $S,E,($R$PLUS)", + "*RES $S,H,($R$PLUS)", + "*RES $S,L,($R$PLUS)", + "RES $S,($R$PLUS)", // RES 7 + "*RES $S,A,($R$PLUS)", + + "*SET $S,B,($R$PLUS)", // 0xC0 + "*SET $S,C,($R$PLUS)", + "*SET $S,D,($R$PLUS)", + "*SET $S,E,($R$PLUS)", + "*SET $S,H,($R$PLUS)", + "*SET $S,L,($R$PLUS)", + "SET $S,($R$PLUS)", // SET 0 + "*SET $S,A,($R$PLUS)", + + "*SET $S,B,($R$PLUS)", // 0xC8 + "*SET $S,C,($R$PLUS)", + "*SET $S,D,($R$PLUS)", + "*SET $S,E,($R$PLUS)", + "*SET $S,H,($R$PLUS)", + "*SET $S,L,($R$PLUS)", + "SET $S,($R$PLUS)", // SET 1 + "*SET $S,A,($R$PLUS)", + + "*SET $S,B,($R$PLUS)", // 0xD0 + "*SET $S,C,($R$PLUS)", + "*SET $S,D,($R$PLUS)", + "*SET $S,E,($R$PLUS)", + "*SET $S,H,($R$PLUS)", + "*SET $S,L,($R$PLUS)", + "SET $S,($R$PLUS)", // SET 2 + "*SET $S,A,($R$PLUS)", + + "*SET $S,B,($R$PLUS)", // 0xD8 + "*SET $S,C,($R$PLUS)", + "*SET $S,D,($R$PLUS)", + "*SET $S,E,($R$PLUS)", + "*SET $S,H,($R$PLUS)", + "*SET $S,L,($R$PLUS)", + "SET $S,($R$PLUS)", // SET 3 + "*SET $S,A,($R$PLUS)", + + "*SET $S,B,($R$PLUS)", // 0xE0 + "*SET $S,C,($R$PLUS)", + "*SET $S,D,($R$PLUS)", + "*SET $S,E,($R$PLUS)", + "*SET $S,H,($R$PLUS)", + "*SET $S,L,($R$PLUS)", + "SET $S,($R$PLUS)", // SET 4 + "*SET $S,A,($R$PLUS)", + + "*SET $S,B,($R$PLUS)", // 0xE8 + "*SET $S,C,($R$PLUS)", + "*SET $S,D,($R$PLUS)", + "*SET $S,E,($R$PLUS)", + "*SET $S,H,($R$PLUS)", + "*SET $S,L,($R$PLUS)", + "SET $S,($R$PLUS)", // SET 5 + "*SET $S,A,($R$PLUS)", + + "*SET $S,B,($R$PLUS)", // 0xF0 + "*SET $S,C,($R$PLUS)", + "*SET $S,D,($R$PLUS)", + "*SET $S,E,($R$PLUS)", + "*SET $S,H,($R$PLUS)", + "*SET $S,L,($R$PLUS)", + "SET $S,($R$PLUS)", // SET 6 + "*SET $S,A,($R$PLUS)", + + "*SET $S,B,($R$PLUS)", // 0xF8 + "*SET $S,C,($R$PLUS)", + "*SET $S,D,($R$PLUS)", + "*SET $S,E,($R$PLUS)", + "*SET $S,H,($R$PLUS)", + "*SET $S,L,($R$PLUS)", + "SET $S,($R$PLUS)", // SET 7 + "*SET $S,A,($R$PLUS)", + ]; + + #endregion Table #DD/#FD,#CB + } +} \ No newline at end of file diff --git a/Debuger/Cpu/Tools/TimingTool.cs b/Debuger/Cpu/Tools/TimingTool.cs new file mode 100644 index 0000000..bac09a9 --- /dev/null +++ b/Debuger/Cpu/Tools/TimingTool.cs @@ -0,0 +1,347 @@ +/* + * Copyright 2001, 2007, 2015 Alex Makeev + * + * This file is part of ZXMAK2 (ZX Spectrum virtual machine). + * + * ZXMAK2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ZXMAK2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ZXMAK2. If not, see . + * + */ +using System; +using ZXMAK2.Engine.Cpu.Processor; + + +namespace ZXMAK2.Engine.Cpu.Tools +{ + public class TimingTool(Z80Cpu cpu, Func reader) + { + private readonly Z80Cpu _cpu = cpu; + private readonly Func _memRead = reader; + + public string GetTimingString(int addr) + { + var t = GetTiming(addr); + return t.HasValue ? string.Format("{0}T", t) : "N/A"; + } + + public int? GetTiming(int addr) + { + if (_memRead == null || _cpu == null) + { + return null; + } + var tableNumber = 0; + var perfTime = 0; + var offset = 0; + var opCode = -1; + while (true) + { + opCode = _memRead((ushort)(addr + offset)); + var res = s_tables[tableNumber][opCode]; + if (res < 0xF0) + { + return res + perfTime; + } + tableNumber = res - 0xF0; + if (tableNumber >= 5) + { + break; + } + perfTime += 4; + offset++; + if (tableNumber == 4) + { + offset++; + } + if (perfTime > 1500000) + { + System.Console.Error.WriteLine("TimingTool.GetTiming: {0}T reached!", perfTime); + return null; + } + } + switch (opCode) + { + case 0x10: + return DjnzTime(perfTime, 0x08, 0x0D); + + case 0xB1: + case 0xB2: + case 0xB9: + case 0xBA: + return DjnzTime(perfTime, 0x0C, 0x11); + + case 0x20: return FlagTime(perfTime, 0x07, 0x0C, 0x40); + case 0x28: return FlagTime(perfTime, 0x0C, 0x07, 0x40); + case 0x30: return FlagTime(perfTime, 0x07, 0x0C, 0x01); + case 0x38: return FlagTime(perfTime, 0x0C, 0x07, 0x01); + case 0xC0: return FlagTime(perfTime, 0x05, 0x0B, 0x40); + case 0xC8: return FlagTime(perfTime, 0x0B, 0x05, 0x40); + case 0xD0: return FlagTime(perfTime, 0x05, 0x0B, 0x01); + case 0xD8: return FlagTime(perfTime, 0x0B, 0x05, 0x01); + case 0xE0: return FlagTime(perfTime, 0x05, 0x0B, 0x04); + case 0xE8: return FlagTime(perfTime, 0x0B, 0x05, 0x04); + case 0xF0: return FlagTime(perfTime, 0x05, 0x0B, 0x80); + case 0xF8: return FlagTime(perfTime, 0x0B, 0x05, 0x80); + case 0xC4: return FlagTime(perfTime, 0x0A, 0x11, 0x40); + case 0xCC: return FlagTime(perfTime, 0x11, 0x0A, 0x40); + case 0xD4: return FlagTime(perfTime, 0x0A, 0x11, 0x01); + case 0xDC: return FlagTime(perfTime, 0x11, 0x0A, 0x01); + case 0xE4: return FlagTime(perfTime, 0x0A, 0x11, 0x04); + case 0xEC: return FlagTime(perfTime, 0x11, 0x0A, 0x04); + case 0xF4: return FlagTime(perfTime, 0x0A, 0x11, 0x80); + case 0xFC: return FlagTime(perfTime, 0x11, 0x0A, 0x80); + + case 0xB0: + case 0xB8: + return RepTime(perfTime, 0x0C, 0x11); + + case 0xB3: + case 0xBB: + return RepFlagTime(perfTime, 0x0C, 0x0C, 0x11); + } + System.Console.Error.WriteLine( + "TimingTool.GetTiming: unexpected opCode #{0:2X}", + opCode); + return null; + } + + + #region Private + + private int DjnzTime(int timeBase, int timeEnd, int timeNoEnd) + { + var ifTime = _cpu.regs.B == 0x01 ? timeEnd : timeNoEnd; + return timeBase + ifTime; + } + + private int FlagTime(int timeBase, int timeOn, int timeOff, int flagMask) + { + var ifTime = (_cpu.regs.F & flagMask) != 0 ? timeOn : timeOff; + return timeBase + ifTime; + } + + private int RepTime(int perfTime, int timeEnd, int timeNoEnd) + { + var ifTime = _cpu.regs.BC == 0x0001 ? timeEnd : timeNoEnd; + return perfTime + ifTime; + } + + private int RepFlagTime(int timeEnd, int timeOn, int timeOff, int perfTime) + { + if (_cpu.regs.BC == 0x0001) + { + return perfTime + timeEnd; + } + var data = _memRead(_cpu.regs.HL); + var ifTime = _cpu.regs.F == data ? timeOn : timeOff; + return perfTime + ifTime; + } + + #endregion Private + + + #region Tables + + private static readonly int[] s_tableDirect = + [ + 0x04,0x0A,0x07,0x06,0x04,0x04,0x07,0x04, //;00h-07h + 0x04,0x0B,0x07,0x06,0x04,0x04,0x07,0x04, //;08h-0Fh + 0xFF,0x0A,0x07,0x06,0x04,0x04,0x07,0x04, //;10h-17h + 0x0C,0x0B,0x07,0x06,0x04,0x04,0x07,0x04, //;18h-1Fh + 0xFF,0x0A,0x10,0x06,0x04,0x04,0x07,0x04, //;20h-27h + 0xFF,0x0B,0x10,0x06,0x04,0x04,0x07,0x04, //;28h-2Fh + 0xFF,0x0A,0x0D,0x06,0x0B,0x0B,0x0A,0x04, //;30h-37h + 0xFF,0x0B,0x0D,0x06,0x04,0x04,0x07,0x04, //;38h-3Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;40h-47h + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;48h-4Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;50h-57h + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;58h-5Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;60h-67h + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;68h-6Fh + 0x07,0x07,0x07,0x07,0x07,0x07,0x04,0x07, //;70h-77h + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;78h-7Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;80h-87h + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;88h-8Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;90h-97h + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;98h-9Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;A0h-A7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;A8h-AFh + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;B0h-B7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x04, //;B8h-BFh + 0xFF,0x0A,0x0A,0x0A,0xFF,0x0B,0x07,0x0B, //;C0h-C7h + 0xFF,0x0A,0x0A,0xF1,0xFF,0x11,0x07,0x0B, //;C8h-CFh + 0xFF,0x0A,0x0A,0x0B,0xFF,0x0B,0x07,0x0B, //;D0h-D7h + 0xFF,0x04,0x0A,0x0B,0xFF,0xF3,0x07,0x0B, //;D8h-DFh + 0xFF,0x0A,0x0A,0x13,0xFF,0x0B,0x07,0x0B, //;E0h-E7h + 0xFF,0x04,0x0A,0x04,0xFF,0xF2,0x07,0x0B, //;E8h-EFh + 0xFF,0x0A,0x0A,0x04,0xFF,0x0B,0x07,0x0B, //;F0h-F7h + 0xFF,0x06,0x0A,0x04,0xFF,0xF3,0x07,0x0B //;F8h-FFh + ]; + + private static readonly int[] s_tableCb = + [ + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;00h-07h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;08h-0Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;10h-17h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;18h-1Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;20h-27h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;28h-2Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;30h-37h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;38h-3Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x08,0x04, // ;40h-47h + 0x04,0x04,0x04,0x04,0x04,0x04,0x08,0x04, // ;48h-4Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x08,0x04, // ;50h-57h + 0x04,0x04,0x04,0x04,0x04,0x04,0x08,0x04, // ;58h-5Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x08,0x04, // ;60h-67h + 0x04,0x04,0x04,0x04,0x04,0x04,0x08,0x04, // ;68h-6Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x08,0x04, // ;70h-77h + 0x04,0x04,0x04,0x04,0x04,0x04,0x08,0x04, // ;78h-7Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;80h-87h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;88h-8Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;90h-97h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;98h-9Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;A0h-A7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;A8h-AFh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;B0h-B7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;B8h-BFh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;C0h-C7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;C8h-CFh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;D0h-D7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;D8h-DFh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;E0h-E7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;E8h-EFh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04, // ;F0h-F7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0B,0x04 // ;F8h-FFh + ]; + + private static readonly int[] s_tableEd = + [ + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;00h-07h + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;08h-0Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;10h-17h + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;18h-1Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;20h-27h + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;28h-2Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;30h-37h + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;38h-3Fh + 0x08,0x08,0x0B,0x10,0x04,0x0A,0x04,0x05, // ;40h-47h + 0x08,0x08,0x0B,0x10,0x04,0x0A,0x04,0x05, // ;48h-4Fh + 0x08,0x08,0x0B,0x10,0x04,0x0A,0x04,0x05, // ;50h-57h + 0x08,0x08,0x0B,0x10,0x04,0x0A,0x04,0x05, // ;58h-5Fh + 0x08,0x08,0x0B,0x10,0x04,0x0A,0x04,0x0E, // ;60h-67h + 0x08,0x08,0x0B,0x10,0x04,0x0A,0x04,0x0E, // ;68h-6Fh + 0x08,0x08,0x0B,0x10,0x04,0x0A,0x04,0x04, // ;70h-77h + 0x08,0x08,0x0B,0x10,0x04,0x0A,0x04,0x04, // ;78h-7Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;80h-87h + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;88h-8Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;90h-97h + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;98h-9Fh + 0x0C,0x0C,0x0C,0x0C,0x04,0x04,0x04,0x04, // ;A0h-A7h + 0x0C,0x0C,0x0C,0x0C,0x04,0x04,0x04,0x04, // ;A8h-AFh + 0xFF,0xFF,0xFF,0xFF,0x04,0x04,0x04,0x04, // ;B0h-B7h + 0xFF,0xFF,0xFF,0xFF,0x04,0x04,0x04,0x04, // ;B8h-BFh + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;C0h-C7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;C8h-CFh + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;D0h-D7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;D8h-DFh + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;E0h-E7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;E8h-EFh + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // ;F0h-F7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04 // ;F8h-FFh + ]; + + + private static readonly int[] s_tableDdFd = + [ + 0x04,0x0A,0x07,0x06,0x04,0x04,0x07,0x04, // ;00h-07h + 0x04,0x0B,0x07,0x06,0x04,0x04,0x07,0x04, // ;08h-0Fh + 0xFF,0x0A,0x07,0x06,0x04,0x04,0x07,0x04, // ;10h-17h + 0x0C,0x0B,0x07,0x06,0x04,0x04,0x07,0x04, // ;18h-1Fh + 0xFF,0x0A,0x10,0x06,0x04,0x04,0x07,0x04, // ;20h-27h + 0xFF,0x0B,0x10,0x06,0x04,0x04,0x07,0x04, // ;28h-2Fh + 0xFF,0x0A,0x0D,0x06,0x13,0x13,0x0F,0x04, // ;30h-37h + 0xFF,0x0B,0x0D,0x06,0x04,0x04,0x0F,0x04, // ;38h-3Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;40h-47h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;48h-4Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;50h-57h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;58h-5Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;60h-67h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;68h-6Fh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x04,0x0F, // ;70h-77h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;78h-7Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;80h-87h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;88h-8Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;90h-97h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;98h-9Fh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;A0h-A7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;A8h-AFh + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;B0h-B7h + 0x04,0x04,0x04,0x04,0x04,0x04,0x0F,0x04, // ;B8h-BFh + 0xFF,0x0A,0x0A,0x0A,0xFF,0x0B,0x0F,0x0B, // ;C0h-C7h + 0xFF,0x0A,0x0A,0xF4,0xFF,0x11,0x0F,0x0B, // ;C8h-CFh + 0xFF,0x0A,0x0A,0x0B,0xFF,0x0B,0x0F,0x0B, // ;D0h-D7h + 0xFF,0x04,0x0A,0x0B,0xFF,0xF3,0x0F,0x0B, // ;D8h-DFh + 0xFF,0x0A,0x0A,0x13,0xFF,0x0B,0x0F,0x0B, // ;E0h-E7h + 0xFF,0x04,0x0A,0x04,0xFF,0xF2,0x0F,0x0B, // ;E8h-EFh + 0xFF,0x0A,0x0A,0x04,0xFF,0x0B,0x0F,0x0B, // ;F0h-F7h + 0xFF,0x06,0x0A,0x04,0xFF,0xF3,0x0F,0x0B // ;F8h-FFh + ]; + + private static readonly int[] s_tableDdCbFdCb = + [ + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;00h-07h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;08h-0Fh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;10h-17h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;18h-1Fh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;20h-27h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;28h-2Fh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;30h-37h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;38h-3Fh + 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // ;40h-47h + 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // ;48h-4Fh + 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // ;50h-57h + 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // ;58h-5Fh + 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // ;60h-67h + 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // ;68h-6Fh + 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // ;70h-77h + 0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C, // ;78h-7Fh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;80h-87h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;88h-8Fh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;90h-97h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;98h-9Fh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;A0h-A7h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;A8h-AFh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;B0h-B7h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;B8h-BFh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;C0h-C7h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;C8h-CFh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;D0h-D7h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;D8h-DFh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;E0h-E7h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;E8h-EFh + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // ;F0h-F7h + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F // ;F8h-FFh + ]; + + private static readonly int[][] s_tables = + [ + s_tableDirect, + s_tableCb, + s_tableEd, + s_tableDdFd, + s_tableDdCbFdCb, + ]; + + #endregion Tables + } +} diff --git a/Debuger/CpuTrace.cs b/Debuger/CpuTrace.cs new file mode 100644 index 0000000..87f7e1d --- /dev/null +++ b/Debuger/CpuTrace.cs @@ -0,0 +1,103 @@ +// CpuTrace — ring buffer storing last N executed instructions with register snapshots +// Used for crash/corruption analysis in the WC emulator +using System; +using System.Text; + +namespace InflateDebugger +{ + /// Snapshot of CPU state at one instruction + public struct TraceEntry + { + public long Tact; + public ushort PC; + public byte Opcode; // first byte of the instruction + public ushort AF, BC, DE, HL; + public ushort AF2, BC2, DE2, HL2; // shadow regs + public ushort IX, IY, SP; + public byte Win0, Win1, Win2, Win3; // page window mapping + + // Optional: what was written (0xFFFF = no write) + public ushort WriteAddr; + public byte WriteVal; + + // WC API trap info (0xFF = not a WC call) + public byte WcFuncNum; + + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendFormat("[{0,10}] PC={1:X4} op={2:X2}", Tact, PC, Opcode); + sb.AppendFormat(" AF={0:X4} BC={1:X4} DE={2:X4} HL={3:X4}", AF, BC, DE, HL); + sb.AppendFormat(" IX={0:X4} SP={1:X4}", IX, SP); + sb.AppendFormat(" W[{0:X2},{1:X2},{2:X2},{3:X2}]", Win0, Win1, Win2, Win3); + if (WcFuncNum != 0xFF) + sb.AppendFormat(" WC_CALL=0x{0:X2}", WcFuncNum); + if (WriteAddr != 0xFFFF) + sb.AppendFormat(" WR[{0:X4}]={1:X2}", WriteAddr, WriteVal); + return sb.ToString(); + } + } + + /// Fixed-size ring buffer of CPU trace entries + public class CpuTrace + { + private readonly TraceEntry[] _buf; + private readonly int _capacity; + private int _head; // next write position + private int _count; // total entries stored (capped at capacity) + + public CpuTrace(int capacity = 200) + { + _capacity = capacity; + _buf = new TraceEntry[capacity]; + _head = 0; + _count = 0; + } + + public int Count => _count; + public int Capacity => _capacity; + + /// Record a new trace entry (overwrites oldest when full) + public void Record(ref TraceEntry entry) + { + _buf[_head] = entry; + _head = (_head + 1) % _capacity; + if (_count < _capacity) _count++; + } + + /// Get entry by age: 0 = most recent, 1 = one before, etc. + public TraceEntry Get(int age) + { + if (age < 0 || age >= _count) + throw new IndexOutOfRangeException($"age={age}, count={_count}"); + int idx = (_head - 1 - age + _capacity * 2) % _capacity; + return _buf[idx]; + } + + /// Dump the last N entries (most recent first) to a string + public string Dump(int maxEntries = 0) + { + if (maxEntries <= 0) maxEntries = _count; + int n = Math.Min(maxEntries, _count); + var sb = new StringBuilder(); + sb.AppendLine($"=== CPU Trace (last {n} of {_count}) ==="); + // Print oldest-first for readability + for (int i = n - 1; i >= 0; i--) + sb.AppendLine(Get(i).ToString()); + return sb.ToString(); + } + + /// Dump trace to file + public void DumpToFile(string path, int maxEntries = 0) + { + System.IO.File.WriteAllText(path, Dump(maxEntries)); + } + + /// Clear all entries + public void Clear() + { + _head = 0; + _count = 0; + } + } +} diff --git a/Debuger/InflateDebugger.csproj b/Debuger/InflateDebugger.csproj new file mode 100644 index 0000000..59dcd88 --- /dev/null +++ b/Debuger/InflateDebugger.csproj @@ -0,0 +1,10 @@ + + + Exe + net8.0 + InflateDebugger + disable + CS8625;CS8618;CS8602;CS8604;CS8600;CS8601 + disable + + diff --git a/Debuger/PluginRunner.cs b/Debuger/PluginRunner.cs new file mode 100644 index 0000000..6f4e9b1 --- /dev/null +++ b/Debuger/PluginRunner.cs @@ -0,0 +1,1094 @@ +// PluginRunner — loads a WMF plugin binary into emulated TSConfig memory, +// emulates WC kernel startup (P8000 dispatch), runs the Z80 CPU, +// and provides analysis/logging infrastructure. +// +// The test_data directory is used as disk root for file I/O emulation. +// VGM/VGZ files placed there can be loaded by the plugin as if they +// were on a real SD card. + +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; +using System.Linq; +using ZXMAK2.Engine.Cpu; +using ZXMAK2.Engine.Cpu.Processor; + +namespace InflateDebugger +{ + public class PluginRunner + { + // ── TSConfig page assignments ───────────────────────────────── + const byte PLGPG = 0x61; // plugin page base + const byte TVBPG = 0x20; // megabuffer base + const byte MTXPG = 0x00; // main text page + const byte SYS_PAGE = 0x05; // WC system / FAT engine page + const byte FONT_PAGE = 0x01; // font page + const byte RESB_PAGE = 0x1D; // window save buffer page + + // Plugin page indices (relative; phys = PLGPG + idx) + const byte PLG_CODE_PAGE = 0; // page 0: CODE+DATA (0x8000–0xBFFF) + // Extra pages appended by build.bat: + // page 1,2: (unused in current build) + // page 3: freq_tables.bin + // page 4: inflate.bin + // page 5: cmdblocks.bin + + // ── Ports ───────────────────────────────────────────────────── + const ushort PORT_BORDER = 0x00FE; // ZX border + const ushort PORT_TSCONF_BORDER = 0x0FAF; + + // Debug ports (same as inflate debugger) + const ushort DBG_STATUS = 0xFFAF; + const ushort DBG_SRC = 0xFEAF; + const ushort DBG_DST = 0xFDAF; + + // OPL3 ports + const ushort PORT_OPL3_ADDR0 = 0x0008; // OPL3 address bank 0 + const ushort PORT_OPL3_DATA = 0x0009; // OPL3 data + const ushort PORT_OPL3_ADDR1 = 0x000A; // OPL3 address bank 1 + + // AY ports + const ushort PORT_AY_ADDR = 0xFFFD; + const ushort PORT_AY_DATA = 0xBFFD; + + // INT position ports (TSConfig raster comparator) + const ushort PORT_HSINT = 0x22AF; // HCNT[8:1], 0–223 + const ushort PORT_VSINTL = 0x23AF; // VCNT[7:0] + const ushort PORT_VSINTH = 0x24AF; // VCNT[8] (bit 0 only) + const ushort PORT_INTMASK = 0x2AAF; + + // ── Configuration ───────────────────────────────────────────── + public long MaxCycles { get; set; } = 2_000_000_000; // 2B cycles + public long WatchdogLimit { get; set; } = 500_000_000; // 500M without WC call = hang + public bool VerboseWcCalls { get; set; } = true; + public bool VerbosePageSwitch { get; set; } = false; + public bool DumpOnExit { get; set; } = true; + public string DumpDir { get; set; } + public string DiskRootPath { get; set; } // test_data directory + + // File to auto-open for the plugin (relative to DiskRootPath) + public string TestFileName { get; set; } + + // ── Emulator state ──────────────────────────────────────────── + public TsMemory Mem { get; private set; } + public CpuUnit Cpu { get; private set; } + public WcKernel Wc { get; private set; } + public CpuTrace Trace { get; private set; } + + private bool _finished; + private long _lastWcCallTact; + private int _exitCode; + + // ISR emulation state + private const ushort ISR_STUB_ADDR = 0x5BF4; // IM2 stub handler (EI+RETI) + private int _isrFireCount; + + // ── Raster-based INT position emulation ────────────────────── + // TSConfig frame: 320 lines × 224 T-states/line = 71680 T at 3.5 MHz + // Raster runs at 3.5 MHz regardless of CPU turbo mode. + // We track raster in fixed-point: units = base_T × RASTER_SCALE. + // This keeps all arithmetic integer across turbo modes. + // + // Turbo multipliers (raster_advance = cpu_delta × mult): + // 3.5 MHz → mult=20 (20/20 = 1.0 raster T per CPU T) + // 7 MHz → mult=10 (10/20 = 0.5) + // 10 MHz → mult=7 ( 7/20 = 0.35, ≈14 MHz + wait states) + // 28 MHz → mult=5 ( 5/20 = 0.25) + + private const int RASTER_SCALE = 20; + private const int LINE_TACTS = 224; // T per line at 3.5 MHz + private const int LINES_PER_FRAME = 320; + private const long FRAME_RASTER = (long)LINE_TACTS * LINES_PER_FRAME * RASTER_SCALE; // 1 433 600 + + private long _rasterPos; // current raster position (fixed-point) + private long _prevTact; // previous Cpu.Tact for delta calc + private int _rasterMult = RASTER_SCALE; // multiplier for current turbo + + // Programmed INT position (written by ISR via OUTI to ports) + private int _intLine; // 9-bit VCNT: VSINTH[0]:VSINTL[7:0] + private int _intHPos; // 8-bit HCNT: HSINT + private long _intTargetRaster; // target in fixed-point units + private bool _intFired; // one-shot per target crossing + + // CPU frequency / turbo mode + // SYSCONF[1:0]: 0=3.5, 1=7, 2=14, 3=28 MHz + private int _turboMode; + + private static int GetRasterMult(int turbo) => turbo switch + { + 0 => 20, // 3.5 MHz → 1:1 + 1 => 10, // 7 MHz → 2:1 + 2 => 7, // ~10 MHz (≈14 MHz + wait) + 3 => 5, // 28 MHz → 4:1 + _ => 20, + }; + + /// Recalculate INT target from current VSINT/HSINT values. + private void RecalcIntTarget() + { + long baseTact = (long)_intLine * LINE_TACTS + _intHPos; + _intTargetRaster = baseTact * RASTER_SCALE; + _intFired = false; // re-arm for new target + } + + // OPL3 register tracking + private byte _opl3Addr0, _opl3Addr1; + private int _opl3WriteCount; + + // Debug port state + private byte _lastDbgStatus; + + // ── Function breakpoints (addresses from vgmplay.noi) ───────── + private static readonly Dictionary _breakpoints = new() + { + { 0x8874, "load_vgm" }, + { 0xAEF5, "inflate_vgz" }, + { 0xAF68, "copy_pages_to_tap" }, + { 0x8DE6, "vgm_parse_header" }, + { 0x8AC1, "main" }, + }; + private readonly HashSet _hitBreakpoints = new(); + private bool _postCptLogged; + private bool _inflateResultLogged; + private bool _parseHeaderResultLogged; + + // ── megabuf page 0 write tracker (first 16 bytes) ───────────── + private struct MbWriteInfo { public byte Value; public long Cycle; public ushort Z80Addr; public ushort PC; } + private MbWriteInfo[] _mbPg0First = new MbWriteInfo[16]; + private MbWriteInfo[] _mbPg0Last = new MbWriteInfo[16]; + private MbWriteInfo[] _mbPg0Prev = new MbWriteInfo[16]; // second-to-last write + private int[] _mbPg0Cnt = new int[16]; + + // ── statistics ──────────────────────────────────────────────── + public long TotalCycles => Cpu?.Tact ?? 0; + public int WcCallCount => Wc?.CallLog.Count ?? 0; + public int ViolationCount => Mem?.Violations.Count ?? 0; + + // ── Run ─────────────────────────────────────────────────────── + + /// + /// Load plugin binary and run it with WC emulation. + /// Returns exit code (0 = normal exit, >0 = error/abort). + /// + public int Run(string wmfPath, string testFile = null) + { + Console.WriteLine("═══════════════════════════════════════════════════"); + Console.WriteLine(" WC Plugin Runner — Full Emulation"); + Console.WriteLine("═══════════════════════════════════════════════════"); + + // ── Setup ────────────────────────────────── + Mem = new TsMemory(); + Cpu = new CpuUnit(); + Wc = new WcKernel(Mem, Cpu); + Trace = new CpuTrace(2000); + + // Wire memory write watcher for megabuf page 0, first 16 bytes + bool _corruptionTrapped = false; + Mem.OnWrite = (addr, page, offset, val) => + { + if (page == TVBPG && offset < 16) + { + var info = new MbWriteInfo { Value = val, Cycle = Cpu.Tact, Z80Addr = addr, PC = Cpu.regs.PC }; + if (_mbPg0Cnt[offset] == 0) + _mbPg0First[offset] = info; + _mbPg0Prev[offset] = _mbPg0Last[offset]; + _mbPg0Last[offset] = info; + _mbPg0Cnt[offset]++; + + // Trap: write to page 0x20 via Win1 (addr 0x4000-0x7FFF) — the corruption! + if (addr >= 0x4000 && addr < 0x8000 && offset == 0 && !_corruptionTrapped) + { + _corruptionTrapped = true; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n *** CORRUPTION TRAP at T={Cpu.Tact} ***"); + Console.WriteLine($" PC=0x{Cpu.regs.PC:X4} z80Addr=0x{addr:X4} val=0x{val:X2}"); + Console.WriteLine($" AF={Cpu.regs.AF:X4} BC={Cpu.regs.BC:X4} DE={Cpu.regs.DE:X4} HL={Cpu.regs.HL:X4}"); + Console.WriteLine($" SP={Cpu.regs.SP:X4} IX={Cpu.regs.IX:X4} IY={Cpu.regs.IY:X4}"); + Console.WriteLine($" Win=[{Mem.GetWindow(0):X2},{Mem.GetWindow(1):X2},{Mem.GetWindow(2):X2},{Mem.GetWindow(3):X2}]"); + // Also show what's at the LDIR source (HL) if this is during LDIR at 0xC60E + Console.WriteLine($" alt AF'={Cpu.regs._AF:X4} BC'={Cpu.regs._BC:X4} DE'={Cpu.regs._DE:X4} HL'={Cpu.regs._HL:X4}"); + // Dump a few bytes of stack + Console.Write(" Stack: "); + for (int si = 0; si < 8; si++) + Console.Write($"{Mem.Read((ushort)(Cpu.regs.SP + si)):X2} "); + Console.WriteLine(); + Console.ResetColor(); + } + } + }; + + if (!string.IsNullOrEmpty(DiskRootPath)) + Wc.DiskRootPath = DiskRootPath; + + if (VerboseWcCalls) + Wc.Log = msg => Console.WriteLine(msg); + + if (!string.IsNullOrEmpty(testFile)) + TestFileName = testFile; + + // ── 1. Load WMF binary ───────────────────── + if (!File.Exists(wmfPath)) + { + Console.Error.WriteLine($"WMF file not found: {wmfPath}"); + return 1; + } + byte[] wmfData = File.ReadAllBytes(wmfPath); + Console.WriteLine($"WMF file: {wmfPath}"); + Console.WriteLine($"WMF size: {wmfData.Length} bytes"); + + LoadPlugin(wmfData); + + // ── 2. Set up WC environment ─────────────── + SetupWcEnvironment(); + + // ── 3. Set up file for the plugin ────────── + SetupTestFile(); + + // ── 4. Set up CPU ────────────────────────── + SetupCpu(); + + // ── 5. Set up page protection ────────────── + SetupProtection(); + + // ── 6. Run ───────────────────────────────── + Console.WriteLine(); + Console.WriteLine("─── Execution Start ─────────────────────────────"); + _exitCode = RunCpu(); + + // ── 7. Analysis ──────────────────────────── + Console.WriteLine(); + Console.WriteLine("─── Post-Run Analysis ───────────────────────────"); + PrintAnalysis(); + + if (DumpOnExit) + DumpState("exit"); + + return _exitCode; + } + + // ═══════════════════════════════════════════════════════════════ + // Plugin loading + // ═══════════════════════════════════════════════════════════════ + + private void LoadPlugin(byte[] wmfData) + { + Console.WriteLine(); + Console.WriteLine("─── Loading Plugin ──────────────────────────────"); + + // WMF format (doc/02_Plugin_Header_Structure.md): + // +0: 16 bytes reserved + // +16: 16 bytes "WildCommanderMDL" signature + // +32: 1 byte version (0x10) + // +33: 1 byte reserved + // +34: 1 byte pages count + // +35: 1 byte start page number (relative, loaded into Win2) + // +36: 12 bytes data blocks (6 × 2: page_index, size_in_512b_sectors) + // +512: data blocks follow + + const int HEADER_SIZE = 512; + + // Detect signature at offset 16 + bool hasSignature = wmfData.Length > 48 && + wmfData[16] == (byte)'W' && wmfData[17] == (byte)'i' && + wmfData[18] == (byte)'l' && wmfData[19] == (byte)'d'; + + if (!hasSignature) + { + Console.WriteLine(" WARNING: No WildCommanderMDL signature found!"); + Console.WriteLine(" Loading as raw pages starting from PLGPG"); + int rawPages = (wmfData.Length + TsMemory.PageSize - 1) / TsMemory.PageSize; + for (int p = 0; p < rawPages; p++) + { + int srcOff = p * TsMemory.PageSize; + int len = Math.Min(TsMemory.PageSize, wmfData.Length - srcOff); + Array.Copy(wmfData, srcOff, Mem.GetPage(PLGPG + p), 0, len); + } + } + else + { + byte version = wmfData[32]; + byte pagesCount = wmfData[34]; + byte startPage = wmfData[35]; + + Console.WriteLine($" Signature: WildCommanderMDL v0x{version:X2}"); + Console.WriteLine($" Pages: {pagesCount}, start page: {startPage}"); + + // Parse data blocks + int dataOffset = HEADER_SIZE; + for (int blk = 0; blk < 6; blk++) + { + byte pageIdx = wmfData[36 + blk * 2]; + byte sectors = wmfData[37 + blk * 2]; + if (sectors == 0) continue; + + int blkSize = sectors * 512; + int physPage = PLGPG + pageIdx; + + Console.WriteLine($" Block {blk}: page {pageIdx} (phys 0x{physPage:X2}), " + + $"{sectors}×512 = {blkSize} bytes"); + + int len = Math.Min(blkSize, wmfData.Length - dataOffset); + if (len > 0) + { + byte[] dest = Mem.GetPage(physPage); + Array.Copy(wmfData, dataOffset, dest, 0, Math.Min(len, TsMemory.PageSize)); + + // If block > 16KB (multiple pages), handle overflow + if (len > TsMemory.PageSize) + { + Console.WriteLine($" WARNING: Block exceeds 16KB, truncated"); + } + } + dataOffset += blkSize; + } + + // Show extensions + Console.Write(" Extensions: "); + for (int ext = 0; ext < 32; ext++) + { + int off = 64 + ext * 3; + if (wmfData[off] == 0) break; + if (ext > 0) Console.Write(", "); + Console.Write(System.Text.Encoding.ASCII.GetString(wmfData, off, 3)); + } + Console.WriteLine(); + + // Show plugin name + string name = System.Text.Encoding.ASCII.GetString(wmfData, 165, 32).TrimEnd('\0', ' '); + Console.WriteLine($" Name: \"{name}\""); + } + + // Verify: show first bytes of code page at 0x8000 + Mem.SetWindow(2, PLGPG); // Win2 = plugin page 0 + Console.Write(" Code @0x8000: "); + for (int i = 0; i < 16; i++) + Console.Write($"{Mem.Read((ushort)(0x8000 + i)):X2} "); + Console.WriteLine(); + } + + // ═══════════════════════════════════════════════════════════════ + // WC environment setup + // ═══════════════════════════════════════════════════════════════ + + private void SetupWcEnvironment() + { + Console.WriteLine(); + Console.WriteLine("─── WC Environment ──────────────────────────────"); + + // Window mapping (as WC P8000 sets up before CALL 0x8000): + // Win0 = TXT page (0x00) + // Win1 = WC system page (0x05) + // Win2 = Plugin code page (PLGPG = 0x61) + // Win3 = TXT page (0x00) — WC always sets this before calling plugin + Mem.SetWindow(0, MTXPG); + Mem.SetWindow(1, SYS_PAGE); + Mem.SetWindow(2, PLGPG); + Mem.SetWindow(3, MTXPG); + + // Initialize WC system variables + Wc.SysVarPage = SYS_PAGE; + Wc.InitSysVars(); + + // Set up IM2 vector area at 0x5BFF (in Win1 = SYS_PAGE) + // 0x5BFF = Win1 offset 0x1BFF + // We'll put a dummy ISR address there for now + + // Set up the JP instruction at 0x6006 (in Win1 = SYS_PAGE) + // 0x6006 = Win1 offset 0x2006 + // JP to itself (we trap this before execution) + var sysPage = Mem.GetPage(SYS_PAGE); + sysPage[0x2006] = 0xC3; // JP + sysPage[0x2007] = 0x06; // lo + sysPage[0x2008] = 0x60; // hi → JP 0x6006 (infinite loop, but we intercept) + + // Fill font page with dummy data (printable chars) + // Font is at FONT_PAGE (0x01), character bitmaps + // We just leave it zeroed — text output is stubbed anyway + + Console.WriteLine($" Windows: W0=0x{MTXPG:X2} W1=0x{SYS_PAGE:X2} W2=0x{PLGPG:X2} W3=0x{MTXPG:X2}"); + Console.WriteLine($" SysVars @0x6000 initialized"); + } + + // ═══════════════════════════════════════════════════════════════ + // Test file setup + // ═══════════════════════════════════════════════════════════════ + + private void SetupTestFile() + { + if (string.IsNullOrEmpty(TestFileName)) return; + + Console.WriteLine(); + Console.WriteLine("─── Test File ───────────────────────────────────"); + + string fullPath = TestFileName; + if (!Path.IsPathRooted(fullPath) && !string.IsNullOrEmpty(DiskRootPath)) + fullPath = Path.Combine(DiskRootPath, TestFileName); + + if (!File.Exists(fullPath)) + { + Console.Error.WriteLine($" Test file not found: {fullPath}"); + return; + } + + byte[] fileData = File.ReadAllBytes(fullPath); + Wc.SetFileData(fileData); + + Console.WriteLine($" File: {TestFileName}"); + Console.WriteLine($" Size: {fileData.Length} bytes ({(fileData.Length + 16383) / 16384} pages)"); + + // Detect VGZ + if (fileData.Length > 2 && fileData[0] == 0x1F && fileData[1] == 0x8B) + Console.WriteLine(" Format: VGZ (gzip-compressed VGM)"); + else if (fileData.Length > 4 && fileData[0] == 0x56 && fileData[1] == 0x67 && + fileData[2] == 0x6D && fileData[3] == 0x20) + Console.WriteLine(" Format: VGM (raw)"); + } + + // ═══════════════════════════════════════════════════════════════ + // CPU setup + // ═══════════════════════════════════════════════════════════════ + + private void SetupCpu() + { + Console.WriteLine(); + Console.WriteLine("─── CPU Setup ───────────────────────────────────"); + + // Wire CPU bus + Cpu.RDMEM = addr => Mem.Read(addr); + Cpu.RDMEM_M1 = addr => Mem.Read(addr); + Cpu.WRMEM = (addr, val) => Mem.Write(addr, val); + Cpu.RDPORT = HandlePortRead; + Cpu.WRPORT = HandlePortWrite; + Cpu.RDNOMREQ = addr => { }; + Cpu.WRNOMREQ = addr => { }; + Cpu.RESET = () => { }; + Cpu.NMIACK_M1 = () => { }; + // Clear INT on acknowledge — prevents re-trigger since we use pulse semantics + Cpu.INTACK_M1 = () => { Cpu.INT = false; }; + Cpu.SCANSIG = () => { }; + + // Initial CPU state as WC P8000 sets before CALL 0x8000: + // A = call type (0x00 = by file extension) + // BC = pointer to filename string (in Win2 area) + // HL = file size low word + // DE = file size high word + // IX = panel structure pointer (we'll use a dummy) + // SP = WC stack (somewhere in Win1 area, ~0x5BF0) + + // Prepare filename in plugin data area (Win2, 0x8000–0xBFFF) + // We'll put it at end of DATA area, say 0xBF00 + string fname = TestFileName ?? "test.vgm"; + byte[] nameBytes = System.Text.Encoding.ASCII.GetBytes(fname); + ushort nameAddr = 0xBF00; + for (int i = 0; i < nameBytes.Length && i < 60; i++) + Mem.WriteUnprotected((ushort)(nameAddr + i), nameBytes[i]); + Mem.WriteUnprotected((ushort)(nameAddr + Math.Min(nameBytes.Length, 60)), 0); + + uint fileSize = Wc.FileSize; + + Cpu.regs.A = 0x00; // call type = by extension + Cpu.regs.BC = nameAddr; + Cpu.regs.HL = (ushort)(fileSize & 0xFFFF); + Cpu.regs.DE = (ushort)((fileSize >> 16) & 0xFFFF); + Cpu.regs.IX = 0xBE00; // dummy panel ptr + Cpu.regs.SP = 0x5BF0; // WC stack area + Cpu.regs.PC = 0x8000; // plugin entry point + + // Set A' = file extension index (0) + // _AF is ushort: A' in high byte, F' in low byte + Cpu.regs._AF = 0x0000; + + // Set DE' = number of files (say 10), HL' = current file number (1) + Cpu.regs._DE = 10; + Cpu.regs._HL = 1; + + // Push return address (sentinel 0x0000) so RET from main() goes to 0x0000 + Cpu.regs.SP -= 2; + Mem.WriteUnprotected(Cpu.regs.SP, 0x00); + Mem.WriteUnprotected((ushort)(Cpu.regs.SP + 1), 0x00); + + // Enable interrupts (plugin expects them for ISR) + Cpu.IFF1 = false; // Will be enabled by the plugin itself (EI after isr_init) + Cpu.IFF2 = false; + Cpu.IM = 2; // IM 2 (plugin uses IM 2 for ISR) + Cpu.INT = false; + Cpu.NMI = false; + Cpu.RST = false; + Cpu.HALTED = false; + + // ── IM2 vector table setup ──────────────────────────────── + // WC uses I=0x5B. On INT, Z80 reads vector from (I*256+databus). + // Data bus = 0xFF → vector at address 0x5BFF. + // We place a stub ISR handler (EI + RETI) at 0x5BF4 and point + // the vector there. This prevents uninitialized-vector crashes + // when copy_pages_to_tap or inflate_vgz does EI before the + // plugin installs its real ISR handler. + Cpu.regs.IR = (ushort)(0x5B << 8); // I = 0x5B, R = 0 + { + var sysP = Mem.GetPage(SYS_PAGE); + // Stub handler at 0x5BF4 (offset 0x1BF4 in SYS_PAGE) + sysP[0x1BF4] = 0xFB; // EI + sysP[0x1BF5] = 0xED; // RETI prefix + sysP[0x1BF6] = 0x4D; // RETI + // Vector at 0x5BFF → lo=0xF4, hi=0x5B → handler at 0x5BF4 + sysP[0x1BFF] = 0xF4; // lo byte of handler address + // 0x5C00 = offset 0x1C00, but that's page boundary + // 0x5C00 is still in Win1 (0x4000–0x7FFF), offset 0x1C00 in SYS_PAGE + sysP[0x1C00] = 0x5B; // hi byte of handler address + } + Console.WriteLine(" IM2 stub handler at 0x5BF4 (EI+RETI), vector at 0x5BFF→0x5BF4"); + + Cpu.Tact = 0; + Cpu.FX = CpuModeIndex.None; + Cpu.XFX = CpuModeEx.None; + + Console.WriteLine($" PC=0x{Cpu.regs.PC:X4} SP=0x{Cpu.regs.SP:X4}"); + Console.WriteLine($" A=0x{Cpu.regs.A:X2} BC=0x{Cpu.regs.BC:X4} DE=0x{Cpu.regs.DE:X4} HL=0x{Cpu.regs.HL:X4}"); + Console.WriteLine($" File: \"{fname}\" ({fileSize} bytes) @ 0x{nameAddr:X4}"); + } + + // ═══════════════════════════════════════════════════════════════ + // Page protection + // ═══════════════════════════════════════════════════════════════ + + private void SetupProtection() + { + Console.WriteLine(); + Console.WriteLine("─── Page Protection ─────────────────────────────"); + + // NOTE: WC system page (0x05) contains the stack at ~0x5BF0, + // so we cannot write-protect it. The plugin legitimately writes there. + // We only protect pages that should NEVER be written by the plugin. + + // Protect font page (read-only, plugin should never write fonts) + Mem.ProtectPage(FONT_PAGE); + Console.WriteLine($" Protected: page 0x{FONT_PAGE:X2} (font)"); + + // Hook for violation logging + Mem.OnViolation = v => + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($" !! {v} at tact={Cpu.Tact} PC=0x{Cpu.regs.PC:X4}"); + Console.ResetColor(); + }; + } + + // ═══════════════════════════════════════════════════════════════ + // Port I/O handlers + // ═══════════════════════════════════════════════════════════════ + + private byte HandlePortRead(ushort port) + { + // ZX keyboard ports (0xFE with specific A8-A15) + // Return 0xFF = no key pressed (all bits high) + if ((port & 0xFF) == 0xFE) return 0xFF; + + // AY register read + if (port == PORT_AY_ADDR) return 0xFF; + + return 0xFF; + } + + private void HandlePortWrite(ushort port, byte value) + { + // TSConfig page ports + if (Mem.HandlePort(port, value)) + { + if (VerbosePageSwitch) + Console.WriteLine($" [{Cpu.Tact,10}] Page port 0x{port:X4} = 0x{value:X2}"); + return; + } + + switch (port) + { + // ZX border + case PORT_BORDER: + break; + case PORT_TSCONF_BORDER: + break; + + // OPL3 ports + case PORT_OPL3_ADDR0: + _opl3Addr0 = value; + break; + case PORT_OPL3_DATA: + _opl3WriteCount++; + break; + case PORT_OPL3_ADDR1: + _opl3Addr1 = value; + break; + + // AY ports + case PORT_AY_ADDR: + case PORT_AY_DATA: + break; + + // INT position ports (ISR uses OUTI to these) + case PORT_VSINTH: // 0x24AF — VCNT[8] (bit 0) + _intLine = (_intLine & 0xFF) | ((value & 0x01) << 8); + RecalcIntTarget(); + break; + case PORT_VSINTL: // 0x23AF — VCNT[7:0] + _intLine = (_intLine & 0x100) | value; + RecalcIntTarget(); + break; + case PORT_HSINT: // 0x22AF — HCNT[8:1], 0–223 + _intHPos = value; + RecalcIntTarget(); + break; + case PORT_INTMASK: // 0x2AAF + break; + + // Debug ports + case DBG_STATUS: + _lastDbgStatus = value; + break; + case DBG_SRC: + case DBG_DST: + break; + + // TSConfig system ports + case 0x20AF: // SYSCONF (turbo bits [1:0]) + { + int newTurbo = value & 0x03; + if (newTurbo != _turboMode) + { + int oldMult = _rasterMult; + _turboMode = newTurbo; + _rasterMult = GetRasterMult(newTurbo); + Console.WriteLine($" [TURBO] port 0x20AF = 0x{value:X2} mode {_turboMode} rasterMult {oldMult} → {_rasterMult}"); + } + break; + } + case 0x2BAF: // CACHECONF + break; + + default: + // Silently ignore unknown ports + break; + } + } + + // ═══════════════════════════════════════════════════════════════ + // Main execution loop + // ═══════════════════════════════════════════════════════════════ + + private int RunCpu() + { + _finished = false; + _lastWcCallTact = 0; + _rasterPos = 0; + _prevTact = 0; + _intFired = false; + RecalcIntTarget(); + long progressTact = 0; + bool inWin1Warned = false; // only warn once about Win1 stray + + while (!_finished) + { + ushort pc = Cpu.regs.PC; + + // ── Sentinel: plugin returned to 0x0000 ── + if (pc == 0x0000) + { + byte exitA = Cpu.regs.A; + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"\n Plugin returned at cycle {Cpu.Tact}"); + Console.WriteLine($" A = 0x{exitA:X2} (exit code: {ExitCodeName(exitA)})"); + Console.ResetColor(); + + // Dump stack area & VGM header + Console.WriteLine($" SP=0x{Cpu.regs.SP:X4}"); + Console.Write(" Stack area [SP-8..SP+8]: "); + for (int si = -8; si < 8; si++) + Console.Write($"{Mem.Read((ushort)(Cpu.regs.SP + si)):X2} "); + Console.WriteLine(); + // Dump megabuffer page 0 header (VGM signature area) + Console.Write(" Megabuf pg0 [0..15]: "); + var mbPage = Mem.GetPage(TVBPG); + for (int mi = 0; mi < 16; mi++) + Console.Write($"{mbPage[mi]:X2} "); + Console.WriteLine(); + Console.Write(" Win3 page 0xC000 [0..15]: "); + byte w3now = Mem.GetWindow(3); + var w3pg = Mem.GetPage(w3now); + for (int mi = 0; mi < 16; mi++) + Console.Write($"{w3pg[mi]:X2} "); + Console.WriteLine($" (Win3=0x{w3now:X2})"); + // Show hit/missed breakpoints + Console.Write(" Breakpoints hit: "); + foreach (var bp in _breakpoints) + Console.Write($"{bp.Value}={(_hitBreakpoints.Contains(bp.Key) ? "YES" : "NO")} "); + Console.WriteLine(); + + _finished = true; + return 0; + } + + // ── PC range check: detect stray into Win0/Win1 ── + if (pc >= 0x4000 && pc < 0x8000 && pc != WcKernel.WC_ENTRY + && pc != ISR_STUB_ADDR && pc != ISR_STUB_ADDR + 1 && pc != ISR_STUB_ADDR + 2 + && !inWin1Warned) + { + // PC wandered into Win1 (system page) — NOT the WC_ENTRY + inWin1Warned = true; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n !! STRAY PC: 0x{pc:X4} (Win1 area) at cycle {Cpu.Tact}"); + Console.ResetColor(); + PrintCpuState(); + Console.WriteLine(" Trace (last 20):"); + Trace.Dump(20); + DumpState("stray_win1"); + return 7; + } + if (pc < 0x4000 && pc != 0x0000 && !inWin1Warned) + { + inWin1Warned = true; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n !! STRAY PC: 0x{pc:X4} (Win0 area) at cycle {Cpu.Tact}"); + Console.ResetColor(); + PrintCpuState(); + Console.WriteLine(" Trace (last 20):"); + Trace.Dump(20); + DumpState("stray_win0"); + return 7; + } + + // ── WC API trap ── + if (Wc.TryIntercept()) + { + _lastWcCallTact = Cpu.Tact; + + if (VerboseWcCalls && Wc.CallLog.Count > 0) + { + var last = Wc.CallLog[^1]; + Console.WriteLine($" [{Cpu.Tact,10}] WC 0x{last.FuncNum:X2} ({WcKernel.GetFuncName(last.FuncNum)})"); + + // After MNGCVPL: show what Win3 now maps to and first 4 bytes at 0xC000 + if (last.FuncNum == WcKernel.FN_MNGCVPL) + { + byte w3 = Mem.GetWindow(3); + var pg = Mem.GetPage(w3); + Console.WriteLine($" → Win3=0x{w3:X2} @C000: {pg[0]:X2} {pg[1]:X2} {pg[2]:X2} {pg[3]:X2}"); + } + } + + continue; + } + + // ── Function breakpoints ── + if (_breakpoints.TryGetValue(pc, out string? bpName) && _hitBreakpoints.Add(pc)) + { + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine($"\n ► ENTER {bpName}() at cycle {Cpu.Tact}"); + Console.WriteLine($" A=0x{Cpu.regs.A:X2} HL=0x{Cpu.regs.HL:X4} DE=0x{Cpu.regs.DE:X4} BC=0x{Cpu.regs.BC:X4} SP=0x{Cpu.regs.SP:X4}"); + Console.WriteLine($" Win: [{Mem.GetWindow(0):X2} {Mem.GetWindow(1):X2} {Mem.GetWindow(2):X2} {Mem.GetWindow(3):X2}]"); + // For copy_pages_to_tap / inflate_vgz: show stack (return address) + Console.Write(" Stack: "); + for (int si = 0; si < 12; si++) + Console.Write($"{Mem.Read((ushort)(Cpu.regs.SP + si)):X2} "); + Console.WriteLine(); + Console.ResetColor(); + } + + // ── Post copy_pages_to_tap trace (0x8903-0x8920) ── + if (pc >= 0x8903 && pc <= 0x8920 && !_postCptLogged) + { + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.Write($" [T{Cpu.Tact}] PC={pc:X4} op={Mem.Read(pc):X2}"); + Console.Write($" AF={Cpu.regs.AF:X4} BC={Cpu.regs.BC:X4} DE={Cpu.regs.DE:X4} HL={Cpu.regs.HL:X4} SP={Cpu.regs.SP:X4}"); + Console.WriteLine($" W=[{Mem.GetWindow(0):X2},{Mem.GetWindow(1):X2},{Mem.GetWindow(2):X2},{Mem.GetWindow(3):X2}]"); + Console.ResetColor(); + if (pc == 0x8920) _postCptLogged = true; // stop after range + } + + // ── Post inflate_vgz result (0x8917 = or a,a after call _inflate_vgz) ── + if (pc == 0x8917 && !_inflateResultLogged) + { + _inflateResultLogged = true; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"\n ◄ inflate_vgz() returned A=0x{Cpu.regs.A:X2} (0=OK) at cycle {Cpu.Tact}"); + // Dump megabuf page 0 first 128 bytes (decompressed VGM header) + byte[] mbPg0 = Mem.GetPage(TVBPG); + Console.WriteLine(" Decompressed VGM header (megabuf pg0, first 128 bytes):"); + for (int row = 0; row < 8; row++) + { + Console.Write($" {row * 16:X4}: "); + for (int col = 0; col < 16; col++) + Console.Write($"{mbPg0[row * 16 + col]:X2} "); + Console.WriteLine(); + } + // Write tracker for first 16 bytes + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine(" Megabuf pg0 write tracker (first 16 bytes):"); + for (int i = 0; i < 16; i++) + { + if (_mbPg0Cnt[i] == 0) + Console.WriteLine($" [{i:X2}] NEVER WRITTEN (current=0x{mbPg0[i]:X2})"); + else + { + Console.Write($" [{i:X2}] writes={_mbPg0Cnt[i]}"); + Console.Write($" first=(0x{_mbPg0First[i].Value:X2} @T{_mbPg0First[i].Cycle} z80=0x{_mbPg0First[i].Z80Addr:X4} PC=0x{_mbPg0First[i].PC:X4})"); + if (_mbPg0Cnt[i] >= 2) + Console.Write($" prev=(0x{_mbPg0Prev[i].Value:X2} @T{_mbPg0Prev[i].Cycle} z80=0x{_mbPg0Prev[i].Z80Addr:X4} PC=0x{_mbPg0Prev[i].PC:X4})"); + Console.Write($" last=(0x{_mbPg0Last[i].Value:X2} @T{_mbPg0Last[i].Cycle} z80=0x{_mbPg0Last[i].Z80Addr:X4} PC=0x{_mbPg0Last[i].PC:X4})"); + Console.WriteLine(); + } + } + Console.ResetColor(); + } + + // ── Post vgm_parse_header result (0x8BBA = or a,a after call _vgm_parse_header) ── + if (pc == 0x8BBA && !_parseHeaderResultLogged) + { + _parseHeaderResultLogged = true; + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"\n ◄ vgm_parse_header() returned A=0x{Cpu.regs.A:X2} (0=VGM_OK) at cycle {Cpu.Tact}"); + // If error, dump the Win3 view of header (should be megabuf pg0 mapped by mngcvpl(0)) + if (Cpu.regs.A != 0) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(" !! VGM PARSE HEADER FAILED — dumping Win3 (megabuf pg0):"); + byte w3 = Mem.GetWindow(3); + byte[] pg = Mem.GetPage(w3); + for (int row = 0; row < 16; row++) + { + Console.Write($" {row * 16:X4}: "); + for (int col = 0; col < 16; col++) + Console.Write($"{pg[row * 16 + col]:X2} "); + Console.WriteLine(); + } + } + Console.ResetColor(); + } + + // ── Record trace entry ── + { + var entry = new TraceEntry + { + Tact = Cpu.Tact, + PC = pc, + Opcode = Mem.Read(pc), + AF = Cpu.regs.AF, BC = Cpu.regs.BC, + DE = Cpu.regs.DE, HL = Cpu.regs.HL, + AF2 = Cpu.regs._AF, BC2 = Cpu.regs._BC, + DE2 = Cpu.regs._DE, HL2 = Cpu.regs._HL, + IX = Cpu.regs.IX, IY = Cpu.regs.IY, + SP = Cpu.regs.SP, + Win0 = Mem.GetWindow(0), Win1 = Mem.GetWindow(1), + Win2 = Mem.GetWindow(2), Win3 = Mem.GetWindow(3), + WriteAddr = 0xFFFF, + WcFuncNum = 0xFF + }; + Trace.Record(ref entry); + } + + // ── Execute one instruction ── + Cpu.ExecCycle(); + + // ── Raster-based INT emulation ── + // Advance raster by CPU T-states consumed, scaled to 3.5 MHz base. + // Fire INT when raster crosses programmed VSINT/HSINT position. + { + long cpuDelta = Cpu.Tact - _prevTact; + _prevTact = Cpu.Tact; + + long prevRaster = _rasterPos; + _rasterPos += cpuDelta * _rasterMult; + + bool crossed = false; + if (_rasterPos >= FRAME_RASTER) + { + // Old-frame tail: check with previous _intFired state + if (!_intFired && _intTargetRaster > prevRaster) + crossed = true; + + _rasterPos %= FRAME_RASTER; + _intFired = false; // new frame → re-arm + + // New-frame start: target near raster origin + if (!crossed && _intTargetRaster <= _rasterPos) + crossed = true; + } + else + { + crossed = !_intFired && + _intTargetRaster > prevRaster && _intTargetRaster <= _rasterPos; + } + + if (crossed) + { + _intFired = true; + Cpu.INT = true; + _isrFireCount++; + if (_isrFireCount <= 5 || _isrFireCount % 100000 == 0) + { + int line = (int)(_intTargetRaster / RASTER_SCALE / LINE_TACTS); + int hpos = (int)(_intTargetRaster / RASTER_SCALE % LINE_TACTS); + Console.WriteLine($" [ISR #{_isrFireCount}] T={Cpu.Tact} line={line} hpos={hpos} PC=0x{Cpu.regs.PC:X4}"); + } + } + } + if (_lastWcCallTact > 0 && (Cpu.Tact - _lastWcCallTact) > WatchdogLimit) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n WATCHDOG: {Cpu.Tact - _lastWcCallTact} cycles since last WC call"); + Console.WriteLine($" PC=0x{pc:X4} SP=0x{Cpu.regs.SP:X4}"); + Console.ResetColor(); + PrintCpuState(); + return 4; + } + + // ── Max cycles ── + if (Cpu.Tact > MaxCycles) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n MAX CYCLES ({MaxCycles}) exceeded"); + Console.ResetColor(); + return 5; + } + + // ── HALT detection ── + if (Cpu.HALTED) + { + // In IM2, HALT waits for next INT — that's normal for ISR idle + // But if interrupts are disabled + HALT, it's a deadlock + if (!Cpu.IFF1) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n DEADLOCK: HALT with DI at PC=0x{pc:X4}"); + Console.ResetColor(); + return 6; + } + } + + // ── Progress indicator every 100M cycles ── + if (Cpu.Tact - progressTact > 100_000_000) + { + progressTact = Cpu.Tact; + Console.Write($"\r Cycles: {Cpu.Tact / 1_000_000}M WC calls: {Wc.CallLog.Count} OPL3 writes: {_opl3WriteCount} "); + } + } + + return 0; + } + + // ═══════════════════════════════════════════════════════════════ + // Analysis and dumps + // ═══════════════════════════════════════════════════════════════ + + private void PrintCpuState() + { + var r = Cpu.regs; + Console.WriteLine($" AF={r.AF:X4} BC={r.BC:X4} DE={r.DE:X4} HL={r.HL:X4}"); + Console.WriteLine($" AF'={r._AF:X4} BC'={r._BC:X4} DE'={r._DE:X4} HL'={r._HL:X4}"); + Console.WriteLine($" IX={r.IX:X4} IY={r.IY:X4} SP={r.SP:X4} PC={r.PC:X4}"); + Console.WriteLine($" IFF1={Cpu.IFF1} IFF2={Cpu.IFF2} IM={Cpu.IM} HALTED={Cpu.HALTED}"); + Console.WriteLine($" Win0=0x{Mem.GetWindow(0):X2} Win1=0x{Mem.GetWindow(1):X2} Win2=0x{Mem.GetWindow(2):X2} Win3=0x{Mem.GetWindow(3):X2}"); + + Console.Write(" Code @PC: "); + for (int i = 0; i < 8; i++) + Console.Write($"{Mem.Read((ushort)(r.PC + i)):X2} "); + Console.WriteLine(); + + Console.Write(" Stack: "); + for (int i = 0; i < 8; i++) + Console.Write($"{Mem.Read((ushort)(r.SP + i)):X2} "); + Console.WriteLine(); + } + + private void PrintAnalysis() + { + Console.WriteLine($" Total cycles: {Cpu.Tact:N0}"); + Console.WriteLine($" WC API calls: {Wc.CallLog.Count}"); + Console.WriteLine($" OPL3 writes: {_opl3WriteCount}"); + Console.WriteLine($" Violations: {Mem.Violations.Count}"); + Console.WriteLine($" Trace entries: {Trace.Count}"); + Console.WriteLine($" Dirty pages: {Mem.DirtyPages.Count}"); + + // WC function frequency + if (Wc.CallLog.Count > 0) + { + Console.WriteLine(); + Console.WriteLine(" WC function frequency:"); + var freq = Wc.CallLog + .GroupBy(c => c.FuncNum) + .OrderByDescending(g => g.Count()) + .Take(15); + foreach (var g in freq) + Console.WriteLine($" 0x{g.Key:X2} ({WcKernel.GetFuncName(g.Key),-12}): {g.Count()}"); + } + + // Protection violations summary + if (Mem.Violations.Count > 0) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(); + Console.WriteLine($" !! {Mem.Violations.Count} PROTECTION VIOLATIONS:"); + foreach (var v in Mem.Violations.Take(10)) + Console.WriteLine($" {v}"); + if (Mem.Violations.Count > 10) + Console.WriteLine($" ... and {Mem.Violations.Count - 10} more"); + Console.ResetColor(); + } + } + + private void DumpState(string reason) + { + if (string.IsNullOrEmpty(DumpDir)) return; + + try + { + Directory.CreateDirectory(DumpDir); + + // CPU trace + Trace.DumpToFile(Path.Combine(DumpDir, $"{reason}_trace.txt")); + Console.WriteLine($" Dumped: {reason}_trace.txt"); + + // WC call log + File.WriteAllText( + Path.Combine(DumpDir, $"{reason}_wc_calls.txt"), + Wc.DumpCallLog(200)); + Console.WriteLine($" Dumped: {reason}_wc_calls.txt"); + + // CPU state + using (var sw = new StreamWriter(Path.Combine(DumpDir, $"{reason}_cpu_state.txt"))) + { + var r = Cpu.regs; + sw.WriteLine($"Reason: {reason}"); + sw.WriteLine($"Tact: {Cpu.Tact}"); + sw.WriteLine($"AF={r.AF:X4} BC={r.BC:X4} DE={r.DE:X4} HL={r.HL:X4}"); + sw.WriteLine($"AF'={r._AF:X4} BC'={r._BC:X4} DE'={r._DE:X4} HL'={r._HL:X4}"); + sw.WriteLine($"IX={r.IX:X4} IY={r.IY:X4} SP={r.SP:X4} PC={r.PC:X4}"); + sw.WriteLine($"Wins: {Mem.GetWindow(0):X2} {Mem.GetWindow(1):X2} {Mem.GetWindow(2):X2} {Mem.GetWindow(3):X2}"); + sw.WriteLine($"IFF1={Cpu.IFF1} IM={Cpu.IM}"); + } + Console.WriteLine($" Dumped: {reason}_cpu_state.txt"); + + // Violations + if (Mem.Violations.Count > 0) + { + using var vw = new StreamWriter(Path.Combine(DumpDir, $"{reason}_violations.txt")); + foreach (var v in Mem.Violations) + vw.WriteLine(v); + Console.WriteLine($" Dumped: {reason}_violations.txt ({Mem.Violations.Count} violations)"); + } + } + catch (Exception ex) + { + Console.Error.WriteLine($" Dump error: {ex.Message}"); + } + } + + private static string ExitCodeName(byte code) => code switch + { + 0 => "ESC/Exit", + 1 => "Unrecognized (pass to next plugin)", + 2 => "Next file", + 3 => "Reload directory", + 4 => "Previous file", + _ => $"unknown ({code})" + }; + } +} diff --git a/Debuger/Program.cs b/Debuger/Program.cs new file mode 100644 index 0000000..6042434 --- /dev/null +++ b/Debuger/Program.cs @@ -0,0 +1,674 @@ +// InflateDebugger — Z80 emulator for TSConfig inflate + WC plugin debugging +// +// MODE 1 — inflate (default): +// Loads VGZ file into emulated TSConfig memory pages (as tape loader would), +// loads inflate.bin into Win3, sets up registers as inflate_call.s would, +// and runs the Z80 CPU with debug port monitoring and watchdog. +// +// Usage: dotnet run [--vgz ] [--ref ] [--inflate ] +// [--dump-on-hang] [--max-cycles ] [--watchdog ] +// +// MODE 2 — plugin: +// Loads a WMF plugin binary, emulates WC kernel API (P8000 dispatch), +// emulates disk I/O from test_data directory, runs the full plugin lifecycle +// with CPU trace, page protection, and WC call logging. +// +// Usage: dotnet run --mode plugin --wmf [--file ] +// [--max-cycles ] [--verbose] + +using System; +using System.IO; +using System.IO.Compression; +using System.Collections.Generic; +using System.Linq; +using ZXMAK2.Engine.Cpu; +using ZXMAK2.Engine.Cpu.Processor; + +namespace InflateDebugger +{ + class Program + { + // ── TSConfig page assignments (matching real WC plugin) ── + const byte SRC_START_PAGE = 0xA1; // TAP pages for VGZ source + const byte DST_START_PAGE = 0x20; // megabuffer for decompressed output + const byte INFLATE_PAGE = 0x6C; // inflate code page (base 0x67 + 5) + const byte PAGE_DECODE = 0xEA; // decode working buffer page + const byte WC_BASE_PAGE = 0x67; // WC plugin base page + + // ── Ports ── + const ushort DBG_STATUS = 0xFFAF; + const ushort DBG_SRC = 0xFEAF; + const ushort DBG_DST = 0xFDAF; + + // ── Defaults ── + const long DEFAULT_MAX_CYCLES = 500_000_000; // 500M cycles max + const long DEFAULT_WATCHDOG = 200_000; // 200K cycles without dbg port = hang + + // ── State ── + static TsMemory mem = new(); + static CpuUnit cpu = new(); + + static long lastDbgCycle = 0; + static long watchdogLimit = DEFAULT_WATCHDOG; + static long maxCycles = DEFAULT_MAX_CYCLES; + static bool dumpOnHang = true; + static bool finished = false; + static byte lastDbgStatus = 0; + static byte lastDbgSrc = 0; + static byte lastDbgDst = 0; + static int srcPagesLoaded = 0; + static string? refPath = null; + static string dumpDir = ""; + + // Track which pages were written as destination + static HashSet dstPagesWritten = new(); + + static int Main(string[] args) + { + string basePath = FindProjectRoot(); + + // ── Detect mode ── + string mode = "inflate"; // default + for (int i = 0; i < args.Length; i++) + { + if (args[i] == "--mode" && i + 1 < args.Length) + { + mode = args[++i].ToLowerInvariant(); + break; + } + } + + return mode switch + { + "plugin" => RunPluginMode(args, basePath), + _ => RunInflateMode(args, basePath) + }; + } + + // ═══════════════════════════════════════════════════════════════ + // Plugin mode — runs full WMF plugin with WC API emulation + // ═══════════════════════════════════════════════════════════════ + + static int RunPluginMode(string[] args, string basePath) + { + string? wmfPath = null; + string? testFile = null; + string? diskRoot = Path.Combine(basePath, "Debuger", "test_data"); + long maxCyclesP = 2_000_000_000; + bool verbose = true; + bool verbosePages = false; + + for (int i = 0; i < args.Length; i++) + { + switch (args[i]) + { + case "--wmf" when i + 1 < args.Length: + wmfPath = args[++i]; break; + case "--file" when i + 1 < args.Length: + testFile = args[++i]; break; + case "--disk-root" when i + 1 < args.Length: + diskRoot = args[++i]; break; + case "--max-cycles" when i + 1 < args.Length: + maxCyclesP = long.Parse(args[++i]); break; + case "--verbose": + verbose = true; break; + case "--quiet": + verbose = false; break; + case "--verbose-pages": + verbosePages = true; break; + } + } + + // Default WMF: look in DiskRef/WC/VGMPLAY.WMF or variants/ + if (wmfPath == null) + { + wmfPath = Path.Combine(basePath, @"DiskRef\WC\VGMPLAY.WMF"); + if (!File.Exists(wmfPath)) + wmfPath = Path.Combine(basePath, @"variants\VGMPLAY_v1_b8.WMF"); + } + + // Default test file: first .vgz in test_data + if (testFile == null && Directory.Exists(diskRoot!)) + { + var firstVgz = Directory.GetFiles(diskRoot!, "*.vgz") + .OrderBy(f => f).FirstOrDefault(); + if (firstVgz != null) + testFile = Path.GetFileName(firstVgz); + } + + var runner = new PluginRunner + { + MaxCycles = maxCyclesP, + VerboseWcCalls = verbose, + VerbosePageSwitch = verbosePages, + DumpOnExit = true, + DumpDir = Path.Combine(basePath, @"Debuger\dumps"), + DiskRootPath = diskRoot!, + TestFileName = testFile + }; + + return runner.Run(wmfPath!); + } + + // ═══════════════════════════════════════════════════════════════ + // Inflate mode — original inflate test harness + // ═══════════════════════════════════════════════════════════════ + + static int RunInflateMode(string[] args, string basePath) + { + string vgzPath = Path.Combine(basePath, + @"DiskRef\Music\OPL2\Arcade\_\Act-Fancer_Cybernetick_Hyper_Weapon_(Arcade)\02 Sci-Vax (BGM 1).vgz"); + string inflatePath = Path.Combine(basePath, @"src_sdcc\build\inflate.bin"); + refPath = Path.Combine(basePath, @"Debuger\ref.vgm"); + dumpDir = Path.Combine(basePath, @"Debuger\dumps"); + + // Parse args + for (int i = 0; i < args.Length; i++) + { + switch (args[i]) + { + case "--vgz" when i + 1 < args.Length: vgzPath = args[++i]; break; + case "--ref" when i + 1 < args.Length: refPath = args[++i]; break; + case "--inflate" when i + 1 < args.Length: inflatePath = args[++i]; break; + case "--max-cycles" when i + 1 < args.Length: maxCycles = long.Parse(args[++i]); break; + case "--watchdog" when i + 1 < args.Length: watchdogLimit = long.Parse(args[++i]); break; + case "--dump-on-hang": dumpOnHang = true; break; + case "--no-dump": dumpOnHang = false; break; + } + } + + Console.WriteLine("═══════════════════════════════════════════════════"); + Console.WriteLine(" TSConfig Inflate Debugger"); + Console.WriteLine("═══════════════════════════════════════════════════"); + + // ─── 1. Load VGZ file ─── + if (!File.Exists(vgzPath)) + { + Console.Error.WriteLine($"VGZ file not found: {vgzPath}"); + return 1; + } + byte[] vgzData = File.ReadAllBytes(vgzPath); + Console.WriteLine($"VGZ file: {vgzPath}"); + Console.WriteLine($"VGZ size: {vgzData.Length} bytes"); + + // ─── 2. Load inflate.bin ─── + if (!File.Exists(inflatePath)) + { + Console.Error.WriteLine($"inflate.bin not found: {inflatePath}"); + return 1; + } + byte[] inflateData = File.ReadAllBytes(inflatePath); + Console.WriteLine($"inflate.bin: {inflateData.Length} bytes"); + + // ─── 3. Set up TSConfig memory ─── + SetupMemory(vgzData, inflateData); + + // ─── 4. Set up CPU ─── + SetupCpu(); + + // ─── 5. Run ─── + Console.WriteLine(); + Console.WriteLine("─── Execution Start ─────────────────────────────"); + Console.WriteLine(); + + int exitCode = RunCpu(); + + // ─── 6. Post-run analysis ─── + Console.WriteLine(); + Console.WriteLine("─── Post-Run Analysis ───────────────────────────"); + AnalyzeResults(); + + return exitCode; + } + + static string FindProjectRoot() + { + // Walk up from current dir or exe dir looking for Debuger folder + string? dir = AppDomain.CurrentDomain.BaseDirectory; + for (int i = 0; i < 10 && dir != null; i++) + { + if (Directory.Exists(Path.Combine(dir, "Debuger")) && + Directory.Exists(Path.Combine(dir, "src_sdcc"))) + return dir; + dir = Path.GetDirectoryName(dir); + } + // Try current directory + dir = Environment.CurrentDirectory; + for (int i = 0; i < 10 && dir != null; i++) + { + if (Directory.Exists(Path.Combine(dir, "Debuger")) && + Directory.Exists(Path.Combine(dir, "src_sdcc"))) + return dir; + dir = Path.GetDirectoryName(dir); + } + return @"D:\zx_projects\OplPlug"; + } + + static void SetupMemory(byte[] vgzData, byte[] inflateData) + { + Console.WriteLine(); + Console.WriteLine("─── Memory Setup ────────────────────────────────"); + + // Load VGZ into TAP pages (0xA1+) — as WC tape loader would + srcPagesLoaded = mem.LoadToPages(vgzData, SRC_START_PAGE); + Console.WriteLine($"VGZ loaded to pages 0x{SRC_START_PAGE:X2}–0x{SRC_START_PAGE + srcPagesLoaded - 1:X2} ({srcPagesLoaded} pages)"); + + // Load inflate.bin into inflate page (0x6C) + // inflate.bin is padded to fill code + RAM tables within one 16KB page + if (inflateData.Length > TsMemory.PageSize) + { + Console.WriteLine($"WARNING: inflate.bin ({inflateData.Length}) > 16KB, truncating"); + } + Array.Copy(inflateData, 0, mem.GetPage(INFLATE_PAGE), 0, + Math.Min(inflateData.Length, TsMemory.PageSize)); + Console.WriteLine($"inflate.bin loaded to page 0x{INFLATE_PAGE:X2}"); + + // Set up initial window mapping: + // Win0 = source page 0xA1 (set by inflate entry code) + // Win1 = don't care yet (inflate sets it) + // Win2 = WC base page 0x67 (current plugin page — inflate will remap to PAGE_DECODE) + // Win3 = inflate page 0x6C (set by inflate_call.s before JP 0xC000) + mem.SetWindow(0, SRC_START_PAGE); // Will be set by inflate entry + mem.SetWindow(1, 0x00); // Don't care — inflate sets Win1 + mem.SetWindow(2, WC_BASE_PAGE); // Current Win2 = plugin base + mem.SetWindow(3, INFLATE_PAGE); // inflate code + + // Set up WC system variables at 0x6000–0x6003 + // These are in the page mapped to Win1 at offset 0x2000 + // Actually in the real system, Win1 maps some WC page. + // We need 0x6000–0x6003 readable. Let's put a known page in Win1 + // that has these values. In WC, Win1 = page at sys var area. + // For simplicity: map Win1 to a scratch page, write sys vars there + byte sysVarPage = 0xFE; // use a spare page for sys vars + mem.SetWindow(1, sysVarPage); + // 0x6000 = offset 0x2000 in Win1 + // Write the page values as WC would have them: + mem.GetPage(sysVarPage)[0x2000] = SRC_START_PAGE; // 0x6000 = Win0 page + mem.GetPage(sysVarPage)[0x2001] = sysVarPage; // 0x6001 = Win1 page + mem.GetPage(sysVarPage)[0x2002] = WC_BASE_PAGE; // 0x6002 = Win2 page (plugin base) + mem.GetPage(sysVarPage)[0x2003] = INFLATE_PAGE; // 0x6003 = Win3 page + + Console.WriteLine($"Windows: W0=0x{SRC_START_PAGE:X2} W1=0x{sysVarPage:X2} W2=0x{WC_BASE_PAGE:X2} W3=0x{INFLATE_PAGE:X2}"); + Console.WriteLine($"Sys vars @0x6000: [{SRC_START_PAGE:X2} {sysVarPage:X2} {WC_BASE_PAGE:X2} {INFLATE_PAGE:X2}]"); + + // Display first bytes of inflate at 0xC000 + Console.Write("inflate @0xC000: "); + for (int i = 0; i < 16; i++) + Console.Write($"{mem.Read((ushort)(0xC000 + i)):X2} "); + Console.WriteLine(); + } + + static void SetupCpu() + { + Console.WriteLine(); + Console.WriteLine("─── CPU Setup ───────────────────────────────────"); + + // Wire CPU bus to TSConfig memory + cpu.RDMEM = (addr) => mem.Read(addr); + cpu.RDMEM_M1 = (addr) => mem.Read(addr); + cpu.WRMEM = (addr, val) => mem.Write(addr, val); + cpu.RDPORT = (port) => 0xFF; // unused reads return 0xFF + cpu.WRPORT = HandlePortWrite; + cpu.RDNOMREQ = (addr) => { }; + cpu.WRNOMREQ = (addr) => { }; + cpu.RESET = () => { }; + cpu.NMIACK_M1 = () => { }; + cpu.INTACK_M1 = () => { }; + cpu.SCANSIG = () => { }; + + // No interrupts during inflate + cpu.IFF1 = false; + cpu.IFF2 = false; + cpu.INT = false; + cpu.NMI = false; + cpu.RST = false; + cpu.IM = 1; + + // Set up registers as inflate_call.s would before JP 0xC000: + // A = src start page (0xA1) + // E = dst start page (0x20) + // D = saved Win 2 page (0x67 — the plugin base page) + // SP = 0xFFF0 + // Return address pushed on stack + cpu.regs.A = SRC_START_PAGE; // 0xA1 + cpu.regs.E = DST_START_PAGE; // 0x20 + cpu.regs.D = WC_BASE_PAGE; // 0x67 — saved pg2 + cpu.regs.SP = 0xFFF0; + cpu.regs.PC = 0xC000; + + // Push a sentinel return address (0x0000) so RET goes to addr 0, + // which we detect as "inflate returned" + cpu.regs.SP -= 2; + mem.Write(cpu.regs.SP, 0x00); // low byte + mem.Write((ushort)(cpu.regs.SP + 1), 0x00); // high byte + + cpu.Tact = 0; + cpu.FX = CpuModeIndex.None; + cpu.XFX = CpuModeEx.None; + cpu.HALTED = false; + + Console.WriteLine($"PC=0x{cpu.regs.PC:X4} SP=0x{cpu.regs.SP:X4}"); + Console.WriteLine($"A=0x{cpu.regs.A:X2} D=0x{cpu.regs.D:X2} E=0x{cpu.regs.E:X2}"); + Console.WriteLine($"Return sentinel at [0x{cpu.regs.SP:X4}] = 0x0000"); + } + + static void HandlePortWrite(ushort port, byte value) + { + // TSConfig page ports + if (mem.HandlePort(port, value)) + { + // Log page changes (optional verbose) + return; + } + + // Debug ports + switch (port) + { + case DBG_STATUS: + lastDbgStatus = value; + lastDbgCycle = cpu.Tact; + PrintDbgStatus(value); + break; + + case DBG_SRC: + lastDbgSrc = value; + lastDbgCycle = cpu.Tact; + break; + + case DBG_DST: + lastDbgDst = value; + lastDbgCycle = cpu.Tact; + break; + } + } + + static readonly Dictionary StatusNames = new() + { + [0x0E] = "inflate entry", + [0x0F] = "stores done", + [0x10] = "Win0+Win2 remapped", + [0x11] = "Win2→PAGE_DECODE", + [0x12] = "gzip header OK", + [0x14] = "blocktype0 (stored)", + [0x15] = "blocktype1 (static)", + [0x16] = "blocktype2 (dynamic)", + [0x17] = "Huffman tables built → decode loop", + [0x20] = "page flush (bt0)", + [0x21] = "page flush (decode)", + [0x30] = "EXIT OK", + [0x31] = "FAIL", + [0xA6] = "bt2: HLIT/HDIST/HCLEN read", + [0xA7] = "bt2: makeHuffmanTable(19) done", + [0xA8] = "bt2: decodeProtoHuffman done", + [0xA9] = "bt2: code_length_orders read → mht", + [0xB1] = "mht: count loop done", + [0xB2] = "mht: start codes done", + [0xB3] = "mht: code assignment done", + [0xBF] = "decodeProtoHuffman entry", + [0xC0] = "dph: loop iteration", + [0xC1] = "dph: leaf found", + [0xC2] = "dph: special code", + }; + + static void PrintDbgStatus(byte code) + { + string name = StatusNames.TryGetValue(code, out var n) ? n : "???"; + string extra = ""; + // For codes that also emit SRC/DST, show them + if (code == 0xC1 || code == 0xC2) + extra = $" FEAF=0x{lastDbgSrc:X2}"; + else if (code == 0x10 || code == 0x20 || code == 0x21 || code == 0x30) + extra = $" src_pg=0x{lastDbgSrc:X2} dst_pg=0x{lastDbgDst:X2}"; + + Console.ForegroundColor = code switch + { + 0x30 => ConsoleColor.Green, + 0x31 => ConsoleColor.Red, + >= 0xB0 and <= 0xBF => ConsoleColor.DarkGray, // mht internals + >= 0xC0 and <= 0xCF => ConsoleColor.DarkCyan, // dph internals + _ => ConsoleColor.Yellow + }; + Console.WriteLine($" [{cpu.Tact,10}] DBG 0x{code:X2}: {name}{extra}"); + Console.ResetColor(); + } + + static int RunCpu() + { + long cycleCount = 0; + + while (!finished) + { + // ── Watchdog: execution outside Win3 ── + ushort pc = cpu.regs.PC; + if (pc < 0xC000) + { + if (pc == 0x0000) + { + // Sentinel return address — inflate returned! + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"\n ✓ Inflate returned at cycle {cpu.Tact}, A=0x{cpu.regs.A:X2}"); + Console.ResetColor(); + if (cpu.regs.A == 0) + Console.WriteLine(" Result: SUCCESS (A=0, no carry)"); + else + Console.WriteLine($" Result: FAIL (A=0x{cpu.regs.A:X2})"); + + finished = true; + DumpState("return"); + return cpu.regs.A == 0 ? 0 : 2; + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n ✗ RUNAWAY: PC=0x{pc:X4} at cycle {cpu.Tact}"); + Console.WriteLine($" Last DBG: 0x{lastDbgStatus:X2} at cycle {lastDbgCycle}"); + Console.ResetColor(); + DumpState("runaway"); + return 3; + } + } + + // ── Watchdog: cycles without debug port activity ── + if (cpu.Tact - lastDbgCycle > watchdogLimit && lastDbgCycle > 0) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n ✗ WATCHDOG: {cpu.Tact - lastDbgCycle} cycles since last debug output"); + Console.WriteLine($" Last DBG: 0x{lastDbgStatus:X2} at cycle {lastDbgCycle}"); + Console.WriteLine($" PC=0x{cpu.regs.PC:X4} SP=0x{cpu.regs.SP:X4}"); + Console.ResetColor(); + PrintCpuState(); + if (dumpOnHang) + DumpState("watchdog"); + return 4; + } + + // ── Watchdog: max total cycles ── + if (cpu.Tact > maxCycles) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n ✗ MAX CYCLES ({maxCycles}) exceeded"); + Console.ResetColor(); + DumpState("maxcycles"); + return 5; + } + + // ── HALT detection ── + if (cpu.HALTED) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n ✗ HALT at PC=0x{cpu.regs.PC:X4}, cycle {cpu.Tact}"); + Console.ResetColor(); + DumpState("halt"); + return 6; + } + + // ── Execute one instruction ── + cpu.ExecCycle(); + cycleCount++; + } + + return 0; + } + + static void PrintCpuState() + { + var r = cpu.regs; + Console.WriteLine($" AF={r.AF:X4} BC={r.BC:X4} DE={r.DE:X4} HL={r.HL:X4}"); + Console.WriteLine($" AF'={r._AF:X4} BC'={r._BC:X4} DE'={r._DE:X4} HL'={r._HL:X4}"); + Console.WriteLine($" IX={r.IX:X4} IY={r.IY:X4} SP={r.SP:X4} PC={r.PC:X4}"); + Console.WriteLine($" Win0=pg{mem.GetWindow(0):X2} Win1=pg{mem.GetWindow(1):X2} Win2=pg{mem.GetWindow(2):X2} Win3=pg{mem.GetWindow(3):X2}"); + + // Disassemble a few instructions around PC + Console.Write(" Code @PC: "); + for (int i = 0; i < 8; i++) + Console.Write($"{mem.Read((ushort)(r.PC + i)):X2} "); + Console.WriteLine(); + + // Stack dump + Console.Write(" Stack: "); + for (int i = 0; i < 8; i++) + Console.Write($"{mem.Read((ushort)(r.SP + i)):X2} "); + Console.WriteLine(); + } + + static void DumpState(string reason) + { + if (!dumpOnHang) return; + + try + { + Directory.CreateDirectory(dumpDir); + + // Dump inflate page (Win3) + string inflFile = Path.Combine(dumpDir, $"{reason}_inflate_page_{INFLATE_PAGE:X2}.bin"); + File.WriteAllBytes(inflFile, mem.GetPage(INFLATE_PAGE)); + Console.WriteLine($" Dumped: {inflFile}"); + + // Dump decode buffer page + string decFile = Path.Combine(dumpDir, $"{reason}_decode_page_{PAGE_DECODE:X2}.bin"); + File.WriteAllBytes(decFile, mem.GetPage(PAGE_DECODE)); + Console.WriteLine($" Dumped: {decFile}"); + + // Dump destination pages + for (int p = DST_START_PAGE; p < DST_START_PAGE + 16; p++) + { + byte[] pg = mem.GetPage(p); + bool allZero = true; + for (int i = 0; i < pg.Length && allZero; i++) + if (pg[i] != 0) allZero = false; + if (!allZero) + { + string dstFile = Path.Combine(dumpDir, $"{reason}_dst_page_{p:X2}.bin"); + File.WriteAllBytes(dstFile, pg); + } + } + Console.WriteLine($" Dumped destination pages (0x{DST_START_PAGE:X2}+)"); + + // Dump Win3 area (the whole mapped page, including runtime modifications) + string win3File = Path.Combine(dumpDir, $"{reason}_win3_mapped.bin"); + File.WriteAllBytes(win3File, mem.GetPage(mem.GetWindow(3))); + Console.WriteLine($" Dumped: {win3File} (Win3=page 0x{mem.GetWindow(3):X2})"); + + // CPU state dump + string stateFile = Path.Combine(dumpDir, $"{reason}_cpu_state.txt"); + using (var sw = new StreamWriter(stateFile)) + { + var r = cpu.regs; + sw.WriteLine($"Reason: {reason}"); + sw.WriteLine($"Tact: {cpu.Tact}"); + sw.WriteLine($"AF={r.AF:X4} BC={r.BC:X4} DE={r.DE:X4} HL={r.HL:X4}"); + sw.WriteLine($"AF'={r._AF:X4} BC'={r._BC:X4} DE'={r._DE:X4} HL'={r._HL:X4}"); + sw.WriteLine($"IX={r.IX:X4} IY={r.IY:X4} SP={r.SP:X4} PC={r.PC:X4}"); + sw.WriteLine($"Win0=0x{mem.GetWindow(0):X2} Win1=0x{mem.GetWindow(1):X2} Win2=0x{mem.GetWindow(2):X2} Win3=0x{mem.GetWindow(3):X2}"); + sw.WriteLine($"Last DBG status=0x{lastDbgStatus:X2} src=0x{lastDbgSrc:X2} dst=0x{lastDbgDst:X2}"); + sw.WriteLine($"Last DBG at cycle {lastDbgCycle}"); + sw.WriteLine(); + + // Disassemble around PC + sw.Write("Code @PC: "); + for (int i = 0; i < 32; i++) + sw.Write($"{mem.Read((ushort)(r.PC + i)):X2} "); + sw.WriteLine(); + + sw.Write("Stack: "); + for (int i = 0; i < 16; i++) + sw.Write($"{mem.Read((ushort)(r.SP + i)):X2} "); + sw.WriteLine(); + } + Console.WriteLine($" Dumped: {stateFile}"); + } + catch (Exception ex) + { + Console.Error.WriteLine($" Dump error: {ex.Message}"); + } + } + + static void AnalyzeResults() + { + if (refPath == null || !File.Exists(refPath)) + { + Console.WriteLine("No ref.vgm found, skipping comparison."); + return; + } + + byte[] refData = File.ReadAllBytes(refPath); + Console.WriteLine($"ref.vgm: {refData.Length} bytes"); + + // Collect decompressed output from destination pages + int refPages = (refData.Length + TsMemory.PageSize - 1) / TsMemory.PageSize; + byte[] output = mem.ReadPages(DST_START_PAGE, refPages); + + // Compare + int firstDiff = -1; + int matchBytes = 0; + int compareLen = Math.Min(refData.Length, output.Length); + + for (int i = 0; i < compareLen; i++) + { + if (output[i] == refData[i]) + matchBytes++; + else if (firstDiff < 0) + firstDiff = i; + } + + if (firstDiff < 0 && matchBytes == refData.Length) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($" ✓ PERFECT MATCH: {matchBytes} bytes identical to ref.vgm"); + Console.ResetColor(); + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($" ✗ MISMATCH at offset 0x{firstDiff:X}"); + Console.WriteLine($" Matched: {matchBytes}/{refData.Length} bytes ({100.0 * matchBytes / refData.Length:F1}%)"); + Console.ResetColor(); + + if (firstDiff >= 0) + { + Console.Write(" ref: "); + for (int i = Math.Max(0, firstDiff - 4); i < Math.Min(refData.Length, firstDiff + 12); i++) + Console.Write(i == firstDiff ? $"[{refData[i]:X2}]" : $" {refData[i]:X2} "); + Console.WriteLine(); + + Console.Write(" got: "); + for (int i = Math.Max(0, firstDiff - 4); i < Math.Min(output.Length, firstDiff + 12); i++) + Console.Write(i == firstDiff ? $"[{output[i]:X2}]" : $" {output[i]:X2} "); + Console.WriteLine(); + } + + // Save output for analysis + try + { + Directory.CreateDirectory(dumpDir); + string outFile = Path.Combine(dumpDir, "output.vgm"); + byte[] trimmed = new byte[refData.Length]; + Array.Copy(output, trimmed, Math.Min(output.Length, trimmed.Length)); + File.WriteAllBytes(outFile, trimmed); + Console.WriteLine($" Saved decompressed output: {outFile}"); + } + catch { } + } + } + } +} diff --git a/Debuger/TsMemory.cs b/Debuger/TsMemory.cs new file mode 100644 index 0000000..7bdd723 --- /dev/null +++ b/Debuger/TsMemory.cs @@ -0,0 +1,221 @@ +// TsMemory — TSConfig 256-page × 16KB paged memory with page protection +// Enhanced: write protection, dirty tracking, violation logging +using System; +using System.Collections.Generic; + +namespace InflateDebugger +{ + public class TsMemory + { + public const int PageSize = 0x4000; // 16KB + public const int TotalPages = 256; // 4 MB + + // Physical memory: 256 pages × 16KB + private readonly byte[][] _pages; + + // Window → physical page mapping + // Win0 = 0x0000–0x3FFF, Win1 = 0x4000–0x7FFF, + // Win2 = 0x8000–0xBFFF, Win3 = 0xC000–0xFFFF + private readonly byte[] _winPage = new byte[4]; + + // TSConfig page ports + public const ushort PORT_W0 = 0x10AF; + public const ushort PORT_W1 = 0x11AF; + public const ushort PORT_W2 = 0x12AF; + public const ushort PORT_W3 = 0x13AF; + + // ── Page protection ────────────────────────────────────────── + + /// Set of physical page numbers that are write-protected + private readonly HashSet _protectedPages = new(); + + /// Pages that have been written to (dirty tracking) + private readonly HashSet _dirtyPages = new(); + + /// Protection violation log + public List Violations { get; } = new(); + + /// Max violations to log before stopping (0 = unlimited) + public int MaxViolations { get; set; } = 100; + + /// If true, throw on protection violation + public bool ThrowOnViolation { get; set; } = false; + + /// Callback invoked on every write. Args: z80addr, page, offset, value + public Action OnWrite { get; set; } + + /// Callback invoked on protection violation + public Action OnViolation { get; set; } + + public TsMemory() + { + _pages = new byte[TotalPages][]; + for (int i = 0; i < TotalPages; i++) + _pages[i] = new byte[PageSize]; + } + + // ── Page protection API ────────────────────────────────────── + + public void ProtectPage(int page) => _protectedPages.Add(page); + public void UnprotectPage(int page) => _protectedPages.Remove(page); + public void ProtectRange(int startPage, int count) + { + for (int i = 0; i < count; i++) _protectedPages.Add(startPage + i); + } + public void UnprotectRange(int startPage, int count) + { + for (int i = 0; i < count; i++) _protectedPages.Remove(startPage + i); + } + public bool IsProtected(int page) => _protectedPages.Contains(page); + public void ClearProtection() => _protectedPages.Clear(); + + public IReadOnlyCollection DirtyPages => _dirtyPages; + public void ClearDirtyTracking() => _dirtyPages.Clear(); + + // ── Window management ──────────────────────────────────────── + + /// Set window mapping (0–3) to physical page + public void SetWindow(int win, byte page) + { + _winPage[win] = page; + } + + public byte GetWindow(int win) => _winPage[win]; + + // ── Memory access ──────────────────────────────────────────── + + /// Read byte at 16-bit Z80 address through page windows + public byte Read(ushort addr) + { + int win = addr >> 14; // 0–3 + int offset = addr & 0x3FFF; + return _pages[_winPage[win]][offset]; + } + + /// Write byte at 16-bit Z80 address (with protection check) + public void Write(ushort addr, byte val) + { + int win = addr >> 14; + int offset = addr & 0x3FFF; + byte page = _winPage[win]; + + _dirtyPages.Add(page); + OnWrite?.Invoke(addr, page, offset, val); + + if (_protectedPages.Contains(page)) + { + var v = new ProtectionViolation + { + Z80Addr = addr, + PhysPage = page, + Offset = offset, + Value = val, + OldValue = _pages[page][offset], + Window = win + }; + if (Violations.Count < MaxViolations || MaxViolations == 0) + Violations.Add(v); + OnViolation?.Invoke(v); + if (ThrowOnViolation) + throw new ProtectionViolationException(v); + } + + _pages[page][offset] = val; + } + + /// Direct write bypassing protection (for initial data loading) + public void WriteUnprotected(ushort addr, byte val) + { + int win = addr >> 14; + int offset = addr & 0x3FFF; + _pages[_winPage[win]][offset] = val; + } + + /// Direct access to a physical page + public byte[] GetPage(int page) => _pages[page]; + + /// Load data into physical pages starting at startPage + public int LoadToPages(byte[] data, int startPage) + { + int pagesNeeded = (data.Length + PageSize - 1) / PageSize; + for (int p = 0; p < pagesNeeded; p++) + { + int srcOff = p * PageSize; + int len = Math.Min(PageSize, data.Length - srcOff); + Array.Copy(data, srcOff, _pages[startPage + p], 0, len); + } + return pagesNeeded; + } + + /// Handle OUT to TSConfig page port + public bool HandlePort(ushort port, byte value) + { + switch (port) + { + case PORT_W0: SetWindow(0, value); return true; + case PORT_W1: SetWindow(1, value); return true; + case PORT_W2: SetWindow(2, value); return true; + case PORT_W3: SetWindow(3, value); return true; + default: return false; + } + } + + /// Read all output pages (destination) into a flat array + public byte[] ReadPages(int startPage, int count) + { + byte[] result = new byte[count * PageSize]; + for (int p = 0; p < count; p++) + Array.Copy(_pages[startPage + p], 0, result, p * PageSize, PageSize); + return result; + } + + /// Update WC system variable area (0x6000–0x600E) + public void UpdateSysVars() + { + byte sysPage = _winPage[1]; + _pages[sysPage][0x2000] = _winPage[0]; // 0x6000 = PG0 + _pages[sysPage][0x2001] = _winPage[1]; // 0x6001 = PG4 + _pages[sysPage][0x2002] = _winPage[2]; // 0x6002 = PG8 + _pages[sysPage][0x2003] = _winPage[3]; // 0x6003 = PGC + } + + /// Dump 16 bytes around an address for debugging + public string HexDump(ushort addr, int before = 8, int after = 8) + { + int start = Math.Max(0, addr - before); + int end = Math.Min(0xFFFF, addr + after); + var sb = new System.Text.StringBuilder(); + for (int a = start; a <= end; a++) + { + if (a == addr) sb.Append('['); + sb.AppendFormat("{0:X2}", Read((ushort)a)); + if (a == addr) sb.Append(']'); + else sb.Append(' '); + } + return sb.ToString(); + } + } + + // ── Protection violation types ─────────────────────────────────── + + public struct ProtectionViolation + { + public ushort Z80Addr; + public byte PhysPage; + public int Offset; + public byte Value; + public byte OldValue; + public int Window; + + public override string ToString() => + $"PROTECT VIOLATION: [{Z80Addr:X4}] pg={PhysPage:X2} off={Offset:X4} " + + $"old={OldValue:X2} new={Value:X2} win={Window}"; + } + + public class ProtectionViolationException : Exception + { + public ProtectionViolation Violation { get; } + public ProtectionViolationException(ProtectionViolation v) + : base(v.ToString()) { Violation = v; } + } +} diff --git a/Debuger/WcKernel.cs b/Debuger/WcKernel.cs new file mode 100644 index 0000000..c91041d --- /dev/null +++ b/Debuger/WcKernel.cs @@ -0,0 +1,779 @@ +// WcKernel — WC (Wild Commander) API emulator for the C# debugger +// Traps CALL 0x6006 and emulates function dispatch based on A register +// +// Emulated functions (used by VGM player): +// 0x00 = MNGC_PL — plugin page → Win3 +// 0x01 = PRWOW — draw window (stub) +// 0x02 = RRESB — restore window (stub) +// 0x05 = GADRW — get screen address +// 0x0E = TURBOPL — CPU speed (stub) +// 0x0F = GEDPL — restore display (stub) +// 0x30 = LOAD512 — load sectors from file → memory +// 0x41 = MNGCVPL — video page → Win3 +// 0x4E = MNG0_PL — plugin page → Win0 +// 0x4F = MNG8_PL — plugin page → Win2 +// + keyboard stubs, file stubs, etc. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ZXMAK2.Engine.Cpu; +using ZXMAK2.Engine.Cpu.Processor; + +namespace InflateDebugger +{ + public class WcKernel + { + // ── WC Constants ───────────────────────────────────────────── + + public const ushort WC_ENTRY = 0x6006; + + // Physical page bases + public const byte PLGPG = 0x61; // plugin page base (page 0 = 0x61) + public const byte TVBPG = 0x20; // megabuffer / video page base + public const byte MTXPG = 0x00; // main text page + + // System variable addresses (in Win1 = 0x4000–0x7FFF) + public const ushort SYS_PG0 = 0x6000; + public const ushort SYS_PG4 = 0x6001; + public const ushort SYS_PG8 = 0x6002; + public const ushort SYS_PGC = 0x6003; + public const ushort SYS_ABT = 0x6004; + public const ushort SYS_ENT = 0x6005; + public const ushort SYS_TMN = 0x6009; + public const ushort SYS_CNFV = 0x600D; + public const ushort SYS_HEI = 0x600E; + + // Function numbers (A register values for CALL 0x6006) + public const byte FN_MNGC_PL = 0x00; + public const byte FN_PRWOW = 0x01; + public const byte FN_RRESB = 0x02; + public const byte FN_PRSRW = 0x03; + public const byte FN_TXTPR = 0x04; + public const byte FN_GADRW = 0x05; + public const byte FN_CURSOR = 0x06; + public const byte FN_CURSER = 0x07; + public const byte FN_YN = 0x08; + public const byte FN_ISTR = 0x09; + public const byte FN_NORK = 0x0A; + public const byte FN_MEZZ = 0x0B; + public const byte FN_SCRLWOW = 0x0C; + public const byte FN_PRSRW_A = 0x0D; + public const byte FN_TURBOPL = 0x0E; + public const byte FN_GEDPL = 0x0F; + + // Keyboard: 0x10–0x2F + public const byte FN_KEY_SPACE = 0x10; + public const byte FN_KEY_UP = 0x11; + public const byte FN_KEY_DOWN = 0x12; + public const byte FN_KEY_LEFT = 0x13; + public const byte FN_KEY_RIGHT = 0x14; + public const byte FN_KEY_TAB = 0x15; + public const byte FN_KEY_ENTER = 0x16; + public const byte FN_KEY_ESC = 0x17; + public const byte FN_KEY_BSPC = 0x18; + public const byte FN_KEY_PGUP = 0x19; + public const byte FN_KEY_PGDN = 0x1A; + public const byte FN_KEY_HOME = 0x1B; + public const byte FN_KEY_END = 0x1C; + public const byte FN_KBSCN = 0x1D; + // F1–F10: 0x1E–0x27, ALT=0x28, SHIFT=0x29, CTRL=0x2A + // DEL=0x2B, CAPS=0x2C, ANY=0x2D, WAITREL=0x2E, WAITANY=0x2F + + public const byte FN_LOAD512 = 0x30; + public const byte FN_SAVE512 = 0x31; + public const byte FN_GIPAGPL = 0x32; + public const byte FN_TENTRY = 0x33; + public const byte FN_CHTOSEP = 0x34; + public const byte FN_TMRKDFL = 0x35; + public const byte FN_ADIR = 0x36; + public const byte FN_STREAM = 0x37; + public const byte FN_FINDNEXT = 0x38; + public const byte FN_FENTRY = 0x39; + public const byte FN_LOADNONE = 0x3A; + public const byte FN_GFILE = 0x3B; + public const byte FN_GDIR = 0x3C; + public const byte FN_MKFILE = 0x3D; + public const byte FN_MKDIR = 0x3E; + public const byte FN_RENAME = 0x3F; + public const byte FN_DELFL = 0x40; + + public const byte FN_MNGCVPL = 0x41; + public const byte FN_MNGV_PL = 0x42; + public const byte FN_GVMOD = 0x43; + public const byte FN_GYOFF = 0x44; + public const byte FN_GXOFF = 0x45; + public const byte FN_GVTM = 0x46; + public const byte FN_GVTL = 0x47; + public const byte FN_GVSGP = 0x48; + public const byte FN_MNG0VPL = 0x49; + public const byte FN_MNG8VPL = 0x4A; + public const byte FN_DMAPL = 0x4B; + public const byte FN_PRM_PL = 0x4C; + public const byte FN_INT_PL = 0x4D; + public const byte FN_MNG0_PL = 0x4E; + public const byte FN_MNG8_PL = 0x4F; + + public const byte FN_STRSET = 0x50; + public const byte FN_LOAD256 = 0x51; + public const byte FN_INT_PL_H = 0x52; + public const byte FN_RES_JUMP = 0x53; + public const byte FN_RES_CALL = 0x54; + public const byte FN_KEY_INS = 0x55; + + // ── State ───────────────────────────────────────────────────── + + private readonly TsMemory _mem; + private CpuUnit _cpu; + + // Plugin page offset within the 64-page plugin area (typically 0) + public byte PPGP { get; set; } = 0; + + // Emulated file I/O state + private byte[] _fileData; // loaded file data (whole VGM/VGZ) + private int _filePos; // current file read position (byte offset) + public uint FileSize => _fileData != null ? (uint)_fileData.Length : 0; + + // Disk root directory (test_data path) + public string DiskRootPath { get; set; } + + // WC call log + public List CallLog { get; } = new(); + public bool LogCalls { get; set; } = true; + public int MaxCallLog { get; set; } = 10000; + + // Saved Win0 page (P8000 saves/restores it on each WC call) + private byte _savedWin0Page; + + // Screen height (for gadrw calculations) + public byte ScreenHeight { get; set; } = 36; // 90×36 (TextMode=2) + public byte ScreenWidth { get; set; } = 90; + + // WC system page (mapped to Win1 permanently) + public byte SysVarPage { get; set; } = 0x05; + + // Callbacks for logging + public Action Log { get; set; } + + // ── Constructor ─────────────────────────────────────────────── + + public WcKernel(TsMemory mem, CpuUnit cpu) + { + _mem = mem; + _cpu = cpu; + } + + // ── File I/O emulation ──────────────────────────────────────── + + /// Load a file for emulated file I/O (simulates WC file open + stream) + public void SetFileData(byte[] data) + { + _fileData = data; + _filePos = 0; + } + + /// Load a file from disk root by name + public bool OpenFile(string name) + { + if (string.IsNullOrEmpty(DiskRootPath)) return false; + string path = Path.Combine(DiskRootPath, name); + if (!File.Exists(path)) return false; + _fileData = File.ReadAllBytes(path); + _filePos = 0; + return true; + } + + // ── Setup system variables ──────────────────────────────────── + + public void InitSysVars() + { + var pg = _mem.GetPage(SysVarPage); + + // PG0–PGC (0x6000–0x6003 = offset 0x2000 in Win1) + pg[0x2000] = _mem.GetWindow(0); + pg[0x2001] = _mem.GetWindow(1); + pg[0x2002] = _mem.GetWindow(2); + pg[0x2003] = _mem.GetWindow(3); + + // ABT / ENT = 0 + pg[0x2004] = 0; + pg[0x2005] = 0; + + // JP FUN instruction at 0x6006 (we trap this, but put real bytes for reads) + pg[0x2006] = 0xC3; // JP + pg[0x2007] = 0x00; + pg[0x2008] = 0x00; + + // TMN timer + pg[0x2009] = 0; + pg[0x200A] = 0; + + // CNFv = 7 (VDAC2/FT812 — TSConfig default) + pg[0x200D] = 7; + + // HEI = screen height + pg[0x200E] = ScreenHeight; + } + + // ── Update PGC in sys vars after page switch ────────────────── + + private void UpdateSysPGC() + { + _mem.GetPage(SysVarPage)[0x2003] = _mem.GetWindow(3); + } + private void UpdateSysPG0() + { + _mem.GetPage(SysVarPage)[0x2000] = _mem.GetWindow(0); + } + private void UpdateSysPG8() + { + _mem.GetPage(SysVarPage)[0x2002] = _mem.GetWindow(2); + } + + // ── Main trap handler ───────────────────────────────────────── + + /// + /// Check if CPU is about to execute CALL 0x6006 and intercept it. + /// Call this BEFORE ExecCycle() when PC == address of CALL 0x6006. + /// Returns true if the call was intercepted. + /// + public bool TryIntercept() + { + ushort pc = _cpu.regs.PC; + + // Check: is the instruction at PC a CALL 0x6006? + // CALL nn = CD lo hi → CD 06 60 + if (_mem.Read(pc) == 0xCD && + _mem.Read((ushort)(pc + 1)) == 0x06 && + _mem.Read((ushort)(pc + 2)) == 0x60) + { + byte funcNum = _cpu.regs.A; + ushort retAddr = (ushort)(pc + 3); + + // Log the call + if (LogCalls && CallLog.Count < MaxCallLog) + { + CallLog.Add(new WcCallRecord + { + Tact = _cpu.Tact, + PC = pc, + FuncNum = funcNum, + AF = _cpu.regs.AF, + BC = _cpu.regs.BC, + DE = _cpu.regs.DE, + HL = _cpu.regs.HL, + AF2 = _cpu.regs._AF, + IX = _cpu.regs.IX, + SP = _cpu.regs.SP + }); + } + + // Emulate P8000 dispatch: save Win0, set Win3=TXT + _savedWin0Page = _mem.GetWindow(0); + + // Execute the function + DispatchFunction(funcNum); + + // P8000 epilogue: restore Win0 only. + // Do NOT reset Win3 — page management functions (MNGCPL, MNGCVPL) + // explicitly set Win3 and the caller expects it to persist. + _mem.SetWindow(0, _savedWin0Page); + // Update sys vars to reflect actual window state + UpdateSysPGC(); + UpdateSysPG0(); + + // Set PC past the CALL instruction + _cpu.regs.PC = retAddr; + + // Advance Tact (approximate: a typical WC call takes ~100-500 T-states) + _cpu.Tact += 100; + + return true; + } + + return false; + } + + // ── Function dispatcher ─────────────────────────────────────── + + private void DispatchFunction(byte funcNum) + { + switch (funcNum) + { + // ── Page management ── + case FN_MNGC_PL: Fn_MngcPl(); break; + case FN_MNG0_PL: Fn_Mng0Pl(); break; + case FN_MNG8_PL: Fn_Mng8Pl(); break; + case FN_MNGCVPL: Fn_Mngcvpl(); break; + case FN_MNG0VPL: Fn_Mng0vpl(); break; + case FN_MNG8VPL: Fn_Mng8vpl(); break; + + // ── UI (stubs) ── + case FN_GEDPL: Fn_Gedpl(); break; + case FN_PRWOW: Fn_Prwow(); break; + case FN_RRESB: Fn_Rresb(); break; + case FN_GADRW: Fn_Gadrw(); break; + case FN_PRSRW: Fn_Stub("PRSRW"); break; + case FN_PRSRW_A: Fn_Stub("PRSRW_A");break; + case FN_TXTPR: Fn_Stub("TXTPR"); break; + case FN_SCRLWOW: Fn_Stub("SCRLWOW"); break; + case FN_CURSOR: Fn_Stub("CURSOR"); break; + case FN_CURSER: Fn_Stub("CURSER"); break; + case FN_YN: Fn_Stub("YN"); break; + case FN_ISTR: Fn_Stub("ISTR"); break; + case FN_NORK: Fn_Stub("NORK"); break; + case FN_MEZZ: Fn_Stub("MEZZ"); break; + + // ── Turbo / misc ── + case FN_TURBOPL: Fn_Turbopl(); break; + case FN_PRM_PL: Fn_PrmPl(); break; + case FN_INT_PL: Fn_Stub("INT_PL"); break; + case FN_INT_PL_H: Fn_Stub("INT_PL_H");break; + case FN_STRSET: Fn_Strset(); break; + case FN_DMAPL: Fn_Stub("DMAPL"); break; + + // ── Keyboard (all return 0 = not pressed) ── + case >= FN_KEY_SPACE and <= 0x2F: + Fn_KeyStub(funcNum); + break; + + // ── File I/O ── + case FN_LOAD512: Fn_Load512(); break; + case FN_LOAD256: Fn_Load256(); break; + case FN_SAVE512: Fn_Stub("SAVE512"); break; + case FN_GIPAGPL: Fn_Gipagpl(); break; + case FN_STREAM: Fn_Stream(); break; + case FN_FENTRY: Fn_Fentry(); break; + case FN_GFILE: Fn_Gfile(); break; + case FN_ADIR: Fn_Stub("ADIR"); break; + case FN_FINDNEXT: Fn_Stub("FINDNEXT");break; + case FN_TENTRY: Fn_Stub("TENTRY"); break; + case FN_GDIR: Fn_Stub("GDIR"); break; + case FN_LOADNONE: Fn_Loadnone(); break; + + // ── Graphics (stubs) ── + case FN_MNGV_PL: Fn_Stub("MNGV_PL"); break; + case FN_GVMOD: Fn_Stub("GVMOD"); break; + case FN_GYOFF: Fn_Stub("GYOFF"); break; + case FN_GXOFF: Fn_Stub("GXOFF"); break; + + // ── Resident calls ── + case FN_RES_JUMP: Fn_Stub("RES_JUMP"); break; + case FN_RES_CALL: Fn_Stub("RES_CALL"); break; + + default: + Log?.Invoke($" WC UNKNOWN function 0x{funcNum:X2} at tact={_cpu.Tact}"); + break; + } + } + + // ═══════════════════════════════════════════════════════════════ + // Page management functions + // ═══════════════════════════════════════════════════════════════ + + /// 0x00 MNGC_PL: plugin page A' → Win3. phys = A' + PLGPG + PPGP + private void Fn_MngcPl() + { + byte pageArg = (byte)(_cpu.regs._AF >> 8); // A' register + + if (pageArg == 0xFE) + { + // 0xFE = first text screen + _mem.SetWindow(3, MTXPG); + } + else if (pageArg == 0xFF) + { + // 0xFF = font page + _mem.SetWindow(3, 0x01); + } + else if (pageArg < 64) + { + byte phys = (byte)(pageArg + PLGPG + PPGP); + _mem.SetWindow(3, phys); + } + // else >= 64: NOP (return without changing) + + UpdateSysPGC(); + } + + /// 0x4E MNG0_PL: plugin page A' → Win0. phys = A' + PLGPG + PPGP + private void Fn_Mng0Pl() + { + byte pageArg = (byte)(_cpu.regs._AF >> 8); + if (pageArg < 64) + { + byte phys = (byte)(pageArg + PLGPG + PPGP); + _mem.SetWindow(0, phys); + UpdateSysPG0(); + } + } + + /// 0x4F MNG8_PL: plugin page A' → Win2. phys = A' + PLGPG + PPGP + private void Fn_Mng8Pl() + { + byte pageArg = (byte)(_cpu.regs._AF >> 8); + if (pageArg < 64) + { + byte phys = (byte)(pageArg + PLGPG + PPGP); + _mem.SetWindow(2, phys); + UpdateSysPG8(); + } + } + + /// 0x41 MNGCVPL: video page A' → Win3. phys = A' + TVBPG(0x20) + private void Fn_Mngcvpl() + { + byte vpage = (byte)(_cpu.regs._AF >> 8); + if (vpage < 64) + { + byte phys = (byte)(vpage + TVBPG); + _mem.SetWindow(3, phys); + } + UpdateSysPGC(); + } + + /// 0x49 MNG0VPL: video page A' → Win0 + private void Fn_Mng0vpl() + { + byte vpage = (byte)(_cpu.regs._AF >> 8); + if (vpage < 64) + { + byte phys = (byte)(vpage + TVBPG); + _mem.SetWindow(0, phys); + UpdateSysPG0(); + } + } + + /// 0x4A MNG8VPL: video page A' → Win2 + private void Fn_Mng8vpl() + { + byte vpage = (byte)(_cpu.regs._AF >> 8); + if (vpage < 64) + { + byte phys = (byte)(vpage + TVBPG); + _mem.SetWindow(2, phys); + UpdateSysPG8(); + } + } + + // ═══════════════════════════════════════════════════════════════ + // UI functions (mostly stubs for text-mode UI) + // ═══════════════════════════════════════════════════════════════ + + /// 0x0F GEDPL: restore WC display (stub — sets Win3 to TXT) + private void Fn_Gedpl() + { + // Real WC: restores palette, attributes, text mode + // Also trashes 0x1000–0x1447 in Win0 — we emulate that + _mem.SetWindow(3, MTXPG); + UpdateSysPGC(); + Log?.Invoke(" WC GEDPL (stub)"); + } + + /// 0x01 PRWOW: draw window (stub) + private void Fn_Prwow() + { + // IX = win ptr (from HL in sdcccall) + _mem.SetWindow(3, MTXPG); + UpdateSysPGC(); + } + + /// 0x02 RRESB: restore window background (stub) + private void Fn_Rresb() + { + _mem.SetWindow(3, MTXPG); + UpdateSysPGC(); + } + + /// 0x05 GADRW: get screen address for (y,x) in window + private void Fn_Gadrw() + { + // In wc_api.s: IX=win, D=y, E=x + // Returns DE = screen address + // Text screen at 0xC000, each row = ScreenWidth bytes + // Window content starts at win.x + win.y * ScreenWidth + 0xC000 + // We compute: addr = 0xC000 + (win_y + y) * ScreenWidth + (win_x + x) + + ushort winPtr = _cpu.regs.IX; + byte wy = _mem.Read((ushort)(winPtr + 3)); // win.y at +3 + byte wx = _mem.Read((ushort)(winPtr + 2)); // win.x at +2 + byte y = _cpu.regs.D; + byte x = _cpu.regs.E; + + ushort addr = (ushort)(0xC000 + (wy + y) * ScreenWidth + (wx + x)); + _cpu.regs.DE = addr; + + // Also need to set Win3 to TXT + _mem.SetWindow(3, MTXPG); + UpdateSysPGC(); + } + + /// 0x0E TURBOPL: set CPU/AY speed via SYSCONF port + private void Fn_Turbopl() + { + // B = mode (0=CPU, 1=AY, 0xFF=restore), C = param + byte mode = (byte)(_cpu.regs.BC >> 8); // B + byte param = (byte)(_cpu.regs.BC & 0xFF); // C + + if (mode == 0x00) // WC_TURBO_CPU: set CPU frequency + { + // param: 0=3.5, 1=7, 2=14, 3=28 + // Write turbo bits to SYSCONF port so PluginRunner picks it up + byte sysconf = (byte)(param & 0x03); + _cpu.WRPORT(0x20AF, sysconf); + } + // mode 1 (AY freq) — not relevant for emulation + // mode 0xFF (restore) — restore to 3.5 MHz + else if (mode == 0xFF) + { + _cpu.WRPORT(0x20AF, 0x00); // restore to 3.5 MHz + } + } + + /// 0x4C PRM_PL: get INI parameter (stub — return 0xFF = not set) + private void Fn_PrmPl() + { + _cpu.regs.A = 0xFF; + } + + /// 0x50 STRSET: memset(ptr, len, char) + private void Fn_Strset() + { + // In wc_api.s: HL=dst, DE=len, A'=char + // We actually don't need real WC for this, but the plugin calls it + ushort dst = _cpu.regs.HL; + ushort len = _cpu.regs.DE; + byte ch = (byte)(_cpu.regs._AF >> 8); + + for (int i = 0; i < len; i++) + _mem.Write((ushort)(dst + i), ch); + } + + // ═══════════════════════════════════════════════════════════════ + // Keyboard stubs (all return A=0 = not pressed) + // ═══════════════════════════════════════════════════════════════ + + private void Fn_KeyStub(byte funcNum) + { + _cpu.regs.A = 0; // not pressed + // Clear Z flag by default (A=0 → Z=1 → key not pressed) + } + + // ═══════════════════════════════════════════════════════════════ + // File I/O emulation + // ═══════════════════════════════════════════════════════════════ + + /// 0x30 LOAD512: load blocks×512 bytes from file → memory at dest + private void Fn_Load512() + { + // wc_api.s _wc_load512: HL=dest, B=blocks (from stack via frame pointer) + // WC API #0x30: HL=dest, B=blocks → A=0 ok / 0x0F EOF + // Returns: A = status, HL = next address after loaded data + + ushort loadDest = _cpu.regs.HL; + byte blocks = _cpu.regs.B; + int bytesToLoad = blocks * 512; + + if (_fileData == null) + { + Log?.Invoke(" WC LOAD512: no file data!"); + _cpu.regs.A = 0xFF; // error + return; + } + + int remaining = _fileData.Length - _filePos; + int actual = Math.Min(bytesToLoad, remaining); + bool isEof = (actual < bytesToLoad) || (_filePos + actual >= _fileData.Length); + + // Write data into memory at loadDest (using Win3 map) + for (int i = 0; i < actual; i++) + { + _mem.Write((ushort)(loadDest + i), _fileData[_filePos + i]); + } + _filePos += actual; + + // Update HL to point past the loaded data + _cpu.regs.HL = (ushort)(loadDest + actual); + + _cpu.regs.A = isEof ? (byte)0x0F : (byte)0; // 0x0F = WC_EOF + + Log?.Invoke($" LOAD512: dest=0x{loadDest:X4} blocks={blocks} bytes={actual} eof={isEof} filePos={_filePos}/{_fileData.Length}"); + } + + /// 0x51 LOAD256: load blocks×256 bytes + private void Fn_Load256() + { + // Same pattern as LOAD512 but 256-byte blocks: HL=dest, B=blocks + ushort loadDest = _cpu.regs.HL; + byte blocks = _cpu.regs.B; + int bytesToLoad = blocks * 256; + + if (_fileData == null) { _cpu.regs.A = 0xFF; return; } + + int remaining = _fileData.Length - _filePos; + int actual = Math.Min(bytesToLoad, remaining); + bool isEof = actual < bytesToLoad || _filePos + actual >= _fileData.Length; + + for (int i = 0; i < actual; i++) + _mem.Write((ushort)(loadDest + i), _fileData[_filePos + i]); + _filePos += actual; + + _cpu.regs.HL = (ushort)(loadDest + actual); + _cpu.regs.A = isEof ? (byte)0x0F : (byte)0; + + Log?.Invoke($" LOAD256: dest=0x{loadDest:X4} blocks={blocks} bytes={actual} eof={isEof}"); + } + + /// 0x32 GIPAGPL: seek file to beginning + private void Fn_Gipagpl() + { + _filePos = 0; + } + + /// 0x37 STREAM: open/switch file stream (stub — just log) + private void Fn_Stream() + { + // D = mode (0xFF=root, 0xFE=clone, 0xFD=wcdir, 0/1=stream#) + Log?.Invoke($" WC STREAM mode=0x{_cpu.regs.D:X2}"); + } + + /// 0x39 FENTRY: find file by name + private void Fn_Fentry() + { + // HL = addr of [flag_byte][name_string][0] + ushort namePtr = _cpu.regs.HL; + byte flag = _mem.Read(namePtr); + namePtr++; + + // Read filename + var sb = new StringBuilder(); + for (int i = 0; i < 255; i++) + { + byte ch = _mem.Read((ushort)(namePtr + i)); + if (ch == 0) break; + sb.Append((char)ch); + } + string fileName = sb.ToString(); + Log?.Invoke($" WC FENTRY: \"{fileName}\" flag=0x{flag:X2}"); + + // Try to find the file on disk root + if (!string.IsNullOrEmpty(DiskRootPath)) + { + string path = Path.Combine(DiskRootPath, fileName); + if (File.Exists(path)) + { + _cpu.regs.A = 1; // found (non-zero) + // Set file size in DE:HL + long fsize = new FileInfo(path).Length; + _cpu.regs.HL = (ushort)(fsize & 0xFFFF); + _cpu.regs.DE = (ushort)((fsize >> 16) & 0xFFFF); + + // Store path for subsequent GFILE + LOAD512 + _pendingFileName = path; + return; + } + } + + _cpu.regs.A = 0; // not found + } + + private string _pendingFileName; + + /// 0x3B GFILE: open found file for reading + private void Fn_Gfile() + { + if (!string.IsNullOrEmpty(_pendingFileName) && File.Exists(_pendingFileName)) + { + _fileData = File.ReadAllBytes(_pendingFileName); + _filePos = 0; + Log?.Invoke($" WC GFILE: opened \"{_pendingFileName}\" ({_fileData.Length} bytes)"); + } + else + { + Log?.Invoke(" WC GFILE: no pending file"); + } + } + + /// 0x3A LOADNONE: skip N sectors in stream + private void Fn_Loadnone() + { + byte sectors = (byte)(_cpu.regs._AF >> 8); + int skip = sectors * 512; + if (_fileData != null) + _filePos = Math.Min(_filePos + skip, _fileData.Length); + } + + // ═══════════════════════════════════════════════════════════════ + // Stub for unimplemented functions + // ═══════════════════════════════════════════════════════════════ + + private void Fn_Stub(string name) + { + // Most stubs just need Win3 = TXT (already done in P8000 epilogue) + } + + // ═══════════════════════════════════════════════════════════════ + // Call log + // ═══════════════════════════════════════════════════════════════ + + public string DumpCallLog(int last = 50) + { + var sb = new StringBuilder(); + sb.AppendLine($"=== WC Call Log (last {last} of {CallLog.Count}) ==="); + int start = Math.Max(0, CallLog.Count - last); + for (int i = start; i < CallLog.Count; i++) + { + var c = CallLog[i]; + sb.AppendFormat("[{0,10}] PC={1:X4} FN=0x{2:X2} ({3}) AF={4:X4} BC={5:X4} DE={6:X4} HL={7:X4} IX={8:X4}", + c.Tact, c.PC, c.FuncNum, GetFuncName(c.FuncNum), + c.AF, c.BC, c.DE, c.HL, c.IX); + sb.AppendLine(); + } + return sb.ToString(); + } + + public static string GetFuncName(byte fn) => fn switch + { + FN_MNGC_PL => "MNGC_PL", + FN_PRWOW => "PRWOW", + FN_RRESB => "RRESB", + FN_PRSRW => "PRSRW", + FN_TXTPR => "TXTPR", + FN_GADRW => "GADRW", + FN_TURBOPL => "TURBOPL", + FN_GEDPL => "GEDPL", + FN_LOAD512 => "LOAD512", + FN_LOAD256 => "LOAD256", + FN_GIPAGPL => "GIPAGPL", + FN_STREAM => "STREAM", + FN_FENTRY => "FENTRY", + FN_GFILE => "GFILE", + FN_MNGCVPL => "MNGCVPL", + FN_MNG0_PL => "MNG0_PL", + FN_MNG8_PL => "MNG8_PL", + FN_MNG0VPL => "MNG0VPL", + FN_MNG8VPL => "MNG8VPL", + FN_STRSET => "STRSET", + FN_PRM_PL => "PRM_PL", + FN_ADIR => "ADIR", + FN_LOADNONE=> "LOADNONE", + FN_SAVE512 => "SAVE512", + FN_DMAPL => "DMAPL", + >= FN_KEY_SPACE and <= 0x2F => $"KEY_{fn - FN_KEY_SPACE:X2}", + _ => $"?{fn:X2}" + }; + } + + // ── WC call record ──────────────────────────────────────────────── + + public struct WcCallRecord + { + public long Tact; + public ushort PC; + public byte FuncNum; + public ushort AF, BC, DE, HL; + public ushort AF2; + public ushort IX, SP; + } +} diff --git a/Debuger/plugin_out.txt b/Debuger/plugin_out.txt new file mode 100644 index 0000000..0eb4e7c Binary files /dev/null and b/Debuger/plugin_out.txt differ diff --git a/Debuger/plugin_stage.txt b/Debuger/plugin_stage.txt new file mode 100644 index 0000000..f2c07a6 Binary files /dev/null and b/Debuger/plugin_stage.txt differ diff --git a/Debuger/plugin_stage_diag.txt b/Debuger/plugin_stage_diag.txt new file mode 100644 index 0000000..3357a76 Binary files /dev/null and b/Debuger/plugin_stage_diag.txt differ diff --git a/Debuger/plugin_work_diag.txt b/Debuger/plugin_work_diag.txt new file mode 100644 index 0000000..0f4d802 Binary files /dev/null and b/Debuger/plugin_work_diag.txt differ diff --git a/DiskRef/Help.txt b/DiskRef/Help.txt new file mode 100644 index 0000000..f070714 --- /dev/null +++ b/DiskRef/Help.txt @@ -0,0 +1,36 @@ +Wild Commander v1.00 + +“¯ࠢ«¥­¨¥: + +‚ । ªâ®à¥ áâப¨: +CTRL+SHIFT - ᬥ­  à ᪫ ¤ª¨ ENG/RUS +INSERT - ¯¥४«î祭¨¥ ०¨¬  INS/OVR +CAPS LOCK - ¢ª«î祭¨¥/¢몫î祭¨¥ ०¨¬  CAPS + +‚ ¯ ­¥«ïå: +ALT+F1/F2 - ¢롮à ¤ࠩ¢¥à  ¢ ᮮâ¢. ¯ ­¥«¨ +TAB - ᬥ­  ¯ ­¥«¨ +SPACE - ¬ ન஢ âì 䠩«ë/ª ⠫®£¨ (¤㡫¨àã¥âáï ª« ¢¨襩 INSERT) +F2 - ®âªàëâì ¬¥­î ¯®«짮¢ ⥫ï (¥᫨ ¡ë« § £à㦥­ ¯« £¨­). § ¤ ¥âáï ¢ wc.mnu (¢ ¯ ¯ª¥ á ¯« £¨­ ¬¨) +F4 - । ªâ¨஢ âì 䠩« ¯®¤ ªãàá®஬ ¢ ⥪á⮢®¬ । ªâ®à¥ (¥᫨ ¡ë« § £à㦥­ ¯« £¨­) +F5 - ª®¯¨஢ âì ®⬥祭­ë¥ 䠩«ë/ª ⠫®£¨ ¢ ¯à®⨢®¯®«®¦­ãî ¯ ­¥«ì (¢뢮¤¨â ¤¨ «®£®¢®¥ ®ª­®) +F6 - ¯¥२¬¥­®¢ âì 䠩«/ª ⠫®£ ¯®¤ ªãàá®஬ (¢६ï ᮧ¤ ­¨ï ­¥ ¬¥­ï¥â) +F7 - ᮧ¤ âì ª ⠫®£ ¢  ª⨢­®© ¯ ­¥«¨ +F8 - 㤠«¨âì ®⬥祭­ë¥ 䠩«ë/ª ⠫®£¨ (¤㡫¨àã¥âáï ª« ¢¨襩 DEL) +F9 - ­ áâனª  INI 䠩«  ( ¢⮬ â¨ç¥᪨ ᮧ¤ ¥â ¯ ¯ªã WC ­  boot-device, ¥᫨ ⠪®¢®© ­¥âã) +F10 - ¢맮¢ ¬¥­î § ¯ã᪠/㤠«¥­¨ï ¯« £¨­®¢. ¯® ENTER ⠪®¢ë¥ § ¯ã᪠îâáï (¥᫨ ¯®¤¤¥ন¢ îâ § ¯ã᪠¨§ ¬¥­î), ¯® DEL 㤠«ïîâáï ¨§ ¯ ¬ïâ¨. + +ctrl+F3 - ¢ª«î祭¨¥/¢몫î祭¨¥ á®àâ¨஢ª¨ ¯® ¨¬¥­¨ +ctrl+F4 - ¢ª«î祭¨¥/¢몫î祭¨¥ á®àâ¨஢ª¨ ¯® à áè¨७¨î +ctrl+F6 - ¢ª«î祭¨¥/¢몫î祭¨¥ á®àâ¨஢ª¨ ¯® ࠧ¬¥àã +ctrl+R - ¯¥à¥ç¨â âì ᮤ¥ন¬®¥ ª ⠫®£  ¨§ ¯ ­¥«¨ +ctrl+A - ¢뤥«¨âì ¢á¥ í«¥¬¥­âë ¢ ª ⠫®£¥ + +* - ¨­¢¥àâ¨஢ âì ¬ ન஢ªã ¢ ª ⠫®£¥ + +ENTER - ¢®©â¨ ¢ ª ⠫®£/§ £à㧨âì WMF ¯« £¨­/§ ¯ãáâ¨âì SPG/§ ¯ãáâ¨âì $C/¯à®ᬮâà¥âì 䠩« ¯®¤ ªãàá®஬ +SHIFT+ENTER - ¢뢮¤ ¬¥­î ¢롮à  ¢ìà  (ä®ନàã¥âáï ¨§ ¢áâ஥­­ëå ¢ì஢ ¨ § £à㦥­­ëå ¯« £¨­®¢, ¯®¤¤¥ন¢ îé¨å ¤ ­­®¥ ¬¥­î) +F3 - ®âªàëâì 䠩« ­  ¯à®ᬮâà (ª ª ⥪áâ/HEX/ª à⨭ªã (TGA/SCR/Gigascreen/MGS). § £à㦠¥â 䠩«ë ¤® 1¬¡. …᫨ à áè¨७¨¥ ­¥ ®¯®§­ ­®, â® ¡㤥â ¯।«®¦¥­® ¢ë¡à âì ¤¥©á⢨¥ + + +P.S. ¢® ¢६ï ®¯¥à 権 㤠«¥­¨ï/ª®¯¨஢ ­¨ï 䠩«®¢/ª ⠫®£®¢ ¬®¦­® ¤®áà®筮 ¯à¥ࢠâì ⠪®¢ë¥ ­ ¦ ⨥¬ ­  ESC (¬¥­î ¯®âáï ­  ᬥ­¥ 䠩« )! diff --git a/DiskRef/WC/BMPV.WMF b/DiskRef/WC/BMPV.WMF new file mode 100644 index 0000000..65418c5 Binary files /dev/null and b/DiskRef/WC/BMPV.WMF differ diff --git a/DiskRef/WC/CLOCK.WMF b/DiskRef/WC/CLOCK.WMF new file mode 100644 index 0000000..f638354 Binary files /dev/null and b/DiskRef/WC/CLOCK.WMF differ diff --git a/DiskRef/WC/COMPILER.WMF b/DiskRef/WC/COMPILER.WMF new file mode 100644 index 0000000..c1142bb Binary files /dev/null and b/DiskRef/WC/COMPILER.WMF differ diff --git a/DiskRef/WC/FILE_CR.WMF b/DiskRef/WC/FILE_CR.WMF new file mode 100644 index 0000000..0353f77 Binary files /dev/null and b/DiskRef/WC/FILE_CR.WMF differ diff --git a/DiskRef/WC/FTVIEW.WMF b/DiskRef/WC/FTVIEW.WMF new file mode 100644 index 0000000..094ad7c Binary files /dev/null and b/DiskRef/WC/FTVIEW.WMF differ diff --git a/DiskRef/WC/GIFVIEW.WMF b/DiskRef/WC/GIFVIEW.WMF new file mode 100644 index 0000000..9fcd82c Binary files /dev/null and b/DiskRef/WC/GIFVIEW.WMF differ diff --git a/DiskRef/WC/GSPLAYER.WMF b/DiskRef/WC/GSPLAYER.WMF new file mode 100644 index 0000000..1ed3b8e Binary files /dev/null and b/DiskRef/WC/GSPLAYER.WMF differ diff --git a/DiskRef/WC/ILBMVIEW.WMF b/DiskRef/WC/ILBMVIEW.WMF new file mode 100644 index 0000000..c6f96ec Binary files /dev/null and b/DiskRef/WC/ILBMVIEW.WMF differ diff --git a/DiskRef/WC/MCGSV.WMF b/DiskRef/WC/MCGSV.WMF new file mode 100644 index 0000000..54dbcb0 Binary files /dev/null and b/DiskRef/WC/MCGSV.WMF differ diff --git a/DiskRef/WC/MENU/Hrust v1.3.spg b/DiskRef/WC/MENU/Hrust v1.3.spg new file mode 100644 index 0000000..9bbe621 Binary files /dev/null and b/DiskRef/WC/MENU/Hrust v1.3.spg differ diff --git a/DiskRef/WC/MENU/RUD v2.42.spg b/DiskRef/WC/MENU/RUD v2.42.spg new file mode 100644 index 0000000..fb8778e Binary files /dev/null and b/DiskRef/WC/MENU/RUD v2.42.spg differ diff --git a/DiskRef/WC/MENU/WDC v1.60.$C b/DiskRef/WC/MENU/WDC v1.60.$C new file mode 100644 index 0000000..7ddbeb2 Binary files /dev/null and b/DiskRef/WC/MENU/WDC v1.60.$C differ diff --git a/DiskRef/WC/MOUNTER.WMF b/DiskRef/WC/MOUNTER.WMF new file mode 100644 index 0000000..24c847d Binary files /dev/null and b/DiskRef/WC/MOUNTER.WMF differ diff --git a/DiskRef/WC/PLASMATW.WMF b/DiskRef/WC/PLASMATW.WMF new file mode 100644 index 0000000..b618b31 Binary files /dev/null and b/DiskRef/WC/PLASMATW.WMF differ diff --git a/DiskRef/WC/RE.WMF b/DiskRef/WC/RE.WMF new file mode 100644 index 0000000..3479175 Binary files /dev/null and b/DiskRef/WC/RE.WMF differ diff --git a/DiskRef/WC/ROM_PROG.WMF b/DiskRef/WC/ROM_PROG.WMF new file mode 100644 index 0000000..6639636 Binary files /dev/null and b/DiskRef/WC/ROM_PROG.WMF differ diff --git a/DiskRef/WC/SCRSAVER.WMF b/DiskRef/WC/SCRSAVER.WMF new file mode 100644 index 0000000..8d0432e Binary files /dev/null and b/DiskRef/WC/SCRSAVER.WMF differ diff --git a/DiskRef/WC/SETIME.WMF b/DiskRef/WC/SETIME.WMF new file mode 100644 index 0000000..1d01735 Binary files /dev/null and b/DiskRef/WC/SETIME.WMF differ diff --git a/DiskRef/WC/TAPM.WMF b/DiskRef/WC/TAPM.WMF new file mode 100644 index 0000000..7bb33f9 Binary files /dev/null and b/DiskRef/WC/TAPM.WMF differ diff --git a/DiskRef/WC/TRDUMP.WMF b/DiskRef/WC/TRDUMP.WMF new file mode 100644 index 0000000..42c46ac Binary files /dev/null and b/DiskRef/WC/TRDUMP.WMF differ diff --git a/DiskRef/WC/TRDisp.wmf b/DiskRef/WC/TRDisp.wmf new file mode 100644 index 0000000..c3809e7 Binary files /dev/null and b/DiskRef/WC/TRDisp.wmf differ diff --git a/DiskRef/WC/TXTEDIT.WMF b/DiskRef/WC/TXTEDIT.WMF new file mode 100644 index 0000000..1081d1e Binary files /dev/null and b/DiskRef/WC/TXTEDIT.WMF differ diff --git a/DiskRef/WC/VGMPLAY.WMF b/DiskRef/WC/VGMPLAY.WMF new file mode 100644 index 0000000..8531f8a Binary files /dev/null and b/DiskRef/WC/VGMPLAY.WMF differ diff --git a/DiskRef/WC/VIDEO_PL.WMF b/DiskRef/WC/VIDEO_PL.WMF new file mode 100644 index 0000000..f283bf9 Binary files /dev/null and b/DiskRef/WC/VIDEO_PL.WMF differ diff --git a/DiskRef/WC/WPLAYER.WMF b/DiskRef/WC/WPLAYER.WMF new file mode 100644 index 0000000..0ebfdd6 Binary files /dev/null and b/DiskRef/WC/WPLAYER.WMF differ diff --git a/DiskRef/WC/wc.ini b/DiskRef/WC/wc.ini new file mode 100644 index 0000000..d836b9e --- /dev/null +++ b/DiskRef/WC/wc.ini @@ -0,0 +1 @@ +Wild Commander v1.1 [SETUP] CPU_FREQ=2; 0=3.5MHz, 1=7MHz, 2=14MHz, 3=reserved SavePaths=1; 0-disabled, 1-enabled (remember dirs in panels) SavePosition=1; 0-disabled, 1-enabled (restores cursor position, if enabled) TextMode=2; 0=80x25, 1=80x30, 2=90x36 text mode Logo=1; 0-disabled, 1-enabled (disables logo header and clock) ScreenSaver=9; 0-disabled, 1-9 minutes [PLUGINS] MOUNTER.WMF -memory=128 -cpu=3,5 -cache=off -rs232=115200 SETIME.WMF; memory:128/512/1024/auto cpu:3,5/7/14 cache:on/off rs232:115200/230400 RE.WMF VGMPLAY.WMF TXTEDIT.WMF -highlight=auto; auto,on,off FILE_CR.WMF TRDisp.wmf TRDUMP.WMF ROM_PROG.WMF WPLAYER.WMF GSPLAYER.WMF -midi_chip=2; 1 - first (#FF), 2 - second (#FE) VIDEO_PL.WMF BMPV.WMF ILBMVIEW.WMF FTVIEW.WMF GIFVIEW.WMF MCGSV.WMF TAPM.WMF -loading=real -cpu=3,5 -memory=128 -basic=48 COMPILER.WMF; loading:real/slow/fast cpu:3,5/7/14 memory:128/512/1024/auto basic:48/128 CLOCK.WMF;PLASMATW.WMF;SCRSAVER.WMF [LPANEL] DRV=1; 0-IDEnemoMaster, 1-SD1zc, 2-SDngs, 3-IDEsmucMaster, 4-IDEsmucSlave, 5-IDEnemoSlave, 6-SD2zc INFO=0; file info (0 - disabled, 1 - enabled) CSORT=2; sorting (0 - off, 1 - by name, 2 - by ext, 3 - by size) [RPANEL] DRV=1; 0-IDEnemoMaster, 1-SD1zc, 2-SDngs, 3-IDEsmucMaster, 4-IDEsmucSlave, 5-IDEnemoSlave, 6-SD2zc INFO=0; file info (0 - disabled, 1 - enabled) CSORT=2; sorting (0 - off, 1 - by name, 2 - by ext, 3 - by size) ;this file and all plug-ins must be placed into WC folder (in root dir of Boot-Device) ;¤ ­­ë© 䠩« ¨ ¢á¥ ¯« £¨­ë ­㦭® ࠧ¬¥áâ¨âì ¢ ¯ ¯ª¥ WC ;(¢ ª®୥ § £à㧮筮£® ãáâனá⢠, ª®â®஥ ¢롨ࠥâáï ¢ ts-bios'e [SS+F12]) \ No newline at end of file diff --git a/DiskRef/WC/wc.mnu b/DiskRef/WC/wc.mnu new file mode 100644 index 0000000..198494b --- /dev/null +++ b/DiskRef/WC/wc.mnu @@ -0,0 +1 @@ +W: WDC v1.60 menu\wdc v1.60.$C H: Hrust 1.3 (TR-DOS Only!) menu\Hrust v1.3.spg R: RUD 2.42 (TR-DOS Only!) menu\RUD v2.42.spg \ No newline at end of file diff --git a/DiskRef/WC_History.txt b/DiskRef/WC_History.txt new file mode 100644 index 0000000..d009b8b --- /dev/null +++ b/DiskRef/WC_History.txt @@ -0,0 +1,751 @@ +WildCommander History. + +v1.10 (03.08.2025): +.правлен баг: [файловое ядро] генератор индексов не работал на каталогах и файлах с расширением меньше 3х символов +.обновлен плагин: GSPLAYER .добавлено проигрывание цифровых модулей Digital Music Maker (.DMM) +.обновлен плагин: ROM_PROG - добавлена возможность полной прошивки 512кб, оптимизация кода и обновление интерфейса +.обновлен плагин: File Creator - изменен цвет курсора в юзер меню (теперь не слипается с заголовком окна) + +v1.09 (04.08.2024): +.обновлен плагин: TAPM .перемещена область выделенная под загрузку TAP файла (совпадала с буфером RAM диска под SCL образ) +.обновлен плагин: MOUNTER .добавлена опция выбора скорости rs232 в INI (115200/230400) + +v1.09 (03.08.2024): +.обновлен плагин: GSPLAYER .доработан анализатор MOD файлов +.обновлен плагин: TAPM .добавлен метод загрузки REAL (со звуком) + + +v1.09 (31.07.2024): +.в плагинную систему добавлен резидент вызова и резидент перехода (для вызова и перехода кода между паг в плагине, в одном окне) +.добавлена возможность передавать параметры запуска в плагины из раздела загрузки таковых (в INI файле) +.изменена версия плагинной системы, изменена версия INI файла +.обновлен плагин: TAPM .по умолчанию сделана опция загрузки FAST + .добавлена поддержка опций из INI файла +.обновлен плагин: GSPLAYER .увеличен тайм аут ожидания готовности GS при детекте + .добавлена очистка поля имени трека + .добавлена поддержка опций из INI файла +.обновлен плагин: TXT Editor .добавлена поддержка опций из INI файла +.обновлен плагин: MOUNTER .добавлена поддержка опций из INI файла + +v1.07 (19.04.2024): +.обновлен плагин: GSPLAYER .в midi плеере сделана нормальная синхронизация таймера, с точностью до 8 долей фрейма + .в midi плеере сделан игнор задержки перед кодом конца трека (чтобы срабатывал автопереход на следующий модуль) + .в midi плеере исправлен баг с именем трека нулевой длины + .в midi плеере исправлен баг с именем трека шире поля имени в окне + .в midi плеере исправлен баг в таймере тиков (при добавлении остатка не учитывалось переполнение младших 16 бит) + +v1.07 (17.04.2024): +.обновлен плагин: GSPLAYER .в midi плеере подправлен коэффициент скорости проигрывания + .доработан однобайтовый прогресс бар + +v1.07 (16.04.2024): +.обновлен плагин: GSPLAYER .устранены проблемы с синхронизацией в midi плеере (в ущерб визуализации) + .в midi плеере добавлена возможность выбора чипа по F1 (1го либо 2го) + .в midi плеере добавлен прогресс бар + +v1.07 (15.04.2024): +.правлен баг: неправильно подставлялся режим экрана в меню сетапа (F9), из-за кривых констант +.обновлен плагин: WPLAYER .добавлена ассоциация для .ETC файлов (SAA модули) + .убрано ограничение на 16кб для TFC файлов +.обновлен плагин: GSPLAYER .добавлен тайм-аут с окошком, если при первом использовании плагина гс долго отвечает + .ускорена загрузка MOD файлов в память GS (примерно на 27%) + .добавлено проигрывание MIDI файлов (.MID) через multisound (ay-rs232/midi) + +v1.06 (07.04.2024): +.обновлен плагин: WPLAYER .правлен баг: исправлено проигрывание TFD файлов, был кривой парсинг буфера 48кб, который идет до загрузки данных с FAT + .добавлено проигрывание PSM (Pro Sound Maker) модулей + +v1.06 (05.04.2024): +.добавлена возможность инициализации yes/no (вместо ok/cancel) в функции YN(8) +.немного доработан file info +.правлен баг: при включении на прерывания обработчика от плагина (INT_PL (86)), функции печати окна (PRWOW) и стирания окна (RRESB) переключали окно #8000-#BFFF не отключая вызов в плагин из прерываний (который должен быть в #8000-#BFFF!) +.изменена версия плагинной системы +.обновлен плагин: GSPLAYER .вернул таймер на прерывания + .изменения интерфейса +.обновлен плагин: TXT Editor +.обновлен плагин: WPLAYER (правлен баг: для инитов плееров (которые таковые делают) не включалась пага с музоном после выгребания модуля из буфера) + +v1.05 (30.03.2024): +.правлен баг: при открытии каталогов больше 4095 записей не ставился нулевой индекс на 4096 записи (проверялось только превышение буфера данных = 9 паг) +.правлен баг: снято ограничение на 80 символов ширины экрана в функции PRIAT +.обновлен плагин: TXT&BIN Viewer .в режиме BIN добавлен поиск (ctrl+F), пока что вводить можно только текст + .в режиме BIN теперь не отображаются данные за пределами длины файла (кратно 16 байт) + .правлен баг: в режиме просмотра текста не отображался текст +.обновлен плагин: TXT Editor .ускорен режим печати с подсветкой синтаксиса + .добавлено отображение маркировки в режиме печати с подсветкой синтаксиса + .в редакторах строки добавлено обновление отображения статусов ins/ovr, caps, eng/rus + .добавлен вывод ошибки, если выделенная область не помещается в буфер обмена + .добавлено отображение оффсета курсора и области маркировки + .теперь при открытии .ASM файлов подсветка включается автоматически (F2 вкл/выкл) + .правлен баг: маркировка останавливалась на стыках страниц (не всегда) + .правлен баг: копирование не проверяло превышение буфера обмена (16кб) + +v1.04 (25.03.2024): +.ускорена сортировка по расширению (ещё на ~27%) +.обновлен плагин: TXT&BIN Viewer (в режиме BIN: добавлен вывод оффсета, добавлены клавиши PGUP, PGDN) +.обновлен плагин: TAPM (добавлен запуск z80 снапшотов, версий 1,2,3) + +v1.03 (23.03.2024): +.обновлен плагин: WPLAYER .увеличен макс размер для PT3/VTX модулей до 24кб, в том числе в режиме TS (общий буфер на оба модуля 40кб) + .изменения в интерфейсе + .добавлена возможность запуска модулей размером меньше 256 байт +.обновлен плагин: GSPLAYER .добавлена пауза (M key) + .добавлен анализатор в режиме MOD плеера + +v1.03 (18.03.2024): +.в F9 меню теперь подтягивает текущее состояние опции Logo enabled/disabled +.ускорена сортировка по расширению (примерно на 20%), а так же убрана зависимость от регистра в расширениях при сортировке +.добавлено ожидание отпускания клавиш (USPO), перед выводом окна подтверждения действия, при отработке функции Delete(F8) +.обновлен плагин: WPLAYER (добавлено потоковое проигрывание TFD файлов; починено восстановление фонта, по выходу из плагина, в режиме плей листа) +.обновлен плагин: GSPLAYER (поддержка нового интерфейса, добавлен таймер при проигрывании mp3 и wav) +.обновлен плагин: video_pl (поддержка нового интерфейса) + +v1.02 (10.03.2024): +.добавлена возможность отключения шапки с часами в пользу панелей (вынесена отдельная опция в INI файл и редактор по F9) +.сделана автоматическая центровка всех окон в соответствии c активным режимом экрана (80x25,80x30,90x36) +.мелкие изменения в интерфейсе +.в API изменен формат типа окна, а так же добавлена автоцентровка окон (см. доку) +.изменена версия плагинной системы +.обновлен плагин: MOUNTER (косметические изменения) +.обновлен плагин: WPLAYER (добавлена пауза проигрывания музыки по M, косметические изменения) +.обновлен плагин: ASM Compiler (косметические изменения) +.обновлен плагин: TXT Editor (косметические изменения) +.обновлен плагин: TAPM (косметические изменения) +.обновлен плагин: TRDUMP (TRDump v0.83 by dr.Max) +.обновлен плагин: TRDISP (TRDispatcher v0.43b by dr.Max) + +v1.01 final (30.10.2019): +.правлен баг: удалялся только первый отмаркированный каталог +.правлен баг: копирование дира не копировало файлы с именами больше 64 символов (гробила проца вывода имени) +.обновлен плагин: TXT&BIN Viewer - добавлена поддержка вызова по F10 (просмотр текущего каталога в BIN) + +v1.00 final (20.10.2019): +.добавлен сброс буфера PS/2 при запуске коммандера +.добавлен вывод ошибок в функции удаления каталога +.добавлен вывод имени копируемого файла в функции копирования каталога (в окне с прогресс баром) +.добавлена полная поддержка txt режима 90x36, в том числе в плагинную систему (панели теперь рисуются в полную ширину экрана) +.изменен заголовок и структура wc.ini (!!!). имена файлов с плагинами теперь можно указывать в формате длинного имени +.настройка времени включения скрин сейвера вынесена в INI файл +.разделены вызовы F3 и ENTER (соотв. флаг в заголовке плагина) +.выкинут встроенный просмотр TXT и BIN файлов +.обновлен плагин: TXT Editor - добавлена полная поддержка экрана 90x36 (не только по высоте, но и в ширину) +.обновлен плагин: ASM Compiler - поддержка вызова только по ENTER +.добавлен плагин: TXT&BIN Viewer + +v1.00 rc9 (10.10.2019): +.выкинут встроенный TGA вьювер +.добавлены отработки на нажатия ctrl+A и * (маркировать все и инвертирование маркировки) +.заблокировано выделение ".." при отсутствии маркировки и нажатии F5/F8 +.добавлена поддержка 2й SD карты +.добавлен вывод ошибок для рекурсивного копирования каталога +.исправлен выход по ошибке из копирования при встрече каталогов с включенными атрибутами +.обновлен плагин: MOUNTER - добавлена поддержка 2й SD карты, добавлен инит карты в случае если оная возвращает idle state +.обновлен плагин: BMPV - добавлен просмотр TGA графики + +v1.00 rc7 (05.10.2019): +.добавлено сохранение капслока в коротких именах каталогов и файлов +.правлен баг: поиск длинного имени в каталоге выдавал ложное совпадение в редкой ситуации +.обновлен плагин: MOUNTER - правлен баг в маунте SCL на RAM-диск (парсер каталога не переваривал файлы 0й длины) + +v1.00 rc6 (03.10.2019): +.добавлен скролл бар в панели +.добавлено копирование/удаление каталогов со всеми вложениями (пока-что без интерфейса) + +v1.00 rc4 (13.09.2019): +.добавлен инит бита вида данных в CMOS-часах +.правлен баг: для плагинов типов #13 и #15 не выводился текст напротив подсказки F4 +.в API добавлена поддержка плагинов #11, #12 (вызов по F2 из коммандера) +.в API для функции STREAM добавлен новый функционал (параметр #FD: включение спец потока и позиционирование на папку WC) +.в авто-загрузку плагинов добавлен вывод поля F2 (при встрече соотв. плагина) +.обновлен плагин: File Creator (FILE_CR) - добавлено F2 меню (читает wc.mnu из папки WC, запускает SPG и $C файлы) +.обновлен плагин: TXT Editor - исправлен баг: при сохранении пустого файла, сохранялся файл длиной 0 (генерилась цепочка 4Гб) + +v1.00 rc3 (08.09.2019): +.обновлен плагин: TXT Editor .добавлена отработка ESC при редактировании имени файла в Save As... (возврат в редактор) + .исправлен баг: при сохранении, в случае когда курсор находился в самом конце, длина файла получалась на 1 байт больше реальной +.добавлен плагин: GIFVIEW by Grey (исходники конвертнул для компиляции из самого коммандера, плюс правлено управление и был включен кеш) + +v1.00 rc3 (24.08.2019): +.обновлен депакер MegaLZ, thx to spke (распаковка SPG при запуске, увеличение скорости распаковки в 2 раза) +.добавлено обнуление SFile и смещений тайловых-плоскостей, в процедуре восстановления экрана (GEDPL) +.при ручной загрузке плагина (WMF), если имя плагина совпадает с уже загруженным, то предлагается удалить старый плагин и загрузить новый (упрощает разработку и тестирование плагинов) +.изменена версия плагинной системы (добалены новые типы плагинов) +.обновлен плагин: TXT Editor .добавлена возможность запуска из F10 меню (создание пустого файла) + .добавлена поддержка меню выбора вьюверов (зелёное меню) + .добавлено сохранение по CTRL+ALT+S (Save As...) + .доработки в обработке ошибок при сохранении по выходу + .доработка режима подсветки синтаксиса (F2) +.обновлена документация на плагинную систему + +v0.99 (16.12.2018): +.обновлен плагин: TXT Editor .повышена скорость работы на 14мгц (врубает кеш) + .добавлен режим с подсветкой синтаксиса (бета), переключается по F2 +.обновлен плагин: ASM Compiler .мелкие оптимизации + .добавлен вывод места ошибки при JR offset error (JR/DJNZ) + .добавлена поддержка SAVEBIN (учитывает только имя) +.обновлен плагин: File Creator (FILE_CR) .добавлено создание пустых текстовых файлов (*.txt) + .в окне ввода имени по ESC теперь происходит выход, а не запись файла как было раньше + +v0.99 (24.11.2018): +.обновлен плагин: MOUNTER .при маунте TRD/SCL имя образа теперь сохраняется и отображается при следующих маунтах/дизмаунтах + .добавлена возможность изменения параметров запуска из меню (задание частоты, лока памяти, режима кеш) + [не зависит от параметров в биосе, ресет по F12 все выставит как в биосе...] + .добавлен вариант запуска с бутом (qc3.11), не пишется в образ! + +v0.99 (02.06.2018): +.изменения в интерфейсе (добавлен 2й тип рамок для рисовалки окон, добавлены тени, широкий курсор), часть интерфейса переведена под новый стиль. +.небольшие оптимизации по памяти +.выкинут встроенный SCR, GIGASCREEN, MG вьювер (есть в плагине) +.изменено меню выбора вьювера (зелёная менюшка), меню формируется динамически в зависимости от плагинов в памяти +.обновление API: добавлен 4й и 5й тип плагинов (для вызовов из меню выбора вьювера) +.изменена версия плагинной системы +.обновлен плагин: WPLAYER (добавлена поддержка модулей от нового Fast Tracker 1.08, косметические изменения) +.обновлен плагин: MCGSV (добавлена поддержка меню вызова вьюверов) +.обновлены плагины: TXTEDIT, COMPILER (косметические изменения) +.добавлен плагин: TRDUMP (TRDump v0.56i by dr.Max) +.добавлен плагин: SETIME (SETime v0.1i by dr.Max) + +v0.98 (08.04.2018): +.доработки в опросе PS/2 клавиатуры (теперь правильно обрабатываются нажатия SHIFT+Arrows) +.добавлена обработка "*","-","+" на Numpad'е +.изменена версия плагинной системы +.обновлен плагин: TXT Editor .добавлен буфер обмена (16кб), сохраняется при выходе из плагина + .добавлена маркировка по SHIFT+RIGHT/DOWN/PGDOWN/END и редактирование маркировки по SHIFT+LEFT/UP + .реализован весь основной функционал работы с маркировкой + (копирование в буфер обмена по CTRL+"C", вставка из буфера обмена по CTRL+"V", вырезание по CTRL+"X") +.обновлен плагин: Compiler .добавлена очистка буфера под код (128кб) + +v0.97 (21.08.2017): +.обновлен плагин: Compiler .правлен баг с подсчетом EQUшек из очереди (не ставило стоп токена при генерации очереди) + .добавлена поддержка INCL до 64кб (без рекурсии) + .увеличена скорость компиляции на 26% за счет включения кеш + .увеличено макс. количество операндов с выражениями у команды до 15(16), было 8(9) + .добавлена возможность просмотра значений меток (по 'H' после завершения компиляции, запускается просмотр и листает экраны по одной кнопке) + .добавлен вывод сообщения при запуске компиляции + +v0.97 (18.08.2017): +.обновлен плагин: Compiler (исправлен баг с сохранением OBJ размером более 32кб, правлен баг с декодерами в DS, рефакторинг) +.обновлен плагин: TXT Editor (добавлен хелп по F1, добавлен поиск по ctrl+F и ctrl+G, оптимизации по скорости) + +v0.97 (12.08.2017): +.обновлен плагин: WPLAYER (увеличен буфер под модуль+плеер с 16кб до 32кб, теперь верно играет большие PT3 треки) +.обновлен плагин: TXT Editor (исправлен баг с сохранением файлов размером более 16кб, добавлен функционал) +.добавлен плагин: Compiler (компиляет *.ASM сорцы по ENTER, OBJ сохраняет в *.WMF; пока-что заточен под компиляние плагинов) + +v0.97 (31.07.2016): +.правлен баг: при вызове ISTR(9) обработчик гробил на входе HL' +.добавлена функция в API: ADIR(56) +.добавлено управление Caps Lock Led в PS/2 драйвер +.добавлен вывод ошибок при переименовании файлов/каталогов +.обновление API: добавлена передача номера расширения по которому был запущен плагин +.некоторое перераспределение памяти в ядре (вскоре придётся выкидывать встроенный функционал в плагины (mgs вьювер первый в очереди)) +.изменён детект в IDE драйверах (nemo, smuc) + +v0.96 (05.09.2015-08.03.2016): +.добавлен плагин: MCGSV.WMF (Multi Color'n'Gigascreen Viewer) +.незначительные исправления в документации на API плагинной системы +.добавлено зануление параметров SourceAlign, DestinationAlign, AlignSize при запуске плагинов (функция №13 DMAPL) +.в функцию работы с потоками (57 STREAM), добавлена возможность клонирования потока активной панели +.добавлена комбинация: SHIFT+F3 (просмотр файла с выбором вьювера), так же работает и SHIFT+ENTER +.немного окультурен встроенный TXT-Viewer +.в авто-загрузку плагинов добавлен вывод поля F4 (при встрече соотв. плагина) +.добавлен плагин: TXT Editor (F4) + +v0.94 (21.08.2015): +.правлен баг: вьювер Multi-Giga показывал с битой палитрой +.в API добавлена передача имени активного файла плагинам, при запуске по расширению(#00), либо по F4 (#14) +.в редакторе строки добавлена отработка CAPS LOCK +.расширены возможности функции ISTR(9) из API +.вернул функцию API #58 + +v0.92 (16.08.2015): +.правлен баг: загрузчик INI файла не верно отрабатывал конец файла +.параметры в SETUP(F9) теперь берутся из текущих, ранее оные выставлялись как по умолчанию +.в блок настройки SETUP(F9) добавлены пункты IDEnemoSlave, IDEsmucMaster и IDEsmucSlave +.обновлен плагин: MOUNTER (добавлена поддержка IDEnemoSlave; если обнаружен IDE VideoDAC, то работа с IDEnemo идет без INIR/OTIR; сообщение об ошибке гробило память (был вызов PRIAT после TXTPR)) +.в INI файле теперь можно указывать DRV=3/4/5 (IDEsmucMaster/IDEsmucSlave/IDEnemoSlave) +.добавлена общая поддержка Slave для IDEnemo и IDEsmuc драйверов +.оптимизации в IDEsmuc драйвере +.добавлен детект IDE VideoDAC в IDEnemo драйвер (теперь будет работать и с внешним контроллером [требует замены сигнала DOS\ на +5в]) + +v0.90 (09.08.2015): +.правлен баг: не работал запуск SPGv0.2 (требуется полная переработка загрузчика) +.добавлен доп. уровень защиты на LOAD512, при загрузке из плагинов в #C000-#FFFF + +v0.89 (08.08.2015): +.временно отключена функция API FindNext(58) (пока-что выдает другую структуру данных и не реагирует на A) +.вернул SD(NGS) драйвер, замержены все изменения из SD(ZC) драйвера +.добавлен драйвер под SMUC с открытыми портами +.все драйвера теперь хранятся в пакованном виде + +v0.88 (04.08.2015): +.обновление API (добавлены вызовы INS и DEL; добавлена возможность вешать на прерывания функцию из плагина, а так же отключать опрос клавиатуры и обновление часов) +.обновлен плагин: VIDEO_PL (переделан детект NGS; убран механизм SKIP FRAMES; сделан буфер в 4 кадра; алг. загрузки сделан блоковым [на SD картах дает хороший прирост]) +.обновлен плагин: WPLAYER (доработан детект STP и ASC модулей, а так же отключен вывод имени в модулях без заголовка) + +v0.87 (30.07.2015): +.незначительные изменения в SD(ZC) драйвере (добавлено ожидание busy после завершения транзакции записи и чтения) +.небольшие изменения в API (поддержка #14 типа плагинов) +.обновлен плагин: WPLAYER (вернул старое управление в режиме без плей-листа, изменены цвета, правлен косяк с полем Editor) + +v0.86 (13.07.2015): +.доработана отработка конца каталога (очень редко могло выводить в конце каталога мусор) +.обновлен плагин: ROM_PROG (версия от zboszor) + +v0.85 (13.05.2015): +.правлен баг: не отображались заголовки в setup'е +.обновлен плагин: TAPM (добавлена возможность выбора скорости запуска TAP'ов: SLOW и FAST) + +v0.84 (10.05.2015): +.обновлен плагин: MOUNTER (правлен баг, с приходом инта во время работы с регами NVRAM) +.правлен баг: не инитился флаг активной панели, после включения/повреждения страницы с оным (из-за этого не запоминалась позиция курсора, пока не нажмут TAB) + +v0.83 (08.05.2015): +.добавлено запоминание активной панели и позиции курсора +.в ini файл добавлен новый параметр: CURP (включает хранение позиции курсора вместе с путями) +.в setup добавлен соотв. пункт + +v0.82 (08.05.2015): +.правлен баг: не работал mc/mgs/scr вьювер +.обновлен плагин: MOUNTER (доработано меню, добавлен ресет в Basic128) + +v0.81 (07.05.2015): +.в редактор строки добавлена возможность редактирования строк любой длины, с выводом окна курсора на экран +.функция переименования(F6) теперь может переименовывать файлы/каталоги длиной до 255 символов +.изменено распределение памяти, увеличена область под каталоги (144кб на панель) +.сделан новый TXT вьювер (есть поддержка только cp866) +.правлен баг: в редакторе строки Delete криво отрабатывал с курсором в крайней правой позиции +.правлен баг: функция генерации каталога гробила ячейку #7FFF +.обновлен плагин: MOUNTER (добавлен вывод меню после маунта TRD/SCL) + +v0.79 (05.05.2015): +.добавлено удаление пустых каталогов +.для коротких имен, связанных с длинными, теперь всегда генерируется индекс, дабы не занимать имена... + +v0.78 (04.05.2015): +.правлен баг: сохранение ini файла в setup'e портило заголовок (используется общий буфер с fat ядром) + +v0.77 (04.05.2015): +.в редактор строки добавлена отработка Delete +.добавлено отображение режимов INS/OVR и раскладки ENG/RUS в редакторе строки +.исправлена ошибка с буквой "Ё" в генераторе каталога, отбраковывало оную как левый символ уникода и соотв. не выдавало длинное имя +.та же ошибка исправлена в поиске имени +.добавлен вывод ошибки при создании каталога +.при генерации длинного имени, короткое больше не содержит пробелы (не все софты умеют с оными работать) + +v0.76 (04.05.2015): +.в редактор строки добавлена поддержка русской раскладки +.редактор строки теперь реагирует на Insert (по умочанию стоит режим INS) + +v0.75 (04.05.2015): +.добавлена проверка на дупликаты, при создании короткого имени +.исправлена генерация индексов +.добавлено сообщение о сортировке + +v0.74 (03.05.2015): +.правлен баг: в генераторе короткого имени (всегда выставляло атрибут архивный) +.доработан генератор каталога, не обрабатывал ситуацию с пустым (корневым) каталогом + +v0.73 (03.05.2015): +.коммандер переведен на работу с длинными именами + -макс. длина имени файла/каталога 255 символов + -при создании файла теперь идет проверка на наличие дупликата, так же добавлен возврат типа ошибки + -все функции API, которые выдавали имена в формате [flag(1)],name(1-12),0 - теперь выдают имена до 255 символов! + -все функции API, которые принимали имена в формате [flag(1)],name(1-12),0 - теперь принимают имена до 255 символов + -функция FENTRY теперь может принимать на вход не просто имя файла, но так же и путь (к примеру #00,"..\dir1\dir2\file.txt",0) + -сортировщик переделан под длинные имена + -плагин WPLAYER теперь полноценно работает с ayl/m3u плей листами +.убрана 58я функция из API (MkFile устаревший) +.в API добавлены функции RENAME и FindNext (на место 58й) +.временно отключен SD(NGS) драйвер +.обновлен плагин: WPLAYER (не отдавал неопознанные файлы) +.обновлен плагин: FILE_CR (вызовы 58й функции заменены) + +v0.68 (24.04.2015): +.состояние NumLock теперь не влияет на Insert и Delete в панелях +.добавлены новые функции в API: DelFl, SCRLWOW +.сделано отключение графики при запуске + +v0.67 (23.04.2015): +.правлен баг: 54я функция API не работала +.обновлен плагин: WPLAYER + -работа с плай листами + -режимы проигрывания (цикл/на одном файле/останов) + -генерация списка проигрывания, если есть маркировка на файлах (их кидает в плей лист, при запуске) + -заходы в ayl/m3u (пока-что бесполезные, ибо нету поддержки лонг неймов в API коммандера) + +v0.66 (18.04.2015): +.доработан детект в SD драйвере +.обновлен SD NGS драйвер (замержены все изменения из SD(ZC) драйвера) +.обновлен плагин: WPLAYER, добавлено проигрывание SAA1099 модулей (ETracker) +.обновлена документация и описания + +v0.65 (13.04.2015): +.добавлен новый функционал в интерфейсную часть API +.введена глобальная переменная, нужная для SD драйвера в mounter'е +.обновлен плагин: Mounter (заменен SD драйвер, задействованы новые функции API) + +v0.63 (09.04.2015): +.правлен баг: в блоке настройки INI файла были перепутаны местами левая и правая панель +.правлен баг: пути к панелям гробились при входе в ts-bios, пересекалось с фонтом оного +.переписан SD драйвер, теперь должен видеть все карты (MMC в том числе) + +v0.62 (30.07.2014): +.увеличен размер буфера под плагины (с 512кб до 1мб) +.добавлена сортировка по расширению (ctrl+F4) +.добавлена сортировка по размеру (ctrl+F6) +.обновлен плагин: TAP Mounter (добавлен запуск SNA48/128) + +v0.61 (27.07.2014): +.добавлена сортировка (только по имени, отключается на каталогах больше 510) +.в ini файл добавлен новый пунк: CSORT (сортировка) +.добавлен вывод версии TSconf в F1 меню +.добавлены горячие клавиши: ctrl+F3 - вкл/выкл сортировки в панели; ctrl+R - обновить содержимое панели + +v0.60 (26.07.2014): +.временно отрубил мерж при стирании окон +.доработана пускалка SPGv0.x +.изменено чтение версии TSconf + +v0.59 (25.07.2014): +.убрана wide версия из сборки +.в ini файл добавлен новый пунк: Text Mode (TXTMD), задает разрешение тхт режима (25x80/30x80/36x90) + +v0.58 (24.07.2014): +.добавлено включение кеша в окне с системными функциями (page1) +.добавлено чтение версии TSconf, TGA вьювер теперь учитывает IDE Video-DAC + +v0.57 (21.07.2014): +.правлен баг: при удалении плагина не обновлялась переменная начала свободного места в буфере плагинов +.правлен баг: гробились депакеры запускалки SPG +.добавлена возможность прямой загрузки данных в #0000-#3FFF (плагинная система) +.добавлена возможность запуска скрин сейвера из F10 меню + +v0.56 (09.12.2013-18.07.2014): +.буфер окон перемещен в отдельную страницу +.добавлена дефрагментация, при удалении окон в произвольном порядке (окна не должны пересекаться!) +.добавлена дефрагментация, при удалении плагинов (теперь удаляется не только заголовок плагина, но и место выделенное под него) +.расширен функционал плагинной системы, изменена версия и расширен заголовок (функции MNG0_PL, MNG8_PL, MNG0VPL, MNG8VPL) +.добавлен редактор INI файла (F9) + +v0.55 (02.12.2013): +.расширен функционал плагинной системы, сменена версия таковой (функции DMAPL, GVtm, GVtl, GVsgp) +.увеличен размер графического буфера до 64 страниц (1мб) +.исправлен баг: не сбрасывался путь в панели при запуске, если носитель отсутствовал +.обновлена документация на плагинную систему + +v0.54 (29.11.2013): +.добавлено выставление внутренней частоты по выходу из плагинов (задается в ини) +.добавлено отображение пути к активному каталогу +.добавлен раздел SETUP в ini файл (можно задать частоту процессора и выбрать режим хранения позиций панелей в ОЗУ) +.добавлен плагин: TAP Mounter +.добавлен плагин: IFF/ILBM Viewer + +v0.53 (16.11.2013): +.правлен баг: убрано обращение к порту #2AAF +.правлен баг: запуск SPGv0.x не работал + +v0.50 (24.08.2013): +.правлен баг: вешался прогресс бар, на файлах меньше 64 байт +.увеличен размер видео буфера, выделяемого плагинам (добавлен 3й видео экран, см. функционал) +.добавлены параметры панелей в INI файл (изменена версия такового) +.в сборку добавлена версия коммандера 80x25 (WildCommanderWide) + +v0.48 (28.11.2012): +.обновлен IDE драйвер (добавлена поддержка DMA, доработан детект) +.обновлен SD(ZC) драйвер (добавлена поддержка DMA) +.переделан алг. чтения/записи на мульти-секторный (FAT ядро) + +v0.40 (27.11.2012): +.доработана пускалка SPGv1.0 (добавлена чистка ZX экрана) +.обновлен плагин: BMP Viewer & Sprite Cutter (поддержка разрешений от 1x1 до 512x512, 256c спрайты, ручное изменение размера рамки) + +v0.39 (11.10.2012): +.доработано меню работы с плагинами (F10) +.обновлен плагин: GS Player (переделан mp3 плеер, подправлен детект NGS) + +v0.38 (03.10.2012): +.вынесен опрос Caps Lock в плагины +.исправлена ошибка в DMAPL (3я функция) + +v0.36 (28.09.2012): +.добавлена отработка клавиш LEFT, RIGHT, HOME, END в F10 меню (меню работы с плагинами) +.добавлены новые функции в плагинную систему (MKfile, MKdir, STREAM, DMA) +.исправлена проблема с конверсией имен "..", "." в GENTRYX +.обновлена документация на плагинную систему + +v0.35 (26.09.2012): +.добавлена запускалка SPGv1.0 (позволяет грузить до 256блоков с жатием или без (hrust1/megalz) +.расширен функционал плагинной системы +.добавлены дополнительные защиты в плагинную систему + +v0.33 (31.08.2012): +.правлен баг: при запуске SPG всегда выставлялись 14мгц +.обновлен Nemo-IDE драйвер, переделаны функции чтения/записи (в соответствие с докой) +.обновлен плагин: TRD Mounter (исправлен Nemo-IDE драйвер) +.обновлен плагин: File Creator (правлен баг с 9м сектором в TRD без бута) + +v0.31 (23.07.2012-24.08.2012): +.доработан Nemo-IDE драйвер, для корректной работы с IDE-SD переходниками +.обновлен плагин: TRD Mounter (обновлен Nemo-IDE драйвер) +.обновлен плагин: BMP Viewer & Sprite Cutter (новые размеры рамок) +.обновлен плагин: File Creator (исправлено название бута, было "QC3.05.B") + +v0.30 (05.04.2012-21.07.2012): +.правлен баг: в File Creator'е не проверялся флаг ошибки при вызове функции создания файла +.добавлено игнорирование старших 4х бит в номере кластера (FAT ядро) +.добавлены опросы F1-F10 в плагинную систему + мелкие изменения +.добавлен плагин: BMP Viewer & Sprite Cutter (Beta версия). понимает только BMP 8bit 320x240! + +v0.29 (03.04.2012): +.несколько изменён детект FAT32 (FAT ядро) +.TURBOPL теперь меняет 3х-битную частоту AY (плагинная система) +.убраны мигания экрана при запуске $C/SPG файлов +.реализована функция создания файла - MKFILE (плагинная система) +.добавлен новый плагин: File Creator, умеет создавать пустые TRD и TRD с boot'ом (размером 640/800KB) +.обновлён плагин: TR-DOS Mounter, исправлены мелкие недочёты + +v0.26 (31.03.2012): +.снято ограничение на размер создаваемой цепочки, соотв. теперь можно копировать файлы любых размеров! (FAT ядро) +.добавлена функция скипа данных (FAT ядро) +.добавлена проверка на выход за пределы FAT. теперь: если нету достаточного свободного места, то будет выдана ошибка! (FAT ядро) +.обновлён плагин: Video Player, добавлен фрейм скип, а так же заливка паллитры через DMA (необходимо обновить конфиг!!!) + +v0.25 (29.03.2012): +.в ядро ФС добавлена новая функция, необходимая для Video Player'а (FAT ядро) +.обновлена плагинная система и версия таковой (необходимо обновить ВСЕ плагины) +.добавлен таймер на прерывания (для плагинов) +.добавлен новый плагин: Video Player + +v0.24 (18.03.2012): +.в GS Player добавлено проигрывание mp3 файлов, а так же доработано проигрывание mod'ов и wav'ов +.правлены ошибки в плагинной системе (включение 2го видео экрана не работало) +.доработаны переключения режимов и экранов (больше не мигает экраном с мусором) + +v0.23 (14.03.2012): +.добавлен новый плагин: GS Player (играет MOD и WAV[8bit mono/stereo 8000/11025/16000/22050]) +.отключено чтение структуры FSInfo (FAT ядро) +.изменён фильтр имен (теперь не добавляет "_" именам состоящим из 1го символа) +.флаги ABT и ENT вынесены в фикс. адреса (для возможности опроса из плагинов) + +v0.22 (13.03.2012): +.автозагрузка ини файла и плагинов теперь учитывает boot device (который выставляется в ts-bios'e) + +v0.21 (11.03.2012): +.изменен порт статуса DMA + +v0.20 (10.03.2012): +.добавлен вывод версии конфига (по F1) +.переделаны алгоритмы работы с DMA + +v0.19 (09.03.2012): +.добавлен новый плагин: SCREEN SAVER by r0bat (SCRSAVER.WMF). он же добавлен в INI файл +.добавлена новая функция в плагинную систему (опрос any key). обновлена документация +.обновлен TRD Mounter: добавлена индикация виртуальных дисководов, добавлена защита от ресета в момент маунта + +v0.18 (08.03.2012): +.записи в порты Yoffset и Xoffset перенесены в обработчик прерываний (теперь txt/hex вьюверы работают нормально в режиме 90x36 символов, даже на 3.5МГц) +.исправлены мелкие ошибки в плагинной системе + +v0.16 (07.03.2012): +.обновлен vDOS в TR-DOS маунтере (mounter.wmf) +.окно автозагрузки теперь можно прочитать, зажав любую клавишу при запуске коммандера +.добавлена возможность маркировки по клавише INSERT + +v0.15 (06.03.2012): +.правлены мелкие недочеты в автогрузилке плагинов. обновлен TRD Mounter. + +v0.14 (06.03.2012): +.добавлена возможность автозагрузки плагинов при запуске WC (нужно разместить wc.ini и все плагины в папке WC [в корне SD карты]) +.правлены недочеты во время просмотра текста/графики + +v0.13 (05.03.2012): +.добавлена возможность выхода по ESC из меню ALT+F1/F2 +.оптимизированно распределение памяти в WC (теперь пользуются только первые 2.5MB) +.в маунтер vDOS добавлена возможность маунта SCL как RAM диска! собственно vDOS теперь поддерживает и RAM диск (один на все вирт диски) + +v0.12 (04.03.2012): +.обновлён фонт +.добавлен диалог, при удалении плагинов из меню (F10) +.добавлен инит нового порта DMA + +v0.11 (03.03.2012): +.правлен баг: в плагин передавался неверный размер файла... + +v0.099 (03.03.2012): +.в плагинной системе: IX можно не восстанавливать по выходу из плагина +.теперь после удаления файла/группы файлов курсор не прыгает в начало каталога + +v0.095 (02.03.2012): +.добавлено меню запуска плагинов (F10) +.добавлена функция удаления плагина (DEL в меню запуска[F10]) +.обновлен плагин-маунтилка (mounter.wmf). оный теперь можно вызывать из меню, для маунта RS-232 дисков и дизмаунта вирт дисков... + +v0.091 (22.02.2012): +.выставление pg0 и memConf, при выходе, теперь производится из сетапных переменных +.добавлен плагин TRD Mounter. маунтит TRD образы с FAT32 на виртуальные диски A-D. (запись/чтение идет прямо в образы) + +v0.089 (18.02.2012): +.правлен запуск $C/SPG файлов (под не АТМ пагер) +.добавлено чтение #AC, #AD ячеек из NVRAM при выходе из коммандера (#AC = pg0, #AD = memConf) +.добавлено отключение основного обработчика прерываний (ибо DI может быть отменён из IDE драйвера) +.правлен WP-плагин (оный выводил окно до функции определения модуля) + +v0.085 (14.02.2012): +.добавлена запускалка хобета-файлов ($C) +.расширен функционал доступный из плагинов + обновлена дока + +v0.081 (12.02.2012): +.доработан выход из плагинов (менеджер плагинов) + добавлены защиты +.добавлено выставление стека при запуске +.обновлен дизайн WP-плагина + +v0.079 (09.02.2012): +.доработана плагинная система + правлены баги + обновлены доки +.добавлена возможность загрузки до 32 плагинов, объёмом до 512кб (менеджер плагинов) +.WP плагин переведён на 3.5МГц (основной цикл), дабы TFC нормально игрались +.добавлен новый плагин: ROM writer (прошивает заданный 64Кб блок ПЗУ) +.правлен баг: функция поиска партиций находила FAT16 разделы (FAT ядро) +.правлены мелкие недочёты (при отмене удаления перечитывался каталог, доделано меню Y/N и т.д.) + +v0.073 (06.02.2012): +.в ядро ФС добавлена функция поиска файла/каталога по имени (FAT ядро) +.в плагинной системе добавлены новые возможности (запуск после загрузки и по таймеру) +.добавлена дока по плагинной системе + +v0.072 (05.02.2012): +.временно впилен порт #2A (дабы SPG запускались) +.убрана лишняя чистилка из TGA вьювера + незначительно ускорен алгоритм +.поменяно управление частотой Z80 (новые порты) + +v0.071 (04.02.2012): +.правлен баг: при удалении группы файлов курсор мог оказаться в старом каталоге +.добавлено центрирование имени загруженного плагина +.расширен функционал ядра ФС (необходимый для vTR-DOS'а) + +v0.068 (03.02.2012): +.правлен баг: включенный Num Lock блочил клавиши в панелях (Left, Right, Up, Down, Home, End, pgUP, pgDN на основной клавиатуре) +.добавлена возможность отмены во время копирования/удаления нескольких файлов (по ESC) + +v0.066 (01.02.2012): +.обновлены алгоритмы работы с DMA + правлены мелкие недочеты + +v0.065 (01.02.2012): +.добавлена поддержка дма режима память-память во вьюверах графики +.сделано кеширование в функции удаления цепочки (FAT ядро), оная стала быстрее в ~128 раз +.правлен баг: END на пустом каталоге (корневом) выкидывал курсор в аут... + +v0.061 (31.01.2012): +.добавлена функция удаления файла/группы файлов (F8) + +v0.059 (28.01.2012): +.правлен баг: в плагин передавалась не верная длина файла +.правлен баг: в штампе времени неверно считалось значение секунд (не делилось на 2) +.добавлена проверка заголовка при загрузке плагинов (WMF файлы) +.добавлена ассоциация расширения "$C" c вьювером ZX экранов и гигаскринов +.сделано запоминание координаты курсора в меню выбора вьювера +.обновлён WP-плагин (добавлен прогресс бар и автопереходы [для PT3, VT, PT2, PT1, FLS, STC, STP, FTC, PSC, SQT, ASC, GTR, TFC]) + +v0.058 (27.01.2012): +.расширен функционал доступный из плагинов +.из WP-плагина теперь отрабатывается лавиша ENTER +.теперь, если плагин не опознаёт контент файла - то таковой отдаётся вьюверам + +v0.056 (26.01.2012): +.добавлен вывод окна после удачной загрузки плагина +.незначительно обновлён WP-плагин + +v0.055 (26.01.2012): +.небольшие доработки в плагинной системе +.добавлена возможность просмотра SCR из Hobeta +.обновлён WP-плагин + +v0.052 (25.01.2012): +.добавлена плагинная система +.сделан плагин на основе WP (проигрывает AY/TS/TFM музыку) +.обновлён фонт + +v0.049 (22.01.2012): +.драйвера перенесены в FAT ядро +.исправлена проблема с каталогами > 2048 +.добавлена поддержка ALT/WIN/KOI в тхт-вьювере + автодетектор + +v0.048 (21.01.2012): +.добавлен SD(NGS) драйвер +.добавлен запуск SPG файлов (с меню выбора частоты) + +v0.045 (20.01.2012): +.добавлены ассоциации A80, ASM, DIZ, NF0 для тхт-вьювера +.курсор в окнах теперь учитывает маску +.добавлена маркировка файлов (space) +.добавлено диалоговое окно перед копированием (F5) + копирование группы файлов +.правлен баг: генерилка каталога не учитывала флаг EOC +.вызов функций чтения/записи файла теперь можно делать и при выставленном EOC (FAT ядро) + +v0.041 (18.01.2012): +.убраны пересечения с лучом в тхт-вьювере +.добавлена функция переименования файла/каталога (F6) + +v0.040 (17.01.2012): +.снято ограничение на размер FAT (FAT ядро) +.правлен баг: функции F5 и F7 сбрасывали историю позиций курсора +.правлен баг: скролл вниз в панелях, на каталогах >512 файлов, блочился раньше времени +.добавлен штамп времени и даты при создании файла/каталога (берётся из RTC) +.на Left и Right теперь висят функции Home и End + +v0.036 (16.01.2012): +.исправлен баг с переключением противоположной панели (курентная теряла драйвер) +.добавлена строка с информацией о файле под курсором + +v0.035 (15.01.2012): +.значительно ускорен просмотр гигаскрин/мультигигаскрин графики (~ 1.5 раза) +.улучшен алг. перехода по ENTER из вьюверов +.сделан редактор строки +.добавлены часы +.добавлена функция создания каталога (F7) + +v0.031 (14.01.2012): +.правлен баг: скролл вниз в HEX-вьювере врал одну строчку на стыке страниц +.добавлен просмотр .mg2, .mg4, .mg8 файлов (Мультигигаскрин) +.добавлена ассоциация расширения .gsc с гигаскрином + +v0.030 (13.01.2012): +.правлен баг в HEX-вьювере (не правильно работал скролл вверх) +.в HEX-вьювер добавлена обработка конца файла, плюс незначительно подправлен интерфейс +.в панелях: ENTER - вход в каталог/просмотр файла(как по F3), SHIFT+ENTER - вызвать меню выбора вьювера + +v0.029 (12.01.2012): +.добавлен HEX-вьювер (без обработки конца файла)[F3] +.добиты последние глюки в тхт вьювере (не чистился экран в конце файла) +.добавлен переход на уровень выше по BACKSPACE (в панелях) + +v0.028 (11.01.2012): +.в текст-вьювере сделана правильная обработка длинных строк (длиннее 90 символов) +.в текст-вьювер добавлены клавиши pgUP и pgDN (F3) +.добавлен просмотр ZX картинок и gigascreen (в 256c)[F3] +.добавлен диалог выбора типа просмотра, если расширение не опознано (F3) + +v0.026 (10.01.2012): +.доработано поведение pgDN в панелях +.добавлены клавиши HOME и END в панелях +.добавлена проверка заголовка TGA файлов +.добавлена клавиша ENTER во вьюверах (производит переход к след. файлу) +.доделан тектсовый вьювер (теперь можно скроллить в оба направления) + +v0.025 (10.01.2012): +.добавлена возможность просмотра TGA изображений (в режиме 256с) +.добавлен инит палитры при запуске + +v0.023 (09.01.2012): +.правлен баг с прогресс баром (оный не правильно работал на больших файлах, плюс гробил ide драйвер) +.исправлен баг с входом в каталог на позиции Y=#01FF +.доработана фильтрация при загрузке каталога (теперь не кажет метку тома) +.изменён размер записи в каталоге [в режиме имён 8.3] (25->32) +.увеличен максимальный размер каталога [в режиме имён 8.3] (655->2048) + +v0.022(08.01.2012): +.исправлены глюки с HDD (FAT ядро) +.правлен глюк с неправильной позицией курсора в панели приёмнике после копирования +.добавлено возвращение ошибки из функции генерации цепочки, если размер файла вышел более 6144 кластеров +.добавлен прогресс бар +.добавлены клавиши pgDN/pgUP в панелях diff --git a/DiskRef/WC_todo.txt b/DiskRef/WC_todo.txt new file mode 100644 index 0000000..e35743f --- /dev/null +++ b/DiskRef/WC_todo.txt @@ -0,0 +1,45 @@ +WildCommander todo list: +.добавить обновление не активной панели (при выходе из плагинов которые этого требуют, а так же после всех операций удаления/копирования в коммандере) + +.добавить инфо в сетап +.SPG Logo + +.мониторинг наличия SD карты на прерываниях (FAT ядро?) + +.пересекаются буферы BUX и WINDC, ситуация проявляется в EXTZEL (сейчас не влияет ни на что!) + +SPGv0.2 loader: +.алг. пользует область #5300-#5AFF под буфер, что недопустимо! + +API: +.в функции удаления файла: при удалении каталога, добавить проверку того, что DIR пустой +.в редакторе строки: в инфо добавить возможность вывода позиции курсора/длины строки (буфера) +.в редакторе строки: добавить HOME/END + +FAT ядро: +.доработать анализатор имени (определение типа SN/LN, SN с пробелами делать LN) +.удаление LN в каталоге затирает только SN (добавить полное удаление) +.поисковик не игнорирует пробелы в начале LN (с обеих сторон) +.файловый уровень для RS232 +.файловый уровень для ISO9660 + +TXT viewer: +.Home/End при просмотре +.ручное переключение кодировок(ALT/WIN/KOI) +.ansi-viewer + +TXT Editor: +.добить инспектор + +ROM_PROG: +.сканер ПЗУ + +MOUNTER: +.при маунте SCL на RAM диск, генерить имя диска (в образе) +.(?)при маунте SCL поверх существуещего RAM диска, выводить диалог на замену +.при маунте TRD размером меньше 640кб предупреждать об этом (плюс давать ошибку при попытке записи в область за пределами кластерной цепочки) + +BMPV: +.заменить вызов 58й функции +.доработать алг +.центрирование картинок в TGA-вьювере \ No newline at end of file diff --git a/DiskRef/boot.$C b/DiskRef/boot.$C new file mode 100644 index 0000000..4f2b263 Binary files /dev/null and b/DiskRef/boot.$C differ diff --git a/DiskRef/music_test/opl2_ym2149/01 Fight for the Future.vgz b/DiskRef/music_test/opl2_ym2149/01 Fight for the Future.vgz new file mode 100644 index 0000000..4512ab8 Binary files /dev/null and b/DiskRef/music_test/opl2_ym2149/01 Fight for the Future.vgz differ diff --git a/DiskRef/music_test/opl2_ym2149/08 The X2 Anthem.vgz b/DiskRef/music_test/opl2_ym2149/08 The X2 Anthem.vgz new file mode 100644 index 0000000..34ae46c Binary files /dev/null and b/DiskRef/music_test/opl2_ym2149/08 The X2 Anthem.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/02 How to Play.vgz b/DiskRef/music_test/opl2_ym2203/02 How to Play.vgz new file mode 100644 index 0000000..e31c0c5 Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/02 How to Play.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/05 Stage 1.vgz b/DiskRef/music_test/opl2_ym2203/05 Stage 1.vgz new file mode 100644 index 0000000..ffe9754 Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/05 Stage 1.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/08 Stage 2.vgz b/DiskRef/music_test/opl2_ym2203/08 Stage 2.vgz new file mode 100644 index 0000000..a0ebd24 Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/08 Stage 2.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/09 Stage 3.vgz b/DiskRef/music_test/opl2_ym2203/09 Stage 3.vgz new file mode 100644 index 0000000..264d351 Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/09 Stage 3.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/10 Stage 4.vgz b/DiskRef/music_test/opl2_ym2203/10 Stage 4.vgz new file mode 100644 index 0000000..11866e0 Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/10 Stage 4.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/11 Stage 5.vgz b/DiskRef/music_test/opl2_ym2203/11 Stage 5.vgz new file mode 100644 index 0000000..78596df Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/11 Stage 5.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/12 Stage 6.vgz b/DiskRef/music_test/opl2_ym2203/12 Stage 6.vgz new file mode 100644 index 0000000..bb42cc9 Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/12 Stage 6.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/13 Stage 7.vgz b/DiskRef/music_test/opl2_ym2203/13 Stage 7.vgz new file mode 100644 index 0000000..c68a09f Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/13 Stage 7.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/14 Stage 8.vgz b/DiskRef/music_test/opl2_ym2203/14 Stage 8.vgz new file mode 100644 index 0000000..a3d142c Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/14 Stage 8.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/16 Ending 2.vgz b/DiskRef/music_test/opl2_ym2203/16 Ending 2.vgz new file mode 100644 index 0000000..83cbffa Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/16 Ending 2.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/17 Ending 3.vgz b/DiskRef/music_test/opl2_ym2203/17 Ending 3.vgz new file mode 100644 index 0000000..335d1fb Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/17 Ending 3.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/22 Solo Level 1.vgz b/DiskRef/music_test/opl2_ym2203/22 Solo Level 1.vgz new file mode 100644 index 0000000..8d8808f Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/22 Solo Level 1.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/23 Solo Level 25.vgz b/DiskRef/music_test/opl2_ym2203/23 Solo Level 25.vgz new file mode 100644 index 0000000..f33138c Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/23 Solo Level 25.vgz differ diff --git a/DiskRef/music_test/opl2_ym2203/25 Unknown 1.vgz b/DiskRef/music_test/opl2_ym2203/25 Unknown 1.vgz new file mode 100644 index 0000000..3bc6244 Binary files /dev/null and b/DiskRef/music_test/opl2_ym2203/25 Unknown 1.vgz differ diff --git a/DiskRef/music_test/opl3/01 Heels Beat on the Stone Paving.vgm b/DiskRef/music_test/opl3/01 Heels Beat on the Stone Paving.vgm new file mode 100644 index 0000000..34bf1b3 Binary files /dev/null and b/DiskRef/music_test/opl3/01 Heels Beat on the Stone Paving.vgm differ diff --git a/DiskRef/music_test/opl3/02 Drops are Dots, Vessels are Faces.vgm b/DiskRef/music_test/opl3/02 Drops are Dots, Vessels are Faces.vgm new file mode 100644 index 0000000..d27e218 Binary files /dev/null and b/DiskRef/music_test/opl3/02 Drops are Dots, Vessels are Faces.vgm differ diff --git a/DiskRef/music_test/opl3/03 One and Two and Three.vgm b/DiskRef/music_test/opl3/03 One and Two and Three.vgm new file mode 100644 index 0000000..d283a31 Binary files /dev/null and b/DiskRef/music_test/opl3/03 One and Two and Three.vgm differ diff --git a/DiskRef/music_test/opl3/04 A Bird Flies Even Without Wind.vgm b/DiskRef/music_test/opl3/04 A Bird Flies Even Without Wind.vgm new file mode 100644 index 0000000..1ca61d4 Binary files /dev/null and b/DiskRef/music_test/opl3/04 A Bird Flies Even Without Wind.vgm differ diff --git a/DiskRef/music_test/opl3/05 An Erased Voice is Noise.vgm b/DiskRef/music_test/opl3/05 An Erased Voice is Noise.vgm new file mode 100644 index 0000000..abc112f Binary files /dev/null and b/DiskRef/music_test/opl3/05 An Erased Voice is Noise.vgm differ diff --git a/DiskRef/music_test/opl3/06 An Invisible Chain Hurts Her Neck.vgm b/DiskRef/music_test/opl3/06 An Invisible Chain Hurts Her Neck.vgm new file mode 100644 index 0000000..455ba72 Binary files /dev/null and b/DiskRef/music_test/opl3/06 An Invisible Chain Hurts Her Neck.vgm differ diff --git a/DiskRef/music_test/opl3/07 A Broken Piano Will Not Play.vgm b/DiskRef/music_test/opl3/07 A Broken Piano Will Not Play.vgm new file mode 100644 index 0000000..7bf4fe5 Binary files /dev/null and b/DiskRef/music_test/opl3/07 A Broken Piano Will Not Play.vgm differ diff --git a/DiskRef/music_test/opl3/08 White Virgin, Black Slave.vgm b/DiskRef/music_test/opl3/08 White Virgin, Black Slave.vgm new file mode 100644 index 0000000..6e0d682 Binary files /dev/null and b/DiskRef/music_test/opl3/08 White Virgin, Black Slave.vgm differ diff --git a/DiskRef/music_test/opl3/09 To Dream is a Lady's Privilege.vgm b/DiskRef/music_test/opl3/09 To Dream is a Lady's Privilege.vgm new file mode 100644 index 0000000..aa7623d Binary files /dev/null and b/DiskRef/music_test/opl3/09 To Dream is a Lady's Privilege.vgm differ diff --git a/DiskRef/music_test/ym2149/01 Fight for the Future.vgz b/DiskRef/music_test/ym2149/01 Fight for the Future.vgz new file mode 100644 index 0000000..bb721a0 Binary files /dev/null and b/DiskRef/music_test/ym2149/01 Fight for the Future.vgz differ diff --git a/DiskRef/music_test/ym2149/02 Jungle Vibes.vgz b/DiskRef/music_test/ym2149/02 Jungle Vibes.vgz new file mode 100644 index 0000000..bbda9e5 Binary files /dev/null and b/DiskRef/music_test/ym2149/02 Jungle Vibes.vgz differ diff --git a/DiskRef/music_test/ym2149/03 Klepackin' Heat.vgz b/DiskRef/music_test/ym2149/03 Klepackin' Heat.vgz new file mode 100644 index 0000000..b1e2d2c Binary files /dev/null and b/DiskRef/music_test/ym2149/03 Klepackin' Heat.vgz differ diff --git a/DiskRef/music_test/ym2149/04 Plan Ahead.vgz b/DiskRef/music_test/ym2149/04 Plan Ahead.vgz new file mode 100644 index 0000000..dbf938d Binary files /dev/null and b/DiskRef/music_test/ym2149/04 Plan Ahead.vgz differ diff --git a/DiskRef/music_test/ym2149/05 New Lands.vgz b/DiskRef/music_test/ym2149/05 New Lands.vgz new file mode 100644 index 0000000..e063d4c Binary files /dev/null and b/DiskRef/music_test/ym2149/05 New Lands.vgz differ diff --git a/DiskRef/music_test/ym2149/06 Into Battle.vgz b/DiskRef/music_test/ym2149/06 Into Battle.vgz new file mode 100644 index 0000000..bdad854 Binary files /dev/null and b/DiskRef/music_test/ym2149/06 Into Battle.vgz differ diff --git a/DiskRef/music_test/ym2149/07 Apprehension.vgz b/DiskRef/music_test/ym2149/07 Apprehension.vgz new file mode 100644 index 0000000..55b4537 Binary files /dev/null and b/DiskRef/music_test/ym2149/07 Apprehension.vgz differ diff --git a/DiskRef/music_test/ym2149/08 The X2 Anthem.vgz b/DiskRef/music_test/ym2149/08 The X2 Anthem.vgz new file mode 100644 index 0000000..bb058e7 Binary files /dev/null and b/DiskRef/music_test/ym2149/08 The X2 Anthem.vgz differ diff --git a/DiskRef/music_test/ym2149/09 Valiant Fighters.vgz b/DiskRef/music_test/ym2149/09 Valiant Fighters.vgz new file mode 100644 index 0000000..45b504a Binary files /dev/null and b/DiskRef/music_test/ym2149/09 Valiant Fighters.vgz differ diff --git a/DiskRef/music_test/ym2149/10 No More Fighting.vgz b/DiskRef/music_test/ym2149/10 No More Fighting.vgz new file mode 100644 index 0000000..26bcb4e Binary files /dev/null and b/DiskRef/music_test/ym2149/10 No More Fighting.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/01 Attract Mode.vgz b/DiskRef/music_test/ym2149_ym2203/01 Attract Mode.vgz new file mode 100644 index 0000000..98e6937 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/01 Attract Mode.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/02 Start ~ Surface BGM 1.vgz b/DiskRef/music_test/ym2149_ym2203/02 Start ~ Surface BGM 1.vgz new file mode 100644 index 0000000..785f018 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/02 Start ~ Surface BGM 1.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/03 Start (no SFX).vgz b/DiskRef/music_test/ym2149_ym2203/03 Start (no SFX).vgz new file mode 100644 index 0000000..3c041f4 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/03 Start (no SFX).vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/04 Restart.vgz b/DiskRef/music_test/ym2149_ym2203/04 Restart.vgz new file mode 100644 index 0000000..de002e3 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/04 Restart.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/05 Surface BGM 1.vgz b/DiskRef/music_test/ym2149_ym2203/05 Surface BGM 1.vgz new file mode 100644 index 0000000..bbf6a42 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/05 Surface BGM 1.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/06 Last Start.vgz b/DiskRef/music_test/ym2149_ym2203/06 Last Start.vgz new file mode 100644 index 0000000..acacf54 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/06 Last Start.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/07 Subsurface BGM 1.vgz b/DiskRef/music_test/ym2149_ym2203/07 Subsurface BGM 1.vgz new file mode 100644 index 0000000..31840a4 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/07 Subsurface BGM 1.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/08 Surface BGM 2.vgz b/DiskRef/music_test/ym2149_ym2203/08 Surface BGM 2.vgz new file mode 100644 index 0000000..ea83dc4 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/08 Surface BGM 2.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/09 Subsurface BGM 2.vgz b/DiskRef/music_test/ym2149_ym2203/09 Subsurface BGM 2.vgz new file mode 100644 index 0000000..8564791 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/09 Subsurface BGM 2.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/10 Surface BGM 3.vgz b/DiskRef/music_test/ym2149_ym2203/10 Surface BGM 3.vgz new file mode 100644 index 0000000..03dcd44 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/10 Surface BGM 3.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/11 Surface BGM 4.vgz b/DiskRef/music_test/ym2149_ym2203/11 Surface BGM 4.vgz new file mode 100644 index 0000000..366ec6a Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/11 Surface BGM 4.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/12 Subsurface BGM 3.vgz b/DiskRef/music_test/ym2149_ym2203/12 Subsurface BGM 3.vgz new file mode 100644 index 0000000..27bf9cd Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/12 Subsurface BGM 3.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/13 Game Over.vgz b/DiskRef/music_test/ym2149_ym2203/13 Game Over.vgz new file mode 100644 index 0000000..35192c8 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/13 Game Over.vgz differ diff --git a/DiskRef/music_test/ym2149_ym2203/14 Name Entry.vgz b/DiskRef/music_test/ym2149_ym2203/14 Name Entry.vgz new file mode 100644 index 0000000..de7b539 Binary files /dev/null and b/DiskRef/music_test/ym2149_ym2203/14 Name Entry.vgz differ diff --git a/DiskRef/music_test/ym2203/01 Heels Beat on the Stone Paving.vgm b/DiskRef/music_test/ym2203/01 Heels Beat on the Stone Paving.vgm new file mode 100644 index 0000000..a37eb09 Binary files /dev/null and b/DiskRef/music_test/ym2203/01 Heels Beat on the Stone Paving.vgm differ diff --git a/DiskRef/music_test/ym2203/02 Drops are Dots, Vessels are Faces.vgm b/DiskRef/music_test/ym2203/02 Drops are Dots, Vessels are Faces.vgm new file mode 100644 index 0000000..d99b234 Binary files /dev/null and b/DiskRef/music_test/ym2203/02 Drops are Dots, Vessels are Faces.vgm differ diff --git a/DiskRef/music_test/ym2203/03 One and Two and Three.vgm b/DiskRef/music_test/ym2203/03 One and Two and Three.vgm new file mode 100644 index 0000000..3f51989 Binary files /dev/null and b/DiskRef/music_test/ym2203/03 One and Two and Three.vgm differ diff --git a/DiskRef/music_test/ym2203/04 A Bird Flies Even Without Wind.vgm b/DiskRef/music_test/ym2203/04 A Bird Flies Even Without Wind.vgm new file mode 100644 index 0000000..e9b7871 Binary files /dev/null and b/DiskRef/music_test/ym2203/04 A Bird Flies Even Without Wind.vgm differ diff --git a/DiskRef/music_test/ym2203/05 An Erased Voice is Noise.vgm b/DiskRef/music_test/ym2203/05 An Erased Voice is Noise.vgm new file mode 100644 index 0000000..1e5e18f Binary files /dev/null and b/DiskRef/music_test/ym2203/05 An Erased Voice is Noise.vgm differ diff --git a/DiskRef/music_test/ym2203/06 An Invisible Chain Hurts Her Neck.vgm b/DiskRef/music_test/ym2203/06 An Invisible Chain Hurts Her Neck.vgm new file mode 100644 index 0000000..5a089fe Binary files /dev/null and b/DiskRef/music_test/ym2203/06 An Invisible Chain Hurts Her Neck.vgm differ diff --git a/DiskRef/music_test/ym2203/07 A Broken Piano Will Not Play.vgm b/DiskRef/music_test/ym2203/07 A Broken Piano Will Not Play.vgm new file mode 100644 index 0000000..d3908f7 Binary files /dev/null and b/DiskRef/music_test/ym2203/07 A Broken Piano Will Not Play.vgm differ diff --git a/DiskRef/music_test/ym2203/08 White Virgin, Black Slave.vgm b/DiskRef/music_test/ym2203/08 White Virgin, Black Slave.vgm new file mode 100644 index 0000000..5b27481 Binary files /dev/null and b/DiskRef/music_test/ym2203/08 White Virgin, Black Slave.vgm differ diff --git a/DiskRef/music_test/ym2203/09 To Dream is a Lady's Privilege.vgm b/DiskRef/music_test/ym2203/09 To Dream is a Lady's Privilege.vgm new file mode 100644 index 0000000..f9296d9 Binary files /dev/null and b/DiskRef/music_test/ym2203/09 To Dream is a Lady's Privilege.vgm differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd81b14 --- /dev/null +++ b/README.md @@ -0,0 +1,153 @@ +# VGMPLAY.WMF — VGM Player for TSConfig / Wild Commander + +VGM/VGZ player plugin for **Wild Commander** (WC) on **ZX Spectrum TSConfig** with +**MultiSound FPGA** sound card. Written in C (SDCC) + Z80 assembly. + +**Version:** v0.9-beta + +Исходный проект плагина: [demyanenko-d/VGMPLAY.WMF](https://github.com/demyanenko-d/VGMPLAY.WMF). +Автор исходного кода — GitHub-пользователь [demyanenko-d](https://github.com/demyanenko-d). + +## Supported Sound Chips + +| Chip | VGM cmd | Notes | +|------|---------|-------| +| **OPL1** (YM3526) | `0x5A`/`0x5B` | Через MultiSound OPL3 (compat mode) | +| **OPL2** (YM3812) | `0x5A`/`0x5B` | Через MultiSound OPL3 (compat mode) | +| **OPL3** (YMF262) | `0x5E`/`0x5F` | NEW=1, стерео L/R через C0-C8 | +| **YMF278B** (OPL4) | `0xD0` | FM (порты 0/1) через OPL3 #C4-#C7; wave-часть (порт 2) через #7E/#7F (ZXM-MoonSound) | +| **AY-3-8910 / YM2149** | `0xA0` | Single + dual chip | +| **YM2203** (OPN) | `0x55`/`0xA0` | SSG-часть через AY | +| **SAA1099** | `0xBD` | Single + dual chip (bit7 chip select) | + +**Форматы:** VGM (raw), VGZ (gzip — распаковка через inline inflate). + +## Features + +- **GD3 metadata** — отображение названия трека, игры, автора, системы +- **Двойная буферизация** — ISR 683 Hz проигрывает команды, main loop наполняет буферы +- **Спектроанализатор** — 16-полосный в реальном времени с плавным затуханием +- **OPL4** — воспроизведение FM- и wave-частей YMF278B на ZXM-MoonSound +- **Progress bar** — текущее время / общая длительность + счётчик loop +- **VGZ inflate** — распаковка gzip прямо в память с progress bar +- **Dual chip** — поддержка двухчиповых VGM (AY×2, SAA×2, YM2203×2) +- **SAA chip select** — переключение chip0/chip1 клавишами 1/2 +- **Cmdblocks** — предрассчитанные блоки инициализации/silence в SRAM +- **PS/2 клавиатура** — прямой доступ через CMOS FIFO TSConfig (без WC API) +- **Варианты сборки** — build_variants.ps1 генерирует множество WMF с разными ISR freq / budget + +### OPL4 и спектроанализатор + +Для OPL4 анализатор отслеживает `KeyOn`/`KeyOff` 24 wave-каналов. Каналы 0–15 +соответствуют 16 полосам, а каналы 16–23 объединяются с полосами 0–7. При новом +KeyOn полоса поднимается до максимума, затем плавно опускается до уровня 3, пока +канал активен; после KeyOff она затухает до нуля. Это индикатор активности +каналов, а не измеритель фактической амплитуды PCM-сэмплов. + +## Управление (PS/2 клавиатура) + +| Клавиша | Действие | +|---------|----------| +| **Space** | Следующий трек | +| **P** | Предыдущий трек | +| **Esc** | Выход из плагина | +| **1** | SAA режим 1 — chip0 (левый SAA1099) | +| **2** | SAA режим 2 — chip1 (правый SAA1099) | + +> Клавиши 1/2 активны только для dual-SAA музыки на одночиповой плате (`saa_chips=1`). +> На двухчиповой плате (`saa_chips=2`) режим выбирается автоматически. + +## Настройки (wc.ini) + +Параметры задаются в конфигурации WC: + +```ini +VGMPLAY.WMF -min_dur=10 -max_dur=4 -loops=1 -saa=1 -saa_chips=1 +``` + +| # | Параметр | Default | Описание | +|---|----------|---------|----------| +| 0 | `min_dur` | 10 | Минимальная длительность трека (секунды) | +| 1 | `max_dur` | 4 | Максимальная длительность (минуты) | +| 2 | `loops` | 1 | Количество проигрываний loop-секции | +| 3 | `saa` | 1 | SAA режим: **1** = chip0, **2** = chip1 | +| 4 | `saa_chips` | 1 | Конфигурация платы: **1** = один SAA (default), **2** = два SAA | + +### Логика SAA режимов + +- **`saa_chips=1`** (одночиповая плата, default): + - Single-SAA музыка → всегда играет на chip0 + - Dual-SAA музыка → по умолчанию chip0; переключение клавишами 1/2 +- **`saa_chips=2`** (двухчиповая плата): + - Single-SAA музыка → только chip0 + - Dual-SAA музыка → turbo mode (оба чипа одновременно), переключение недоступно + +## Сборка + +### Требования + +- **SDCC** — компилятор C для Z80 (sdcc, sdasz80, sdld) +- **sjasmplus** — ассемблер для cmdblocks и inflate +- **Node.js** — скрипты генерации (pos_table, freq_tables, ihx2wmf) + +### Основная сборка + +```bat +cd src_sdcc +build.bat +``` + +Результат: `VGMPLAY.WMF` в корне проекта + `tsconf.img` (образ диска). + +### Варианты сборки + +```powershell +cd src_sdcc +.\build_variants.ps1 +``` + +Генерирует все комбинации ISR freq / budget / no-short-waits в папку `variants/`. + +Параметры варианта задаёт единственный файл `inc/variant_cfg.h`: + +| Параметр | Описание | +|----------|----------| +| `ISR_FREQ` | Частота ISR (683 / 1367 / 2734 Hz) | +| `VGM_FILL_CMD_BUDGET` | Макс. VGM-команд между yield (8..64) | +| `VARIANT_DATA_LOC` | Адрес начала сегмента _DATA (hex) | +| `VGM_NO_SHORT_WAITS` | Отключить обработку пауз 0x70-0x7F | + +## Структура проекта + +``` +src_sdcc/ Исходный код (C + ASM) + main.c Главный модуль: UI, playback loop, INI + lib/vgm.c VGM парсер, fill_buffer, emit handlers + lib/keys.c PS/2 клавиатура (FSM, CMOS FIFO) + asm/isr.s ISR: двойная буферизация, pos_table + asm/spectrum.s Спектроанализатор (рендер + decay) + asm/cmdblocks.asm Блоки init/silence для всех чипов + asm/inflate_call.s Обёртка inflate для VGZ + inc/ Заголовочные файлы + scripts/ Node.js утилиты сборки + build/ Временные артефакты +variants/ Предсобранные WMF для A/B тестирования +DiskRef/ Эталонные файлы диска, тестовая музыка +Ref/ Документация чипов, SDK, FPGA reference +doc/ Описание WC Plugin API, VGM формата +SamFlate/ Z80 inflate (распаковка gzip) +tools/ sjasm, Unreal emulator +``` + +## Аппаратные требования + +- **ZX Spectrum TSConfig** (ZX Evolution с TSConf FPGA) +- **Wild Commander** (ОС/файловый менеджер) +- **MultiSound FPGA** — звуковая карта (OPL3 + AY + SAA1099) +- **ZXM-MoonSound** (YMF278B) — требуется для воспроизведения OPL4 wave-части +- CPU 14 MHz (turbo mode, автоматически включается плагином) +- PS/2 клавиатура + +## Лицензия + +Проект предназначен для использования с аппаратной платформой TSConfig. diff --git a/Ref/top.v b/Ref/top.v new file mode 100644 index 0000000..a0b517a --- /dev/null +++ b/Ref/top.v @@ -0,0 +1,404 @@ +module zx_multisound( + input rst_n, + input clk32, + input clkx, + + input [4:0] cfg, + + input [15:0] a, + inout [7:0] d, + input n_rd, + input n_wr, + input n_iorq, + input n_mreq, + input n_m1, + output n_wait, + output n_iorqge, + + input n_dos, + input n_iodos, + + output aa0, + inout [7:0] ad, + output n_rstout, + output n_ard, + output n_awr, + output ym_m, + output n_ym1_cs, + output n_ym2_cs, + output reg fm1_ena, + output reg fm2_ena, + output n_saa_cs, + output n_saa2_cs, // turbo saa + output saa_clk, + output saa2_clk, // turbo saa + output midi_clk, + + input [15:0] ga, + inout [7:0] gd, + output n_grst, + output gclk, + output reg n_gint, + input n_grd, + input n_gwr, + input n_gm1, + input n_gmreq, + input n_giorq, + output n_grom, + output n_gram1, + output n_gram2, + output n_gram3, + output n_gram4, + output [18:15] gma, + + output dac0_out, + output dac1_out, + output dac2_out, + output dac3_out +); + +assign n_rstout = rst_n; + +// n_iorq are useless in zxevo :( +// so we're detecting n_iorq cycle by n_rd/n_wr signal asserted without n_m1/n_mreq +reg ioreq, ioreq_prev; +always @(negedge clk32) begin + ioreq_prev <= ioreq; + // ioreq <= n_iorq == 1'b0 && n_m1 == 1'b1 && n_dos == 1'b1 && n_iodos == 1'b1; + ioreq <= n_m1 == 1'b1 && n_mreq == 1'b1 && (n_rd == 1'b0 || n_wr == 1'b0); +end +wire ioreq_rd = ioreq && n_rd == 1'b0; +wire ioreq_wr = ioreq && n_wr == 1'b0; + +// n_dos are useless in zxevo :( +// so we're just lock some ports access when instruction has been fetched from rom +reg rom_m1_access; +always @(negedge clk32 or negedge rst_n) begin + if (!rst_n) + rom_m1_access <= 0; + else if (n_m1 == 0) + rom_m1_access <= a[15:14] == 2'b00; +end + + + +/* CONFIGURATION */ +wire ym_ena = cfg[0]; +wire saa_ena = cfg[1]; +wire gs_ena = cfg[2]; +wire sd_ena = cfg[3]; + + +/* CLOCKS */ +reg [5:0] clk3_5_cnt = 0; +reg [1:0] clk8_cnt = 0; +reg [2:0] clk12_cnt = 0; +always @(posedge clk32) clk3_5_cnt <= clk3_5_cnt + 6'd7; +always @(posedge clk32) clk8_cnt <= clk8_cnt + 1'b1; +always @(posedge clk32) clk12_cnt <= clk12_cnt + 3'd3; +wire clk3_5 = clk3_5_cnt[5]; +wire clk8 = clk8_cnt[1]; +wire clk12 = clk12_cnt[2]; +wire clk16 = clk8_cnt[0]; + + +/* TURBO SOUND FM */ +wire port_bffd = a[15:14] == 2'b10 && a[3:0] == 4'b1101 && ym_ena; +wire port_fffd = a[15:14] == 2'b11 && a[3:0] == 4'b1101 && ym_ena; +wire port_fffd_full = a[15:13] == 3'b111 && a[3:0] == 4'b1101 && ym_ena; // required for compatibility with #dffd port +reg ym_chip_sel, ym_get_stat; +wire ym_a0 = (~n_rd & a[14] & ~ym_get_stat) | (~n_wr & ~a[14]); +assign n_ym1_cs = ~(~ym_chip_sel && (port_bffd || port_fffd)); +assign n_ym2_cs = ~( ym_chip_sel && (port_bffd || port_fffd)); + +always @(posedge clk32 or negedge rst_n) begin + if (!rst_n) begin + ym_chip_sel <= 0; + ym_get_stat <= 0; + fm1_ena <= 0; + fm2_ena <= 0; + end + else if (port_fffd && ioreq_wr && d[7:4] == 4'b1111) begin + ym_chip_sel <= d[0]; + ym_get_stat <= ~d[1]; + fm1_ena <= d[2]? 1'b0 : 1'bz; + fm2_ena <= d[2]? 1'b0 : 1'bz; + end +end + +assign ym_m = clk3_5; + + +/* SAA1099 — dual chip via bit7 of address write */ +wire port_ff = a[7:0] == 8'hFF && saa_ena && !rom_m1_access; +wire saa_a0 = a[8]; + +/* Chip select: during address write (saa_a0=1), d[7] selects chip directly; + * during data write (saa_a0=0), latched saa_chip is used. + * SAA1099 ignores upper address bits, so bit7 passes harmlessly. */ +reg saa_chip; +reg saa_reg_is_1c; +wire saa_chip_eff = saa_a0 ? d[7] : saa_chip; + +assign n_saa_cs = ~(port_ff && ioreq_wr && saa_chip_eff == 1'b0); +assign n_saa2_cs = ~(port_ff && ioreq_wr && saa_chip_eff == 1'b1); + +/* saa clock control */ +wire port_fffd_saa = a[15:14] == 2'b11 && a[3:0] == 4'b1101 && saa_ena; +reg saa_clk_en; +reg saa2_clk_en; + +always @(posedge clk32 or negedge rst_n) begin + if (!rst_n) begin + saa_clk_en <= 0; + saa2_clk_en <= 0; + saa_chip <= 0; + saa_reg_is_1c <= 0; + end + else if (port_fffd_saa && ioreq_wr && d[7:4] == 4'b1111) begin + saa_clk_en <= ~d[3]; + if (d[3]) saa2_clk_en <= 1'b0; + end + else if (port_ff && ioreq_wr && saa_a0) begin + /* Address write: latch chip select & register id */ + saa_chip <= d[7]; + saa_reg_is_1c <= (d[6:0] == 7'h1C); + end + else if (port_ff && ioreq_wr && !saa_a0 && saa_chip && saa_reg_is_1c && d[0]) begin + /* Data write to chip 2 reg 0x1C with d[0]=1 (Sound Enable): + * enable saa2 clock. d[0]=0 (e.g. 0x02 reset) does NOT enable. + * Disable only via FFFD d[3]=1. */ + saa2_clk_en <= 1'b1; + end +end + +assign saa_clk = saa_clk_en ? clk8 : 1'b0; +assign saa2_clk = saa2_clk_en ? clk8 : 1'b0; + + +/* MIDI */ +assign midi_clk = clk12; + + +/* GENERAL SOUND */ +assign gclk = clk16; +assign n_grst = n_rstout; + +reg gioreq, gioreq_prev; +always @(posedge clk32) begin + gioreq_prev <= gioreq; + gioreq <= n_giorq == 1'b0 && n_gm1 == 1'b1; +end + +reg [8:0] g_int_cnt; +wire g_int_reload = g_int_cnt[8:6] == 4'b101; +always @(posedge clk12 or negedge rst_n) begin + if (!rst_n) begin + g_int_cnt <= 0; + n_gint <= 1'b1; + end + else begin + if (g_int_reload) + g_int_cnt <= 0; + else + g_int_cnt <= g_int_cnt + 1'b1; + + if (g_int_reload) + n_gint <= 1'b0; + else if (g_int_cnt[5]) + n_gint <= 1'b1; + end +end + +/* GS EXTERNAL REGISTERS */ +reg [7:0] gs_regdata, gs_regcmd; +wire port_b3 = a[7:0] == 8'hB3 && gs_ena; +wire port_bb = a[7:0] == 8'hBB && gs_ena; +always @(posedge clk32 or negedge rst_n) begin + if (!rst_n) begin + gs_regdata <= 0; + gs_regcmd <= 0; + end + else begin + if (port_b3 && ioreq_wr) + gs_regdata <= d; + if (port_bb && ioreq_wr) + gs_regcmd <= d; + end +end + +/* GS INTERNAL REGISTERS */ +reg [7:0] gs_reg00, gs_reg_out; +wire [6:0] gs_page = gs_reg00[6:0]; +always @(posedge clk32 or negedge rst_n) begin + if (!rst_n) begin + gs_reg00 <= 0; + gs_reg_out <= 0; + end + else if (~n_giorq && ~n_gwr) begin + if (ga[3:0] == 4'h0) gs_reg00 <= gd; + if (ga[3:0] == 4'h3) gs_reg_out <= gd; + end +end + +/* GS DAC REGISTERS */ +reg gs_vol0_cs; always @(posedge clk32) gs_vol0_cs = ~n_giorq && ga[3:0] == 4'h6; +reg gs_vol1_cs; always @(posedge clk32) gs_vol1_cs = ~n_giorq && ga[3:0] == 4'h7; +reg gs_vol2_cs; always @(posedge clk32) gs_vol2_cs = ~n_giorq && ga[3:0] == 4'h8; +reg gs_vol3_cs; always @(posedge clk32) gs_vol3_cs = ~n_giorq && ga[3:0] == 4'h9; +reg gs_dac0_cs; always @(posedge clk32) gs_dac0_cs = ~n_gmreq && ga[15:13] == 3'b011 && ga[9:8] == 2'd0; +reg gs_dac1_cs; always @(posedge clk32) gs_dac1_cs = ~n_gmreq && ga[15:13] == 3'b011 && ga[9:8] == 2'd1; +reg gs_dac2_cs; always @(posedge clk32) gs_dac2_cs = ~n_gmreq && ga[15:13] == 3'b011 && ga[9:8] == 2'd2; +reg gs_dac3_cs; always @(posedge clk32) gs_dac3_cs = ~n_gmreq && ga[15:13] == 3'b011 && ga[9:8] == 2'd3; +wire gs_vol0_wr = gs_vol0_cs && ~n_gwr; +wire gs_vol1_wr = gs_vol1_cs && ~n_gwr; +wire gs_vol2_wr = gs_vol2_cs && ~n_gwr; +wire gs_vol3_wr = gs_vol3_cs && ~n_gwr; +wire gs_dac0_wr = gs_dac0_cs && ~n_grd; +wire gs_dac1_wr = gs_dac1_cs && ~n_grd; +wire gs_dac2_wr = gs_dac2_cs && ~n_grd; +wire gs_dac3_wr = gs_dac3_cs && ~n_grd; + +/* GS STATUS REGISTER */ +reg gs_flag_cmd, gs_flag_data; +wire [7:0] gs_status = {gs_flag_data, 6'b111111, gs_flag_cmd}; + +always @(posedge clk32 or negedge rst_n) begin + if (!rst_n) + gs_flag_data <= 1'b0; + else if (ioreq_rd && !ioreq_prev && port_b3) + gs_flag_data <= 1'b0; + else if (ioreq_wr && !ioreq_prev && port_b3) + gs_flag_data <= 1'b1; + else if (gioreq && !gioreq_prev && ga[3:0] == 4'h2) + gs_flag_data <= 1'b0; + else if (gioreq && !gioreq_prev && ga[3:0] == 4'h3) + gs_flag_data <= 1'b1; + else if (gioreq && !gioreq_prev && ga[3:0] == 4'hA) + gs_flag_data <= ~gs_reg00[0]; +end + +always @(posedge clk32 or negedge rst_n) begin + if (!rst_n) + gs_flag_cmd <= 1'b0; + else if (ioreq_wr && !ioreq_prev && port_bb) + gs_flag_cmd <= 1'b1; + else if (gioreq && !gioreq_prev && ga[3:0] == 4'h5) + gs_flag_cmd <= 1'b0; + else if (gioreq && !gioreq_prev && ga[3:0] == 4'hB) + gs_flag_cmd <= vol3[5]; +end + +/* GS BUS CONTROLLER */ +assign n_grom = (~n_gmreq && ((ga[15:14] == 2'b00) || (ga[15] && gs_page == 0)))? 1'b0 : 1'b1; +`ifdef GS_RAM_2MB + assign n_gram1 = (~n_gmreq && n_grom && ((gs_page[5:4] == 2'd0) || ~ga[15]))? 1'b0 : 1'b1; + assign n_gram2 = (~n_gmreq && n_grom && (gs_page[5:4] == 2'd1) && ga[15] )? 1'b0 : 1'b1; + assign n_gram3 = (~n_gmreq && n_grom && (gs_page[5:4] == 2'd2) && ga[15] )? 1'b0 : 1'b1; + assign n_gram4 = (~n_gmreq && n_grom && (gs_page[5:4] == 2'd3) && ga[15] )? 1'b0 : 1'b1; +`else + assign n_gram1 = (~n_gmreq && n_grom && (~gs_page[4] || ~ga[15]))? 1'b0 : 1'b1; + assign n_gram2 = (~n_gmreq && n_grom && gs_page[4] && ga[15] )? 1'b0 : 1'b1; + assign n_gram3 = 1'b1; + assign n_gram4 = 1'b1; +`endif +assign gma = (ga[15] == 1'b0)? 4'b0001 : gs_page[3:0]; +assign gd = + (~n_giorq && ~n_grd && ga[3:0] == 4'h4)? gs_status : + (~n_giorq && ~n_grd && ga[3:0] == 4'h2)? gs_regdata : + (~n_giorq && ~n_grd && ga[3:0] == 4'h1)? gs_regcmd : + (~n_giorq && (~n_grd || ~n_gm1))? {8{1'b1}} : + {8{1'bz}} ; + + +/* SOUNDRIVE */ +wire port_xf = sd_ena && a[7] == 1'b0 && a[5] == 1'b0 && a[3:0] == 4'hF && !rom_m1_access; +wire [1:0] port_xf_chn = {a[6],a[4]}; +reg sd_dac0_cs; always @(posedge clk32) sd_dac0_cs = ioreq && port_xf && port_xf_chn == 2'd0; +reg sd_dac1_cs; always @(posedge clk32) sd_dac1_cs = ioreq && port_xf && port_xf_chn == 2'd1; +reg sd_dac2_cs; always @(posedge clk32) sd_dac2_cs = ioreq && port_xf && port_xf_chn == 2'd2; +reg sd_dac3_cs; always @(posedge clk32) sd_dac3_cs = ioreq && port_xf && port_xf_chn == 2'd3; +wire sd_dac0_wr = sd_dac0_cs && ~n_wr; +wire sd_dac1_wr = sd_dac1_cs && ~n_wr; +wire sd_dac2_wr = sd_dac2_cs && ~n_wr; +wire sd_dac3_wr = sd_dac3_cs && ~n_wr; + + +/* DAC */ +reg [5:0] vol0, vol1, vol2, vol3; +always @(posedge clk32 or negedge rst_n) begin + if (!rst_n) begin + vol0 <= 0; + vol1 <= 0; + vol2 <= 0; + vol3 <= 0; + end + else begin + if (sd_dac0_wr) vol0 <= 6'b111111; + else if (gs_vol0_wr) vol0 <= gd[5:0]; + if (sd_dac1_wr) vol1 <= 6'b111111; + else if (gs_vol1_wr) vol1 <= gd[5:0]; + if (sd_dac2_wr) vol2 <= 6'b111111; + else if (gs_vol2_wr) vol2 <= gd[5:0]; + if (sd_dac3_wr) vol3 <= 6'b111111; + else if (gs_vol3_wr) vol3 <= gd[5:0]; + end +end + +reg [7:0] dac0, dac1, dac2, dac3; +always @(posedge clk32 or negedge rst_n) begin + if (!rst_n) begin + dac0 <= 0; + dac1 <= 0; + dac2 <= 0; + dac3 <= 0; + end + else begin + // quartus bug(?): without second condition inside "IF" expression incorrect design may be generated + if (sd_dac0_wr && !gs_dac0_wr) dac0 <= ( d[7]? d : { d[7], ~d[6:0]}); + else if (gs_dac0_wr) dac0 <= (gd[7]? gd : {gd[7],~gd[6:0]}); + if (sd_dac1_wr && !gs_dac1_wr) dac1 <= ( d[7]? d : { d[7], ~d[6:0]}); + else if (gs_dac1_wr) dac1 <= (gd[7]? gd : {gd[7],~gd[6:0]}); + if (sd_dac2_wr && !gs_dac2_wr) dac2 <= ( d[7]? d : { d[7], ~d[6:0]}); + else if (gs_dac2_wr) dac2 <= (gd[7]? gd : {gd[7],~gd[6:0]}); + if (sd_dac3_wr && !gs_dac3_wr) dac3 <= ( d[7]? d : { d[7], ~d[6:0]}); + else if (gs_dac3_wr) dac3 <= (gd[7]? gd : {gd[7],~gd[6:0]}); + end +end + +reg vol0_en, vol1_en, vol2_en, vol3_en; +reg [5:0] vol_cnt; +reg [7:0] dac0_cnt, dac1_cnt, dac2_cnt, dac3_cnt; +assign dac0_out = dac0_cnt[7]? dac0[7] : clk32; +assign dac1_out = dac1_cnt[7]? dac1[7] : clk32; +assign dac2_out = dac2_cnt[7]? dac2[7] : clk32; +assign dac3_out = dac3_cnt[7]? dac3[7] : clk32; +always @(posedge clk32) begin + vol_cnt <= vol_cnt + 6'd31; + vol0_en <= (vol_cnt < vol0) || (&vol0); + vol1_en <= (vol_cnt < vol1) || (&vol1); + vol2_en <= (vol_cnt < vol2) || (&vol2); + vol3_en <= (vol_cnt < vol3) || (&vol3); + if (vol0_en) dac0_cnt <= dac0_cnt[6:0] + dac0[6:0]; else dac0_cnt[7] <= 0; + if (vol1_en) dac1_cnt <= dac1_cnt[6:0] + dac1[6:0]; else dac1_cnt[7] <= 0; + if (vol2_en) dac2_cnt <= dac2_cnt[6:0] + dac2[6:0]; else dac2_cnt[7] <= 0; + if (vol3_en) dac3_cnt <= dac3_cnt[6:0] + dac3[6:0]; else dac3_cnt[7] <= 0; +end + + +/* BUS CONTROLLER */ +assign n_ard = ~ioreq_rd; +assign n_awr = ~ioreq_wr; +assign aa0 = a[1]? saa_a0 : ym_a0 ; +assign ad = ioreq_wr && (port_fffd || port_bffd || port_ff)? d : 8'bzzzzzzzz; + +assign n_wait = 1'bz; +assign n_iorqge = (n_m1 && (port_fffd_full || port_bffd || port_b3 || port_bb))? 1'b0 : 1'b1; +assign d = + ioreq_rd && port_fffd? ad : + ioreq_rd && port_b3? gs_reg_out : + ioreq_rd && port_bb? gs_status : + 8'bzzzzzzzz; + + +endmodule diff --git a/SamFlate/Makefile b/SamFlate/Makefile new file mode 100644 index 0000000..c5906d7 --- /dev/null +++ b/SamFlate/Makefile @@ -0,0 +1,12 @@ +pyz80 = /Users/ascii/unix/src/pyz80/sourceforge/pyz80/pyz80.py + +samflate.dsk: samflate.z80s samflatelib.z80s + $(pyz80) -o samflate.dsk samflate.z80s + +clean: + rm samflate.dsk + +run: samflate.dsk + open $< + + diff --git a/SamFlate/samflate.z80s b/SamFlate/samflate.z80s new file mode 100644 index 0000000..3e8c912 --- /dev/null +++ b/SamFlate/samflate.z80s @@ -0,0 +1,363 @@ +; samflate :: samflate.z80s by Andrew Collier (C) 2009 +; an implementation of the Inflate decompression algorithm +; corresponding to deflate compression as used by gzip +; and compatible with RFC 1951 + +; # You may use and distribute this code under any DFSG-compatible +; # license (eg. BSD, GNU GPLv2). + +; The latest version of this file will be kept available at the sourceforge project, +; "samflate" + +; This is a basic interface for the inflate decoder +; It sets up the necessary memory conditions and calls the inflate code +; (the algorithm implementation is in inflatelib.z80s) + +; It also arranges the screen effect during decompression +; (the screen is switched off most of the time, to improve speed) + +; Other programs using inflate decompression internally will probably not need +; to use much of this file, but you are welcome to use it under the same +; license terms should you wish to. + + +LMPR: EQU 250 +HMPR: EQU 251 +VMPR: EQU 252 +BORDER: EQU 254 +CLUT: EQU 248 +LINE: EQU 249 +STATUS: EQU 249 + +START_OF_PROGRAM: equ 24576 + 32768; after a mode4 screen + a few extra bytes +PAGE_OF_PROGRAM: equ 24 + + + +; in fact, we're usually called in section C + ; should attempt to confirm this by checking BC + +ENTRY_POINT: equ (START_OF_PROGRAM) \ 16384 + 32768 + + + +ORG ENTRY_POINT +DUMP PAGE_OF_PROGRAM, ENTRY_POINT \ 16384 + + + +; Interface to SAM BASIC +; CALL samflate_addr, FROM_ADDRESS, TO_ADDRESS +; where addresses are float types consisting of page and offset in basic's combined format + +; On entry, A contains the number of parameters +; parameters themselves and type information have been placed on the FPC stack + + cp 2 + jp nz, parameter_error + + + rst 0x28 ; call floating point calculator, stack = FR,t,TO,t + + db 0x1d ; SWOP23 ; FR,TO,t,t + db 0x33 ; EXIT + + ; check type information + call JGETINT + cp 0xc0 + jr nz, parameter_error + call JGETINT + cp 0xc0 + jr nz, parameter_error + + + rst 0x28 ; call floating point calculator, stack = FR,TO + + db 0x25 ; DUP ; FR,TO,TO + db 0xe2 ; STK16K ; FR,TO,TO,16K + db 0x08 ; MOD ; FR,TO,TO\16K + db 0x06 ; SWOP ; FR,TO\16K,TO + db 0xe2 ; STK16K ; FR,TO\16K,TO,16K + db 0x09 ; IDIV ; FR,TO\16K,TO/16K + + db 0x1c ; SWOP13 ; TO/16K,TO\16K,FR + + db 0x25 ; DUP ; TO/16K,TO\16K,FR,FR + db 0xe2 ; STK16K ; TO/16K,TO\16K,FR,FR,16K + db 0x08 ; MOD ; TO/16K,TO\16K,FR,FR\16K + db 0x06 ; SWOP ; TO/16K,TO\16K,FR\16K,FR + db 0xe2 ; STK16K ; TO/16K,TO\16K,FR\16K,FR,16K + db 0x09 ; IDIV ; TO/16K,TO\16K,FR\16K,FR/16K + + db 0x06 ; SWOP ; TO/16K,TO\16K,FR/16K,FR\16K + + db 0x33 ; EXIT + + +;JGETINT +;(0121H) +; +;Unstack number from calculator stack into HL. BC holds +;a copy of HL, and A holds a copy of L. An error is +;generated if the rounded number is not in the range 0- +;65535. + +JGETINT: EQU 0x0121 + + call JGETINT + ld ((START_OF_GZDATA \ 16384) + 32768),hl + + call JGETINT + ld a,h + or a + jr nz, invalid_argument_error + ld a,l + dec a + cp 32 + jr nc, invalid_argument_error + ld ((PAGE_OF_GZDATA \ 16384) + 32768),a + + + call JGETINT + ld ((START_OF_DESTINATION \ 16384) + 32768),hl + + call JGETINT + ld a,h + or a + jr nz, invalid_argument_error + ld a,l + dec a + cp 32 + jr nc, invalid_argument_error + ld ((PAGE_OF_DESTINATION \ 16384) + 32768),a + + + di + + ; page ourselves into section D via secion A + + in a,(vmpr) + ld ((vmprs\16384) + 32768),a + + in a,(lmpr) + ld ((lmprs\16384) + 32768),a + + ld ((sps\16384) + 32768),sp + + in a,(hmpr) + ld ((hmprs\16384) + 32768),a + and 31 + or 32 + out (lmpr),a + jp lmem + + +invalid_argument_error: + rst 8 + db 27 + +parameter_error: + rst 8 + db 26 + + + + + +xhmem: + +lmprs: equ $+1 + ld a,00 + out (lmpr),a + +sps: equ $+1 + ld sp,0000 + +vmprs: equ $+1 + ld a,00 + out (vmpr),a + + ld a,0x08 + out (border),a + + ld a,255 + out (line),a + + im 0 + ei + +resultcode: equ $+1 + ld a,00 + or a + ret z + + rst 8 + db 19 ; loading error + + + + + + + + + + +org ($\16384)+49152 + + + + + +hmem: + ; okay, here we are in section D. + + ld sp,0 + + inc a + and 31 + or 32 ; mode 2 + ld (scr_on_vmpr),a + or 96 + ld (scr_off_vmpr),a ; screen "off" only works in modes 3 and 4 + + + + ld a,4 + ld de,(192-4)*32 + 0xe000 +@: ld hl,attrtab + ld bc,32 + ldir + dec a + jr nz, @ + + + ld a,IM2_VALUE_I + ld i,a + ld a,188 + out (line),a + im 2 + ei + + + + +START_OF_GZDATA: equ $+1 + ld hl, 0000 + +PAGE_OF_GZDATA: equ $+1 + ld c, 0 + +START_OF_DESTINATION: equ $+1 + ld de, 0000 + +PAGE_OF_DESTINATION: equ $+1 + ld b, 0 + + + + call inflate + ld ((resultcode\16384) + 49152),a + + ; finishing up, put pages back into initial position +hmprs: equ $+1 + ld a,00 + ld b,a + + and 31 + or 32 + out (lmpr),a + jp xlmem + +attrtab: db 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x40, 0x49, 0x52, 0x5b, 0x64, 0x6d, 0x76, 0x7f + db 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x40, 0x49, 0x52, 0x5b, 0x64, 0x6d, 0x76, 0x7f + db 0x09, 0x12 + + +@: +ds align 256 +IM2_VALUE_I: equ $/256 + for 257, db interrupt_routine/256 + + + org ($\16384) +lmem: + dec a + and 31 + out (hmpr),a + jp hmem + + +xlmem: + ld a,b + out (hmpr),a + jp xhmem + + org ($\16384)+49152 + + + + +@: +ds align 32 +paltab: db 125,93,85,89,83,81,28,28,20,28,81,83,89,85,93 + db 125,93,85,89,83,81,28,28,20,28,81,83,89,85,93 + +frameinterrupt: + +scr_off_vmpr: equ $+1 + ld a,00 + out (vmpr),a + + ld a,0x88 + out (border),a + + push hl + push bc + + ld hl,paltab + ld bc,16*256 + CLUT + + exx + ld a,h ; *usually* the position in the compressed file. But we may be in the alternate set anyway, + ; so randomly this will be a completely different value. Fortunately we use this only for a + ; visual effect of general progress, and nothing at all important depends on the value. + exx + and 15 + add l + ld l,a + + for 15, outi + + pop bc + pop hl + pop af + ei + reti + +@: +DS ALIGN 257 ; yes, 257. + +interrupt_routine: + push af + + ld a,0x08 + out (border),a + +scr_on_vmpr: equ $+1 + ld a,00 + out (vmpr),a ; turn on the screen - we need to do this much quicker than turning it off.. + + in a,(STATUS) + rra + jp c, frameinterrupt + + pop af + ei + reti + + +INCLUDE "samflatelib.z80s" + + + diff --git a/SamFlate/samflatelib.z80s b/SamFlate/samflatelib.z80s new file mode 100644 index 0000000..3e844c0 --- /dev/null +++ b/SamFlate/samflatelib.z80s @@ -0,0 +1,1815 @@ +; samflate :: inflatelib.z80s by Andrew Collier (C) 2009 +; an implementation of the Inflate decompression algorithm +; corresponding to deflate compression as used by gzip +; and compatible with RFC 1951 + +; Although I have not directly translated the code, this implementation +; is based on Paul Sladen's pyflate.py which I used as a reference +; for the compressed file format. Therefore I'm releasing this code +; under the same license terms: +; # You may use and distribute this code under any DFSG-compatible +; # license (eg. BSD, GNU GPLv2). + +; The latest version of this file will be kept available at the sourceforge project, +; "samflate" + +; My intent is that it should not in general be necessary to modify this file +; in order to use gz decompression in general programs +; (not branching the file will make for easier bug-fixing and maintenance) + +; This code must be called in section D, with stack near the top of memory +; * The first 257 bytes of section D might be overwritten! * +; section C will be used as temporary storage + +; Interrupts may be enabled, provided that they IM2 is selected and a +; handler table is included inside section D. +; (If you're wondering why the code isn't in section A, it is necessary (for the dictionary lookups to work) +; that the temporary work page - which would have to be B in that case - immediately follows the rotating memory window) + +; The code runs in section D and uses section C as temporary storage +; (i.e you should allow 32k of storage for operation, +; which cannot overlap with the compressed or uncompressed file. +; If you're careful, the beginning of the compressed file can overlap with the end +; of the uncompressed.) + +; in fact we write the decoded file into C before copying it to the destination page +; we may write up to 257 bytes of section D as well, to do page checks less often +; gz data is generally resident in sections A+B +; this means we do not need to change the paging between every byte + +; moreover we only need to check the encoded page window after overflowing 16k of the decoded file +; (in a 32k window we are guaranteed to be able to see 16k of the compressed file +; wherever the block starts; we know the compressed block is smaller than the decoded block. +; If it were not, it would be stored in the file as block type 0.) + +; we will often want to copy whole pages from C to low memory +; (partial pages at the start or end of files should be done more simply and perhaps more slowly) +; We can optimise copies by using the stack - but need to take care not to corrupt anything if interrupts occur +; we use the stack to write into section A +; - interrupts either write memory in A which will then be written by correct copied data +; or else write into D, which under our own control and can be safe if we don't overwrite +; values from the stack which might otherwise have been in use + +; LIMITATIONS + +; This code does not keep track of a checksum +; Errors in the gz file are not likely to be detected and may cause a crash. + + +inflate: + ; decode a block of gzip data starting at offset HL [of page C *] + ; writing the result starting at offset DE [of page B *] + + ; * unless assembled with INFLATELIB_CONFIG__NO_MEMORY_PAGING defined + ; use this when the decoded data is KNOWN to fit inside free memory + ; e.g. this library can be assembled within the top 8k of section D, + ; and decompressing a MODE 4 screen at DE==32768 can be made to work + + ; returns A=0, carry clear, if decompression appeared to be successful + ; returns A!=0, carry set, if error was detected. + ; (NB. We do not calculate checksum. Most error conditions are not detected and will probably cause a crash.) + + +IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING) + ld a,d + and 0x3f ; section A + ld d,a + ld (dst), de + + or 0x80 ; section C + ld d,a + + + in a,(lmpr) + ld (inflate_lmprs),a + + ld a,b + and 31 + or 32 + ld (dst.page),a + dec a + dec a + ld (dst.page.minustwo),a + + ld a,h + and 0x3f ; section A + ld h,a + + ld a,c + and 31 + or 32 + ld (src.page),a + + out (lmpr),a +ENDIF + ld (interim_dst),de + + + ; by default, the source file will be in sections A/B + ; we write decoded information into section C and copy completed pages into destination memory + + ; for quick and easy access we use alternate registers to store src file pointers + + ld c,(hl) + inc hl + ld b,8 + + ; hl = pointer to next byte + ; c = a partially used byte (shifted, lowest bits are valid - others RAZ) + ; b = number of valid bits in c + + exx + + + + call read8bits + cp 0x1f ; check gzip magic numbers + jp nz, fail + call read8bits + cp 0x8b ; check gzip magic numbers + jp nz, fail + + call read8bits + cp 0x8 ; method - only type 8 (DEFLATE) is implemented + jp nz, fail + + call read8bits + ld d,a ; flags + + ld a,6 ; mtime, extra_flags, os_type + call dropbytes + + ld a,d + and 0x04 + call nz, drop_gz_fextra + + ld a,d ; read a again because any of these functions might have changed it + and 0x08 + call nz, drop_gz_fname + + ld a,d + and 0x10 + call nz, drop_gz_fcomment + + ld a,d + and 0x02 + call nz, drop_gz_fhcrc + + +NEW_BLOCK: + + call read1bit ; last + ld (this_was_last_block),a + + call read2bits ; blocktype + cp 3 + jp z, fail + cp 1 + jp c, blocktype0 + jp z, blocktype1 + jp blocktype2 + + +blocktype0: ; raw block data type + call readalign + call read8bits + ld c,a + call read8bits + ld b,a + + + ; apparently for some reason, the block length is stored twice (and should match) + call read8bits + ld l,a + call read8bits + ld h,a + + or a + sbc hl,bc + ld a,h + or l + jp nz, fail + + + ld de,(interim_dst) + +IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING) + +try_another_page_worth: + ld l,e + ld h,d + add hl,bc + ld a,h + sub 0x80 + cp 0x40 + jr c, fits_in_section + + ; the area to be copied will fill our section C working area + + + ld hl, 49152 + or a + sbc hl,de + + ld a,l + ld l,c + ld c,a + + ld a,h + ld h,b + ld b,a + + or a + sbc hl,bc + ld (remaining_copy_length),hl + + exx + push hl + exx + pop hl ; address of the data to copy from (guaranteeed that 16k is available) + + ldir + + push hl + exx + pop hl ; store the source pointer back in HL' - revisit this in a moment to sort out 16k wrapping + exx + + ; now, copy the filled page to the destination address + + ld a,(dst.page) + out (lmpr),a + + ld de,(dst) + + ld hl,16384 ; number of bytes until the end of this page + or a + sbc hl,de + + ld c,l + ld b,h + + ld l,e + ld a,d + or 0x80 + ld h,a + + ldir + + ld hl,0 + ld (dst),hl + ld a,(dst.page) + inc a + ld (dst.page),a + dec a + dec a + ld (dst.page.minustwo),a + + + ; now, get the gz source back into low memory + exx + bit 6,h + jr z, no_16k_rollover + + ld a,h + and 0x3f + ld h,a + + ld a,(src.page) + inc a + ld (src.page),a + +no_16k_rollover: + exx + + ld a,(src.page) + out (lmpr),a + + ld de,32768 + +remaining_copy_length: equ $+1 + ld bc,0000 + + jp try_another_page_worth + + +fits_in_section: +ENDIF + + exx + push hl + exx + pop hl + ldir + push hl + exx + pop hl ; store the source pointer back in HL' - revisit this in a moment to sort out 16k wrapping + exx + ld (interim_dst),de + + jp END_BLOCK + + +blocktype1: ; static huffman + + ld hl,288 + ld (literals),hl + ld a,32 + ld (distances),a + + ld hl,codelengths + + ld b,144 + ld a,8 + +@: ld (hl),a + inc hl + djnz @- + + ld b,112 + ld a,9 + +@: ld (hl),a + inc hl + djnz @- + + ld b,24 + ld a,7 + +@: ld (hl),a + inc hl + djnz @- + + ld b,8 + ld a,8 + +@: ld (hl),a + inc hl + djnz @- + + jp huffman_common + + +blocktype2: ; dynamic huffman + + call read5bits ; HLIT + inc a + ld l,a + ld h,1 + ld (literals),hl + + call read5bits ; HDIST + inc a + ld (distances),a + + ; the shape of the huffman tree used to compress this block is presented in a huffman-encoded form + ; thus first, we are given the shape of the tree used to describe it. + + call read4bits + add 4 + ld (code_lengths_length),a + + + ld hl,codelengths + ld b,20 + xor a + +clear_codelengths_loop: + ld (hl),a + inc hl + djnz clear_codelengths_loop + + ld de, code_length_orders + ld h,codelengths/256 + +code_lengths_length: equ $+1 + ld b,00 + +code_lengths_loop: + ld a,(de) + add codelengths\256 + ld l,a + call read3bits + ld (hl),a + inc de + djnz code_lengths_loop + + + ; create a huffman tree we can use from these code lengths + + ld bc,19 ; (not code_lengths_length... total number of symbols, including those which may not have been described) + ld hl,codelengths + ld de,Huffman_Tables_Start + + call makeHuffmanTable + + + ; now, decode this to find the shape of the "real" huffman table + + ; first tree contains literals/lengths entries, second contains distances entries + + + + ld hl,Huffman_Tables_Start + ld de,codelengths +literals: equ $+1 + ld bc,0000 + + ld a,(distances) + add c + ld c,a + + ld a,b + adc 0 + ld b,a + + + call decodeProtoHuffman + + + +huffman_common: + ;; now we can create huffman tables from these code lengths + + ld bc,(literals) + ld hl, codelengths + ld de,Huffman_Tables_Start + + call makeHuffmanTable + + ld de,(next_huffman_table_store) + ld (Distance_Huffman_Tables_Start),de + + ld hl, codelengths + ld bc,(literals) + add hl,bc +distances: equ $+1 + ld c,00 + ld b,0 + + call makeHuffmanTable + + + + ; now we can decode the actual data from the file! + +interim_dst: equ $+1 + ld de,32768 ; not true! + + +Distance_Huffman_Tables_Start: equ $+1 + ld hl,0000 + + ld a,l + rra + rra + ld l,a + + ld (Distance_HuffmanTableBegin),hl + + + ld hl, Huffman_Tables_Start + + ld a,l + rra + rra + ld l,a + + ld (HuffmanTableBegin),hl + + +decode_tableloop: + + exx + ld a,b + exx + add a + add a + ld (decode_tableloop_jr_offset),a + + + +HuffmanTableBegin: equ $+1 + ld hl,0000 + ld a,l + +decode_tableloop_jr_offset: equ $+1 + jr $+2 + jp fail + nop + jp decode_tableloop_unrolled_0 + nop + jp decode_tableloop_unrolled_7 + nop + jp decode_tableloop_unrolled_6 + nop + jp decode_tableloop_unrolled_5 + nop + jp decode_tableloop_unrolled_4 + nop + jp decode_tableloop_unrolled_3 + nop + jp decode_tableloop_unrolled_2 + nop + jp decode_tableloop_unrolled_1 + + +UNROLL: equ FOR 8 + +decode_tableloop_unrolled_{UNROLL}: + ; as read1bit, in-lined (can also unroll loop and Duff on b') - returns bit in CARRY FLAG ONLY + + exx +if (UNROLL > 0) + dec b + or a + rr c +ELSE + or a + rr c + + ld b,8 + ld c,(hl) + inc hl +ENDIF + exx + + + adc a + add a + ld l,a + + ld a,(hl) + bit 7,a + jp z, decoded_symbol + + inc hl + ld h,(hl) + +NEXT UNROLL + jp decode_tableloop_unrolled_0 + +decoded_symbol: + or a + jr nz, decode_special + + + inc hl + ld a,(hl) ; this is ONLY so we can see value in the debugger + ldi ; copy a byte from hl to de, inc de [we don't care about hl or bc, but ldi is still faster] + +IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING) + bit 6,D + jp nz, copy_decoded_page_to_destination + +ENDIF + + jp decode_tableloop + + +end_of_huffman_block: + + + ld (interim_dst),de + jp END_BLOCK + + +decode_special: + inc hl + ld a,(hl) + or a + jr z, end_of_huffman_block + + +; thus we have decoded a length code +; a few more bits *may* be used to work out exactly how many bytes are repeated + +; then a decode from the distance tree will tell us where the sequence is copied from + + + + ld hl, lengthcodetable - 4 ; because code offsets start at 1 not 0 + add a + add a + add l + ld l,a + ld a,h + adc 0 + ld h,a + + ld c,(hl) + inc hl + ld b,(hl) + inc hl + ld a,(hl) + inc hl + ld h,(hl) + ld l,a + ld (@calladdress),hl + +@calladdress: equ $+1 + call 0000 + + add c + ld c,a + ld a,b + adc 0 + ld b,a + + ; dictionary lookup length is max 258 bytes + ; we can copy the whole thing without checking for page flip if we specify that the + ; first 257 bytes of section D may be overwritten + ; this means that after copying a page we always jump to a new symbol + ; which simplifies things (i.e. never into the middle of a copy) + + + + + +distance_decode_tableloop: + + exx + ld a,b + exx + add a + add a + ld (distance_decode_tableloop_jr_offset),a + + + +Distance_HuffmanTableBegin: equ $+1 + ld hl,0000 + ld a,l + +distance_decode_tableloop_jr_offset: equ $+1 + jr $+2 + jp fail + nop + jp distance_decode_tableloop_unrolled_0 + nop + jp distance_decode_tableloop_unrolled_7 + nop + jp distance_decode_tableloop_unrolled_6 + nop + jp distance_decode_tableloop_unrolled_5 + nop + jp distance_decode_tableloop_unrolled_4 + nop + jp distance_decode_tableloop_unrolled_3 + nop + jp distance_decode_tableloop_unrolled_2 + nop + jp distance_decode_tableloop_unrolled_1 + + +UNROLL: equ FOR 8 + +distance_decode_tableloop_unrolled_{UNROLL}: + ; as read1bit, in-lined (can also unroll loop and Duff on b') - returns bit in CARRY FLAG ONLY + + exx +if (UNROLL > 0) + dec b + or a + rr c +ELSE + or a + rr c + + ld b,8 + ld c,(hl) + inc hl +ENDIF + exx + + + adc a + add a + ld l,a + + ld a,(hl) + bit 7,a + jp z, distance_decoded_symbol + + inc hl + ld h,(hl) + +NEXT UNROLL + jp distance_decode_tableloop_unrolled_0 + + +distance_decoded_symbol: + + inc hl + ld a,(hl) + + add a + ld l,a ; l = 2x original a + add a ; a = 4x orginal a + add l ; + + + + ld hl, distancecodetable + add l + ld l,a + ld a,h + adc 0 + ld h,a + + + push de + + + ld e,(hl) + inc hl + ld d,(hl) + inc hl + + push de + + ld e,(hl) + inc hl + ld d,(hl) + inc hl + ld (@calladdress1),de + + ld e,(hl) + inc hl + ld d,(hl) + inc hl + ld (@calladdress2),de + + +@calladdress1: equ $+1 + call 0000 + ld e,a + +@calladdress2: equ $+1 + call 0000 + ld d,a + + pop hl + + add hl,de + + ex de,hl + ld hl,0 + or a + sbc hl, de + + pop de + + ; bc is length, hl is 0-distance, de is current write point + + add hl,de + + +IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING) + ; page in the real destination page +dst.page.minustwo: equ $+1 + ld a,00 + out (lmpr),a +ENDIF + + ldir + +IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING) +src.page: equ $+1 + ld a,00 + out (lmpr),a +ENDIF + + +IF defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING) + jp decode_tableloop +ELSE + bit 6,D + jp z, decode_tableloop + +copy_decoded_page_to_destination: + + ; at this point, section C is full + ; we may have written a few bytes into section D - these need to go back into C when the pointers are reset + + ; at the start of a file, we are writing only partial pages + ; (end of file also requires a copy out of C, but that's not here) + + ld (store_number_of_extra_bytes_written),de ; save this for later use + + +dst.page: equ $+1 + ld a,00 + out (lmpr),a + +dst: equ $+1 + ld de,0 + + ld a,e + or d + jr z, optimized_whole_page_copy + + ld hl,16384 + or a + sbc hl,de + + ld c,l + ld b,h + + ld l,e + ld a,d + or 0x80 + ld h,a + + ldir + + jp finished_page_copy + +optimized_whole_page_copy: + ld hl,32768 + ld de,0 + xor a + +page_copy_loop: + for 64, ldi + dec a + jp nz, page_copy_loop + + +finished_page_copy: + ld hl,0 + ld (dst),hl + ld a,(dst.page) + inc a + ld (dst.page),a + dec a + dec a + ld (dst.page.minustwo),a + + + ld de,32768 + +store_number_of_extra_bytes_written: equ $+1 + ld bc,0000 + ld a,b + and 0x3f + ld b,a + or c + jr z,no_extra_bytes_written + + ld hl,49152 + ldir + +no_extra_bytes_written: + + + ; put the gzdata back into low memory + + ; at this point we also need to check if the gzdata pointer needs to be updated + ; (i.e. roll page back to section A if it has reached B) + + exx + bit 6,h + jr z, no_src_rollover + + ld a,h + and 0x3f + ld h,a + + ld a,(src.page) + inc a + ld (src.page),a + +no_src_rollover: + exx + + ld a,(src.page) + out (lmpr),a + + + jp decode_tableloop + +ENDIF + + + + + +; for each length code starting at symbol-257 +; a word describing how many bits to read (function call or NULL) +; a word describing how many are added to the value read (if any) +lengthcodetable: + dw 3, read0bits + dw 4, read0bits + dw 5, read0bits + dw 6, read0bits + dw 7, read0bits + dw 8, read0bits + dw 9, read0bits + dw 10, read0bits + dw 11, read1bits + dw 13, read1bits + dw 15, read1bits + dw 17, read1bits + dw 19, read2bits + dw 23, read2bits + dw 27, read2bits + dw 31, read2bits + dw 35, read3bits + dw 43, read3bits + dw 51, read3bits + dw 59, read3bits + dw 67, read4bits + dw 83, read4bits + dw 99, read4bits + dw 115, read4bits + dw 131, read5bits + dw 163, read5bits + dw 195, read5bits + dw 227, read5bits + dw 258, read0bits + +distancecodetable: + dw 1, read0bits, read0bits + dw 2, read0bits, read0bits + dw 3, read0bits, read0bits + dw 4, read0bits, read0bits + dw 5, read1bits, read0bits + dw 7, read1bits, read0bits + dw 9, read2bits, read0bits + dw 13, read2bits, read0bits + dw 17, read3bits, read0bits + dw 25, read3bits, read0bits + dw 33, read4bits, read0bits + dw 49, read4bits, read0bits + dw 65, read5bits, read0bits + dw 97, read5bits, read0bits + dw 129, read6bits, read0bits + dw 193, read6bits, read0bits + dw 257, read7bits, read0bits + dw 385, read7bits, read0bits + dw 513, read8bits, read0bits + dw 769, read8bits, read0bits + dw 1025, read8bits, read1bits + dw 1537, read8bits, read1bits + dw 2049, read8bits, read2bits + dw 3073, read8bits, read2bits + dw 4097, read8bits, read3bits + dw 6145, read8bits, read3bits + dw 8193, read8bits, read4bits + dw 12289, read8bits, read4bits + dw 16385, read8bits, read5bits + dw 24577, read8bits, read5bits + + + + +END_BLOCK: + +this_was_last_block: equ $+1 + ld a,00 + or a + jp z, NEW_BLOCK + + ; copy any remaining bytes from C into the destination page + +IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING) + + + ld a,(dst.page) + out (lmpr),a + + ld de,(dst) ; start of written data (A) + + ld hl,(interim_dst) ; one past the end of written data (C) + + or a + sbc hl,de + + ld c,l + ld a,h + and 0x3f + ld b,a + or c + jr z, no_bytes_to_be_written_in_next_page + + ld l,e + ld a,d + and 0x3f + ld d,a + or 0x80 + ld h,a + + ldir +no_bytes_to_be_written_in_next_page: +ENDIF + +exit: + +IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING) +inflate_lmprs: equ $+1 + ld a,00 + out (lmpr),a +ENDIF + + xor a + ret + +fail: +IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING) + ld a,(inflate_lmprs) + out (lmpr),a +ENDIF + + ld a,1 + scf + ret + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +drop_gz_fextra: + push hl + push bc + + call read8bits + ld l,a + call read8bits + ld h,a + or c + jp z,gz_fextra_ret + + + ld bc,255 + +gz_fextra_loop: + ld a,h + or a + jr z, gz_fextra_lastbytes + + ld a,255 + call dropbytes + + or a + sbc hl,bc + jr gz_fextra_loop + +gz_fextra_lastbytes: + ld a,l + call dropbytes + +gz_fextra_ret: + pop bc + pop hl + ret + + + +drop_gz_fname: +drop_gz_fcomment: + +drop_gz_fname_loop: + call read8bits + or a + jr nz, drop_gz_fname_loop + + ret + + +drop_gz_fhcrc: + ld a,2 + call dropbytes + ret + + + + + + + +dropbytes: + or a + ret z + + push bc + ld b,a +dropbytesloop: + call read8bits ; we can optimize this, by fiddling the file pointer + ; but we MUST guarantee that if there are valid bits in c then they are updated to the new file position + ; since this is not called too often, it's easier to be valid and slow for the time being + djnz dropbytesloop + pop bc + ret + +makeHuffmanTable: + ; hl points to a table of code lengths + ; number of symbols in that table is bc + ; de is where the caller want the initial lookup table to be store + ; (further tables will be stored at incrementing addresses) + + ; a lookup table will consist of 16 entries 2 bytes each + ; 0[7] type (0=four bits have been decoded, do more lookup / 1=symbol has been decoded + ; 0[6] 1 (to confirm initialized) + ; if type==0 if type==1 + ; 0[4-5] number of undecoded bits + ; 0[0-2] address of next table/32 MSB symbol MSB + ; 1[0-7] address of next table/32 LSB symbol LSB + + + ld (huffman_number_symbols),bc + ld (huffman_code_lengths),hl + ld (huffman_table_store),de + + + ; 1) Count the number of codes for each code length. Let bl_count[N] be the number of codes of length N, N >= 1. + + ; zero out initial codelength_count + + ld hl,codelength_count + + ld b,18 +clear_codelength_count_loop: + ld (hl),0 + inc hl + ld (hl),0 + inc hl + djnz clear_codelength_count_loop + + ; now increment in codelength_count for each length described + +huffman_number_symbols: equ $+1 + ld bc,0000 + +huffman_code_lengths: equ $+1 + ld hl,0000 + + ld de,codelength_count + +codelength_count_loop: + ld a,(hl) + inc hl + + add a,a ; rla with guaranteed 0 bit clear + ld e,a + + ld a,(de) + inc a + ld (de),a + or a + jr nz, @+ + inc de + ld a,(de) + inc a + ld (de),a +@: dec bc + ld a,b + or c + jr nz, codelength_count_loop + + ; 2) Find the numerical value of the smallest code for each code length: + + ; bl_count[0] = 0; // because this is the number of unused codes. We need to ignore this + xor a + ld e,a + ld (de),a + inc de + ld (de),a + + ; code = 0; + ld e,a + ld d,a + ld c,a + + ; for (bits = 1; bits <= MAX_BITS; bits++) { + ld b,1 + +smallest_codes_loop: + + ; code = (code + bl_count[bits-1]) << 1; + ld h, codelength_count/256 + ld a,b + dec a + add a,a + ld l,a + + ld a,(hl) + add e + ld e,a + + inc hl ; doesn't change carry bit, so following ADC carries through from previous add + ld a,(hl) + adc d + ld d,a + + ld a,c + adc 0 + ld c,a + + rl e + rl d + rl c + + ; next_code[bits] = code; + ld h, next_code/256 + ld a,b + rla + rla + ld l,a + + ld (hl),e + inc hl + ld (hl),d + inc hl + ld (hl),c + inc hl + ld (hl),0 + + ; } + inc b + ld a,b + cp 19 + + jr c, smallest_codes_loop + + + +; 3) Assign numerical values to all codes, using consecutive values for all codes +; of the same length with the base values determined at step 2. Codes that are never +; used (which have a bit length of zero) must not be assigned a value. + + ; for (n = 0; n <= max_code; n++) { + + ld bc,0 +assign_huffman_codes_loop: + + ; len = tree[n].Len; + + ld hl, (huffman_code_lengths) + add hl,bc + + ld a,(hl) + + ; if (len != 0) { + or a + jr z, no_zerolength_code + + ; tree[n].Code = next_code[len]; + rla + rla + ld l,a + ld h,next_code/256 + + push hl + + ld e,(hl) + inc hl + ld d,(hl) + inc hl + ld a,(hl) + + + ld hl, codes + add hl,bc + add hl,bc + add hl,bc + add hl,bc + + ld (hl),e + inc hl + ld (hl),d + inc hl + ld (hl),a + + ; next_code[len]++; + + pop hl + + inc e + ld (hl),e + inc hl + + ld e,a + + ld a,d + adc 0 + ld (hl),a + inc hl + + ld a,e + adc 0 + ld (hl),a + + ; } +no_zerolength_code: + + ; } + inc bc + + ld hl,(huffman_number_symbols) + + or a + sbc hl,bc + jr nz, assign_huffman_codes_loop + + + + +; now rearrange those integers into a format we will find easier to use + + ld hl,(huffman_table_store) + + xor a + + ld (hl),a + inc hl + ld (hl),a + inc hl + ld (hl),a + inc hl + ld (hl),a + inc hl + + ld (next_huffman_table_store),hl + + ld hl,0 + ld (huffman_codes_tabulated),hl + + + ld de,(huffman_codes_tabulated) +predecode_next_symbol: + + ld hl,(huffman_code_lengths) + + add hl,de + + ld a,(hl) + or a + jp z, symbol_not_used + ld b,a + + + ld hl, codes + + add hl,de + add hl,de + add hl,de + add hl,de + + + ld d,(hl) + inc hl + ld c,(hl) + inc hl + ld a,(hl) + + rra + rr c + rr d + rra + rr c + rr d + rra + + ld e,a + ; this means that c[7:0], d[7:0], e[7:6] contain exactly 18 bits with c containing most significant bits + + ; we can consider all the other bits in a consistent manner by shifting left e,d,c + ; b is the number of bits actually used by this code + + ld a,18 + sub b + jr z, no_unused_code_bits + + or a +discard_unused_code_bits: + rl e + rl d + rl c + dec a + jr nz, discard_unused_code_bits + +no_unused_code_bits: + + +huffman_table_store: equ $+1 ; all codes start parseing at this table + ld hl,0000 + +next_huffman_predecode_bit: + ; we now have b bits of a code store in c,d,e + ; our current pointer in the table is hl + + rl e + rl d + rl c + + jr nc,@ + + inc hl + inc hl + +@: + ; so we have chosen a bit path, either write a symbol here or follow/write a new table entry + + dec b + jr z, write_a_symbol_definition + + ; table redirect + + ld a,(hl) + or a + jr z, write_a_table_definition + + ; follow the table already written + + inc hl + ld h,(hl) + + add a,a ; we always take the 0 branch here, and add later if necessary + add a,a + ld l,a + + jr next_huffman_predecode_bit + +write_a_table_definition: + + push de ; store while we use more registers + +next_huffman_table_store: equ $+1 + ld de,0000 + + ld a,e + rra + scf + rra + ld (hl),a + inc hl + ld (hl),d + + ld l,e + ld h,d + + xor a + ld (de),a + inc de + ld (de),a + inc de + ld (de),a + inc de + ld (de),a + inc de + + ; the address of the bottom four pixels of attribute space in a mode 2 screen at section D + ; is 63360 == 0xf780 + ; it can be useful to avoid these + + ld a,d + cp 0xf7 + jr nz, @ + ld a,e + cp 0x80 + jr nz, @ + ld de,0xf800 + +@: ld (next_huffman_table_store),de + + pop de + + jr next_huffman_predecode_bit + + +write_a_symbol_definition: + +huffman_codes_tabulated: equ $+1 + ld de,0000 + + ld (hl),d + inc hl + ld (hl),e + + +symbol_not_used: + inc de + ld (huffman_codes_tabulated),de + + ld hl,(huffman_number_symbols) + or a + sbc hl,de + + ld a,h + or l + jp nz, predecode_next_symbol + + ret + + + + + +decodeProtoHuffman: + ; decode bc huffman length codes + ; to an area of memory starting at de + ; using the huffman table at hl + + ld a,l + rra + rra + ld l,a + ld (ProtoHuffmanTableBegin),hl + + ; this is called infrequently for small tables + ; write it small and simple - the main one can be optimized more for speed + + jr protodecode_tablejump + +protodecode_tableloop: + inc hl + ld h,(hl) + ld l,a + +protodecode_tablejump: + call read1bit + rra + + ld a,l + adc a + add a + ld l,a + + ld a,(hl) + bit 7,a + jr nz, protodecode_tableloop + + + inc hl + ld a,(hl) + cp 16 + jr nc, protodecode_special + + ld (de),a + inc de + dec bc + +protodecode_loop: + +ProtoHuffmanTableBegin: equ $+1 + ld hl,0000 + + ld a,b + or c + jr nz, protodecode_tablejump + + ret + +protodecode_special: + cp 17 + jr z, protodecode_17 + jr nc, protodecode_18 + +protodecode_16: + + call read2bits + add 3 + ld h,a + + dec de + ld a,(de) + inc de + +@: ld (de),a + inc de + dec bc + dec h + jr nz,@ + + jr protodecode_loop + +protodecode_17: + + call read3bits + add 3 + ld h,a + xor a + +@: ld (de),a + inc de + dec bc + dec h + jr nz,@ + + jr protodecode_loop + +protodecode_18: + + call read7bits + add 11 + ld h,a + xor a + +@: ld (de),a + inc de + dec bc + dec h + jr nz,@ + + jr protodecode_loop + + + + +; file read routines +; huffman compression requires that we read partial bytes + +; these functions use the alternate registers +; they will not affect main registers, except to return the result in A + +; c' contains unused bits of a byte read from memory +; b' contains number of bits valid +; hl' contains pointer to next byte to read + +; we read bits LSB first +; calls for more than one bit expect the partial bytes returned as LSB = LSB + + +read1bit: +read1bits: + exx + dec b + jr z,@ + xor a + rr c + rla + exx + ret +@: xor a + rr c + rla + + ld b,8 + ld c,(hl) + inc hl + + exx + ret + + +N: equ FOR 9 +if (N >= 2) + + +read{N}bits: + exx + ld a,b + sub N + jr c, @l1 + jr z, @l2 + + ld b,a + + ld e,c + ld a,c + + IF N <= 4 + FOR N, rra + ELSE + FOR 8-N, rlca + ENDIF + + and 0xff >> N + + ld c,a + + ld a,e + and (1<256 symbols per length + +ds align 32 +codelengths: ds 288 + 33 ; single byte per entry, max size of literals tree = 287, of lengths tree = 33 + +ds align 256 +next_code: ds 128 ; 4 bytes per (32) code length + + +ds align 256 +codes: ds 288 * 4 ; worst case of 18bits*288 symbols (using 4 bytes per entry for easier code lookup) + ; I'm aware that a maximum of 286 symbols are used - but the static huffman encoding + ; contains space for 288, the last are never used but we need reserve space for them + + DS ALIGN 4 +Huffman_Tables_Start: + +print 65534 - Huffman_Tables_Start + 2, "bytes available for Huffman predecode tables" + diff --git a/VGMPLAY.WMF b/VGMPLAY.WMF new file mode 100644 index 0000000..8531f8a Binary files /dev/null and b/VGMPLAY.WMF differ diff --git a/VgmParserREF/vgm-lib.js b/VgmParserREF/vgm-lib.js new file mode 100644 index 0000000..b8fe818 --- /dev/null +++ b/VgmParserREF/vgm-lib.js @@ -0,0 +1,1852 @@ +/** + * VGM Library v1.71 Specification Compliant Parser + * + * Based on VGM Specification v1.71 beta by Valley Bell + * https://vgmrips.net/misc/vgmspec171.txt + * + * "VGM (Video Game Music) is a sample-accurate sound logging format for the Sega + * Master System, the Sega Game Gear and possibly many other machines (e.g. Sega + * Genesis). + * + * The normal file extension is .vgm but files can also be GZip compressed into + * .vgz files. However, a VGM player should attempt to support compressed and + * uncompressed files with either extension." + */ + +const fs = require('fs'); +const path = require('path'); +const zlib = require('zlib'); + +/** + * VGM Header Chip Clock Offsets (VGM Specification v1.71) + * + * "The format starts with a 256 byte header" + * "All integer values are *unsigned* and written in "Intel" byte order (Little + * Endian), so for example 0x12345678 is written as 0x78 0x56 0x34 0x12." + * + * Офсеты тактовых частот чипов в заголовке VGM файла согласно спецификации v1.71. + * Каждый чип имеет 4-байтовое поле с тактовой частотой в Гц. + * "It should be 0 if there is no [chip] used." + */ +const CHIP_OFFSETS = { + // "0x0C: SN76489 clock (32 bits) + // Input clock rate in Hz for the SN76489 PSG chip. A typical value is + // 3579545. It should be 0 if there is no PSG chip used. + // Note: Bit 31 (0x80000000) is used on combination with the dual-chip-bit + // to indicate that this is a T6W28. (PSG variant used in NeoGeo Pocket)" + SN76489: { offset: 0x0C, name: 'SN76489 PSG', minVersion: 0x100 }, + + // "0x10: YM2413 clock (32 bits) + // Input clock rate in Hz for the YM2413 chip. A typical value is 3579545. + // It should be 0 if there is no YM2413 chip used." + YM2413: { offset: 0x10, name: 'YM2413 OPLL', minVersion: 0x100 }, + // "0x14: GD3 offset (32 bits) + // Relative offset to GD3 tag. 0 if no GD3 tag. + // GD3 tags are descriptive tags similar in use to ID3 tags in MP3 files." + GD3: { offset: 0x14, name: 'GD3 offset', minVersion: 0x100 }, + + // "0x2C: YM2612 clock (32 bits) + // Input clock rate in Hz for the YM2612 chip. A typical value is 7670454. + // For version 1.01 and earlier files, the YM2413 clock rate should be + // used for the clock rate of the YM2612." + YM2612: { offset: 0x2C, name: 'YM2612 OPN2', minVersion: 0x150 }, + + // "0x30: YM2151 clock (32 bits) + // Input clock rate in Hz for the YM2151 chip. A typical value is 3579545." + // "0x30: YM2151 clock (32 bits) + // Input clock rate in Hz for the YM2151 chip. A typical value is 3579545. + // It should be 0 if there us no YM2151 chip used." + YM2151: { offset: 0x30, name: 'YM2151 OPM', minVersion: 0x150 }, + // "[VGM 1.51 additions:] + // 0x38: Sega PCM clock (32 bits) + // Input clock rate in Hz for the Sega PCM chip. A typical value is + // 4000000. It should be 0 if there is no Sega PCM chip used." + SegaPCM: { offset: 0x38, name: 'Sega PCM', minVersion: 0x151 }, + // "0x40: RF5C68 clock (32 bits) + // Input clock rate in Hz for the RF5C68 PCM chip. A typical value is + // 12500000. It should be 0 if there is no RF5C68 chip used." + RF5C68: { offset: 0x40, name: 'RF5C68 PCM', minVersion: 0x151 }, + // "0x44: YM2203 clock (32 bits) + // Input clock rate in Hz for the YM2203 chip. A typical value is 3000000." + YM2203: { offset: 0x44, name: 'YM2203 OPN', minVersion: 0x151 }, + // "0x48: YM2608 clock (32 bits) + // Input clock rate in Hz for the YM2608 chip. A typical value is 8000000." + YM2608: { offset: 0x48, name: 'YM2608 OPNA', minVersion: 0x151 }, + // "0x4C: YM2610/YM2610B clock (32 bits) + // Input clock rate in Hz for the YM2610/B chip. A typical value is + // 8000000. It should be 0 if there is no YM2610/B chip used. + // Note: Bit 31 is used to set whether it is a YM2610 or a YM2610B chip. + // If bit 31 is set it is an YM2610B, if bit 31 is clear it is an YM2610." + YM2610: { offset: 0x4C, name: 'YM2610/B OPNB', minVersion: 0x151 }, + // "0x50: YM3812 clock (32 bits) + // Input clock rate in Hz for the YM3812 chip. A typical value is 3579545." + YM3812: { offset: 0x50, name: 'YM3812 OPL2', minVersion: 0x151 }, + // "0x54: YM3526 clock (32 bits) + // Input clock rate in Hz for the YM3526 chip. A typical value is 3579545." + YM3526: { offset: 0x54, name: 'YM3526 OPL', minVersion: 0x151 }, + // "0x58: Y8950 clock (32 bits) + // Input clock rate in Hz for the Y8950 chip. A typical value is 3579545." + Y8950: { offset: 0x58, name: 'Y8950 OPL', minVersion: 0x151 }, + // "0x5C: YMF262 clock (32 bits) + // Input clock rate in Hz for the YMF262 chip. A typical value is 14318180." + YMF262: { offset: 0x5C, name: 'YMF262 OPL3', minVersion: 0x151 }, + // "0x60: YMF278B clock (32 bits) + // Input clock rate in Hz for the YMF278B chip. A typical value is + // 33868800. It should be 0 if there is no YMF278B chip used." + YMF278B: { offset: 0x60, name: 'YMF278B OPL4', minVersion: 0x151 }, + // "0x64: YMF271 clock (32 bits) + // Input clock rate in Hz for the YMF271 chip. A typical value is + // 16934400. It should be 0 if there is no YMF271 chip used." + YMF271: { offset: 0x64, name: 'YMF271 OPX', minVersion: 0x151 }, + // "0x68: YMZ280B clock (32 bits) + // Input clock rate in Hz for the YMZ280B chip. A typical value is + // 16934400. It should be 0 if there is no YMZ280B chip used." + YMZ280B: { offset: 0x68, name: 'YMZ280B PCMD8', minVersion: 0x151 }, + // "0x6C: RF5C164 clock (32 bits) + // Input clock rate in Hz for the RF5C164 PCM chip. A typical value is + // 12500000. It should be 0 if there is no RF5C164 chip used." + RF5C164: { offset: 0x6C, name: 'RF5C164 PCM', minVersion: 0x151 }, + // "0x70: PWM clock (32 bits) + // Input clock rate in Hz for the PWM chip. A typical value is + // 23011361. It should be 0 if there is no PWM chip used." + PWM: { offset: 0x70, name: 'PWM', minVersion: 0x151 }, + // "0x74: AY8910 clock (32 bits) + // Input clock rate in Hz for the AY8910 chip. A typical value is 1789750. + // It should be 0 if there is no AY8910 chip used." + AY8910: { offset: 0x74, name: 'AY8910 PSG', minVersion: 0x151 }, + // "[VGM 1.61 additions:] + // 0x80: GameBoy DMG clock (32 bits) + // Input clock rate in Hz for the GameBoy DMG chip, LR35902. A typical + // value is 4194304. It should be 0 if there is no GB DMG chip used." + DMG: { offset: 0x80, name: 'GameBoy DMG', minVersion: 0x161 }, + // "0x84: NES APU clock (32 bits) + // Input clock rate in Hz for the NES APU chip, N2A03. A typical value is + // 1789772. It should be 0 if there is no NES APU chip used. + // Note: Bit 31 is used to enable the FDS sound addon. Set to enable, + // clear to disable." + NES_APU: { offset: 0x84, name: 'NES APU', minVersion: 0x161 }, + // "0x88: MultiPCM clock (32 bits) + // Input clock rate in Hz for the MultiPCM chip. A typical value is + // 8053975. It should be 0 if there is no MultiPCM chip used." + MultiPCM: { offset: 0x88, name: 'MultiPCM', minVersion: 0x161 }, + // "0x8C: uPD7759 clock (32 bits) + // Input clock rate in Hz for the uPD7759 chip. A typical value is 640000. + // It should be 0 if there is no uPD7759 chip used." + uPD7759: { offset: 0x8C, name: 'uPD7759 ADPCM', minVersion: 0x161 }, + // "0x90: OKIM6258 clock (32 bits) + // Input clock rate in Hz for the OKIM6258 chip. A typical value is + // 4000000. It should be 0 if there is no OKIM6258 chip used." + OKIM6258: { offset: 0x90, name: 'OKIM6258 ADPCM', minVersion: 0x161 }, + // "0x98: OKIM6295 clock (32 bits) + // Input clock rate in Hz for the OKIM6295 chip. A typical value is + // 8000000. It should be 0 if there is no OKIM6295 chip used." + OKIM6295: { offset: 0x98, name: 'OKIM6295 ADPCM', minVersion: 0x161 }, + // "0x9C: K051649 clock (32 bits) + // Input clock rate in Hz for the K051649 chip. A typical value is + // 1500000. It should be 0 if there is no K051649 chip used." + K051649: { offset: 0x9C, name: 'K051649 SCC', minVersion: 0x161 }, + // "0xA0: K054539 clock (32 bits) + // Input clock rate in Hz for the K054539 chip. A typical value is + // 18432000. It should be 0 if there is no K054539 chip used." + K054539: { offset: 0xA0, name: 'K054539 PCM', minVersion: 0x161 }, + // "0xA4: HuC6280 clock (32 bits) + // Input clock rate in Hz for the HuC6280 chip. A typical value is + // 3579545. It should be 0 if there is no HuC6280 chip used." + HuC6280: { offset: 0xA4, name: 'HuC6280 PSG', minVersion: 0x161 }, + // "0xA8: C140 clock (32 bits) + // Input clock rate in Hz for the C140 chip. A typical value is 8000000. + // It should be 0 if there is no C140 chip used." + C140: { offset: 0xA8, name: 'C140', minVersion: 0x161 }, + // "0xAC: K053260 clock (32 bits) + // Input clock rate in Hz for the K053260 chip. A typical value is + // 3579545. It should be 0 if there is no K053260 chip used." + K053260: { offset: 0xAC, name: 'K053260 PCMA', minVersion: 0x161 }, + // "0xB0: Pokey clock (32 bits) + // Input clock rate in Hz for the Pokey chip. A typical value is 1789772. + // It should be 0 if there is no Pokey chip used." + Pokey: { offset: 0xB0, name: 'Pokey', minVersion: 0x161 }, + // "0xB4: QSound clock (32 bits) + // Input clock rate in Hz for the QSound chip. A typical value is 4000000. + // It should be 0 if there is no QSound chip used." + QSound: { offset: 0xB4, name: 'QSound', minVersion: 0x161 }, + // "[VGM 1.71 additions:] + // 0xB8: SCSP clock (32 bits) + // Input clock rate in Hz for the SCSP chip. A typical value is 22579200. + // It should be 0 if there is no SCSP chip used." + SCSP: { offset: 0xB8, name: 'SCSP', minVersion: 0x171 }, + // "0xC0: WonderSwan clock (32 bits) + // Input clock rate in Hz for the WonderSwan chip. A typical value is + // 3072000. It should be 0 if there is no WonderSwan chip used." + WonderSwan: { offset: 0xC0, name: 'WonderSwan', minVersion: 0x171 }, + // "0xC4: Virtual Boy VSU clock (32 bits) + // Input clock rate in Hz for the VSU chip. A typical value is 5000000. + // It should be 0 if there is no VSU chip used." + VSU: { offset: 0xC4, name: 'Virtual Boy VSU', minVersion: 0x171 }, + // "0xC8: SAA1099 clock (32 bits) + // Input clock rate in Hz for the SAA1099 chip. A typical value is + // 8000000. It should be 0 if there is no SAA1099 chip used." + // [VGM 1.71 additions] - Creative Music System sound chip + SAA1099: { offset: 0xC8, name: 'SAA1099', minVersion: 0x171 }, + // "0xCC: ES5503 clock (32 bits) + // Input clock rate in Hz for the ES5503 chip. A typical value is 7159090. + // It should be 0 if there is no ES5503 chip used." + ES5503: { offset: 0xCC, name: 'ES5503 DOC', minVersion: 0x171 }, + // "0xD0: ES5505/ES5506 clock (32 bits) + // Input clock rate in Hz for the ES5506 chip. A typical value is + // 16000000. It should be 0 if there is no ES5506 chip used. + // Note: Bit 31 is used to set whether it is an ES5505 or an ES5506 chip. + // If bit 31 is set it is an ES5506, if bit 31 is clear it is an ES5505." + ES5505: { offset: 0xD0, name: 'ES5505/ES5506 OTTO', minVersion: 0x171 }, + // "0xD8: Seta X1-010 clock (32 bits) + // Input clock rate in Hz for the X1-010 chip. A typical value is + // 16000000. It should be 0 if there is no X1-010 chip used." + X1_010: { offset: 0xD8, name: 'X1-010 Seta', minVersion: 0x171 }, + // "0xDC: Namco C352 clock (32 bits) + // Input clock rate in Hz for the C352 chip. A typical value is 24192000. + // It should be 0 if there is no C352 chip used." + C352: { offset: 0xDC, name: 'C352', minVersion: 0x171 }, + // "0xE0: Irem GA20 clock (32 bits) + // Input clock rate in Hz for the GA20 chip. A typical value is 3579545. + // It should be 0 if there is no GA20 chip used." + GA20: { offset: 0xE0, name: 'GA20', minVersion: 0x171 } +}; + +/** + * VGM Commands (VGM Specification v1.71) + * + * "Starting at the location specified by the VGM data offset (or, + * offset 0x40 for file versions below 1.50) is found a sequence of commands + * containing data written to the chips or timing information. A command is one of:" + * + * "Some ranges are reserved for future use, with different numbers of operands:" + * "On encountering these, the correct number of bytes should be skipped." + */ +// VGM команды (опкоды) +const VGM_COMMANDS = { + // "0x4F dd : Game Gear PSG stereo, write dd to port 0x06" + 0x4F: { name: 'GG_PSG_STEREO', params: 1, description: 'Game Gear PSG stereo' }, + // "0x50 dd : PSG (SN76489/SN76496) write value dd" + 0x50: { name: 'PSG_WRITE', params: 1, description: 'PSG (SN76489/SN76496) write value' }, + + // "0x51 aa dd : YM2413, write value dd to register aa" + 0x51: { name: 'YM2413_WRITE', params: 2, description: 'YM2413 write' }, + + // "0x52 aa dd : YM2612 port 0, write value dd to register aa" + 0x52: { name: 'YM2612_PORT0', params: 2, description: 'YM2612 port 0 write' }, + // "0x53 aa dd : YM2612 port 1, write value dd to register aa" + 0x53: { name: 'YM2612_PORT1', params: 2, description: 'YM2612 port 1 write' }, + + // "0x54 aa dd : YM2151, write value dd to register aa" + 0x54: { name: 'YM2151_WRITE', params: 2, description: 'YM2151 write' }, + + // "0x55 aa dd : YM2203, write value dd to register aa" + 0x55: { name: 'YM2203_WRITE', params: 2, description: 'YM2203 write' }, + + // "0x56 aa dd : YM2608 port 0, write value dd to register aa" + 0x56: { name: 'YM2608_PORT0', params: 2, description: 'YM2608 port 0 write' }, + // "0x57 aa dd : YM2608 port 1, write value dd to register aa" + 0x57: { name: 'YM2608_PORT1', params: 2, description: 'YM2608 port 1 write' }, + + // "0x58 aa dd : YM2610 port 0, write value dd to register aa" + 0x58: { name: 'YM2610_PORT0', params: 2, description: 'YM2610 port 0 write' }, + // "0x59 aa dd : YM2610 port 1, write value dd to register aa" + 0x59: { name: 'YM2610_PORT1', params: 2, description: 'YM2610 port 1 write' }, + + // "0x5A aa dd : YM3812, write value dd to register aa" + 0x5A: { name: 'YM3812_WRITE', params: 2, description: 'YM3812 write' }, + + // "0x5B aa dd : YM3526, write value dd to register aa" + 0x5B: { name: 'YM3526_WRITE', params: 2, description: 'YM3526 write' }, + + // "0x5C aa dd : Y8950, write value dd to register aa" + 0x5C: { name: 'Y8950_WRITE', params: 2, description: 'Y8950 write' }, + + // "0x5D aa dd : YMZ280B, write value dd to register aa" + 0x5D: { name: 'YMZ280B_WRITE', params: 2, description: 'YMZ280B write' }, + + // "0x5E aa dd : YMF262 port 0, write value dd to register aa" + 0x5E: { name: 'YMF262_PORT0', params: 2, description: 'YMF262 port 0 write' }, + // "0x5F aa dd : YMF262 port 1, write value dd to register aa" + 0x5F: { name: 'YMF262_PORT1', params: 2, description: 'YMF262 port 1 write' }, + + // "0x61 nn nn : Wait n samples, n can range from 0 to 65535 (approx 1.49 + // seconds). Longer pauses than this are represented by multiple + // wait commands." + 0x61: { name: 'WAIT_NNNN', params: 2, description: 'Wait n samples (16-bit)' }, + // "0x62 : wait 735 samples (60th of a second), a shortcut for + // 0x61 0xdf 0x02" + 0x62: { name: 'WAIT_60HZ', params: 0, description: 'Wait 735 samples (60 Hz)' }, + // "0x63 : wait 882 samples (50th of a second), a shortcut for + // 0x61 0x72 0x03" + 0x63: { name: 'WAIT_50HZ', params: 0, description: 'Wait 882 samples (50 Hz)' }, + + // "0x66 : end of sound data" + 0x66: { name: 'END', params: 0, description: 'End of sound data' }, + + // "0x67 ... : data block: see below" + 0x67: { name: 'DATA_BLOCK', params: -1, description: 'Data block' }, + + // "0x68 ... : PCM RAM write: see below" + 0x68: { name: 'PCM_RAM_WRITE', params: -1, description: 'PCM RAM write' }, + + // "0x7n : wait n+1 samples, n can range from 0 to 15." + // Короткие паузы: 0x70 = 1 sample, 0x71 = 2 samples, ... 0x7F = 16 samples + ...[...Array(16)].reduce((acc, _, i) => { + acc[0x70 + i] = { name: `WAIT_${i + 1}`, params: 0, description: `Wait ${i + 1} samples` }; + return acc; + }, {}), + + // "0x8n : YM2612 port 0 address 2A write from the data bank, then wait + // n samples; n can range from 0 to 15. Note that the wait is n, + // NOT n+1. (Note: Written to first chip instance only.)" + // YM2612 DAC паттерн с ожиданием + ...[...Array(16)].reduce((acc, _, i) => { + acc[0x80 + i] = { name: `YM2612_DAC_WAIT_${i}`, params: 0, description: `YM2612 PCM data write & wait ${i} samples` }; + return acc; + }, {}), + + // "0xA0 aa dd : AY8910, write value dd to register aa" + 0xA0: { name: 'AY8910_WRITE', params: 2, description: 'AY8910 write' }, + + // "0xB0 aa dd : RF5C68, write value dd to register aa" + 0xB0: { name: 'RF5C164_WRITE', params: 2, description: 'RF5C164 write' }, + // "0xB1 aa dd : RF5C164, write value dd to register aa" + 0xB1: { name: 'RF5C68_WRITE', params: 2, description: 'RF5C68 write' }, + // "0xB2 ad dd : PWM, write value ddd to register a (d is MSB, dd is LSB)" + 0xB2: { name: 'PWM_WRITE', params: 2, description: 'PWM write' }, + // "0xB3 aa dd : GameBoy DMG, write value dd to register aa + // Note: Register 00 equals GameBoy address FF10." + 0xB3: { name: 'DMG_WRITE', params: 2, description: 'GameBoy DMG write' }, + // "0xB4 aa dd : NES APU, write value dd to register aa" + 0xB4: { name: 'NES_APU_WRITE', params: 2, description: 'NES APU write' }, + // "0xB5 aa dd : MultiPCM, write value dd to register aa" + 0xB5: { name: 'MULTI_PCM_WRITE', params: 2, description: 'MultiPCM write' }, + // "0xB6 aa dd : uPD7759, write value dd to register aa" + 0xB6: { name: 'UPD7759_WRITE', params: 2, description: 'uPD7759 write' }, + // "0xB7 aa dd : OKIM6258, write value dd to register aa" + 0xB7: { name: 'OKIM6258_WRITE', params: 2, description: 'OKIM6258 write' }, + // "0xB8 aa dd : OKIM6295, write value dd to register aa" + 0xB8: { name: 'OKIM6295_WRITE', params: 2, description: 'OKIM6295 write' }, + // "0xB9 aa dd : HuC6280, write value dd to register aa" + 0xB9: { name: 'HUC6280_WRITE', params: 2, description: 'HuC6280 write' }, + // "0xBA aa dd : K053260, write value dd to register aa" + 0xBA: { name: 'K053260_WRITE', params: 2, description: 'K053260 write' }, + // "0xBB aa dd : Pokey, write value dd to register aa" + 0xBB: { name: 'POKEY_WRITE', params: 2, description: 'Pokey write' }, + // "0xBC aa dd : WonderSwan, write value dd to register aa" + 0xBC: { name: 'WONDERSWAN_WRITE', params: 2, description: 'WonderSwan write' }, + // "0xBD aa dd : SAA1099, write value dd to register aa" + 0xBD: { name: 'SAA1099_WRITE', params: 2, description: 'SAA1099 write' }, + // "0xBE aa dd : ES5506, write 8-bit value dd to register aa" + 0xBE: { name: 'ES5506_WRITE', params: 2, description: 'ES5506 write' }, + // "0xBF aa dd : GA20, write value dd to register aa" + 0xBF: { name: 'GA20_WRITE', params: 2, description: 'GA20 write' }, + + // "0xC0 bbaa dd : Sega PCM, write value dd to memory offset aabb" + 0xC0: { name: 'SEGA_PCM_WRITE', params: 3, description: 'Sega PCM write' }, + // "0xC1 bbaa dd : RF5C68, write value dd to memory offset aabb" + 0xC1: { name: 'RF5C164_MEM_WRITE', params: 3, description: 'RF5C164 memory write' }, + // "0xC2 bbaa dd : RF5C164, write value dd to memory offset aabb" + 0xC2: { name: 'RF5C68_MEM_WRITE', params: 3, description: 'RF5C68 memory write' }, + // "0xC3 cc bbaa : MultiPCM, write set bank offset aabb to channel cc" + 0xC3: { name: 'YM2608_WRITE_PORT2', params: 3, description: 'YM2608 write port 2/3' }, + + // "0xE0 dddddddd : seek to offset dddddddd (Intel byte order) in PCM data bank" + 0xE0: { name: 'SEEK_PCM', params: 4, description: 'Seek to offset in PCM data bank' }, + + // "0xE1 aabb ddee: C352 write 16-bit value ddee to register aabb" + 0xE1: { name: 'C352_WRITE', params: 3, description: 'C352 write' } +}; + +/** + * Анализирует gzip заголовок VGZ файла согласно RFC 1952 + * + * \"The normal file extension is .vgm but files can also be GZip compressed into + * .vgz files. However, a VGM player should attempt to support compressed and + * uncompressed files with either extension. (ZLib's GZIO library makes this + * trivial to implement.)\" + * + * GZIP Заголовок (RFC 1952): + * +---+---+---+---+---+---+---+---+---+---+ + * |ID1|ID2|CM |FLG| MTIME |XFL|OS | + * +---+---+---+---+---+---+---+---+---+---+ + * + * \"A gzip file consists of a series of 'members' (compressed data sets). + * The format of each member is specified in the following section. The + * members simply appear one after another in the file, with no additional + * information before, between, or after them.\" + * + * ID1 (IDentification 1) = 0x1f + * ID2 (IDentification 2) = 0x8b + * CM (Compression Method) = обычно 8 (deflate) + * FLG (FLaGs) = различные флаги + * MTIME (Modification TIME) = время последней модификации файла + * XFL (eXtra FLags) = дополнительные флаги компрессии + * OS (Operating System) = файловая система/ОС, где создавался файл + * + * @param {Buffer} compressedData - Сжатые gzip данные + * @returns {Object} Информация о gzip заголовке согласно RFC 1952 + * @throws {Error} При неверной сигнатуре GZIP + */ +function analyzeGZIP(compressedData) { + const info = {}; + + // Проверка GZIP magic number согласно RFC 1952: + // \"The member begins with a fixed 10-byte header, containing a magic number, + // a version indicator, a timestamp, extra flags, and an OS indicator.\" + if (compressedData[0] !== 0x1f || compressedData[1] !== 0x8b) { + throw new Error('Invalid GZIP signature'); + } + + // Compression method (RFC 1952): + // \"CM (Compression Method) This identifies the compression method used in the file. + // CM = 8 denotes the 'deflate' compression method with a window size up to 32K.\" + const method = compressedData[2]; + const methodNames = { + 0: 'Reserved 0', + 1: 'Reserved 1 (compress)', + 2: 'Reserved 2 (pack)', + 3: 'Reserved 3 (lzh)', + 4: 'Reserved 4', + 5: 'Reserved 5', + 6: 'Reserved 6', + 7: 'Reserved 7', + 8: 'DEFLATE (LZ77 + Huffman)', + // 9-255 зарезервированы + }; + info.compressionMethod = methodNames[method] || `Reserved/Unknown (${method})`; + info.compressionMethodCode = method; + + // Флаги гzip заголовка (RFC 1952): + // "FLG (FLaGs) Reserved FLG bits must be zero. The remaining FLG bits are: + // bit 0 FTEXT, bit 1 FHCRC, bit 2 FEXTRA, bit 3 FNAME, bit 4 FCOMMENT" + const flags = compressedData[3]; + info.flags = { + FTEXT: !!(flags & 0x01), + FHCRC: !!(flags & 0x02), + FEXTRA: !!(flags & 0x04), + FNAME: !!(flags & 0x08), + FCOMMENT: !!(flags & 0x10) + }; + + // Время модификации файла (RFC 1952): + // "MTIME (Modification Time) This gives the most recent modification time of the + // original file being compressed. The time is in Unix format" + const mtime = compressedData.readUInt32LE(4); + info.timestamp = mtime; + if (mtime > 0) { + info.modificationDate = new Date(mtime * 1000).toISOString(); + } + + // Дополнительные флаги компрессии (RFC 1952): + // "XFL (eXtra FLags) These may be used by specific compression methods. + // The 'deflate' method (CM = 8) sets these flags as follows: + // XFL = 2 - compressor used maximum compression, slowest algorithm + // XFL = 4 - compressor used fastest algorithm" + const xfl = compressedData[8]; + const compressionLevels = { + 2: 'Maximum compression (slowest)', + 4: 'Fastest compression' + }; + info.compressionLevel = compressionLevels[xfl] || 'Default compression'; + info.compressionLevelCode = xfl; + + // Операционная система (RFC 1952): + // "OS (Operating System) This identifies the type of file system on which compression + // took place. This may be useful in determining end-of-line convention for text files." + const os = compressedData[9]; + const osNames = { + 0: 'FAT filesystem (MS-DOS, OS/2, NT/Win32)', + 1: 'Amiga', + 2: 'VMS (or OpenVMS)', + 3: 'Unix', + 4: 'VM/CMS', + 5: 'Atari TOS', + 6: 'HPFS filesystem (OS/2, NT)', + 7: 'Macintosh', + 8: 'Z-System', + 9: 'CP/M', + 10: 'TOPS-20', + 11: 'NTFS filesystem (NT)', + 12: 'QDOS', + 13: 'Acorn RISCOS', + 255: 'Unknown' + }; + info.os = osNames[os] || `Unknown (${os})`; + info.osCode = os; + + return info; +} + +/** + * Читает VGZ файл и возвращает распакованный VGM + информацию о сжатии + * + * \"The normal file extension is .vgm but files can also be GZip compressed into + * .vgz files. However, a VGM player should attempt to support compressed and + * uncompressed files with either extension. (ZLib's GZIO library makes this + * trivial to implement.)\" + * + * VGZ (VGM GZip) файлы содержат VGM данные, сжатые алгоритмом gzip. + * Эта функция автоматически определяет формат файла и распаковывает при необходимости. + * + * @param {string} filePath - Путь к VGZ файлу + * @returns {Object} { data: Buffer с VGM данными, gzipInfo: инфо о сжатии } + * @throws {Error} При ошибках чтения или распаковки + */ +function readVGZ(filePath) { + try { + const compressed = fs.readFileSync(filePath); + const uncompressed = zlib.gunzipSync(compressed); + + // Анализируем gzip заголовок + const gzipInfo = analyzeGZIP(compressed); + + // Добавляем информацию о размерах и степени сжатия + gzipInfo.compressedSize = compressed.length; + gzipInfo.uncompressedSize = uncompressed.length; + gzipInfo.compressionRatio = ((1 - compressed.length / uncompressed.length) * 100).toFixed(2); + gzipInfo.compressionRatioText = `${gzipInfo.compressionRatio}%`; + + return { data: uncompressed, gzipInfo }; + } catch (err) { + throw new Error(`Failed to read/decompress VGZ: ${err.message}`); + } +} + +/** + * Парсит заголовок VGM + */ +function parseVGMHeader(vgm) { + const header = {}; + + // Проверка сигнатуры + const signature = vgm.slice(0, 4).toString(); + if (signature !== 'Vgm ') { + throw new Error('Invalid VGM signature'); + } + header.signature = signature; + + // Версия (формат: 0xAABB где AA - major, BB - minor в BCD) + const versionRaw = vgm.readUInt32LE(0x08); + + // Версионная система VGM использует BCD для minor version + // Например: 0x0171 = версия 1.71, 0x0161 = версия 1.61 + const major = (versionRaw >> 8) & 0xFF; + const minorBCD = versionRaw & 0xFF; + + // Конвертируем minor version из BCD в десятичную + const minorHigh = (minorBCD >> 4) & 0x0F; + const minorLow = minorBCD & 0x0F; + const minor = minorHigh * 10 + minorLow; + + header.version = `${major}.${minor.toString().padStart(2, '0')}`; + header.versionHex = versionRaw; + header.versionRaw = versionRaw; // Для отладки + + // \"VGM files use a sample rate of 44100 Hz. All wait times are given as a + // number of samples to wait at this rate.\" + // \"VGMs run with a rate of 44100 samples per second. All sample values use + // this unit.\" + // Фиксированная частота дискретизации 44,1 кГц для всех VGM файлов + header.sampleRate = 44100; + header.sampleRateNote = 'Fixed by VGM specification'; + + // "0x18: Total # samples (32 bits) + // Total of all wait values in the file." + header.totalSamples = vgm.readUInt32LE(0x18); + header.totalSeconds = header.totalSamples / header.sampleRate; + + // "0x1C: Loop offset (32 bits) + // Relative offset to loop point, or 0 if no loop." + const loopOffset = vgm.readUInt32LE(0x1C); + header.loopOffset = loopOffset > 0 ? loopOffset + 0x1C : 0; + + // "0x20: Loop # samples (32 bits) + // Number of samples in one loop, or 0 if there is no loop. + // Total of all wait values between the loop point and the end of the file." + header.loopSamples = vgm.readUInt32LE(0x20); + header.loopSeconds = header.loopSamples / header.sampleRate; + header.hasLoop = header.loopSamples > 0; + + // Длительность введения до начала цикла (расчетное значение): + // intro_samples = total_samples - loop_samples + header.introSamples = header.totalSamples - header.loopSamples; + header.introSeconds = header.introSamples / header.sampleRate; + + // "[VGM 1.01 additions:] + // 0x24: Rate (32 bits) + // "Rate" of recording in Hz, used for rate scaling on playback. It is + // typically 50 for PAL systems and 60 for NTSC systems." + header.rate = vgm.readUInt32LE(0x24); + + // "[VGM 1.50 additions:] + // 0x34: VGM data offset (32 bits) + // Relative offset to VGM data stream. + // If the VGM data starts at absolute offset 0x40, this will contain + // value 0x0000000C. For versions prior to 1.50, it should be 0 and the + // VGM data must start at offset 0x40." + let dataOffset = 0x40; // По умолчанию для версий < 1.50 + if (versionRaw >= 0x150) { + const vgmDataOffset = vgm.readUInt32LE(0x34); + if (vgmDataOffset > 0) { + dataOffset = 0x34 + vgmDataOffset; + } + } + header.dataOffset = dataOffset; + + // \"0x14: GD3 offset (32 bits) + // Relative offset to GD3 tag. 0 if no GD3 tag. + // GD3 tags are descriptive tags similar in use to ID3 tags in MP3 files.\" + // GD3 содержит метаданные: название трека, игры, автора, систему и дату + const gd3Offset = vgm.readUInt32LE(0x14); + header.gd3Offset = gd3Offset > 0 ? gd3Offset + 0x14 : 0; + + // Размер заголовка в зависимости от версии VGM + let headerSize = 0x40; // v1.00-1.01: 64 bytes + if (versionRaw >= 0x150) { + // v1.50+: Используем VGM data offset для определения размера + const vgmDataOffset = vgm.readUInt32LE(0x34); + if (vgmDataOffset > 0) { + headerSize = 0x34 + vgmDataOffset; + } else { + // Если offset не указан, используем размер по умолчанию для версии + if (versionRaw >= 0x171) { + headerSize = 0x100; // v1.71+: 256 bytes + } else if (versionRaw >= 0x161) { + headerSize = 0xC0; // v1.61-1.70: 192 bytes + } else if (versionRaw >= 0x151) { + headerSize = 0x80; // v1.51-1.60: 128 bytes + } else { + headerSize = 0x80; // v1.50: 128 bytes + } + } + } + header.headerSize = headerSize; + header.maxHeaderOffset = headerSize; + + // Чипы (с учетом версии VGM) + header.chips = []; + for (const [chipKey, chipInfo] of Object.entries(CHIP_OFFSETS)) { + if (chipKey === 'GD3') continue; // Пропускаем GD3 offset + + // Проверяем, доступен ли чип в данной версии VGM + if (chipInfo.minVersion && versionRaw < chipInfo.minVersion) { + continue; // Чип недоступен в этой версии + } + + const offset = chipInfo.offset; + // Проверяем, что offset находится в пределах заголовка + if (offset + 4 <= headerSize && offset + 4 <= vgm.length) { + const clockRaw = vgm.readUInt32LE(offset); + + // \"Dual Chip Support + // These chips support two instances of a chip in one vgm: + // PSG, YM2413, YM2612, YM2151, YM2203, YM2608, YM2610, YM3812, YM3526, Y8950, + // YMZ280B, YMF262, YMF278B, YMF271, AY8910, GameBoy DMG, NES APU, MultiPCM, + // uPD7759, OKIM6258, OKIM6295, K051649, K054539, HuC6280, C140, K053260, Pokey, + // SCSP + // + // Dual chip support is activated by setting bit 30 (0x40000000) in the chip's + // clock value. (Note: The PSG needs this bit set for T6W28 mode.)\" + const isDualChip = (clockRaw & 0x40000000) !== 0; + const actualClock = clockRaw & ~0x40000000; // Убираем dual chip бит + + if (actualClock > 0 && clockRaw < 0x80000000) { // Проверяем raw значение на адекватность + // Список чипов поддерживающих dual mode по официальной спецификации VGM v1.71 + // Note: PSG (SN76489) использует бит 30 для T6W28 mode, не для dual chip + const dualChipSupported = [ + 'YM2413', 'YM2612', 'YM2151', 'YM2203', 'YM2608', 'YM2610', + 'YM3812', 'YM3526', 'Y8950', 'YMZ280B', 'YMF262', 'YMF278B', + 'YMF271', 'AY8910', 'DMG', 'NES_APU', 'MultiPCM', 'uPD7759', + 'OKIM6258', 'OKIM6295', 'K051649', 'K054539', 'HuC6280', + 'C140', 'K053260', 'Pokey', 'SCSP' + // SAA1099 не в официальном списке, но поддерживается практически + ]; + + // Специальная обработка для PSG (SN76489): бит 30 указывает T6W28 mode + const isT6W28 = chipKey === 'SN76489' && isDualChip; + const isPsgDualMode = chipKey === 'SN76489' && isDualChip; + + // Форматируем версию добавления чипа (BCD формат) + let versionAddedStr = '1.00'; + if (chipInfo.minVersion) { + const vMajor = (chipInfo.minVersion >> 8) & 0xFF; + const vMinorHex = chipInfo.minVersion & 0xFF; + const vMinorHigh = (vMinorHex >> 4) & 0x0F; + const vMinorLow = vMinorHex & 0x0F; + const vMinor = vMinorHigh * 10 + vMinorLow; + versionAddedStr = `${vMajor}.${vMinor.toString().padStart(2, '0')}`; + } + + // Определяем название чипа с учетом dual mode и специальных случаев + let chipDisplayName = chipInfo.name; + let isDualChipForDisplay = false; + + if (chipKey === 'SN76489' && isDualChip) { + // PSG: бит 30 означает T6W28 mode (NeoGeo Pocket variant) + chipDisplayName = 'T6W28 PSG'; + isDualChipForDisplay = false; + } else if (isDualChip && (dualChipSupported.includes(chipKey) || chipKey === 'SAA1099')) { + // Официально поддерживаемые dual chip + SAA1099 (практическая поддержка) + chipDisplayName = `2x ${chipInfo.name}`; + isDualChipForDisplay = true; + } + + header.chips.push({ + type: chipKey, + name: chipDisplayName, + clock: actualClock, + clockRaw: clockRaw, + isDualChip: isDualChipForDisplay, + isT6W28: isT6W28, + clockMHz: (actualClock / 1000000).toFixed(3), + versionAdded: versionAddedStr + }); + } + } + } + + return header; +} + +/** + * Парсит GD3 теги (метаданные) из VGM файла + * + * \"GD3 tags are descriptive tags similar in use to ID3 tags in MP3 files. + * See the GD3 specification for more details. The GD3 tag is usually + * stored immediately after the VGM data.\" + * + * GD3 тег содержит информацию о треке в UTF-16LE кодировке: + * - Track title (Eng + Jpn) + * - Game title (Eng + Jpn) + * - System name (Eng + Jpn) + * - Author (Eng + Jpn) + * - Release date + * - Creator/Dumper + * - Notes + * + * @param {Buffer} vgmBuffer - Буфер VGM файла + * @param {number} gd3Offset - Смещение GD3 тега + * @returns {Object|null} Распарсенные GD3 теги или null + */ +function parseGD3(vgmBuffer, gd3Offset) { + if (!gd3Offset || gd3Offset >= vgmBuffer.length) { + return null; + } + + try { + // Проверяем сигнатуру GD3 + const signature = vgmBuffer.slice(gd3Offset, gd3Offset + 4).toString(); + if (signature !== 'Gd3 ') { + return null; + } + + const version = vgmBuffer.readUInt32LE(gd3Offset + 4); + const dataLength = vgmBuffer.readUInt32LE(gd3Offset + 8); + + let pos = gd3Offset + 12; + const strings = []; + + // Читаем UTF-16LE строки (каждая заканчивается на 0x0000) + for (let i = 0; i < 10; i++) { // GD3 содержит 10 строк + let str = ''; + while (pos + 1 < vgmBuffer.length) { + const char = vgmBuffer.readUInt16LE(pos); + if (char === 0) break; + str += String.fromCharCode(char); + pos += 2; + } + strings.push(str); + pos += 2; // Пропускаем нулевой терминатор + } + + return { + version: version, + trackTitle: strings[0] || '', + trackTitleJapanese: strings[1] || '', + gameName: strings[2] || '', + gameNameJapanese: strings[3] || '', + systemName: strings[4] || '', + systemNameJapanese: strings[5] || '', + trackAuthor: strings[6] || '', + trackAuthorJapanese: strings[7] || '', + date: strings[8] || '', + ripper: strings[9] || '', + notes: strings[10] || '' + }; + } catch (error) { + return null; + } +} + +/** + * Парсит команды VGM (опкоды) согласно VGM спецификации v1.71 + * + * \"The VGM data is made up of command bytes, some of which are followed by + * data bytes. Commands 0x00-0x4f and 0x50-0xff are used for different groups.\" + * + * \"VGM files use a sample rate of 44100 Hz. All wait times are given as a + * number of samples to wait at this rate.\" + * + * Основные группы команд: + * 0x50-0x5f: PSG (SN76489) - \"0x50 dd : PSG (SN76489/SN76496) write value dd\" + * 0x61: Wait - \"0x61 nnnn : Wait nnnn samples\" + * 0x62: Wait - \"0x62 : Wait 735 samples (60th of a second)\" + * 0x63: Wait - \"0x63 : Wait 882 samples (50th of a second)\" + * 0x66: End - \"0x66 : End of sound data\" + * 0x70-0x8f: Wait short - \"0x7n : Wait n+1 samples, n can range from 0 to 15\" + * 0xa0-0xa9: YM2612 - \"0xA0 aa dd : YM2612 port 0 address aa data dd\" + * 0xb0-0xb8: RF5C68 - \"0xB0 aa dd : RF5C68 register aa data dd\" + * 0xc0-0xdf: Sega PCM - \"0xCn dddd : Sega PCM memory write\" + * + * @param {Buffer} vgm - VGM данные + * @param {number} dataOffset - Смещение начала команд в VGM + * @returns {Object} Объект с проанализированными командами и статистикой + */ +function parseVGMCommands(vgm, dataOffset) { + const commands = {}; + let position = dataOffset; + let totalSamples = 0; + let commandCount = 0; + let writeCommandCount = 0; // Счетчик команд записи данных в чипы (не WAIT команды) + let totalCommandBytes = 0; // Общее количество байт всех команд (включая WAIT) + let lastWritePosition = 0; // Позиция в samples последней команды записи данных в чип + let maxWriteGap = 0; // Максимальный интервал между командами записи в samples + const writeGaps = []; // Все интервалы между командами (для статистики) + + while (position < vgm.length) { + const opcode = vgm[position]; + commandCount++; + + // Обработка команд + if (opcode === 0x66) { + // End of sound data + const key = opcode; + if (!commands[key]) { + commands[key] = { + opcode: `0x${opcode.toString(16).toUpperCase().padStart(2, '0')}`, + name: 'END', + description: 'End of sound data', + count: 0, + samples: 0 + }; + } + commands[key].count++; + totalCommandBytes += 1; + break; + } else if (opcode === 0x61) { + // \"0x61 nn nn : Wait n samples, n can range from 0 to 65535 (approx 1.49 seconds)\" + // Wait n samples - точное ожидание на указанное количество семплов + const samples = vgm.readUInt16LE(position + 1); + const key = `0x61_${samples}`; + + if (!commands[key]) { + commands[key] = { + opcode: '0x61', + name: `WAIT_NN(${samples})`, + description: `Wait ${samples} samples`, + count: 0, + samples: 0, + waitValue: samples + }; + } + commands[key].count++; + commands[key].samples += samples; + totalSamples += samples; + totalCommandBytes += 3; + position += 3; // opcode + 2 bytes для samples + } else if (opcode === 0x62) { + // "0x62 : Wait 735 samples (60th of a second)" + // Стандартная пауза для NTSC (60 Гц) систем: 735/44100 = 16.67ms + if (!commands[opcode]) { + commands[opcode] = { + opcode: `0x${opcode.toString(16).toUpperCase().padStart(2, '0')}`, + name: 'WAIT_60HZ', + description: 'Wait 735 samples (60 Hz)', + count: 0, + samples: 0 + }; + } + commands[opcode].count++; + commands[opcode].samples += 735; + totalSamples += 735; + totalCommandBytes += 1; + + position += 1; + } else if (opcode === 0x63) { + // "0x63 : Wait 882 samples (50th of a second)" + // Стандартная пауза для PAL (50 Гц) систем: 882/44100 = 20ms + if (!commands[opcode]) { + commands[opcode] = { + opcode: `0x${opcode.toString(16).toUpperCase().padStart(2, '0')}`, + name: 'WAIT_50HZ', + description: 'Wait 882 samples (50 Hz)', + count: 0, + samples: 0 + }; + } + commands[opcode].count++; + commands[opcode].samples += 882; + totalSamples += 882; + totalCommandBytes += 1; + position += 1; + } else if (opcode >= 0x70 && opcode <= 0x7F) { + // Wait 1-16 samples + const samples = opcode - 0x6F; + if (!commands[opcode]) { + const cmdInfo = VGM_COMMANDS[opcode]; + commands[opcode] = { + opcode: `0x${opcode.toString(16).toUpperCase().padStart(2, '0')}`, + name: cmdInfo.name, + description: cmdInfo.description, + count: 0, + samples: 0 + }; + } + commands[opcode].count++; + commands[opcode].samples += samples; + totalSamples += samples; + totalCommandBytes += 1; + position += 1; + } else if (opcode >= 0x80 && opcode <= 0x8F) { + // YM2612 PCM data + wait + const samples = opcode - 0x80; + if (!commands[opcode]) { + commands[opcode] = { + opcode: `0x${opcode.toString(16).toUpperCase().padStart(2, '0')}`, + name: `YM2612_DAC_WAIT_${samples}`, + description: `YM2612 DAC write + wait ${samples} samples`, + count: 0, + samples: 0 + }; + } + commands[opcode].count++; + commands[opcode].samples += samples; + totalSamples += samples; + totalCommandBytes += 1; + position += 1; + } else if (opcode === 0x67) { + // Data block: 0x67 0x66 tt ss ss ss ss (data) + if (!commands[opcode]) { + commands[opcode] = { + opcode: `0x${opcode.toString(16).toUpperCase().padStart(2, '0')}`, + name: 'DATA_BLOCK', + description: 'Data block', + count: 0, + samples: 0 + }; + } + commands[opcode].count++; + position += 2; // Skip 0x67 0x66 + const blockType = vgm[position++]; + const blockSize = vgm.readUInt32LE(position); + totalCommandBytes += 2 + 1 + 4 + blockSize; // 0x67 + 0x66 + type + size + data + position += 4 + blockSize; + } else if (opcode === 0x68) { + // PCM RAM write + if (!commands[opcode]) { + commands[opcode] = { + opcode: `0x${opcode.toString(16).toUpperCase().padStart(2, '0')}`, + name: 'PCM_RAM_WRITE', + description: 'PCM RAM write', + count: 0, + samples: 0 + }; + } + commands[opcode].count++; + totalCommandBytes += 12; + position += 12; // Fixed size + } else { + // Стандартные команды записи + const cmdInfo = VGM_COMMANDS[opcode]; + if (!commands[opcode]) { + if (cmdInfo) { + commands[opcode] = { + opcode: `0x${opcode.toString(16).toUpperCase().padStart(2, '0')}`, + name: cmdInfo.name, + description: cmdInfo.description, + count: 0, + samples: 0 + }; + } else { + commands[opcode] = { + opcode: `0x${opcode.toString(16).toUpperCase().padStart(2, '0')}`, + name: `UNKNOWN_0x${opcode.toString(16).toUpperCase()}`, + description: 'Unknown command', + count: 0, + samples: 0 + }; + } + } + commands[opcode].count++; + + // Отслеживаем команды записи (не WAIT, не END, не DATA_BLOCK) + const isWriteCommand = opcode !== 0x66 && opcode !== 0x67 && opcode !== 0x68 && + opcode !== 0x61 && opcode !== 0x62 && opcode !== 0x63 && + (opcode < 0x70 || opcode > 0x7F) && (opcode < 0x80 || opcode > 0x8F); + + if (isWriteCommand) { + writeCommandCount++; + const gap = totalSamples - lastWritePosition; + // Игнорируем задержки > 1000 семплов + if (gap > 0 && gap <= 1000) { + writeGaps.push(gap); + if (gap > maxWriteGap) { + maxWriteGap = gap; + } + } + lastWritePosition = totalSamples; + } + + if (cmdInfo) { + if (cmdInfo.params > 0) { + totalCommandBytes += 1 + cmdInfo.params; + position += 1 + cmdInfo.params; + } else if (cmdInfo.params === 0) { + totalCommandBytes += 1; + position += 1; + } else { + // Variable length - skip safely + totalCommandBytes += 1; + position += 1; + } + } else { + // Unknown command - try to skip + totalCommandBytes += 1; + position += 1; + } + } + + // Защита от бесконечного цикла + if (commandCount > 10000000) { + console.warn('Too many commands, stopping parsing'); + break; + } + } + + // Рассчитываем среднюю задержку + const avgWriteGap = writeGaps.length > 0 + ? writeGaps.reduce((sum, gap) => sum + gap, 0) / writeGaps.length + : 0; + + return { + commands: Object.values(commands).sort((a, b) => b.count - a.count), + totalCommands: commandCount, + totalSamples, + totalSeconds: totalSamples / 44100, + totalCommandBytes: totalCommandBytes, + writeCommands: writeCommandCount, + maxWriteGap: maxWriteGap, + avgWriteGap: avgWriteGap, + maxWriteGapMs: (maxWriteGap / 44100 * 1000).toFixed(2), + avgWriteGapMs: (avgWriteGap / 44100 * 1000).toFixed(2) + }; +} + +/** + * Полный анализ VGM файла согласно спецификации v1.71 + * + * \"Starting at the location specified by the VGM data offset (or, + * offset 0x40 for file versions below 1.50) is found a sequence of commands + * containing data written to the chips or timing information.\" + * + * Выполняет комплексный анализ VGM файла: + * - Парсит заголовок с chip clocks и метаданными + * - Анализирует команды VGM stream + * - Подсчитывает статистику: команды, samples, timing + * - Определяет используемые чипы и их частоты + * - Извлекает информацию о loop points + * + * @param {Buffer} vgmBuffer - Буфер с VGM данными + * @returns {Object} Полный анализ файла с header, commands и статистикой + */ +function analyzeVGM(vgmBuffer) { + const header = parseVGMHeader(vgmBuffer); + const commandData = parseVGMCommands(vgmBuffer, header.dataOffset); + + return { + header, + commands: commandData.commands, + stats: { + totalCommands: commandData.totalCommands, + totalSamples: commandData.totalSamples, + totalCommandBytes: commandData.totalCommandBytes, + calculatedSeconds: commandData.totalSeconds, + writeCommands: commandData.writeCommands, + maxWriteGap: commandData.maxWriteGap, + avgWriteGap: commandData.avgWriteGap, + maxWriteGapMs: commandData.maxWriteGapMs, + avgWriteGapMs: commandData.avgWriteGapMs + } + }; +} + +/** + * Анализ VGZ файла с автоматической распаковкой + * + * \"The normal file extension is .vgm but files can also be GZip compressed into + * .vgz files. However, a VGM player should attempt to support compressed and + * uncompressed files with either extension.\" + * + * VGZ (VGM GZip) файлы содержат стандартные VGM данные, сжатые алгоритмом gzip. + * Эта функция автоматически распаковывает VGZ и выполняет полный анализ VGM, + * добавляя дополнительную информацию о сжатии (степень сжатия, размеры, метаданные gzip). + * + * @param {string} filePath - Путь к VGZ файлу + * @returns {Object} Полный анализ VGM + информация о gzip сжатии + * @throws {Error} При ошибках чтения, распаковки или анализа + */ +function analyzeVGZFile(filePath) { + const { data: vgm, gzipInfo } = readVGZ(filePath); + const analysis = analyzeVGM(vgm); + + // Добавляем информацию о сжатии + analysis.compression = gzipInfo; + + return analysis; +} + +/** + * Декомпилятор VGM файлов с полной поддержкой спецификации v1.71 + * + * \"Starting at the location specified by the VGM data offset (or, + * offset 0x40 for file versions below 1.50) is found a sequence of commands + * containing data written to the chips or timing information.\" + * + * Этот декомпилятор обеспечивает: + * - Полную информацию о заголовке VGM с chip clocks + * - Парсинг GD3 метаданных (track titles, game info, etc) + * - Детекцию loop points и структуры файла + * - Временные метки для навигации по треку + * - Статистику команд и длительности + * - Поддержку dual chip режимов + * + * @param {Buffer} vgmBuffer - Буфер с VGM данными + * @param {Object} options - Опции декомпиляции + * @returns {Object} Результат декомпиляции со всей информацией + */ +function decompileVGM(vgmBuffer, options = {}) { + const { + showTimeMarks = true, // Показывать временные метки каждую секунду + showAllCommands = true, // Показывать ВСЕ команды по умолчанию + maxCommands = 0, // Без ограничений по умолчанию + showStatistics = true, // Показывать статистику + showHeader = true // Показывать полную информацию о заголовке + } = options; + + const header = parseVGMHeader(vgmBuffer); + + // Парсим GD3 теги если есть + let gd3 = null; + if (header.gd3Offset > 0) { + gd3 = parseGD3(vgmBuffer, header.gd3Offset); + } + + const decompilation = { + header: header, + gd3: gd3, + commands: [], + statistics: { + totalCommands: 0, + totalSamples: 0, + totalSeconds: 0, + writeCommands: 0, + waitCommands: 0, + dataBlocks: 0, + timeMarks: [], + loopFound: false, + loopSample: 0 + } + }; + + // \"VGM files use a sample rate of 44100 Hz. All wait times are given as a + // number of samples to wait at this rate.\" + // + // VGM стандарт использует фиксированную частоту дискретизации 44,1 кГц для всех timing операций + const SAMPLE_RATE = 44100; + const dataOffset = header.dataOffset; + + let position = dataOffset; + let currentSamples = 0; + let lastTimeMarkSeconds = 0; + let commandIndex = 0; + let commandNumber = 1; // Номер команды для вывода (начинаем с 1) + + // Вычисляем позицию лупа в samples + const loopSample = header.totalSamples - header.loopSamples; + if (header.hasLoop && header.loopSamples > 0) { + decompilation.statistics.loopSample = loopSample; + decompilation.statistics.loopFound = true; + } + + /** + * Добавляет команду в декомпилированный VGM вывод + * + * Центральная функция для записи всех VGM команд в результирующий декомпилированный + * код. Учитывает ограничения на количество команд (maxCommands) и опцию showAllCommands. + * Также ведет статистику общего количества команд. + * + * @param {Object} cmd - Объект команды с полями type, position, offset, raw, mnemonic, description, dataBytes + */ + function addCommand(cmd) { + if (maxCommands === 0 || showAllCommands || decompilation.commands.length < maxCommands) { + // Специальная обработка для временных меток и меток петли + if (cmd.type === 'TIME_MARK' || cmd.type === 'LOOP_MARK') { + cmd.command = cmd.command; // Используем готовый форматированный текст + } else { + // Форматируем обычные команды согласно новому формату + cmd.command = formatCommand(commandNumber, cmd.raw || [cmd.opcode || 0], cmd.mnemonic || cmd.description, cmd.dataBytes); + commandNumber++; // Увеличиваем номер команды только для обычных команд + } + decompilation.commands.push(cmd); + } + + // Статистика для всех команд, кроме временных меток + if (cmd.type !== 'TIME_MARK' && cmd.type !== 'LOOP_MARK') { + decompilation.statistics.totalCommands++; + } + } + + /** + * Добавляет временные метки в декомпилированный код + * + * \"VGM files use a sample rate of 44100 Hz. All wait times are given as a + * number of samples to wait at this rate.\" + * + * Добавляет визуальные временные метки каждую секунду для улучшения читаемости + * декомпилированного VGM кода. Помогает ориентироваться во времени воспроизведения. + * + * @param {number} samples - Текущее количество samples с начала воспроизведения + */ + function addTimeMark(samples) { + const seconds = Math.floor(samples / SAMPLE_RATE); + if (seconds > lastTimeMarkSeconds) { + const timeStr = formatTime(seconds); + addCommand({ + type: 'TIME_MARK', + position: position, + offset: `0x${position.toString(16).toUpperCase()}`, + samples: samples, + seconds: seconds, + timeString: timeStr, + command: `\n*** time ${timeStr} ***********`, + description: `Временная метка` + }); + decompilation.statistics.timeMarks.push(seconds); + lastTimeMarkSeconds = seconds; + } + } + + /** + * Проверяет и отмечает точку начала цикла (loop point) + * + * \"4+4 bytes: Loop offset (32 bits, starting from 0x1C) + * Relative offset to loop point, or 0 if no looping. This is added to the VGM + * position 0x1C (where the VGM header ends) to get the absolute position from the + * beginning of the file.\" + * + * VGM файлы могут содержать информацию о цикле (loop). Эта функция следит за + * текущей позицией в samples и добавляет визуальную метку в декомпилированный + * код когда достигается точка начала цикла. + * + * @param {number} samples - Текущее количество samples с начала воспроизведения + */ + function checkLoopPoint(samples) { + if (decompilation.statistics.loopFound && samples >= loopSample && !decompilation.loopMarkAdded) { + const loopTimeStr = formatTime(Math.floor(loopSample / SAMPLE_RATE)); + const loopSampleStr = loopSample.toString(); + addCommand({ + type: 'LOOP_MARK', + position: position, + offset: `0x${position.toString(16).toUpperCase()}`, + samples: loopSample, + seconds: loopSample / SAMPLE_RATE, + timeString: loopTimeStr, + command: `\n*** LOOP POINT ${loopTimeStr} ***********`, + description: `Точка начала цикла` + }); + decompilation.loopMarkAdded = true; + } + } + + // Helper function для создания временной метки + function createTimeMark(position, samples, seconds) { + const timeStr = formatTime(seconds); + return { + type: 'TIME_MARK', + position: position, + offset: `0x${position.toString(16).toUpperCase()}`, + samples: samples, + seconds: seconds, + timeString: timeStr, + command: `\n***************** ${timeStr} **************`, + description: `Временная метка` + }; + } + + /** + * Форматирует команду VGM в декомпилированный вид + * + * Формат: 0000001: 62 00 10 WAIT (31) + * ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ + * номер hex байты + мнемоника + * + * @param {number} commandNumber - Номер команды (начиная с 1) + * @param {Array} rawBytes - Байты команды + * @param {string} mnemonic - Мнемоника команды + * @param {Array} dataBytes - Дополнительные данные (для массивов) + * @returns {string} Отформатированная строка команды + */ + function formatCommand(commandNumber, rawBytes, mnemonic, dataBytes = null) { + const number = commandNumber.toString().padStart(7, '0'); + const hexBytes = rawBytes.map(b => b.toString(16).toUpperCase().padStart(2, '0')).join(' '); + const hex = hexBytes.padEnd(24, ' '); // Выравнивание для hex части + let result = `${number}: ${hex}${mnemonic}`; + + // Если есть дополнительные данные, выводим их отдельными строками по 16 байт + if (dataBytes && dataBytes.length > 0) { + result += '\n'; // Пустая строка перед данными + for (let i = 0; i < dataBytes.length; i += 16) { + const chunk = dataBytes.slice(i, i + 16); + const hexChunk = chunk.map(b => b.toString(16).toUpperCase().padStart(2, '0')).join(' '); + const padding = ' '.repeat(16); // Отступ для выравнивания с основной командой + result += `${padding}${hexChunk}`; + if (i + 16 < dataBytes.length) { + result += '\n'; + } + } + result += '\n'; // Пустая строка после данных + } + + return result; + } + + /** + * Форматирует время в формате MM:SS + * + * Используется для отображения временных меток в декомпилированном VGM. + * Преобразует количество секунд в стандартный формат времени. + * + * @param {number} totalSeconds - Общее количество секунд + * @returns {string} Время в формате "MM:SS" + */ + function formatTime(totalSeconds) { + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + } + + while (position < vgmBuffer.length && (maxCommands === 0 || commandIndex < maxCommands * 2)) { + const opcode = vgmBuffer[position]; + commandIndex++; + + // Проверяем точку лупа + checkLoopPoint(currentSamples); + + // Добавляем временную метку каждую секунду + if (showTimeMarks) { + addTimeMark(currentSamples); + } + + const cmd = { + type: 'COMMAND', + position: position, + offset: `0x${position.toString(16).toUpperCase().padStart(6, '0')}`, + opcode: `0x${opcode.toString(16).toUpperCase().padStart(2, '0')}`, + samples: currentSamples, + ticks: currentSamples, + seconds: (currentSamples / SAMPLE_RATE).toFixed(3), + raw: [], + parameters: [], + command: '', + description: '' + }; + + // Парсинг команд по спецификации VGM v1.71 + if (opcode === 0x66) { + // End of sound data + cmd.raw = [opcode]; + cmd.mnemonic = 'END'; + cmd.description = 'End of sound data'; + addCommand(cmd); + break; + + } else if (opcode === 0x67) { + // \"0x67 0x66 tt ss ss ss ss (data)\" + // Data block: contains compressed data or large data arrays + if (position + 6 < vgmBuffer.length) { + const blockMarker = vgmBuffer[position + 1]; // Should be 0x66 + const blockType = vgmBuffer[position + 2]; + const blockSize = vgmBuffer.readUInt32LE(position + 3); + + // Команда + маркер + тип + байты размера в основной строке + const sizeBytes = Array.from(vgmBuffer.subarray(position + 3, position + 7)); + cmd.raw = [opcode, blockMarker, blockType, ...sizeBytes]; + + // В dataBytes только сами данные, без заголовков + if (blockSize > 0 && position + 7 + blockSize <= vgmBuffer.length) { + const dataArray = Array.from(vgmBuffer.subarray(position + 7, position + 7 + blockSize)); + cmd.dataBytes = dataArray; + cmd.mnemonic = `DATA_BLOCK (type: 0x${blockType.toString(16).toUpperCase()}, size: ${blockSize})`; + cmd.description = `Data block: type=${blockType}, size=${blockSize} bytes`; + position += 7 + blockSize; + } else { + cmd.dataBytes = null; // Нет данных, только заголовок + cmd.mnemonic = `DATA_BLOCK_HEADER (type: 0x${blockType.toString(16).toUpperCase()}, size: ${blockSize})`; + cmd.description = `Data block header only: type=${blockType}, size=${blockSize} bytes`; + position += 7; + } + + decompilation.statistics.dataBlocks++; + addCommand(cmd); + } else { + // Incomplete data block + cmd.raw = [opcode]; + cmd.mnemonic = 'DATA_BLOCK_INCOMPLETE'; + cmd.description = 'Incomplete data block header'; + addCommand(cmd); + position++; + } + + } else if (opcode >= 0x70 && opcode <= 0x7F) { + // \"0x7n : wait n+1 samples, n can range from 0 to 15.\" + // Короткое ожидание: 0x70 = 1 sample, 0x7F = 16 samples + const waitSamples = (opcode & 0x0F) + 1; + cmd.raw = [opcode]; + cmd.mnemonic = `WAIT (${waitSamples})`; + cmd.description = `Wait ${waitSamples} ticks (${(waitSamples / SAMPLE_RATE * 1000).toFixed(1)}ms)`; + currentSamples += waitSamples; + decompilation.statistics.waitCommands++; + addCommand(cmd); + position++; + + } else if (opcode === 0x61) { + // Wait n samples + if (position + 2 < vgmBuffer.length) { + const waitSamples = vgmBuffer.readUInt16LE(position + 1); + cmd.raw = [opcode, vgmBuffer[position + 1], vgmBuffer[position + 2]]; + cmd.mnemonic = `WAIT (${waitSamples})`; + cmd.description = `Wait ${waitSamples} ticks (${(waitSamples / SAMPLE_RATE * 1000).toFixed(1)}ms)`; + currentSamples += waitSamples; + decompilation.statistics.waitCommands++; + addCommand(cmd); + position += 3; + } else break; + + } else if (opcode === 0x62) { + // "0x62 : wait 735 samples (60th of a second), a shortcut for + // 0x61 0xdf 0x02" + const waitSamples = 735; + cmd.raw = [opcode]; + cmd.mnemonic = `WAIT60 (${waitSamples})`; + cmd.description = `Wait ${waitSamples} ticks (16.7ms, 60Hz)`; + currentSamples += waitSamples; + decompilation.statistics.waitCommands++; + addCommand(cmd); + position++; + + } else if (opcode === 0x63) { + // Wait 882 samples (50Hz) + const waitSamples = 882; + cmd.raw = [opcode]; + cmd.mnemonic = `WAIT50 (${waitSamples})`; + cmd.description = `Wait ${waitSamples} ticks (20.0ms, 50Hz)`; + currentSamples += waitSamples; + addCommand(cmd); + position++; + + } else if (opcode >= 0x80 && opcode <= 0x8F) { + // YM2612 DAC + wait + const waitSamples = opcode & 0x0F; + cmd.raw = [opcode]; + cmd.mnemonic = `YM2612DAC_WAIT (${waitSamples})`; + cmd.description = `YM2612 DAC write then wait ${waitSamples} samples`; + currentSamples += waitSamples; + decompilation.statistics.writeCommands++; + addCommand(cmd); + position++; + + // Чип команды записи (2 параметра) + } else if ([0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0xA0, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF].includes(opcode)) { + if (position + 2 < vgmBuffer.length) { + const reg = vgmBuffer[position + 1]; + const data = vgmBuffer[position + 2]; + cmd.raw = [opcode, reg, data]; + cmd.parameters = [`0x${reg.toString(16).toUpperCase().padStart(2, '0')}`, `0x${data.toString(16).toUpperCase().padStart(2, '0')}`]; + + // Определяем тип чипа по опкоду + const chipCommands = { + 0x50: 'PSG', 0x51: 'YM2413', 0x52: 'YM2612 Port 0', 0x53: 'YM2612 Port 1', + 0x54: 'YM2151', 0x55: 'YM2203', 0x56: 'YM2608 Port 0', 0x57: 'YM2608 Port 1', + 0x58: 'YM2610 Port 0', 0x59: 'YM2610 Port 1', 0x5A: 'YM3812', 0x5B: 'YM3526', + 0x5C: 'Y8950', 0x5D: 'YMZ280B', 0x5E: 'YMF262 Port 0', 0x5F: 'YMF262 Port 1', + 0xA0: 'AY8910', 0xB0: 'RF5C68', 0xB1: 'RF5C164', 0xB2: 'PWM', 0xB3: 'GameBoy DMG', + 0xB4: 'NES APU', 0xB5: 'MultiPCM', 0xB6: 'uPD7759', 0xB7: 'OKIM6258', 0xB8: 'OKIM6295', + 0xB9: 'HuC6280', 0xBA: 'K053260', 0xBB: 'Pokey', 0xBC: 'WonderSwan', 0xBD: 'SAA1099', + 0xBE: 'ES5506', 0xBF: 'GA20' + }; + + const chipName = chipCommands[opcode] || 'Unknown'; + cmd.mnemonic = `${chipName} (0x${reg.toString(16).toUpperCase().padStart(2, '0')}, 0x${data.toString(16).toUpperCase().padStart(2, '0')})`; + cmd.description = `${chipName} write: reg=0x${reg.toString(16).toUpperCase()}, data=0x${data.toString(16).toUpperCase()}`; + decompilation.statistics.writeCommands++; + addCommand(cmd); + position += 3; + } else break; + + } else { + // Неизвестная или нереализованная команда + cmd.raw = [opcode]; + cmd.mnemonic = `UNKNOWN (0x${opcode.toString(16).toUpperCase()})`; + cmd.description = `Unknown/Unimplemented command`; + addCommand(cmd); + position++; + } + } + + // Финальные статистики + decompilation.statistics.totalSamples = currentSamples; + decompilation.statistics.totalSeconds = (currentSamples / SAMPLE_RATE).toFixed(3); + + return decompilation; +} + +module.exports = { + readVGZ, + parseVGMHeader, + parseVGMCommands, + parseVGMHeader, + analyzeVGM, + analyzeVGZFile, + analyzeGZIP, + decompileVGM, + CHIP_OFFSETS, + VGM_COMMANDS +}; + +// =================================================================== +// CLI Interface +// =================================================================== + +/** + * Распаковка одного VGZ файла в VGM + * @param {string} vgzPath - Путь к VGZ файлу + * @param {string} outputPath - Путь для сохранения VGM файла (необязательно) + */ +function unpackVGZ(vgzPath, outputPath) { + try { + // Резолвим путь относительно текущего рабочего каталога + const resolvedVgzPath = path.resolve(vgzPath); + + console.log(`Unpacking: ${vgzPath}`); + + const { data: vgmData, gzipInfo } = readVGZ(resolvedVgzPath); + + // Определяем имя выходного файла + if (!outputPath) { + outputPath = path.resolve(vgzPath.replace(/\.vgz$/i, '.vgm')); + } else { + outputPath = path.resolve(outputPath); + } + + // Записываем VGM данные + fs.writeFileSync(outputPath, vgmData); + + console.log(`✓ Unpacked: ${outputPath}`); + console.log(` Compressed: ${gzipInfo.compressedSize} bytes`); + console.log(` Uncompressed: ${gzipInfo.uncompressedSize} bytes`); + console.log(` Compression ratio: ${gzipInfo.compressionRatioText}`); + + return true; + } catch (error) { + console.error(`✗ Error unpacking ${vgzPath}: ${error.message}`); + return false; + } +} + +/** + * Распаковка всех VGZ файлов в текущем каталоге + * @param {string} dirPath - Путь к каталогу (по умолчанию текущий) + */ +function unpackAllVGZ(dirPath = '.') { + try { + // Резолвим путь относительно текущего рабочего каталога + const resolvedDirPath = path.resolve(dirPath); + + const files = fs.readdirSync(resolvedDirPath); + const vgzFiles = files.filter(file => file.toLowerCase().endsWith('.vgz')); + + if (vgzFiles.length === 0) { + console.log('No .vgz files found in current directory'); + return; + } + + console.log(`Found ${vgzFiles.length} VGZ files to unpack:`); + + let successful = 0; + let failed = 0; + + vgzFiles.forEach(vgzFile => { + const vgzPath = path.join(resolvedDirPath, vgzFile); + if (unpackVGZ(vgzPath)) { + successful++; + } else { + failed++; + } + }); + + console.log('\n' + '='.repeat(50)); + console.log(`Unpacking complete: ${successful} successful, ${failed} failed`); + + } catch (error) { + console.error(`Error reading directory: ${error.message}`); + } +} + +/** + * Отображение информации о заголовке VGM/VGZ файла + * @param {string} filePath - Путь к файлу + */ +function showHeader(filePath) { + try { + // Резолвим путь относительно текущего рабочего каталога + const resolvedPath = path.resolve(filePath); + + let vgmData; + let compressionInfo = null; + + if (filePath.toLowerCase().endsWith('.vgz')) { + const result = readVGZ(resolvedPath); + vgmData = result.data; + compressionInfo = result.gzipInfo; + } else { + vgmData = fs.readFileSync(resolvedPath); + } + + const header = parseVGMHeader(vgmData); + + console.log('='.repeat(60)); + console.log(`VGM Header Information: ${path.basename(filePath)}`); + console.log('='.repeat(60)); + + if (compressionInfo) { + console.log('Compression Info:'); + console.log(` Format: VGZ (gzip compressed VGM)`); + console.log(` Compressed size: ${compressionInfo.compressedSize} bytes`); + console.log(` Uncompressed size: ${compressionInfo.uncompressedSize} bytes`); + console.log(` Compression ratio: ${compressionInfo.compressionRatioText}`); + console.log(` OS: ${compressionInfo.os}`); + console.log(''); + } + + console.log('VGM Header:'); + console.log(` Format: ${header.signature}`); + console.log(` Version: ${header.versionString} (${header.versionHex})`); + console.log(` File size: ${header.eofOffset} bytes`); + console.log(` Data offset: 0x${header.dataOffset.toString(16).toUpperCase()}`); + console.log(''); + + console.log('Timing:'); + console.log(` Total samples: ${header.totalSamples}`); + console.log(` Total time: ${header.totalSeconds.toFixed(2)} seconds`); + console.log(` Sample rate: ${header.sampleRate} Hz`); + + if (header.hasLoop) { + console.log(` Loop offset: 0x${header.loopOffset.toString(16).toUpperCase()}`); + console.log(` Loop samples: ${header.loopSamples}`); + console.log(` Loop time: ${header.loopSeconds.toFixed(2)} seconds`); + console.log(` Intro time: ${header.introSeconds.toFixed(2)} seconds`); + } else { + console.log(` Loop: No loop`); + } + console.log(''); + + console.log('Sound Chips:'); + header.chips.forEach(chip => { + const dualText = chip.isDualChip ? ' (Dual Chip)' : ''; + const clockText = typeof chip.clockMHz === 'number' ? `${chip.clockMHz.toFixed(3)} MHz` : '(clock info unavailable)'; + console.log(` ${chip.name}: ${clockText}${dualText}`); + }); + + if (header.gd3Offset > 0) { + console.log(''); + console.log('GD3 Tags:'); + try { + const gd3 = parseGD3(vgmData, header.gd3Offset); + console.log(` Track: ${gd3.trackTitle || 'N/A'}`); + console.log(` Game: ${gd3.gameName || 'N/A'}`); + console.log(` System: ${gd3.systemName || 'N/A'}`); + console.log(` Author: ${gd3.trackAuthor || 'N/A'}`); + console.log(` Date: ${gd3.date || 'N/A'}`); + console.log(` Ripper: ${gd3.ripper || 'N/A'}`); + } catch (err) { + console.log(` Error reading GD3: ${err.message}`); + } + } + + } catch (error) { + console.error(`Error reading file ${filePath}: ${error.message}`); + } +} + +/** + * Декомпиляция VGM/VGZ файла + * @param {string} filePath - Путь к файлу + * @param {Object} options - Опции декомпиляции + */ +function decompileFile(filePath, options = {}) { + try { + // Резолвим путь относительно текущего рабочего каталога + const resolvedPath = path.resolve(filePath); + + let vgmData; + + if (filePath.toLowerCase().endsWith('.vgz')) { + const result = readVGZ(resolvedPath); + vgmData = result.data; + } else { + vgmData = fs.readFileSync(resolvedPath); + } + + const decompileOptions = { + maxCommands: options.maxCommands !== undefined ? options.maxCommands : 0, // 0 = без ограничений + showTimeMarks: options.showTimeMarks !== false, + showAllCommands: options.showAllCommands || false + }; + + const decompilation = decompileVGM(vgmData, decompileOptions); + + // Выводим заголовок + console.log(`; VGM Decompilation: ${path.basename(filePath)}`); + console.log(`; Generated by VGM-lib v1.71`); + console.log(`; File: ${filePath}`); + console.log(`; Size: ${vgmData.length} bytes`); + console.log(`; Format: ${decompilation.header.signature} ${decompilation.header.versionString}`); + console.log(`;`); + + // Выводим информацию об использованных чипах + if (decompilation.header.chips.length > 0) { + console.log(`; Sound chips:`); + decompilation.header.chips.forEach(chip => { + const dualText = chip.isDualChip ? ' (Dual)' : ''; + const clockText = typeof chip.clockMHz === 'number' ? `${chip.clockMHz.toFixed(3)} MHz` : '(clock info unavailable)'; + console.log(`; ${chip.name}: ${clockText}${dualText}`); + }); + console.log(`;`); + } + + // Выводим статистику + console.log(`; Statistics:`); + console.log(`; Total commands: ${decompilation.statistics.totalCommands}`); + console.log(`; Total samples: ${decompilation.statistics.totalSamples}`); + console.log(`; Total time: ${decompilation.statistics.totalSeconds} seconds`); + + if (decompilation.statistics.loopFound) { + console.log(`; Loop sample: ${decompilation.statistics.loopSample}`); + } + console.log(`;`); + console.log(''); + + // Выводим команды + decompilation.commands.forEach(cmd => { + console.log(cmd.command); + }); + + } catch (error) { + console.error(`Error decompiling file ${filePath}: ${error.message}`); + } +} + +/** + * Показать справку по CLI командам + */ +function showHelp() { + console.log('VGM Library CLI v1.71'); + console.log('Usage: vgm [options]'); + console.log(''); + console.log('Commands:'); + console.log(' --header Show VGM/VGZ header information'); + console.log(' --decompile Decompile VGM/VGZ file to console'); + console.log(' --unpack Unpack single VGZ file to VGM'); + console.log(' --unpack-all [dir] Unpack all VGZ files in directory'); + console.log(' --help Show this help'); + console.log(''); + console.log('Decompile options:'); + console.log(' --max-commands Limit decompiled commands (default: unlimited)'); + console.log(' --no-time-marks Disable time markers in decompilation'); + console.log(' --show-all Show all commands (ignore max-commands limit)'); + console.log(''); + console.log('Examples:'); + console.log(' vgm --header song.vgm'); + console.log(' vgm --decompile song.vgz > song_decompiled.txt'); + console.log(' vgm --unpack song.vgz'); + console.log(' vgm --unpack-all'); + console.log(' vgm --decompile song.vgm --max-commands 500 --no-time-marks'); +} + +// CLI Entry Point +if (require.main === module) { + const args = process.argv.slice(2); + + if (args.length === 0 || args.includes('--help') || args.includes('-h')) { + showHelp(); + process.exit(0); + } + + const command = args[0]; + + switch (command) { + case '--header': + if (args.length < 2) { + console.error('Error: --header requires file path'); + console.error('Usage: vgm --header '); + process.exit(1); + } + showHeader(args[1]); + break; + + case '--decompile': + if (args.length < 2) { + console.error('Error: --decompile requires file path'); + console.error('Usage: vgm --decompile [options]'); + process.exit(1); + } + + const decompileOptions = {}; + + // Парсим опции декомпиляции + for (let i = 2; i < args.length; i++) { + if (args[i] === '--max-commands' && i + 1 < args.length) { + decompileOptions.maxCommands = parseInt(args[i + 1]); + i++; // skip next arg + } else if (args[i] === '--no-time-marks') { + decompileOptions.showTimeMarks = false; + } else if (args[i] === '--show-all') { + decompileOptions.showAllCommands = true; + } + } + + decompileFile(args[1], decompileOptions); + break; + + case '--unpack': + if (args.length < 2) { + console.error('Error: --unpack requires file path'); + console.error('Usage: vgm --unpack '); + process.exit(1); + } + unpackVGZ(args[1]); + break; + + case '--unpack-all': + const targetDir = args.length >= 2 ? args[1] : '.'; + unpackAllVGZ(targetDir); + break; + + default: + console.error(`Unknown command: ${command}`); + console.error('Use --help to see available commands'); + process.exit(1); + } +} diff --git a/build/wc_api.lib b/build/wc_api.lib new file mode 100644 index 0000000..435b1aa Binary files /dev/null and b/build/wc_api.lib differ diff --git a/build/wc_api/wc_adir.lst b/build/wc_api/wc_adir.lst new file mode 100644 index 0000000..e6318e4 --- /dev/null +++ b/build/wc_api/wc_adir.lst @@ -0,0 +1,11 @@ + 1 .module wc_adir + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_adir + 00000000 5 _wc_adir:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 38 [ 7] 8 ld a, #0x38 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_adir.rel b/build/wc_api/wc_adir.rel new file mode 100644 index 0000000..2702843 --- /dev/null +++ b/build/wc_api/wc_adir.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_adir +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_adir Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 38 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_adir.sym b/build/wc_api/wc_adir.sym new file mode 100644 index 0000000..bf8b374 --- /dev/null +++ b/build/wc_api/wc_adir.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_adir 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_chtosep.lst b/build/wc_api/wc_chtosep.lst new file mode 100644 index 0000000..268c211 --- /dev/null +++ b/build/wc_api/wc_chtosep.lst @@ -0,0 +1,13 @@ + 1 .module wc_chtosep + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_chtosep + 00000000 5 _wc_chtosep:: + 00000000 DD E5 [15] 6 push ix + 00000002 42 [ 4] 7 ld b, d + 00000003 4B [ 4] 8 ld c, e ; BC = bufend (from DE) + 00000004 EB [ 4] 9 ex de, hl ; DE = buf (from HL) + 00000005 3E 34 [ 7] 10 ld a, #0x34 + 00000007 CD 06 60 [17] 11 call WC_ENTRY + 0000000A DD E1 [14] 12 pop ix + 0000000C C9 [10] 13 ret diff --git a/build/wc_api/wc_chtosep.rel b/build/wc_api/wc_chtosep.rel new file mode 100644 index 0000000..a4d4523 --- /dev/null +++ b/build/wc_api/wc_chtosep.rel @@ -0,0 +1,12 @@ +XL4 +H 1 areas 2 global symbols +M wc_chtosep +S .__.ABS. Def00000000 +A _CODE size D flags 0 addr 0 +S _wc_chtosep Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 42 4B EB 3E 34 CD 06 60 DD E1 +R 00 00 00 00 +T 0C 00 00 00 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_chtosep.sym b/build/wc_api/wc_chtosep.sym new file mode 100644 index 0000000..db3e181 --- /dev/null +++ b/build/wc_api/wc_chtosep.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_chto 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size D flags 0 + diff --git a/build/wc_api/wc_curser.lst b/build/wc_api/wc_curser.lst new file mode 100644 index 0000000..d4680d4 --- /dev/null +++ b/build/wc_api/wc_curser.lst @@ -0,0 +1,12 @@ + 1 .module wc_curser + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_curser + 00000000 5 _wc_curser:: + 00000000 DD E5 [15] 6 push ix + 00000002 E5 [11] 7 push hl + 00000003 DD E1 [14] 8 pop ix + 00000005 3E 07 [ 7] 9 ld a, #0x07 + 00000007 CD 06 60 [17] 10 call WC_ENTRY + 0000000A DD E1 [14] 11 pop ix + 0000000C C9 [10] 12 ret diff --git a/build/wc_api/wc_curser.rel b/build/wc_api/wc_curser.rel new file mode 100644 index 0000000..9ed4e49 --- /dev/null +++ b/build/wc_api/wc_curser.rel @@ -0,0 +1,12 @@ +XL4 +H 1 areas 2 global symbols +M wc_curser +S .__.ABS. Def00000000 +A _CODE size D flags 0 addr 0 +S _wc_curser Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 E5 DD E1 3E 07 CD 06 60 DD E1 +R 00 00 00 00 +T 0C 00 00 00 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_curser.sym b/build/wc_api/wc_curser.sym new file mode 100644 index 0000000..fcba247 --- /dev/null +++ b/build/wc_api/wc_curser.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_curs 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size D flags 0 + diff --git a/build/wc_api/wc_cursor.lst b/build/wc_api/wc_cursor.lst new file mode 100644 index 0000000..b617928 --- /dev/null +++ b/build/wc_api/wc_cursor.lst @@ -0,0 +1,12 @@ + 1 .module wc_cursor + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_cursor + 00000000 5 _wc_cursor:: + 00000000 DD E5 [15] 6 push ix + 00000002 E5 [11] 7 push hl + 00000003 DD E1 [14] 8 pop ix + 00000005 3E 06 [ 7] 9 ld a, #0x06 + 00000007 CD 06 60 [17] 10 call WC_ENTRY + 0000000A DD E1 [14] 11 pop ix + 0000000C C9 [10] 12 ret diff --git a/build/wc_api/wc_cursor.rel b/build/wc_api/wc_cursor.rel new file mode 100644 index 0000000..d767cd7 --- /dev/null +++ b/build/wc_api/wc_cursor.rel @@ -0,0 +1,12 @@ +XL4 +H 1 areas 2 global symbols +M wc_cursor +S .__.ABS. Def00000000 +A _CODE size D flags 0 addr 0 +S _wc_cursor Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 E5 DD E1 3E 06 CD 06 60 DD E1 +R 00 00 00 00 +T 0C 00 00 00 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_cursor.sym b/build/wc_api/wc_cursor.sym new file mode 100644 index 0000000..fcba247 --- /dev/null +++ b/build/wc_api/wc_cursor.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_curs 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size D flags 0 + diff --git a/build/wc_api/wc_delfl.lst b/build/wc_api/wc_delfl.lst new file mode 100644 index 0000000..3405905 --- /dev/null +++ b/build/wc_api/wc_delfl.lst @@ -0,0 +1,10 @@ + 1 .module wc_delfl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_delfl + 00000000 5 _wc_delfl:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 4B [ 7] 7 ld a, #0x4B + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_delfl.rel b/build/wc_api/wc_delfl.rel new file mode 100644 index 0000000..608c26d --- /dev/null +++ b/build/wc_api/wc_delfl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_delfl +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_delfl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 4B CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_delfl.sym b/build/wc_api/wc_delfl.sym new file mode 100644 index 0000000..b84fe4d --- /dev/null +++ b/build/wc_api/wc_delfl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_delf 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_dmapl.lst b/build/wc_api/wc_dmapl.lst new file mode 100644 index 0000000..733c2c4 --- /dev/null +++ b/build/wc_api/wc_dmapl.lst @@ -0,0 +1,11 @@ + 1 .module wc_dmapl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_dmapl + 00000000 5 _wc_dmapl:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 0D [ 7] 8 ld a, #0x0D + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_dmapl.rel b/build/wc_api/wc_dmapl.rel new file mode 100644 index 0000000..5430aa3 --- /dev/null +++ b/build/wc_api/wc_dmapl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_dmapl +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_dmapl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 0D CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_dmapl.sym b/build/wc_api/wc_dmapl.sym new file mode 100644 index 0000000..b7d740b --- /dev/null +++ b/build/wc_api/wc_dmapl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_dmap 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_fentry.lst b/build/wc_api/wc_fentry.lst new file mode 100644 index 0000000..7112b28 --- /dev/null +++ b/build/wc_api/wc_fentry.lst @@ -0,0 +1,15 @@ + 1 .module wc_fentry + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_fentry + 00000000 5 _wc_fentry:: + 00000000 DD E5 [15] 6 push ix + 7 ; HL = name (sdcccall(1); WC нужен HL — уже на месте!) + 00000002 3E 3B [ 7] 8 ld a, #0x3B + 00000004 CD 06 60 [17] 9 call WC_ENTRY + 00000007 3E 00 [ 7] 10 ld a, #0 + 00000009 28 01 [12] 11 jr z, _wc_fentry_end + 0000000B 3C [ 4] 12 inc a + 0000000C 13 _wc_fentry_end: + 0000000C DD E1 [14] 14 pop ix + 0000000E C9 [10] 15 ret diff --git a/build/wc_api/wc_fentry.rel b/build/wc_api/wc_fentry.rel new file mode 100644 index 0000000..2bf105a --- /dev/null +++ b/build/wc_api/wc_fentry.rel @@ -0,0 +1,14 @@ +XL4 +H 1 areas 2 global symbols +M wc_fentry +S .__.ABS. Def00000000 +A _CODE size F flags 0 addr 0 +S _wc_fentry Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 3B CD 06 60 3E 00 28 01 3C +R 00 00 00 00 +T 0C 00 00 00 +R 00 00 00 00 +T 0C 00 00 00 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_fentry.sym b/build/wc_api/wc_fentry.sym new file mode 100644 index 0000000..9009d3d --- /dev/null +++ b/build/wc_api/wc_fentry.sym @@ -0,0 +1,16 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_fent 00000000 GR + 0 _wc_fent 0000000C R + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size F flags 0 + diff --git a/build/wc_api/wc_findnext.lst b/build/wc_api/wc_findnext.lst new file mode 100644 index 0000000..0b0fae4 --- /dev/null +++ b/build/wc_api/wc_findnext.lst @@ -0,0 +1,22 @@ + 1 .module wc_findnext + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_findnext + 00000000 5 _wc_findnext:: + 00000000 DD E5 [15] 6 push ix + 00000002 DD 21 00 00 [14] 7 ld ix, #0 + 00000006 DD 39 [15] 8 add ix, sp + 00000008 DD 7E 04 [19] 9 ld a, 4(ix) ; A = flags (from stack) + 0000000B 08 [ 4] 10 ex af, af' ; A' = flags + 0000000C EB [ 4] 11 ex de, hl ; DE = entry_buf (SDCC HL → WC DE) + 0000000D 3E 3A [ 7] 12 ld a, #0x3A + 0000000F CD 06 60 [17] 13 call WC_ENTRY + 00000012 3E 00 [ 7] 14 ld a, #0 + 00000014 28 01 [12] 15 jr z, _wc_findnext_end + 00000016 3C [ 4] 16 inc a + 00000017 17 _wc_findnext_end: + 00000017 DD E1 [14] 18 pop ix + 19 ; callee cleans 1 byte (flags) + 00000019 E1 [10] 20 pop hl + 0000001A 33 [ 6] 21 inc sp + 0000001B E9 [ 4] 22 jp (hl) diff --git a/build/wc_api/wc_findnext.rel b/build/wc_api/wc_findnext.rel new file mode 100644 index 0000000..e22a1df --- /dev/null +++ b/build/wc_api/wc_findnext.rel @@ -0,0 +1,16 @@ +XL4 +H 1 areas 2 global symbols +M wc_findnext +S .__.ABS. Def00000000 +A _CODE size 1C flags 0 addr 0 +S _wc_findnext Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 DD 21 00 00 DD 39 DD 7E 04 08 +R 00 00 00 00 +T 0C 00 00 00 EB 3E 3A CD 06 60 3E 00 28 01 3C +R 00 00 00 00 +T 17 00 00 00 +R 00 00 00 00 +T 17 00 00 00 DD E1 E1 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_findnext.sym b/build/wc_api/wc_findnext.sym new file mode 100644 index 0000000..3ee159c --- /dev/null +++ b/build/wc_api/wc_findnext.sym @@ -0,0 +1,16 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_find 00000000 GR + 0 _wc_find 00000017 R + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 1C flags 0 + diff --git a/build/wc_api/wc_gadrw.lst b/build/wc_api/wc_gadrw.lst new file mode 100644 index 0000000..c14bfe5 --- /dev/null +++ b/build/wc_api/wc_gadrw.lst @@ -0,0 +1,25 @@ + 1 .module wc_gadrw + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_gadrw + 00000000 5 _wc_gadrw:: + 00000000 DD E5 [15] 6 push ix + 00000002 E5 [11] 7 push hl ; save win (HL = 1st arg) + 00000003 DD 21 00 00 [14] 8 ld ix, #0 + 00000007 DD 39 [15] 9 add ix, sp + 00000009 DD 56 06 [19] 10 ld d, 6(ix) ; D = y + 0000000C DD 5E 07 [19] 11 ld e, 7(ix) ; E = x + 0000000F DD 6E 00 [19] 12 ld l, 0(ix) + 00000012 DD 66 01 [19] 13 ld h, 1(ix) ; HL = win + 00000015 E5 [11] 14 push hl + 00000016 DD E1 [14] 15 pop ix ; IX = win + 00000018 3E 05 [ 7] 16 ld a, #0x05 + 0000001A CD 06 60 [17] 17 call WC_ENTRY ; HL = result addr + 0000001D EB [ 4] 18 ex de, hl ; DE = result (sdcccall(1) uint16) + 0000001E E1 [10] 19 pop hl ; remove saved win (use HL, keep DE) + 0000001F DD E1 [14] 20 pop ix ; restore caller IX + 21 ; callee cleans 2 bytes (y+x); preserve DE = return value + 00000021 E1 [10] 22 pop hl ; HL = return address + 00000022 33 [ 6] 23 inc sp + 00000023 33 [ 6] 24 inc sp ; discard y(1)+x(1) + 00000024 E9 [ 4] 25 jp (hl) ; return, DE = result diff --git a/build/wc_api/wc_gadrw.rel b/build/wc_api/wc_gadrw.rel new file mode 100644 index 0000000..5f4f3d0 --- /dev/null +++ b/build/wc_api/wc_gadrw.rel @@ -0,0 +1,16 @@ +XL4 +H 1 areas 2 global symbols +M wc_gadrw +S .__.ABS. Def00000000 +A _CODE size 25 flags 0 addr 0 +S _wc_gadrw Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 E5 DD 21 00 00 DD 39 DD 56 06 +R 00 00 00 00 +T 0C 00 00 00 DD 5E 07 DD 6E 00 DD 66 01 E5 DD E1 +R 00 00 00 00 +T 18 00 00 00 3E 05 CD 06 60 EB E1 DD E1 E1 33 33 +R 00 00 00 00 +T 24 00 00 00 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_gadrw.sym b/build/wc_api/wc_gadrw.sym new file mode 100644 index 0000000..e8f129f --- /dev/null +++ b/build/wc_api/wc_gadrw.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_gadr 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 25 flags 0 + diff --git a/build/wc_api/wc_gdir.lst b/build/wc_api/wc_gdir.lst new file mode 100644 index 0000000..14f6975 --- /dev/null +++ b/build/wc_api/wc_gdir.lst @@ -0,0 +1,10 @@ + 1 .module wc_gdir + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_gdir + 00000000 5 _wc_gdir:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 3F [ 7] 7 ld a, #0x3F + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_gdir.rel b/build/wc_api/wc_gdir.rel new file mode 100644 index 0000000..e2e574e --- /dev/null +++ b/build/wc_api/wc_gdir.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_gdir +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_gdir Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 3F CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_gdir.sym b/build/wc_api/wc_gdir.sym new file mode 100644 index 0000000..04b3e5d --- /dev/null +++ b/build/wc_api/wc_gdir.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_gdir 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_gedpl.lst b/build/wc_api/wc_gedpl.lst new file mode 100644 index 0000000..cb2f3db --- /dev/null +++ b/build/wc_api/wc_gedpl.lst @@ -0,0 +1,10 @@ + 1 .module wc_gedpl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_gedpl + 00000000 5 _wc_gedpl:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 0F [ 7] 7 ld a, #0x0F + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_gedpl.rel b/build/wc_api/wc_gedpl.rel new file mode 100644 index 0000000..ac0b466 --- /dev/null +++ b/build/wc_api/wc_gedpl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_gedpl +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_gedpl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 0F CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_gedpl.sym b/build/wc_api/wc_gedpl.sym new file mode 100644 index 0000000..908e725 --- /dev/null +++ b/build/wc_api/wc_gedpl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_gedp 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_gfile.lst b/build/wc_api/wc_gfile.lst new file mode 100644 index 0000000..c0b8a12 --- /dev/null +++ b/build/wc_api/wc_gfile.lst @@ -0,0 +1,10 @@ + 1 .module wc_gfile + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_gfile + 00000000 5 _wc_gfile:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 3E [ 7] 7 ld a, #0x3E + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_gfile.rel b/build/wc_api/wc_gfile.rel new file mode 100644 index 0000000..9c6aa68 --- /dev/null +++ b/build/wc_api/wc_gfile.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_gfile +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_gfile Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 3E CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_gfile.sym b/build/wc_api/wc_gfile.sym new file mode 100644 index 0000000..eddb93d --- /dev/null +++ b/build/wc_api/wc_gfile.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_gfil 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_gipagpl.lst b/build/wc_api/wc_gipagpl.lst new file mode 100644 index 0000000..dcbcfd6 --- /dev/null +++ b/build/wc_api/wc_gipagpl.lst @@ -0,0 +1,10 @@ + 1 .module wc_gipagpl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_gipagpl + 00000000 5 _wc_gipagpl:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 32 [ 7] 7 ld a, #0x32 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_gipagpl.rel b/build/wc_api/wc_gipagpl.rel new file mode 100644 index 0000000..09646f1 --- /dev/null +++ b/build/wc_api/wc_gipagpl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_gipagpl +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_gipagpl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 32 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_gipagpl.sym b/build/wc_api/wc_gipagpl.sym new file mode 100644 index 0000000..7c08656 --- /dev/null +++ b/build/wc_api/wc_gipagpl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_gipa 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_gvmod.lst b/build/wc_api/wc_gvmod.lst new file mode 100644 index 0000000..e1823d9 --- /dev/null +++ b/build/wc_api/wc_gvmod.lst @@ -0,0 +1,11 @@ + 1 .module wc_gvmod + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_gvmod + 00000000 5 _wc_gvmod:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 42 [ 7] 8 ld a, #0x42 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_gvmod.rel b/build/wc_api/wc_gvmod.rel new file mode 100644 index 0000000..2ed7c7b --- /dev/null +++ b/build/wc_api/wc_gvmod.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_gvmod +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_gvmod Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 42 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_gvmod.sym b/build/wc_api/wc_gvmod.sym new file mode 100644 index 0000000..7474cbe --- /dev/null +++ b/build/wc_api/wc_gvmod.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_gvmo 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_gvsgp.lst b/build/wc_api/wc_gvsgp.lst new file mode 100644 index 0000000..c56defc --- /dev/null +++ b/build/wc_api/wc_gvsgp.lst @@ -0,0 +1,11 @@ + 1 .module wc_gvsgp + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_gvsgp + 00000000 5 _wc_gvsgp:: + 00000000 DD E5 [15] 6 push ix + 00000002 4F [ 4] 7 ld c, a + 00000003 3E 47 [ 7] 8 ld a, #0x47 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_gvsgp.rel b/build/wc_api/wc_gvsgp.rel new file mode 100644 index 0000000..6de00ab --- /dev/null +++ b/build/wc_api/wc_gvsgp.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_gvsgp +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_gvsgp Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 4F 3E 47 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_gvsgp.sym b/build/wc_api/wc_gvsgp.sym new file mode 100644 index 0000000..6f6ec85 --- /dev/null +++ b/build/wc_api/wc_gvsgp.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_gvsg 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_gvtl.lst b/build/wc_api/wc_gvtl.lst new file mode 100644 index 0000000..40e35b8 --- /dev/null +++ b/build/wc_api/wc_gvtl.lst @@ -0,0 +1,17 @@ + 1 .module wc_gvtl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_gvtl + 00000000 5 _wc_gvtl:: + 00000000 DD E5 [15] 6 push ix + 00000002 DD 21 00 00 [14] 7 ld ix, #0 + 00000006 DD 39 [15] 8 add ix, sp + 00000008 DD 4E 04 [19] 9 ld c, 4(ix) ; C = page + 0000000B 47 [ 4] 10 ld b, a ; B = plane + 0000000C 3E 46 [ 7] 11 ld a, #0x46 + 0000000E CD 06 60 [17] 12 call WC_ENTRY + 00000011 DD E1 [14] 13 pop ix + 14 ; callee cleans 1 byte (page) + 00000013 E1 [10] 15 pop hl + 00000014 33 [ 6] 16 inc sp + 00000015 E9 [ 4] 17 jp (hl) diff --git a/build/wc_api/wc_gvtl.rel b/build/wc_api/wc_gvtl.rel new file mode 100644 index 0000000..34bd01b --- /dev/null +++ b/build/wc_api/wc_gvtl.rel @@ -0,0 +1,12 @@ +XL4 +H 1 areas 2 global symbols +M wc_gvtl +S .__.ABS. Def00000000 +A _CODE size 16 flags 0 addr 0 +S _wc_gvtl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 DD 21 00 00 DD 39 DD 4E 04 47 +R 00 00 00 00 +T 0C 00 00 00 3E 46 CD 06 60 DD E1 E1 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_gvtl.sym b/build/wc_api/wc_gvtl.sym new file mode 100644 index 0000000..0c310ad --- /dev/null +++ b/build/wc_api/wc_gvtl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_gvtl 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 16 flags 0 + diff --git a/build/wc_api/wc_gvtm.lst b/build/wc_api/wc_gvtm.lst new file mode 100644 index 0000000..556d6da --- /dev/null +++ b/build/wc_api/wc_gvtm.lst @@ -0,0 +1,11 @@ + 1 .module wc_gvtm + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_gvtm + 00000000 5 _wc_gvtm:: + 00000000 DD E5 [15] 6 push ix + 00000002 4F [ 4] 7 ld c, a + 00000003 3E 45 [ 7] 8 ld a, #0x45 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_gvtm.rel b/build/wc_api/wc_gvtm.rel new file mode 100644 index 0000000..4b09618 --- /dev/null +++ b/build/wc_api/wc_gvtm.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_gvtm +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_gvtm Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 4F 3E 45 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_gvtm.sym b/build/wc_api/wc_gvtm.sym new file mode 100644 index 0000000..da03742 --- /dev/null +++ b/build/wc_api/wc_gvtm.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_gvtm 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_gxoff.lst b/build/wc_api/wc_gxoff.lst new file mode 100644 index 0000000..1c9f356 --- /dev/null +++ b/build/wc_api/wc_gxoff.lst @@ -0,0 +1,10 @@ + 1 .module wc_gxoff + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_gxoff + 00000000 5 _wc_gxoff:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 44 [ 7] 7 ld a, #0x44 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_gxoff.rel b/build/wc_api/wc_gxoff.rel new file mode 100644 index 0000000..0e29145 --- /dev/null +++ b/build/wc_api/wc_gxoff.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_gxoff +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_gxoff Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 44 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_gxoff.sym b/build/wc_api/wc_gxoff.sym new file mode 100644 index 0000000..fabe424 --- /dev/null +++ b/build/wc_api/wc_gxoff.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_gxof 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_gyoff.lst b/build/wc_api/wc_gyoff.lst new file mode 100644 index 0000000..96c4db3 --- /dev/null +++ b/build/wc_api/wc_gyoff.lst @@ -0,0 +1,10 @@ + 1 .module wc_gyoff + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_gyoff + 00000000 5 _wc_gyoff:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 43 [ 7] 7 ld a, #0x43 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_gyoff.rel b/build/wc_api/wc_gyoff.rel new file mode 100644 index 0000000..79f5d05 --- /dev/null +++ b/build/wc_api/wc_gyoff.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_gyoff +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_gyoff Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 43 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_gyoff.sym b/build/wc_api/wc_gyoff.sym new file mode 100644 index 0000000..e7b8714 --- /dev/null +++ b/build/wc_api/wc_gyoff.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_gyof 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_int_pl.lst b/build/wc_api/wc_int_pl.lst new file mode 100644 index 0000000..e8afbce --- /dev/null +++ b/build/wc_api/wc_int_pl.lst @@ -0,0 +1,11 @@ + 1 .module wc_int_pl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_int_pl + 00000000 5 _wc_int_pl:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 56 [ 7] 8 ld a, #0x56 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_int_pl.rel b/build/wc_api/wc_int_pl.rel new file mode 100644 index 0000000..de0bc9b --- /dev/null +++ b/build/wc_api/wc_int_pl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_int_pl +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_int_pl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 56 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_int_pl.sym b/build/wc_api/wc_int_pl.sym new file mode 100644 index 0000000..eee03c8 --- /dev/null +++ b/build/wc_api/wc_int_pl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_int_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_int_pl_handler.lst b/build/wc_api/wc_int_pl_handler.lst new file mode 100644 index 0000000..a30834f --- /dev/null +++ b/build/wc_api/wc_int_pl_handler.lst @@ -0,0 +1,13 @@ + 1 .module wc_int_pl_handler + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_int_pl_handler + 00000000 5 _wc_int_pl_handler:: + 00000000 DD E5 [15] 6 push ix + 7 ; HL = handler_addr (sdcccall(1); WC тоже нужен HL) + 00000002 3E FF [ 7] 8 ld a, #0xFF + 00000004 08 [ 4] 9 ex af, af' ; A' = 0xFF + 00000005 3E 56 [ 7] 10 ld a, #0x56 + 00000007 CD 06 60 [17] 11 call WC_ENTRY + 0000000A DD E1 [14] 12 pop ix + 0000000C C9 [10] 13 ret diff --git a/build/wc_api/wc_int_pl_handler.rel b/build/wc_api/wc_int_pl_handler.rel new file mode 100644 index 0000000..3ca29ad --- /dev/null +++ b/build/wc_api/wc_int_pl_handler.rel @@ -0,0 +1,12 @@ +XL4 +H 1 areas 2 global symbols +M wc_int_pl_handler +S .__.ABS. Def00000000 +A _CODE size D flags 0 addr 0 +S _wc_int_pl_handler Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E FF 08 3E 56 CD 06 60 DD E1 +R 00 00 00 00 +T 0C 00 00 00 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_int_pl_handler.sym b/build/wc_api/wc_int_pl_handler.sym new file mode 100644 index 0000000..155ccaf --- /dev/null +++ b/build/wc_api/wc_int_pl_handler.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_int_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size D flags 0 + diff --git a/build/wc_api/wc_istr.lst b/build/wc_api/wc_istr.lst new file mode 100644 index 0000000..39fa7e8 --- /dev/null +++ b/build/wc_api/wc_istr.lst @@ -0,0 +1,19 @@ + 1 .module wc_istr + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_istr + 00000000 5 _wc_istr:: + 00000000 DD E5 [15] 6 push ix + 00000002 DD 21 00 00 [14] 7 ld ix, #0 + 00000006 DD 39 [15] 8 add ix, sp + 00000008 DD 7E 04 [19] 9 ld a, 4(ix) ; A = mode (from stack) + 0000000B 08 [ 4] 10 ex af, af' ; A' = mode + 0000000C E5 [11] 11 push hl + 0000000D DD E1 [14] 12 pop ix ; IX = win (HL = 1st arg) + 0000000F 3E 09 [ 7] 13 ld a, #0x09 + 00000011 CD 06 60 [17] 14 call WC_ENTRY + 00000014 DD E1 [14] 15 pop ix + 16 ; callee cleans 1 byte (mode) + 00000016 E1 [10] 17 pop hl + 00000017 33 [ 6] 18 inc sp + 00000018 E9 [ 4] 19 jp (hl) diff --git a/build/wc_api/wc_istr.rel b/build/wc_api/wc_istr.rel new file mode 100644 index 0000000..688fd67 --- /dev/null +++ b/build/wc_api/wc_istr.rel @@ -0,0 +1,14 @@ +XL4 +H 1 areas 2 global symbols +M wc_istr +S .__.ABS. Def00000000 +A _CODE size 19 flags 0 addr 0 +S _wc_istr Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 DD 21 00 00 DD 39 DD 7E 04 08 +R 00 00 00 00 +T 0C 00 00 00 E5 DD E1 3E 09 CD 06 60 DD E1 E1 33 +R 00 00 00 00 +T 18 00 00 00 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_istr.sym b/build/wc_api/wc_istr.sym new file mode 100644 index 0000000..cb91ea0 --- /dev/null +++ b/build/wc_api/wc_istr.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_istr 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 19 flags 0 + diff --git a/build/wc_api/wc_kbscn.lst b/build/wc_api/wc_kbscn.lst new file mode 100644 index 0000000..ef208a9 --- /dev/null +++ b/build/wc_api/wc_kbscn.lst @@ -0,0 +1,11 @@ + 1 .module wc_kbscn + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_kbscn + 00000000 5 _wc_kbscn:: + 00000000 08 [ 4] 6 ex af, af' ; A' = mode + 00000001 DD E5 [15] 7 push ix + 00000003 3E 2A [ 7] 8 ld a, #0x2A + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_kbscn.rel b/build/wc_api/wc_kbscn.rel new file mode 100644 index 0000000..51e4f2a --- /dev/null +++ b/build/wc_api/wc_kbscn.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_kbscn +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_kbscn Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 2A CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_kbscn.sym b/build/wc_api/wc_kbscn.sym new file mode 100644 index 0000000..df65ec6 --- /dev/null +++ b/build/wc_api/wc_kbscn.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_kbsc 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_key_alt.lst b/build/wc_api/wc_key_alt.lst new file mode 100644 index 0000000..582bd79 --- /dev/null +++ b/build/wc_api/wc_key_alt.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_alt + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_alt + 00000000 5 _wc_key_alt:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 27 [ 7] 7 ld a, #0x27 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_alt.rel b/build/wc_api/wc_key_alt.rel new file mode 100644 index 0000000..a39c5f2 --- /dev/null +++ b/build/wc_api/wc_key_alt.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_alt +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_alt Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 27 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_alt.sym b/build/wc_api/wc_key_alt.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_alt.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_any.lst b/build/wc_api/wc_key_any.lst new file mode 100644 index 0000000..4324cba --- /dev/null +++ b/build/wc_api/wc_key_any.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_any + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_any + 00000000 5 _wc_key_any:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 2D [ 7] 7 ld a, #0x2D + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_any.rel b/build/wc_api/wc_key_any.rel new file mode 100644 index 0000000..4ffb5a8 --- /dev/null +++ b/build/wc_api/wc_key_any.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_any +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_any Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 2D CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_any.sym b/build/wc_api/wc_key_any.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_any.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_bspc.lst b/build/wc_api/wc_key_bspc.lst new file mode 100644 index 0000000..c7dbce7 --- /dev/null +++ b/build/wc_api/wc_key_bspc.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_bspc + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_bspc + 00000000 5 _wc_key_bspc:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 18 [ 7] 7 ld a, #0x18 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_bspc.rel b/build/wc_api/wc_key_bspc.rel new file mode 100644 index 0000000..175b111 --- /dev/null +++ b/build/wc_api/wc_key_bspc.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_bspc +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_bspc Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 18 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_bspc.sym b/build/wc_api/wc_key_bspc.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_bspc.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_caps.lst b/build/wc_api/wc_key_caps.lst new file mode 100644 index 0000000..9b7a640 --- /dev/null +++ b/build/wc_api/wc_key_caps.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_caps + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_caps + 00000000 5 _wc_key_caps:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 2C [ 7] 7 ld a, #0x2C + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_caps.rel b/build/wc_api/wc_key_caps.rel new file mode 100644 index 0000000..3e00224 --- /dev/null +++ b/build/wc_api/wc_key_caps.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_caps +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_caps Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 2C CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_caps.sym b/build/wc_api/wc_key_caps.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_caps.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_ctrl.lst b/build/wc_api/wc_key_ctrl.lst new file mode 100644 index 0000000..e20a813 --- /dev/null +++ b/build/wc_api/wc_key_ctrl.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_ctrl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_ctrl + 00000000 5 _wc_key_ctrl:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 29 [ 7] 7 ld a, #0x29 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_ctrl.rel b/build/wc_api/wc_key_ctrl.rel new file mode 100644 index 0000000..696a96d --- /dev/null +++ b/build/wc_api/wc_key_ctrl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_ctrl +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_ctrl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 29 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_ctrl.sym b/build/wc_api/wc_key_ctrl.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_ctrl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_del.lst b/build/wc_api/wc_key_del.lst new file mode 100644 index 0000000..efdc81b --- /dev/null +++ b/build/wc_api/wc_key_del.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_del + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_del + 00000000 5 _wc_key_del:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 2B [ 7] 7 ld a, #0x2B + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_del.rel b/build/wc_api/wc_key_del.rel new file mode 100644 index 0000000..0a0cdaf --- /dev/null +++ b/build/wc_api/wc_key_del.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_del +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_del Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 2B CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_del.sym b/build/wc_api/wc_key_del.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_del.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_down.lst b/build/wc_api/wc_key_down.lst new file mode 100644 index 0000000..e5d71a0 --- /dev/null +++ b/build/wc_api/wc_key_down.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_down + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_down + 00000000 5 _wc_key_down:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 12 [ 7] 7 ld a, #0x12 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_down.rel b/build/wc_api/wc_key_down.rel new file mode 100644 index 0000000..11fd222 --- /dev/null +++ b/build/wc_api/wc_key_down.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_down +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_down Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 12 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_down.sym b/build/wc_api/wc_key_down.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_down.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_end.lst b/build/wc_api/wc_key_end.lst new file mode 100644 index 0000000..1a9054c --- /dev/null +++ b/build/wc_api/wc_key_end.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_end + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_end + 00000000 5 _wc_key_end:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 1C [ 7] 7 ld a, #0x1C + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_end.rel b/build/wc_api/wc_key_end.rel new file mode 100644 index 0000000..94e846c --- /dev/null +++ b/build/wc_api/wc_key_end.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_end +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_end Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 1C CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_end.sym b/build/wc_api/wc_key_end.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_end.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_enter.lst b/build/wc_api/wc_key_enter.lst new file mode 100644 index 0000000..1b96ffd --- /dev/null +++ b/build/wc_api/wc_key_enter.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_enter + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_enter + 00000000 5 _wc_key_enter:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 16 [ 7] 7 ld a, #0x16 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_enter.rel b/build/wc_api/wc_key_enter.rel new file mode 100644 index 0000000..aec9be2 --- /dev/null +++ b/build/wc_api/wc_key_enter.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_enter +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_enter Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 16 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_enter.sym b/build/wc_api/wc_key_enter.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_enter.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_esc.lst b/build/wc_api/wc_key_esc.lst new file mode 100644 index 0000000..d1534fd --- /dev/null +++ b/build/wc_api/wc_key_esc.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_esc + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_esc + 00000000 5 _wc_key_esc:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 17 [ 7] 7 ld a, #0x17 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_esc.rel b/build/wc_api/wc_key_esc.rel new file mode 100644 index 0000000..47d4551 --- /dev/null +++ b/build/wc_api/wc_key_esc.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_esc +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_esc Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 17 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_esc.sym b/build/wc_api/wc_key_esc.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_esc.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_f1.lst b/build/wc_api/wc_key_f1.lst new file mode 100644 index 0000000..53d7e87 --- /dev/null +++ b/build/wc_api/wc_key_f1.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_f1 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_f1 + 00000000 5 _wc_key_f1:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 1D [ 7] 7 ld a, #0x1D + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_f1.rel b/build/wc_api/wc_key_f1.rel new file mode 100644 index 0000000..c5a8346 --- /dev/null +++ b/build/wc_api/wc_key_f1.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_f1 +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_f1 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 1D CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_f1.sym b/build/wc_api/wc_key_f1.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_f1.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_f10.lst b/build/wc_api/wc_key_f10.lst new file mode 100644 index 0000000..bbe4c7e --- /dev/null +++ b/build/wc_api/wc_key_f10.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_f10 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_f10 + 00000000 5 _wc_key_f10:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 26 [ 7] 7 ld a, #0x26 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_f10.rel b/build/wc_api/wc_key_f10.rel new file mode 100644 index 0000000..17bc3d1 --- /dev/null +++ b/build/wc_api/wc_key_f10.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_f10 +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_f10 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 26 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_f10.sym b/build/wc_api/wc_key_f10.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_f10.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_f2.lst b/build/wc_api/wc_key_f2.lst new file mode 100644 index 0000000..d30b01a --- /dev/null +++ b/build/wc_api/wc_key_f2.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_f2 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_f2 + 00000000 5 _wc_key_f2:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 1E [ 7] 7 ld a, #0x1E + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_f2.rel b/build/wc_api/wc_key_f2.rel new file mode 100644 index 0000000..6f33fa8 --- /dev/null +++ b/build/wc_api/wc_key_f2.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_f2 +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_f2 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 1E CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_f2.sym b/build/wc_api/wc_key_f2.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_f2.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_f3.lst b/build/wc_api/wc_key_f3.lst new file mode 100644 index 0000000..45c0727 --- /dev/null +++ b/build/wc_api/wc_key_f3.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_f3 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_f3 + 00000000 5 _wc_key_f3:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 1F [ 7] 7 ld a, #0x1F + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_f3.rel b/build/wc_api/wc_key_f3.rel new file mode 100644 index 0000000..b61594e --- /dev/null +++ b/build/wc_api/wc_key_f3.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_f3 +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_f3 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 1F CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_f3.sym b/build/wc_api/wc_key_f3.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_f3.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_f4.lst b/build/wc_api/wc_key_f4.lst new file mode 100644 index 0000000..36261e9 --- /dev/null +++ b/build/wc_api/wc_key_f4.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_f4 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_f4 + 00000000 5 _wc_key_f4:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 20 [ 7] 7 ld a, #0x20 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_f4.rel b/build/wc_api/wc_key_f4.rel new file mode 100644 index 0000000..e1180d5 --- /dev/null +++ b/build/wc_api/wc_key_f4.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_f4 +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_f4 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 20 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_f4.sym b/build/wc_api/wc_key_f4.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_f4.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_f5.lst b/build/wc_api/wc_key_f5.lst new file mode 100644 index 0000000..06ac61b --- /dev/null +++ b/build/wc_api/wc_key_f5.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_f5 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_f5 + 00000000 5 _wc_key_f5:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 21 [ 7] 7 ld a, #0x21 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_f5.rel b/build/wc_api/wc_key_f5.rel new file mode 100644 index 0000000..0182534 --- /dev/null +++ b/build/wc_api/wc_key_f5.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_f5 +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_f5 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 21 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_f5.sym b/build/wc_api/wc_key_f5.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_f5.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_f6.lst b/build/wc_api/wc_key_f6.lst new file mode 100644 index 0000000..4df80e9 --- /dev/null +++ b/build/wc_api/wc_key_f6.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_f6 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_f6 + 00000000 5 _wc_key_f6:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 22 [ 7] 7 ld a, #0x22 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_f6.rel b/build/wc_api/wc_key_f6.rel new file mode 100644 index 0000000..88f0c96 --- /dev/null +++ b/build/wc_api/wc_key_f6.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_f6 +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_f6 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 22 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_f6.sym b/build/wc_api/wc_key_f6.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_f6.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_f7.lst b/build/wc_api/wc_key_f7.lst new file mode 100644 index 0000000..c90489a --- /dev/null +++ b/build/wc_api/wc_key_f7.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_f7 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_f7 + 00000000 5 _wc_key_f7:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 23 [ 7] 7 ld a, #0x23 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_f7.rel b/build/wc_api/wc_key_f7.rel new file mode 100644 index 0000000..d6a46f8 --- /dev/null +++ b/build/wc_api/wc_key_f7.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_f7 +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_f7 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 23 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_f7.sym b/build/wc_api/wc_key_f7.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_f7.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_f8.lst b/build/wc_api/wc_key_f8.lst new file mode 100644 index 0000000..58e3545 --- /dev/null +++ b/build/wc_api/wc_key_f8.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_f8 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_f8 + 00000000 5 _wc_key_f8:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 24 [ 7] 7 ld a, #0x24 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_f8.rel b/build/wc_api/wc_key_f8.rel new file mode 100644 index 0000000..efd4742 --- /dev/null +++ b/build/wc_api/wc_key_f8.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_f8 +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_f8 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 24 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_f8.sym b/build/wc_api/wc_key_f8.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_f8.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_f9.lst b/build/wc_api/wc_key_f9.lst new file mode 100644 index 0000000..f8e1bf8 --- /dev/null +++ b/build/wc_api/wc_key_f9.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_f9 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_f9 + 00000000 5 _wc_key_f9:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 25 [ 7] 7 ld a, #0x25 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_f9.rel b/build/wc_api/wc_key_f9.rel new file mode 100644 index 0000000..cccd928 --- /dev/null +++ b/build/wc_api/wc_key_f9.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_f9 +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_f9 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 25 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_f9.sym b/build/wc_api/wc_key_f9.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_f9.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_home.lst b/build/wc_api/wc_key_home.lst new file mode 100644 index 0000000..63d1f4f --- /dev/null +++ b/build/wc_api/wc_key_home.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_home + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_home + 00000000 5 _wc_key_home:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 1B [ 7] 7 ld a, #0x1B + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_home.rel b/build/wc_api/wc_key_home.rel new file mode 100644 index 0000000..419ec91 --- /dev/null +++ b/build/wc_api/wc_key_home.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_home +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_home Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 1B CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_home.sym b/build/wc_api/wc_key_home.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_home.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_ins.lst b/build/wc_api/wc_key_ins.lst new file mode 100644 index 0000000..bc396d5 --- /dev/null +++ b/build/wc_api/wc_key_ins.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_ins + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_ins + 00000000 5 _wc_key_ins:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 55 [ 7] 7 ld a, #0x55 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_ins.rel b/build/wc_api/wc_key_ins.rel new file mode 100644 index 0000000..7ed61f6 --- /dev/null +++ b/build/wc_api/wc_key_ins.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_ins +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_ins Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 55 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_ins.sym b/build/wc_api/wc_key_ins.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_ins.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_left.lst b/build/wc_api/wc_key_left.lst new file mode 100644 index 0000000..d857413 --- /dev/null +++ b/build/wc_api/wc_key_left.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_left + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_left + 00000000 5 _wc_key_left:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 13 [ 7] 7 ld a, #0x13 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_left.rel b/build/wc_api/wc_key_left.rel new file mode 100644 index 0000000..0b0e0c2 --- /dev/null +++ b/build/wc_api/wc_key_left.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_left +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_left Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 13 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_left.sym b/build/wc_api/wc_key_left.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_left.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_pgdn.lst b/build/wc_api/wc_key_pgdn.lst new file mode 100644 index 0000000..a2f4e6b --- /dev/null +++ b/build/wc_api/wc_key_pgdn.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_pgdn + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_pgdn + 00000000 5 _wc_key_pgdn:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 1A [ 7] 7 ld a, #0x1A + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_pgdn.rel b/build/wc_api/wc_key_pgdn.rel new file mode 100644 index 0000000..a728788 --- /dev/null +++ b/build/wc_api/wc_key_pgdn.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_pgdn +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_pgdn Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 1A CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_pgdn.sym b/build/wc_api/wc_key_pgdn.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_pgdn.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_pgup.lst b/build/wc_api/wc_key_pgup.lst new file mode 100644 index 0000000..e5c52ce --- /dev/null +++ b/build/wc_api/wc_key_pgup.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_pgup + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_pgup + 00000000 5 _wc_key_pgup:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 19 [ 7] 7 ld a, #0x19 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_pgup.rel b/build/wc_api/wc_key_pgup.rel new file mode 100644 index 0000000..d753e0b --- /dev/null +++ b/build/wc_api/wc_key_pgup.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_pgup +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_pgup Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 19 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_pgup.sym b/build/wc_api/wc_key_pgup.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_pgup.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_right.lst b/build/wc_api/wc_key_right.lst new file mode 100644 index 0000000..87ab633 --- /dev/null +++ b/build/wc_api/wc_key_right.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_right + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_right + 00000000 5 _wc_key_right:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 14 [ 7] 7 ld a, #0x14 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_right.rel b/build/wc_api/wc_key_right.rel new file mode 100644 index 0000000..a9dd026 --- /dev/null +++ b/build/wc_api/wc_key_right.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_right +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_right Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 14 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_right.sym b/build/wc_api/wc_key_right.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_right.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_shift.lst b/build/wc_api/wc_key_shift.lst new file mode 100644 index 0000000..83eefc0 --- /dev/null +++ b/build/wc_api/wc_key_shift.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_shift + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_shift + 00000000 5 _wc_key_shift:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 28 [ 7] 7 ld a, #0x28 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_shift.rel b/build/wc_api/wc_key_shift.rel new file mode 100644 index 0000000..e3bc880 --- /dev/null +++ b/build/wc_api/wc_key_shift.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_shift +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_shift Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 28 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_shift.sym b/build/wc_api/wc_key_shift.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_shift.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_space.lst b/build/wc_api/wc_key_space.lst new file mode 100644 index 0000000..0cebeec --- /dev/null +++ b/build/wc_api/wc_key_space.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_space + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_space + 00000000 5 _wc_key_space:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 10 [ 7] 7 ld a, #0x10 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_space.rel b/build/wc_api/wc_key_space.rel new file mode 100644 index 0000000..0f6258b --- /dev/null +++ b/build/wc_api/wc_key_space.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_space +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_space Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 10 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_space.sym b/build/wc_api/wc_key_space.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_space.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_tab.lst b/build/wc_api/wc_key_tab.lst new file mode 100644 index 0000000..8b4560c --- /dev/null +++ b/build/wc_api/wc_key_tab.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_tab + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_tab + 00000000 5 _wc_key_tab:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 15 [ 7] 7 ld a, #0x15 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_tab.rel b/build/wc_api/wc_key_tab.rel new file mode 100644 index 0000000..bb91f2e --- /dev/null +++ b/build/wc_api/wc_key_tab.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_tab +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_tab Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 15 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_tab.sym b/build/wc_api/wc_key_tab.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_tab.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_up.lst b/build/wc_api/wc_key_up.lst new file mode 100644 index 0000000..2933a34 --- /dev/null +++ b/build/wc_api/wc_key_up.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_up + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_up + 00000000 5 _wc_key_up:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 11 [ 7] 7 ld a, #0x11 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_up.rel b/build/wc_api/wc_key_up.rel new file mode 100644 index 0000000..d140322 --- /dev/null +++ b/build/wc_api/wc_key_up.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_up +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_up Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 11 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_up.sym b/build/wc_api/wc_key_up.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_up.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_wait_any.lst b/build/wc_api/wc_key_wait_any.lst new file mode 100644 index 0000000..25471a3 --- /dev/null +++ b/build/wc_api/wc_key_wait_any.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_wait_any + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_wait_any + 00000000 5 _wc_key_wait_any:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 2F [ 7] 7 ld a, #0x2F + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_wait_any.rel b/build/wc_api/wc_key_wait_any.rel new file mode 100644 index 0000000..ad62a86 --- /dev/null +++ b/build/wc_api/wc_key_wait_any.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_wait_any +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_wait_any Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 2F CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_wait_any.sym b/build/wc_api/wc_key_wait_any.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_wait_any.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_key_wait_release.lst b/build/wc_api/wc_key_wait_release.lst new file mode 100644 index 0000000..e4a7764 --- /dev/null +++ b/build/wc_api/wc_key_wait_release.lst @@ -0,0 +1,10 @@ + 1 .module wc_key_wait_release + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_key_wait_release + 00000000 5 _wc_key_wait_release:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 2E [ 7] 7 ld a, #0x2E + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_key_wait_release.rel b/build/wc_api/wc_key_wait_release.rel new file mode 100644 index 0000000..4e30e8c --- /dev/null +++ b/build/wc_api/wc_key_wait_release.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_key_wait_release +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_key_wait_release Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 2E CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_key_wait_release.sym b/build/wc_api/wc_key_wait_release.sym new file mode 100644 index 0000000..0f41fb6 --- /dev/null +++ b/build/wc_api/wc_key_wait_release.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_key_ 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_load256.lst b/build/wc_api/wc_load256.lst new file mode 100644 index 0000000..3b3b19f --- /dev/null +++ b/build/wc_api/wc_load256.lst @@ -0,0 +1,16 @@ + 1 .module wc_load256 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_load256 + 00000000 5 _wc_load256:: + 00000000 DD E5 [15] 6 push ix + 00000002 DD 21 00 00 [14] 7 ld ix, #0 + 00000006 DD 39 [15] 8 add ix, sp + 00000008 DD 46 04 [19] 9 ld b, 4(ix) ; B = blocks (1 byte on stack) + 10 ; HL = dest (already in HL) + 0000000B 3E 3C [ 7] 11 ld a, #0x3C + 0000000D CD 06 60 [17] 12 call WC_ENTRY + 00000010 DD E1 [14] 13 pop ix + 00000012 E1 [10] 14 pop hl ; HL = return address + 00000013 33 [ 6] 15 inc sp ; discard 1-byte 'blocks' arg + 00000014 E9 [ 4] 16 jp (hl) ; return (callee cleans stack arg) diff --git a/build/wc_api/wc_load256.rel b/build/wc_api/wc_load256.rel new file mode 100644 index 0000000..f6610db --- /dev/null +++ b/build/wc_api/wc_load256.rel @@ -0,0 +1,12 @@ +XL4 +H 1 areas 2 global symbols +M wc_load256 +S .__.ABS. Def00000000 +A _CODE size 15 flags 0 addr 0 +S _wc_load256 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 DD 21 00 00 DD 39 DD 46 04 3E +R 00 00 00 00 +T 0C 00 00 00 3C CD 06 60 DD E1 E1 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_load256.sym b/build/wc_api/wc_load256.sym new file mode 100644 index 0000000..e1f22be --- /dev/null +++ b/build/wc_api/wc_load256.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_load 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 15 flags 0 + diff --git a/build/wc_api/wc_load512.lst b/build/wc_api/wc_load512.lst new file mode 100644 index 0000000..907c90a --- /dev/null +++ b/build/wc_api/wc_load512.lst @@ -0,0 +1,16 @@ + 1 .module wc_load512 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_load512 + 00000000 5 _wc_load512:: + 00000000 DD E5 [15] 6 push ix + 00000002 DD 21 00 00 [14] 7 ld ix, #0 + 00000006 DD 39 [15] 8 add ix, sp + 00000008 DD 46 04 [19] 9 ld b, 4(ix) ; B = blocks (1 byte on stack) + 10 ; HL = dest (sdcccall(1) first uint16 → HL) + 0000000B 3E 30 [ 7] 11 ld a, #0x30 + 0000000D CD 06 60 [17] 12 call WC_ENTRY + 00000010 DD E1 [14] 13 pop ix + 00000012 E1 [10] 14 pop hl ; HL = return address + 00000013 33 [ 6] 15 inc sp ; discard 1-byte 'blocks' arg + 00000014 E9 [ 4] 16 jp (hl) ; return (callee cleans stack arg) diff --git a/build/wc_api/wc_load512.rel b/build/wc_api/wc_load512.rel new file mode 100644 index 0000000..1b36f98 --- /dev/null +++ b/build/wc_api/wc_load512.rel @@ -0,0 +1,12 @@ +XL4 +H 1 areas 2 global symbols +M wc_load512 +S .__.ABS. Def00000000 +A _CODE size 15 flags 0 addr 0 +S _wc_load512 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 DD 21 00 00 DD 39 DD 46 04 3E +R 00 00 00 00 +T 0C 00 00 00 30 CD 06 60 DD E1 E1 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_load512.sym b/build/wc_api/wc_load512.sym new file mode 100644 index 0000000..e1f22be --- /dev/null +++ b/build/wc_api/wc_load512.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_load 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 15 flags 0 + diff --git a/build/wc_api/wc_loadnone.lst b/build/wc_api/wc_loadnone.lst new file mode 100644 index 0000000..ffecf9c --- /dev/null +++ b/build/wc_api/wc_loadnone.lst @@ -0,0 +1,11 @@ + 1 .module wc_loadnone + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_loadnone + 00000000 5 _wc_loadnone:: + 00000000 DD E5 [15] 6 push ix + 00000002 47 [ 4] 7 ld b, a + 00000003 3E 3D [ 7] 8 ld a, #0x3D + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_loadnone.rel b/build/wc_api/wc_loadnone.rel new file mode 100644 index 0000000..0c624a8 --- /dev/null +++ b/build/wc_api/wc_loadnone.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_loadnone +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_loadnone Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 47 3E 3D CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_loadnone.sym b/build/wc_api/wc_loadnone.sym new file mode 100644 index 0000000..4609b28 --- /dev/null +++ b/build/wc_api/wc_loadnone.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_load 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_mezz.lst b/build/wc_api/wc_mezz.lst new file mode 100644 index 0000000..2d879c0 --- /dev/null +++ b/build/wc_api/wc_mezz.lst @@ -0,0 +1,34 @@ + 1 .module wc_mezz + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_mezz + 00000000 5 _wc_mezz:: + 00000000 DD E5 [15] 6 push ix + 00000002 E5 [11] 7 push hl ; save win (HL = 1st arg) + 00000003 DD 21 00 00 [14] 8 ld ix, #0 + 00000007 DD 39 [15] 9 add ix, sp + 00000009 DD 7E 06 [19] 10 ld a, 6(ix) ; A = msg_num + 0000000C DD 6E 07 [19] 11 ld l, 7(ix) + 0000000F DD 66 08 [19] 12 ld h, 8(ix) ; HL = str + 00000012 DD 56 09 [19] 13 ld d, 9(ix) ; D = y + 00000015 DD 5E 0A [19] 14 ld e, 10(ix) ; E = x + 00000018 D9 [ 4] 15 exx + 00000019 08 [ 4] 16 ex af, af' ; A' = msg_num + 0000001A DD 6E 00 [19] 17 ld l, 0(ix) + 0000001D DD 66 01 [19] 18 ld h, 1(ix) ; HL = win + 00000020 E5 [11] 19 push hl + 00000021 DD E1 [14] 20 pop ix ; IX = win + 00000023 D9 [ 4] 21 exx ; HL=str, D=y, E=x + 00000024 3E 0C [ 7] 22 ld a, #0x0C + 00000026 CD 06 60 [17] 23 call WC_ENTRY + 00000029 7A [ 4] 24 ld a, d ; return: next row + 0000002A E1 [10] 25 pop hl ; remove saved win + 0000002B DD E1 [14] 26 pop ix + 27 ; callee cleans 5 bytes + 0000002D E1 [10] 28 pop hl ; HL = return address + 0000002E 33 [ 6] 29 inc sp + 0000002F 33 [ 6] 30 inc sp + 00000030 33 [ 6] 31 inc sp + 00000031 33 [ 6] 32 inc sp + 00000032 33 [ 6] 33 inc sp + 00000033 E9 [ 4] 34 jp (hl) diff --git a/build/wc_api/wc_mezz.rel b/build/wc_api/wc_mezz.rel new file mode 100644 index 0000000..8ac0c17 --- /dev/null +++ b/build/wc_api/wc_mezz.rel @@ -0,0 +1,18 @@ +XL4 +H 1 areas 2 global symbols +M wc_mezz +S .__.ABS. Def00000000 +A _CODE size 34 flags 0 addr 0 +S _wc_mezz Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 E5 DD 21 00 00 DD 39 DD 7E 06 +R 00 00 00 00 +T 0C 00 00 00 DD 6E 07 DD 66 08 DD 56 09 DD 5E 0A +R 00 00 00 00 +T 18 00 00 00 D9 08 DD 6E 00 DD 66 01 E5 DD E1 D9 +R 00 00 00 00 +T 24 00 00 00 3E 0C CD 06 60 7A E1 DD E1 E1 33 33 +R 00 00 00 00 +T 30 00 00 00 33 33 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_mezz.sym b/build/wc_api/wc_mezz.sym new file mode 100644 index 0000000..3aa537c --- /dev/null +++ b/build/wc_api/wc_mezz.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_mezz 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 34 flags 0 + diff --git a/build/wc_api/wc_mkdir.lst b/build/wc_api/wc_mkdir.lst new file mode 100644 index 0000000..104ce5e --- /dev/null +++ b/build/wc_api/wc_mkdir.lst @@ -0,0 +1,10 @@ + 1 .module wc_mkdir + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_mkdir + 00000000 5 _wc_mkdir:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 49 [ 7] 7 ld a, #0x49 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_mkdir.rel b/build/wc_api/wc_mkdir.rel new file mode 100644 index 0000000..ab4d96c --- /dev/null +++ b/build/wc_api/wc_mkdir.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_mkdir +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_mkdir Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 49 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_mkdir.sym b/build/wc_api/wc_mkdir.sym new file mode 100644 index 0000000..a427ddf --- /dev/null +++ b/build/wc_api/wc_mkdir.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_mkdi 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_mkfile.lst b/build/wc_api/wc_mkfile.lst new file mode 100644 index 0000000..094d06b --- /dev/null +++ b/build/wc_api/wc_mkfile.lst @@ -0,0 +1,10 @@ + 1 .module wc_mkfile + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_mkfile + 00000000 5 _wc_mkfile:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 48 [ 7] 7 ld a, #0x48 + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_mkfile.rel b/build/wc_api/wc_mkfile.rel new file mode 100644 index 0000000..a8b4e3d --- /dev/null +++ b/build/wc_api/wc_mkfile.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_mkfile +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_mkfile Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 48 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_mkfile.sym b/build/wc_api/wc_mkfile.sym new file mode 100644 index 0000000..fffdc52 --- /dev/null +++ b/build/wc_api/wc_mkfile.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_mkfi 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_mng0_pl.lst b/build/wc_api/wc_mng0_pl.lst new file mode 100644 index 0000000..9db0109 --- /dev/null +++ b/build/wc_api/wc_mng0_pl.lst @@ -0,0 +1,11 @@ + 1 .module wc_mng0_pl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_mng0_pl + 00000000 5 _wc_mng0_pl:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 4E [ 7] 8 ld a, #0x4E + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_mng0_pl.rel b/build/wc_api/wc_mng0_pl.rel new file mode 100644 index 0000000..a79fdea --- /dev/null +++ b/build/wc_api/wc_mng0_pl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_mng0_pl +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_mng0_pl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 4E CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_mng0_pl.sym b/build/wc_api/wc_mng0_pl.sym new file mode 100644 index 0000000..cb3f424 --- /dev/null +++ b/build/wc_api/wc_mng0_pl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_mng0 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_mng0vpl.lst b/build/wc_api/wc_mng0vpl.lst new file mode 100644 index 0000000..6a3b1d9 --- /dev/null +++ b/build/wc_api/wc_mng0vpl.lst @@ -0,0 +1,11 @@ + 1 .module wc_mng0vpl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_mng0vpl + 00000000 5 _wc_mng0vpl:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 50 [ 7] 8 ld a, #0x50 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_mng0vpl.rel b/build/wc_api/wc_mng0vpl.rel new file mode 100644 index 0000000..6c4be80 --- /dev/null +++ b/build/wc_api/wc_mng0vpl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_mng0vpl +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_mng0vpl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 50 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_mng0vpl.sym b/build/wc_api/wc_mng0vpl.sym new file mode 100644 index 0000000..cb3f424 --- /dev/null +++ b/build/wc_api/wc_mng0vpl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_mng0 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_mng8_pl.lst b/build/wc_api/wc_mng8_pl.lst new file mode 100644 index 0000000..d449ad6 --- /dev/null +++ b/build/wc_api/wc_mng8_pl.lst @@ -0,0 +1,11 @@ + 1 .module wc_mng8_pl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_mng8_pl + 00000000 5 _wc_mng8_pl:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 4F [ 7] 8 ld a, #0x4F + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_mng8_pl.rel b/build/wc_api/wc_mng8_pl.rel new file mode 100644 index 0000000..023e0c3 --- /dev/null +++ b/build/wc_api/wc_mng8_pl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_mng8_pl +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_mng8_pl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 4F CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_mng8_pl.sym b/build/wc_api/wc_mng8_pl.sym new file mode 100644 index 0000000..bf03c09 --- /dev/null +++ b/build/wc_api/wc_mng8_pl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_mng8 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_mng8vpl.lst b/build/wc_api/wc_mng8vpl.lst new file mode 100644 index 0000000..f56dd4c --- /dev/null +++ b/build/wc_api/wc_mng8vpl.lst @@ -0,0 +1,11 @@ + 1 .module wc_mng8vpl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_mng8vpl + 00000000 5 _wc_mng8vpl:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 51 [ 7] 8 ld a, #0x51 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_mng8vpl.rel b/build/wc_api/wc_mng8vpl.rel new file mode 100644 index 0000000..649dd39 --- /dev/null +++ b/build/wc_api/wc_mng8vpl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_mng8vpl +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_mng8vpl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 51 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_mng8vpl.sym b/build/wc_api/wc_mng8vpl.sym new file mode 100644 index 0000000..bf03c09 --- /dev/null +++ b/build/wc_api/wc_mng8vpl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_mng8 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_mngc_pl.lst b/build/wc_api/wc_mngc_pl.lst new file mode 100644 index 0000000..7e79b93 --- /dev/null +++ b/build/wc_api/wc_mngc_pl.lst @@ -0,0 +1,11 @@ + 1 .module wc_mngc_pl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_mngc_pl + 00000000 5 _wc_mngc_pl:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 00 [ 7] 8 ld a, #0x00 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_mngc_pl.rel b/build/wc_api/wc_mngc_pl.rel new file mode 100644 index 0000000..15f4f89 --- /dev/null +++ b/build/wc_api/wc_mngc_pl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_mngc_pl +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_mngc_pl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 00 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_mngc_pl.sym b/build/wc_api/wc_mngc_pl.sym new file mode 100644 index 0000000..376108c --- /dev/null +++ b/build/wc_api/wc_mngc_pl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_mngc 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_mngcvpl.lst b/build/wc_api/wc_mngcvpl.lst new file mode 100644 index 0000000..7e2b28b --- /dev/null +++ b/build/wc_api/wc_mngcvpl.lst @@ -0,0 +1,11 @@ + 1 .module wc_mngcvpl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_mngcvpl + 00000000 5 _wc_mngcvpl:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 41 [ 7] 8 ld a, #0x41 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_mngcvpl.rel b/build/wc_api/wc_mngcvpl.rel new file mode 100644 index 0000000..476766d --- /dev/null +++ b/build/wc_api/wc_mngcvpl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_mngcvpl +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_mngcvpl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 41 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_mngcvpl.sym b/build/wc_api/wc_mngcvpl.sym new file mode 100644 index 0000000..376108c --- /dev/null +++ b/build/wc_api/wc_mngcvpl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_mngc 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_mngv_pl.lst b/build/wc_api/wc_mngv_pl.lst new file mode 100644 index 0000000..7e4ea99 --- /dev/null +++ b/build/wc_api/wc_mngv_pl.lst @@ -0,0 +1,11 @@ + 1 .module wc_mngv_pl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_mngv_pl + 00000000 5 _wc_mngv_pl:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 40 [ 7] 8 ld a, #0x40 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_mngv_pl.rel b/build/wc_api/wc_mngv_pl.rel new file mode 100644 index 0000000..3eb0751 --- /dev/null +++ b/build/wc_api/wc_mngv_pl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_mngv_pl +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_mngv_pl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 40 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_mngv_pl.sym b/build/wc_api/wc_mngv_pl.sym new file mode 100644 index 0000000..d9195da --- /dev/null +++ b/build/wc_api/wc_mngv_pl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_mngv 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_nork.lst b/build/wc_api/wc_nork.lst new file mode 100644 index 0000000..569d305 --- /dev/null +++ b/build/wc_api/wc_nork.lst @@ -0,0 +1,18 @@ + 1 .module wc_nork + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_nork + 00000000 5 _wc_nork:: + 00000000 DD E5 [15] 6 push ix + 00000002 DD 21 00 00 [14] 7 ld ix, #0 + 00000006 DD 39 [15] 8 add ix, sp + 00000008 DD 7E 04 [19] 9 ld a, 4(ix) ; A = val (from stack) + 0000000B 08 [ 4] 10 ex af, af' ; A' = val + 11 ; HL = addr (1st arg, already in HL) + 0000000C 3E 0A [ 7] 12 ld a, #0x0A + 0000000E CD 06 60 [17] 13 call WC_ENTRY + 00000011 DD E1 [14] 14 pop ix + 15 ; callee cleans 1 byte (val) + 00000013 E1 [10] 16 pop hl + 00000014 33 [ 6] 17 inc sp + 00000015 E9 [ 4] 18 jp (hl) diff --git a/build/wc_api/wc_nork.rel b/build/wc_api/wc_nork.rel new file mode 100644 index 0000000..99053dc --- /dev/null +++ b/build/wc_api/wc_nork.rel @@ -0,0 +1,12 @@ +XL4 +H 1 areas 2 global symbols +M wc_nork +S .__.ABS. Def00000000 +A _CODE size 16 flags 0 addr 0 +S _wc_nork Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 DD 21 00 00 DD 39 DD 7E 04 08 +R 00 00 00 00 +T 0C 00 00 00 3E 0A CD 06 60 DD E1 E1 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_nork.sym b/build/wc_api/wc_nork.sym new file mode 100644 index 0000000..6a043c2 --- /dev/null +++ b/build/wc_api/wc_nork.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_nork 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 16 flags 0 + diff --git a/build/wc_api/wc_prm_pl.lst b/build/wc_api/wc_prm_pl.lst new file mode 100644 index 0000000..cd2e6a0 --- /dev/null +++ b/build/wc_api/wc_prm_pl.lst @@ -0,0 +1,15 @@ + 1 .module wc_prm_pl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_prm_pl + 00000000 5 _wc_prm_pl:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 53 [ 7] 8 ld a, #0x53 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 10 ; Z=нет параметра → вернуть 0xFF, иначе A=номер опции + 00000008 20 02 [12] 11 jr nz, _wc_prm_pl_end + 0000000A 3E FF [ 7] 12 ld a, #0xFF + 0000000C 13 _wc_prm_pl_end: + 0000000C DD E1 [14] 14 pop ix + 0000000E C9 [10] 15 ret diff --git a/build/wc_api/wc_prm_pl.rel b/build/wc_api/wc_prm_pl.rel new file mode 100644 index 0000000..7a43f50 --- /dev/null +++ b/build/wc_api/wc_prm_pl.rel @@ -0,0 +1,14 @@ +XL4 +H 1 areas 2 global symbols +M wc_prm_pl +S .__.ABS. Def00000000 +A _CODE size F flags 0 addr 0 +S _wc_prm_pl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 53 CD 06 60 20 02 3E FF +R 00 00 00 00 +T 0C 00 00 00 +R 00 00 00 00 +T 0C 00 00 00 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_prm_pl.sym b/build/wc_api/wc_prm_pl.sym new file mode 100644 index 0000000..fc985f0 --- /dev/null +++ b/build/wc_api/wc_prm_pl.sym @@ -0,0 +1,16 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_prm_ 00000000 GR + 0 _wc_prm_ 0000000C R + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size F flags 0 + diff --git a/build/wc_api/wc_prsrw.lst b/build/wc_api/wc_prsrw.lst new file mode 100644 index 0000000..00a235c --- /dev/null +++ b/build/wc_api/wc_prsrw.lst @@ -0,0 +1,34 @@ + 1 .module wc_prsrw + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_prsrw + 00000000 5 _wc_prsrw:: + 00000000 DD E5 [15] 6 push ix + 00000002 E5 [11] 7 push hl ; save win (HL = 1st arg) + 00000003 D5 [11] 8 push de ; save str (DE = 2nd arg) + 00000004 DD 21 00 00 [14] 9 ld ix, #0 + 00000008 DD 39 [15] 10 add ix, sp + 0000000A DD 46 0B [19] 11 ld b, 11(ix) ; B = len_hi + 0000000D DD 4E 0A [19] 12 ld c, 10(ix) ; C = len_lo + 00000010 DD 56 08 [19] 13 ld d, 8(ix) ; D = y + 00000013 DD 5E 09 [19] 14 ld e, 9(ix) ; E = x + 00000016 DD 6E 00 [19] 15 ld l, 0(ix) + 00000019 DD 66 01 [19] 16 ld h, 1(ix) ; HL = str + 0000001C D9 [ 4] 17 exx + 0000001D DD 6E 02 [19] 18 ld l, 2(ix) + 00000020 DD 66 03 [19] 19 ld h, 3(ix) ; HL = win + 00000023 E5 [11] 20 push hl + 00000024 DD E1 [14] 21 pop ix ; IX = win + 00000026 D9 [ 4] 22 exx ; BC=len, DE=y|x, HL=str + 00000027 3E 03 [ 7] 23 ld a, #0x03 + 00000029 CD 06 60 [17] 24 call WC_ENTRY + 0000002C D1 [10] 25 pop de ; remove saved str + 0000002D E1 [10] 26 pop hl ; remove saved win + 0000002E DD E1 [14] 27 pop ix ; restore caller IX + 28 ; callee cleans: y(1)+x(1)+len(2) = 4 bytes + 00000030 E1 [10] 29 pop hl ; HL = return address + 00000031 33 [ 6] 30 inc sp + 00000032 33 [ 6] 31 inc sp + 00000033 33 [ 6] 32 inc sp + 00000034 33 [ 6] 33 inc sp + 00000035 E9 [ 4] 34 jp (hl) diff --git a/build/wc_api/wc_prsrw.rel b/build/wc_api/wc_prsrw.rel new file mode 100644 index 0000000..d1c23a6 --- /dev/null +++ b/build/wc_api/wc_prsrw.rel @@ -0,0 +1,18 @@ +XL4 +H 1 areas 2 global symbols +M wc_prsrw +S .__.ABS. Def00000000 +A _CODE size 36 flags 0 addr 0 +S _wc_prsrw Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 E5 D5 DD 21 00 00 DD 39 DD 46 +R 00 00 00 00 +T 0C 00 00 00 0B DD 4E 0A DD 56 08 DD 5E 09 DD 6E +R 00 00 00 00 +T 18 00 00 00 00 DD 66 01 D9 DD 6E 02 DD 66 03 E5 +R 00 00 00 00 +T 24 00 00 00 DD E1 D9 3E 03 CD 06 60 D1 E1 DD E1 +R 00 00 00 00 +T 30 00 00 00 E1 33 33 33 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_prsrw.sym b/build/wc_api/wc_prsrw.sym new file mode 100644 index 0000000..d30a15e --- /dev/null +++ b/build/wc_api/wc_prsrw.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_prsr 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 36 flags 0 + diff --git a/build/wc_api/wc_prsrw_attr.lst b/build/wc_api/wc_prsrw_attr.lst new file mode 100644 index 0000000..904582d --- /dev/null +++ b/build/wc_api/wc_prsrw_attr.lst @@ -0,0 +1,50 @@ + 1 .module wc_prsrw_attr + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_prsrw_attr + 00000000 5 _wc_prsrw_attr:: + 00000000 DD E5 [15] 6 push ix + 00000002 E5 [11] 7 push hl ; save win (HL = 1st arg) + 00000003 D5 [11] 8 push de ; save str (DE = 2nd arg) + 00000004 DD 21 00 00 [14] 9 ld ix, #0 + 00000008 DD 39 [15] 10 add ix, sp + 11 + 12 ; ── вызов #03 (prsrw) ────────────────────────────────── + 0000000A DD 46 0B [19] 13 ld b, 11(ix) ; B = len_hi + 0000000D DD 4E 0A [19] 14 ld c, 10(ix) ; C = len_lo + 00000010 DD 56 08 [19] 15 ld d, 8(ix) ; D = y + 00000013 DD 5E 09 [19] 16 ld e, 9(ix) ; E = x + 00000016 DD 6E 00 [19] 17 ld l, 0(ix) + 00000019 DD 66 01 [19] 18 ld h, 1(ix) ; HL = str + 0000001C D9 [ 4] 19 exx + 0000001D DD 6E 02 [19] 20 ld l, 2(ix) + 00000020 DD 66 03 [19] 21 ld h, 3(ix) ; HL = win + 00000023 DD 7E 0C [19] 22 ld a, 12(ix) ; A = attr (IX всё ещё → фрейм) + 00000026 F5 [11] 23 push af ; сохраняем attr на стеке + 00000027 E5 [11] 24 push hl + 00000028 DD E1 [14] 25 pop ix ; IX = win + 0000002A D9 [ 4] 26 exx ; BC=len, DE=y|x, HL=str; HL'=win + 0000002B 3E 03 [ 7] 27 ld a, #0x03 + 0000002D CD 06 60 [17] 28 call WC_ENTRY + 29 + 30 ; ── вызов #04 (priat) ────────────────────────────────── + 00000030 F1 [10] 31 pop af ; A = attr + 00000031 08 [ 4] 32 ex af, af' ; A' = attr + 00000032 D9 [ 4] 33 exx ; HL = win (из HL'), BC=len, DE=y|x + 00000033 E5 [11] 34 push hl + 00000034 DD E1 [14] 35 pop ix ; IX = win + 00000036 D9 [ 4] 36 exx ; restore BC=len, DE=y|x + 00000037 3E 04 [ 7] 37 ld a, #0x04 + 00000039 CD 06 60 [17] 38 call WC_ENTRY + 39 + 0000003C D1 [10] 40 pop de ; remove saved str + 0000003D E1 [10] 41 pop hl ; remove saved win + 0000003E DD E1 [14] 42 pop ix ; restore caller IX + 43 ; callee cleans 5 bytes: y(1)+x(1)+len(2)+attr(1) + 00000040 E1 [10] 44 pop hl ; HL = return address + 00000041 33 [ 6] 45 inc sp + 00000042 33 [ 6] 46 inc sp + 00000043 33 [ 6] 47 inc sp + 00000044 33 [ 6] 48 inc sp + 00000045 33 [ 6] 49 inc sp + 00000046 E9 [ 4] 50 jp (hl) diff --git a/build/wc_api/wc_prsrw_attr.rel b/build/wc_api/wc_prsrw_attr.rel new file mode 100644 index 0000000..48bfd5c --- /dev/null +++ b/build/wc_api/wc_prsrw_attr.rel @@ -0,0 +1,20 @@ +XL4 +H 1 areas 2 global symbols +M wc_prsrw_attr +S .__.ABS. Def00000000 +A _CODE size 47 flags 0 addr 0 +S _wc_prsrw_attr Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 E5 D5 DD 21 00 00 DD 39 DD 46 +R 00 00 00 00 +T 0C 00 00 00 0B DD 4E 0A DD 56 08 DD 5E 09 DD 6E +R 00 00 00 00 +T 18 00 00 00 00 DD 66 01 D9 DD 6E 02 DD 66 03 DD +R 00 00 00 00 +T 24 00 00 00 7E 0C F5 E5 DD E1 D9 3E 03 CD 06 60 +R 00 00 00 00 +T 30 00 00 00 F1 08 D9 E5 DD E1 D9 3E 04 CD 06 60 +R 00 00 00 00 +T 3C 00 00 00 D1 E1 DD E1 E1 33 33 33 33 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_prsrw_attr.sym b/build/wc_api/wc_prsrw_attr.sym new file mode 100644 index 0000000..59ec119 --- /dev/null +++ b/build/wc_api/wc_prsrw_attr.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_prsr 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 47 flags 0 + diff --git a/build/wc_api/wc_prwow.lst b/build/wc_api/wc_prwow.lst new file mode 100644 index 0000000..6aa9b8a --- /dev/null +++ b/build/wc_api/wc_prwow.lst @@ -0,0 +1,12 @@ + 1 .module wc_prwow + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_prwow + 00000000 5 _wc_prwow:: + 00000000 DD E5 [15] 6 push ix + 00000002 E5 [11] 7 push hl + 00000003 DD E1 [14] 8 pop ix + 00000005 3E 01 [ 7] 9 ld a, #0x01 + 00000007 CD 06 60 [17] 10 call WC_ENTRY + 0000000A DD E1 [14] 11 pop ix + 0000000C C9 [10] 12 ret diff --git a/build/wc_api/wc_prwow.rel b/build/wc_api/wc_prwow.rel new file mode 100644 index 0000000..ff53442 --- /dev/null +++ b/build/wc_api/wc_prwow.rel @@ -0,0 +1,12 @@ +XL4 +H 1 areas 2 global symbols +M wc_prwow +S .__.ABS. Def00000000 +A _CODE size D flags 0 addr 0 +S _wc_prwow Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 E5 DD E1 3E 01 CD 06 60 DD E1 +R 00 00 00 00 +T 0C 00 00 00 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_prwow.sym b/build/wc_api/wc_prwow.sym new file mode 100644 index 0000000..1ad13e2 --- /dev/null +++ b/build/wc_api/wc_prwow.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_prwo 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size D flags 0 + diff --git a/build/wc_api/wc_rename.lst b/build/wc_api/wc_rename.lst new file mode 100644 index 0000000..24a15fa --- /dev/null +++ b/build/wc_api/wc_rename.lst @@ -0,0 +1,10 @@ + 1 .module wc_rename + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_rename + 00000000 5 _wc_rename:: + 00000000 DD E5 [15] 6 push ix + 00000002 3E 4A [ 7] 7 ld a, #0x4A + 00000004 CD 06 60 [17] 8 call WC_ENTRY + 00000007 DD E1 [14] 9 pop ix + 00000009 C9 [10] 10 ret diff --git a/build/wc_api/wc_rename.rel b/build/wc_api/wc_rename.rel new file mode 100644 index 0000000..3a87ce4 --- /dev/null +++ b/build/wc_api/wc_rename.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_rename +S .__.ABS. Def00000000 +A _CODE size A flags 0 addr 0 +S _wc_rename Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 3E 4A CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_rename.sym b/build/wc_api/wc_rename.sym new file mode 100644 index 0000000..dedf643 --- /dev/null +++ b/build/wc_api/wc_rename.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_rena 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size A flags 0 + diff --git a/build/wc_api/wc_resident_call.lst b/build/wc_api/wc_resident_call.lst new file mode 100644 index 0000000..c27abed --- /dev/null +++ b/build/wc_api/wc_resident_call.lst @@ -0,0 +1,8 @@ + 1 .module wc_resident_call + 2 .area _CODE + 3 .globl _wc_resident_call + 00000000 4 _wc_resident_call:: + 00000000 EB [ 4] 5 ex de, hl ; HL = addr (from DE = 2nd arg) + 6 ; A = page (1st arg, already in A) + 00000001 CD 28 60 [17] 7 call 0x6028 ; resident call — returns here + 00000004 C9 [10] 8 ret diff --git a/build/wc_api/wc_resident_call.rel b/build/wc_api/wc_resident_call.rel new file mode 100644 index 0000000..4f61fcb --- /dev/null +++ b/build/wc_api/wc_resident_call.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_resident_call +S .__.ABS. Def00000000 +A _CODE size 5 flags 0 addr 0 +S _wc_resident_call Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 EB CD 28 60 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_resident_call.sym b/build/wc_api/wc_resident_call.sym new file mode 100644 index 0000000..b702511 --- /dev/null +++ b/build/wc_api/wc_resident_call.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | 0 _wc_resi 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 5 flags 0 + diff --git a/build/wc_api/wc_resident_jump.lst b/build/wc_api/wc_resident_jump.lst new file mode 100644 index 0000000..d453937 --- /dev/null +++ b/build/wc_api/wc_resident_jump.lst @@ -0,0 +1,7 @@ + 1 .module wc_resident_jump + 2 .area _CODE + 3 .globl _wc_resident_jump + 00000000 4 _wc_resident_jump:: + 00000000 EB [ 4] 5 ex de, hl ; HL = addr (from DE = 2nd arg) + 6 ; A = page (1st arg, already in A) + 00000001 C3 20 60 [10] 7 jp 0x6020 ; resident jump — never returns! diff --git a/build/wc_api/wc_resident_jump.rel b/build/wc_api/wc_resident_jump.rel new file mode 100644 index 0000000..c25d2d2 --- /dev/null +++ b/build/wc_api/wc_resident_jump.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_resident_jump +S .__.ABS. Def00000000 +A _CODE size 4 flags 0 addr 0 +S _wc_resident_jump Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 EB C3 20 60 +R 00 00 00 00 diff --git a/build/wc_api/wc_resident_jump.sym b/build/wc_api/wc_resident_jump.sym new file mode 100644 index 0000000..ad035f7 --- /dev/null +++ b/build/wc_api/wc_resident_jump.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | 0 _wc_resi 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 4 flags 0 + diff --git a/build/wc_api/wc_rresb.lst b/build/wc_api/wc_rresb.lst new file mode 100644 index 0000000..c3531e5 --- /dev/null +++ b/build/wc_api/wc_rresb.lst @@ -0,0 +1,12 @@ + 1 .module wc_rresb + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_rresb + 00000000 5 _wc_rresb:: + 00000000 DD E5 [15] 6 push ix + 00000002 E5 [11] 7 push hl + 00000003 DD E1 [14] 8 pop ix + 00000005 3E 02 [ 7] 9 ld a, #0x02 + 00000007 CD 06 60 [17] 10 call WC_ENTRY + 0000000A DD E1 [14] 11 pop ix + 0000000C C9 [10] 12 ret diff --git a/build/wc_api/wc_rresb.rel b/build/wc_api/wc_rresb.rel new file mode 100644 index 0000000..704a40c --- /dev/null +++ b/build/wc_api/wc_rresb.rel @@ -0,0 +1,12 @@ +XL4 +H 1 areas 2 global symbols +M wc_rresb +S .__.ABS. Def00000000 +A _CODE size D flags 0 addr 0 +S _wc_rresb Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 E5 DD E1 3E 02 CD 06 60 DD E1 +R 00 00 00 00 +T 0C 00 00 00 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_rresb.sym b/build/wc_api/wc_rresb.sym new file mode 100644 index 0000000..b1feeac --- /dev/null +++ b/build/wc_api/wc_rresb.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_rres 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size D flags 0 + diff --git a/build/wc_api/wc_save512.lst b/build/wc_api/wc_save512.lst new file mode 100644 index 0000000..8832377 --- /dev/null +++ b/build/wc_api/wc_save512.lst @@ -0,0 +1,16 @@ + 1 .module wc_save512 + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_save512 + 00000000 5 _wc_save512:: + 00000000 DD E5 [15] 6 push ix + 00000002 DD 21 00 00 [14] 7 ld ix, #0 + 00000006 DD 39 [15] 8 add ix, sp + 00000008 DD 46 04 [19] 9 ld b, 4(ix) + 10 ; HL = src (DON'T ex de, hl!) + 0000000B 3E 31 [ 7] 11 ld a, #0x31 + 0000000D CD 06 60 [17] 12 call WC_ENTRY + 00000010 DD E1 [14] 13 pop ix + 00000012 E1 [10] 14 pop hl + 00000013 33 [ 6] 15 inc sp + 00000014 E9 [ 4] 16 jp (hl) diff --git a/build/wc_api/wc_save512.rel b/build/wc_api/wc_save512.rel new file mode 100644 index 0000000..47f79d8 --- /dev/null +++ b/build/wc_api/wc_save512.rel @@ -0,0 +1,12 @@ +XL4 +H 1 areas 2 global symbols +M wc_save512 +S .__.ABS. Def00000000 +A _CODE size 15 flags 0 addr 0 +S _wc_save512 Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 DD 21 00 00 DD 39 DD 46 04 3E +R 00 00 00 00 +T 0C 00 00 00 31 CD 06 60 DD E1 E1 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_save512.sym b/build/wc_api/wc_save512.sym new file mode 100644 index 0000000..770f6dc --- /dev/null +++ b/build/wc_api/wc_save512.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_save 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 15 flags 0 + diff --git a/build/wc_api/wc_scrlwow.lst b/build/wc_api/wc_scrlwow.lst new file mode 100644 index 0000000..e59b880 --- /dev/null +++ b/build/wc_api/wc_scrlwow.lst @@ -0,0 +1,33 @@ + 1 .module wc_scrlwow + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_scrlwow + 00000000 5 _wc_scrlwow:: + 00000000 DD E5 [15] 6 push ix + 00000002 E5 [11] 7 push hl ; save win (HL = 1st arg) + 00000003 DD 21 00 00 [14] 8 ld ix, #0 + 00000007 DD 39 [15] 9 add ix, sp + 00000009 DD 7E 0A [19] 10 ld a, 10(ix) ; A = flags + 0000000C DD 46 08 [19] 11 ld b, 8(ix) ; B = h + 0000000F DD 4E 09 [19] 12 ld c, 9(ix) ; C = w + 00000012 DD 56 06 [19] 13 ld d, 6(ix) ; D = y + 00000015 DD 5E 07 [19] 14 ld e, 7(ix) ; E = x + 00000018 D9 [ 4] 15 exx + 00000019 08 [ 4] 16 ex af, af' ; A' = flags + 0000001A DD 6E 00 [19] 17 ld l, 0(ix) + 0000001D DD 66 01 [19] 18 ld h, 1(ix) + 00000020 E5 [11] 19 push hl + 00000021 DD E1 [14] 20 pop ix ; IX = win + 00000023 D9 [ 4] 21 exx ; BC=h/w, DE=y/x + 00000024 3E 54 [ 7] 22 ld a, #0x54 + 00000026 CD 06 60 [17] 23 call WC_ENTRY + 00000029 E1 [10] 24 pop hl ; remove saved win + 0000002A DD E1 [14] 25 pop ix + 26 ; callee cleans 5 bytes + 0000002C E1 [10] 27 pop hl + 0000002D 33 [ 6] 28 inc sp + 0000002E 33 [ 6] 29 inc sp + 0000002F 33 [ 6] 30 inc sp + 00000030 33 [ 6] 31 inc sp + 00000031 33 [ 6] 32 inc sp + 00000032 E9 [ 4] 33 jp (hl) diff --git a/build/wc_api/wc_scrlwow.rel b/build/wc_api/wc_scrlwow.rel new file mode 100644 index 0000000..c159e3e --- /dev/null +++ b/build/wc_api/wc_scrlwow.rel @@ -0,0 +1,18 @@ +XL4 +H 1 areas 2 global symbols +M wc_scrlwow +S .__.ABS. Def00000000 +A _CODE size 33 flags 0 addr 0 +S _wc_scrlwow Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 E5 DD 21 00 00 DD 39 DD 7E 0A +R 00 00 00 00 +T 0C 00 00 00 DD 46 08 DD 4E 09 DD 56 06 DD 5E 07 +R 00 00 00 00 +T 18 00 00 00 D9 08 DD 6E 00 DD 66 01 E5 DD E1 D9 +R 00 00 00 00 +T 24 00 00 00 3E 54 CD 06 60 E1 DD E1 E1 33 33 33 +R 00 00 00 00 +T 30 00 00 00 33 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_scrlwow.sym b/build/wc_api/wc_scrlwow.sym new file mode 100644 index 0000000..9366638 --- /dev/null +++ b/build/wc_api/wc_scrlwow.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_scrl 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 33 flags 0 + diff --git a/build/wc_api/wc_stream.lst b/build/wc_api/wc_stream.lst new file mode 100644 index 0000000..185caa4 --- /dev/null +++ b/build/wc_api/wc_stream.lst @@ -0,0 +1,11 @@ + 1 .module wc_stream + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_stream + 00000000 5 _wc_stream:: + 00000000 DD E5 [15] 6 push ix + 00000002 57 [ 4] 7 ld d, a + 00000003 3E 39 [ 7] 8 ld a, #0x39 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_stream.rel b/build/wc_api/wc_stream.rel new file mode 100644 index 0000000..b4d888a --- /dev/null +++ b/build/wc_api/wc_stream.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_stream +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_stream Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 57 3E 39 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_stream.sym b/build/wc_api/wc_stream.sym new file mode 100644 index 0000000..65760aa --- /dev/null +++ b/build/wc_api/wc_stream.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_stre 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_strset.lst b/build/wc_api/wc_strset.lst new file mode 100644 index 0000000..a8dccb1 --- /dev/null +++ b/build/wc_api/wc_strset.lst @@ -0,0 +1,38 @@ + 1 .module wc_strset + 2 .area _CODE + 3 .globl _wc_strset + 00000000 4 _wc_strset:: + 00000000 DD E5 [15] 5 push ix + 00000002 DD 21 00 00 [14] 6 ld ix, #0 + 00000006 DD 39 [15] 7 add ix, sp + 8 + 00000008 DD 7E 04 [19] 9 ld a, 4(ix) ; c (из-за push ix) + 0000000B DD E1 [14] 10 pop ix + 11 + 12 ; записываем первый байт + 0000000D 77 [ 7] 13 ld (hl), a + 14 + 0000000E 42 [ 4] 15 ld b, d + 0000000F 4B [ 4] 16 ld c, e + 17 + 18 ; len == 1 → всё + 00000010 0B [ 6] 19 dec bc + 00000011 78 [ 4] 20 ld a, b + 00000012 B1 [ 4] 21 or c + 00000013 28 05 [12] 22 jr z, _wc_strset_done + 23 + 24 ; DE = HL + 1 + 00000015 E5 [11] 25 push hl + 00000016 D1 [10] 26 pop de + 00000017 13 [ 6] 27 inc de + 28 + 29 ; BC = len - 1 уже + 30 + 00000018 ED B0 [21] 31 ldir + 32 + 0000001A 33 _wc_strset_done: + 34 ; очистка стека + 0000001A C1 [10] 35 pop bc + 0000001B 33 [ 6] 36 inc sp + 0000001C C5 [11] 37 push bc + 0000001D C9 [10] 38 ret diff --git a/build/wc_api/wc_strset.rel b/build/wc_api/wc_strset.rel new file mode 100644 index 0000000..21a80a5 --- /dev/null +++ b/build/wc_api/wc_strset.rel @@ -0,0 +1,18 @@ +XL4 +H 1 areas 2 global symbols +M wc_strset +S .__.ABS. Def00000000 +A _CODE size 1E flags 0 addr 0 +S _wc_strset Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 DD 21 00 00 DD 39 DD 7E 04 DD +R 00 00 00 00 +T 0C 00 00 00 E1 77 42 4B 0B 78 B1 28 05 E5 D1 13 +R 00 00 00 00 +T 18 00 00 00 ED B0 +R 00 00 00 00 +T 1A 00 00 00 +R 00 00 00 00 +T 1A 00 00 00 C1 33 C5 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_strset.sym b/build/wc_api/wc_strset.sym new file mode 100644 index 0000000..e4bd720 --- /dev/null +++ b/build/wc_api/wc_strset.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | 0 _wc_strs 00000000 GR | 0 _wc_strs 0000001A R + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 1E flags 0 + diff --git a/build/wc_api/wc_tentry.lst b/build/wc_api/wc_tentry.lst new file mode 100644 index 0000000..1d42a6c --- /dev/null +++ b/build/wc_api/wc_tentry.lst @@ -0,0 +1,11 @@ + 1 .module wc_tentry + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_tentry + 00000000 5 _wc_tentry:: + 00000000 DD E5 [15] 6 push ix + 00000002 EB [ 4] 7 ex de, hl ; DE = addr (SDCC HL → WC DE) + 00000003 3E 33 [ 7] 8 ld a, #0x33 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 DD E1 [14] 10 pop ix + 0000000A C9 [10] 11 ret diff --git a/build/wc_api/wc_tentry.rel b/build/wc_api/wc_tentry.rel new file mode 100644 index 0000000..dbe3a54 --- /dev/null +++ b/build/wc_api/wc_tentry.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_tentry +S .__.ABS. Def00000000 +A _CODE size B flags 0 addr 0 +S _wc_tentry Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 EB 3E 33 CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_tentry.sym b/build/wc_api/wc_tentry.sym new file mode 100644 index 0000000..a227af2 --- /dev/null +++ b/build/wc_api/wc_tentry.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_tent 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size B flags 0 + diff --git a/build/wc_api/wc_tmrkdfl.lst b/build/wc_api/wc_tmrkdfl.lst new file mode 100644 index 0000000..570af53 --- /dev/null +++ b/build/wc_api/wc_tmrkdfl.lst @@ -0,0 +1,30 @@ + 1 .module wc_tmrkdfl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_tmrkdfl + 00000000 5 _wc_tmrkdfl:: + 00000000 DD E5 [15] 6 push ix + 00000002 E5 [11] 7 push hl ; save panel (HL = 1st arg) + 00000003 D5 [11] 8 push de ; save filenum (DE = 2nd arg) + 00000004 DD 21 00 00 [14] 9 ld ix, #0 + 00000008 DD 39 [15] 10 add ix, sp + 0000000A DD 5E 08 [19] 11 ld e, 8(ix) + 0000000D DD 56 09 [19] 12 ld d, 9(ix) ; DE = namebuf (from stack) + 00000010 DD 6E 00 [19] 13 ld l, 0(ix) + 00000013 DD 66 01 [19] 14 ld h, 1(ix) ; HL = filenum (from saved DE) + 00000016 D9 [ 4] 15 exx + 00000017 DD 6E 02 [19] 16 ld l, 2(ix) + 0000001A DD 66 03 [19] 17 ld h, 3(ix) ; HL = panel (from saved HL) + 0000001D E5 [11] 18 push hl + 0000001E DD E1 [14] 19 pop ix ; IX = panel + 00000020 D9 [ 4] 20 exx ; HL=filenum, DE=namebuf + 00000021 3E 36 [ 7] 21 ld a, #0x36 + 00000023 CD 06 60 [17] 22 call WC_ENTRY + 00000026 D1 [10] 23 pop de ; remove saved filenum + 00000027 E1 [10] 24 pop hl ; remove saved panel + 00000028 DD E1 [14] 25 pop ix + 26 ; callee cleans 2 bytes (namebuf) + 0000002A E1 [10] 27 pop hl + 0000002B 33 [ 6] 28 inc sp + 0000002C 33 [ 6] 29 inc sp + 0000002D E9 [ 4] 30 jp (hl) diff --git a/build/wc_api/wc_tmrkdfl.rel b/build/wc_api/wc_tmrkdfl.rel new file mode 100644 index 0000000..a7cf9e3 --- /dev/null +++ b/build/wc_api/wc_tmrkdfl.rel @@ -0,0 +1,16 @@ +XL4 +H 1 areas 2 global symbols +M wc_tmrkdfl +S .__.ABS. Def00000000 +A _CODE size 2E flags 0 addr 0 +S _wc_tmrkdfl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 E5 D5 DD 21 00 00 DD 39 DD 5E +R 00 00 00 00 +T 0C 00 00 00 08 DD 56 09 DD 6E 00 DD 66 01 D9 DD +R 00 00 00 00 +T 18 00 00 00 6E 02 DD 66 03 E5 DD E1 D9 3E 36 CD +R 00 00 00 00 +T 24 00 00 00 06 60 D1 E1 DD E1 E1 33 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_tmrkdfl.sym b/build/wc_api/wc_tmrkdfl.sym new file mode 100644 index 0000000..bcfc0ac --- /dev/null +++ b/build/wc_api/wc_tmrkdfl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_tmrk 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 2E flags 0 + diff --git a/build/wc_api/wc_turbopl.lst b/build/wc_api/wc_turbopl.lst new file mode 100644 index 0000000..a4a6073 --- /dev/null +++ b/build/wc_api/wc_turbopl.lst @@ -0,0 +1,12 @@ + 1 .module wc_turbopl + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_turbopl + 00000000 5 _wc_turbopl:: + 00000000 DD E5 [15] 6 push ix + 00000002 4D [ 4] 7 ld c, l ; C = param (2-й аргумент) + 00000003 47 [ 4] 8 ld b, a ; B = mode (1-й аргумент) + 00000004 3E 0E [ 7] 9 ld a, #0x0E + 00000006 CD 06 60 [17] 10 call WC_ENTRY + 00000009 DD E1 [14] 11 pop ix + 0000000B C9 [10] 12 ret diff --git a/build/wc_api/wc_turbopl.rel b/build/wc_api/wc_turbopl.rel new file mode 100644 index 0000000..a223b11 --- /dev/null +++ b/build/wc_api/wc_turbopl.rel @@ -0,0 +1,10 @@ +XL4 +H 1 areas 2 global symbols +M wc_turbopl +S .__.ABS. Def00000000 +A _CODE size C flags 0 addr 0 +S _wc_turbopl Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 4D 47 3E 0E CD 06 60 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_turbopl.sym b/build/wc_api/wc_turbopl.sym new file mode 100644 index 0000000..d4efa6d --- /dev/null +++ b/build/wc_api/wc_turbopl.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_turb 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size C flags 0 + diff --git a/build/wc_api/wc_txtpr.lst b/build/wc_api/wc_txtpr.lst new file mode 100644 index 0000000..a7948f1 --- /dev/null +++ b/build/wc_api/wc_txtpr.lst @@ -0,0 +1,30 @@ + 1 .module wc_txtpr + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_txtpr + 00000000 5 _wc_txtpr:: + 00000000 DD E5 [15] 6 push ix ; (1) save caller IX + 00000002 E5 [11] 7 push hl ; (2) save win ptr (HL = 1st arg) + 00000003 D5 [11] 8 push de ; (3) save str ptr (DE = 2nd arg) + 00000004 DD 21 00 00 [14] 9 ld ix, #0 + 00000008 DD 39 [15] 10 add ix, sp + 0000000A DD 56 09 [19] 11 ld d, 9(ix) ; D = y + 0000000D DD 5E 08 [19] 12 ld e, 8(ix) ; E = x + 00000010 DD 6E 00 [19] 13 ld l, 0(ix) + 00000013 DD 66 01 [19] 14 ld h, 1(ix) ; HL = str ptr + 00000016 D9 [ 4] 15 exx ; shadow: HL'=str, D'=y, E'=x + 00000017 DD 6E 02 [19] 16 ld l, 2(ix) + 0000001A DD 66 03 [19] 17 ld h, 3(ix) ; HL = win ptr + 0000001D E5 [11] 18 push hl + 0000001E DD E1 [14] 19 pop ix ; IX = win ptr + 00000020 D9 [ 4] 20 exx ; restore HL=str, D=y, E=x + 00000021 3E 0B [ 7] 21 ld a, #0x0B + 00000023 CD 06 60 [17] 22 call WC_ENTRY + 00000026 7A [ 4] 23 ld a, d ; A = return: next Y + 00000027 D1 [10] 24 pop de ; (3) restore DE + 00000028 E1 [10] 25 pop hl ; (2) restore HL + 00000029 DD E1 [14] 26 pop ix ; (1) restore IX + 0000002B E1 [10] 27 pop hl ; HL = return address + 0000002C 33 [ 6] 28 inc sp ; } discard caller's push bc (2 bytes: y, x) + 0000002D 33 [ 6] 29 inc sp ; } + 0000002E E9 [ 4] 30 jp (hl) ; return (callee cleans stack args) diff --git a/build/wc_api/wc_txtpr.rel b/build/wc_api/wc_txtpr.rel new file mode 100644 index 0000000..9c9cad6 --- /dev/null +++ b/build/wc_api/wc_txtpr.rel @@ -0,0 +1,16 @@ +XL4 +H 1 areas 2 global symbols +M wc_txtpr +S .__.ABS. Def00000000 +A _CODE size 2F flags 0 addr 0 +S _wc_txtpr Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 DD E5 E5 D5 DD 21 00 00 DD 39 DD 56 +R 00 00 00 00 +T 0C 00 00 00 09 DD 5E 08 DD 6E 00 DD 66 01 D9 DD +R 00 00 00 00 +T 18 00 00 00 6E 02 DD 66 03 E5 DD E1 D9 3E 0B CD +R 00 00 00 00 +T 24 00 00 00 06 60 7A D1 E1 DD E1 E1 33 33 E9 +R 00 00 00 00 diff --git a/build/wc_api/wc_txtpr.sym b/build/wc_api/wc_txtpr.sym new file mode 100644 index 0000000..5edf8f7 --- /dev/null +++ b/build/wc_api/wc_txtpr.sym @@ -0,0 +1,15 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_txtp 00000000 GR + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 2F flags 0 + diff --git a/build/wc_api/wc_yn.lst b/build/wc_api/wc_yn.lst new file mode 100644 index 0000000..9535038 --- /dev/null +++ b/build/wc_api/wc_yn.lst @@ -0,0 +1,15 @@ + 1 .module wc_yn + 2 .area _CODE + 00006006 3 WC_ENTRY = 0x6006 + 4 .globl _wc_yn + 00000000 5 _wc_yn:: + 00000000 08 [ 4] 6 ex af, af' + 00000001 DD E5 [15] 7 push ix + 00000003 3E 08 [ 7] 8 ld a, #0x08 + 00000005 CD 06 60 [17] 9 call WC_ENTRY + 00000008 3E 00 [ 7] 10 ld a, #0 + 0000000A 20 01 [12] 11 jr nz, _wc_yn_end + 0000000C 3C [ 4] 12 inc a + 0000000D 13 _wc_yn_end: + 0000000D DD E1 [14] 14 pop ix + 0000000F C9 [10] 15 ret diff --git a/build/wc_api/wc_yn.rel b/build/wc_api/wc_yn.rel new file mode 100644 index 0000000..25c799b --- /dev/null +++ b/build/wc_api/wc_yn.rel @@ -0,0 +1,16 @@ +XL4 +H 1 areas 2 global symbols +M wc_yn +S .__.ABS. Def00000000 +A _CODE size 10 flags 0 addr 0 +S _wc_yn Def00000000 +T 00 00 00 00 +R 00 00 00 00 +T 00 00 00 00 08 DD E5 3E 08 CD 06 60 3E 00 20 01 +R 00 00 00 00 +T 0C 00 00 00 3C +R 00 00 00 00 +T 0D 00 00 00 +R 00 00 00 00 +T 0D 00 00 00 DD E1 C9 +R 00 00 00 00 diff --git a/build/wc_api/wc_yn.sym b/build/wc_api/wc_yn.sym new file mode 100644 index 0000000..c30a4d0 --- /dev/null +++ b/build/wc_api/wc_yn.sym @@ -0,0 +1,16 @@ + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 1 +Hexadecimal [32-Bits] + +Symbol Table + + .__.$$$.= 00002710 L | .__.ABS.= 00000000 G | .__.CPU.= 00000000 L + .__.H$L.= 00000000 L | WC_ENTRY= 00006006 | 0 _wc_yn 00000000 GR + 0 _wc_yn_e 0000000D R + + ASxxxx Assembler V05.50.4+NoICE+SDCCmods-WIP-R14 (Zilog Z80 / Hitachi HD64180 / ZX-Next / eZ80 / R800) Page 2 +Hexadecimal [32-Bits] + +Area Table + + 0 _CODE size 10 flags 0 + diff --git a/doc/01_WC_Plugin_Overview.md b/doc/01_WC_Plugin_Overview.md new file mode 100644 index 0000000..8d42c21 --- /dev/null +++ b/doc/01_WC_Plugin_Overview.md @@ -0,0 +1,380 @@ +# Wild Commander — Обзор Системы Плагинов + +> Версия документа: 2.0 — дополнена по результатам анализа исходников +> Источники: `inc/wc_api.h`, `asm/wc_api.s`, `asm/crt0.s`, `DiskRef/Help.txt`, `DiskRef/WC_History.txt` + +## Введение + +Wild Commander (WC) — файловый менеджер с системой плагинов для платформы +**TS-Configuration** (ZX Spectrum Evo). Выполняет роль основной оболочки и +загрузчика для работы с FAT32-дисками, SD-картами и IDE. + +WC запускается из BootROM / vDOS и предоставляет: +- Двухпанельный интерфейс (80 × 25/30/36 TXT-режим) +- FAT32 (длинные и короткие имена до 255 символов) +- Систему плагинов (.WMF) с горячей загрузкой/выгрузкой +- Поддержку устройств: SD (Z-Controller), IDE NEMO, IDE SMUC, NeoGS SD +- Встроенный просмотрщик (F3): TXT, HEX, TGA, SCR, Gigascreen, MGS — до 1 МБ +- Мультиязычность (ENG / RUS, переключение CTRL+SHIFT) + +### Версии + +| Компонент | Версия | Примечание | +|-----------|--------|------------| +| WC | 1.10 (03.08.2025) | Последняя из `WC_History.txt` | +| Plugin System | **#10** | Формат заголовка `.WMF` | +| Идентификатор | `WildCommanderMDL` | 16 байт, offset +16 в заголовке | + +## Общая концепция + +Плагины WC (.WMF файлы) — модульные расширения, выполняемые в адресном +пространстве Z80. Поддерживают до **64 страниц** (1 МБ) кода/данных. + +Типичные области применения: +- Просмотр файлов различных форматов (BMP, GIF, ILBM, SCR) +- Воспроизведение музыки и видео (VGM, MOD, WAV, MP3/GS) +- Редактирование файлов (TXT Editor с подсветкой синтаксиса) +- Работа с ROM/Flash (ROM_PROG) +- Монтирование образов дисков (MOUNTER: TRD, TAP, FDI) +- Утилиты (часы, скринсейвер, компилятор, дамп памяти) + +## Типы запуска плагинов + +При запуске плагина, в регистр **A** передается тип вызова: + +| Код | Константа (C) | Описание | +|-----|---------------|----------| +| #00 | `WC_PCOND_EXT_ONLY` | По расширению файла (ENTER / F3) | +| #01 | `WC_PCOND_ON_LOAD` | При первой загрузке плагина (ENTER) | +| #02 | `WC_PCOND_TIMER` | По таймеру скринсейвера + меню F10 | +| #03 | `WC_PCOND_F10` | Из меню запуска плагинов (F10) | +| #04 | `WC_PCOND_VIEWER` | Из меню вьювера (SHIFT+F3 / SHIFT+ENTER) | +| #05 | `WC_PCOND_F10_VIEW` | F10 + SHIFT+F3 | +| #11 | `WC_PCOND_F10_F2` | F10 + F2 | +| #12 | `WC_PCOND_F2` | По нажатию F2 | +| #13 | `WC_PCOND_F10_F4` | F10 + F4 | +| #14 | `WC_PCOND_F4` | По нажатию F4 | +| #15 | `WC_PCOND_F10_V_F4` | F10 + SHIFT+F3 + F4 | + +> **Примечание:** Все плагины также вызываются по расширению, если расширения +> указаны в блоке расширений заголовка (offset +64). ENTER и F3 разделены +> начиная с WC v1.00 — флаг +63 определяет, какой из них запускает плагин. + +## Входные параметры при запуске + +WC делает `CALL #8000`. В `crt0.s` (SDCC) входные регистры сохраняются +в глобальные переменные автоматически. + +```asm +; Входные параметры: +A - тип вызова (см. таблицу выше) +[DE,HL] - длина файла в байтах (DE=hi16, HL=lo16) +BC - адрес строки с именем файла (для типов #00, #04, #14) + формат: name(1-255), 0 (длинное FAT имя) +IX - адрес структуры активной панели + +; Альтернативные регистры (только для #00,#04,#12,#14): +A' - номер расширения (0-31) для типа #00 +DE' - количество файлов/каталогов в панели (включая "..") +HL' - номер объекта из панели (от 1) +``` + +В SDCC-плагине (`crt0.s` автоматически): +```c +extern uint8_t wc_file_ext; // ← A' (номер расширения) +extern char * wc_file_name; // ← BC (указатель на имя) +extern uint32_t wc_file_size; // ← [DE,HL] (32-бит размер) +``` + +## Коды возврата из плагина + +При выходе из плагина регистр **A** определяет действие WC: + +| A | Константа (C) | Действие | +|---|---------------|----------| +| 0 | `WC_EXIT_ESC` | Просто выход (ESC) | +| 1 | `WC_EXIT_UNRECOGNIZED` | Файл не опознан → передать другому плагину (тип #00) | +| 2 | `WC_EXIT_NEXT` | Перейти на следующий файл (тип #00) | +| 3 | `WC_EXIT_RELOAD_DIR` | Перечитать каталог | +| 4 | `WC_EXIT_PREV` | Перейти на предыдущий файл (тип #00) | + +В SDCC-плагине: `wc_exit_code = 0; return 0;` — `crt0.s` считывает +`wc_exit_code` в A перед `RET` обратно в WC. + +> **Код 2/4 (NEXT/PREV):** WC вызывает тот же плагин повторно с другим +> файлом. `crt0.s` заново обнуляет BSS (`_DATA`) и копирует инициализаторы +> (`_INITIALIZER → _INITIALIZED`), поэтому все `static` переменные сбросятся. + +## Системные адреса + +> Источник: `src_sdcc/inc/wc_api.h`, раздел «СИСТЕМНЫЕ ПЕРЕМЕННЫЕ WC» + +| Адрес | Константа | Размер | Описание | +|---------------|--------------|--------|---------------------------------------------------| +| `#6000` | `WC_SYS_PG0` | 1 | Текущая страница в окне #0000 | +| `#6001` | `WC_SYS_PG4` | 1 | Текущая страница в окне #4000 | +| `#6002` | `WC_SYS_PG8` | 1 | Текущая страница в окне #8000 | +| `#6003` | `WC_SYS_PGC` | 1 | Текущая страница в окне #C000 | +| `#6004` | `WC_SYS_ABT` | 1 | 1 = был нажат ESC | +| `#6005` | `WC_SYS_ENT` | 1 | 1 = был нажат ENTER | +| `#6006` | `WC_SYS_FUN` | 3 | **Точка входа WC API** (JP к диспетчеру) | +| `#6009` | `WC_SYS_TMN` | 2 | Таймер INT (uint16_t, ++/кадр ≈50 Гц) | +| `#600B` | `WC_SYS_FEP` | 1 | Страница FAT-движка в окне 0 | +| `#600C` | `WC_SYS_XPP` | 1 | Сохранённая страница | +| `#600D` | `WC_SYS_CNFV`| 1 | ЦАП: 0=PWM, 1=3bit, 2=4bit, 3=5bit, 7=VDAC2/FT812 | +| `#600E` | `WC_SYS_HEI` | 1 | Высота TXT-экрана: 25 / 30 / 36 | +| `#600F` | `WC_SYS_TXTMD`| 1 | Текущий видеорежим WC (0=TXT) | +| `#6010` | `WC_SYS_INIFLG`| 1 | Флаг загрузки INI-конфигурации | +| `#6011` | `WC_SYS_RSQBC`| 2 | RSQ буфер-счётчик | +| `#6013` | `WC_SYS_RSQB2`| 2 | RSQ буфер 2 | +| `#6015` | `WC_SYS_SDBSF`| 1 | Состояние SD1 драйвера | +| `#6016` | `WC_SYS_SDBSF2`| 1 | Состояние SD2 драйвера | +| `#6017` | `WC_SYS_F3ENTFL`| 1 | Флаг F3/ENTER для файловых операций | +| `#6018` | `WC_SYS_FEP2`| 2 | FAT engine page extra | +| `#6020` | `WC_SYS_PLRESAD`| 8 | Резидент перехода: A=страница, HL=адрес → JP(HL) | +| `#6028` | `WC_SYS_PLRESCALL`| 8 | Резидент вызова: A=страница, HL=адрес → CALL(HL) | +| `#8000` | — | — | **Точка входа плагина** (WC делает CALL #8000) | + +Чтение из C: +```c +#define WC_SYS_ABT (*(volatile uint8_t*)0x6004) +#define WC_SYS_TMN (*(volatile uint16_t*)0x6009) +#define WC_SYS_HEI (*(volatile uint8_t*)0x600E) + +// Или через макросы wc_api.h: +uint8_t h = wc_get_height(); // 25/30/36 +uint16_t t = wc_get_timer(); // кадровый счётчик +uint8_t esc = wc_get_abt(); // 1 = ESC нажат +``` + +## Вызов API функций + +Все функции WC вызываются через точку доступа #6006: + +```asm +WLD EQU #6006 + +; Пример вызова функции PRWOW (вывод окна) +PRWOW LD A,1 + JP WLD + +; Пример вызова ESC (проверка нажатия ESC) +ESC LD A,#17 + JP WLD +``` + +**Регистровые соглашения WC API:** +- Вход: **A** = номер функции, остальные параметры в регистрах (см. справочник) +- Некоторые функции используют **A'** (альтернативный A через `EX AF,AF'`) +- **Портит**: AF, BC, DE, HL, IX +- **Сохраняет**: IY, AF', BC', DE', HL', SP + +> ⚠️ **IX сохраняется** диспетчером P8000, но **AF, BC, DE, HL портятся** +> при каждом вызове `CALL #6006`. + +--- + +## Карта памяти (физические страницы) + +> Источник: `wc_api.h` — полная таблица `WC_PAGE_*` + +| Страницы | Константа | Назначение | +|----------|-----------|------------| +| `#00` | `WC_PAGE_MTXPG` | Основной текстовый экран (TXT) | +| `#01` | `WC_PAGE_FONT0` | Шрифт основного TXT + Hrust/MegaLZ | +| `#02` | `WC_PAGE_CODE` | Код WC (ядро) | +| `#05` | `WC_PAGE_FAT_ENGINE` | FAT движок + стек + данные | +| `#08` | `WC_PAGE_BUFPG` | 2-й текстовый экран / буфер | +| `#09` | `WC_PAGE_FONT1` | Шрифт для 2-го TXT | +| `#0B–#13` | `WC_PAGE_CAT_LEFT` | Каталог левой панели (9 стр.) | +| `#14–#1C` | `WC_PAGE_CAT_RIGHT` | Каталог правой панели (9 стр.) | +| `#1D` | `WC_PAGE_RESB` | Буфер сохранения окон (RESB) | +| `#1E` | `WC_PAGE_CPPG1` | Позиции левой панели | +| `#1F` | `WC_PAGE_CPPG2` | Позиции правой панели | +| **#20–#5F** | **`WC_PAGE_TVBPG`** | **Мегабуфер (64 стр. = 1 МБ)**, он же видео: | +| `#20–#2F` | `WC_PAGE_GV1` | — видеобуфер 1 (16 стр.) | +| `#30–#3F` | `WC_PAGE_GV2` | — видеобуфер 2 | +| `#40–#4F` | `WC_PAGE_GV3` | — видеобуфер 3 | +| `#50–#5F` | `WC_PAGE_GV4` | — видеобуфер 4 | +| `#60` | `WC_PAGE_PLHPG` | Заголовки плагинов | +| **#61–#A0** | **`WC_PAGE_PLGPG`** | **Код/данные плагинов (до 64 стр.)** | +| `#A1–#BF` | `WC_PAGE_TAP_START` | TAP-область (31 стр. = 496 КБ) | +| `#C0–#E7` | `WC_PAGE_VDOS_RAM` | vDOS RAM-диск (40 стр. = 640 КБ) | +| `#E8–#EF` | `WC_PAGE_VDOS_FREE` | Свободные страницы | +| `#F0` | `WC_PAGE_PNLPG` | Поток левой панели | +| `#F1` | `WC_PAGE_PNL_RIGHT` | Поток правой панели | +| `#F2` | `WC_PAGE_INIPG` | INI-поток (плагин #00) | +| `#F3` | `WC_PAGE_STRPG` | Поток плагина #01 | +| `#F4–#F7` | — | SPG loader, boot pages (5,2), panel paths | +| `#F8–#FB` | `WC_PAGE_VROM_START` | Виртуальный ROM (4 стр.) | +| `#FC–#FF` | — | vDOS: таблицы, драйверы, трапы | + +> **Плагин видит:** свои заказанные страницы (0..N) через `wc_mngc_pl()` / +> `wc_mng0_pl()` / `wc_mng8_pl()`. Физическая нумерация скрыта — WC +> маппит логическую страницу N плагина в `WC_PAGE_PLGPG + N`. + +--- + +## Поддерживаемые устройства хранения + +| Код | Константа | Устройство | +|-----|-----------|------------| +| 0 | `WC_DEV_SD_ZC` | SD-карта (Z-Controller) | +| 1 | `WC_DEV_IDE_NEMO_M` | IDE Nemo — Master | +| 2 | `WC_DEV_IDE_NEMO_S` | IDE Nemo — Slave | +| 3 | `WC_DEV_SD_NGS` | SD-карта (NeoGS) | +| 4 | `WC_DEV_IDE_SMUC_M` | IDE SMUC — Master | +| 5 | `WC_DEV_IDE_SMUC_S` | IDE SMUC — Slave | +| 6 | `WC_DEV_SD2_ZC` | Вторая SD (Z-Controller SD2) | + +--- + +## Работа со страницами памяти + +### Логическая нумерация + +Плагин запрашивает N страниц в заголовке (offset +34). WC выделяет их +из пула `#61–#A0`. Плагин обращается по **логическому** номеру 0..N−1. + +### Окна CPU и функции маппинга + +| Окно | Адреса | Функция ASM | Функция C | Примечание | +|------|--------|-------------|-----------|------------| +| Win 0 | `#0000–#3FFF` | `MNG0_PL` (#4E) | `wc_mng0_pl(page)` | ⚠ Данные для WC API нельзя класть сюда | +| Win 2 | `#8000–#BFFF` | `MNG8_PL` (#4F) | `wc_mng8_pl(page)` | Код плагина — обычно стартовая страница | +| Win 3 | `#C000–#FFFF` | `MNGC_PL` (#00) | `wc_mngc_pl(page)` | ⚠ Сбрасывается после КАЖДОГО вызова WC API | + +Специальные значения для `MNGC_PL`: +- `#FF` — шрифтовая страница (нельзя писать в `#E000–#FFFF`!) +- `#FE` — первый текстовый экран (с панелями WC) + +### Резидент перехода (#6020) + +Переключает страницу плагина и делает **безвозвратный** JP: + +```c +// C: никогда не вернётся в вызывающий код +void wc_resident_jump(uint8_t page, uint16_t addr); +``` + +```asm +; ASM: A = страница (0..N-1), HL = адрес (#8000–#BFFF) + LD A,1 + LD HL,#8000 + CALL #6020 ; → WC переключает Win2 на стр.1, JP (HL) +``` + +### Резидент вызова (#6028) + +Вызывает код в другой странице плагина **с возвратом**: + +```c +void wc_resident_call(uint8_t page, uint16_t addr); +``` + +```asm +; ASM: A = страница, HL = адрес. Сохраняет всё кроме A,HL. + LD A,2 + LD HL,#A000 + CALL #6028 ; → WC переключает, CALL (HL), возвращает стр. назад +``` + +> Добавлено в WC v1.09 (31.07.2024). До этого многостраничные плагины +> требовали прямой OUT в порт Page2 (#12AF). + +--- + +## Важные замечания + +1. **При входе в плагин может быть любой экран!** Обязательно вызывайте + `GEDPL` (#0F) / `wc_gedpl()` для переключения на текстовый режим. + +2. **Вызывать GEDPL лучше после анализа файла**, когда убедились что файл + подходит для плагина (и вернули `wc_exit_code = 1` если нет). + +3. **Обязательно сохраняйте IX** при входе в плагин, если планируете + использовать файловые функции. + +4. **⚠ Win3 (#C000) сбрасывается после каждого вызова WC API!** + Внутри `CALL #6006` WC переключает окно `#C000` на текстовую страницу + и **не восстанавливает** его. Если плагин работал через `mngcvpl()` / + `mngc_pl()`, он **обязан** повторить маппинг после любого UI-вызова + (`gedpl`, `prwow`, `txtpr`, `rresb`, `load512` и т.д.). + +5. **GEDPL затирает #1000–#1447 в окне 0** — нельзя хранить данные в этой области. + +6. **Буферы и строки для файловых функций** (`load512`, `fentry`, `findnext`, + и т.д.) **обязаны** быть в `#8000–#FFFF`. WC ремапит `#0000–#7FFF` + под FAT-движок внутри каждого файлового вызова. + +7. **FindNext портит #0000–#1FFF** на странице драйвера — не хранить + данные там при итерации по каталогу. + +8. **WC API портит регистры:** AF, BC, DE, HL, IX. Сохраняет: IY, AF', + BC', DE', HL', SP. Это верно для **всех** функций через `CALL #6006`. + +9. **Стек не настраивается** — плагин использует стек WC (в Win 1, `#4000–#7FFF`). + Стек WC достаточно глубокий для обычных плагинов, но рекурсия требует осторожности. + +--- + +## Горячие клавиши WC (для пользователя) + +> Источник: `DiskRef/Help.txt` + +| Клавиша | Действие | +|---------|----------| +| ENTER | Войти в каталог / запустить .WMF / просмотреть файл | +| F3 | Встроенный просмотрщик (TXT/HEX/TGA/SCR/MGS, до 1 МБ) | +| SHIFT+F3/SHIFT+ENTER | Меню выбора вьювера (плагин с `WC_PCOND_VIEWER`) | +| F2 | Пользовательское меню (из `wc.mnu`) | +| F4 | Редактирование / выбор текстового редактора | +| F5 | Копирование отмеченных файлов в другую панель | +| F6 | Переименование файла/каталога | +| F7 | Создание каталога | +| F8 / DEL | Удаление отмеченных файлов | +| F9 | Настройки INI (авто-создаёт каталог WC на загрузочном устройстве) | +| F10 | Меню запуска/удаления плагинов | +| TAB | Переключение между панелями | +| SPACE / INSERT | Отметить файл/каталог | +| * | Инвертировать выделение | +| ALT+F1/F2 | Выбор диска для левой/правой панели | +| CTRL+F3 | Сортировка по имени | +| CTRL+F4 | Сортировка по расширению | +| CTRL+F6 | Сортировка по размеру | +| CTRL+R | Обновить каталог | +| CTRL+A | Выделить все объекты | +| CTRL+SHIFT | Переключение ENG/RUS | + +--- + +## Примеры плагинов + +В каталоге `DiskRef/WC/` находятся скомпилированные плагины WC: + +| Файл | Назначение | +|------|------------| +| `GSPLAYER.WMF` | Плеер для GS (MP3, MOD, WAV) | +| `WPLAYER.WMF` | Музыкальный плеер с плейлистами | +| `VGMPLAY.WMF` | VGM плеер (OPL3/AY/SAA) — этот проект | +| `TXTEDIT.WMF` | Текстовый редактор с подсветкой синтаксиса | +| `BMPV.WMF` | Просмотрщик BMP | +| `GIFVIEW.WMF` | Просмотрщик GIF | +| `ILBMVIEW.WMF` | Просмотрщик IFF/ILBM | +| `FTVIEW.WMF` | Просмотрщик FT-шрифтов | +| `MOUNTER.WMF` | Монтирование TRD/TAP/FDI образов | +| `ROM_PROG.WMF` | Программатор ROM/Flash | +| `SCRSAVER.WMF` | Скринсейвер (тип вызова `WC_PCOND_TIMER`) | +| `CLOCK.WMF` | Часы | +| `SETIME.WMF` | Установка времени | +| `COMPILER.WMF` | Компилятор | +| `RE.WMF` | Ассемблер/отладчик | + +--- + +## Ссылки на другие разделы документации + +- [02_Plugin_Header_Structure.md](02_Plugin_Header_Structure.md) — формат 512-байтного заголовка +- [03_WC_API_Reference.md](03_WC_API_Reference.md) — полный справочник функций +- [04_TSConfig_Platform.md](04_TSConfig_Platform.md) — аппаратная платформа, порты, регистры +- [06_Example_Plugin.md](06_Example_Plugin.md) — примеры плагинов на ASM и SDCC C diff --git a/doc/02_Plugin_Header_Structure.md b/doc/02_Plugin_Header_Structure.md new file mode 100644 index 0000000..0cbf3ad --- /dev/null +++ b/doc/02_Plugin_Header_Structure.md @@ -0,0 +1,311 @@ +# Структура заголовка плагина (512 байт) + +> Источники: `inc/wc_api.h` (константы `WC_PLUGIN_*`, `WC_PCOND_*`), +> `DiskRef/WC_History.txt` (история изменений формата) + +## Общая структура + +Заголовок плагина имеет фиксированный размер **512 байт** и содержит всю +необходимую информацию для загрузки и запуска плагина системой WC. + +**Текущая версия формата:** `#10` (`WC_PLUGIN_VERSION`), идентификатор: `WildCommanderMDL`. + +Формат неоднократно менялся (WC v0.56, v1.00, v1.02, v1.06, v1.09) — +старые плагины несовместимы с новой версией. + +## Детальная структура + +| Offset | Размер | Описание | +|--------|--------|----------| +| +0 | 16 | Резерв (reserved) | +| +16 | 16 | Идентификатор: "WildCommanderMDL" | +| +32 | 1 | Версия формата (#10) | +| +33 | 1 | Резерв | +| +34 | 1 | Количество страниц для плагина | +| +35 | 1 | Номер страницы для #8000 (от 0) | +| +36 | 12 | Блоки данных (6 x 2 байта) | +| +48 | 15 | Резерв | +| +63 | 1 | Флаги для блока расширений | +| +64 | 96 | Блок расширений (32 x 3 байта) | +| +160 | 1 | #00 (маркер окончания расширений) | +| +161 | 4 | Максимальный размер файла | +| +165 | 32 | Имя плагина | +| +197 | 1 | Тип условий вызова плагина | +| +198 | 6 | Текст для F4 или F2 | +| +204 | 24 | Текст для меню вьювера | +| +228 | 32 | Резерв | +| +260 | 252 | Структура параметров INI | + +## Детальное описание полей + +### Количество страниц (+34) + +Указывает, сколько 16 КБ страниц памяти будет выделено плагину. +Максимум: **64 страницы** (`WC_PLUGIN_MAX_PAGES`) = 1 МБ. + +Страницы выделяются из пула `#61–#A0` (физические), плагин обращается +по логическому номеру 0..N−1 через `wc_mngc_pl()` / `wc_mng0_pl()` / `wc_mng8_pl()`. + +Часть страниц может использоваться как буфер, который **сохраняется +между повторными запусками** плагина (NEXT/PREV) — код плагина WC не +перезагружает, если плагин уже загружен. + +### Номер стартовой страницы (+35) + +Номер страницы (от 0), которая будет включена в окно #8000 при запуске плагина. Нумерация начинается с 0. + +### Блоки данных (+36, 12 байт) + +Описывает до 6 блоков данных плагина: + +``` +Для каждого блока (2 байта): + Байт 0: Номер страницы (от 0) для размещения блока + Байт 1: Размер блока (x512 байт) +``` + +Блоки данных следуют сразу за заголовком плагина (512 байт). + +Пример: +```asm +DB 0,16 ; Блок 0: код плагина, 16x512 = 8КБ +DB 1,32 ; Блок 1: данные плеера, 32x512 = 16КБ +DB 2,9 ; Блок 2: дополнительный плеер, 9x512 = 4.5КБ +DB 0,0 ; Остальные блоки не используются +DB 0,0 +DB 0,0 +``` + +### Флаги блока расширений (+63) + +Определяет, какая клавиша запускает плагин по расширению: + +``` +BIT 0: Режим запуска + 0 - запуск и по ENTER, и по F3 + 1 - см. BIT 1 + +BIT 1 (если BIT 0 = 1): + 0 - запуск только по ENTER + 1 - запуск только по F3 +``` + +| Биты [1:0] | Поведение | +|------------|------------------------------------------| +| `00` | ENTER и F3 — оба запускают плагин | +| `01` | Только ENTER (F3 = встроенный вьювер WC) | +| `11` | Только F3 (ENTER = вход в файл как данные) | + +> Разделение ENTER и F3 добавлено в WC v1.00 (октябрь 2019). + +### Блок расширений (+64, 96 байт) + +Содержит до 32 расширений файлов (по 3 байта каждое). + +**Важно:** Расширения указываются **ТОЛЬКО БОЛЬШИМИ БУКВАМИ!** + +Примеры: +```asm +DB "TXT" ; Текстовые файлы +DB "MOD" ; MOD музыка +DB "WAV" ; WAV аудио +DB "MP3" ; MP3 аудио +DB "VGM" ; VGM музыка +; ... до 32 расширений +DS 27*3 ; Остальные заполняем нулями +``` + +Блок можно забить нулями, если не нужна инициация по расширениям. + +### Максимальный размер файла (+161, 4 байта) + +Определяет максимальный размер файла, который плагин может обработать. Файлы больше этого размера плагину не передаются. + +Примеры: +```asm +DW #FFFF,#FFFF ; Без ограничений +DW #6000,#0000 ; Максимум 24КБ +``` + +### Имя плагина (+165, 32 байта) + +Текстовое имя плагина, которое показывается в меню. + +```asm +DB "VGM Player for OPL3 v1.0 " +``` + +### Тип условий вызова (+197) + +Определяет, при каких условиях плагин должен вызываться (помимо расширений): + +| Код | Константа (C) | Условие вызова | +|-----|---------------|----------------| +| #00 | `WC_PCOND_EXT_ONLY` | Только по расширению | +| #01 | `WC_PCOND_ON_LOAD` | Сразу после загрузки (при ENTER) | +| #02 | `WC_PCOND_TIMER` | По таймеру скринсейвера + из меню F10 | +| #03 | `WC_PCOND_F10` | Из меню F10 | +| #04 | `WC_PCOND_VIEWER` | Из меню вьювера (SHIFT+F3) | +| #05 | `WC_PCOND_F10_VIEW` | F10 + SHIFT+F3 | +| #11 | `WC_PCOND_F10_F2` | F10 + F2 | +| #12 | `WC_PCOND_F2` | По F2 | +| #13 | `WC_PCOND_F10_F4` | F10 + F4 | +| #14 | `WC_PCOND_F4` | По F4 | +| #15 | `WC_PCOND_F10_V_F4` | F10 + SHIFT+F3 + F4 | + +**Примечание:** Все плагины с указанными расширениями также вызываются +по расширению. Тип вызова расширяет набор способов запуска. + +### Текст для F2/F4 (+198, 6 байт) + +Если тип вызова #11/#12 - текст для подсказки F2 +Если тип вызова #13/#14/#15 - текст для подсказки F4 + +```asm +DB "Edit " ; Для редактора +DB "Play " ; Для плеера +``` + +### Текст для меню вьювера (+204, 24 байта) + +Текст, который выводится в меню выбора вьювера (при типах #04/#05/#15): + +```asm +DB "Play VGM on OPL3 " +``` + +## Структура параметров INI (+260, 252 байта) + +> Добавлено в WC v1.09 (31.07.2024) + +Позволяет плагину принимать параметры из INI-файла WC (`wc.ini`). +Параметры отображаются в меню F9 → настройки плагина. + +### Формат записи + +``` ++0: 1 байт - длина ПОЛНОЙ структуры (включая этот байт) или #00 (конец) ++1: 1 байт - default_option (1-based номер опции по умолчанию) ++2: N байт - "parameter_name" (ASCII, без терминатора) ++2+N: 1 байт - количество опций (1-10) ++3+N: M байт - "option1",0, "option2",0, ... +... следующая запись или #00 +``` + +Максимум 8 параметров, каждый до 10 опций. + +### Пример + +```asm +PAR1: + DB PAR1END-PAR1 ; Длина структуры + DB 1 ; Опция по умолчанию = 1 + DB "opl_port" ; Имя параметра + DB 2 ; Количество опций + DB "C4",0 ; Опция 1 + DB "C0",0 ; Опция 2 +PAR1END: +PAR2: + DB PAR2END-PAR2 + DB 1 + DB "freq" + DB 3 + DB "683",0 + DB "1367",0 + DB "2734",0 +PAR2END: + DB 0 ; Конец списка параметров +``` + +### Чтение параметров в плагине + +```c +// wc_prm_pl(param_num) — param_num с 0 +// Возвращает: номер опции (1-based), 0xFF если параметр не задан +uint8_t opt = wc_prm_pl(0); // прочитать 1-й параметр +if (opt == 0xFF) opt = 1; // значение по умолчанию +``` + +```asm +; ASM: A' = номер параметра (0-based) + EXA + LD A,0 ; Параметр #0 + CALL WLD + EXA + LD A,#53 ; PRM_PL + CALL WLD + ; Z=не задан, NZ: A=номер опции (1-based) +``` + +В INI-файле (`wc.ini`) параметры записываются по длинному имени плагина: +```ini +[VGM Player v0.5] +opl_port=C4 +freq=683 +``` + +## Пример полного заголовка + +```asm + ORG #6000 + +; +0: Резерв + DS 16 + +; +16: Идентификатор + DB "WildCommanderMDL" + +; +32: Версия + DB #10 + +; +33: Резерв + DB 0 + +; +34: Количество страниц + DB 2 + +; +35: Стартовая страница + DB 0 + +; +36: Блоки данных + DB 0,8 ; Код: 4КБ + DB 1,16 ; Данные: 8КБ + DB 0,0 + DB 0,0 + DB 0,0 + DB 0,0 + +; +48: Резерв + DS 15 + +; +63: Флаги расширений + DB 0 + +; +64: Расширения + DB "VGM" + DB "VGZ" + DS 30*3 + +; +160: Конец расширений + DB 0 + +; +161: Максимальный размер + DW #FFFF,#FFFF + +; +165: Имя плагина + DB "VGM Player v1.0 " + +; +197: Тип вызова + DB #00 ; По расширению + +; +198-228: Резерв/меню + DS 6 + DS 24 + DS 32 + +; +260: Параметры INI + DB 0 ; Нет параметров + + ORG #8000,#6200 +; Здесь начинается код плагина +``` diff --git a/doc/03_WC_API_Reference.md b/doc/03_WC_API_Reference.md new file mode 100644 index 0000000..eee8a9d --- /dev/null +++ b/doc/03_WC_API_Reference.md @@ -0,0 +1,1151 @@ +# WC API — Полный справочник функций + +> Источники: `inc/wc_api.h` (1878 стр.), `asm/wc_api.s` (1339 стр.), +> `DiskRef/WC_History.txt` (история изменений API) + +## Вызов функций + +Все функции вызываются через точку доступа **#6006** (`JP` к диспетчеру): + +```asm +WLD EQU #6006 + +; В регистр A загружается номер функции +; Остальные параметры - в соответствующие регистры +LD A, функция_номер +JP WLD +``` + +**Регистровые соглашения:** +- **Портит**: AF, BC, DE, HL, IX +- **Сохраняет**: IY, AF’, BC’, DE’, HL’, SP +- Функции с параметром в **A’** используют `EX AF,AF'` перед вызовом + +> ⚠️ **Win 3 (#C000) сбрасывается** на текстовую страницу после **каждого** вызова +> `CALL #6006`. Плагин обязан повторить `mngcvpl()`/`mngc_pl()` после любого API-вызова. + +## Категории функций + +1. [Управление памятью](#управление-памятью) +2. [Работа с окнами](#работа-с-окнами) +3. [Работа с клавиатурой](#работа-с-клавиатурой) +4. [Файловые операции](#файловые-операции) +5. [Работа с графикой](#работа-с-графикой) +6. [Системные функции](#системные-функции) +7. [DMA операции](#dma-операции) +8. [Константы и структуры](#константы-и-структуры) +9. [Таблица кодов функций](#таблица-кодов-функций) + +--- + +## Управление памятью + +### #00 - MNGC_PL +Включение страницы плагина на #C000 + +**Вход:** +- A' - номер страницы (от 0) + - #FF - страница с фонтом (нельзя использовать #E000-#FFFF!) + - #FE - первый текстовый экран (с панелями) + +**Пример:** +```asm +MNGC_PL: EXA + LD A,0 ; Страница 0 + CALL WLD + EXA + RET +``` + +### #78 - MNG0_PL +Включение страницы плагина на #0000 + +**Вход:** +- A' - номер страницы (от 0) + +**Важно:** Все структуры для файловых функций должны лежать в #8000-#FFFF! + +### #79 - MNG8_PL +Включение страницы плагина на #8000 + +**Вход:** +- A' - номер страницы (от 0) + +--- + +## Работа с окнами + +### Структура окна (SOW - Structure Of Window) + +```asm +; Базовая структура (для типа 0) +SOW: + DB 0 ; +0: Флаги окна + DB 0 ; +1: Маска цвета курсора + DB 255 ; +2: X позиция (255=центр) + DB 255 ; +3: Y позиция (255=центр) + DB 40 ; +4: Ширина (0=весь экран) + DB 20 ; +5: Высота (0=весь экран) + DB %01111111 ; +6: Цвет (PAPER+INK) + DB 0 ; +7: Резерв (всегда 0) + DW #0000 ; +8: Адрес буфера (#0000=не выведено, #FFFF=без буфера) + DB 0 ; +10: Линия разделитель 1 + DB 0 ; +11: Линия разделитель 2 (смещение снизу) + DB 1 ; +12: Позиция курсора (от 1) + DB 20 ; +13: Нижний ограничитель + DB %01011000 ; +14: Цвет курсора + DB %01111111 ; +15: Цвет под курсором +``` + +**Флаги окна (+0):** +``` +[7]: %1 - окно с тенью +[6]: %1 - курсор во всю ширину +[5]: резерв +[4]: %1 - окно с инверсной шапкой +[3-0]: Тип окна: + 0 - стандартное окно, рамки 1-го типа + 1 - окно с заголовками и текстом (без курсора) + 2 - окно с заголовками и текстом + курсор + 3 - стандартное окно, рамки 2-го типа + 4 - окно с заголовками (рамки 2-го типа) + 5 - окно с заголовками + курсор (рамки 2-го типа) + 6 - стандартное окно, рамки 3-го типа + 7 - окно с заголовками (рамки 3-го типа) + 8 - окно с заголовками + курсор (рамки 3-го типа) +``` + +### #01 - PRWOW +Вывод окна на экран + +**Вход:** +- IX - адрес структуры окна (SOW) в #8000-#BFFF или #0000-#3FFF + +**Примечание:** Включает основной TXT экран в #C000 и оставляет его по выходу! + +### #02 - RRESB +Стирание окна (восстановление информации) + +**Вход:** +- IX - адрес SOW + +**Примечание:** Включает основной TXT экран в #C000! + +### #03 - PRSRW +Печать строки в окне + +**Вход:** +- IX - адрес SOW +- HL - адрес текста (в #8000-#BFFF или #0000-#3FFF) +- D - Y координата +- E - X координата +- BC - длина строки + +### #04 - PRIAT +Выставление цвета (после PRSRW) + +**Вход:** +- A' - цвет (PAPER+INK) +- Использует координаты и длину из PRSRW + +### #05 - GADRW +Получение адреса в окне + +**Вход:** +- IX - адрес SOW +- D - Y координата +- E - X координата + +**Выход:** +- HL - адрес в памяти + +### #06 - CURSOR +Печать курсора + +**Вход:** +- IX - адрес SOW + +### #07 - CURSER +Стирание курсора + +**Вход:** +- IX - адрес SOW + +### #0B - TXTPR +Вывод текста с разметкой в окне + +**Вход:** +- IX - адрес SOW +- HL - адрес текста (в #8000-#BFFF или #0000-#3FFF) +- D - Y (внутри окна) +- E - X (внутри окна) + +**Выход:** +- D - Y (следующая строка) +- E - X (без изменений) + +**Коды разметки текста:** +``` +#00 - конец строки и абзаца +#00,#00 - разделитель между сообщениями +#0B,XX - сдвиг вправо на XX символов +#0C,YY - сдвиг вниз на YY строк +#0D - перенос строки +#0E - центрирование строки +#09 - инверсия цвета + +#01-#08 - цвет чернил (1=синий, 7=белый, 8=черный) +#01-#08,#01-#08 - папер+инк + +#FE,XXXX - ссылка на строку (XXXX - адрес) +``` + +--- + +## Работа с клавиатурой + +Все функции возвращают **NZ** если клавиша нажата, **Z** если нет. + +| Функция | Код | Клавиша | +|---------|-----|---------| +| SPKE | #10 | SPACE | +| UPPP | #11 | UP Arrow | +| DWWW | #12 | DOWN Arrow | +| LFFF | #13 | LEFT Arrow | +| RGGG | #14 | RIGHT Arrow | +| TABK | #15 | TAB | +| ENKE | #16 | ENTER | +| ESC | #17 | ESCAPE | +| BSPC | #18 | BACKSPACE | +| PGU | #19 | PAGE UP | +| PGD | #1A | PAGE DOWN | +| HOME | #1B | HOME | +| END | #1C | END | +| F1-F10 | #1D-#26 | F1-F10 | +| ALT | #27 | ALT | +| SHIFT| #28 | SHIFT | +| CTRL | #29 | CTRL | +| DEL | #2B | DELETE | +| INS | #55 | INSERT | +| CAPS | #2C | CAPS LOCK | +| ANYK | #2D | Любая клавиша | +| USPO | #2E | Пауза до отпускания клавиш | +| NUSP | #2F | Ожидание любой клавиши | + +### #2A - KBSCN +Сканирование клавиатуры + +**Вход:** +- A' - режим обработки + - #00 - учитывает SHIFT, CAPS, ENG/RUS (вызывать 1 раз в фрейм) + - #01 - всегда выдает код из таблицы (можно вызывать много раз) + +**Выход:** +- NZ: A - код клавиши +- Z: A - #00 (неизвестная клавиша) или #FF (буфер пуст) + +**Пример:** +```asm +WAIT_KEY: + EI + HALT + LD A,1 + CALL KBSCN + CP 'm' ; Ждем 'm' + JR NZ,WAIT_KEY +``` + +--- + +## Файловые операции + +### #30 - LOAD512 +Потоковая загрузка файла + +**Вход:** +- HL - адрес буфера +- B - количество блоков (по 512 байт) + +**Выход:** +- HL - новое значение адреса +- A - #0F если конец файла + +### #31 - SAVE512 +Потоковая запись файла + +**Вход:** +- HL - адрес буфера +- B - количество блоков (по 512 байт) + +**Выход:** +- HL - новое значение адреса +- A - #0F если конец файла + +### #32 - GIPAGPL +Позиционирование на начало файла + +Устанавливает указатель на начало файла, переданного плагину. + +### #3B - FENTRY +Поиск файла/каталога + +**Вход:** +- HL - адрес структуры: flag(1), name(1-255), #00 + - flag: #00 = файл, #10 = каталог + +**Выход:** +- Z - файл не найден +- NZ - файл найден, [DE,HL] = размер файла + - Вызовите GFILE/GDIR для активации + +**Пример:** +```asm + LD HL,FILE_STRUCT + LD A,#3B + CALL WLD + JR Z,NOT_FOUND + ; Файл найден + CALL GFILE ; Активировать файл + ... +FILE_STRUCT: + DB #00 ; Файл + DB "music.vgm",0 +``` + +### #3E - GFILE +Активация найденного файла + +Устанавливает указатель на начало файла, найденного FENTRY. + +### #3F - GDIR +Активация найденного каталога + +Делает каталог, найденный FENTRY, активным. + +### #48 - MKfile +Создание файла + +**Вход:** +- HL - адрес структуры: flag(1), length(4), name(1-255), #00 + +**Выход:** +- A - код ошибки +- Z - файл создан +- NZ - ошибка + +### #39 - STREAM +Работа с потоками + +**Вход:** +- D - команда: + - #FF - активировать root dir + - #FE - клонировать поток в #00 и #01 + - #FD - активировать поток INI файла + - #00/#01 - номер потока, требует BC + +- BC (для #00/#01): + - B - устройство (0=SD, 1=IDE Master, 2=IDE Slave) + - C - раздел + - #FFFF - просто включить поток + +**Выход:** +- Z - поток активен +- NZ - ошибка + +--- + +## Работа с графикой + +### #40 - MNGV_PL +Включение видео страницы + +**Вход:** +- A' - номер видео страницы: + - #00 - основной экран (TXT) + - #01 - видео буфер 1 (16 страниц) + - #02 - видео буфер 2 (16 страниц) + - #03 - видео буфер 3 (16 страниц) + - #04 - видео буфер 4 (16 страниц) + +### #41 - MNGCVPL +Включение страницы видео буфера на #C000 + +**Вход:** +- A' - номер страницы: + - #00-#0F - из буфера 1 + - #10-#1F - из буфера 2 + - #20-#2F - из буфера 3 + - #30-#3F - из буфера 4 + +### #42 - GVmod +Включение видео режима + +**Вход:** +- A' - видео режим: +``` +[7-6]: Разрешение + %00 - 256x192 + %01 - 320x200 + %10 - 320x240 + %11 - 360x288 +[1-0]: Тип + %00 - ZX + %01 - 16c + %10 - 256c + %11 - txt +``` + +--- + +## Системные функции + +### #0E - TURBOPL +Управление частотой процессора + +**Вход:** +- B - выбор Z80/AY: + - #00 (`WC_TURBO_CPU`) - частота Z80: + - C = %00 (3.5 МГц), %01 (7 МГц), %10 (14 МГц), %11 (28 МГц) + - #01 (`WC_TURBO_AY`) - частота AY: + - C = %00 (1.75 МГц), %01 (1.7733 МГц), %10 (3.5 МГц), %11 (3.546 МГц) + - #FF (`WC_TURBO_RESTORE`) - восстановить значения из настроек WC + +> AY частоты 1.7733/3.546 МГц — это NTSC-кварц (315/88 МГц ÷ 2). + +### #0F - GEDPL +Восстановление текстового режима + +**Важно:** Обязательно вызывать при запуске плагина! + +Восстанавливает палитру, оффсеты, включает TXT режим. + +**Примечание:** Гробит область #1000-#1447! + +### #56 - INT_PL +Работа с прерываниями + +**Вход:** +- A' - команда: + - #00 - отключить все INT функции + - #01 - отключить обновление времени + - #02 - отключить опрос PS/2 клавиатуры + - #FF - установить обработчик INT от плагина + - HL - адрес обработчика (#8000-#BFFE) + +**Примечание:** Все параметры восстанавливаются при выходе из плагина. + +### #53 - PRM_PL +Запрос параметров запуска + +**Вход:** +- A' - номер параметра (1-8) + +**Выход:** +- Z - параметр не найден +- NZ - параметр получен + - A - номер выбранной опции (1-10) + +--- + +## DMA операции + +### #0D - DMAPL +Работа с DMA + +**Инициализация:** +- A' = #00: инит S и D (BHL=Source, CDE=Destination) +- A' = #01: инит S (BHL) +- A' = #02: инит D (CDE) +- A' = #05: DMA_T (B = количество бёрстов, 0=1...255=256) +- A' = #06: DMA_N (B = размер бёрста) + +Формат B/C для #00-#02: +``` +[7]: %1 - страница из блока плагина (0-5) + %0 - страница из видео буферов (0-63) +[6-0]: номер страницы +``` + +**Запуск:** +- A' = #FC: RAM→RAM +- A' = #FE: RAM→RAM с ожиданием +- A' = #FF: ожидание готовности DMA + +**Выход:** +- Z - операция завершена +- NZ - DMA не готов + +**Пример:** +```asm +; Копирование 4KB из страницы 0 в страницу 1 + LD B,#80 ; Страница 0 плагина + LD HL,#8000 ; Адрес источника + LD C,#81 ; Страница 1 плагина + LD DE,#8000 ; Адрес назначения + EXA + LD A,#00 ; Инит S и D + CALL WLD + LD A,#05 + LD B,8 ; 8 бёрстов + CALL WLD + LD A,#06 + LD B,255 ; По 512 байт + CALL WLD + LD A,#FE ; Запуск с ожиданием + CALL WLD + EXA +``` + +## Макросы для удобства + +```asm +; Определения для вызова функций +WLD EQU #6006 + +; Окна +PRWOW MACRO + LD A,1 + JP WLD + ENDM + +RRESB MACRO + LD A,2 + JP WLD + ENDM + +; Клавиатура +ESC MACRO + LD A,#17 + JP WLD + ENDM + +ENKE MACRO + LD A,#16 + JP WLD + ENDM + +; Файлы +LOAD512 MACRO + LD A,#30 + JP WLD + ENDM + +GEDPL MACRO + LD A,#0F + JP WLD + ENDM +``` + +--- + +## C API (SDCC) + +> Источник: `src_sdcc/inc/wc_api.h` + `src_sdcc/asm/wc_api.s` +> Компилятор: SDCC 4.x, соглашение **sdcccall(1)** + +### Соглашение вызова sdcccall(1) + +| Аргумент | Тип | Регистр | +|----------|-------------|----------| +| 1-й | `uint8_t` | `A` | +| 1-й | `uint16_t` / указатель | `HL` | +| 2-й | `uint16_t` / указатель | `DE` | +| 3-й+ | любой | стек (callee-cleans) | +| Возврат | `uint8_t` | `A` | +| Возврат | `uint16_t` | `HL` | + +WC API **портит**: `AF, BC, DE, HL, IX` при каждом вызове. +WC API **сохраняет**: `IY, AF', BC', DE', HL', SP`. + +--- + +### Управление памятью (страницы) + +```c +// Переключить окно #C000 на логическую страницу плагина N +void wc_mngc_pl(uint8_t page); + +// Переключить окно #0000 на логическую страницу плагина N +void wc_mng0_pl(uint8_t page); + +// Переключить окно #8000 на логическую страницу плагина N +void wc_mng8_pl(uint8_t page); + +// Переключить окно #C000 на VPL-страницу мегабуфера N (физич. = WC_PAGE_TVBPG + N) +void wc_mngcvpl(uint8_t vpage); + +// Переключить видеобуфер (WC_VIDEO_TXT / WC_VIDEO_BUF1..4) +void wc_mngv_pl(uint8_t mode); +``` + +> ⚠️ После **любого** вызова WC API окно `#C000` сбрасывается на текстовую страницу! +> Если плагин работал с `mngcvpl()`, после каждого UI-вызова нужно снова вызвать `mngcvpl()`. + +--- + +### Отображение / UI + +```c +// Восстановить дисплей WC (TXT-режим, панели) +// ⚠ Затирает #1000–#1447 в окне 0! +void wc_gedpl(void); + +// Нарисовать окно +void wc_prwow(wc_window_t *win); + +// Убрать окно (восстановить фон из RESB) +void wc_rresb(wc_window_t *win); + +// Прокрутить область окна +void wc_scrlwow(wc_window_t *win, uint8_t y, uint8_t x, + uint8_t h, uint8_t w, uint8_t flags); + +// Вывести строку с разметкой WC в окно +// Возвращает: номер строки после последнего символа +uint8_t wc_txtpr(wc_window_t *win, const char *str, uint8_t y, uint8_t x); + +// Вывести сообщение (несколько абзацев, разделённых 0x00 0x00) +uint8_t wc_mezz(wc_window_t *win, uint8_t msg_num, + const char *str, uint8_t y, uint8_t x); + +// Вывести строку без разметки (raw) +void wc_prsrw(wc_window_t *win, const char *str, + uint8_t y, uint8_t x, uint16_t len); + +// Вывести строку без разметки с заданным атрибутом цвета +void wc_prsrw_attr(wc_window_t *win, const char *str, + uint8_t y, uint8_t x, uint16_t len, uint8_t attr); + +// Получить адрес экрана по позиции в окне +uint16_t wc_gadrw(wc_window_t *win, uint8_t y, uint8_t x); + +// Показать/убрать курсор +void wc_cursor(wc_window_t *win); +void wc_curser(wc_window_t *win); + +// Диалог Yes/No или Ok/Cancel +// mode: WC_YN_YES_NO, WC_YN_OK_CANCEL, WC_YN_POLL, WC_YN_EXIT +// Возвращает: 0=нет/отмена, 1=да/ок (при POLL: текущий выбор) +uint8_t wc_yn(uint8_t mode); + +// Ввод строки в окне +void wc_istr(wc_window_t *win, uint8_t mode); + +// Вставить символ val по адресу addr в текстовой памяти +void wc_nork(uint16_t addr, uint8_t val); +``` + +--- + +### Клавиатура + +```c +// Опрос конкретной клавиши (возвращает 1 если нажата) +uint8_t wc_key_space(void); +uint8_t wc_key_up(void); +uint8_t wc_key_down(void); +uint8_t wc_key_left(void); +uint8_t wc_key_right(void); +uint8_t wc_key_tab(void); +uint8_t wc_key_enter(void); +uint8_t wc_key_esc(void); +uint8_t wc_key_bspc(void); +uint8_t wc_key_pgup(void); +uint8_t wc_key_pgdn(void); +uint8_t wc_key_home(void); +uint8_t wc_key_end(void); +uint8_t wc_key_f1(void); +uint8_t wc_key_f2(void); +uint8_t wc_key_f3(void); +uint8_t wc_key_f4(void); +uint8_t wc_key_f5(void); +uint8_t wc_key_f6(void); +uint8_t wc_key_f7(void); +uint8_t wc_key_f8(void); +uint8_t wc_key_f9(void); +uint8_t wc_key_f10(void); + +// Модификаторы (без автоповтора) +uint8_t wc_key_alt(void); +uint8_t wc_key_shift(void); +uint8_t wc_key_ctrl(void); + +// Прочие клавиши +uint8_t wc_key_del(void); +uint8_t wc_key_ins(void); +uint8_t wc_key_caps(void); +uint8_t wc_key_any(void); // любая клавиша нажата (без автоповтора) + +// Ждать нажатия любой клавиши, вернуть скан-код +uint8_t wc_key_wait_any(void); + +// Ждать отпускания всех клавиш +void wc_key_wait_release(void); + +// Получить скан-код нажатой клавиши +// mode: WC_KBSCN_NORMAL (с учётом SHIFT/CL/Lang) или WC_KBSCN_RAW +uint8_t wc_kbscn(uint8_t mode); +``` + +--- + +### Файловые операции + +Все буферы и строки файловых функций **обязаны** быть в `#8000–#FFFF` +(WC ремапит `#0000–#7FFF` внутри FAT-операций). + +```c +// Прочитать blocks×512 байт по адресу dest из открытого файла +// Возвращает: 0=OK, WC_EOF=конец файла, иное=ошибка +uint8_t wc_load512(uint16_t dest, uint8_t blocks); + +// Прочитать blocks×256 байт по адресу dest +uint8_t wc_load256(uint16_t dest, uint8_t blocks); + +// Записать blocks×512 байт по адресу src в открытый файл +void wc_save512(uint16_t src, uint8_t blocks); + +// Пропустить sectors×512 байт в открытом файле (без чтения) +void wc_loadnone(uint8_t sectors); + +// Сменить поток (контекст файловой операции) +// mode: WC_STREAM_ROOT, WC_STREAM_CLONE, WC_STREAM_WCDIR, 0/1 +void wc_stream(uint8_t mode); + +// Управление каталогом (WC_ADIR_SEEK_START, WC_ADIR_RESET_NEXT) +void wc_adir(uint8_t mode); + +// Открыть файл по имени в текущем каталоге +// name_with_flag: [1 байт флагов][имя][0] +// Возвращает: 0=не найден, ненулевое=найден +uint8_t wc_fentry(uint16_t name_with_flag); + +// Найти следующий файл в каталоге +// flags: [7]=только короткие имена, [1:0]=00 все/01 файлы/10 каталоги +// Возвращает: 0 (Z=1)=конец каталога, ненулевое=найден +uint8_t wc_findnext(uint16_t entry_buf, uint8_t flags); + +// Открыть файл, найденный последним wc_fentry() +void wc_gfile(void); + +// Войти в каталог, найденный последним wc_fentry() +void wc_gdir(void); + +// Создать файл: [1 байт флагов][длина][имя][0] +uint8_t wc_mkfile(uint16_t name_with_flag); + +// Создать каталог +uint8_t wc_mkdir(uint16_t name); + +// Переименовать файл/каталог +uint8_t wc_rename(uint16_t old_name, uint16_t new_name); + +// Удалить файл +uint8_t wc_delfl(uint16_t name_with_flag); + +// Перейти к началу файла плагина (позиционировать поток на первый блок) +void wc_gipagpl(void); + +// Прочитать FAT ENTRY (32 байта) файла в буфер по адресу addr +void wc_tentry(uint16_t addr); + +// Обойти цепочку секторов, записать список секторов в buf..bufend +void wc_chtosep(uint16_t buf, uint16_t bufend); + +// Получить заголовок помеченного файла +// panel = IX структура панели WC, filenum = номер файла, namebuf = буфер +void wc_tmrkdfl(uint16_t panel, uint16_t filenum, uint16_t namebuf); +``` + +--- + +### Графика и видео + +```c +// Переключить видеорежим: WC_VIDEO_TXT / WC_VIDEO_BUF1..4 +void wc_mngv_pl(uint8_t mode); + +// Отобразить видеостраницу vpage (0x00–0x3F) на окно #C000 +void wc_mngcvpl(uint8_t vpage); + +// Отобразить видеостраницу vpage на окно #0000 +// ⚠ GEDPL (~wc_gedpl) затирает #1000–#1447 — не хранить данные там! +void wc_mng0vpl(uint8_t vpage); + +// Отобразить видеостраницу vpage на окно #8000 +void wc_mng8vpl(uint8_t vpage); + +// Установить видеорежим TSConfig +// mode: TSCONF_VM_* | TSCONF_RRES_* (байт VConfig) +void wc_gvmod(uint8_t mode); + +// Установить Y-смещение прокрутки графики +void wc_gyoff(uint16_t y); + +// Установить X-смещение прокрутки графики +void wc_gxoff(uint16_t x); + +// Установить страницу тайловой карты +void wc_gvtm(uint8_t page); + +// Установить страницу графики тайловой плоскости (plane=0 или 1) +void wc_gvtl(uint8_t plane, uint8_t page); + +// Установить страницу графики спрайтов +void wc_gvsgp(uint8_t page); +``` + +--- + +### DMA + +```c +// Настройка и запуск DMA TSConfig. +// Подфункции: +// 0x00 — src+dst: B=src_page, HL=src_addr, C=dst_page, DE=dst_addr +// 0x01 — только src; 0x02 — только dst +// 0x05 — DMA_T; 0x06 — DMA_N; 0x07 — T+N +// 0xFC — RAM→RAM (с ожиданием) 0xFB — BLT→RAM +// 0xFA — FILL 0xF9 — RAM→CRAM (палитра) +// 0xF8 — RAM→SFILE (спрайты) 0xFD — запустить без ожидания +// 0xFE — запустить с ожиданием 0xFF — ждать готовности DMA +void wc_dmapl(uint8_t subfunc); +``` + +--- + +### Системные функции + +```c +// Прерывания (WC_INT_DISABLE_ALL / NO_TIME / NO_PS2 / PLUGIN) +// При WC_INT_PLUGIN — следующий вызов wc_int_pl_handler() задаёт адрес +void wc_int_pl(uint8_t mode); + +// Установить адрес обработчика ISR плагина (после wc_int_pl(WC_INT_PLUGIN)) +void wc_int_pl_handler(uint16_t handler_addr); + +// Управление CPU/AY частотой +// mode: WC_TURBO_CPU (param 0=3.5, 1=7, 2=14, 3=28 МГц) +// WC_TURBO_AY (param 0=1.750, 1=1.773, 2=3.5, 3=3.546 МГц) +// WC_TURBO_RESTORE (0xFF) — восстановить из настроек WC +void wc_turbopl(uint8_t mode, uint8_t param); + +// Получить параметр из INI-файла плагина +// param_num: 0-based номер параметра +// Возвращает: номер опции (0-based), 0xFF если параметр не задан +uint8_t wc_prm_pl(uint8_t param_num); + +// Резидентный JP на другую страницу плагина (без возврата!) +// page: номер страницы (от 0), addr: адрес в #8000–#BFFF +void wc_resident_jump(uint8_t page, uint16_t addr); + +// Резидентный CALL на другую страницу плагина (с возвратом) +// Сохраняет все регистры кроме A и HL +void wc_resident_call(uint8_t page, uint16_t addr); + +// Заполнить буфер символом c (аналог memset) +void wc_strset(char *dsr, uint16_t len, char c); +``` + +--- + +### Системные переменные (прямой доступ по адресу) + +```c +// Читаются напрямую, без вызова WC API: +extern volatile uint8_t wc_exit_code; // #8000 − записать перед return из main() + // 0=ESC, 1=не распознан, 2=следующий, + // 3=перечитать каталог, 4=предыдущий + +#define WC_SYS_ABT (*(volatile uint8_t*)0x6004) // 1 = был нажат ESC +#define WC_SYS_ENT (*(volatile uint8_t*)0x6005) // 1 = был нажат ENTER +#define WC_SYS_TMN (*(volatile uint16_t*)0x6009) // таймер INT (инкремент каждый INT) +#define WC_SYS_HEI (*(volatile uint8_t*)0x600E) // высота TXT-экрана (25/30/36) +#define WC_SYS_PG0 (*(volatile uint8_t*)0x6000) // текущая страница окна #0000 +#define WC_SYS_PGC (*(volatile uint8_t*)0x6003) // текущая страница окна #C000 +``` + +--- + +### Пример минимального плагина (SDCC C) + +```c +#include "inc/wc_api.h" +#include "inc/types.h" + +// Структура окна в DATA (0x8000+) +static wc_window_t g_win; + +// Код выхода (WC читает A при RET из main) +uint8_t wc_exit_code; + +int main(void) { + + // --- Настройка окна --- + g_win.type = WC_WIN_SINGLE; + g_win.cur_mask = 0x07; + g_win.x = 255; // по центру + g_win.y = 255; + g_win.width = 40; + g_win.height = 8; + g_win.color = WC_COLOR(WC_BLACK, WC_BRIGHT_WHITE); + g_win.buf_addr = 0x0000; // автосохранение фона + + // --- Отрисовка UI --- + wc_gedpl(); + wc_prwow(&g_win); + wc_txtpr(&g_win, "Hello from plugin!", 1, 1); + + // --- Главный цикл --- + while (1) { + if (wc_key_esc()) break; + if (wc_key_enter()) { + // ... какое-то действие + } + } + + // --- Выход --- + wc_rresb(&g_win); + wc_gedpl(); + + wc_exit_code = 0; // 0 = нормальный выход (ESC) + return 0; +} +``` +--- + +## Константы и структуры + +### Цветовая палитра (EGA-совместимая) + +```c +#define WC_COLOR(bg, fg) ((uint8_t)(((bg) << 4) | (fg))) + +#define WC_BLACK 0 #define WC_DARK_GRAY 8 +#define WC_BLUE 1 #define WC_BRIGHT_BLUE 9 +#define WC_RED 2 #define WC_BRIGHT_RED 10 +#define WC_MAGENTA 3 #define WC_BRIGHT_MAGENTA 11 +#define WC_GREEN 4 #define WC_BRIGHT_GREEN 12 +#define WC_CYAN 5 #define WC_BRIGHT_CYAN 13 +#define WC_YELLOW 6 #define WC_BRIGHT_YELLOW 14 +#define WC_WHITE 7 #define WC_BRIGHT_WHITE 15 +``` + +### Стили окон + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `WC_WIN_NO_BORDER` | 0x00 | Без рамки, со стандартным курсором | +| `WC_WIN_SINGLE` | 0x01 | Одинарная рамка, без курсора | +| `WC_WIN_DOUBLE` | 0x02 | Двойная рамка + курсор | +| `WC_WIN_TYPE3`..`TYPE8` | 0x03..0x08 | Рамки 2/3 типа ± курсор/заголовки | + +**Флаги (OR с типом):** + +| Флаг | Значение | Описание | +|------|----------|----------| +| `WC_WIN_HEADER` | 0x10 | Инверсный заголовок сверху | +| `WC_WIN_WIDE_CURSOR` | 0x40 | Курсор на всю ширину | +| `WC_WIN_SHADOW` | 0x80 | Тень у окна | + +### Структура `wc_window_t` (16 байт) + +```c +typedef struct { + uint8_t type; /* +0: стиль+флаги (WC_WIN_*) */ + uint8_t cur_mask; /* +1: маска цвета курсора */ + uint8_t x; /* +2: X (255=по центру) */ + uint8_t y; /* +3: Y (255=по центру) */ + uint8_t width; /* +4: ширина (0=весь экран) */ + uint8_t height; /* +5: высота (0=весь экран) */ + uint8_t color; /* +6: WC_COLOR(bg, fg) */ + uint8_t _reserved; /* +7: ВСЕГДА 0! */ + uint16_t buf_addr; /* +8: буфер фона (0=авто, 0xFFFF=без буфера)*/ + uint8_t divider1; /* +10: разделитель 1 (смещение от верха) */ + uint8_t divider2; /* +11: разделитель 2 (смещение от низа) */ + uint8_t cursor_pos; /* +12: позиция курсора (от 1) */ + uint8_t cursor_bot; /* +13: нижний ограничитель курсора */ + uint8_t cursor_clr; /* +14: цвет курсора */ + uint8_t under_clr; /* +15: цвет под курсором */ +} wc_window_t; +``` + +> Авто-центрирование (x=255, y=255) работает для режимов 80×25, 80×30, 90×36. +> Добавлено в WC v1.02. + +### Структура FAT-записи (32 байта) + +```c +typedef struct { + uint8_t name[11]; /* +0: короткое имя 8.3 (без точки) */ + uint8_t attr; /* +11: атрибуты FAT (см. WC_FAT_ATTR_*) */ + uint8_t _res1; /* +12: NTRes */ + uint8_t crt_time_ms; /* +13: время создания (0.1 сек) */ + uint16_t crt_time; /* +14: время создания (FAT format) */ + uint16_t crt_date; /* +16: дата создания */ + uint16_t acc_date; /* +18: дата доступа */ + uint16_t cluster_hi; /* +20: старшее слово кластера */ + uint16_t mod_time; /* +22: время модификации */ + uint16_t mod_date; /* +24: дата модификации */ + uint16_t cluster_lo; /* +26: младшее слово кластера */ + uint32_t file_size; /* +28: размер файла */ +} wc_fat_entry_t; +``` + +**Атрибуты FAT:** + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `WC_FAT_ATTR_RDONLY` | 0x01 | Только чтение | +| `WC_FAT_ATTR_HIDDEN` | 0x02 | Скрытый | +| `WC_FAT_ATTR_SYSTEM` | 0x04 | Системный | +| `WC_FAT_ATTR_VOLLBL` | 0x08 | Метка тома | +| `WC_FAT_ATTR_DIR` | 0x10 | Каталог | +| `WC_FAT_ATTR_ARCHIVE` | 0x20 | Архивный | +| `WC_FAT_ATTR_LFN` | 0x0F | LFN-запись (RDONLY\|HIDDEN\|SYSTEM\|VOLLBL) | + +### Структура FindNext + +```c +typedef struct { + uint32_t size; /* опционально, если WC_FIND_WITH_SIZE */ + uint16_t date; /* опционально, если WC_FIND_WITH_DATE */ + uint16_t time; /* опционально, если WC_FIND_WITH_TIME */ + uint8_t flag; /* 0x10=каталог, 0x00=файл */ + char name[1]; /* имя, z-string (длина переменная) */ +} wc_findnext_entry_t; +``` + +**Флаги для `wc_findnext(entry_buf, flags)`:** + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `WC_FIND_ALL` | 0x00 | Все записи | +| `WC_FIND_FILES` | 0x01 | Только файлы | +| `WC_FIND_DIRS` | 0x02 | Только каталоги | +| `WC_FIND_WITH_SIZE` | 0x04 | Включить поле size | +| `WC_FIND_WITH_DATE` | 0x08 | Включить поле date | +| `WC_FIND_WITH_TIME` | 0x10 | Включить поле time | +| `WC_FIND_FULL` | 0x1C | Все опциональные поля | +| `WC_FIND_SHORT_ONLY` | 0x80 | Только короткие имена 8.3 | + +### Коды ошибок файловых операций + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `WC_EOF` | 0x0F | Конец файла / конец цепочки | +| `WC_ERR_LONG_NAME` | 1 | Недопустимое длинное имя | +| `WC_ERR_SHORT_IDX` | 2 | Короткое имя требует индекса | +| `WC_ERR_LONG_EXISTS` | 3 | Длинное имя уже существует | +| `WC_ERR_SHORT_EXISTS` | 4 | Короткое имя уже существует | +| `WC_ERR_NOT_FOUND` | 8 | Файл/каталог не найден | +| `WC_ERR_NO_SPACE` | 16 | Нет свободного места | +| `WC_ERR_UNKNOWN` | 255 | Неизвестная ошибка | + +### Константы скроллинга (`wc_scrlwow`) + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `WC_SCRL_DOWN` | 0x00 | Прокрутка вниз | +| `WC_SCRL_UP` | 0x01 | Прокрутка вверх | +| `WC_SCRL_WITH_ATTRS` | 0x80 | Прокручивать и атрибуты | +| `WC_SCRL_CLEAR_SRC` | 0x40 | Очистить источник после сдвига | + +### Константы потоков (`wc_stream`) + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `WC_STREAM_ROOT` | 0xFF | Корневой каталог | +| `WC_STREAM_CLONE` | 0xFE | Клонировать текущий поток в #00 и #01 | +| `WC_STREAM_WCDIR` | 0xFD | Каталог WC (для INI) | +| 0x00, 0x01 | — | Выбрать поток 0 или 1 | + +### Константы видеобуферов + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `WC_VIDEO_TXT` | 0x00 | Текстовый режим WC | +| `WC_VIDEO_BUF1` | 0x01 | Видеобуфер 1 (стр. #20–#2F) | +| `WC_VIDEO_BUF2` | 0x02 | Видеобуфер 2 (стр. #30–#3F) | +| `WC_VIDEO_BUF3` | 0x03 | Видеобуфер 3 (стр. #40–#4F) | +| `WC_VIDEO_BUF4` | 0x04 | Видеобуфер 4 (стр. #50–#5F) | + +> Мегабуфер (64 стр.) и видеобуферы **пересекаются** физически: +> VPL-страница N = физическая `WC_PAGE_TVBPG + N` = `#20 + N`. + +### Размеры TXT-экрана + +| Константа | Значение | +|-----------|----------| +| `WC_SCREEN_80x25` | 25 | +| `WC_SCREEN_80x30` | 30 | +| `WC_SCREEN_90x36` | 36 | + +--- + +## Таблица кодов функций + +> Полный маппинг ASM → WC API code → C wrapper + +| Код A | ASM | C функция | Параметр в A' (EXA)? | Примечание | +|-------|-----|-----------|----------------------|------------| +| #00 | MNGC_PL | `wc_mngc_pl(page)` | **Да** (page) | #FF=font, #FE=TXT | +| #01 | PRWOW | `wc_prwow(win)` | — | IX=win | +| #02 | RRESB | `wc_rresb(win)` | — | IX=win | +| #03 | PRSRW | `wc_prsrw(win,str,y,x,len)` | — | | +| #04 | PRIAT | (часть prsrw_attr) | **Да** (color) | Вызывается сразу после #03 | +| #05 | GADRW | `wc_gadrw(win,y,x)` | — | → HL=addr | +| #06 | CURSOR | `wc_cursor(win)` | — | | +| #07 | CURSER | `wc_curser(win)` | — | | +| #08 | YN | `wc_yn(mode)` | **Да** (mode) | | +| #09 | ISTR | `wc_istr(win,mode)` | **Да** (mode) | | +| #0A | NORK | `wc_nork(addr,val)` | **Да** (val) | | +| #0B | TXTPR | `wc_txtpr(win,str,y,x)` | — | → A=next_y | +| #0C | MEZZ | `wc_mezz(win,msg,str,y,x)` | **Да** (msg_num) | | +| #0D | DMAPL | `wc_dmapl(subfunc)` | **Да** (subfunc) | | +| #0E | TURBOPL | `wc_turbopl(mode,param)` | — | B=mode, C=param | +| #0F | GEDPL | `wc_gedpl()` | — | ⚠ Портит #1000–#1447 | +| #10..#29 | Keys | `wc_key_*()` | — | → NZ=нажата | +| #2A | KBSCN | `wc_kbscn(mode)` | **Да** (mode) | | +| #2B | DEL | `wc_key_del()` | — | | +| #2C | CAPS | `wc_key_caps()` | — | | +| #2D | ANYK | `wc_key_any()` | — | | +| #2E | USPO | `wc_key_wait_release()` | — | | +| #2F | NUSP | `wc_key_wait_any()` | — | | +| #30 | LOAD512 | `wc_load512(dest,blocks)` | — | B=blocks | +| #31 | SAVE512 | `wc_save512(src,blocks)` | — | | +| #32 | GIPAGPL | `wc_gipagpl()` | — | | +| #33 | TENTRY | `wc_tentry(addr)` | — | | +| #34 | CHTOSEP | `wc_chtosep(buf,bufend)` | — | | +| #36 | TMRKDFL | `wc_tmrkdfl(panel,fnum,buf)` | — | | +| #38 | ADIR | `wc_adir(mode)` | **Да** (mode) | | +| #39 | STREAM | `wc_stream(mode)` | — | D=mode | +| #3A | FINDNEXT | `wc_findnext(buf,flags)` | **Да** (flags) | | +| #3B | FENTRY | `wc_fentry(name)` | — | → Z/NZ | +| #3C | LOAD256 | `wc_load256(dest,blocks)` | — | | +| #3D | LOADNONE | `wc_loadnone(sectors)` | — | B=sectors | +| #3E | GFILE | `wc_gfile()` | — | | +| #3F | GDIR | `wc_gdir()` | — | | +| #40 | MNGV_PL | `wc_mngv_pl(mode)` | **Да** (mode) | | +| #41 | MNGCVPL | `wc_mngcvpl(vpage)` | **Да** (vpage) | | +| #42 | GVmod | `wc_gvmod(mode)` | **Да** (mode) | | +| #43 | GYOFF | `wc_gyoff(y)` | — | HL=y | +| #44 | GXOFF | `wc_gxoff(x)` | — | HL=x | +| #45 | GVtm | `wc_gvtm(page)` | — | C=page | +| #46 | GVtl | `wc_gvtl(plane,page)` | — | B=plane,C=page | +| #47 | GVsgp | `wc_gvsgp(page)` | — | C=page | +| #48 | MKfile | `wc_mkfile(name)` | — | | +| #49 | MKdir | `wc_mkdir(name)` | — | | +| #4A | RENAME | `wc_rename(old,new)` | — | | +| #4B | DELFL | `wc_delfl(name)` | — | | +| #4E | MNG0_PL | `wc_mng0_pl(page)` | **Да** (page) | | +| #4F | MNG8_PL | `wc_mng8_pl(page)` | **Да** (page) | | +| #50 | MNG0VPL | `wc_mng0vpl(vpage)` | **Да** (vpage) | | +| #51 | MNG8VPL | `wc_mng8vpl(vpage)` | **Да** (vpage) | | +| #53 | PRM_PL | `wc_prm_pl(param)` | **Да** (param) | → Z=нет, NZ: A=opt | +| #54 | SCRLWOW | `wc_scrlwow(win,y,x,h,w,fl)` | **Да** (flags) | | +| #55 | INS | `wc_key_ins()` | — | | +| #56 | INT_PL | `wc_int_pl(mode)` | **Да** (mode) | | +| — | PLRESAD | `wc_resident_jump(p,addr)` | — | CALL #6020, JP | +| — | PLRESCALL | `wc_resident_call(p,addr)` | — | CALL #6028 | +| — | STRSET | `wc_strset(dst,len,c)` | — | Утилита (LDIR) | \ No newline at end of file diff --git a/doc/04_TSConfig_Platform.md b/doc/04_TSConfig_Platform.md new file mode 100644 index 0000000..a85c33f --- /dev/null +++ b/doc/04_TSConfig_Platform.md @@ -0,0 +1,658 @@ +# TSConfig Platform Reference + +> Источники: `inc/tsconf.h` (532 стр.), `inc/wc_api.h` (системные константы), +> [TSConfig Wiki](https://github.com/tslabs/zx-evo) + +## Обзор платформы + +TS-Configuration (TSConfig) — расширенная конфигурация на базе **ZX Evolution** +(Большой Спектрум / ZX Evo), реализованная в FPGA (Altera/Xilinx). + +| Параметр | Значение | +|-----------|----------| +| CPU | Z80 @ 3.5 / 7 / 14 / 28 МГц | +| RAM | до 4096 КБ (256 страниц × 16 КБ) | +| ROM | до 512 КБ (32 страницы) | +| Видео | до 360×288, 256 цветов, TXT 80/90 столбцов | +| Спрайты | До 85 на строку, 8×8–64×64, 3 плоскости | +| Тайлы | 2 плоскости, 8×8 | +| DMA | Аппаратный контроллер (RAM↔RAM, RAM↔SPI/IDE, FILL, CRAM, SFILE) | +| Кэш | 512 байт, по-оконно | +| Звук | AY-3-8910 (TurboSound: 2 чипа), OPL3 (YMF262), SAA1099 ×2, HUS | +| Хранилище | SD (Z-Controller, NeoGS), IDE (NEMO, SMUC), FAT32 | +| Совместимость | Pentagon-128/512/1024, порт #7FFD | + +## Память + +### Страничная адресация + +Процессор Z80 адресует 64 КБ памяти, разделенные на 4 окна по 16 КБ: + +| Окно | Адреса CPU | Регистр страницы | R/W | Reset | +|------|------------|------------------|-----|-------| +| 0 | #0000..#3FFF | Page0 | W | #00 | +| 1 | #4000..#7FFF | Page1 | W | #05 | +| 2 | #8000..#BFFF | Page2 | R/W | #02 | +| 3 | #C000..#FFFF | Page3 | R/W | #00 | + +**Доступно:** +- RAM: до 4096 КБ (256 страниц по 16 КБ) +- ROM: до 512 КБ (32 страницы по 16 КБ) + +### Регистры управления памятью + +Доступ к регистрам через порты **#nnAF**, где nn - номер регистра. + +#### MemConfig (регистр #07) + +``` +Биты: +7:6 - LCK128[1:0] - режим порта #7FFD +5:4 - резерв +3 - W0_RAM - выбор ROM/RAM в окне 0 +2 - !W0_MAP - режим маппинга окна 0 +1 - W0_WE - разрешение записи в окно 0 +0 - ROM128 - выбор страниц маппинга +``` + +#### Page0..3 (регистры #10-#13) + +8-битные регистры, содержащие номер страницы для соответствующего окна. + +## Порты ввода-вывода + +### Схема адресации портов + +TSConfig использует порты с адресом **#nnAF**, где: +- Младшие 8 бит (A0-A7) = #AF +- Старшие 8 бит (A8-A15) = номер регистра (nn) + +### Базовые регистры TSConfig + +> Источник: `src_sdcc/inc/tsconf.h` — `#define TSCONF_PORT(reg) (((uint16_t)(reg) << 8) | 0xAF)` + +| Порт | Константа | R/W | Описание | +|---------|----------------------------|-----|-------------------------------------| +| `#00AF` | `TSCONF_PORT_VCONFIG` | RW | VConfig: видеорежим (VM, RRES) | +| `#01AF` | `TSCONF_PORT_VPAGE` | W | VPage: страница видеобуфера | +| `#02AF` | `TSCONF_PORT_GXOFFSL` | W | GFX X offset (low) | +| `#03AF` | `TSCONF_PORT_GXOFFSH` | W | GFX X offset (high) | +| `#04AF` | `TSCONF_PORT_GYOFFSL` | W | GFX Y offset (low) | +| `#05AF` | `TSCONF_PORT_GYOFFSH` | W | GFX Y offset (high) | +| `#06AF` | `TSCONF_PORT_TSCONFIG` | W | TSConfig: tile/sprite engine | +| `#07AF` | `TSCONF_PORT_PALSEL` | W | PalSel: выбор палитры | +| `#0FAF` | `TSCONF_PORT_BORDER` | W | Border: цвет бордюра (биты 2:0) | +| `#10AF` | `TSCONF_PORT_PAGE0` | **W** | **Page0: страница окна #0000** | +| `#11AF` | `TSCONF_PORT_PAGE1` | **W** | **Page1: страница окна #4000** | +| `#12AF` | `TSCONF_PORT_PAGE2` | **RW** | **Page2: страница окна #8000** | +| `#13AF` | `TSCONF_PORT_PAGE3` | **RW** | **Page3: страница окна #C000** | +| `#14AF` | `TSCONF_PORT_FMADDR` | W | Font Maps | +| `#15AF` | `TSCONF_PORT_TMPAGE` | W | TileMap page | +| `#16AF` | `TSCONF_PORT_T0GPAGE` | W | Tile plane 0 gfx page | +| `#17AF` | `TSCONF_PORT_T1GPAGE` | W | Tile plane 1 gfx page | +| `#18AF` | `TSCONF_PORT_SGPAGE` | W | Sprite gfx page | +| `#20AF` | `TSCONF_PORT_SYSCONFIG` | W | **SysConfig: CPU freq + кэш** | +| `#21AF` | `TSCONF_PORT_MEMCONFIG` | W | MemConfig: режим памяти | +| `#22AF` | `TSCONF_PORT_HSINT` | W | FRAME-INT: горизонт. позиция | +| `#23AF` | `TSCONF_PORT_VSINTL` | W | FRAME-INT: вертик. позиция (low) | +| `#24AF` | `TSCONF_PORT_VSINTH` | W | FRAME-INT: вертик. позиция (high) + auto-inc | +| `#29AF` | `TSCONF_PORT_FDDVIRT` | W | FDD виртуализация | +| `#2AAF` | `TSCONF_PORT_INTMASK` | W | **INTMask: маска прерываний** | +| `#2BAF` | `TSCONF_PORT_CACHECONFIG` | W | **CacheConfig: кэш по окнам** | + +#### Порты тайловых смещений + +| Порт | Описание | +|------|----------| +| `#40AF`/`#41AF` | Tile plane 0: X offset (lo/hi) | +| `#42AF`/`#43AF` | Tile plane 0: Y offset (lo/hi) | +| `#44AF`/`#45AF` | Tile plane 1: X offset (lo/hi) | +| `#46AF`/`#47AF` | Tile plane 1: Y offset (lo/hi) | + +#### Порты DMA + +| Порт | Константа | Описание | +|------|-----------|----------| +| `#19AF` | `TSCONF_PORT_DMASADDRL` | Source addr low | +| `#1AAF` | `TSCONF_PORT_DMASADDRH` | Source addr high | +| `#1BAF` | `TSCONF_PORT_DMASADDRX` | Source page (extended) | +| `#1CAF` | `TSCONF_PORT_DMADADDRL` | Dest addr low | +| `#1DAF` | `TSCONF_PORT_DMADADDRH` | Dest addr high | +| `#1EAF` | `TSCONF_PORT_DMADADDRX` | Dest page (extended) | +| `#25AF` | `TSCONF_PORT_DMAWPDEV` | Wait Port device | +| `#26AF` | `TSCONF_PORT_DMALEN` | Burst length | +| `#27AF` | `TSCONF_PORT_DMACTRL` | Control (W) / Status (R) | +| `#28AF` | `TSCONF_PORT_DMANUM` | Bursts num (low) | +| `#2CAF` | `TSCONF_PORT_DMANUMH` | Bursts num (high) | +| `#2DAF` | `TSCONF_PORT_DMAWPADDR` | Wait Port addr | + +> ⚠️ **Частая ошибка:** Page0–Page3 = `#10AF`–`#13AF`, SysConfig = `#20AF`. +> Не путать с VConfig (`#00AF`) или VPage (`#01AF`)! + +### Порты OPL3 — YMF262 + +**OPL3 чип располагается по портам #C4–#C7:** + +| Порт | Константа | Назначение | +|-------|-----------|------------| +| `#C4` | `TSCONF_PORT_OPL3_ADDR0` | Bank 0: регистр адреса | +| `#C5` | `TSCONF_PORT_OPL3_DATA0` | Bank 0: данные | +| `#C6` | `TSCONF_PORT_OPL3_ADDR1` | Bank 1: регистр адреса | +| `#C7` | `TSCONF_PORT_OPL3_DATA1` | Bank 1: данные | + +**Регистр NEW (Bank 1, reg 0x05):** +- `NEW=1` (0x01) → OPL3 mode: биты L/R в регистрах `C0–C8` управляют маршрутизацией звука +- `NEW=0` (0x00) → OPL2-compat: L+R всегда активны (обязательно для файлов OPL2/OPL1!) + +```c +// inc/opl3.h +void opl3_write_b0(uint8_t reg, uint8_t data); // Bank 0 (#C4/#C5) +void opl3_write_b1(uint8_t reg, uint8_t data); // Bank 1 (#C6/#C7) +void opl3_init(void); // сброс + NEW=1 + +// Для OPL2/OPL1 VGM: после opl3_init() нужно: +opl3_write_b1(0x05, 0x00); // NEW=0 — иначе тишина! +``` + +### Порты AY-3-8910 / YM2149 + +| Порт | Константа | Назначение | +|---------|-----------|------------| +| `#FFFD` | `TSCONF_PORT_AY_ADDR` | Регистр адреса / чтение (`OUT/IN`) | +| `#BFFD` | `TSCONF_PORT_AY_DATA` | Запись данных | + +**TurboSound (два AY):** второй чип выбирается через порт `#FFFD`: +- `OUT (#FFFD), #FF` (= `TSCONF_AY_TS_CHIP0`) → чип 0 (основной) +- `OUT (#FFFD), #FE` (= `TSCONF_AY_TS_CHIP1`) → чип 1 + +**Частоты AY** (управление через `wc_turbopl(WC_TURBO_AY, param)`): + +| param | Частота | Примечание | +|-------|---------|------------| +| 0 | 1,750,000 Гц | Стандарт TSConfig / Pentagon | +| 1 | 1,773,400 Гц | NTSC-кварц (315/88 МГц ÷ 2) | +| 2 | 3,500,000 Гц | Удвоенная | +| 3 | 3,546,800 Гц | Удвоенная NTSC | + +### Порты SAA1099 — MultiSound card + +| Чип | Данные | Адрес | Константы | +|------|--------|-------|-----------| +| SAA1 | `#00FF` | `#01FF` | `TSCONF_PORT_SAA_DATA/ADDR` | +| SAA2 | `#00FE` | `#01FE` | `TSCONF_PORT_SAA2_DATA/ADDR` | + +> Некоторые источники указывают SAA2 по портам `#02FF`/`#03FF` — это +> зависит от реализации MultiSound платы. В `tsconf.h` используются +> `#00FE`/`#01FE`. + +**Пример записи в регистр OPL3:** +```asm +; Запись в регистр банка 0 + LD BC,#C4 ; Порт адреса + LD A,reg ; Номер регистра + OUT (C),A + LD B,#C5 ; Порт данных + LD A,value ; Значение + OUT (C),A + +; Запись в регистр банка 1 + LD BC,#C6 ; Порт адреса банка 1 + LD A,reg ; Номер регистра + OUT (C),A + LD B,#C7 ; Порт данных банка 1 + LD A,value ; Значение + OUT (C),A +``` + +## Процессор + +### Частоты CPU + +| Режим | Частота | SysConfig [1:0] | Константа | +|--------|---------|-----------------|----------| +| 00 | 3.5 МГц | `00` | `TSCONF_CPU_3_5MHZ` | +| 01 | 7.0 МГц | `01` | `TSCONF_CPU_7MHZ` | +| 10 | 14.0 МГц | `10` | `TSCONF_CPU_14MHZ` | +| 11 | 28.0 МГц | `11` | (non-standard, не все FPGA) | + +> В WC API `wc_turbopl(WC_TURBO_CPU, param)` поддерживает +> param 0–3 (3.5/7/14/28 МГц). + +Управление через регистр **SysConfig #20AF** (биты 1:0): + +```c +// tsconf.h +#define TSCONF_CPU_3_5MHZ 0x00 +#define TSCONF_CPU_7MHZ 0x01 +#define TSCONF_CPU_14MHZ 0x02 +#define TSCONF_CPU_MASK 0x03 +#define TSCONF_CACHE_EN 0x04 // бит 2 — глобальный кэш + +// Convenience macros: +tsconf_set_cpu(TSCONF_CPU_14MHZ); // только CPU +tsconf_set_cpu_cache(TSCONF_CPU_14MHZ, TSCONF_CACHE_EN); // CPU + кэш +``` + +### Кэш + +512 байт кэш-памяти для ускорения работы на 14/28 МГц. + +**Глобально:** бит 2 регистра **SysConfig** (`TSCONF_CACHE_EN`). + +**По-оконно:** регистр **CacheConfig #2BAF**: + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TSCONF_CACHE_EN_0000` | 0x01 | Кэш для #0000–#3FFF | +| `TSCONF_CACHE_EN_4000` | 0x02 | Кэш для #4000–#7FFF | +| `TSCONF_CACHE_EN_8000` | 0x04 | Кэш для #8000–#BFFF | +| `TSCONF_CACHE_EN_C000` | 0x08 | Кэш для #C000–#FFFF | +| `TSCONF_CACHE_ALL` | 0x0F | Все окна | +| `TSCONF_CACHE_NONE` | 0x00 | Кэш выключен | + +```c +tsconf_set_cache(TSCONF_CACHE_EN_8000 | TSCONF_CACHE_EN_C000); +``` + +**Важно:** После DMA операций необходима **инвалидация кэша**: + +```asm +; Инвалидация: 512 байт LDIR на себя + LD HL,#FE00 + LD DE,#FE00 + LD BC,#0200 + LDIR +``` + +## Прерывания + +### Источники прерываний + +TSConfig поддерживает несколько источников INT: + +| Источник | Бит | IM2 вектор | Константа | +|----------|-----|------------|-----------| +| FRAME | 0 | `#FF` (`TSCONF_IVEC_FRAME`) | `TSCONF_INT_FRAME` | +| LINE | 1 | `#FD` (`TSCONF_IVEC_LINE`) | `TSCONF_INT_LINE` | +| DMA | 2 | `#FB` (`TSCONF_IVEC_DMA`) | `TSCONF_INT_DMA` | +| WTP | 3 | — | `TSCONF_INT_WTP` | + +### Регистр маски прерываний (INTMask #2AAF) + +``` +Биты: +3 - WTP (Wait Port) interrupt enable +2 - DMA interrupt enable +1 - LINE interrupt enable +0 - FRAME interrupt enable + +Reset: #01 (только FRAME включен) +``` + +```c +tsconf_set_intmask(TSCONF_INT_FRAME | TSCONF_INT_LINE); +``` + +### Позиционирование FRAME-INT + +```c +// Задать вертикальную строку и горизонтальную позицию +tsconf_set_frame_int(vline, hpos); +// Отключить +tsconf_disable_frame_int(); + +// Авто-инкремент VCNT (для множественных INT в кадре): +// VSINTH[7:4] = auto-increment value +#define TSCONF_VSINTH_INC(n) ((n) << 4) +``` + +--- + +## MemConfig (#21AF) — Конфигурация памяти + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TSCONF_MEM_ROM128` | 0x01 | Выбор банка ROM (= бит 4 порта #7FFD) | +| `TSCONF_MEM_W0_WE` | 0x02 | Разрешить запись в окно #0000 | +| `TSCONF_MEM_W0_MAP_DIS` | 0x04 | Отключить маппинг через Page0 | +| `TSCONF_MEM_W0_RAM` | 0x08 | RAM вместо ROM в окне #0000 | +| `TSCONF_MEM_LCK128_512K` | 0x00 | Pentagon-512 режим | +| `TSCONF_MEM_LCK128_128K` | 0x40 | Pentagon-128 режим | +| `TSCONF_MEM_LCK128_AUTO` | 0x80 | Авто-определение | +| `TSCONF_MEM_LCK128_1M` | 0xC0 | Расширенный 1 МБ | + +--- + +## TSU — Tile/Sprite Engine (#06AF) + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TSCONF_TS_EXT` | 0x01 | TSU 360×288 (extended) | +| `TSCONF_T0Z_EN` | 0x04 | Tile[0] visible (plane 0) | +| `TSCONF_T1Z_EN` | 0x08 | Tile[0] visible (plane 1) | +| `TSCONF_T0_EN` | 0x20 | Tile layer 0 enable | +| `TSCONF_T1_EN` | 0x40 | Tile layer 1 enable | +| `TSCONF_S_EN` | 0x80 | Sprite layers enable | + +--- + +## HUS — Hurricane Sound Engine + +> Порты `#80AF`–`#87AF` (есть не на всех FPGA-прошивках) + +HUS — аппаратный звуковой движок TSConfig для воспроизведения +PCM-сэмплов из RAM с DMA. + +| Порт | Константа | Описание | +|------|-----------|----------| +| `#80AF` | `TSCONF_PORT_HUSCTRL` | Control (W) / Status (R) | +| `#81AF` | `TSCONF_PORT_HUSSAMPRATE` | Sample Rate: 1,750,000 / (SR+1) | +| `#82AF` | `TSCONF_PORT_HUSTICKRATEL` | Tick Rate (low) | +| `#83AF` | `TSCONF_PORT_HUSTICKRATEH` | Tick Rate (high) | +| `#84AF`–`#87AF` | `TSCONF_PORT_HUSRLD0`–`3` | Reload ch 0–31 | + +**Биты статуса (чтение #80AF):** + +| Константа | Бит | Описание | +|-----------|-----|----------| +| `TSCONF_HUS_ACT` | 0 | 1 = HUS активен | +| `TSCONF_HUS_TICK` | 1 | 1 = tick event | +| `TSCONF_HUS_HALF` | 6 | 1 = полбуфера | +| `TSCONF_HUS_EMPTY` | 7 | 1 = буфер пуст | + +**Управление (запись #80AF):** + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TSCONF_HUS_PAUSE` | 0x02 | Пауза воспроизведения | +| `TSCONF_HUS_RESET` | 0x80 | Сброс HUS | + +### ISR плагина — собственный таймер + +Плагин может зарегистрировать собственный ISR через +`wc_int_pl(WC_INT_PLUGIN)` + `wc_int_pl_handler(addr)`. + +В VGM-плеере ISR настраивается через **перехват** вектора IM2 +(FRAME INT, вектор `#5BFF`) с высокой частотой вызова. +POS_TABLE задаёт позиции INT-триггеров в кадре: + +| Вариант | ISR_FREQ | T-states/тик | Тиков/кадр | VGM_SAMPLE_SHIFT | Семплов/тик | +|---------|----------|-------------|------------|------------------|---------| +| Quarter | **683 Гц** | 5120 T | 14 | 6 (44100/64) | 64 | +| Half | 1367 Гц | 2560 T | 28 | 5 (44100/32) | 32 | +| Full | 2734 Гц | 1280 T | 56 | 4 (44100/16) | 16 | + +Конфигурация варианта хранится в `inc/variant_cfg.h`: + +```c +// variant_cfg.h (текущий вариант) +#define ISR_FREQ 683 +#define ISR_TICKS_PER_FRAME 14 // 683/50≈14 +#define VGM_SAMPLE_SHIFT 6 // 44100 >> 6 = 689 ≈ 683 +#define VGM_SAMPLE_MASK 0x3Fu +#define VGM_FILL_CMD_BUDGET 32 // команд за один yield +#define VARIANT_POS_ENTRIES 14 // записей в pos_table +#define VARIANT_POS_STEP 5120 // T-states между INT +``` + +### Механизм pos_table + +`pos_table` — кольцевой буфер позиций INT в кадре. +Каждая запись: 5 байт `[VSINTH, VSINTL, HSINT, next_ptr_lo, next_ptr_hi]`. +ISR выводит 3 байта через OUTI (порты VSINTH/VSINTL/HSINT), +затем переходит по next_ptr к следующей записи. +Генерируется скриптом `scripts/gen_pos_table.js`. + +### Частота прерываний TSConfig (FRAME) + +- FRAME: зависит от видео режима + - 50 Гц (312 строк PAL) + - 60 Гц (262 строки NTSC) + - ~48.8 Гц (320 строк Pentagon) + +## DMA Контроллер + +TSConfig имеет аппаратный DMA для быстрого копирования/заполнения данных. + +### Режимы DMA (регистр DMACTRL #27AF) + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TSCONF_DMA_RAM_RAM` | 0x09 | RAM → RAM копирование | +| `TSCONF_DMA_BLT1_RAM` | 0x89 | RAM → RAM с пропуском нулей (transparency) | +| `TSCONF_DMA_BLT2_RAM` | 0x31 | Blitter с aдером | +| `TSCONF_DMA_FILL` | 0x21 | Заполнение RAM | +| `TSCONF_DMA_RAM_CRAM` | 0xA1 | RAM → CRAM (палитра, RGB555) | +| `TSCONF_DMA_RAM_SFILE` | — | RAM → Sprite File | +| `TSCONF_DMA_SPI_RAM` | 0x11 | SPI → RAM (чтение SD) | +| `TSCONF_DMA_RAM_SPI` | 0x91 | RAM → SPI (запись SD) | +| `TSCONF_DMA_IDE_RAM` | 0x19 | IDE → RAM | +| `TSCONF_DMA_RAM_IDE` | 0x99 | RAM → IDE | +| `TSCONF_DMA_FDD_RAM` | 0x29 | FDD → RAM | +| `TSCONF_DMA_RAM_WTP` | 0xB9 | RAM → Wait Port | +| `TSCONF_DMA_WTP_RAM` | 0x39 | Wait Port → RAM | + +### Флаги DMA + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TSCONF_DMA_ASZ_256` | 0x00 | Выравнивание 256 байт | +| `TSCONF_DMA_ASZ_512` | 0x04 | Выравнивание 512 байт | +| `TSCONF_DMA_D_ALGN` | 0x08 | Выравнивание приёмника | +| `TSCONF_DMA_S_ALGN` | 0x10 | Выравнивание источника | +| `TSCONF_DMA_ACT` | 0x80 | DMA active (бит статуса при чтении) | + +### Wait Port Device (#25AF) + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TSCONF_DMAWP_GLUCLOCK` | 0x00 | GluClock | +| `TSCONF_DMAWP_COMPORT` | 0x01 | COM-порт | + +### Основные регистры DMA + +> Порты DMA описаны выше в таблице портов. Основные: + +| Регистр | Порт | Описание | +|---------|------|----------| +| DMA_SAL/H/X | `#19AF`/`#1AAF`/`#1BAF` | Адрес + страница источника | +| DMA_DAL/H/X | `#1CAF`/`#1DAF`/`#1EAF` | Адрес + страница приёмника | +| DMA_LEN | `#26AF` | Размер бёрста (N) | +| DMA_NUM/H | `#28AF`/`#2CAF` | Количество бёрстов (T) | +| DMA_CTRL | `#27AF` | Режим + запуск (W) / статус (R) | + +### Пример DMA через WC API (C) + +```c +// Копирование 4 КБ из стр. 0 плагина (#8000) в стр. 1 (#8000) +wc_dmapl(WC_DMA_INIT_SD); // потребует B,C,HL,DE в регистрах +wc_dmapl(WC_DMA_SET_TN); // B=бёрсты, C=размер бёрста +wc_dmapl(WC_DMA_RUN_RAM); // запуск RAM→RAM с ожиданием +``` + +--- + +## Использование портов из SDCC + +`tsconf.h` объявляет все порты как `__sfr __banked` переменные для +прямого ввода/вывода без `__asm`: + +```c +// Примеры: +sfr_page2 = 5; // OUT (#12AF), 5 +sfr_sysconfig = TSCONF_CPU_14MHZ; // OUT (#20AF), 2 +sfr_border = 2; // OUT (#0FAF), 2 +sfr_opl3_addr0 = 0x20; // OUT (#00C4), 0x20 +sfr_opl3_data0 = 0xFF; // OUT (#00C5), 0xFF + +// Чтение: +uint8_t page = sfr_page2; // IN A,(#12AF) +``` + +Полный список: `sfr_vconfig`, `sfr_vpage`, `sfr_gxoffsl/h`, `sfr_gyoffsl/h`, +`sfr_tsconfig`, `sfr_palsel`, `sfr_border`, `sfr_page0`–`sfr_page3`, +`sfr_sysconfig`, `sfr_memconfig`, `sfr_intmask`, `sfr_cacheconfig`, +`sfr_dmasaddrh/l/x`, `sfr_dmadaddrh/l/x`, `sfr_dmalen`, `sfr_dmactrl`, +`sfr_dmanum/h`, `sfr_opl3_addr0/data0/addr1/data1`, `sfr_ay_addr/data`, +`sfr_saa_addr/data`, `sfr_saa2_addr/data`. + +## Видео система + +### Видео режимы (VConfig #00AF) + +| Режим | Константа | Разрешения | Цвета | +|-------|-----------|------------|-------| +| ZX | `TSCONF_VM_ZX` (0x00) | 256×192 | 16 (атрибуты) | +| 16c | `TSCONF_VM_16C` (0x01) | 256..360 | 16 (4 bpp) | +| 256c | `TSCONF_VM_256C` (0x02) | 256..360 | 256 (8 bpp) | +| TXT | `TSCONF_VM_TXT` (0x03) | 80×25, 80×30, 90×36 | 256 | + +**Разрешения:** + +| Константа | Значение | Пиксели | +|-----------|----------|---------| +| `TSCONF_RRES_256x192` | 0x00 | 256×192 | +| `TSCONF_RRES_320x200` | 0x40 | 320×200 | +| `TSCONF_RRES_320x240` | 0x80 | 320×240 | +| `TSCONF_RRES_360x288` | 0xC0 | 360×288 | + +**Прочие биты VConfig:** + +| Константа | Бит | Описание | +|-----------|-----|----------| +| `TSCONF_VC_FT_EN` | 2 | FT812 enable (VDAC2) | +| `TSCONF_VC_GFXOVR` | 3 | GFX over TSU | +| `TSCONF_VC_NOTSU` | 4 | Отключить TSU graphics | +| `TSCONF_VC_NOGFX` | 5 | Отключить main graphics | + +**Чтение VConfig (статус):** + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TSCONF_VDVER_2BIT_PWM` | 0x00 | 2-бит ЦАП (ШИМ) | +| `TSCONF_VDVER_3BIT` | 0x01 | 3-бит | +| `TSCONF_VDVER_4BIT` | 0x02 | 4-бит | +| `TSCONF_VDVER_5BIT` | 0x03 | 5-бит | +| `TSCONF_VDVER_VDAC2` | 0x07 | VDAC2 / FT812 | +| `TSCONF_STATUS_PWR_UP` | 0x40 | Після включення | + +### Палитра + +256 ячеек палитры, формат RGB555 (15 бит цвета). + +### Спрайты + +- До 85 спрайтов на строку +- Размеры: 8x8 до 64x64 пикселей +- До 3 плоскостей спрайтов +- До 16 палитр на строку + +## Совместимость с Pentagon-128 + +### Порт #7FFD + +``` +Биты: +7:6 - дополнительные биты страницы (512K/1024K) +5 - LOCK (блокировка переключения) +4 - ROM128 (выбор ROM) +3 - SCR (экран 5 или 7) +2:0 - номер страницы для #C000 +``` + +### Режимы совместимости (MemConfig [7:6]) + +| LCK128 | Режим | Описание | +|--------|-------|----------| +| 00 | Pentagon-512 | `TSCONF_MEM_LCK128_512K` | +| 01 | Pentagon-128 | `TSCONF_MEM_LCK128_128K` | +| 10 | Авто | `TSCONF_MEM_LCK128_AUTO` | +| 11 | 1024 КБ | `TSCONF_MEM_LCK128_1M` | + +--- + +## FDD виртуализация (#29AF) + +TSConfig позволяет виртуализировать FDD-приводы (для TRD/TAP): + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TSCONF_FDD_A` | 0x01 | Привод A | +| `TSCONF_FDD_B` | 0x02 | Привод B | +| `TSCONF_FDD_C` | 0x04 | Привод C | +| `TSCONF_FDD_D` | 0x08 | Привод D | +| `TSCONF_FDD_VG_OPEN` | 0x80 | Открыть VG93 | + +--- + +## Файловая система + +WC работает через FAT32: +- **SD-карты**: Z-Controller (основной), NeoGS SD +- **IDE**: NEMO (Master/Slave), SMUC (Master/Slave) +- **Длинные имена**: до 255 символов (с WC v0.73) +- **Каталоги**: >4095 записей (с WC v1.05) + +--- + +## Полезные ссылки + +- TSConfig спецификация: https://github.com/tslabs/zx-evo +- Регистры TSConfig: [src_sdcc/inc/tsconf.h](../src_sdcc/inc/tsconf.h) +- WC API: [src_sdcc/inc/wc_api.h](../src_sdcc/inc/wc_api.h) +- ISR конфигурация: [src_sdcc/inc/variant_cfg.h](../src_sdcc/inc/variant_cfg.h) + +## Пример инициализации для плагина + +```asm +; Базовая инициализация для музыкального плагина + +INIT_PLUGIN: + ; Сохранить текущее состояние + LD A,(#6002) ; Страница в окне 2 + PUSH AF + LD A,(#6003) ; Страница в окне 3 + PUSH AF + + ; Восстановить TXT режим + LD A,#0F + CALL WLD ; GEDPL + + ; Установить частоту CPU + LD B,#00 ; Z80 frequency + LD C,%01 ; 7 MHz + LD A,#0E + CALL WLD ; TURBOPL + + ; Настроить прерывания + LD HL,INT_HANDLER + EXA + LD A,#FF + CALL WLD ; INT_PL + EXA + + ; Инициализация OPL3 + CALL INIT_OPL3 + + RET + +; Обработчик прерываний +INT_HANDLER: + PUSH AF, BC, DE, HL + ; Ваш код обработки музыки + CALL UPDATE_MUSIC + POP HL, DE, BC, AF + EI + RETI + +; Инициализация OPL3 +INIT_OPL3: + ; Сброс OPL3 + LD BC,#C4 + XOR A + OUT (C),A + LD B,#C5 + OUT (C),A + ; Дополнительная инициализация... + RET +``` diff --git a/doc/05_VGM_Format.md b/doc/05_VGM_Format.md new file mode 100644 index 0000000..4385b08 --- /dev/null +++ b/doc/05_VGM_Format.md @@ -0,0 +1,293 @@ +# VGM Format Reference + +> **Источник истины:** `src_sdcc/inc/vgm.h`, `src_sdcc/lib/vgm.c` + +## Обзор формата VGM + +VGM (Video Game Music) — формат хранения логов регистров звуковых чипов. + +### Основные характеристики + +- Расширение файла: **.vgm** (raw) или **.vgz** (gzip-сжатый) +- Содержит команды записи в регистры звуковых чипов +- Поддерживаемые плагином чипы: **YMF262 (OPL3), YM3812 (OPL2), YM3526 (OPL1), Y8950, AY-3-8910/YM2149/YM2203, SAA1099** +- Частота семплирования фиксирована: **44100 Гц** + +## Структура заголовка VGM (ключевые смещения) + +> Все поля — little-endian 32 бит, если не указано иное. +> Минимальная поддерживаемая версия: **1.00** (`0x00000100`). + +| Offset | Размер | Название | Описание | С версии | +|--------|--------|------------------|------------------------------------------------|----------| +| `0x00` | 4 | Ident | Сигнатура `"Vgm "` (0x56 0x67 0x6D 0x20) | 1.00 | +| `0x04` | 4 | EoF offset | Расстояние до EOF от позиции 0x04 | 1.00 | +| `0x08` | 4 | Version | BCD: `0x0150`=v1.50, `0x0151`=v1.51 | 1.00 | +| `0x0C` | 4 | SN76489 clock | Тактовая частота SN76489 (0 = нет) | 1.00 | +| `0x10` | 4 | YM2413 clock | Тактовая частота YM2413 / OPLL (0 = нет) | 1.00 | +| `0x14` | 4 | GD3 offset | Смещение GD3-тега от `0x14` (0 = нет тега) | 1.00 | +| `0x18` | 4 | Total samples | Полная длина трека в семплах (÷44100 = сек) | 1.00 | +| `0x1C` | 4 | Loop offset | Смещение точки петли от `0x1C` (0 = нет) | 1.00 | +| `0x20` | 4 | Loop samples | Длина тела петли в семплах | 1.00 | +| `0x24` | 4 | Rate | Кадровая частота (50/60 Гц, информативно) | 1.01 | +| `0x34` | 4 | **VGM data offset** | Смещение VGM-данных от `0x34`; 0 → данные с `0x40` | **1.50** | +| `0x44` | 4 | YM2203 clock | **YM2203 (OPN/PSG)** (0 = нет) | 1.51 | +| `0x50` | 4 | **YM3812 clock** | **YM3812 (OPL2)** — AdLib/SoundBlaster | **1.51** | +| `0x54` | 4 | **YM3526 clock** | **YM3526 (OPL1)** — ISA AdLib | **1.51** | +| `0x58` | 4 | **Y8950 clock** | **Y8950 (OPL+ADPCM)** — MSX-Audio | **1.51** | +| `0x5C` | 4 | **YMF262 clock** | **YMF262 (OPL3)** — SB Pro 2 / TSConfig | **1.51** | +| `0x60` | 4 | YMF278B clock | YMF278B (OPL4) (0 = нет) | 1.51 | +| `0x74` | 4 | AY8910 clock | **AY-3-8910 / YM2149** (0 = нет) | 1.51 | +| `0xC8` | 4 | SAA1099 clock | **SAA1099** — SAM Coupé (0 = нет) | 1.71 | + +> ⚠️ **Частая ошибка в документации:** смещения чипов в диапазоне `0x50`–`0x5C` — не `0x5C`/`0x60`/`0x64`/`0x68`. +> Используйте константы: `VGM_OFF_YM3812=0x50`, `VGM_OFF_YM3526=0x54`, `VGM_OFF_Y8950=0x58`, `VGM_OFF_YMF262=0x5C`. + +### Смещение данных VGM + +``` +Если заголовок[0x34] (VGM data offset) == 0: + данные начинаются с абсолютного адреса 0x40 (старые файлы, < v1.50) +Иначе: + данные начинаются с 0x34 + заголовок[0x34] +``` + +Типичное значение для v1.51+: `VGM_data_offset = 0x4C`, данные с `0x80`. +Реализация в `vgm_parse_header()`: хранит готовый адрес начала данных в `vgm_data_start`. + +### Bit 30 поля clock — флаг dual-chip + +Бит 30 любого clock-поля (`clock & 0x40000000`) означает **два экземпляра** чипа (`VGM_CF_DUAL`). +Реальная тактовая частота = `clock & 0x3FFFFFFF`. + +### Обнаружение чипов — `vgm_parse_header()` + +Приоритет определения `vgm_chip_type`: **YMF262 > YM3812 > YM3526/Y8950**: + +```c +// vgm.h — константы +#define VGM_OFF_YM3812 0x50 // OPL2 +#define VGM_OFF_YM3526 0x54 // OPL1 +#define VGM_OFF_Y8950 0x58 // OPL+ADPCM +#define VGM_OFF_YMF262 0x5C // OPL3 +#define VGM_OFF_AY8910 0x74 // AY-3-8910 / YM2149 +#define VGM_OFF_YM2203 0x44 // OPN (PSG-часть = AY) +#define VGM_OFF_SAA1099 0xC8 // SAA1099 + +// Значения vgm_chip_type +#define VGM_CHIP_NONE 0 +#define VGM_CHIP_OPL 1 // YM3526 или Y8950 +#define VGM_CHIP_OPL2 2 // YM3812 +#define VGM_CHIP_OPL3 3 // YMF262 +``` + +## VGM-команды — полная таблица + +| Код | Байт | Параметры | Описание | +|--------------|------|-----------|-----------------------------------------------| +| `0x55` | 3 | `rr dd` | YM2203 write (PSG-часть, reg 0–15) | +| `0x5A` | 3 | `rr dd` | YM3812 (OPL2) write | +| `0x5B` | 3 | `rr dd` | YM3526 (OPL1) write | +| `0x5C` | 3 | `rr dd` | Y8950 (OPL+ADPCM) write | +| `0x5E` | 3 | `rr dd` | YMF262 (OPL3) Bank 0 write | +| `0x5F` | 3 | `rr dd` | YMF262 (OPL3) Bank 1 write | +| `0x61` | 3 | `lo hi` | Задержка: `(hi<<8)\|lo` семплов | +| `0x62` | 1 | — | Задержка 735 семплов (1/60 с) | +| `0x63` | 1 | — | Задержка 882 семпла (1/50 с) | +| `0x66` | 1 | — | End of data → конец потока / loop | +| `0x67` | var | — | Data block (PCM и пр.) — пропускается | +| `0x70`–`0x7F`| 1 | — | Задержка 1–16 семплов (`(cmd & 0x0F) + 1`) | +| `0xA0` | 3 | `rr dd` | AY8910 write; бит 7 `rr` = чип 2 | +| `0xBD` | 3 | `rr dd` | SAA1099 write; бит 7 `rr` = чип 2 | +| `0xD0` | 4 | `pp aa dd`| YMF278B (OPL4) write, порт `pp` | + +Прочие команды пропускаются по таблице длин VGM spec (размер 1, 2, или 3 байта). + +### `0xD0` — YMF278B (OPL4) write + +``` +0xD0 pp aa dd +``` + +- `pp` бит 7 — выбор чипа (dual-chip); игнорируется, на ZXM-MoonSound один чип. +- `pp & 0x7F` — порт: + - `0` — FM Bank 0 (те же регистры/порты #C4-#C5, что и `0x5E` для OPL3) + - `1` — FM Bank 1 (те же регистры/порты #C6-#C7, что и `0x5F` для OPL3) + - `2` — wave-часть чипа (табличный синтез), порты #7E (адрес) / #7F (данные) +- `aa` — номер регистра, `dd` — значение. + +Чтобы чип реально принимал запись в порт 2 (wave-часть), в Bank 1 +регистре `0x05` должен быть установлен бит NEW2 (бит 1), в дополнение к +обычному биту NEW (бит 0) — см. `CMDBLK_INIT_OPL4` в `cmdblocks.asm`. +Без NEW2 запись в порты #7E/#7F реальным чипом YMF278B игнорируется. + +--- + +## Масштабирование задержек + +VGM sample rate = **44100 Гц**, ISR rate = **1367 Гц** (= 44100 / 32): + +```c +// inc/isr.h +#define VGM_SAMPLE_SHIFT 5 // делитель = 2^5 = 32 +#define VGM_SAMPLE_MASK 0x1Fu // маска остатка +``` + +Алгоритм `vgm_fill_buffer()`: +- Накапливает семплы задержек +- При накоплении ≥ 32 → конвертирует `samples >> 5` в ISR-тики → команда `CMD_WAIT` +- Задержки < 32 семплов (< 0.73 мс) **теряются** (rounded down, это нормально) + +``` +1 секунда = 44100 семплов = 1378 ISR-тиков ≈ 1367 Гц +1 фрейм (50Hz) = 882 семпла = 27 ISR-тиков +1 фрейм (60Hz) = 735 семплов = 22 ISR-тика +``` + +--- + +## GD3 — метаданные трека + +GD3-тег содержит Unicode-строки (UCS-2 LE, нуль-терминированные): + +| № | Содержимое | +|---|--------------------| +| 1 | Track title (EN) | +| 2 | Track title (JP) | +| 3 | Game name (EN) | +| 4 | Game name (JP) | +| 5 | System name (EN) | +| 6 | System name (JP) | +| 7 | Author (EN) | +| 8 | Author (JP) | +| 9 | Release date | +|10 | VGM pack author | +|11 | Notes | + +В плагине читаются только **английские** поля (нечётные), конвертируются в ASCII: символы > 127 → `'?'`. +Макс. длина поля: `VGM_GD3_LEN = 48` байт. + +```c +// inc/vgm.h +extern char vgm_gd3_track[VGM_GD3_LEN]; +extern char vgm_gd3_game[VGM_GD3_LEN]; +extern char vgm_gd3_system[VGM_GD3_LEN]; +extern char vgm_gd3_author[VGM_GD3_LEN]; +``` + +--- + +## Петля (loop) + +```c +// inc/vgm.h +extern uint16_t vgm_loop_addr; // адрес точки петли в окне #C000 (0 = нет) +extern uint8_t vgm_loop_page; // VPL-страница точки петли +``` + +Если `loop_offset (0x1C) != 0`: +- Абсолютное смещение в файле = `0x1C + loop_offset` +- `vgm_rewind_to_loop()` переставляет `vgm_read_ptr` / `vgm_cur_page` на точку петли + +--- + +## VGZ — gzip-сжатый VGM + +Файл `.vgz` — стандартный gzip. Схема обработки в плагине: + +``` +1. Загрузить .vgz в мегабуфер (VPL-страницы выше #20) +2. copy_pages_to_tap(n) — скопировать во временную TAP-область (#A1–#BF, 31 стр.) +3. inflate_vgz(src, dst) — распаковать (inflate.asm) обратно в мегабуфер +4. Работать с VGM как обычно +``` + +Макс. размер распакованного VGM через TAP: 31 × 16 КБ = **496 КБ**. + +```c +void copy_pages_to_tap(uint8_t n_pages); +uint8_t inflate_vgz(uint8_t src_page, uint8_t dst_page); +// возвращает: 0 = успех, 1 = ошибка (не gzip или CRC-ошибка) +``` + +--- + +## OPL2 vs OPL3 — проблема L/R output + +**Проблема:** `opl3_init()` устанавливает `NEW=1` (OPL3 mode). +В OPL3 mode биты 4–5 регистров `C0–C8` управляют маршрутизацией L/R (`L=1, R=1` для звука). +Файлы OPL2/OPL1 (команды `0x5A`/`0x5B`) пишут `C0` без битов L/R → **тишина**. + +**Решение в `start_playback()` (main.c):** + +```c +opl3_init(); // NEW=1 по умолчанию +if (vgm_chip_type != VGM_CHIP_OPL3) { + opl3_write_b1(0x05, 0x00); // NEW=0: OPL2-совместимый режим +} +// В OPL2-compat звук всегда идёт на оба канала L+R +``` + +--- + +## Пример парсинга заголовка (SDCC C) + +```c +#include "inc/vgm.h" +#include "inc/wc_api.h" + +// Загрузить первую страницу файла в окно #C000 и разобрать заголовок +wc_mngcvpl(0); // Переключить Win3 (#C000) на VPL-страницу 0 +wc_load512(0xC000, 32); // Загрузить 16 КБ с диска + +uint8_t rc = vgm_parse_header(); + +switch (rc) { +case VGM_OK: + // vgm_chip_type → VGM_CHIP_OPL / OPL2 / OPL3 + // vgm_chip_count → кол-во обнаруженных чипов + // vgm_version → 0x0151 для v1.51 + // vgm_loop_addr → 0 = нет петли + break; +case VGM_ERR_HEADER: + // Нет сигнатуры "Vgm " или заголовок повреждён + break; +case VGM_ERR_NOOPL: + // Файл не содержит поддерживаемого чипа (OPL/AY/SAA) + break; +} +``` + +--- + +## Полезные константы (из `vgm.h`) + +```c +// Смещения clock-полей в заголовке (relative byte offsets) +#define VGM_OFF_SN76489 0x0C +#define VGM_OFF_YM2413 0x10 +#define VGM_OFF_YM2203 0x44 // OPN +#define VGM_OFF_YM3812 0x50 // OPL2 ← основной +#define VGM_OFF_YM3526 0x54 // OPL1 +#define VGM_OFF_Y8950 0x58 // MSX-Audio +#define VGM_OFF_YMF262 0x5C // OPL3 ← основной +#define VGM_OFF_YMF278B 0x60 // OPL4 +#define VGM_OFF_AY8910 0x74 // AY-3-8910 / YM2149 +#define VGM_OFF_SAA1099 0xC8 // SAA1099 + +// Коды результата vgm_parse_header() +#define VGM_OK 0 +#define VGM_ERR_HEADER 1 +#define VGM_ERR_NOOPL 2 + +// ISR / VGM sample rate mapping +#define VGM_SAMPLE_SHIFT 5 // 44100 / 32 ≈ 1367 Гц (ISR_FREQ) +``` + +--- + +## Ссылки + +- VGM Format Specification: https://vgmrips.net/wiki/VGM_Specification +- YMF262 (OPL3) Datasheet: Yamaha YMF262 Application Manual +- Архив VGM-треков: https://vgmrips.net/ diff --git a/doc/06_Example_Plugin.md b/doc/06_Example_Plugin.md new file mode 100644 index 0000000..c3f1e40 --- /dev/null +++ b/doc/06_Example_Plugin.md @@ -0,0 +1,475 @@ +# Пример простого плагина для WC + +## Минимальный плагин "Hello World" + +Этот пример показывает базовую структуру плагина для Wild Commander. + +```asm +;--------------------------------------- +; Hello World Plugin +; Простейший пример плагина для WC +;--------------------------------------- + + ORG #6000 + +; Константы WC API +WLD EQU #6006 ; Точка вызова API +ABT EQU #6004 ; Флаг ESC +ENT EQU #6005 ; Флаг ENTER + +;--------------------------------------- +; ЗАГОЛОВОК ПЛАГИНА (512 байт) +;--------------------------------------- + +; +0: Резерв (16 байт) + DS 16 + +; +16: Идентификатор (16 байт) + DB "WildCommanderMDL" + +; +32: Версия формата + DB #10 + +; +33: Резерв + DB 0 + +; +34: Количество страниц + DB 1 ; Плагин занимает 1 страницу + +; +35: Стартовая страница + DB 0 ; Запускаться будет со страницы 0 + +; +36: Блоки данных (6 x 2 байта) + DB 0,1 ; Блок 0: код, 1x512 байт + DB 0,0 + DB 0,0 + DB 0,0 + DB 0,0 + DB 0,0 + +; +48: Резерв (15 байт) + DS 15 + +; +63: Флаги расширений + DB 0 ; Стандартный режим + +; +64: Блок расширений (32 x 3 байта) + DS 32*3 ; Не используем расширения + +; +160: Конец блока расширений + DB 0 + +; +161: Максимальный размер файла (4 байта) + DW #FFFF,#FFFF ; Без ограничений + +; +165: Имя плагина (32 байта) + DB "Hello World Plugin " + +; +197: Тип условий вызова + DB #03 ; Из меню (F10) + +; +198: Текст для F2/F4 (6 байт) + DS 6 + +; +204: Текст для меню вьювера (24 байта) + DS 24 + +; +228: Резерв (32 байта) + DS 32 + +; +260: Параметры INI (252 байта) + DB 0 ; Нет параметров + DS 251 + +;--------------------------------------- +; КОД ПЛАГИНА +;--------------------------------------- + + ORG #8000,#6200 + +;--------------------------------------- +; Точка входа плагина +;--------------------------------------- +PLUGIN: + ; Сохранить входные параметры + LD (SAVE_IX),IX + LD (SAVE_HL),HL + LD (SAVE_DE),DE + LD (SAVE_BC),BC + LD (SAVE_A),A + + ; Восстановить текстовый режим + CALL GEDPL + + ; Показать окно + LD IX,WINDOW + CALL PRWOW + + ; Вывести текст + LD HL,TEXT + LD DE,#0102 ; Y=1, X=2 + LD BC,12 ; Длина + CALL PRSRW + + ; Применить цвет + EXA + LD A,%00001111 ; Синий на белом + CALL PRIAT + EXA + +; Главный цикл +MAIN_LOOP: + EI + HALT ; Ждем прерывание + + ; Проверка ESC + CALL ESC_KEY + JR Z,MAIN_LOOP + + ; Выход + LD IX,WINDOW + CALL RRESB ; Стереть окно + + XOR A ; A=0: просто выход + RET + +;--------------------------------------- +; WC API функции +;--------------------------------------- + +PRWOW: ; Вывод окна + LD A,1 + JP WLD + +RRESB: ; Стирание окна + LD A,2 + JP WLD + +PRSRW: ; Печать строки + LD A,3 + JP WLD + +PRIAT: ; Установка цвета + EXA + LD A,4 + JP WLD + +ESC_KEY: ; Проверка ESC + LD A,#17 + JP WLD + +GEDPL: ; Восстановление TXT режима + LD A,#0F + JP WLD + +;--------------------------------------- +; Данные +;--------------------------------------- + +; Структура окна +WINDOW: + DB 0 ; Флаги + DB 0 ; Маска курсора + DB 255 ; X (центр) + DB 255 ; Y (центр) + DB 30 ; Ширина + DB 5 ; Высота + DB %01111111 ; Цвет (белый на синем) + DB 0 ; Резерв + DW #0000 ; Буфер + DB 0,0 ; Разделители + +; Текст +TEXT: + DB "Hello World!" + +; Сохраненные регистры +SAVE_A: DB 0 +SAVE_BC: DW 0 +SAVE_DE: DW 0 +SAVE_HL: DW 0 +SAVE_IX: DW 0 + + END +``` + +## Компиляция + +Используйте sjasm для компиляции: + +```batch +sjasm hello.asm hello.wmf +``` + +## Пояснения к коду + +### Структура + +1. **Заголовок (#6000-#61FF)** - 512 байт метаданных +2. **Код (#8000+)** - исполняемый код плагина + +### Ключевые моменты + +1. **GEDPL обязателен!** Вызывайте его в начале, чтобы восстановить текстовый режим. + +2. **Структура окна** должна быть в памяти плагина (#8000-#BFFF). + +3. **Главный цикл** должен включать: + - `EI` - разрешить прерывания + - `HALT` - ждать прерывание (экономит энергию) + - Проверку клавиш + +4. **Выход** через `RET` с кодом в A. + +## Пример с файлом + +```asm +;--------------------------------------- +; Text Viewer Plugin - просмотр текстовых файлов +;--------------------------------------- + + ORG #6000 + +WLD EQU #6006 + +; ... Заголовок плагина ... + +; +64: Расширения + DB "TXT" ; .txt файлы + DB "DOC" ; .doc файлы + DS 30*3 + +; +197: Тип вызова + DB #00 ; По расширению + + ORG #8000,#6200 + +;--------------------------------------- +; Точка входа +;--------------------------------------- +PLUGIN: + ; Проверить тип вызова + CP #00 ; По расширению? + JR Z,VIEW_FILE + + ; Другой тип вызова - не наш файл + LD A,1 ; Файл не опознан + RET + +VIEW_FILE: + ; Сохранить параметры + LD (FILESIZE),HL + LD (FILESIZE+2),DE + LD (FILENAME),BC + LD (PANEL_IX),IX + + ; Восстановить TXT режим + CALL GEDPL + + ; Загрузить начало файла + LD HL,BUFFER + LD B,16 ; 16 блоков = 8KB + CALL LOAD512 + + ; Показать окно + LD IX,WINDOW + CALL PRWOW + + ; Вывести текст + LD HL,BUFFER + LD DE,#0101 + LD BC,1000 + CALL PRSRW + +; Цикл просмотра +VIEW_LOOP: + EI + HALT + + CALL ESC_KEY + JR Z,VIEW_LOOP + + ; Выход + LD IX,WINDOW + CALL RRESB + XOR A + RET + +;--------------------------------------- +; WC API +;--------------------------------------- + +LOAD512: LD A,#30 + JP WLD + +PRWOW: LD A,1 + JP WLD + +RRESB: LD A,2 + JP WLD + +PRSRW: LD A,3 + JP WLD + +ESC_KEY: LD A,#17 + JP WLD + +GEDPL: LD A,#0F + JP WLD + +;--------------------------------------- +; Данные +;--------------------------------------- + +WINDOW: + DB 0 + DB 0 + DB 0 ; X=0 + DB 0 ; Y=0 + DB 80 ; На весь экран + DB 25 + DB %01111111 + DB 0 + DW #0000 + DB 0,0 + +BUFFER: DS 8192 ; Буфер для файла + +FILENAME: DW 0 +FILESIZE: DS 4 +PANEL_IX: DW 0 + + END +``` + +## Следующий шаг + +Изучите примеры в каталоге PlugRef/: +- **wild_player/** - полноценный музыкальный плеер +- **gsplayer/** - плеер для GS +- **txt_editor/** - редактор текста + +--- + +## Минимальный плагин на SDCC C + +> Компилятор: SDCC 4.x, ключи `--opt-code-size -mz80 --sdcccall 1` +> Источник соглашений: `src_sdcc/inc/wc_api.h` + +```c +/* + * hello_plugin.c — минимальный WC-плагин на SDCC C + * + * Сборка: + * sdcc --opt-code-size -mz80 --sdcccall 1 --code-loc 0x8000 \ + * --data-loc 0xB800 -I inc hello_plugin.c wc_api.s crt0.s + * makebin -s 16384 hello_plugin.ihx hello_plugin.bin + * pack hello_plugin.bin HELLO.WMF # добавить заголовок WMF + */ + +#include "inc/wc_api.h" +#include "inc/types.h" + +/* ───────────────────────────────────────────────── + * DATA: размещается в #B800+ (--data-loc 0xB800) + * ──────────────────────────────────────────────── */ + +/* Окно в секции DATA (адрес в #8000–#BFFF — обязательно!) */ +static wc_window_t g_win; + +/* Буфер вывода */ +static char g_buf[64]; + +/* + * wc_exit_code — специальная переменная: WC читает A при + * выходе из main(). Значения: 0=ESC, 2=след. файл, 3=reload dir. + */ +uint8_t wc_exit_code; + +/* ───────────────────────────────────────────────── + * CODE: размещается в #8000+ (--code-loc 0x8000) + * ──────────────────────────────────────────────── */ + +int main(void) { + /* 1. Восстановить дисплей WC (TXT-режим, панели) + * ⚠ Затирает #1000–#1447 в окне 0 */ + wc_gedpl(); + + /* 2. Настроить окно */ + g_win.type = WC_WIN_SINGLE; /* однолинейная рамка */ + g_win.cur_mask = 0x07; + g_win.x = 255; /* 255 = по центру */ + g_win.y = 255; + g_win.width = 44; + g_win.height = 6; + g_win.color = WC_COLOR(WC_BLACK, WC_BRIGHT_WHITE); + g_win.buf_addr = 0x0000; /* 0 = автосохранение фона в RESB */ + + /* 3. Нарисовать окно */ + wc_prwow(&g_win); + + /* 4. Вывести текст (разметка WC: 0x0E = центрирование) */ + wc_txtpr(&g_win, "\x0eHello from C plugin!", 1, 1); + wc_txtpr(&g_win, "\x0e\x03Press ESC to exit", 3, 1); + + /* 5. Главный цикл */ + while (1) { + if (wc_key_esc()) break; + if (wc_key_enter()) break; + + /* пример: цикл ожидания клавиши без блокировки */ + /* uint8_t k = wc_kbscn(WC_KBSCN_NORMAL); */ + } + + /* 6. Убрать окно и восстановить дисплей */ + wc_rresb(&g_win); + wc_gedpl(); + + /* 7. Код выхода */ + wc_exit_code = 0; /* 0 = нормальный выход */ + return 0; +} +``` + +### Полезные паттерны + +**Загрузка файла в мегабуфер:** +```c +void load_file_to_megabuf(void) { + uint8_t page = 0; + uint8_t rc; + + wc_stream(WC_STREAM_ROOT); // открыть файловый поток + // wc_fentry() уже вызван WC перед стартом плагина + + do { + /* ⚠ После любого wc_* Win3 сбрасывается — переключаем снова */ + wc_mngcvpl(page); + rc = wc_load512(0xC000, 32); // читать 16 КБ + page++; + } while (rc == 0 && page < 64); + /* rc == WC_EOF (0x0F) = конец файла, rc != 0 && != 0x0F = ошибка */ +} +``` + +**Установка собственного ISR:** +```c +extern void my_isr(void); // ISR написан на ASM в asm/isr.s + +void setup_isr(void) { + wc_int_pl(WC_INT_PLUGIN); // режим плагина + wc_int_pl_handler((uint16_t)my_isr); // задать адрес + /* Теперь my_isr() вызывается ~1367 раз/сек */ +} + +void restore_isr(void) { + wc_int_pl(WC_INT_DISABLE_ALL); // вернуть управление WC +} +``` + +**Переключение CPU на 14 МГц:** +```c +wc_turbopl(WC_TURBO_CPU, 0x02); // 14 МГц +// ...работа... +wc_turbopl(WC_TURBO_RESTORE, 0); // восстановить +``` diff --git a/doc/TODO_streaming_inflate.md b/doc/TODO_streaming_inflate.md new file mode 100644 index 0000000..c5c252a --- /dev/null +++ b/doc/TODO_streaming_inflate.md @@ -0,0 +1,147 @@ +# TODO: Потоковая (streaming) распаковка inflate + +**Дата:** 2026-03-28 +**Статус:** Идея / Анализ завершён, реализация отложена + +## Цель + +Заменить текущую полную предварительную распаковку VGZ → мегабуфер +на потоковую (чанковую) распаковку с прерываниями EI, что даст: + +- Мгновенный старт воспроизведения (не ждать 2–10 сек) +- Снятие ограничения 1 МБ (мегабуфер) на размер raw VGM +- Возможность играть VGZ файлы > 496 КБ (TAP) через кольцевой буфер + +## Ключевые находки + +### 1. ISR не использует Win 0 / Win 1 / Win 3 + +ISR-хэндлер и все данные (cmd_buf_a/b, isr_read_ptr, pos_ptr) живут +исключительно в Win 2 (0x8000–0xBFFF, _CODE + _DATA). Это означает: + +**Win 0, Win 1, Win 3 можно свободно переключать при EI.** + +### 2. Раскладка окон во время inflate-чанка + +| Win | Адреса | Содержимое | Кто использует | +|-----|-----------------|---------------------------|--------------------------| +| 0 | 0x0000–0x3FFF | Source VGZ page (ротация) | inflate: чтение HL' | +| 1 | 0x4000–0x7FFF | Decode buffer (PAGE_DECODE)| inflate: запись DE | +| 2 | 0x8000–0xBFFF | Player code + ISR + DATA | **НЕ ТРОГАЕМ**, ISR тут | +| 3 | 0xC000–0xFFFF | Inflate code + Huffman | inflate: исполнение | + +Win 2 сейчас занят decode buffer (#EA) во время inflate. +Для streaming нужно перенести decode buffer из Win 2 в Win 1. + +### 3. Self-modifying code = бесплатное сохранение состояния + +Все переменные inflate (`src_page`, `dst_page`, `dst`, `interim_dst`, +`this_was_last_block`, Huffman таблицы ~10 КБ) — self-modifying code +на inflate-странице (Win 3). При unmap/remap страницы значения +автоматически сохраняются. Стек (SP = 0xFFF0, адреса возврата +inflate_core → blocktype2 → decode_tableloop) тоже на inflate-странице. + +**Нужно сохранять только CPU-регистры (18 байт):** +- AF, BC, DE, HL (8 байт main regs) +- AF', BC', DE', HL' (8 байт alt regs — bit-stream state!) +- IX (2 байта) +- SP хранится в C-переменной caller + +### 4. DMA вместо LDIR для page flush + +Текущий `copy_decoded_page` (LDIR 16 КБ) = **344064 T** ≈ 49 мс @ 7 МГц. + +DMA RAM→RAM (TSCONF_DMA_RAM_RAM = 0x09): **~8000–16000 T** (20–40× быстрее). +Адресация: 22-bit [page(8)][H(6)][L(7)][0]. +DMA копирует страницу→страницу без участия CPU и без маппинга окон. + +Двойной буфер DMA: пока DMA копирует страницу N, inflate пишет в страницу N+1. +Не нужно ждать флаг TSCONF_DMA_ACT. + +### 5. Backref: DMA для cross-page, LDIR для intra-page + +~80–90% backrefs внутри текущего буфера → LDIR из Win 1 в Win 1. + +Cross-page backrefs (distance > current_offset): DMA из физической +страницы history → физическую страницу decode buffer. Окна не трогаем. + +Исключение: overlapping backrefs (distance < length, паттерн "repeat") +DMA не гарантирует побайтную семантику → fallback: temp remap Win 0 = history, +LDIR, restore Win 0 = src. + +### 6. Overflow 258 байт — решение для Win 1 + +Если decode buffer в Win 1 (0x4000–0x7FFF), overflow LDIR пойдёт +в Win 2 (player code). **Нельзя.** + +Решение: split-copy перед LDIR. Проверить DE + BC > 0x8000? +Если да — LDIR до границы, DMA flush, сброс DE = 0x4000, LDIR остаток. +Overhead: ~30 T на backref (проверка). + +### 7. Yield point: decode_tableloop + +Единственная разумная точка yield — вершина `decode_tableloop`. +Добавить yield_counter (self-mod var): dec → jp z, inflate_yield. +Caller задаёт количество 8-символьных батчей за чанк. + +### 8. Оценка бюджета + +| Параметр | Значение | +|----------------------------------|--------------------------| +| Потребление VGM (типичный) | ~2.5 КБ/сек (50 б/кадр) | +| Скорость inflate @ 7 МГц | ~50–100 КБ/сек | +| Нужный duty cycle | **2.5–5% CPU** | +| Overhead контекста (вход+выход) | ~600 T / 71680 T = 0.8% | +| Запас | 10–20× | + +### 9. Кольцевой буфер выхода + +``` +Megabuffer pages: [P0][P1][P2][P3][P4][P5][P6][P7] ... ring + +write_ptr → inflate пишет (producer) +read_ptr → player читает (consumer) +``` + +- Старт воспроизведения после первых 2–4 готовых страниц +- Нет ограничения 1 МБ: ring buffer кольцуется +- Max VGM raw: 496 КБ × 3 (сжатие) ≈ 1.5 МБ + +## Контекст переключения (pseudo-code) + +```asm +inflate_enter: + ; Save Win 0/1/3 page numbers (from C vars or sys vars) + ; OUT #10AF = src_page ; Win 0 = source VGZ + ; OUT #11AF = PAGE_DECODE ; Win 1 = decode buffer + ; OUT #13AF = inflate_page ; Win 3 = inflate code + ; Restore saved CPU regs (18 bytes from save area on inflate page) + ; JP resume_point + +inflate_yield: + ; Save CPU regs to save area on inflate page + ; ld sp, (caller_sp) + ; Restore Win 0/1/3 from saved values + ; ret to main loop +``` + +## План реализации + +1. [ ] Перенести decode buffer из Win 2 в Win 1 +2. [ ] Добавить split-copy проверку для LDIR overflow в Win 1 +3. [ ] Реализовать yield/resume в decode_tableloop +4. [ ] Заменить LDIR page flush на DMA +5. [ ] Реализовать DMA backref для cross-page +6. [ ] Добавить кольцевой буфер (producer/consumer) в main.c +7. [ ] Добавить inflate_enter / inflate_yield в inflate_call.s +8. [ ] Тестирование с файлами разных размеров и степеней сжатия + +## Риски + +- makeHuffmanTable: может занять десятки тысяч T-states (непредсказуемо). + При yield внутри него нужен глубокий стек save. Альтернатива: + не yield'ить во время построения таблиц, только во время decode. +- DMA overlap с ISR: ISR не использует DMA, конфликтов нет. +- Первый блок может быть dynamic Huffman → долгое построение таблиц перед + первыми декодированными данными. Mitigation: буферизовать 2–4 страницы + перед стартом воспроизведения. diff --git a/old_asm_src/build.bat b/old_asm_src/build.bat new file mode 100644 index 0000000..77b0315 --- /dev/null +++ b/old_asm_src/build.bat @@ -0,0 +1,32 @@ +@echo off +REM Build script for VGM Player plugin +REM Requires sjasm assembler in tools/sjasm directory + +echo Building VGM Player plugin... + +REM Check if sjasm exists +if not exist "tools\sjasm\sjasmplus.exe" ( + echo Error: sjasmplus.exe not found in tools\sjasm\ + echo Please install sjasm assembler + exit /b 1 +) + +REM Compile the plugin +"tools\sjasm\sjasmplus.exe" src\vgmplayer.asm + +if errorlevel 1 ( + echo. + echo Compilation failed! + exit /b 1 +) + +echo. +echo Build successful! +echo Output: vgmplay.wmf +echo. + +REM Create disk image +echo Creating disk image... +call create_disk.bat + +exit /b 0 diff --git a/old_asm_src/create_disk.bat b/old_asm_src/create_disk.bat new file mode 100644 index 0000000..ba17d84 --- /dev/null +++ b/old_asm_src/create_disk.bat @@ -0,0 +1,52 @@ +@echo off +REM Create disk image with VGM Player plugin for TSConfig/WC +REM Image size: 512MB = 524288 KB + +echo Creating TSConfig disk image with VGM Player... +echo. + +REM Check if plugin was built +if not exist "vgmplay.wmf" ( + echo Error: vgmplay.wmf not found! + echo Please run build.bat first. + exit /b 1 +) + +REM Step 1: Copy plugin to DiskRef\WC directory +echo Step 1: Copying plugin to DiskRef\WC... +copy /Y vgmplay.wmf DiskRef\WC\VGMPLAY.WMF +if errorlevel 1 ( + echo Error: Failed to copy plugin + exit /b 1 +) +echo Done: vgmplay.wmf copied to DiskRef\WC\ + +REM Step 2: Create disk image +echo. +echo Step 2: Creating 512MB disk image... +echo Using robimg.exe to create tsconf.img +del tsconf.img 2>nul + +tools\robimg.exe -p="tsconf.img" -s=524288 -C="DiskRef" + +if errorlevel 1 ( + echo Error: Failed to create disk image + exit /b 1 +) + +echo. +echo ======================================== +echo Disk image created successfully! +echo ======================================== +echo. +echo Output file: tsconf.img (512MB) +echo Contains: DiskRef contents + VGM Player plugin +echo. +echo To use: +echo 1. Mount tsconf.img in your TSConfig emulator +echo 2. Boot Wild Commander +echo 3. Navigate to WC directory +echo 4. Find VGMPLAY.WMF and press Enter to load +echo 5. Select a .VGM file to play +echo. + diff --git a/old_asm_src/isr.asm b/old_asm_src/isr.asm new file mode 100644 index 0000000..f9404ab --- /dev/null +++ b/old_asm_src/isr.asm @@ -0,0 +1,224 @@ +; ═══════════════════════════════════════════════════════════════════════════ +; Модуль ISR - 2734 Гц, обработчик прерывания +; ═══════════════════════════════════════════════════════════════════════════ +; +; Архитектура: ВСЕ значения для регистров INT предрассчитаны в таблице. +; ISR читает 3 байта через 3×OUTI — НИКАКИХ вычислений! +; +; Частота: 3,500,000 / 1280 = 2734.375 Hz (~44100/16, разница ~0.8%) +; Цикл: 56 тиков × 1280 = 71680 позиций = 1 видеокадр → ИДЕАЛЬНОЕ замыкание +; Таблица: 56 × 5 = 280 байт +; +; При 14 МГц с wait-state (~x2.5): ~3200 эффективных тактов между тиками. +; +; Коды команд: +; CMD_WRITE_B0 = #40 [#40, reg, val, pad] → OUT OPL3_B0_ADDR/DATA +; CMD_WRITE_B1 = #80 [#80, reg, val, pad] → OUT OPL3_B1_ADDR/DATA +; CMD_WAIT = #C0 [#C0, lo, hi, pad] → ISR_WAIT_CTR = HL +; CMD_END_BUF = #E0 [#E0, pad,pad, pad] → переключить буфер +; ═══════════════════════════════════════════════════════════════════════════ + +; Коды команд (= смещение от ISR_HANDLER, определены в vgmplayer.asm) +; CMD_WRITE_B0 = #40, CMD_WRITE_B1 = #80, CMD_WAIT = #C0, CMD_END_BUF = #E0 + +; ═══════════════════════════════════════════════════════════════════════════ +; ISR_INIT - Инициализация таблицы векторов IM2 и указателя позиции +; ═══════════════════════════════════════════════════════════════════════════ +ISR_INIT: + DI + + ; --- Заполнить таблицу векторов IM2 (257 байт, выровнено по 256) --- + LD HL,IM2_TABLE + LD DE,IM2_TABLE+1 + LD A,LOW ISR_HANDLER + LD (HL),A + LD BC,256 + LDIR + LD HL,IM2_TABLE+#100 + LD (HL),HIGH ISR_HANDLER + + ; Установить регистр I + LD A,HIGH IM2_TABLE + LD I,A + + ; --- Разрешить только прерывание FRAME --- + LD BC,PORT_INTMASK + LD A,%00000001 ; только FRAME + OUT (C),A + + ; --- Инициализировать указатель позиции --- + LD HL,POS_TABLE + LD (POS_PTR),HL ; храним в переменной + + ; --- Инициализировать счётчик ожидания --- + LD HL,0 + LD (ISR_WAIT_CTR),HL + + ; --- Записать начальную позицию в железо: строка 0, hpos 0 --- + LD BC,PORT_HSINT + XOR A + OUT (C),A ; HSINT = 0 + LD B,HIGH PORT_VSINTL + OUT (C),A ; VSINTL = 0 + LD B,HIGH PORT_VSINTH + OUT (C),A ; VSINTH = 0 + + ; Переключиться в IM2 + IM 2 + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; ISR_HANDLER - прерывание FRAME (2734 Гц) +; ─── OUTI-блок и таблицу не трогать! ──────────────────────────────────── +; Команды: CMD_WRITE_B0=#40, CMD_WRITE_B1=#80, CMD_WAIT=#C0, CMD_END_BUF=#E0 +; ═══════════════════════════════════════════════════════════════════════════ +ISR_HANDLER: + PUSH AF + PUSH HL + PUSH BC + PUSH DE + + ; ─── OUTI×3: вывести позицию INT из таблицы (не трогать!) ─────── + LD HL,(POS_PTR) + LD BC,#25AF ; B=#25, C=#AF + OUTI ; B→#24, OUT #24AF,(HL++) → VSINTH + OUTI ; B→#23, OUT #23AF,(HL++) → VSINTL + OUTI ; B→#22, OUT #22AF,(HL++) → HSINT + LD A,(HL) ; next_lo + INC HL + LD H,(HL) ; next_hi + LD L,A ; HL = next_ptr + LD (POS_PTR),HL + + ; ─── SECOND_TIMER: счётчик секунд (ВСЕГДА, независимо от WAIT_CTR) ── + LD HL,(SECOND_TIMER) + DEC HL + LD A,H + OR L + LD (SECOND_TIMER),HL + JR NZ,ISR_NO_SEC + ; Секунда истекла + LD HL,ISR_FREQ + LD (SECOND_TIMER),HL + LD HL,(PLAY_SECONDS) + INC HL + LD (PLAY_SECONDS),HL +ISR_NO_SEC: + + ; ─── WAIT_CTR: если 0 — идти выполнять команды ────────────────── + LD HL,(ISR_WAIT_CTR) + LD A,H + OR L + JP Z,ISR_EXEC_CMD + DEC HL + LD (ISR_WAIT_CTR),HL + +ISR_EXIT: + XOR A + OUT (#FE),A ; DEBUG: чёрный = выход из ISR + POP DE + POP BC + POP HL + POP AF + EI + RET + +; ─── Exec-путь ─────────────────────────────────────────────────────────── +ISR_EXEC_CMD: + LD A,(ISR_ENABLED) + OR A + JP Z,ISR_EXIT + LD HL,(ISR_READ_PTR) + EX DE,HL ; DE = указатель потока команд + +ISR_CMD_LOOP: + LD A,(DE) + INC DE + CP CMD_WRITE_B0 ; #40 + JP Z,ISR_WRITE_B0 + CP CMD_WRITE_B1 ; #80 + JP Z,ISR_WRITE_B1 + CP CMD_WAIT ; #C0 + JP Z,ISR_DO_WAIT + ; CMD_END_BUF (#E0) — любое другое значение тоже сюда +ISR_DO_END_BUF: + LD A,2 + OUT (#FE),A ; DEBUG: красный = смена буфера + LD A,(ISR_ACTIVE_BUF) + XOR 1 + LD (ISR_ACTIVE_BUF),A + JR NZ,ISR_END_USE_B + LD DE,CMD_BUF_A + JP ISR_CMD_LOOP ; продолжать в новом буфере! +ISR_END_USE_B: + LD DE,CMD_BUF_B + JP ISR_CMD_LOOP ; продолжать в новом буфере! + +ISR_WRITE_B0: + LD A,6 + OUT (#FE),A ; DEBUG: жёлтый = запись OPL3 + LD A,(DE) ; reg + LD BC,OPL3_ADDR0 ; B=0, C=#C4 → полный адрес порта #00C4 + OUT (C),A + INC DE + LD A,(DE) ; val + LD BC,OPL3_DATA0 ; B=0, C=#C5 → полный адрес порта #00C5 + OUT (C),A + INC DE + INC DE ; pad + JP ISR_CMD_LOOP + +ISR_WRITE_B1: + LD A,6 + OUT (#FE),A ; DEBUG: жёлтый = запись OPL3 + LD A,(DE) ; reg + LD BC,OPL3_ADDR1 ; B=0, C=#C6 → полный адрес порта #00C6 + OUT (C),A + INC DE + LD A,(DE) ; val + LD BC,OPL3_DATA1 ; B=0, C=#C7 → полный адрес порта #00C7 + OUT (C),A + INC DE + INC DE ; pad + JP ISR_CMD_LOOP + +ISR_DO_WAIT: + LD A,(DE) ; wait_lo + INC DE + LD L,A + LD A,(DE) ; wait_hi + INC DE + LD H,A ; HL = wait counter + INC DE ; pad + LD (ISR_WAIT_CTR),HL + EX DE,HL + LD (ISR_READ_PTR),HL ; сохранить позицию потока + JP ISR_EXIT + +; ─── Переменные ISR ────────────────────────────────────────────────────── +POS_PTR: DW POS_TABLE ; указатель на текущую запись в таблице +SECOND_TIMER: DW ISR_FREQ ; обратный счётчик 2734 → 0 = 1 секунда + + +; ═══════════════════════════════════════════════════════════════════════════ +; Таблица векторов IM2 (выравнивание по 256 байт, размер 257 байт) +; ═══════════════════════════════════════════════════════════════════════════ + ALIGN 256 +IM2_TABLE: + DS 257 + +; ═══════════════════════════════════════════════════════════════════════════ +; Командные буферы (двойная буферизация) +; ═══════════════════════════════════════════════════════════════════════════ + ALIGN 4 +CMD_BUF_A: + DS CMD_BUF_SIZE +CMD_BUF_B: + DS CMD_BUF_SIZE + +; ═══════════════════════════════════════════════════════════════════════════ +; Таблица позиций - 2734 Гц (шаг=1280, 56 записей, чистый цикл) +; Формат: [VSINTH, VSINTL, HSINT] для последовательности 3×OUTI +; Размер: 280 байт +; ═══════════════════════════════════════════════════════════════════════════ + INCLUDE "src/pos_table_43750.inc" diff --git a/old_asm_src/keyboard.asm b/old_asm_src/keyboard.asm new file mode 100644 index 0000000..72af2ba --- /dev/null +++ b/old_asm_src/keyboard.asm @@ -0,0 +1,109 @@ +; ═══════════════════════════════════════════════════════════════════════════ +; Keyboard Module - Direct ZX Spectrum matrix reading +; ═══════════════════════════════════════════════════════════════════════════ +; Reads keyboard via IN port #FE (no WC API, no interrupts needed). +; TSConfig translates PS/2 → ZX matrix in hardware. +; +; ZX Spectrum keyboard matrix (active low): +; Port #FEFE: CAPS, Z, X, C, V (row 0) +; Port #FDFE: A, S, D, F, G (row 1) +; Port #FBFE: Q, W, E, R, T (row 2) +; Port #F7FE: 1, 2, 3, 4, 5 (row 3) +; Port #EFFE: 0, 9, 8, 7, 6 (row 4) +; Port #DFFE: P, O, I, U, Y (row 5) +; Port #BFFE: ENTER, L, K, J, H (row 6) +; Port #7FFE: SPACE, SYM, M, N, B (row 7) +; +; Each row: bit 0 = rightmost key, bit 4 = leftmost key +; Bit = 0 means key pressed, bit = 1 means not pressed +; ═══════════════════════════════════════════════════════════════════════════ + +; Debounce counter (simple frame-based debouncing) +KBD_DEBOUNCE: DB 0 + +; ═══════════════════════════════════════════════════════════════════════════ +; KBD_READ - Read keyboard and return action code +; ═══════════════════════════════════════════════════════════════════════════ +; Output: A = action code +; 0 = nothing pressed +; 1 = ESC (Caps Shift + Space) +; 2 = next track (Space alone) +; 3 = prev track (P key) +; 4 = pause/unpause (M key) +; ═══════════════════════════════════════════════════════════════════════════ +KBD_READ: + ; Simple debouncing: skip reads for N frames after a keypress + LD A,(KBD_DEBOUNCE) + OR A + JR Z,KBD_DO_READ + DEC A + LD (KBD_DEBOUNCE),A + XOR A ; return 0 + RET + +KBD_DO_READ: + ; --- Check ESC = Caps Shift + Space --- + ; Caps Shift = row 0 bit 0 (#FEFE) + LD A,#FE + IN A,(#FE) + BIT 0,A ; Caps Shift + JR NZ,KBD_CHECK_SPACE ; not pressed + + ; Caps Shift is held. Check Space = row 7 bit 0 (#7FFE) + LD A,#7F + IN A,(#FE) + BIT 0,A ; Space + JR NZ,KBD_CHECK_SPACE ; not pressed + ; ESC detected! + LD A,16 + LD (KBD_DEBOUNCE),A + LD A,1 + RET + +KBD_CHECK_SPACE: + ; --- Check SPACE alone (without Caps Shift) --- + ; Space = row 7 bit 0 (#7FFE) + LD A,#7F + IN A,(#FE) + BIT 0,A + JR NZ,KBD_CHECK_P + + ; Check that Caps Shift is NOT held (to distinguish from ESC) + LD A,#FE + IN A,(#FE) + BIT 0,A ; CShift=0 means held + JR Z,KBD_CHECK_P ; CShift held → this is ESC, not SPACE + + ; SPACE alone = next track + LD A,12 + LD (KBD_DEBOUNCE),A + LD A,2 + RET + +KBD_CHECK_P: + ; --- Check P key = row 5 bit 0 (#DFFE) --- + LD A,#DF + IN A,(#FE) + BIT 0,A + JR NZ,KBD_CHECK_M + ; P pressed = previous track + LD A,12 + LD (KBD_DEBOUNCE),A + LD A,3 + RET + +KBD_CHECK_M: + ; --- Check M key = row 7 bit 2 (#7FFE) --- + LD A,#7F + IN A,(#FE) + BIT 2,A + JR NZ,KBD_NOTHING + ; M pressed = pause + LD A,12 + LD (KBD_DEBOUNCE),A + LD A,4 + RET + +KBD_NOTHING: + XOR A + RET diff --git a/old_asm_src/opl3.asm b/old_asm_src/opl3.asm new file mode 100644 index 0000000..04a0ceb --- /dev/null +++ b/old_asm_src/opl3.asm @@ -0,0 +1,206 @@ +; ═══════════════════════════════════════════════════════════════════════════ +; OPL3 (YMF262) Hardware Driver - Simplified +; ═══════════════════════════════════════════════════════════════════════════ +; +; OPL3_INIT - Detect and initialize OPL3 +; OPL3_RESET - Silence all channels +; +; Ports: #C4/#C5 = bank 0 addr/data, #C6/#C7 = bank 1 addr/data +; ═══════════════════════════════════════════════════════════════════════════ + +; ═══════════════════════════════════════════════════════════════════════════ +; OPL3_INIT - Detect OPL3 and enable OPL3 mode +; ═══════════════════════════════════════════════════════════════════════════ +; Output: OPL3_FOUND = 1 if found, 0 if not +OPL3_INIT: + PUSH AF + PUSH BC + PUSH DE + + ; --- Detect OPL3 using timer method --- + ; 1. Reset timers + LD BC,OPL3_ADDR0 + LD A,#04 ; Timer control register + OUT (C),A + CALL OPL3_DELAY_SHORT + + LD BC,OPL3_DATA0 + LD A,#60 ; Reset T1 and T2 flags + OUT (C),A + CALL OPL3_DELAY_LONG + + ; 2. Read status - should be 0 + LD BC,OPL3_ADDR0 + IN A,(C) + AND #E0 + JR NZ,OPL3_NOT_FOUND + + ; 3. Start timer 1 with max value + LD BC,OPL3_ADDR0 + LD A,#02 ; Timer 1 register + OUT (C),A + CALL OPL3_DELAY_SHORT + + LD BC,OPL3_DATA0 + LD A,#FF + OUT (C),A + CALL OPL3_DELAY_LONG + + LD BC,OPL3_ADDR0 + LD A,#04 + OUT (C),A + CALL OPL3_DELAY_SHORT + + LD BC,OPL3_DATA0 + LD A,#21 ; Start timer 1, unmask + OUT (C),A + + ; 4. Wait for timer to overflow (~100 µs) + LD B,200 ; generous delay at 14 MHz +OPL3_DET_WAIT: + DJNZ OPL3_DET_WAIT + + ; 5. Read status + LD BC,OPL3_ADDR0 + IN A,(C) + AND #E0 + JR Z,OPL3_NOT_FOUND ; timer didnt fire = no OPL + + ; 6. Reset timers again + LD BC,OPL3_ADDR0 + LD A,#04 + OUT (C),A + CALL OPL3_DELAY_SHORT + + LD BC,OPL3_DATA0 + LD A,#60 + OUT (C),A + CALL OPL3_DELAY_LONG + + ; --- OPL3 Found! --- + LD A,1 + LD (OPL3_FOUND),A + + ; Enable OPL3 mode (register 0x105 = bit 0) + LD BC,OPL3_ADDR1 + LD A,#05 + OUT (C),A + CALL OPL3_DELAY_SHORT + + LD BC,OPL3_DATA1 + LD A,#01 + OUT (C),A + CALL OPL3_DELAY_LONG + + ; Waveform select enable (register 0x01 = 0x20) + LD BC,OPL3_ADDR0 + LD A,#01 + OUT (C),A + CALL OPL3_DELAY_SHORT + + LD BC,OPL3_DATA0 + LD A,#20 + OUT (C),A + CALL OPL3_DELAY_LONG + + POP DE + POP BC + POP AF + RET + +OPL3_NOT_FOUND: + XOR A + LD (OPL3_FOUND),A + POP DE + POP BC + POP AF + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; OPL3_RESET - Silence all OPL3 channels +; ═══════════════════════════════════════════════════════════════════════════ +OPL3_RESET: + PUSH AF + PUSH BC + PUSH DE + + LD A,(OPL3_FOUND) + OR A + JR Z,OPL3_RESET_DONE + + ; Write 0 to all registers in both banks + ; Bank 0: registers #20-#F5 + LD D,#20 ; start register +OPL3_RESET_BANK0: + LD BC,OPL3_ADDR0 + LD A,D + OUT (C),A + CALL OPL3_DELAY_SHORT + + LD BC,OPL3_DATA0 + XOR A + OUT (C),A + CALL OPL3_DELAY_LONG + + INC D + LD A,D + CP #F6 + JR C,OPL3_RESET_BANK0 + + ; Bank 1: registers #20-#F5 + LD D,#20 +OPL3_RESET_BANK1: + LD BC,OPL3_ADDR1 + LD A,D + OUT (C),A + CALL OPL3_DELAY_SHORT + + LD BC,OPL3_DATA1 + XOR A + OUT (C),A + CALL OPL3_DELAY_LONG + + INC D + LD A,D + CP #F6 + JR C,OPL3_RESET_BANK1 + + ; Disable OPL3 mode on exit + LD BC,OPL3_ADDR1 + LD A,#05 + OUT (C),A + CALL OPL3_DELAY_SHORT + LD BC,OPL3_DATA1 + XOR A + OUT (C),A + CALL OPL3_DELAY_LONG + +OPL3_RESET_DONE: + POP DE + POP BC + POP AF + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; OPL3 Delays +; ═══════════════════════════════════════════════════════════════════════════ +; At 14 MHz: ~3.3 µs for address = 46 cycles, ~22 µs for data = 308 cycles +; DJNZ = 13 cycles per iteration at 14 MHz (or 8+5) + +OPL3_DELAY_SHORT: + ; ~3.5 µs at 14 MHz = ~49 cycles + PUSH BC + LD B,5 +OPL3_DS_LOOP: + DJNZ OPL3_DS_LOOP + POP BC + RET + +OPL3_DELAY_LONG: + ; ~22 µs at 14 MHz = ~308 cycles + PUSH BC + LD B,25 +OPL3_DL_LOOP: + DJNZ OPL3_DL_LOOP + POP BC + RET diff --git a/old_asm_src/pos_table_43750.inc b/old_asm_src/pos_table_43750.inc new file mode 100644 index 0000000..0312989 --- /dev/null +++ b/old_asm_src/pos_table_43750.inc @@ -0,0 +1,66 @@ +; Position table for 2734 Hz (step=1280, 56 entries, perfect 1-frame cycle) +; Format: [VSINTH, VSINTL, HSINT, next_lo, next_hi] - 5 bytes per entry +; Frequency: 3,500,000 / 1280 = 2734.375 Hz (~44100/16) +; At 14MHz with wait-states: ~3200 effective T-states between interrupts + +POS_TABLE_ENTRIES EQU 56 +ISR_FREQ EQU 2734 ; used for SECOND_TIMER countdown + +POS_TABLE: + DB #00, #00, #00 : DW $+2 ; [ 0] line= 0, hpos= 0 + DB #00, #05, #A0 : DW $+2 ; [ 1] line= 5, hpos=160 + DB #00, #0B, #60 : DW $+2 ; [ 2] line= 11, hpos= 96 + DB #00, #11, #20 : DW $+2 ; [ 3] line= 17, hpos= 32 + DB #00, #16, #C0 : DW $+2 ; [ 4] line= 22, hpos=192 + DB #00, #1C, #80 : DW $+2 ; [ 5] line= 28, hpos=128 + DB #00, #22, #40 : DW $+2 ; [ 6] line= 34, hpos= 64 + DB #00, #28, #00 : DW $+2 ; [ 7] line= 40, hpos= 0 + DB #00, #2D, #A0 : DW $+2 ; [ 8] line= 45, hpos=160 + DB #00, #33, #60 : DW $+2 ; [ 9] line= 51, hpos= 96 + DB #00, #39, #20 : DW $+2 ; [ 10] line= 57, hpos= 32 + DB #00, #3E, #C0 : DW $+2 ; [ 11] line= 62, hpos=192 + DB #00, #44, #80 : DW $+2 ; [ 12] line= 68, hpos=128 + DB #00, #4A, #40 : DW $+2 ; [ 13] line= 74, hpos= 64 + DB #00, #50, #00 : DW $+2 ; [ 14] line= 80, hpos= 0 + DB #00, #55, #A0 : DW $+2 ; [ 15] line= 85, hpos=160 + DB #00, #5B, #60 : DW $+2 ; [ 16] line= 91, hpos= 96 + DB #00, #61, #20 : DW $+2 ; [ 17] line= 97, hpos= 32 + DB #00, #66, #C0 : DW $+2 ; [ 18] line=102, hpos=192 + DB #00, #6C, #80 : DW $+2 ; [ 19] line=108, hpos=128 + DB #00, #72, #40 : DW $+2 ; [ 20] line=114, hpos= 64 + DB #00, #78, #00 : DW $+2 ; [ 21] line=120, hpos= 0 + DB #00, #7D, #A0 : DW $+2 ; [ 22] line=125, hpos=160 + DB #00, #83, #60 : DW $+2 ; [ 23] line=131, hpos= 96 + DB #00, #89, #20 : DW $+2 ; [ 24] line=137, hpos= 32 + DB #00, #8E, #C0 : DW $+2 ; [ 25] line=142, hpos=192 + DB #00, #94, #80 : DW $+2 ; [ 26] line=148, hpos=128 + DB #00, #9A, #40 : DW $+2 ; [ 27] line=154, hpos= 64 + DB #00, #A0, #00 : DW $+2 ; [ 28] line=160, hpos= 0 + DB #00, #A5, #A0 : DW $+2 ; [ 29] line=165, hpos=160 + DB #00, #AB, #60 : DW $+2 ; [ 30] line=171, hpos= 96 + DB #00, #B1, #20 : DW $+2 ; [ 31] line=177, hpos= 32 + DB #00, #B6, #C0 : DW $+2 ; [ 32] line=182, hpos=192 + DB #00, #BC, #80 : DW $+2 ; [ 33] line=188, hpos=128 + DB #00, #C2, #40 : DW $+2 ; [ 34] line=194, hpos= 64 + DB #00, #C8, #00 : DW $+2 ; [ 35] line=200, hpos= 0 + DB #00, #CD, #A0 : DW $+2 ; [ 36] line=205, hpos=160 + DB #00, #D3, #60 : DW $+2 ; [ 37] line=211, hpos= 96 + DB #00, #D9, #20 : DW $+2 ; [ 38] line=217, hpos= 32 + DB #00, #DE, #C0 : DW $+2 ; [ 39] line=222, hpos=192 + DB #00, #E4, #80 : DW $+2 ; [ 40] line=228, hpos=128 + DB #00, #EA, #40 : DW $+2 ; [ 41] line=234, hpos= 64 + DB #00, #F0, #00 : DW $+2 ; [ 42] line=240, hpos= 0 + DB #00, #F5, #A0 : DW $+2 ; [ 43] line=245, hpos=160 + DB #00, #FB, #60 : DW $+2 ; [ 44] line=251, hpos= 96 + DB #01, #01, #20 : DW $+2 ; [ 45] line=257, hpos= 32 + DB #01, #06, #C0 : DW $+2 ; [ 46] line=262, hpos=192 + DB #01, #0C, #80 : DW $+2 ; [ 47] line=268, hpos=128 + DB #01, #12, #40 : DW $+2 ; [ 48] line=274, hpos= 64 + DB #01, #18, #00 : DW $+2 ; [ 49] line=280, hpos= 0 + DB #01, #1D, #A0 : DW $+2 ; [ 50] line=285, hpos=160 + DB #01, #23, #60 : DW $+2 ; [ 51] line=291, hpos= 96 + DB #01, #29, #20 : DW $+2 ; [ 52] line=297, hpos= 32 + DB #01, #2E, #C0 : DW $+2 ; [ 53] line=302, hpos=192 + DB #01, #34, #80 : DW $+2 ; [ 54] line=308, hpos=128 + DB #01, #3A, #40 : DW POS_TABLE ; [ 55] line=314, hpos= 64 +POS_TABLE_END: diff --git a/old_asm_src/ui.asm b/old_asm_src/ui.asm new file mode 100644 index 0000000..be6ce96 --- /dev/null +++ b/old_asm_src/ui.asm @@ -0,0 +1,213 @@ +; ═══════════════════════════════════════════════════════════════════════════ +; Модуль UI - текстовый интерфейс плеера +; ═══════════════════════════════════════════════════════════════════════════ +; Использует WC API для рисования окна. +; +; Обязательный порядок при старте плагина: +; 1. CALL GEDPL ; восстановить дисплей/палитру WC (обязательно!) +; 2. CALL UI_INIT ; нарисовать окно (IX = PLWND) +; 3. CALL UI_DRAW_INFO ; заполнить содержимое +; +; Все оконные функции (PRWOW, RRESB, TXTPR) требуют IX = SOW (PLWND) +; ═══════════════════════════════════════════════════════════════════════════ + +; ─── Структура окна SOW (Structure Of Window) ───────────────────────────── +; Должна лежать в #8000-#BFFF или #0000-#3FFF. +; +8 (DW) = 0 при первом вызове PRWOW, WC заполнит адресом буфера. +PLWND: + DB 0 ; +0: тип 0 = стандартное окно с рамкой + DB 0 ; +1: маска цвета курсора + DB WND_X ; +2: X позиция на экране + DB WND_Y ; +3: Y позиция на экране + DB WND_W ; +4: ширина окна + DB WND_H ; +5: высота окна + DB CLR_MAIN ; +6: цвет (paper+ink) + DB 0 ; +7: резерв (всегда 0!) + DW 0 ; +8,+9: буфер (0 = не выведено, WC заполнит) + DB 0 ; +10: разделитель 1 (0 = нет) + DB 0 ; +11: разделитель 2 (0 = нет) + +; ═══════════════════════════════════════════════════════════════════════════ +; UI_INIT - Вывести окно плеера на экран +; ═══════════════════════════════════════════════════════════════════════════ +UI_INIT: + PUSH AF + PUSH BC + PUSH DE + PUSH HL + + LD IX,PLWND ; IX = SOW (обязательно для PRWOW!) + CALL PRWOW ; нарисовать окно + + POP HL + POP DE + POP BC + POP AF + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; UI_DRAW_INFO - Нарисовать информацию о файле в окне +; ═══════════════════════════════════════════════════════════════════════════ +UI_DRAW_INFO: + PUSH AF + PUSH BC + PUSH DE + PUSH HL + + ; Заголовок плеера + LD IX,PLWND ; IX = SOW + LD D,1 ; Y = 1 (внутри окна) + LD E,2 ; X = 2 (внутри окна) + LD HL,STR_TITLE + CALL TXTPR + + ; Строка подсказки клавиш + LD IX,PLWND + LD D,2 + LD E,2 + LD HL,STR_KEYS + CALL TXTPR + + ; Статус (Playing/Paused) + CALL UI_DRAW_STATUS + + POP HL + POP DE + POP BC + POP AF + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; UI_DRAW_STATUS - Показать статус воспроизведения +; ═══════════════════════════════════════════════════════════════════════════ +UI_DRAW_STATUS: + PUSH AF + PUSH BC + PUSH DE + PUSH HL + + LD A,(PAUSED) + OR A + JR NZ,UI_SHOW_PAUSED + LD HL,STR_PLAYING + JR UI_SHOW_STATUS +UI_SHOW_PAUSED: + LD HL,STR_PAUSED +UI_SHOW_STATUS: + LD IX,PLWND ; IX = SOW + LD D,4 ; Y = 4 (внутри окна) + LD E,2 + CALL TXTPR + + POP HL + POP DE + POP BC + POP AF + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; UI_UPDATE - Периодическое обновление (вызывается из главного цикла) +; ═══════════════════════════════════════════════════════════════════════════ +UI_UPDATE: + PUSH AF + PUSH BC + PUSH DE + PUSH HL + + ; --- Вычислить MM:SS из PLAY_SECONDS --- + LD HL,(PLAY_SECONDS) + LD B,0 ; B = minutes +UI_DIV60: + LD A,H + OR L + JR Z,UI_DIV_DONE ; HL=0 → B=min, HL=0=sec + LD DE,-60 ; 0xFFC4 + ADD HL,DE ; HL -= 60; carry set ← HL_orig >= 60 + JR NC,UI_DIV_UNDO ; нет carry → HL_orig < 60 → восстановить + INC B ; засчитать минуту + JR UI_DIV60 +UI_DIV_UNDO: + LD DE,60 + ADD HL,DE ; восстановить HL = остаток (секунды) +UI_DIV_DONE: + LD C,L ; C = секунды (0-59) + + ; --- Записать MM в TIME_BUF+6,7 --- + LD A,B + LD HL,TIME_BUF+6 + CALL UI_BYTE2DEC + + ; --- Записать SS в TIME_BUF+9,10 --- + LD A,C + LD HL,TIME_BUF+9 + CALL UI_BYTE2DEC + + ; --- Отобразить --- + LD IX,PLWND + LD D,6 + LD E,2 + LD HL,TIME_BUF + CALL TXTPR + + POP HL + POP DE + POP BC + POP AF + RET + +; UI_BYTE2DEC: конвертировать A (0-99) в 2 ASCII-цифры по адресу (HL) +; Меняет: A, F +UI_BYTE2DEC: + LD B,0 +UI_B2D_L: + CP 10 + JR C,UI_B2D_END + SUB 10 + INC B + JR UI_B2D_L +UI_B2D_END: + ; B=десятки, A=единицы + PUSH AF + LD A,B + ADD A,'0' + LD (HL),A + INC HL + POP AF + ADD A,'0' + LD (HL),A + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; UI_REMOVE - Закрыть окно плеера (восстановить экран) +; ═══════════════════════════════════════════════════════════════════════════ +UI_REMOVE: + PUSH AF + PUSH BC + PUSH DE + PUSH HL + + LD IX,PLWND ; IX = SOW (обязательно для RRESB!) + CALL RRESB ; восстановить экран под окном + + POP HL + POP DE + POP BC + POP AF + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; SHOW_ERROR / SHOW_WARNING - Заглушка (не реализовано) +; ═══════════════════════════════════════════════════════════════════════════ +SHOW_ERROR: +SHOW_WARNING: + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; Строки UI (завершаются нулём — формат WC TXTPR) +; ═══════════════════════════════════════════════════════════════════════════ +STR_TITLE: DB "VGM Player v2.0 (OPL3)",0 +STR_KEYS: DB "ESC-Exit SPC-Next P-Prev M-Pause",0 +STR_PLAYING: DB "Playing... ",0 +STR_PAUSED: DB "** PAUSED ** ",0 +; TIME_BUF: динамический буфер для отображения времени (изменяется в UI_UPDATE) +TIME_BUF: DB "Time: 00:00",0 diff --git a/old_asm_src/vgm.asm b/old_asm_src/vgm.asm new file mode 100644 index 0000000..f2880f0 --- /dev/null +++ b/old_asm_src/vgm.asm @@ -0,0 +1,615 @@ +; ═══════════════════════════════════════════════════════════════════════════ +; VGM Parser & Buffer Filler +; ═══════════════════════════════════════════════════════════════════════════ +; +; - VGM_PARSE_HEADER: Parse VGM file header from VGMBUF +; - VGM_FILL_BUFFER: Fill a command buffer (A=0 for BUF_A, A=1 for BUF_B) +; Reads VGM stream, converts to ISR commands +; +; VGM opcodes processed: +; 0x5E rr dd - YMF262 port 0 write (addr=rr, data=dd) +; 0x5F rr dd - YMF262 port 1 write (addr=rr, data=dd) +; 0x61 nn nn - Wait N samples (16-bit LE) +; 0x62 - Wait 735 samples (1/60 sec) +; 0x63 - Wait 882 samples (1/50 sec) +; 0x66 - End of sound data +; 0x70-0x7F - Wait 1-16 samples +; +; Command buffer format (4 bytes each, aligned): +; CMD_WAIT (0): [0x00, ticks_lo, ticks_hi, 0x00] +; CMD_WRITE (1): [0x01, port, value, 0x00] +; CMD_END (FF): [0xFF, 0x00, 0x00, 0x00] +; +; Wait counter mapping: VGM "wait N samples" → ISR wait = N-1 +; (because the ISR tick that processes the WAIT command is tick 0, +; and counter=0 means "process next tick") +; ═══════════════════════════════════════════════════════════════════════════ + +; ═══════════════════════════════════════════════════════════════════════════ +; VGM_PARSE_HEADER +; ═══════════════════════════════════════════════════════════════════════════ +; Parse VGM header at VGMBUF. +; Output: Z = success, NZ = error +VGM_PARSE_HEADER: + PUSH HL + PUSH DE + PUSH BC + + ; Check "Vgm " signature + LD HL,VGMBUF + LD A,(HL) + CP 'V' + JP NZ,VGM_PARSE_FAIL + INC HL + LD A,(HL) + CP 'g' + JP NZ,VGM_PARSE_FAIL + INC HL + LD A,(HL) + CP 'm' + JP NZ,VGM_PARSE_FAIL + INC HL + LD A,(HL) + CP ' ' + JP NZ,VGM_PARSE_FAIL + + ; Read total samples (offset 0x18, 32-bit) + LD HL,VGMBUF+#18 + LD E,(HL) + INC HL + LD D,(HL) + LD (VGM_TOTAL_SAMPLES),DE + INC HL + LD E,(HL) + INC HL + LD D,(HL) + LD (VGM_TOTAL_SAMPLES+2),DE + + ; Read OPL3 clock (offset 0x5C, 32-bit) - must be non-zero + LD HL,VGMBUF+#5C + LD E,(HL) + INC HL + LD D,(HL) + LD (VGM_CLOCK),DE + INC HL + LD E,(HL) + INC HL + LD D,(HL) + LD (VGM_CLOCK+2),DE + + ; Check OPL3 clock != 0 (at least low word should be non-zero for OPL3) + LD HL,(VGM_CLOCK) + LD A,H + OR L + JR NZ,VGM_HAS_OPL3 + LD HL,(VGM_CLOCK+2) + LD A,H + OR L + ; OPL3 clock = 0: no OPL3 data, but well allow it (might still have data) +VGM_HAS_OPL3: + + ; Read data offset (offset 0x34, relative to 0x34) + ; For VGM version < 1.50, data starts at 0x40 + LD HL,VGMBUF+#08 ; version + LD A,(HL) ; version low byte (BCD minor) + LD B,A + INC HL + LD A,(HL) ; version byte 2 + ; Check if version >= 1.50 + ; Version bytes: [08]=minor_lo, [09]=minor_hi, [0A]=major_lo, [0B]=major_hi + ; For v1.50: bytes are 50 01 00 00 or similar + ; Simplified: if offset 0x34 has non-zero value and version >= 1.50, use it + LD HL,VGMBUF+#34 + LD E,(HL) + INC HL + LD D,(HL) + ; data_offset = value at 0x34 (relative to 0x34) + ; If 0, default to 0x0C (data starts at 0x40 = 0x34+0x0C) + LD A,D + OR E + JR NZ,VGM_USE_DATA_OFF + LD DE,#0C ; default: 0x40 - 0x34 = 0x0C +VGM_USE_DATA_OFF: + ; VGM_DATA_START = VGMBUF + 0x34 + data_offset + LD HL,VGMBUF+#34 + ADD HL,DE + LD (VGM_DATA_START),HL + LD (VGM_READ_PTR),HL + + ; Read loop offset (offset 0x1C, relative to 0x1C) + LD HL,VGMBUF+#1C + LD E,(HL) + INC HL + LD D,(HL) + LD A,D + OR E + JR Z,VGM_NO_LOOP + ; VGM_LOOP_ADDR = #C000 + (abs_offset & #3FFF), VGM_LOOP_PAGE = abs_offset >> 14 + ; abs_offset = #1C + value_at_1C (16-битное; достаточно для loop-поинтов < 64KB от начала) + LD HL,#001C + ADD HL,DE ; HL = абсолютное смещение точки петли + LD A,H + SRL A:SRL A:SRL A:SRL A:SRL A:SRL A ; A = H >> 6 = номер VPL-страницы + LD (VGM_LOOP_PAGE),A + LD A,H + AND #3F + OR #C0 + LD H,A ; HL = #C000 + (offset & #3FFF) + LD (VGM_LOOP_ADDR),HL + JR VGM_LOOP_DONE +VGM_NO_LOOP: + LD HL,0 + LD (VGM_LOOP_ADDR),HL ; 0 = нет петли +VGM_LOOP_DONE: + ; VGM_END_ADDR и VGM_END_PAGE вычисляются в PLUGIN при загрузке файла + + ; Success + POP BC + POP DE + POP HL + XOR A ; Z = success + RET + +VGM_PARSE_FAIL: + POP BC + POP DE + POP HL + OR 1 ; NZ = error + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; VGM_FILL_BUFFER +; ═══════════════════════════════════════════════════════════════════════════ +; Fill a command buffer with ISR commands from VGM stream. +; Input: A = buffer index (0=BUF_A, 1=BUF_B) +; Reads from VGM_READ_PTR, updates it. +; ═══════════════════════════════════════════════════════════════════════════ +VGM_FILL_BUFFER: + PUSH HL + PUSH DE + PUSH BC + PUSH IX + + ; If song ended or paused, fill with silence + LD B,A ; save buffer index + LD A,(SONG_ENDED) + OR A + JP NZ,VGM_FILL_SILENCE + LD A,(PAUSED) + OR A + JP NZ,VGM_FILL_SILENCE + LD A,B ; restore buffer index + + ; Get buffer base address + OR A + JR NZ,VGM_FILL_USE_B + LD IX,CMD_BUF_A + JR VGM_FILL_START +VGM_FILL_USE_B: + LD IX,CMD_BUF_B +VGM_FILL_START: + ; ВАЖНО: WC API (напр. UI_UPDATE) могла переключить страницу #C000! + ; Восстановить правильную VPL-страницу перед чтением VGM. + PUSH IX ; MNGCVPL (WLD) может затереть IX + LD A,(VGM_CUR_PAGE) + CALL MNGCVPL ; отобразить нужную страницу VGM на #C000 + POP IX + + ; DE = write pointer (offset from buffer start) + LD DE,0 ; offset 0 + LD HL,(VGM_READ_PTR) ; HL = VGM read pointer + +VGM_FILL_LOOP: + ; Check if buffer is almost full (leave room for END_BUF + 1 WAIT) + ; Buffer = 512 bytes, reserve last 8 bytes + ; When D>=1 offset>=256, close immediately + ; When D=0, close if E >= 248 (#F8) + LD A,D + OR A + JP NZ,VGM_FILL_CLOSE ; offset >= 256, definitely close to end + LD A,E + CP #F8 ; 248 - leave room in low page + JP NC,VGM_FILL_CLOSE + + ; Проверка конца VGM: сравниваем CUR_PAGE с END_PAGE, затем HL с END_ADDR + PUSH DE + LD A,(VGM_CUR_PAGE) + LD D,A + LD A,(VGM_END_PAGE) + CP D ; A=END_PAGE, D=CUR_PAGE + JR C,VGM_FILL_AT_END ; END_PAGE < CUR_PAGE → прошли конец + JR NZ,VGM_FILL_NOT_END ; END_PAGE > CUR_PAGE → ещё не конец + ; На последней странице: сравниваем HL с VGM_END_ADDR + LD DE,(VGM_END_ADDR) + LD A,H + CP D + JR C,VGM_FILL_NOT_END ; H < D + JR NZ,VGM_FILL_AT_END ; H > D + LD A,L + CP E + JR C,VGM_FILL_NOT_END ; L < E +VGM_FILL_AT_END: + POP DE + ; Song ended + LD A,1 + LD (SONG_ENDED),A + JP VGM_FILL_CLOSE +VGM_FILL_NOT_END: + POP DE + + ; Read next VGM opcode (+ проверка границы страницы) + LD A,(HL) + INC HL + CALL VGM_CHK_PAGE + + ; === 0x5E: YMF262 port 0 write (addr, data) === + CP #5E + JP Z,VGM_OP_OPL_PORT0 + + ; === 0x5F: YMF262 port 1 write (addr, data) === + CP #5F + JP Z,VGM_OP_OPL_PORT1 + + ; === 0x61: Wait N samples (16-bit) === + CP #61 + JP Z,VGM_OP_WAIT_N + + ; === 0x62: Wait 735 samples (1/60 sec) === + CP #62 + JP Z,VGM_OP_WAIT_60HZ + + ; === 0x63: Wait 882 samples (1/50 sec) === + CP #63 + JP Z,VGM_OP_WAIT_50HZ + + ; === 0x66: End of sound data === + CP #66 + JP Z,VGM_OP_END + + ; === 0x70-0x7F: Wait 1-16 samples === + LD B,A + AND #F0 + CP #70 + JP Z,VGM_OP_WAIT_SHORT + + ; Unknown opcode: try to skip it + ; Most VGM opcodes are 1-3 bytes. Common skips: + LD A,B ; restore opcode + ; 0x3x, 0x4x - single byte (reserved) + CP #50 + JP C,VGM_FILL_LOOP ; < 0x50: skip 1 byte total (already read) + ; 0x5x - 3 bytes total, we need to skip 2 more + AND #F0 + CP #50 + JR Z,VGM_SKIP2 + ; 0xAx, 0xBx - 3 bytes + LD A,B + AND #F0 + CP #A0 + JR Z,VGM_SKIP2 + CP #B0 + JR Z,VGM_SKIP2 + ; 0xCx, 0xDx - 4 bytes + CP #C0 + JR Z,VGM_SKIP3 + CP #D0 + JR Z,VGM_SKIP3 + ; 0xEx, 0xFx - 5 bytes + CP #E0 + JR Z,VGM_SKIP4 + CP #F0 + JR Z,VGM_SKIP4 + ; Default: skip nothing more + JP VGM_FILL_LOOP + +VGM_SKIP2: + INC HL + CALL VGM_CHK_PAGE + INC HL + CALL VGM_CHK_PAGE + JP VGM_FILL_LOOP +VGM_SKIP3: + INC HL + CALL VGM_CHK_PAGE + INC HL + CALL VGM_CHK_PAGE + INC HL + CALL VGM_CHK_PAGE + JP VGM_FILL_LOOP +VGM_SKIP4: + INC HL + CALL VGM_CHK_PAGE + INC HL + CALL VGM_CHK_PAGE + INC HL + CALL VGM_CHK_PAGE + INC HL + CALL VGM_CHK_PAGE + JP VGM_FILL_LOOP + +; --- OPL3 port 0 write: emit WRITE addr0, WRITE data0 --- +VGM_OP_OPL_PORT0: + ; Read register number and value + LD B,(HL) ; register number + INC HL + CALL VGM_CHK_PAGE + LD C,(HL) ; value + INC HL + CALL VGM_CHK_PAGE + + ; Emit: WRITE_B0 reg+val (банк 0) + PUSH HL + PUSH IX + POP HL ; HL = buffer base + ADD HL,DE ; HL = current write position + LD (HL),CMD_WRITE_B0 ; command + INC HL + LD (HL),B ; register number + INC HL + LD (HL),C ; value + INC HL + LD (HL),0 ; padding + POP HL ; restore VGM read ptr + + ; Advance write offset by 4 + LD A,E + ADD A,4 + LD E,A + JP NC,VGM_FILL_LOOP + INC D + JP VGM_FILL_LOOP + +; --- OPL3 port 1 write: emit WRITE addr1, WRITE data1 --- +VGM_OP_OPL_PORT1: + LD B,(HL) ; register number + INC HL + CALL VGM_CHK_PAGE + LD C,(HL) ; value + INC HL + CALL VGM_CHK_PAGE + + PUSH HL + PUSH IX + POP HL + ADD HL,DE + LD (HL),CMD_WRITE_B1 ; command + INC HL + LD (HL),B ; register number (банк 1) + INC HL + LD (HL),C ; value + INC HL + LD (HL),0 ; padding + POP HL + + LD A,E + ADD A,4 + LD E,A + JP NC,VGM_FILL_LOOP + INC D + JP VGM_FILL_LOOP + +; --- Wait N samples (16-bit LE) --- +; ISR freq = 2734 Hz, VGM samples = 44100 Hz. Factor = 44100/2734 ~ 16.13. +; Approximation: ISR_ticks = VGM_samples >> 4 (divide by 16) +; Ignore delays < 16 samples (too small to represent), emit nothing. +VGM_OP_WAIT_N: + LD C,(HL) ; ticks low + INC HL + CALL VGM_CHK_PAGE + LD B,(HL) ; ticks high + INC HL + CALL VGM_CHK_PAGE + ; BC = N samples. Divide by 16: shift right 4. + SRL B + RR C + SRL B + RR C + SRL B + RR C + SRL B + RR C ; ISR_ticks in BC (= N/16) + ; If BC == 0 (N < 16): skip + LD A,B + OR C + JP Z,VGM_FILL_LOOP + ; ISR_WAIT_CTR = ticks - 1 + DEC BC + + PUSH HL + PUSH IX + POP HL + ADD HL,DE + LD (HL),CMD_WAIT + INC HL + LD (HL),C ; ticks low + INC HL + LD (HL),B ; ticks high + INC HL + LD (HL),0 ; padding + POP HL + + LD A,E + ADD A,4 + LD E,A + JP NC,VGM_FILL_LOOP + INC D + JP VGM_FILL_LOOP + +; --- Wait 735 samples (1/60 sec) --- +VGM_OP_WAIT_60HZ: + ; 735/16 = 45 ISR ticks, -1 = 44 = #2C + PUSH HL + PUSH IX + POP HL + ADD HL,DE + LD (HL),CMD_WAIT + INC HL + LD (HL),#2C ; 44 low + INC HL + LD (HL),#00 ; 44 high + INC HL + LD (HL),0 + POP HL + + LD A,E + ADD A,4 + LD E,A + JP NC,VGM_FILL_LOOP + INC D + JP VGM_FILL_LOOP + +; --- Wait 882 samples (1/50 sec) --- +VGM_OP_WAIT_50HZ: + ; 882/16 = 55 ISR ticks, -1 = 54 = #36 + PUSH HL + PUSH IX + POP HL + ADD HL,DE + LD (HL),CMD_WAIT + INC HL + LD (HL),#36 ; 54 low + INC HL + LD (HL),#00 ; 54 high + INC HL + LD (HL),0 + POP HL + + LD A,E + ADD A,4 + LD E,A + JP NC,VGM_FILL_LOOP + INC D + JP VGM_FILL_LOOP + +; --- Wait 1-16 samples (opcode 0x70-0x7F) --- +; N = low nibble (0-15) = wait (N+1) samples. +; (N+1) >> 2: if < 1, skip; otherwise emit WAIT with value-1. +VGM_OP_WAIT_SHORT: + LD A,B ; restore opcode + AND #0F ; N = 0..15, actual wait = N+1 samples + INC A ; A = 1..16 samples + SRL A ; /2 + SRL A ; /4 + SRL A ; /8 + SRL A ; /16 + JP Z,VGM_FILL_LOOP ; < 16 samples: skip entirely + ; WAIT_CTR = A - 1 + DEC A + LD C,A + + PUSH HL + PUSH IX + POP HL + ADD HL,DE + LD (HL),CMD_WAIT + INC HL + LD (HL),C ; ticks low + INC HL + LD (HL),0 ; ticks high = 0 + INC HL + LD (HL),0 + POP HL + + LD A,E + ADD A,4 + LD E,A + JP NC,VGM_FILL_LOOP + INC D + JP VGM_FILL_LOOP + +; --- End of VGM data --- +VGM_OP_END: + LD A,1 + LD (SONG_ENDED),A + ; Fall through to close buffer + +; --- Close buffer: emit CMD_END_BUF --- +VGM_FILL_CLOSE: + LD (VGM_READ_PTR),HL ; save VGM read position + + ; Write CMD_END_BUF at current write position + PUSH IX + POP HL + ADD HL,DE + LD (HL),CMD_END_BUF + INC HL + LD (HL),0 + INC HL + LD (HL),0 + INC HL + LD (HL),0 + + POP IX + POP BC + POP DE + POP HL + RET + +; --- Fill silence buffer (for paused/ended state) --- +VGM_FILL_SILENCE: + ; Buffer index in B + LD A,B + OR A + JR NZ,VGM_SIL_USE_B + LD IX,CMD_BUF_A + JR VGM_SIL_FILL +VGM_SIL_USE_B: + LD IX,CMD_BUF_B +VGM_SIL_FILL: + ; Fill with: WAIT 54 (~20ms at 2734Hz), END_BUF + PUSH IX + POP HL + LD (HL),CMD_WAIT + INC HL + LD (HL),#36 ; 54 low (= ~20ms) + INC HL + LD (HL),#00 ; 54 high + INC HL + LD (HL),0 + INC HL + LD (HL),CMD_END_BUF + INC HL + LD (HL),0 + INC HL + LD (HL),0 + INC HL + LD (HL),0 + + POP IX + POP BC + POP DE + POP HL + RET + +; ─── VGM_CHK_PAGE: проверка границы VPL-страницы после INC HL ───────────── +; Если HL = #0000 (переход #FFFF→#0000), отображает следующую VPL-страницу +; на #C000 и устанавливает HL = #C000. +; Сохраняет A (через PUSH/POP AF). Может изменить HL при смене страницы. +VGM_CHK_PAGE: + PUSH AF + LD A,H + OR L + JR NZ,VGM_CHK_RET + ; Граница страницы: VGM_CUR_PAGE++, новая страница → #C000 + ; WC API (MNGCVPL) может затирать BC и DE — сохраняем их! + PUSH BC + PUSH DE + PUSH IX ; WLD (WC API) затирает IX! + LD A,(VGM_CUR_PAGE) + INC A + LD (VGM_CUR_PAGE),A + CALL MNGCVPL ; A = новый VPL-индекс → отобразить на #C000 + POP IX + POP DE + POP BC + LD HL,#C000 +VGM_CHK_RET: + POP AF + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; VGM_FILL_INACTIVE_BUF (called from main loop) +; ═══════════════════════════════════════════════════════════════════════════ +; Check which buffer ISR is reading, fill the other one if it changed. +; This is just a wrapper; main loop handles tracking in MAIN_LOOP. +; Kept for potential future use. diff --git a/old_asm_src/vgmplayer.asm b/old_asm_src/vgmplayer.asm new file mode 100644 index 0000000..f19d905 --- /dev/null +++ b/old_asm_src/vgmplayer.asm @@ -0,0 +1,520 @@ +; ═══════════════════════════════════════════════════════════════════════════ +; VGM Player Plugin for Wild Commander +; OPL3 (YMF262) playback on TSConfig +; Architecture v2: ISR-driven at 44100 Hz, double-buffered commands +; (C) 2026 +; ═══════════════════════════════════════════════════════════════════════════ + + DEVICE NOSLOT64K ; плоская 64К память, без ограничений слотов + + OUTPUT "vgmplay.wmf" + +PLUGHEAD EQU 0 ; заголовок в начале файла (как в PLUGBP) +PLUGSTAR EQU PLUGHEAD+512 ; код с байта #200 в файле + +PLUGLEN EQU END-PLUGIN +PAGES EQU (PLUGLEN/16384)+1 +BLKSIZE EQU (PLUGLEN+511)/512 + + ORG PLUGHEAD ; = 0, заголовок физически с байта 0 в файле + +; ═══════════════════════════════════════════════════════════════════════════ +; Plugin Header (512 bytes, exact WC format) +; ═══════════════════════════════════════════════════════════════════════════ + +; +0: Reserved (16 bytes) + DS 16 +; +16: Magic signature (16 bytes) + DB "WildCommanderMDL" +; +32: Plugin system version + DB #10 +; +33: Reserved + DB 0 +; +34: Number of 16KB pages + DB PAGES +; +35: Start page (0-based, mapped to #8000) + DB 0 +; +36: Data blocks (6 x 2 bytes = 12 bytes) + DB 0,BLKSIZE ; Block 0: plugin code, page 0 + DB 0,0 ; Block 1: unused + DB 0,0 ; Block 2: unused + DB 0,0 ; Block 3: unused + DB 0,0 ; Block 4: unused + DB 0,0 ; Block 5: unused +; +48: Reserved (15 bytes) + DS 15 +; +63: Extension flags (0 = launch by ENTER and F3) + DB 0 +; +64: Supported extensions (32 slots x 3 bytes = 96 bytes) + DB "VGM" + DS 31*3 ; Remaining slots empty +; +160: End marker + DB 0 +; +161: Max file size (32-bit LE: DW low_word, high_word) + DW #FFFF,#FFFF ; Unlimited +; +165: Plugin name (32 bytes, space-padded) + DB "VGM Player for OPL3 v2.0 " +; +197: Activation type (0 = by extension only) + DB 0 +; +198: F2/F4 button text (6 bytes) + DS 6 +; +204: Viewer menu text (24 bytes) + DS 24 +; +228: Reserved (32 bytes) + DS 32 +; +260: INI Parameters + DB 0 ; No parameters +; Pad to 512 bytes + DS PLUGSTAR-$ + + ; Заголовок занимает 512 байт (PLUGSTAR = #200), + ; код физически с байта #200, WC отображает страницу на #8000. + ; DISP #8000 — все метки кода резолвятся в диапазон #8000+ + DISP #8000 + +; ═══════════════════════════════════════════════════════════════════════════ +; Constants +; ═══════════════════════════════════════════════════════════════════════════ +WLD EQU #6006 ; WC API entry point +VPL EQU 32 ; WC VPL constant + +; --- TSConfig Ports (port = reg_number * 256 + #AF) --- +PORT_SYSCONFIG EQU #20AF ; SysConfig: bit1:0 = CPU freq, bit2 = cache +PORT_HSINT EQU #22AF ; Horizontal INT position (0-223) +PORT_VSINTL EQU #23AF ; Vertical INT position low (bits 7:0) +PORT_VSINTH EQU #24AF ; Vertical INT position high (bit0=VCNT[8], bits7:4=VINT_INC) +PORT_INTMASK EQU #2AAF ; INT mask: bit0=FRAME, bit1=LINE, bit2=DMA + +; --- CPU speed values for SysConfig bits 1:0 --- +CPU_3_5MHZ EQU %00000000 +CPU_7MHZ EQU %00000001 +CPU_14MHZ EQU %00000010 + +; --- OPL3 Ports --- +OPL3_ADDR0 EQU #C4 +OPL3_DATA0 EQU #C5 +OPL3_ADDR1 EQU #C6 +OPL3_DATA1 EQU #C7 + +; --- ISR Command Types (код = смещение в странице ISR_HANDLER) --- +CMD_WRITE_B0 EQU #40 ; [#40, reg, val, pad] OPL3 банк 0 +CMD_WRITE_B1 EQU #80 ; [#80, reg, val, pad] OPL3 банк 1 +CMD_WAIT EQU #C0 ; [#C0, lo, hi, pad] ждать N тиков +CMD_END_BUF EQU #E0 ; [#E0, pad, pad, pad] переключить буфер + +; --- Command Buffer sizes --- +CMD_BUF_SIZE EQU 512 ; bytes per buffer (128 commands) + +; --- Window layout --- +WND_X EQU 9 +WND_Y EQU 5 +WND_W EQU 62 +WND_H EQU 14 + +; --- Colors --- +CLR_MAIN EQU %01111111 + +; --- Коды ошибок для ERROR_BORDER --- +; Формат: биты [5:3]=категория, биты [2:0]=код. Оба поля ≠ 0 (цвета бордюра 1-7) +; Цвета ZX: 1=синий, 2=красный, 3=маджента, 4=зелёный, 5=циан, 6=жёлтый, 7=белый +; +; Категории (старшие 3 бита): +; 1 (синий) = файловые ошибки +; 2 (красный) = ошибки формата VGM +; 3 (маджента) = аппаратные ошибки +; +; Конкретные коды (младшие 3 бита) — внутри категории: +; 1 = первая ошибка, 2 = вторая, и т.д. +; +ERR_FILE_SMALL EQU (1<<3)|1 ; файл <= 255 байт: черный, синий(1), синий(1) +ERR_VGM_HEADER EQU (2<<3)|1 ; плохой заголовок: черный, синий(1), красный(2) +ERR_FILE_LOAD EQU (1<<3)|2 ; ошибка загрузки: черный, красный(2), синий(1) + +; --- Memory addresses --- +VGMBUF EQU #C000 ; VGM данные через WC VPL (окно 3: #C000-#FFFF) + +; ═══════════════════════════════════════════════════════════════════════════ +; Plugin Entry Point +; ═══════════════════════════════════════════════════════════════════════════ +PLUGIN: + PUSH IX ; Сохранить IX (WC передаёт структуру активной панели) + + ; Сохранить входные параметры от WC + ; A` = индекс расширения, BC = указатель на имя файла, + ; HL = размер файла (младшее слово), DE = размер файла (старшее слово) + EXA + LD (FILE_EXT),A + EXA + LD (FILE_NAME),BC + LD (FILE_SIZE),HL + LD (FILE_SIZE+2),DE + + ; Init variables + XOR A + LD (EXIT_CODE),A + LD (PAUSED),A + LD (SONG_ENDED),A + LD (ISR_ENABLED),A + LD (ISR_ACTIVE_BUF),A + LD (UNDERRUN_FLAG),A + LD (OPL3_FOUND),A + LD (UI_FRAME_CTR),A + LD (LAST_ISR_BUF),A + + LD HL,0 + LD (PLAY_SECONDS),HL + + ; Переключиться на 14 МГц (PORT_SYSCONFIG только на запись) + ; При выходе всегда восстанавливаем 3.5 МГц + LD BC,PORT_SYSCONFIG + LD A,CPU_14MHZ + OUT (C),A + + ; Проверка размера файла: достаточно > 256 байт + ; (VGM заголовок минимум 64 байта, но реальные файлы от 8КБ) + LD A,(FILE_SIZE+1) ; старший байт младшего слова + OR A + LD A,ERR_FILE_SMALL + JP Z,ERROR_BORDER ; <= 255 байт → ошибка +FILE_SIZE_OK: + + ; === Загрузить VGM файл в VPL-страницы WC (окно #C000, 16KB каждая) === + ; Шаг 1: вычислить число страниц B = ceil(FILE_SIZE / 16384) + LD HL,(FILE_SIZE) + LD DE,(FILE_SIZE+2) + LD BC,16383 ; добавляем для округления вверх + ADD HL,BC + JR NC,VGM_LOAD_NC + INC DE +VGM_LOAD_NC: + ; pages = (E << 2) | (H >> 6) [файлы < 4МБ] + LD A,H + SRL A:SRL A:SRL A:SRL A:SRL A:SRL A ; A = H >> 6 + LD C,A + LD A,E + ADD A,A:ADD A,A + OR C + LD B,A ; B = число страниц + + ; Шаг 2: вычислить VGM_END_PAGE и VGM_END_ADDR + DEC A ; номер последней страницы (0-based) + LD (VGM_END_PAGE),A + ; VGM_END_ADDR = #C000 + (FILE_SIZE & #3FFF) + LD HL,(FILE_SIZE) + LD A,H + AND #3F + OR #C0 + LD H,A + LD (VGM_END_ADDR),HL + + ; Шаг 3: загружать страницы по одной + LD A,B + LD (VGM_PAGES_TOTAL),A + XOR A + LD (VGM_CUR_PAGE),A +VGM_LOAD_LOOP: + LD A,(VGM_PAGES_TOTAL) + LD B,A + LD A,(VGM_CUR_PAGE) + CP B + JR NC,VGM_LOAD_DONE + CALL MNGCVPL ; A = VPL-индекс страницы → отобразить на #C000 + LD HL,#C000 + LD B,32 ; 32 × 512 = 16384 байт + CALL LOAD512 + LD A,(VGM_CUR_PAGE) + INC A + LD (VGM_CUR_PAGE),A + JR VGM_LOAD_LOOP +VGM_LOAD_DONE: + ; Отобразить страницу 0 для разбора заголовка + XOR A + LD (VGM_CUR_PAGE),A + CALL MNGCVPL + + ; Разобрать VGM заголовок + CALL VGM_PARSE_HEADER + LD A,ERR_VGM_HEADER + JP NZ,ERROR_BORDER + + ; Init OPL3 + CALL OPL3_INIT + + ; Восстановить дисплей (ОБЯЗАТЕЛЬНО перед любыми вызовами WC UI API) + CALL GEDPL + + ; Init UI (uses WC API, still in normal interrupt mode) + CALL UI_INIT + CALL UI_DRAW_INFO + + ; Disable WC interrupts (A'=0 via INT_PL) + ; WC will auto-restore on plugin exit + XOR A + EXA ; A' = 0 + CALL INT_PL + + ; Prepare initial data in both buffers (до ISR_INIT!) + LD A,0 + CALL VGM_FILL_BUFFER ; fill BUF_A + LD A,1 + CALL VGM_FILL_BUFFER ; fill BUF_B + + ; Set ISR read pointer to start of BUF_A + LD HL,CMD_BUF_A + LD (ISR_READ_PTR),HL + + ; Enable ISR processing + LD A,1 + LD (ISR_ENABLED),A + + ; Setup IM2 + start timer (после заполнения буферов!) + CALL ISR_INIT + EI + +; ═══════════════════════════════════════════════════════════════════════════ +; Main Loop +; ═══════════════════════════════════════════════════════════════════════════ +MAIN_LOOP: + EI ; гарантируем прерывания (WC API мог сделать DI) + ; 1. Check if ISR switched buffers -> fill the free one + LD A,(ISR_ACTIVE_BUF) + LD B,A + LD A,(LAST_ISR_BUF) + CP B + JR Z,ML_NO_FILL ; same buffer, nothing to do + + ; ISR switched! Update tracking and fill the now-free buffer + LD A,B + LD (LAST_ISR_BUF),A + ; The free buffer is the one ISR is NOT reading + XOR 1 ; 0->1, 1->0 (A = free buffer index) + LD C,A ; save free buffer index + LD A,1:OUT (#FE),A ; DEBUG: синий → входим в VGM_FILL_BUFFER + LD A,C ; restore free buffer index + CALL VGM_FILL_BUFFER + XOR A:OUT (#FE),A ; DEBUG: чёрный → VGM_FILL_BUFFER вернулся + +ML_NO_FILL: + ; 2. Update UI periodically + LD A,(UI_FRAME_CTR) + INC A + LD (UI_FRAME_CTR),A + AND #1F ; every 32 iterations + JR NZ,ML_NO_UI + CALL UI_UPDATE +ML_NO_UI: + + ; 3. Read keyboard (direct ZX matrix, no interrupts needed) + CALL KBD_READ + OR A + JR NZ,GOT_KEY + + ; 4. Check if song ended + LD A,(SONG_ENDED) + OR A + JP NZ,SONG_DONE + + JP MAIN_LOOP + +GOT_KEY: + ; A = action code: 1=ESC, 2=next, 3=prev, 4=pause + CP 1 + JR Z,DO_EXIT + CP 2 + JR Z,DO_NEXT + CP 3 + JR Z,DO_PREV + CP 4 + JR Z,DO_PAUSE + JR MAIN_LOOP + +DO_EXIT: + XOR A + LD (EXIT_CODE),A + JR EXIT_PLAYER + +DO_NEXT: + LD A,2 + LD (EXIT_CODE),A + JR EXIT_PLAYER + +DO_PREV: + LD A,4 + LD (EXIT_CODE),A + JR EXIT_PLAYER + +DO_PAUSE: + LD A,(PAUSED) + XOR 1 + LD (PAUSED),A + ; When pausing, ISR keeps running but main loop stops filling buffers + ; When unpausing, ISR was just replaying silence/waits + CALL UI_DRAW_STATUS + JP MAIN_LOOP + +SONG_DONE: + ; Проверить петлю (VGM_LOOP_ADDR = 0 → нет петли) + LD HL,(VGM_LOOP_ADDR) + LD A,H + OR L + JR Z,SONG_NO_LOOP + + ; Переключиться на страницу петли и перезапустить + LD A,(VGM_LOOP_PAGE) + LD (VGM_CUR_PAGE),A + CALL MNGCVPL + LD HL,(VGM_LOOP_ADDR) + LD (VGM_READ_PTR),HL + XOR A + LD (SONG_ENDED),A + JP MAIN_LOOP + +SONG_NO_LOOP: + LD A,2 ; next file + LD (EXIT_CODE),A + ; fall through + +; ═══════════════════════════════════════════════════════════════════════════ +; Exit +; ═══════════════════════════════════════════════════════════════════════════ +EXIT_PLAYER: + DI + + ; Stop ISR + XOR A + LD (ISR_ENABLED),A + + ; Silence OPL3 + CALL OPL3_RESET + + ; Restore IM1 + IM 1 + EI + + ; Восстановить 3.5 МГц + LD BC,PORT_SYSCONFIG + LD A,CPU_3_5MHZ + OUT (C),A + + ; Remove UI + CALL UI_REMOVE + + ; Return exit code to WC + LD A,(EXIT_CODE) + POP IX ; Восстановить IX для WC + RET + +ERROR_EXIT: + ; Восстановить 3.5 МГц + LD BC,PORT_SYSCONFIG + LD A,CPU_3_5MHZ + OUT (C),A + XOR A + POP IX ; Восстановить IX для WC + RET + +; ─── Отладка: вывод кода ошибки бордюром, зависание ─────────────────── +; Вход: A = код ошибки (биты [2:0] = младший, [5:3] = старший; оба ≠ 0) +; Цикл: чёрный → цвет(биты[2:0]) → цвет(биты[5:3]) → повтор +; Задержка каждого цвета ≈ 300мс при 14МГц +; TODO: убрать в релизной версии +ERROR_BORDER: + LD C,A ; C = код ошибки на всё время +ERR_LOOP: + ; 1. Чёрный бордюр (разделитель) + XOR A + OUT (#FE),A + CALL ERR_DELAY + ; 2. Младшие 3 бита → цвет + LD A,C + AND %00000111 + OUT (#FE),A + CALL ERR_DELAY + ; 3. Биты [5:3] → цвет (сдвиг вправо на 3) + LD A,C + RRCA + RRCA + RRCA + AND %00000111 + OUT (#FE),A + CALL ERR_DELAY + JR ERR_LOOP + +ERR_DELAY: + LD D,4 +ERR_DLY_OUT: + LD B,0 +ERR_DLY_INN: + DJNZ ERR_DLY_INN + DEC D + JR NZ,ERR_DLY_OUT + RET + +; ═══════════════════════════════════════════════════════════════════════════ +; WC API Wrappers +; CALL WLD + IM 2 + RET: IM2 восстанавливается после каждого вызова WC +; (WC может переключать процессор обратно на IM1) +; ═══════════════════════════════════════════════════════════════════════════ +PRWOW: LD A,1: CALL WLD:IM 2:EI:RET ; Print window +RRESB: LD A,2: CALL WLD:IM 2:EI:RET ; Remove window +PRSRW: LD A,3: CALL WLD:IM 2:EI:RET ; Print string in window +PRIAT: EXA:LD A,4:CALL WLD:IM 2:EI:RET ; Print attributes +TXTPR: LD A,11: CALL WLD:IM 2:EI:RET ; Print text +GEDPL: LD A,15: CALL WLD:IM 2:EI:RET ; Get display +LOAD512:LD A,#30:CALL WLD:IM 2:EI:RET ; Load 512-byte blocks +MNGCVPL:EXA:LD A,#41:CALL WLD:IM 2:EI:RET; Map VPL page to #C000 + +INT_PL: EXA:LD A,86:CALL WLD:IM 2:EI:RET ; Interrupt control + +; ═══════════════════════════════════════════════════════════════════════════ +; Global Variables +; ═══════════════════════════════════════════════════════════════════════════ +FILE_EXT: DB 0 +FILE_NAME: DW 0 +FILE_SIZE: DW 0,0 ; 32-bit little-endian + +EXIT_CODE: DB 0 +PAUSED: DB 0 +SONG_ENDED: DB 0 + +; VGM header data +VGM_DATA_START: DW 0 ; адрес первого байта VGM команд (#C000-#FFFF) +VGM_LOOP_ADDR: DW 0 ; адрес точки петли (#C000-#FFFF; 0 = нет петли) +VGM_LOOP_PAGE: DB 0 ; VPL-страница (0-based) точки петли +VGM_READ_PTR: DW 0 ; текущий адрес чтения в #C000-#FFFF +VGM_CUR_PAGE: DB 0 ; текущая VPL-страница отображённая на #C000 +VGM_END_ADDR: DW 0 ; адрес конца данных в последней странице +VGM_END_PAGE: DB 0 ; номер последней страницы (VPL, 0-based) +VGM_PAGES_TOTAL: DB 0 ; суммарное число загруженных страниц +VGM_TOTAL_SAMPLES: DW 0,0 ; 32-bit total samples +VGM_CLOCK: DW 0,0 ; OPL3 clock value + +; Playback time +PLAY_SECONDS: DW 0 ; секунды воспроизведения (обновляется в ISR) + +; ISR state (shared between ISR and main loop) +ISR_ENABLED: DB 0 ; 0=ISR does nothing, 1=active +ISR_ACTIVE_BUF: DB 0 ; 0=reading BUF_A, 1=reading BUF_B +ISR_READ_PTR: DW 0 ; current read pointer in active buffer +ISR_WAIT_CTR: DW 0 ; tick countdown (0=process commands) +; ISR_STEP_IDX, ISR_HPOS, ISR_LINE are in isr.asm (local to ISR) + +LAST_ISR_BUF: DB 0 ; main loop tracking +UNDERRUN_FLAG: DB 0 + +; OPL3 state +OPL3_FOUND: DB 0 + +; UI +UI_FRAME_CTR: DB 0 + +; ═══════════════════════════════════════════════════════════════════════════ +; Include Modules +; ═══════════════════════════════════════════════════════════════════════════ + INCLUDE "src/isr.asm" + INCLUDE "src/vgm.asm" + INCLUDE "src/opl3.asm" + INCLUDE "src/ui.asm" + INCLUDE "src/keyboard.asm" + +END: diff --git a/src_sdcc/.vscode/tasks.json b/src_sdcc/.vscode/tasks.json new file mode 100644 index 0000000..b73f166 --- /dev/null +++ b/src_sdcc/.vscode/tasks.json @@ -0,0 +1,13 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build VGM Player", + "type": "shell", + "command": "cmd /c build.bat", + "isBackground": false, + "problemMatcher": [], + "group": "build" + } + ] +} \ No newline at end of file diff --git a/src_sdcc/asm/cmdblocks.asm b/src_sdcc/asm/cmdblocks.asm new file mode 100644 index 0000000..f620cfd --- /dev/null +++ b/src_sdcc/asm/cmdblocks.asm @@ -0,0 +1,584 @@ +; ============================================================================ +; cmdblocks.asm — VGM-format command blocks for chip init/silence +; +; Compiled with sjasmplus → raw binary 16KB, appended as plugin page 5. +; +; Each block contains standard VGM opcodes identical to those in .vgm files: +; 0x5E rr vv — OPL3 bank 0 write +; 0x5F rr vv — OPL3 bank 1 write +; 0x55 rr vv — YM2203 write (also used for MultiSound control regs 0xF0+) +; 0xA0 rr vv — AY8910 write (bit7 of rr = chip 2) +; 0xBD rr vv — SAA1099 write (bit7 of rr = chip 2) +; 0x66 — end of block +; +; The standard vgm_fill_buffer() parses these blocks in cmdblk_mode, +; converting VGM opcodes to ISR commands exactly as it does for .vgm data. +; This eliminates the need for a separate command copier — budget counting, +; buffer overflow protection and command parsing all come for free. +; +; NOTE on SAA clock: MultiSound intercepts AY reg select 0xF0-0xFF on +; port 0xFFFD for hardware control. VGM opcode 0xA0 strips bit7 of +; the register byte for chip selection, so 0xA0 CANNOT send regs >= 0x80 +; to chip 1. We use YM2203 opcode 0x55 instead — it sends the register +; byte raw to 0xFFFD, which MultiSound intercepts correctly. +; +; Layout: +; 0xC000 : pointer table (uint16 × N) — absolute addresses of blocks +; 0xC000+N*2.. : block data (variable-length VGM commands, 3 bytes each) +; +; Usage from C: +; wc_mngc_pl(CMDBLK_PAGE); +; vgm_read_ptr = ((uint16_t*)0xC000)[blk_idx]; +; vgm_cmdblk_mode = 1; vgm_song_ended = 0; +; vgm_fill_buffer(buf_idx); +; vgm_cmdblk_mode = 0; +; +; Assembled with: sjasmplus asm\cmdblocks.asm +; → SAVEBIN "build/cmdblocks.bin", #C000, #4000 +; ============================================================================ + + DEVICE NOSLOT64K + ORG #C000 + +; ── VGM opcodes ───────────────────────────────────────────────────── +VGM_OPL_B0 EQU #5E ; OPL3 bank 0: 5E rr vv +VGM_OPL_B1 EQU #5F ; OPL3 bank 1: 5F rr vv +VGM_YM2203 EQU #55 ; YM2203 SSG+FM chip 1: 55 rr vv +VGM_YM2203_2 EQU #A5 ; YM2203 SSG+FM chip 2: A5 rr vv +VGM_AY EQU #A0 ; AY8910: A0 rr vv (bit7 = chip 2) +VGM_SAA EQU #BD ; SAA1099: BD rr vv (bit7 = chip 2) +VGM_OPL4 EQU #D0 ; YMF278B (OPL4): D0 pp rr vv (pp = port 0/1/2) +VGM_END EQU #66 ; End of data + +; ── Macros (each emits standard VGM bytes, 3 bytes per command) ───── + + MACRO opl3_bank0 addr, val + db VGM_OPL_B0, addr, val + ENDM + + MACRO opl3_bank1 addr, val + db VGM_OPL_B1, addr, val + ENDM + + MACRO ym2203_write reg, val + db VGM_YM2203, reg, val + ENDM + + MACRO ym2203_2_write reg, val + db VGM_YM2203_2, reg, val + ENDM + + MACRO ay_write reg, val + db VGM_AY, reg, val + ENDM + + MACRO ay2_write reg, val + db VGM_AY, reg | #80, val + ENDM + + MACRO saa_write reg, val + db VGM_SAA, reg, val + ENDM + + MACRO saa2_write reg, val + db VGM_SAA, reg | #80, val + ENDM + + MACRO opl4_wave_write reg, val + db VGM_OPL4, #02, reg, val + ENDM + + MACRO blk_end + db VGM_END + ENDM + +; ═══════════════════════════════════════════════════════════════════════ +; Pointer table (indices = CMDBLK_xxx from vgm.h) +; ═══════════════════════════════════════════════════════════════════════ +ptr_table: + dw blk_init_opl3 ; 0 = CMDBLK_INIT_OPL3 + dw blk_init_opl2 ; 1 = CMDBLK_INIT_OPL2 + dw blk_silence_opl ; 2 = CMDBLK_SILENCE_OPL + dw blk_silence_ay ; 3 = CMDBLK_SILENCE_AY + dw blk_silence_ay2 ; 4 = CMDBLK_SILENCE_AY2 + dw blk_silence_saa ; 5 = CMDBLK_SILENCE_SAA + dw blk_silence_saa2 ; 6 = CMDBLK_SILENCE_SAA2 + dw blk_saa_clock_on ; 7 = CMDBLK_SAA_CLK_ON + dw blk_saa_clock_off ; 8 = CMDBLK_SAA_CLK_OFF + dw blk_silence_ym2203 ; 9 = CMDBLK_SILENCE_YM2203 + dw blk_silence_ym2203_2 ; 10 = CMDBLK_SILENCE_YM2203_2 + dw blk_saa_init ; 11 = CMDBLK_SAA_INIT + dw blk_saa2_init ; 12 = CMDBLK_SAA2_INIT + dw blk_init_opl4 ; 13 = CMDBLK_INIT_OPL4 + dw blk_silence_wave ; 14 = CMDBLK_SILENCE_WAVE + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_INIT_OPL3 — OPL3 init (NEW=1, clean state) +; +; OPL3 NEW=1 enables full OPL3 register set. 4-op connections are +; cleared (reg 0x104=0) to avoid leftover pairing from a previous +; track. Test/WSE register 0x01 is zeroed — OPL3 does not use WSE; +; waveform select is implicit via E0-F5 registers. +; +; VGM stream: 5F 05 01 5F 04 00 5E 01 00 66 +; ═══════════════════════════════════════════════════════════════════════ +blk_init_opl3: + opl3_bank1 #05, #01 ; VGM: 5F 05 01 — OPL3 NEW=1 + opl3_bank1 #04, #00 ; VGM: 5F 04 00 — 4-op OFF (clean) + opl3_bank0 #01, #00 ; VGM: 5E 01 00 — Test/WSE=0 + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_INIT_OPL4 — YMF278B (OPL4, ZXM-MoonSound) init: NEW=1 + NEW2=1 +; +; Как CMDBLK_INIT_OPL3 (NEW=1, L/R через C0-C8), но дополнительно взводит +; бит NEW2 (бит 1 регистра Bank1 #05). На реальном чипе YMF278B этот бит +; обязателен — без него чип игнорирует любые записи в wave-часть (порты +; #7E/#7F), т.е. VGM-команды 0xD0 с портом 2 останутся немы. +; +; VGM stream: 5F 05 03 5F 04 00 5E 01 00 66 +; ═══════════════════════════════════════════════════════════════════════ +blk_init_opl4: + opl3_bank1 #05, #03 ; VGM: 5F 05 03 — NEW=1 + NEW2=1 (разблокировать wave) + opl3_bank1 #04, #00 ; VGM: 5F 04 00 — 4-op OFF (clean) + opl3_bank0 #01, #00 ; VGM: 5E 01 00 — Test/WSE=0 + opl4_wave_write #02, #10 ; WT headers 384-511 from 0x200000, + ; memory access OFF (sound generation mode) + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_INIT_OPL2 — OPL2 compat init (NEW=0, clean state) +; +; Forces OPL2 compatibility mode. Critically: 0x104=0 clears any +; residual 4-op state that would persist after an OPL3 track (the +; most common cause of OPL2 artifacts). Test/WSE register = 0. +; +; VGM stream: 5F 05 00 5F 04 00 5E 01 00 66 +; ═══════════════════════════════════════════════════════════════════════ +blk_init_opl2: + opl3_bank1 #05, #00 ; VGM: 5F 05 00 — OPL3 NEW=0 (OPL2) + opl3_bank1 #04, #00 ; VGM: 5F 04 00 — 4-op OFF (clean) + opl3_bank0 #01, #00 ; VGM: 5E 01 00 — Test/WSE=0 + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SILENCE_OPL — Full OPL3 silence (both banks) +; +; VGM stream (55 commands): +; 5E B0..B8 00 × 9 Key Off Bank 0 (ch 0-8) +; 5F B0..B8 00 × 9 Key Off Bank 1 (ch 0-8) +; 5E 4x 3F ×18 TL max Bank 0 (18 operators) +; 5F 4x 3F ×18 TL max Bank 1 (18 operators) +; 5E BD 00 Percussion Off +; 66 End +; ═══════════════════════════════════════════════════════════════════════ +blk_silence_opl: + ; ── Key Off: Bank 0 — VGM: 5E B0..B8 00 ── + opl3_bank0 #B0, #00 + opl3_bank0 #B1, #00 + opl3_bank0 #B2, #00 + opl3_bank0 #B3, #00 + opl3_bank0 #B4, #00 + opl3_bank0 #B5, #00 + opl3_bank0 #B6, #00 + opl3_bank0 #B7, #00 + opl3_bank0 #B8, #00 + ; ── Key Off: Bank 1 — VGM: 5F B0..B8 00 ── + opl3_bank1 #B0, #00 + opl3_bank1 #B1, #00 + opl3_bank1 #B2, #00 + opl3_bank1 #B3, #00 + opl3_bank1 #B4, #00 + opl3_bank1 #B5, #00 + opl3_bank1 #B6, #00 + opl3_bank1 #B7, #00 + opl3_bank1 #B8, #00 + ; ── TL max: Bank 0 — VGM: 5E {40-45,48-4D,50-55} 3F ── + opl3_bank0 #40, #3F + opl3_bank0 #41, #3F + opl3_bank0 #42, #3F + opl3_bank0 #43, #3F + opl3_bank0 #44, #3F + opl3_bank0 #45, #3F + opl3_bank0 #48, #3F + opl3_bank0 #49, #3F + opl3_bank0 #4A, #3F + opl3_bank0 #4B, #3F + opl3_bank0 #4C, #3F + opl3_bank0 #4D, #3F + opl3_bank0 #50, #3F + opl3_bank0 #51, #3F + opl3_bank0 #52, #3F + opl3_bank0 #53, #3F + opl3_bank0 #54, #3F + opl3_bank0 #55, #3F + ; ── TL max: Bank 1 — VGM: 5F {40-45,48-4D,50-55} 3F ── + opl3_bank1 #40, #3F + opl3_bank1 #41, #3F + opl3_bank1 #42, #3F + opl3_bank1 #43, #3F + opl3_bank1 #44, #3F + opl3_bank1 #45, #3F + opl3_bank1 #48, #3F + opl3_bank1 #49, #3F + opl3_bank1 #4A, #3F + opl3_bank1 #4B, #3F + opl3_bank1 #4C, #3F + opl3_bank1 #4D, #3F + opl3_bank1 #50, #3F + opl3_bank1 #51, #3F + opl3_bank1 #52, #3F + opl3_bank1 #53, #3F + opl3_bank1 #54, #3F + opl3_bank1 #55, #3F + ; ── Waveform reset: Bank 0 — VGM: 5E {E0-E5,E8-ED,F0-F5} 00 ── + ; Clears non-sine waveforms that linger between tracks. + ; Critical for clean OPL2 playback after OPL3 usage. + opl3_bank0 #E0, #00 + opl3_bank0 #E1, #00 + opl3_bank0 #E2, #00 + opl3_bank0 #E3, #00 + opl3_bank0 #E4, #00 + opl3_bank0 #E5, #00 + opl3_bank0 #E8, #00 + opl3_bank0 #E9, #00 + opl3_bank0 #EA, #00 + opl3_bank0 #EB, #00 + opl3_bank0 #EC, #00 + opl3_bank0 #ED, #00 + opl3_bank0 #F0, #00 + opl3_bank0 #F1, #00 + opl3_bank0 #F2, #00 + opl3_bank0 #F3, #00 + opl3_bank0 #F4, #00 + opl3_bank0 #F5, #00 + ; ── Waveform reset: Bank 1 — VGM: 5F {E0-E5,E8-ED,F0-F5} 00 ── + opl3_bank1 #E0, #00 + opl3_bank1 #E1, #00 + opl3_bank1 #E2, #00 + opl3_bank1 #E3, #00 + opl3_bank1 #E4, #00 + opl3_bank1 #E5, #00 + opl3_bank1 #E8, #00 + opl3_bank1 #E9, #00 + opl3_bank1 #EA, #00 + opl3_bank1 #EB, #00 + opl3_bank1 #EC, #00 + opl3_bank1 #ED, #00 + opl3_bank1 #F0, #00 + opl3_bank1 #F1, #00 + opl3_bank1 #F2, #00 + opl3_bank1 #F3, #00 + opl3_bank1 #F4, #00 + opl3_bank1 #F5, #00 + ; ── Percussion Off — VGM: 5E BD 00 ── + opl3_bank0 #BD, #00 + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SILENCE_WAVE — YMF278B (OPL4) wave-часть silence, 24 канала +; +; CMDBLK_SILENCE_OPL глушит только FM-банки (0x5E/0x5F) — wave-каналы +; (табличный синтез, до 24 голосов) им не затрагиваются и продолжают +; звучать после выхода из плеера. Для каждого канала N (0-23): +; рег. (0x68+N), data=0x40 — Key Off + Damp (бит7:6=01, форсированный +; быстрый спад огибающей, EG_DMP) +; рег. (0x50+N), data=0xFF — TL=0x7F (макс. затухание) + LD=1 +; (мгновенно, без интерполяции громкости) +; Оба варианта дублируют друг друга (как KeyOff+TL max у FM-банков) — +; гарантия тишины независимо от состояния огибающей канала. +; +; VGM stream: D0 02 68 40 ... D0 02 7F 40 (Key Off+Damp, 24 канала) +; D0 02 50 FF ... D0 02 67 FF (TL max, 24 канала) +; 66 +; ═══════════════════════════════════════════════════════════════════════ +blk_silence_wave: + opl4_wave_write #68, #40 + opl4_wave_write #69, #40 + opl4_wave_write #6A, #40 + opl4_wave_write #6B, #40 + opl4_wave_write #6C, #40 + opl4_wave_write #6D, #40 + opl4_wave_write #6E, #40 + opl4_wave_write #6F, #40 + opl4_wave_write #70, #40 + opl4_wave_write #71, #40 + opl4_wave_write #72, #40 + opl4_wave_write #73, #40 + opl4_wave_write #74, #40 + opl4_wave_write #75, #40 + opl4_wave_write #76, #40 + opl4_wave_write #77, #40 + opl4_wave_write #78, #40 + opl4_wave_write #79, #40 + opl4_wave_write #7A, #40 + opl4_wave_write #7B, #40 + opl4_wave_write #7C, #40 + opl4_wave_write #7D, #40 + opl4_wave_write #7E, #40 + opl4_wave_write #7F, #40 + opl4_wave_write #50, #FF + opl4_wave_write #51, #FF + opl4_wave_write #52, #FF + opl4_wave_write #53, #FF + opl4_wave_write #54, #FF + opl4_wave_write #55, #FF + opl4_wave_write #56, #FF + opl4_wave_write #57, #FF + opl4_wave_write #58, #FF + opl4_wave_write #59, #FF + opl4_wave_write #5A, #FF + opl4_wave_write #5B, #FF + opl4_wave_write #5C, #FF + opl4_wave_write #5D, #FF + opl4_wave_write #5E, #FF + opl4_wave_write #5F, #FF + opl4_wave_write #60, #FF + opl4_wave_write #61, #FF + opl4_wave_write #62, #FF + opl4_wave_write #63, #FF + opl4_wave_write #64, #FF + opl4_wave_write #65, #FF + opl4_wave_write #66, #FF + opl4_wave_write #67, #FF + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SILENCE_AY — Silence AY8910 chip 1 +; +; VGM stream: +; A0 07 3F Mixer: all tone+noise off +; A0 08 00 Volume A = 0 +; A0 09 00 Volume B = 0 +; A0 0A 00 Volume C = 0 +; 66 End +; ═══════════════════════════════════════════════════════════════════════ +blk_silence_ay: + ay_write 7, #3F ; VGM: A0 07 3F + ay_write 8, #00 ; VGM: A0 08 00 + ay_write 9, #00 ; VGM: A0 09 00 + ay_write 10, #00 ; VGM: A0 0A 00 + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SILENCE_AY2 — Silence AY8910 chip 2 (TurboSound) +; +; VGM stream (bit7 = chip 2): +; A0 87 3F Mixer off +; A0 88 00 Volume A = 0 +; A0 89 00 Volume B = 0 +; A0 8A 00 Volume C = 0 +; 66 End +; ═══════════════════════════════════════════════════════════════════════ +blk_silence_ay2: + ay2_write 7, #3F ; VGM: A0 87 3F + ay2_write 8, #00 ; VGM: A0 88 00 + ay2_write 9, #00 ; VGM: A0 89 00 + ay2_write 10, #00 ; VGM: A0 8A 00 + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SILENCE_SAA — Silence SAA1099 chip 1 +; +; VGM stream: +; BD 1C 02 Reset all (sound gen + internal state) +; BD 1C 00 Sound enable off +; BD 00..05 00 Amplitude ch 0-5 = 0 +; 66 End +; ═══════════════════════════════════════════════════════════════════════ +blk_silence_saa: + saa_write #1C, #02 ; VGM: BD 1C 02 — Reset all + saa_write #1C, #00 ; VGM: BD 1C 00 — Sound enable off + saa_write #00, #00 ; VGM: BD 00 00 — Amp ch0 = 0 + saa_write #01, #00 ; VGM: BD 01 00 — Amp ch1 = 0 + saa_write #02, #00 ; VGM: BD 02 00 — Amp ch2 = 0 + saa_write #03, #00 ; VGM: BD 03 00 — Amp ch3 = 0 + saa_write #04, #00 ; VGM: BD 04 00 — Amp ch4 = 0 + saa_write #05, #00 ; VGM: BD 05 00 — Amp ch5 = 0 + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SILENCE_SAA2 — Silence SAA1099 chip 2 +; +; VGM stream (bit7 = chip 2): +; BD 9C 02 Reset all +; BD 9C 00 Sound enable off +; BD 80..85 00 Amplitude ch 0-5 = 0 +; 66 End +; ═══════════════════════════════════════════════════════════════════════ +blk_silence_saa2: + saa2_write #1C, #02 ; VGM: BD 9C 02 — Reset all + saa2_write #1C, #00 ; VGM: BD 9C 00 — Sound enable off + saa2_write #00, #00 ; VGM: BD 80 00 — Amp ch0 = 0 + saa2_write #01, #00 ; VGM: BD 81 00 — Amp ch1 = 0 + saa2_write #02, #00 ; VGM: BD 82 00 — Amp ch2 = 0 + saa2_write #03, #00 ; VGM: BD 83 00 — Amp ch3 = 0 + saa2_write #04, #00 ; VGM: BD 84 00 — Amp ch4 = 0 + saa2_write #05, #00 ; VGM: BD 85 00 — Amp ch5 = 0 + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SAA_CLK_ON — Enable SAA1099 + FM clock (MultiSound) +; +; Uses YM2203 opcode (0x55) so that register 0xF3 is sent raw to 0xFFFD. +; AY opcode 0xA0 would strip bit7 for chip selection → can't reach 0xF3. +; MultiSound intercepts reg select 0xF0-0xFF for hardware control. +; bit0=1(YM2) bit1=1(normal) bit2=0(FM ON) bit3=0(SAA ON) +; +; VGM stream: 55 F3 00 66 +; ═══════════════════════════════════════════════════════════════════════ +blk_saa_clock_on: + ym2203_write #F3, #00 ; VGM: 55 F3 00 — SAA+FM clock ON + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SAA_CLK_OFF — Disable SAA1099 + FM clock (MultiSound) +; +; VGM stream: 55 FF 00 66 +; bit2=1(FM OFF) bit3=1(SAA OFF) +; ═══════════════════════════════════════════════════════════════════════ +blk_saa_clock_off: + ym2203_write #FF, #00 ; VGM: 55 FF 00 — SAA+FM clock OFF + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SILENCE_YM2203 — Silence YM2203 (SSG + FM) + reset prescaler +; +; Prescaler reset (reg 0x2D, write-only, value ignored): +; 55 2D 00 Reset prescaler → defaults: /6 FM, /4 SSG +; Prevents state leak from tracks that set /3+/2 via 0x2E. +; SSG (AY-compatible): +; 55 07 3F Mixer: all tone+noise off +; 55 08 00 SSG Volume A = 0 +; 55 09 00 SSG Volume B = 0 +; 55 0A 00 SSG Volume C = 0 +; FM — Key Off all 3 channels: +; 55 28 00 Key Off ch 0 +; 55 28 01 Key Off ch 1 +; 55 28 02 Key Off ch 2 +; FM — TL max (0x7F) all 12 operators: +; 55 40..42 7F slot 1 (M1): ch 0,1,2 +; 55 44..46 7F slot 3 (C1): ch 0,1,2 +; 55 48..4A 7F slot 2 (M2): ch 0,1,2 +; 55 4C..4E 7F slot 4 (C2): ch 0,1,2 +; Total: 20 VGM commands + end = 61 bytes +; ═══════════════════════════════════════════════════════════════════════ +blk_silence_ym2203: + ; ── SSG silence — VGM: 55 rr vv ── + ym2203_write 7, #3F ; VGM: 55 07 3F — Mixer off + ym2203_write 8, #00 ; VGM: 55 08 00 — SSG Vol A = 0 + ym2203_write 9, #00 ; VGM: 55 09 00 — SSG Vol B = 0 + ym2203_write 10, #00 ; VGM: 55 0A 00 — SSG Vol C = 0 + ; ── FM Key Off — VGM: 55 28 xx ── + ym2203_write #28, #00 ; VGM: 55 28 00 — Key Off ch 0 + ym2203_write #28, #01 ; VGM: 55 28 01 — Key Off ch 1 + ym2203_write #28, #02 ; VGM: 55 28 02 — Key Off ch 2 + ; ── FM TL max — VGM: 55 4x 7F ── + ym2203_write #40, #7F ; VGM: 55 40 7F — TL slot1 ch0 + ym2203_write #41, #7F ; VGM: 55 41 7F — TL slot1 ch1 + ym2203_write #42, #7F ; VGM: 55 42 7F — TL slot1 ch2 + ym2203_write #44, #7F ; VGM: 55 44 7F — TL slot3 ch0 + ym2203_write #45, #7F ; VGM: 55 45 7F — TL slot3 ch1 + ym2203_write #46, #7F ; VGM: 55 46 7F — TL slot3 ch2 + ym2203_write #48, #7F ; VGM: 55 48 7F — TL slot2 ch0 + ym2203_write #49, #7F ; VGM: 55 49 7F — TL slot2 ch1 + ym2203_write #4A, #7F ; VGM: 55 4A 7F — TL slot2 ch2 + ym2203_write #4C, #7F ; VGM: 55 4C 7F — TL slot4 ch0 + ym2203_write #4D, #7F ; VGM: 55 4D 7F — TL slot4 ch1 + ym2203_write #4E, #7F ; VGM: 55 4E 7F — TL slot4 ch2 + + ; ── Prescaler reset — VGM: 55 2D 00 ── + ym2203_write #2F, #01 ; VGM: 55 2D 00 + ym2203_write #2D, #01 ; VGM: A5 2E 00 + + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SILENCE_YM2203_2 — Silence YM2203 chip 2 (TurboSound) +; +; Uses VGM opcode 0xA5 (dual chip: 0x55 → 0xA5) → vgm_fill_buffer +; generates CMD_WRITE_AY2 which selects chip 2 via isr_ms_ctrl. +; +; Prescaler reset: +; A5 2D 00 Reset prescaler → defaults: /6 FM, /4 SSG +; SSG (chip 2): +; A5 07 3F Mixer: all tone+noise off +; A5 08 00 SSG Volume A = 0 +; A5 09 00 SSG Volume B = 0 +; A5 0A 00 SSG Volume C = 0 +; FM — Key Off all 3 channels (chip 2): +; A5 28 00 Key Off ch 0 +; A5 28 01 Key Off ch 1 +; A5 28 02 Key Off ch 2 +; FM — TL max (0x7F) all 12 operators (chip 2): +; A5 40..42 7F slot 1 (M1): ch 0,1,2 +; A5 44..46 7F slot 3 (C1): ch 0,1,2 +; A5 48..4A 7F slot 2 (M2): ch 0,1,2 +; A5 4C..4E 7F slot 4 (C2): ch 0,1,2 +; Total: 20 VGM commands + end = 61 bytes +; ═══════════════════════════════════════════════════════════════════════ +blk_silence_ym2203_2: + ; ── SSG silence (chip 2) — VGM: A5 rr vv ── + ym2203_2_write 7, #3F ; VGM: A5 07 3F — Mixer off + ym2203_2_write 8, #00 ; VGM: A5 08 00 — SSG Vol A = 0 + ym2203_2_write 9, #00 ; VGM: A5 09 00 — SSG Vol B = 0 + ym2203_2_write 10, #00 ; VGM: A5 0A 00 — SSG Vol C = 0 + ; ── FM Key Off (chip 2) — VGM: A5 28 xx ── + ym2203_2_write #28, #00 ; VGM: A5 28 00 — Key Off ch 0 + ym2203_2_write #28, #01 ; VGM: A5 28 01 — Key Off ch 1 + ym2203_2_write #28, #02 ; VGM: A5 28 02 — Key Off ch 2 + ; ── FM TL max (chip 2) — VGM: A5 4x 7F ── + ym2203_2_write #40, #7F ; VGM: A5 40 7F — TL slot1 ch0 + ym2203_2_write #41, #7F ; VGM: A5 41 7F — TL slot1 ch1 + ym2203_2_write #42, #7F ; VGM: A5 42 7F — TL slot1 ch2 + ym2203_2_write #44, #7F ; VGM: A5 44 7F — TL slot3 ch0 + ym2203_2_write #45, #7F ; VGM: A5 45 7F — TL slot3 ch1 + ym2203_2_write #46, #7F ; VGM: A5 46 7F — TL slot3 ch2 + ym2203_2_write #48, #7F ; VGM: A5 48 7F — TL slot2 ch0 + ym2203_2_write #49, #7F ; VGM: A5 49 7F — TL slot2 ch1 + ym2203_2_write #4A, #7F ; VGM: A5 4A 7F — TL slot2 ch2 + ym2203_2_write #4C, #7F ; VGM: A5 4C 7F — TL slot4 ch0 + ym2203_2_write #4D, #7F ; VGM: A5 4D 7F — TL slot4 ch1 + ym2203_2_write #4E, #7F ; VGM: A5 4E 7F — TL slot4 ch2 + + ; ── Prescaler reset (chip 2) — VGM: A5 2D 00 ── + ; ── Prescaler reset — VGM: 55 2D 00 ── + ym2203_2_write #2F, #01 ; VGM: 55 2D 00 + ym2203_2_write #2D, #01 ; VGM: A5 2E 00 + + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SAA_INIT — SAA1099 chip 1 Sound Enable ON +; +; Writes reg 0x1C = 0x01 to chip 0 (Sound Enable ON). +; Parsed via cmdblk → fb_is_vgm==0 → bypasses saa_mode filter. +; +; VGM stream: BD 1C 01 66 +; ═══════════════════════════════════════════════════════════════════════ +blk_saa_init: + saa_write #1C, #01 ; VGM: BD 1C 01 — Sound Enable ON (chip 0) + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; CMDBLK_SAA2_INIT — SAA1099 chip 2 Sound Enable ON (+ clock enable) +; +; Writes reg 0x1C|0x80 = 0x9C to chip 1 (bit7 selects chip 1). +; In HDL: address write 0x9C → saa_reg_is_1c=1, then data 0x01 with +; d[0]=1 triggers saa2_clk_en (one-way clock enable for chip 2). +; Parsed via cmdblk → fb_is_vgm==0 → bypasses saa_mode filter. +; +; VGM stream: BD 9C 01 66 +; ═══════════════════════════════════════════════════════════════════════ +blk_saa2_init: + saa2_write #1C, #01 ; VGM: BD 9C 01 — Sound Enable ON (chip 1 + clk) + blk_end + +; ═══════════════════════════════════════════════════════════════════════ +; Padding to 16 KB +; ═══════════════════════════════════════════════════════════════════════ + IF $ < #10000 + ds #10000 - $, #00 + ENDIF + + SAVEBIN "build/cmdblocks.bin", #C000, #4000 diff --git a/src_sdcc/asm/crt0.s b/src_sdcc/asm/crt0.s new file mode 100644 index 0000000..2f4e65c --- /dev/null +++ b/src_sdcc/asm/crt0.s @@ -0,0 +1,160 @@ +;============================================================================== +; @file crt0.s +; @brief Стартовый код WC-плагина (SDCC Z80, --no-std-crt0) +; +; Этот файл заменяет стандартный crt0 SDCC. Он должен линковаться +; ПЕРВЫМ в цепочке .rel-файлов, чтобы _plugin_entry оказался +; по адресу --code-loc (обычно #8000). +; +; Входные параметры WC (при CALL plugin_base): +; A' = индекс расширения файла +; BC = указатель на имя файла (z-строка) +; HL = размер файла (младшее слово) +; DE = размер файла (старшее слово) +; IX = указатель на структуру активной панели WC +; +; Выход: A = код возврата (0=ESC, 2=следующий, 4=предыдущий) +; +; Адресное пространство: +; #8000-#BFFF = код + данные плагина +; #C000-#FFFF = VPL окно (данные VGM через WC API) +; +; ВАЖНО: gsinit обнуляет _DATA и копирует _INITIALIZER → _INITIALIZED. +; Без этого при повторном запуске (NEXT/PREV) стат. переменные +; содержат мусор от предыдущего запуска. +;============================================================================== + + .module crt0 + .globl _main + .globl _wc_exit_code + .globl _wc_file_idx + .globl _wc_file_count + + ; Линкер-символы для runtime-инициализации C + .globl s__DATA + .globl l__DATA + .globl s__INITIALIZED + .globl s__INITIALIZER + .globl l__INITIALIZER + +;------------------------------------------------------------------------------ +; Entry point (WC вызывает по "CALL plugin_base") +; Вход: +; A' = индекс расширения файла +; BC = указатель на имя файла (z-строка) +; HL = размер файла (младшее слово) +; DE = размер файла (старшее слово) +; IX = указатель на структуру активной панели (нужен WC API) +;------------------------------------------------------------------------------ + .area _CODE + +_plugin_entry:: + ;-------------------------------------------------------------- + ; Сохранить параметры WC на стеке: gsinit обнулит _DATA, + ; поэтому нельзя записывать в глобалы до инициализации. + ;-------------------------------------------------------------- + push hl ; size_lo + push de ; size_hi + push bc ; file_name ptr + + ex af, af' + push af ; file_ext (в A) + ex af, af' + + ;--- Сохранить HL'/DE' (номер объекта / кол-во файлов) --- + exx + push hl ; file_idx (HL') + push de ; file_count (DE') + exx + + ;-------------------------------------------------------------- + ; Инициализировать C runtime (обнулить _DATA, + ; скопировать начальные значения в _INITIALIZED) + ;-------------------------------------------------------------- + call gsinit + + ;-------------------------------------------------------------- + ; Восстановить параметры WC из стека в (теперь чистые) глобалы + ;-------------------------------------------------------------- + pop de ; file_count (DE') + ld (_wc_file_count), de + pop hl ; file_idx (HL') + ld (_wc_file_idx), hl + + pop af + ld (_wc_file_ext), a + + pop bc + ld (_wc_file_name), bc + + pop de ; size_hi + pop hl ; size_lo + ld (_wc_file_size+0), hl + ld (_wc_file_size+2), de + + ; Вызвать main() + call _main + + ; main() вернула void, код выхода — в _wc_exit_code + ld a, (_wc_exit_code) + + ; IX не менялся — просто возврат + ret + +;------------------------------------------------------------------------------ +; Глобальные переменные параметров WC (читаются из main.c) +;------------------------------------------------------------------------------ + .area _DATA + +_wc_file_ext:: .db 0 +_wc_file_name:: .dw 0 +_wc_file_size:: .dw 0, 0 ; 32-bit LE +_wc_exit_code:: .db 0 +_wc_file_idx:: .dw 0 ; HL': номер объекта в панели (от 1) +_wc_file_count::.dw 0 ; DE': кол-во объектов в панели (вкл. "..") + +;------------------------------------------------------------------------------ +; Инициализация C runtime (SDCC: _GSINIT выполняется при старте) +; +; 1) Обнулить _DATA — все глобальные/статические переменные без +; явного инициализатора (аналог BSS). +; 2) Скопировать _INITIALIZER → _INITIALIZED — переменные с явным +; начальным значением (static uint8_t x = 5; и т.п.). +;------------------------------------------------------------------------------ + .area _GSINIT + +gsinit:: + ; ── 1. Обнулить _DATA ──────────────────────────────────────── + ld bc, #l__DATA + ld a, b + or a, c + jr z, 00001$ ; l__DATA == 0 → нечего обнулять + + ld hl, #s__DATA + ld (hl), #0 ; первый байт = 0 + dec bc + ld a, b + or a, c + jr z, 00001$ ; l__DATA == 1 → уже готово + + ld d, h + ld e, l + inc de ; DE = s__DATA + 1 + ldir ; заполнить остаток нулями +00001$: + + ; ── 2. Скопировать _INITIALIZER → _INITIALIZED ────────────── + ld bc, #l__INITIALIZER + ld a, b + or a, c + jr z, 00002$ ; нет инициализированных данных + + ld hl, #s__INITIALIZER ; источник (в ROM / code page 1) + ld de, #s__INITIALIZED ; приёмник (в RAM / _DATA area) + ldir +00002$: + + .area _GSFINAL + +gsfinal:: + ret diff --git a/src_sdcc/asm/inflate.asm b/src_sdcc/asm/inflate.asm new file mode 100644 index 0000000..20d380c --- /dev/null +++ b/src_sdcc/asm/inflate.asm @@ -0,0 +1,1929 @@ +; ============================================================================ +; inflate.asm — TSConfig gzip inflate (adapted from SamFlate by Andrew Collier) +; +; Original SamFlate (C) 2009, released under DFSG-compatible license (BSD/GPL). +; TSConfig adaptation: replaces SAM Coupe LMPR/HMPR paging with TSConfig +; page-window port I/O (0x10AF–0x13AF). +; +; Memory layout during decompression (interrupts disabled): +; Win 0 (0x0000–0x3FFF): Source VGZ data (rotating pages) +; Win 1 (0x4000–0x7FFF): Source VGZ next page (contiguous 32K src window) +; Temporarily: backref history (dest_page - 1) during LDIR +; Win 2 (0x8000–0xBFFF): Decode working buffer (fixed page PAGE_DECODE) +; Win 3 (0xC000–0xFFFF): This code + RAM tables (extra WMF page) +; +; SamFlate was designed for SAM Coupe where LMPR gives a 32K contiguous +; source window (sections A+B). On TSConfig, Win 0+Win 1 together provide +; the same 32K window so that source reads never fall off the page boundary +; between output flushes. Win 1 is briefly remapped for backref LDIR, then +; immediately restored to src_page + 1. +; +; Entry: Called via JP 0xC200 (inflate_init) from inflate_call.s +; A = source start physical page (e.g. #A1 for TAP) +; E = destination start physical page (e.g. #20 for megabuffer) +; D = saved Win 2 page (to restore on exit) +; SP = safe stack in Win 3 area (e.g. #FFF0) +; Return address already pushed on stack by caller +; +; Exit: RET with A=0 carry clear (success) or A!=0 carry set (error). +; Win 2 restored to saved page. Caller restores Win 0/1/3. +; +; Assembled with sjasmplus -> raw binary, loaded as extra WMF page. +; NOTE: first 257 bytes at 0xC000 may be overwritten during inflate +; (DEFLATE dict copy overflow). Entry code runs once before inflate. +; ============================================================================ + + DEVICE NOSLOT64K + ORG #C000 + +; -- TSConfig port equates --------------------------------------------------- +PORT_W0 EQU #10AF +PORT_W1 EQU #11AF +PORT_W2 EQU #12AF +PAGE_DECODE EQU #EA ; free page for decode buffer + +; -- Debug port equates (emulator shows 'Illegal port! OUT') ---------------- +DBG_STATUS EQU #FFAF ; status/milestone code +DBG_SRC EQU #FEAF ; current src page +DBG_DST EQU #FDAF ; current dst page + +; -- Macro: emit debug milestone (destroys BC, A) --------------------------- + MACRO DBG_OUT val + IFDEF DBG_ENABLE + ld a, val + ld bc, DBG_STATUS + out (c), a + ENDIF + ENDM + +; -- Macro: emit debug milestone + src/dst pages (destroys BC, A) ----------- + MACRO DBG_OUT_PAGES val + IFDEF DBG_ENABLE + ld a, val + ld bc, DBG_STATUS + out (c), a + ld a, (src_page + 1) + ld bc, DBG_SRC + out (c), a + ld a, (dst_page + 1) + ld bc, DBG_DST + out (c), a + ENDIF + ENDM + +; -- Macro: write A to TSConfig page window port (destroys BC) --------------- + MACRO OUT_W0 + ld bc, PORT_W0 + out (c), a + ENDM + + MACRO OUT_W1 + ld bc, PORT_W1 + out (c), a + ENDM + + MACRO OUT_W2 + ld bc, PORT_W2 + out (c), a + ENDM + +; -- Macro: unrolled Huffman decode body ------------------------------------- + MACRO HUFFDEC lbl, mode, tgt +lbl: + exx + IF mode > 0 + dec b + or a + rr c + ELSE + or a + rr c + ld b, 8 + ld c, (hl) + inc hl + ENDIF + exx + adc a, a + add a, a + ld l, a + ld a, (hl) + bit 7, a + jp z, tgt + inc hl + ld h, (hl) + ENDM + + +; ============================================================================ +; ENTRY POINT (0xC000) -- JP to safe init zone +; +; This 3-byte JP may be overwritten by DEFLATE backref LDIR overflow +; (up to 258 bytes past 0xBFFF). That's fine: by the time the overflow +; happens, we've already jumped to init and are deep inside inflate_core. +; +; inflate_call.s calls JP 0xC200 directly (inflate_init in safe zone). +; The JP at 0xC000 is NOT used at runtime; it exists only as a +; fallback marker and will be overwritten by LDIR on first call. +; ============================================================================ +entry: + jp inflate_init + +; ============================================================================ +; PADDING — survive DEFLATE backref overflow +; +; During LDIR of a backref copy, DE can advance up to 258 bytes +; past 0xBFFF into Win 3 (0xC000+), overwriting inflate code. +; inflate_init MUST start at or after 0xC102 to survive. +; We place it at 0xC200 for a clean round address. +; ============================================================================ + DS #C1F0 - $, 0 + +; ── Progress bar parameter block ──────────────────────────────────────── +; Written by inflate_call.s before JP 0xC200. +; Safe from LDIR overflow (max 258 bytes → 0xC102 < 0xC1F0). +; +; inflate_call.s uses absolute addresses 0xC1F0–0xC1F7 to write these. +; ──────────────────────────────────────────────────────────────────────── +pb_text_pg: DB 0 ; 0xC1F0: text screen physical page +pb_attr_lo: DB 0 ; 0xC1F1: attr base offset low byte (bit7=1, attr area) +pb_attr_hi: DB 0 ; 0xC1F2: attr base offset high byte +pb_total: DB 0 ; 0xC1F3: estimated total output pages (0=disabled) +pb_bar_w: DB 0 ; 0xC1F4: bar width in columns +pb_green: DB 0 ; 0xC1F5: green attr value +pb_error: DB 0 ; 0xC1F6: Bresenham error accumulator (runtime) +pb_col: DB 0 ; 0xC1F7: current filled column (runtime) + + ASSERT $ <= #C200, "progress block overflows past inflate_init!" + DS #C200 - $, 0 + +; ============================================================================ +; INFLATE INIT — safe zone (≥ 0xC102), runs every call +; +; Registers on entry: +; A = source start physical page +; E = destination start physical page +; D = saved Win 2 page (to restore on exit) +; SP = safe stack in Win 3 area +; Return address already pushed on stack +; ============================================================================ +inflate_init: + ASSERT $ == #C200, "inflate_init must be at exactly 0xC200 (inflate_call.s hardcodes this)" + ; A = src start page, D = saved pg2, E = dst start page + ld (src_page + 1), a + ld a, d + ld (wc_saved_pg2 + 1), a + ld a, e + ld (dst_page + 1), a + sub 2 + ld (dst_page_m2 + 1), a + + ; Initial destination offset = 0 + ld hl, 0 + ld (dst + 1), hl + + ; Remap Win 2 = decode buffer page + ld a, PAGE_DECODE + OUT_W2 + + ; Remap Win 0+1 = first two source pages (32K contiguous window) + ld a, (src_page + 1) + OUT_W0 + inc a + ld b, HIGH PORT_W1 ; C still = #AF from OUT_W0 + out (c), a ; Win 1 = src_page + 1 + + ; Source pointer at start of Win 0, decode buffer at 0x8000 + ld hl, #0000 + ld de, #8000 + ; fall through to inflate_core + +; ============================================================================ +; INFLATE CORE -- gzip header parse + DEFLATE blocks +; ============================================================================ +inflate_core: + ld (interim_dst + 1), de + + ; HL'/C'/B' = source state (alternate registers) + ld c, (hl) + inc hl + ld b, 8 + exx + + ; -- gzip header -- + call read8bits + cp #1F + jp nz, fail + call read8bits + cp #8B + jp nz, fail + call read8bits + cp 8 + jp nz, fail + + call read8bits + ld d, a ; flags + ld a, 6 + call dropbytes + + ld a, d + and #04 + call nz, drop_gz_fextra + ld a, d + and #08 + call nz, drop_gz_fname + ld a, d + and #10 + call nz, drop_gz_fcomment + ld a, d + and #02 + call nz, drop_gz_fhcrc + + +; -- DEFLATE block loop ------------------------------------------------------ + ; DBG: 0x12 = gzip header parsed OK, entering DEFLATE blocks + DBG_OUT #12 + +NEW_BLOCK: + call read1bit + ld (this_was_last_block + 1), a + call read2bits + cp 3 + jp z, fail + cp 1 + jp c, blocktype0 + jp z, blocktype1 + jp blocktype2 + + +; ============================================================================ +; BLOCK TYPE 0 -- stored (raw) data +; ============================================================================ +blocktype0: + DBG_OUT #14 + call readalign + call read8bits + ld c, a + call read8bits + ld b, a + call read8bits + ld l, a + call read8bits + ld h, a + or a + sbc hl, bc + ld a, h + or l + jp nz, fail + + ld de, (interim_dst + 1) + +bt0_try_page: + ld l, e + ld h, d + add hl, bc + ld a, h + sub #80 + cp #40 + jp c, bt0_fits + + ; Won't fit: flush page + ld hl, #C000 + or a + sbc hl, de + ld a, l + ld l, c + ld c, a + ld a, h + ld h, b + ld b, a + or a + sbc hl, bc + ld (bt0_remaining + 1), hl + + exx + push hl + exx + pop hl + ldir + push hl + exx + pop hl + exx + + ; Copy decode buffer -> destination + ld a, (dst_page + 1) + OUT_W0 + + ld de, (dst + 1) + ld a, e + or d + jr z, bt0_whole + + ld hl, #4000 + or a + sbc hl, de + ld c, l + ld b, h + ld l, e + ld a, d + or #80 + ld h, a + ldir + jr bt0_flushed + +bt0_whole: + ld hl, #8000 + ld de, #0000 + ld bc, #4000 + ldir + +bt0_flushed: + ld hl, 0 + ld (dst + 1), hl + ld a, (dst_page + 1) + inc a + cp #60 ; megabuffer limit (0x20-0x5F) + jp nc, fail ; overflow → abort + ld (dst_page + 1), a + dec a + dec a + ld (dst_page_m2 + 1), a + + ; DBG: 0x20 = bt0 page flushed + DBG_OUT_PAGES #20 + + ; Source rollover + exx + bit 6, h + jr z, bt0_no_srcroll + ld a, h + and #3F + ld h, a + ld a, (src_page + 1) + inc a + ld (src_page + 1), a +bt0_no_srcroll: + exx + + ld a, (src_page + 1) + OUT_W0 + inc a + ld b, HIGH PORT_W1 ; C still = #AF from OUT_W0 + out (c), a ; Win 1 = src_page + 1 + call progress_update + + ld de, #8000 +bt0_remaining EQU $ + ld bc, #0000 + jp bt0_try_page + +bt0_fits: + exx + push hl + exx + pop hl + ldir + push hl + exx + pop hl + exx + ld (interim_dst + 1), de + jp END_BLOCK + + +; ============================================================================ +; BLOCK TYPE 1 -- static Huffman +; ============================================================================ +blocktype1: + DBG_OUT #15 + ld hl, 288 + ld (literals + 1), hl + ld a, 32 + ld (distances + 1), a + + ld hl, codelengths + ld b, 144 + ld a, 8 +bt1_s1: ld (hl), a + inc hl + djnz bt1_s1 + ld b, 112 + ld a, 9 +bt1_s2: ld (hl), a + inc hl + djnz bt1_s2 + ld b, 24 + ld a, 7 +bt1_s3: ld (hl), a + inc hl + djnz bt1_s3 + ld b, 8 + ld a, 8 +bt1_s4: ld (hl), a + inc hl + djnz bt1_s4 + jp huffman_common + + +; ============================================================================ +; BLOCK TYPE 2 -- dynamic Huffman +; ============================================================================ +blocktype2: + DBG_OUT #16 + call read5bits + inc a + ld l, a + ld h, 1 + ld (literals + 1), hl + + call read5bits + inc a + ld (distances + 1), a + + call read4bits + add a, 4 + ld (bt2_cll_cnt + 1), a + + ld hl, codelengths + ld b, 20 + xor a +bt2_clr: + ld (hl), a + inc hl + djnz bt2_clr + + ; DBG: 0xA6 = HLIT/HDIST/HCLEN read OK, reading code length orders + DBG_OUT #A6 + + ld de, code_length_orders + ld h, HIGH codelengths +bt2_cll_cnt EQU $ + ld b, 0 +bt2_cll: + ld a, (de) + add a, LOW codelengths + ld l, a + call read3bits + ld (hl), a + inc de + djnz bt2_cll + + ; DBG: 0xA9 = code length orders read OK, calling makeHuffmanTable + DBG_OUT #A9 + + ld bc, 19 + ld hl, codelengths + ld de, Huffman_Tables_Start + call makeHuffmanTable + + ; DBG: 0xA7 = first makeHuffmanTable done (proto codes) + DBG_OUT #A7 + + ld hl, Huffman_Tables_Start + ld de, codelengths +literals EQU $ + ld bc, #0000 + ld a, (distances + 1) + add a, c + ld c, a + ld a, b + adc a, 0 + ld b, a + call decodeProtoHuffman + + ; DBG: 0xA8 = decodeProtoHuffman done + DBG_OUT #A8 + +; -- common Huffman table building ------------------------------------------- +huffman_common: + ld bc, (literals + 1) + ld hl, codelengths + ld de, Huffman_Tables_Start + call makeHuffmanTable + + ld de, (next_htable_store + 1) + ld (Dist_HT_Start + 1), de + + ld hl, codelengths + ld bc, (literals + 1) + add hl, bc +distances EQU $ + ld c, 0 + ld b, 0 + call makeHuffmanTable + + ; Set up decode loop +interim_dst EQU $ + ld de, #8000 + +Dist_HT_Start EQU $ + ld hl, #0000 + ld a, l + rra + rra + ld l, a + ld (Dist_HTBegin + 1), hl + + ld hl, Huffman_Tables_Start + ld a, l + rra + rra + ld l, a + ld (HTBegin + 1), hl + + + ; DBG: 0x17 = Huffman tables built, entering decode loop + DBG_OUT #17 + +; ============================================================================ +; HUFFMAN DECODE LOOP -- unrolled 8x +; ============================================================================ +decode_tableloop: + exx + ld a, b + exx + add a, a + add a, a + ld (dtl_jr + 1), a + +HTBegin EQU $ + ld hl, #0000 + ld a, l + +dtl_jr EQU $ + jr $+2 + jp fail + nop + jp decode_tlu_0 + nop + jp decode_tlu_7 + nop + jp decode_tlu_6 + nop + jp decode_tlu_5 + nop + jp decode_tlu_4 + nop + jp decode_tlu_3 + nop + jp decode_tlu_2 + nop + jp decode_tlu_1 + + HUFFDEC decode_tlu_0, 0, decoded_symbol + HUFFDEC decode_tlu_1, 1, decoded_symbol + HUFFDEC decode_tlu_2, 1, decoded_symbol + HUFFDEC decode_tlu_3, 1, decoded_symbol + HUFFDEC decode_tlu_4, 1, decoded_symbol + HUFFDEC decode_tlu_5, 1, decoded_symbol + HUFFDEC decode_tlu_6, 1, decoded_symbol + HUFFDEC decode_tlu_7, 1, decoded_symbol + jp decode_tlu_0 + + +decoded_symbol: + or a + jr nz, decode_special + + inc hl + ld a, (hl) + ldi + + bit 6, d + jp nz, copy_decoded_page + jp decode_tableloop + + +end_of_huffman_block: + ld (interim_dst + 1), de + jp END_BLOCK + + +; -- Length/distance decode --------------------------------------------------- +decode_special: + inc hl + ld a, (hl) + or a + jr z, end_of_huffman_block + + ld hl, lengthcodetable - 4 + add a, a + add a, a + add a, l + ld l, a + ld a, h + adc a, 0 + ld h, a + + ld c, (hl) + inc hl + ld b, (hl) + inc hl + ld a, (hl) + inc hl + ld h, (hl) + ld l, a + ld (len_call + 1), hl + +len_call EQU $ + call #0000 + add a, c + ld c, a + ld a, b + adc a, 0 + ld b, a + + +; -- Distance decode loop ---------------------------------------------------- +dist_decode_tl: + exx + ld a, b + exx + add a, a + add a, a + ld (ddtl_jr + 1), a + +Dist_HTBegin EQU $ + ld hl, #0000 + ld a, l + +ddtl_jr EQU $ + jr $+2 + jp fail + nop + jp dist_tlu_0 + nop + jp dist_tlu_7 + nop + jp dist_tlu_6 + nop + jp dist_tlu_5 + nop + jp dist_tlu_4 + nop + jp dist_tlu_3 + nop + jp dist_tlu_2 + nop + jp dist_tlu_1 + + HUFFDEC dist_tlu_0, 0, dist_sym + HUFFDEC dist_tlu_1, 1, dist_sym + HUFFDEC dist_tlu_2, 1, dist_sym + HUFFDEC dist_tlu_3, 1, dist_sym + HUFFDEC dist_tlu_4, 1, dist_sym + HUFFDEC dist_tlu_5, 1, dist_sym + HUFFDEC dist_tlu_6, 1, dist_sym + HUFFDEC dist_tlu_7, 1, dist_sym + jp dist_tlu_0 + +dist_sym: + inc hl + ld a, (hl) + add a, a + ld l, a + add a, a + add a, l + ld hl, distancecodetable + add a, l + ld l, a + ld a, h + adc a, 0 + ld h, a + + push de + ld e, (hl) + inc hl + ld d, (hl) + inc hl + push de + ld e, (hl) + inc hl + ld d, (hl) + inc hl + ld (dcall1 + 1), de + ld e, (hl) + inc hl + ld d, (hl) + ld (dcall2 + 1), de + +dcall1 EQU $ + call #0000 + ld e, a +dcall2 EQU $ + call #0000 + ld d, a + + pop hl + add hl, de + ex de, hl + ld hl, 0 + or a + sbc hl, de + pop de + add hl, de ; HL = write_ptr - distance + + +; ============================================================================ +; BACKREF COPY -- TSConfig paging for history access +; ============================================================================ + push bc +dst_page_m2 EQU $ + ld a, 0 + ld bc, PORT_W0 + out (c), a ; Win 0 = dest_page - 2 + inc a + ld b, HIGH PORT_W1 + out (c), a ; Win 1 = dest_page - 1 + pop bc + + ldir + + ; Restore Win 0+1 = source pages (BC=0 after LDIR) +src_page EQU $ + ld a, 0 + OUT_W0 + inc a + ld b, HIGH PORT_W1 ; C still = #AF from OUT_W0 + out (c), a ; Win 1 = src_page + 1 + + bit 6, d + jp z, decode_tableloop + ; fall through + + +; ============================================================================ +; FLUSH DECODE BUFFER to destination page +; ============================================================================ +copy_decoded_page: + ld (cpd_extra + 1), de + +dst_page EQU $ + ld a, 0 + OUT_W0 + +dst EQU $ + ld de, 0 + ld a, e + or d + jr z, cpd_whole + + ; Partial page + ld hl, #4000 + or a + sbc hl, de + ld c, l + ld b, h + ld l, e + ld a, d + or #80 + ld h, a + ldir + jr cpd_done + +cpd_whole: + ld hl, #8000 + ld de, #0000 + ld bc, #4000 + ldir + +cpd_done: + ld hl, 0 + ld (dst + 1), hl + ld a, (dst_page + 1) + inc a + cp #60 ; megabuffer limit (0x20-0x5F) + jp nc, fail ; overflow → abort + ld (dst_page + 1), a + dec a + dec a + ld (dst_page_m2 + 1), a + + ; DBG: 0x21 = cpd page flushed + DBG_OUT_PAGES #21 + + ld de, #8000 + +cpd_extra EQU $ + ld bc, #0000 + ld a, b + and #3F + ld b, a + or c + jr z, cpd_no_extra + ld hl, #C000 + ldir + +cpd_no_extra: + ; Source rollover + exx + bit 6, h + jr z, cpd_no_srcroll + ld a, h + and #3F + ld h, a + ld a, (src_page + 1) + inc a + ld (src_page + 1), a +cpd_no_srcroll: + exx + ld a, (src_page + 1) + OUT_W0 + inc a + ld b, HIGH PORT_W1 ; C still = #AF from OUT_W0 + out (c), a ; Win 1 = src_page + 1 + push de ; save decode write ptr (DE clobbered by progress_update) + call progress_update + pop de ; restore decode write ptr + jp decode_tableloop + + +; ============================================================================ +; END_BLOCK -- last block check, final flush, exit +; ============================================================================ +END_BLOCK: +this_was_last_block EQU $ + ld a, 0 + or a + jp z, NEW_BLOCK + + ; Final flush + ld a, (dst_page + 1) + OUT_W0 + ld de, (dst + 1) + ld hl, (interim_dst + 1) + or a + sbc hl, de + ld c, l + ld a, h + and #3F + ld b, a + or c + jr z, exit_ok + ld l, e + ld a, d + and #3F + ld d, a + or #80 + ld h, a + ldir + +exit_ok: + ; DBG: 0x30 = exit_ok + DBG_OUT_PAGES #30 + +wc_saved_pg2 EQU $ + ld a, 0 + OUT_W2 + xor a + ret + +fail: + ; DBG: 0x31 = fail + DBG_OUT #31 + + ld a, (wc_saved_pg2 + 1) + OUT_W2 + ld a, 1 + scf + ret + + +; ============================================================================ +; PROGRESS BAR UPDATE (Bresenham proportional column fill) +; +; Called after each output page flush (from copy_decoded_page and bt0 flush). +; Briefly remaps Win 0 to the text screen page, writes green attr bytes +; to fill the progress bar proportionally, restores Win 0 to source page. +; +; Destroys: A, BC, DE, HL. Alternate registers untouched. +; Parameters in pb_* block at 0xC1F0 (set by inflate_call.s before entry). +; ============================================================================ +progress_update: + ld a, (pb_total) + or a + ret z ; 0 = disabled + + ; Bar already full? + ld a, (pb_col) + ld hl, pb_bar_w + cp (hl) + ret nc ; col >= width → done + + ; Bresenham: error += bar_width + ld a, (pb_error) + add a, (hl) ; (hl) still = pb_bar_w + ld d, a ; D = new error + + ; E = total + ld a, (pb_total) + ld e, a + + ; error < total → no column this time + ld a, d + cp e + jr c, pb_save_err ; skip fill, just save error + + ; ═══ At least one column to fill ═══ + ; Map Win 0 → text screen page + ld a, (pb_text_pg) + OUT_W0 ; destroys BC + + ; HL = attr base address (bit7 of L is already set) + ld a, (pb_attr_hi) + ld h, a + ld a, (pb_attr_lo) + ld l, a + ld a, (pb_col) + add a, l + ld l, a ; attr area: stride 1, no overflow + ; C = green attr value, B = bar width limit + ld a, (pb_green) + ld c, a + ld a, (pb_bar_w) + ld b, a + +pb_fill_loop: + ld (hl), c ; write green attr + inc l ; next column (stride 1, same row) + + ; pb_col++ + ld a, (pb_col) + inc a + ld (pb_col), a + cp b ; col >= bar_width? + jr nc, pb_fill_done + + ; error -= total + ld a, d + sub e + ld d, a + cp e ; still >= total? + jr nc, pb_fill_loop + +pb_fill_done: + ; Restore Win 0 → source page + ld a, (src_page + 1) + OUT_W0 ; destroys BC + +pb_save_err: + ld a, d + ld (pb_error), a + ret + + +; ============================================================================ +; GZIP HEADER SKIP ROUTINES +; ============================================================================ +drop_gz_fextra: + push hl + push bc + call read8bits + ld l, a + call read8bits + ld h, a + or c + jp z, fextra_ret + ld bc, 255 +fextra_lp: + ld a, h + or a + jr z, fextra_last + ld a, 255 + call dropbytes + or a + sbc hl, bc + jr fextra_lp +fextra_last: + ld a, l + call dropbytes +fextra_ret: + pop bc + pop hl + ret + +drop_gz_fname: +drop_gz_fcomment: + call read8bits + or a + jr nz, drop_gz_fname + ret + +drop_gz_fhcrc: + ld a, 2 + call dropbytes + ret + + +; ============================================================================ +; UTILITY +; ============================================================================ +dropbytes: + or a + ret z + push bc + ld b, a +db_lp: call read8bits + djnz db_lp + pop bc + ret + + +; ============================================================================ +; makeHuffmanTable +; ============================================================================ +makeHuffmanTable: + ld (mht_nsym + 1), bc + ld (mht_clens + 1), hl + ld (mht_tstore + 1), de + + ld hl, codelength_count + ld b, 18 +mht_clr: + ld (hl), 0 + inc hl + ld (hl), 0 + inc hl + djnz mht_clr + +mht_nsym EQU $ + ld bc, #0000 +mht_clens EQU $ + ld hl, #0000 + ld de, codelength_count + +mht_cnt: + ld a, (hl) + inc hl + add a, a + ld e, a + ld a, (de) + inc a + ld (de), a + or a + jr nz, mht_cnt_ok + inc de + ld a, (de) + inc a + ld (de), a +mht_cnt_ok: + dec bc + ld a, b + or c + jr nz, mht_cnt + + ; DBG: 0xB1 = mht_cnt done (count loop) + DBG_OUT #B1 + + xor a + ld e, a + ld (de), a + inc de + ld (de), a + + ld e, a + ld d, a + ld c, a + ld b, 1 +mht_sc: + ld h, HIGH codelength_count + ld a, b + dec a + add a, a + ld l, a + ld a, (hl) + add a, e + ld e, a + inc hl + ld a, (hl) + adc a, d + ld d, a + ld a, c + adc a, 0 + ld c, a + rl e + rl d + rl c + ld h, HIGH next_code + ld a, b + rla + rla + ld l, a + ld (hl), e + inc hl + ld (hl), d + inc hl + ld (hl), c + inc hl + ld (hl), 0 + inc b + ld a, b + cp 19 + jr c, mht_sc + + ; DBG: 0xB2 = mht_sc done (start codes) + DBG_OUT #B2 + + ld bc, 0 +mht_assign: + ld hl, (mht_clens + 1) + add hl, bc + ld a, (hl) + or a + jr z, mht_nocode + + rla + rla + ld l, a + ld h, HIGH next_code + push hl + ld e, (hl) + inc hl + ld d, (hl) + inc hl + ld a, (hl) + + ld hl, codes + add hl, bc + add hl, bc + add hl, bc + add hl, bc + ld (hl), e + inc hl + ld (hl), d + inc hl + ld (hl), a + + pop hl + inc e + ld (hl), e + inc hl + ld e, a + ld a, d + adc a, 0 + ld (hl), a + inc hl + ld a, e + adc a, 0 + ld (hl), a + +mht_nocode: + inc bc + ld hl, (mht_nsym + 1) + or a + sbc hl, bc + jr nz, mht_assign + + ; DBG: 0xB3 = mht_assign done (code assignment) + DBG_OUT #B3 + + ; Build lookup tables + ld hl, (mht_tstore + 1) + xor a + ld (hl), a + inc hl + ld (hl), a + inc hl + ld (hl), a + inc hl + ld (hl), a + inc hl + ld (next_htable_store + 1), hl + + ld hl, 0 + ld (mht_tabulated + 1), hl + ld de, (mht_tabulated + 1) + +mht_predec: + ld hl, (mht_clens + 1) + add hl, de + ld a, (hl) + or a + jp z, mht_unused + ld b, a + + ld hl, codes + add hl, de + add hl, de + add hl, de + add hl, de + ld d, (hl) + inc hl + ld c, (hl) + inc hl + ld a, (hl) + rra + rr c + rr d + rra + rr c + rr d + rra + ld e, a + + ld a, 18 + sub b + jr z, mht_nodiscard + or a +mht_discard: + rl e + rl d + rl c + dec a + jr nz, mht_discard +mht_nodiscard: + +mht_tstore EQU $ + ld hl, #0000 + +mht_nextbit: + rl e + rl d + rl c + jr nc, mht_bitzero + inc hl + inc hl +mht_bitzero: + dec b + jr z, mht_wrsym + + ld a, (hl) + or a + jr z, mht_wrtable + + inc hl + ld h, (hl) + add a, a + add a, a + ld l, a + jr mht_nextbit + +mht_wrtable: + push de +next_htable_store EQU $ + ld de, #0000 + ld a, e + rra + scf + rra + ld (hl), a + inc hl + ld (hl), d + ld l, e + ld h, d + xor a + ld (de), a + inc de + ld (de), a + inc de + ld (de), a + inc de + ld (de), a + inc de + ld (next_htable_store + 1), de + pop de + jr mht_nextbit + +mht_wrsym: +mht_tabulated EQU $ + ld de, #0000 + ld (hl), d + inc hl + ld (hl), e + +mht_unused: + inc de + ld (mht_tabulated + 1), de + ld hl, (mht_nsym + 1) + or a + sbc hl, de + ld a, h + or l + jp nz, mht_predec + ret + + +; ============================================================================ +; decodeProtoHuffman +; ============================================================================ +decodeProtoHuffman: + ld a, l + rra + rra + ld l, a + ld (dph_begin + 1), hl + IFDEF DBG_ENABLE + ; DBG: 0xBF = decodeProtoHuffman entry + push af + push bc + ld a, #BF + ld bc, DBG_STATUS + out (c), a + pop bc + pop af + ENDIF + jr dph_jump + +dph_tloop: + inc hl + ld h, (hl) + ld l, a +dph_jump: + call read1bit + rra + ld a, l + adc a, a + add a, a + ld l, a + ld a, (hl) + bit 7, a + jr nz, dph_tloop + inc hl + ld a, (hl) + IFDEF DBG_ENABLE + ; DBG: 0xC1 = leaf found, FEAF = symbol value + push af + push bc + ld bc, DBG_SRC + out (c), a + ld a, #C1 + ld bc, DBG_STATUS + out (c), a + pop bc + pop af + ENDIF + cp 16 + jr nc, dph_special + ld (de), a + inc de + dec bc +dph_loop: + IFDEF DBG_ENABLE + ; DBG: 0xC0 = dph loop iteration + push af + push bc + ld a, #C0 + ld bc, DBG_STATUS + out (c), a + pop bc + pop af + ENDIF +dph_begin EQU $ + ld hl, #0000 + ld a, b + or c + jr nz, dph_jump + ret + +dph_special: + IFDEF DBG_ENABLE + ; DBG: 0xC2 = special code, FEAF = code value + push af + push bc + ld bc, DBG_SRC + out (c), a + ld a, #C2 + ld bc, DBG_STATUS + out (c), a + pop bc + pop af + ENDIF + cp 17 + jr z, dph_17 + jr nc, dph_18 + ; proto 16 + call read2bits + add a, 3 + ld h, a + dec de + ld a, (de) + inc de +dph_p16: + ld (de), a + inc de + dec bc + dec h + jr nz, dph_p16 + jr dph_loop + +dph_17: + call read3bits + add a, 3 + ld h, a + xor a +dph_p17: + ld (de), a + inc de + dec bc + dec h + jr nz, dph_p17 + jr dph_loop + +dph_18: + call read7bits + add a, 11 + ld h, a + xor a +dph_p18: + ld (de), a + inc de + dec bc + dec h + jr nz, dph_p18 + jr dph_loop + + +; ============================================================================ +; BIT READ FUNCTIONS +; Alternate regs: HL'=src ptr, C'=current byte, B'=valid bits +; ============================================================================ + +read0bits: + xor a + ret + +read1bit: +read1bits: + exx + dec b + jr z, r1_refill + xor a + rr c + rla + exx + ret +r1_refill: + xor a + rr c + rla + ld b, 8 + ld c, (hl) + inc hl + exx + ret + +; -- read2bits --------------------------------------------------------------- +read2bits: + exx + ld a, b + sub 2 + jr c, r2_need + jr z, r2_exact + ld b, a + ld e, c + ld a, c + rra + rra + and #3F + ld c, a + ld a, e + and #03 + exx + ret +r2_exact: + ld a, c + ld b, 8 + ld c, (hl) + inc hl + exx + ret +r2_need: + ld e, c + ld d, b + ld c, (hl) + inc hl + ld a, c +r2_sl: add a, a + djnz r2_sl + or e + and #03 + ld e, a + ld a, 2 + sub d + ld b, a + ld a, c +r2_sr: or a + rra + djnz r2_sr + ld c, a + ld a, d + sub 2 + add a, 8 + ld b, a + ld a, e + exx + ret + +; -- read3bits --------------------------------------------------------------- +read3bits: + exx + ld a, b + sub 3 + jr c, r3_need + jr z, r3_exact + ld b, a + ld e, c + ld a, c + rra + rra + rra + and #1F + ld c, a + ld a, e + and #07 + exx + ret +r3_exact: + ld a, c + ld b, 8 + ld c, (hl) + inc hl + exx + ret +r3_need: + ld e, c + ld d, b + ld c, (hl) + inc hl + ld a, c +r3_sl: add a, a + djnz r3_sl + or e + and #07 + ld e, a + ld a, 3 + sub d + ld b, a + ld a, c +r3_sr: or a + rra + djnz r3_sr + ld c, a + ld a, d + sub 3 + add a, 8 + ld b, a + ld a, e + exx + ret + +; -- read4bits --------------------------------------------------------------- +read4bits: + exx + ld a, b + sub 4 + jr c, r4_need + jr z, r4_exact + ld b, a + ld e, c + ld a, c + rra + rra + rra + rra + and #0F + ld c, a + ld a, e + and #0F + exx + ret +r4_exact: + ld a, c + ld b, 8 + ld c, (hl) + inc hl + exx + ret +r4_need: + ld e, c + ld d, b + ld c, (hl) + inc hl + ld a, c +r4_sl: add a, a + djnz r4_sl + or e + and #0F + ld e, a + ld a, 4 + sub d + ld b, a + ld a, c +r4_sr: or a + rra + djnz r4_sr + ld c, a + ld a, d + sub 4 + add a, 8 + ld b, a + ld a, e + exx + ret + +; -- read5bits --------------------------------------------------------------- +read5bits: + exx + ld a, b + sub 5 + jr c, r5_need + jr z, r5_exact + ld b, a + ld e, c + ld a, c + rlca + rlca + rlca + and #07 + ld c, a + ld a, e + and #1F + exx + ret +r5_exact: + ld a, c + ld b, 8 + ld c, (hl) + inc hl + exx + ret +r5_need: + ld e, c + ld d, b + ld c, (hl) + inc hl + ld a, c +r5_sl: add a, a + djnz r5_sl + or e + and #1F + ld e, a + ld a, 5 + sub d + ld b, a + ld a, c +r5_sr: or a + rra + djnz r5_sr + ld c, a + ld a, d + sub 5 + add a, 8 + ld b, a + ld a, e + exx + ret + +; -- read6bits --------------------------------------------------------------- +read6bits: + exx + ld a, b + sub 6 + jr c, r6_need + jr z, r6_exact + ld b, a + ld e, c + ld a, c + rlca + rlca + and #03 + ld c, a + ld a, e + and #3F + exx + ret +r6_exact: + ld a, c + ld b, 8 + ld c, (hl) + inc hl + exx + ret +r6_need: + ld e, c + ld d, b + ld c, (hl) + inc hl + ld a, c +r6_sl: add a, a + djnz r6_sl + or e + and #3F + ld e, a + ld a, 6 + sub d + ld b, a + ld a, c +r6_sr: or a + rra + djnz r6_sr + ld c, a + ld a, d + sub 6 + add a, 8 + ld b, a + ld a, e + exx + ret + +; -- read7bits --------------------------------------------------------------- +read7bits: + exx + ld a, b + sub 7 + jr c, r7_need + jr z, r7_exact + ld b, a + ld e, c + ld a, c + rlca + and #01 + ld c, a + ld a, e + and #7F + exx + ret +r7_exact: + ld a, c + ld b, 8 + ld c, (hl) + inc hl + exx + ret +r7_need: + ld e, c + ld d, b + ld c, (hl) + inc hl + ld a, c +r7_sl: add a, a + djnz r7_sl + or e + and #7F + ld e, a + ld a, 7 + sub d + ld b, a + ld a, c +r7_sr: or a + rra + djnz r7_sr + ld c, a + ld a, d + sub 7 + add a, 8 + ld b, a + ld a, e + exx + ret + +; -- read8bits --------------------------------------------------------------- +read8bits: + exx + ld a, b + sub 8 + jr c, r8_need + ; b was 8 (exact) + ld a, c + ld b, 8 + ld c, (hl) + inc hl + exx + ret +r8_need: + ld e, c + ld d, b + ld c, (hl) + inc hl + ld a, c +r8_sl: add a, a + djnz r8_sl + or e + ld e, a + ld a, 8 + sub d + ld b, a + ld a, c +r8_sr: or a + rra + djnz r8_sr + ld c, a + ld a, d + sub 8 + add a, 8 + ld b, a + ld a, e + exx + ret + +; Byte-align +readalign: + exx + ld c, (hl) + inc hl + ld b, 8 + exx + ret + + +; ============================================================================ +; DATA TABLES +; ============================================================================ + +lengthcodetable: + DW 3, read0bits + DW 4, read0bits + DW 5, read0bits + DW 6, read0bits + DW 7, read0bits + DW 8, read0bits + DW 9, read0bits + DW 10, read0bits + DW 11, read1bits + DW 13, read1bits + DW 15, read1bits + DW 17, read1bits + DW 19, read2bits + DW 23, read2bits + DW 27, read2bits + DW 31, read2bits + DW 35, read3bits + DW 43, read3bits + DW 51, read3bits + DW 59, read3bits + DW 67, read4bits + DW 83, read4bits + DW 99, read4bits + DW 115, read4bits + DW 131, read5bits + DW 163, read5bits + DW 195, read5bits + DW 227, read5bits + DW 258, read0bits + +distancecodetable: + DW 1, read0bits, read0bits + DW 2, read0bits, read0bits + DW 3, read0bits, read0bits + DW 4, read0bits, read0bits + DW 5, read1bits, read0bits + DW 7, read1bits, read0bits + DW 9, read2bits, read0bits + DW 13, read2bits, read0bits + DW 17, read3bits, read0bits + DW 25, read3bits, read0bits + DW 33, read4bits, read0bits + DW 49, read4bits, read0bits + DW 65, read5bits, read0bits + DW 97, read5bits, read0bits + DW 129, read6bits, read0bits + DW 193, read6bits, read0bits + DW 257, read7bits, read0bits + DW 385, read7bits, read0bits + DW 513, read8bits, read0bits + DW 769, read8bits, read0bits + DW 1025, read8bits, read1bits + DW 1537, read8bits, read1bits + DW 2049, read8bits, read2bits + DW 3073, read8bits, read2bits + DW 4097, read8bits, read3bits + DW 6145, read8bits, read3bits + DW 8193, read8bits, read4bits + DW 12289, read8bits, read4bits + DW 16385, read8bits, read5bits + DW 24577, read8bits, read5bits + +code_length_orders: + DB 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + + DB "tsconf-inflate", 0 + + DISPLAY "Code size: ", /D, $ - #C000, " bytes" + + +; ============================================================================ +; RAM TABLES (zero-initialized) +; ============================================================================ + + ALIGN 256 +codelength_count: + DS 36 + + ALIGN 32 +codelengths: + DS 288 + 33 + + ALIGN 256 +next_code: + DS 128 + + ALIGN 256 +codes: + DS 288 * 4 + + ALIGN 4 +Huffman_Tables_Start: + + DISPLAY "Huffman space: ", /D, #FFFE - Huffman_Tables_Start + 2, " bytes" + + SAVEBIN "build/inflate.bin", #C000, $ - #C000 diff --git a/src_sdcc/asm/inflate_call.s b/src_sdcc/asm/inflate_call.s new file mode 100644 index 0000000..0bc907b --- /dev/null +++ b/src_sdcc/asm/inflate_call.s @@ -0,0 +1,330 @@ +;============================================================================== +; inflate_call.s — SDCC обёртка (sdcccall(1)) для вызова inflate из C +; +; uint8_t inflate_vgz(uint8_t src_page, uint8_t dst_page); +; void copy_pages_to_tap(uint8_t n_pages); +; +; sdcccall(1): A = первый аргумент +; Возврат inflate_vgz: A = 0 (успех) / 1 (ошибка) +; +; Inflate binary живёт в экстра-странице WMF. Обёртка маппит её в Win 3 +; (ORG 0xC000) и делает JP 0xC000. Decode buffer — в Win 2 (0x8000), +; настраивается самим inflate. При выходе inflate восстанавливает Win 2. +; +; ╔══════════════════════════════════════════════════════════════════════╗ +; ║ Почему код inflate обязан быть в Win 3, а не Win 2: ║ +; ║ DEFLATE backref вычисляет HL = write_ptr − distance. ║ +; ║ При decode buffer в Win 2 (0x8000) → HL ∈ [0x0000..0xBFFF], ║ +; ║ никогда не попадает в Win 3 (0xC000+), где живёт код. ║ +; ║ Если поменять местами (код Win 2, decode Win 3) → HL может ║ +; ║ попасть в 0x8000–0xBFFF, что потребует ремапа Win 2 во время ║ +; ║ исполнения из неё — невозможно. ║ +; ╚══════════════════════════════════════════════════════════════════════╝ +; +; copy_pages_to_tap: +; Копирует N страниц из мегабуфера (#20+) в TAP-страницы (#A1+). +; DI на время копирования, восстанавливает Win 0/Win 3 из sys vars. +; +; inflate_vgz: +; 1. Сохранить аргументы, IX, SP, Win 1 +; 2. DI, маппить Win 3 = inflate page через порт +; 3. SP = 0xFFF0, push return addr → JP 0xC000 +; 4. По возврату: SP, Win 1 восстанавливаем, EI, IX → ret +; Win 0 / Win 3 — восстанавливает вызывающий C-код через WC API +;============================================================================== + + .module inflate_call + + .globl _inflate_vgz + .globl _copy_pages_to_tap + + ; Progress bar params (filled by C before inflate_vgz) + .globl _ifl_pb_text_pg + .globl _ifl_pb_attr_ofs + .globl _ifl_pb_total + .globl _ifl_pb_bw + .globl _ifl_pb_green + +;--- Константы --------------------------------------------------------------- +INFLATE_PAGE_IDX = 4 ; индекс inflate в плагине (1 code + 3 LUT) + ; физ. страница = base_page + INFLATE_PAGE_IDX + ; base_page читается из WC sys var (0x6002) +PORT_W0 = 0x10AF ; TSConfig Win 0 page port +PORT_W1 = 0x11AF ; TSConfig Win 1 page port +PORT_W3 = 0x13AF ; TSConfig Win 3 page port + +;--- DATA (0xB800+) ---------------------------------------------------------- + .area _DATA + +_ifl_src_pg: .ds 1 ; src_page (физ. стр. TAP) +_ifl_dst_pg: .ds 1 ; dst_page (физ. стр. мегабуфера) +_ifl_sv_pg1: .ds 1 ; saved Win 1 physical page +_ifl_sv_pg2: .ds 1 ; saved Win 2 physical page → inflate D reg +_ifl_sv_sp: .ds 2 ; saved SP +_ifl_inflate_pg: .ds 1 ; computed inflate phys page (debug/scan) + +; Progress bar parameters (set by C, copied to inflate page at 0xC1F0) +_ifl_pb_text_pg: .ds 1 ; text screen physical page +_ifl_pb_attr_ofs: .ds 2 ; attr base offset within text page (lo, hi) +_ifl_pb_total: .ds 1 ; estimated total output pages (0=disabled) +_ifl_pb_bw: .ds 1 ; bar width in columns +_ifl_pb_green: .ds 1 ; green attr value + +;--- CODE (0x8000+) ---------------------------------------------------------- + .area _CODE + +_inflate_vgz:: + push ix ; callee-saved (SDCC) + + ;--- 1. сохранить аргументы --- + ld (_ifl_src_pg), a ; A = src_page + ld a, l + ld (_ifl_dst_pg), a ; L = dst_page + + ;--- 2. DI --- + di + + ;--- 3. сохранить Win 1, Win 2 из WC sys vars (@ 0x6001/0x6002) --- + ld a, (0x6001) + ld (_ifl_sv_pg1), a + ld a, (0x6002) + ld (_ifl_sv_pg2), a + + ;--- 4. сохранить SP --- + ld (_ifl_sv_sp), sp + +.ifdef DBG_ENABLE + ;--- DBG: 0x01 = saved context, about to map inflate page --- + ld bc, #0xFFAF + ld a, #0x01 + out (c), a +.endif + + ;--- 5. маппить Win 3 = inflate page (dynamic: base + IDX) --- + ld a, (0x6002) ; Win 2 phys page = plugin base +.ifdef DBG_ENABLE + ld bc, #0xFEAF + out (c), a ; DBG: FEAF = base page from 0x6002 +.endif + add a, #INFLATE_PAGE_IDX ; + 5 = inflate page + ld (_ifl_inflate_pg), a ; save computed inflate page +.ifdef DBG_ENABLE + ld bc, #0xFDAF + out (c), a ; DBG: FDAF = computed inflate page +.endif + ld bc, #PORT_W3 + out (c), a + +.ifdef DBG_ENABLE + ;--- DBG: 0x02 = Win3 mapped to inflate page --- + ld bc, #0xFFAF + ld a, #0x02 + out (c), a + ld a, (0xC000) + ld bc, #0xFEAF + out (c), a ; byte[0] — expect 0xF5 (push af) + ld a, (0xC001) + ld bc, #0xFDAF + out (c), a ; byte[1] — expect 0xC5 (push bc) + + ;=== DBG: scan pages base+3..base+7 looking for inflate === + ld a, (0x6002) + add a, #3 + ld d, a + ld e, #0 +_ifl_scan_loop: + ld a, d + ld bc, #PORT_W3 + out (c), a + ld a, #0xD0 + add a, e + ld bc, #0xFFAF + out (c), a + ld a, d + ld bc, #0xFDAF + out (c), a + ld a, (0xC000) + ld bc, #0xFEAF + out (c), a + inc d + inc e + ld a, e + cp #5 + jr nz, _ifl_scan_loop + + ;--- DBG: 0x04 = scan done, re-map Win3 to inflate page --- + ld bc, #0xFFAF + ld a, #0x04 + out (c), a + ld a, (_ifl_inflate_pg) + ld bc, #PORT_W3 + out (c), a +.endif + + ;--- 6. стек в Win 3, push return addr --- + ld sp, #0xFFF0 + ld hl, #_ifl_return + push hl + +.ifdef DBG_ENABLE + ;--- DBG: 0x05 = SP set, return addr pushed, loading regs --- + ld bc, #0xFFAF + ld a, #0x05 + out (c), a + ld a, (_ifl_src_pg) + ld bc, #0xFEAF + out (c), a + ld a, (_ifl_dst_pg) + ld bc, #0xFDAF + out (c), a +.endif + + ;--- 6b. Copy progress bar params to inflate page (0xC1F0+) --- + ; Win 3 = inflate page (mapped in step 5) + ; Win 2 = _CODE/_DATA (still accessible for reading _ifl_pb_*) + ld a, (_ifl_pb_text_pg) + ld (0xC1F0), a + ld hl, (_ifl_pb_attr_ofs) + ld (0xC1F1), hl + ld a, (_ifl_pb_total) + ld (0xC1F3), a + ld a, (_ifl_pb_bw) + ld (0xC1F4), a + ld a, (_ifl_pb_green) + ld (0xC1F5), a + ; Initialize runtime state + xor a + ld (0xC1F6), a ; pb_error = 0 + ld (0xC1F7), a ; pb_col = 0 + + ;--- 7. входные регистры inflate: A=src, E=dst, D=saved_pg2 --- + ld a, (_ifl_sv_pg2) + ld d, a ; D = saved Win 2 page + ld a, (_ifl_dst_pg) + ld e, a ; E = dst_page + ld a, (_ifl_src_pg) ; A = src_page + +.ifdef DBG_ENABLE + ;--- DBG: 0x06 = about to JP 0xC000 --- + push af + push bc + ld bc, #0xFFAF + ld a, #0x06 + out (c), a + pop bc + pop af +.endif + + ;--- 8. запуск inflate --- + ; JP 0xC200 = inflate_init (safe zone, survives backref LDIR) + ; НЕ JP 0xC000! Entry zone 0xC000–0xC1FF перезаписывается + ; LDIR overflow при распаковке backref'ов. + jp 0xC200 + + ;--- сюда вернёмся по RET из inflate --- + ; A = 0 (ok) / 1 (error) + ; Win 2 уже восстановлен inflate → _DATA и код доступны +_ifl_return: + ld e, a ; результат → E + +.ifdef DBG_ENABLE + ;--- DBG: 0x03 = returned from inflate, A=result --- + ld bc, #0xFFAF + ld a, #0x03 + out (c), a + ld a, e + ld bc, #0xFEAF + out (c), a +.endif + + ;--- восстановить SP (→ C-стек, сразу после push ix) --- + ld sp, (_ifl_sv_sp) + + ;--- восстановить Win 1 (WC API точка входа @ 0x6006) --- + ld a, (_ifl_sv_pg1) + ld bc, #PORT_W1 + out (c), a + + ;--- вернуть результат --- + ld a, e + pop ix ; восстановить IX + ei ; EI + задержка 1 instr → ret атомарен + ret + + +;============================================================================== +; void copy_pages_to_tap(uint8_t n_pages) +; +; Копирует n_pages страниц из мегабуфера (#20..#20+n-1) в TAP (#A1..#A1+n-1). +; Использует прямые port-записи в Win 0 / Win 3. +; DI на время работы; при выходе: Win 0, Win 3 восстановлены, EI. +; +; sdcccall(1): A = n_pages +;============================================================================== + +MEGABUF_START = 0x20 ; первая физ. страница мегабуфера +TAP_START = 0xA1 ; первая физ. страница TAP + +_copy_pages_to_tap:: + or a + ret z ; n=0 → ничего делать + + push ix + + ld c, a ; C = n_pages (сохраним) + di + + ;--- сохранить текущие страницы Win 0 / Win 3 --- + ld a, (0x6000) + ld (_cpt_sv_pg0), a + ld a, (0x6003) + ld (_cpt_sv_pg3), a + + ;--- подготовить счётчики --- + ld d, #MEGABUF_START ; D = src phys page + ld e, #TAP_START ; E = dst phys page + ld b, c ; B = loop counter + +_cpt_loop: + push bc + push de + + ;--- Map Win 0 = megabuf page D --- + ld a, d + ld bc, #PORT_W0 + out (c), a + + ;--- Map Win 3 = TAP page E --- + ld a, e + ld b, #0x13 ; C ещё 0xAF от предыдущего BC + out (c), a + + ;--- LDIR 16 КБ: Win 0 → Win 3 --- + ld hl, #0x0000 + ld de, #0xC000 + ld bc, #0x4000 + ldir + + pop de + inc d ; следующая megabuf page + inc e ; следующая TAP page + pop bc + djnz _cpt_loop + + ;--- восстановить Win 0 --- + ld a, (_cpt_sv_pg0) + ld bc, #PORT_W0 + out (c), a + + ;--- восстановить Win 3 --- + ld a, (_cpt_sv_pg3) + ld b, #0x13 + out (c), a + + ei + pop ix + ret + +;--- static vars for copy (в CODE — не в DATA, чтобы не зависеть от Win 2) --- +_cpt_sv_pg0: .ds 1 +_cpt_sv_pg3: .ds 1 diff --git a/src_sdcc/asm/isr.s b/src_sdcc/asm/isr.s new file mode 100644 index 0000000..ff7ad17 --- /dev/null +++ b/src_sdcc/asm/isr.s @@ -0,0 +1,663 @@ +;============================================================================== +; @file isr.s +; Формат записи: [VSINTH, VSINTL, HSINT, next_lo, next_hi] +; Читается тремя OUTI: BC=#24AF -> VSINTH, #23AF -> VSINTL, #22AF -> HSINT, +; затем читается next_ptr. +; +; Командные буферы: cmd_buf_a, cmd_buf_b (по 512 байт, 128 команд). +; +; Переменные ISR (volatile, доступны из C через isr.h): +; _isr_active_buf : 0=A, 1=B (пишет ISR, читает main) +; _isr_enabled : 0/1 (пишет main, читает ISR) +; _isr_play_seconds : uint16 (счётчик секунд, пишет ISR) +; _isr_read_ptr : uint16 (указатель чтения в активном буфере) +; +; Расход памяти _DATA: +; _im2_table_buf : 512 байт (гарантирует 256-aligned) +; _cmd_buf_a/b : 2 x 512 = 1024 байт +; Переменные : 14 байт +; Итого : ~1550 байт +;============================================================================== + + .module isr_mod + + .globl _isr_ms_ctrl + .globl _isr_active_buf + .globl _isr_enabled + .globl _isr_tick_ctr + .globl _isr_border_color + .globl _isr_play_seconds + .globl _isr_read_ptr + .globl _isr_done + .globl _cmd_buf_a + .globl _cmd_buf_b + .globl _isr_init + .globl _isr_deinit + .globl _call_wc_handler + .globl _spectrum_levels + .globl _vgm_chip_type + +; Импорт таблицы позиций из pos_table.s + .globl pos_table + +; Порты TSConfig +PORT_HSINT = 0x22AF +PORT_VSINTL = 0x23AF +PORT_VSINTH = 0x24AF +PORT_INTMASK = 0x2AAF +PORT_SYSCONF = 0x20AF ; System Config (turbo bits [1:0]) +TURBO_7MHZ = 0x01 ; SysConf value: 7 MHz +TURBO_14MHZ = 0x02 ; SysConf value: 14 MHz + +; Адрес вектора FRAME INT в IM2-таблице WC (I=#5B, vector=0xFF → #5BFF) +WC_IM2_VEC = 0x5BFF + +; Коды команд (8 типов, шаг 0x20 + ISR_DONE) +CMD_WRITE_AY = 0x00 +CMD_INC_SEC = 0x10 +CMD_WRITE_AY2 = 0x20 +CMD_CALL_WC = 0x30 +CMD_WRITE_B0 = 0x40 +CMD_SKIP_TICKS= 0x50 +CMD_WRITE_SAA = 0x60 +CMD_WRITE_B1 = 0x80 +CMD_WRITE_WAVE= 0x70 +CMD_WAIT = 0xC0 +CMD_END_BUF = 0xE0 +CMD_ISR_DONE = 0xF0 + +; Отладка: раскрасить бордюр во время ISR (0=выкл, 1=вкл) +DEBUG_BORDER = 0 + +; Порты OPL3 +OPL3_ADDR0 = 0xC4 +OPL3_DATA0 = 0xC5 +OPL3_ADDR1 = 0xC6 +OPL3_DATA1 = 0xC7 + +; Порты OPL4 Wave-часть (ZXM-MoonSound: MOON_WREG/MOON_WDAT в moon_driver.asm) +OPL4_WREG = 0x7E +OPL4_WDAT = 0x7F + +; Порты AY-3-8910 / YM2149 +AY_ADDR = 0xFD ; C-byte, B=0xFF → #FFFD +AY_DATA_B = 0xBF ; B-byte, C=#FD → #BFFD + +; Порты SAA1099 (MultiSound card, dual-chip via bit7 of address) +; addr=#01FF data=#00FF +; bit7 of reg byte written to #01FF selects chip (0=chip0, 1=chip1) +; chip1 clk on: automatic on first write to chip1 (address with bit7=1) +; both clk off: OUT #FFFD, d[3]=1 + +ISR_FREQ = 683 ; informational only (actual timing from pos_table) + +;============================================================================== +; isr_init() — Перехват вектора IM2 WC и настройка 683 Hz +;============================================================================== + .area _CODE + +;============================================================================== +; Вместо собственной IM2-таблицы (512 байт) используем существующую +; таблицу WC. WC работает в IM2 с I=#5B; вектор FRAME (data bus=0xFF) +; читается из адреса #5BFF (2 байта: lo, hi). +; +; isr_init: +; 1. Сохранить текущий вектор (#5BFF) → _isr_saved_vec +; 2. Записать адрес _isr_handler в #5BFF +; 3. Настроить INTMASK, позицию INT, указатели +; 4. IM 2 (для надёжности) +; Вызывающий код делает EI. +; +; isr_deinit: +; 1. Восстановить сохранённый вектор WC +; 2. Сбросить позицию INT на line=0 (1 INT/кадр) +; Вызывающий код делает EI. +;============================================================================== +_isr_init:: + di + + ;--- Сохранить оригинальный вектор WC --- + ld hl, (WC_IM2_VEC) + ld (_isr_saved_vec), hl + + ;--- Установить наш обработчик --- + ld hl, #_isr_handler + ld (WC_IM2_VEC), hl + + ;--- Разрешить только FRAME INT --- + ld bc, #PORT_INTMASK + ld a, #0x01 + out (c), a + + ;--- Инициализировать указатель позиции --- + ld hl, #pos_table + ld (_pos_ptr), hl + + ;--- Инициализировать счётчик ожидания = 0 --- + ld hl, #0 + ld (_isr_wait_ctr), hl + + ;--- Инициализировать read_ptr → cmd_buf_a --- + ld hl, #_cmd_buf_a + ld (_isr_read_ptr), hl + + ;--- Счётчик тиков --- + xor a + ld (_isr_tick_ctr), a + + ;--- Счётчик секунд --- + ld hl, #0 + ld (_isr_play_seconds), hl + + ;--- Reset analyzer previous mask + levels (adjacent) --- + xor a + ld hl, #_spectrum_prev_mask + ld (hl), a + ld de, #(_spectrum_prev_mask + 1) + ld bc, #17 + ldir + + ;--- Начальная позиция: строка 0, hpos 0 --- + ld bc, #PORT_HSINT + out (c), a + ld b, #>PORT_VSINTL + out (c), a + ld b, #>PORT_VSINTH + out (c), a + + ;--- Подтвердить IM2 (WC и так в IM2, но для надёжности) --- + im 2 + ret + +;============================================================================== +; isr_deinit() — Восстановить вектор WC и сбросить INT-позиции +;============================================================================== +_isr_deinit:: + di + + ;--- Проверить: вызывался ли isr_init? --- + ld hl, (_isr_saved_vec) + ld a, h + or l + ret z ; saved_vec == 0 → init не вызывался + + ;--- Восстановить оригинальный вектор WC --- + ld (WC_IM2_VEC), hl + + ;--- Сбросить saved_vec (защита от повторного вызова) --- + ld hl, #0 + ld (_isr_saved_vec), hl + + ;--- Сбросить позицию INT: line=0, hpos=0 (один INT/кадр) --- + ld bc, #PORT_HSINT + xor a + out (c), a + ld b, #>PORT_VSINTL + out (c), a + ld b, #>PORT_VSINTH + out (c), a + + ;--- INTMASK = FRAME only (WC по умолчанию) --- + ld bc, #PORT_INTMASK + ld a, #0x01 + out (c), a + + ; Не делаем EI — вызывающий код решает сам + ret + +;============================================================================== +; ISR_HANDLER — обработчик прерывания FRAME (2734 Гц) +; ─── OUTI-блок и таблицу НЕ ТРОГАТЬ! ──────────────────────────────────── +;============================================================================== + .area _CODE + +_isr_handler:: + push af +.if DEBUG_BORDER + ld a, #0x06 ; DEBUG: yellow border + out (0xFE), a +.endif + push hl + push bc + push de + + ;--- OUTI×3: записать следующую позицию INT из таблицы --- + ; Формат записи: [vsinth_byte, vsintl_byte, hsint_byte, next_lo, next_hi] + ; BC стартует 0x24AF: OUTI→0x24AF(VSINTH), B→0x23; OUTI→0x23AF(VSINTL), B→0x22; OUTI→0x22AF(HSINT) + ld hl, (_pos_ptr) + ld bc, #0x25AF ; B=#25, C=#AF + outi ; → VSINTH (#24AF) outi predecrement B: B=0x24, C=0xAF + outi ; → VSINTL (#23AF) + outi ; → HSINT (#22AF) + ; Следующий ptr (2 байта) + ld a, (hl) + inc hl + ld h, (hl) + ld l, a ; HL = next_ptr + ld (_pos_ptr), hl + + ;--- Счётчик тиков: простой INC byte (21T, всегда) --- + ld hl, #_isr_tick_ctr + inc (hl) ; 11T: wrap at 256 — main loop считает секунды + + ;--- WAIT_CTR: если 0 — выполнять команды --- + ld a, (_isr_enabled) + or a + jp z, _isr_exit ; не активен — быстрый выход + ld hl, (_isr_wait_ctr) + ld a, h + or l + jp z, _isr_exec_cmd + dec hl + ld (_isr_wait_ctr), hl + +_isr_exit: +.if DEBUG_BORDER + ld a, (_isr_border_color) ; DEBUG: restore border from variable + out (0xFE), a +.endif + pop de + pop bc + pop hl + pop af + ei + ret + +;--- Выполнение команд -------------------------------------------------- +_isr_exec_cmd: + ld hl, (_isr_read_ptr) + ex de, hl ; DE = указатель команд + +_isr_cmd_loop: + ld a, (de) + inc de + ; Dispatch: command types — hot path first + ; OPL Bank 0 writes (most common register writes) + cp #CMD_WRITE_B0 + jp z, _isr_write_b0 + ; ★ CMD_SKIP_TICKS — every ISR tick ends with this (promoted from #12) + cp #CMD_SKIP_TICKS + jp z, _isr_skip_ticks + ; OPL Bank 1 writes (OPL3 second bank) + cp #CMD_WRITE_B1 + jp z, _isr_write_b1 + ; Wait (long pauses, less common than SKIP_TICKS) + cp #CMD_WAIT + jp z, _isr_do_wait + ; AY/SAA/end commands (less frequent per-interrupt) + cp #CMD_WRITE_AY + jp z, _isr_write_ay + cp #CMD_WRITE_AY2 + jp z, _isr_write_ay2 + cp #CMD_WRITE_SAA + jp z, _isr_write_saa + ; OPL4 Wave-часть (ZXM-MoonSound), редко используется + cp #CMD_WRITE_WAVE + jp z, _isr_write_wave + ; Rare commands + cp #CMD_END_BUF + jp z, _isr_do_end_buf + cp #CMD_ISR_DONE + jp z, _isr_cmd_done + cp #CMD_INC_SEC + jp z, _isr_inc_sec + cp #CMD_CALL_WC + jp z, _isr_call_wc + ; Unknown command: skip 3 bytes, continue + inc de + inc de + inc de + jp _isr_cmd_loop + +_isr_do_end_buf: + ld a, (_isr_active_buf) + xor #1 + ld (_isr_active_buf), a + jr nz, _isr_end_use_b + ld hl, #_cmd_buf_a + jr _isr_end_save_ptr +_isr_end_use_b: + ld hl, #_cmd_buf_b +_isr_end_save_ptr: + ld (_isr_read_ptr), hl + ex de, hl + jp _isr_cmd_loop + +_isr_write_b0: + ld bc, #PORT_SYSCONF + ld a, #TURBO_7MHZ + out (c), a ; → 7 MHz + ld a, (de) ; reg + ld c, #OPL3_ADDR0 ; B=#20, don't-care for C4-C7 + out (c), a + inc de + ld a, (de) ; val + inc c ; C5 = data port + out (c), a + ld c, #0xAF ; BC=#20AF (B still #20) + ld a, #TURBO_14MHZ + out (c), a ; → 14 MHz + inc de + inc de ; pad + jp _isr_cmd_loop + +_isr_write_b1: + ld bc, #PORT_SYSCONF + ld a, #TURBO_7MHZ + out (c), a ; → 7 MHz + ld a, (de) + ld c, #OPL3_ADDR1 ; B=#20, don't-care for C4-C7 + out (c), a + inc de + ld a, (de) + inc c ; C7 = data port + out (c), a + ld c, #0xAF ; BC=#20AF (B still #20) + ld a, #TURBO_14MHZ + out (c), a ; → 14 MHz + inc de + inc de + jp _isr_cmd_loop + +_isr_do_wait: + ld a, (de) ; lo + inc de + ld l, a + ld a, (de) ; hi + inc de + ld h, a + inc de ; skip pad byte + ld (_isr_wait_ctr), hl + ex de, hl + ld (_isr_read_ptr), hl + jp _isr_exit + +;--- AY8910 chip 1 write: [reg, val, pad] -------------------------------- +_isr_write_ay: + ld bc, #PORT_SYSCONF + ld a, #TURBO_7MHZ + out (c), a ; → 7 MHz + ld bc, #0xFFFD + ld a, (_isr_ms_ctrl) ; MultiSound ctrl (bit0=0 → chip1) + out (c), a + ld a, (de) ; reg + out (c), a ; select AY register + inc de + ld a, (de) ; val + ld b, #0xBF ; BC = #BFFD + out (c), a ; write data + ld bc, #PORT_SYSCONF + ld a, #TURBO_14MHZ + out (c), a ; → 14 MHz + inc de + inc de ; pad + jp _isr_cmd_loop + +;--- AY8910 chip 2 write (TurboSound): [reg, val, pad] ------------------- +_isr_write_ay2: + ld bc, #PORT_SYSCONF + ld a, #TURBO_7MHZ + out (c), a ; → 7 MHz + ld bc, #0xFFFD + ld a, (_isr_ms_ctrl) ; MultiSound ctrl base + or a, #0x01 ; bit0=1 → chip2 + out (c), a + ld a, (de) ; reg + out (c), a ; select register + inc de + ld a, (de) ; val + ld b, #0xBF ; BC = #BFFD + out (c), a + ld bc, #PORT_SYSCONF + ld a, #TURBO_14MHZ + out (c), a ; → 14 MHz + inc de + inc de ; pad + jp _isr_cmd_loop + +;--- SAA1099 write: [reg, val, pad] ---------------------------------------- +; reg bit7 = chip select (0=chip0, 1=chip1), passed directly to #01FF. +; HDL uses bit7 for CS routing; SAA1099 ignores upper address bits. +_isr_write_saa: + ld bc, #PORT_SYSCONF + ld a, #TURBO_7MHZ + out (c), a ; → 7 MHz + ld a, (de) ; reg (bit7 = chip select) + ld bc, #0x01FF ; SAA address port + out (c), a ; write register address (bit7 → chip CS) + inc de + ld a, (de) ; val + ld b, #0x00 ; BC = #00FF SAA data port + out (c), a + ld bc, #PORT_SYSCONF + ld a, #TURBO_14MHZ + out (c), a ; → 14 MHz + inc de + inc de ; pad + jp _isr_cmd_loop + +;--- OPL4 Wave-часть write (ZXM-MoonSound): [reg, val, pad] -------------- +; Порты #7E (addr) / #7F (data) — как MOON_WREG/MOON_WDAT в оригинальном +; Z80-драйвере карты (moon_driver.asm). B don't-care (карта декодирует +; только младший байт адреса), как и для OPL3_ADDR0..OPL3_DATA1. +; +; ВАЖНО: в отличие от FM-банков (isr_write_b0/b1), референсный NedoOS- +; драйвер (common/moonsound.asm: opl4_wait) ждёт busy-флаг (бит0 порта +; #C4) перед КАЖДОЙ записью в порты #7E/#7F, включая обычные регистры +; wave-канала (не только memory-access режим) — без этого запись может +; быть потеряна чипом. См. chip_wait()/wave_out_reg()/wave_out_dat() в +; lib/vgm.c (та же логика для синхронной заливки сэмплов). +_isr_write_wave: + ld bc, #PORT_SYSCONF + ld a, #TURBO_7MHZ + out (c), a ; → 7 MHz + ld c, #OPL4_WREG ; B=#20, don't-care (как MOON_WREG) +wwave_wait1: + in a, (0xC4) ; busy-флаг (бит0) + rrca + jr c, wwave_wait1 + ld a, (de) ; reg + out (c), a + inc de + inc c ; C=#7F = data port (MOON_WDAT) +wwave_wait2: + in a, (0xC4) + rrca + jr c, wwave_wait2 + ld a, (de) ; val + out (c), a + ld bc, #PORT_SYSCONF + ld a, #TURBO_14MHZ + out (c), a ; → 14 MHz + inc de + inc de ; pad + jp _isr_cmd_loop + +;--- CMD_INC_SEC: инкремент счётчика секунд [0,0,0] --------------------- +_isr_inc_sec: + inc de + inc de + inc de ; пропустить 3 байта параметров + ld hl, (_isr_play_seconds) + inc hl + ld (_isr_play_seconds), hl + jp _isr_cmd_loop + +;--- CMD_SKIP_TICKS: skip N + spectrum bitmask [N,smask_lo,smask_hi] -------- +; CMD_SKIP_TICKS(N): +; N=0 — не перепрограммирует, просто exit (1 тик пауза) +; N>=1 — пропустить N записей, перепрограммировать INT (N+1 тиков пауза) +; Max N=55 (кадр минус 1). +_isr_skip_ticks: + ld a, (de) ; N = skip count (0..55) + inc de + ;--- Spectrum: KeyOn attack + sustained activity floor --- + push af ; save N + ld a, (de) ; mask_lo + inc de + ld l, a ; L = mask_lo + ld a, (de) ; mask_hi + inc de + ld h, a ; H = mask_hi + push de ; save read_ptr + + ; OPL4 gets edge/sustain processing. Other chips keep the old + ; event-analyzer behaviour by comparing every mask with zero. + ld a, (_vgm_chip_type) + cp #4 ; VGM_CHIP_OPL4 + jr z, _skip_spec_opl4 + ld de, #0 + jr _skip_spec_masks_ready +_skip_spec_opl4: + ld de, (_spectrum_prev_mask) + ld (_spectrum_prev_mask), hl +_skip_spec_masks_ready: + ; E/D are previous low/high, L/H are current low/high. + ld c, e ; previous low byte + ld e, h ; preserve current high byte + ld a, l ; current low byte + ld hl, #_spectrum_levels + call _skip_spec_byte + ld c, d ; previous high byte + ld a, e ; current high byte + call _skip_spec_byte + pop de ; restore read_ptr + pop af ; restore N + ; Сохранить read_ptr + ex de, hl + ld (_isr_read_ptr), hl + ; N=0: просто exit (как CMD_WAIT(0) без накладных на wait_ctr) + or a + jp z, _isr_exit + ; Пропустить N записей pos_table: перейти по N-1 ссылкам, + ; затем OUTI×3 из N-ой записи (перепрограммировать INT) + ld hl, (_pos_ptr) + dec a ; N-1 линков пропустить + jr z, _skip_program ; N=1: сразу программируем текущую + ld b, a +_skip_loop: + inc hl ; пропустить VSINTH + inc hl ; VSINTL + inc hl ; HSINT + ld a, (hl) ; next_lo + inc hl + ld h, (hl) ; next_hi + ld l, a + djnz _skip_loop +_skip_program: + ; HL = запись для перепрограммирования INT портов + ld bc, #0x25AF + outi ; VSINTH (#24AF) + outi ; VSINTL (#23AF) + outi ; HSINT (#22AF) + ; Следующий next_ptr + ld a, (hl) + inc hl + ld h, (hl) + ld l, a + ld (_pos_ptr), hl + jp _isr_exit + +; Unpack one activity byte into eight spectrum bars. +; A=current bits, C=previous bits, HL=first level; advances HL by 8. +; Rising edge: peak 8. Sustained KeyOn: decay is allowed down to floor 3. +_skip_spec_byte: + ld b, #8 +_skip_spec_bit: + rr c ; carry = previous activity bit + jr c, _skip_spec_was_on + rra ; carry = current activity bit + jr nc, _skip_spec_next + ld (hl), #8 ; new KeyOn attack + jr _skip_spec_next +_skip_spec_was_on: + rra ; carry = current activity bit + jr nc, _skip_spec_next ; KeyOff: ordinary decay + push af ; preserve remaining current bits + ld a, (hl) + cp #3 + jr nc, _skip_spec_floor_ok + ld (hl), #3 ; sustained activity floor +_skip_spec_floor_ok: + pop af +_skip_spec_next: + inc hl + djnz _skip_spec_bit + ret + +;--- CMD_ISR_DONE: заморозить ISR, выставить флаг [0,0,0] ------------------ +; ISR зацикливается на этой команде, не переключает буферы. +; Main loop ждёт isr_done == 1 перед завершением. +_isr_cmd_done: + inc de + inc de + inc de ; пропустить 3 байта параметров + ; Выставить флаг готовности к завершению + ld a, #1 + ld (_isr_done), a + ; Перемотать read_ptr на начало CMD_ISR_DONE (DE - 4) + ex de, hl + dec hl + dec hl + dec hl + dec hl + ld (_isr_read_ptr), hl + jp _isr_exit + +;--- CMD_CALL_WC: вызов оригинального WC handler [0,0,0] ------------------ +; Внутренняя команда ISR (не используется buffer filler, зарезервировано) +_isr_call_wc: + inc de + inc de + inc de ; пропустить 3 байта параметров + jp _isr_cmd_loop ; просто пропустить + +;============================================================================== +; call_wc_handler() — Вызов WC ISR из main loop (C-callable) +; +; WC ожидает периодический вызов своего ISR для обновления TMN, +; клавиатуры, часов и др. Вызываем из main loop ~50 Гц. +; +; Механизм: DI → JP (saved_vec). WC handler делает EI+RET +; обратно к вызывающему коду (tail-call через JP). +;============================================================================== +_call_wc_handler:: + ld hl, (_isr_saved_vec) + ld a, h + or l + ret z ; saved_vec==0 → isr_init не вызывался + di + jp (hl) ; tail-call: WC handler делает EI+RET + +;============================================================================== + .area _DATA + +_isr_ms_ctrl:: .db 0xFE ; MultiSound ctrl: FM OFF + SAA OFF (idle) +_isr_active_buf:: .db 0 +_isr_enabled:: .db 0 +_isr_tick_ctr:: .db 0 ; инкрементируется каждые ISR тик (2734/сек, wrap 256) +_isr_border_color:: .db 0 ; цвет бордюра при выходе из ISR (пишет main, читает ISR) +_isr_play_seconds:: .dw 0 ; инкрементируется CMD_INC_SEC +_isr_read_ptr:: .dw 0 +_isr_wait_ctr:: .dw 0 +_isr_done:: .db 0 ; 1 = ISR замер на CMD_ISR_DONE, готов к завершению +_pos_ptr: .dw 0 ; инициализируется в isr_init() +_isr_saved_vec: .dw 0 ; сохранённый вектор WC (#5BFF) + +;============================================================================== +; Spectrum analyzer levels (16 bars, updated by 16-bit bitmask in CMD_SKIP_TICKS) +;============================================================================== +_spectrum_prev_mask: + .ds 2 ; previous CMD_SKIP activity bits +_spectrum_levels:: + .ds 16 ; 16 bytes, one per bar (0-8) + +;============================================================================== +; Командные буферы (512 байт каждый) +;============================================================================== + + .even +_cmd_buf_a:: + .ds 512 +_cmd_buf_b:: + .ds 512 diff --git a/src_sdcc/asm/opl3.s b/src_sdcc/asm/opl3.s new file mode 100644 index 0000000..268b4a0 --- /dev/null +++ b/src_sdcc/asm/opl3.s @@ -0,0 +1,335 @@ +;============================================================================== +; @file opl3.s +; @brief OPL3/AY/SAA register operations for SDCC Z80 (sdcccall(1)) +; +; Порты TSConfig для OPL3: +; #C4 = Bank 0 Address #C5 = Bank 0 Data +; #C6 = Bank 1 Address #C7 = Bank 1 Data +;============================================================================== + + .module opl3 + .area _CODE + +;---------------------------------------------------------------------- +; opl3_write_b0(uint8_t addr, uint8_t data) — Bank 0 +;---------------------------------------------------------------------- + .globl _opl3_write_b0 +_opl3_write_b0:: + ld c, #0xC4 + jr _opl3_write + +;---------------------------------------------------------------------- +; opl3_write_b1(uint8_t addr, uint8_t data) — Bank 1 +;---------------------------------------------------------------------- + .globl _opl3_write_b1 +_opl3_write_b1:: + ld c, #0xC6 +_opl3_write: + out (c), a ; addr (B don't-care for C4-C7) + ld hl, #2 + add hl, sp + ld a, (hl) + inc c + out (c), a ; data + ret + +;---------------------------------------------------------------------- +; tsconf_out(uint16_t port, uint8_t val) +;---------------------------------------------------------------------- + .globl _tsconf_out +_tsconf_out:: + ld b, d + ld c, e + ld hl, #2 + add hl, sp + ld a, (hl) + out (c), a + ret + +;---------------------------------------------------------------------- +; opl3_init() — OPL3 enable + wavesel + reset (falls through) +;---------------------------------------------------------------------- + .globl _opl3_init +_opl3_init:: + ld a, #0x05 + ld c, #0xC6 + out (c), a ; B don't-care for C4-C7 + ld a, #0x01 ; NEW bit only (OPL3 enable) + inc c + out (c), a + ld a, #0x01 + ld c, #0xC4 + out (c), a + ld a, #0x20 + inc c + out (c), a + ; fall through to opl3_reset + +;---------------------------------------------------------------------- +; opl3_reset() — Full OPL3 silence: zero ALL significant registers +; Both banks: Key-Off (B0-B8=0), Total Level max atten (40-55=3F), +; Percussion off (BD=0), F-Num (A0-A8=0), Feedback (C0-C8=0), +; AM/Vib/Mult (20-35=0), AR/DR (60-75=0), SL/RR (80-95=0), +; Waveform (E0-F5=0). +; Uses _opl3_zero_range and _opl3_fill_range helpers. +;---------------------------------------------------------------------- + .globl _opl3_reset +_opl3_reset:: + ; ── Bank 0 ── + ld c, #0xC4 + call _opl3_reset_bank + + ; Percussion off: BD = 0 (Bank 0 only) + ld a, #0xBD + out (c), a + inc c + xor a, a + out (c), a + dec c + + ; ── Bank 1 ── + ld c, #0xC6 + ; fall through to _opl3_reset_bank + +;---------------------------------------------------------------------- +; _opl3_reset_bank — zero all significant regs of one OPL3 bank +; C = addr port (0xC4 or 0xC6) +; Destroys: A, B, D, E +;---------------------------------------------------------------------- +_opl3_reset_bank: + ; KeyOff: B0-B8 = 0 + ld d, #0xB0 + ld e, #9 + call _opl3_zero_range + + ; Total Level max attenuation: 40-55 = 0x3F + ld d, #0x40 + ld e, #22 + ld a, #0x3F + call _opl3_fill_range + + ; F-Num lo: A0-A8 = 0 + ld d, #0xA0 + ld e, #9 + call _opl3_zero_range + + ; Feedback/Output: C0-C8 = 0 + ld d, #0xC0 + ld e, #9 + call _opl3_zero_range + + ; AM/Vib/Mult: 20-35 = 0 + ld d, #0x20 + ld e, #22 + call _opl3_zero_range + + ; AR/DR: 60-75 = 0 + ld d, #0x60 + ld e, #22 + call _opl3_zero_range + + ; SL/RR: 80-95 = 0 + ld d, #0x80 + ld e, #22 + call _opl3_zero_range + + ; Waveform: E0-F5 = 0 + ld d, #0xE0 + ld e, #22 + jr _opl3_zero_range ; tail-call → ret + +;---------------------------------------------------------------------- +; _opl3_zero_range — fill E consecutive regs starting at D with 0 +; C = port, D = start reg, E = count +;---------------------------------------------------------------------- +_opl3_zero_range: + xor a, a + ; fall through +;---------------------------------------------------------------------- +; _opl3_fill_range — fill E consecutive regs starting at D with value A +; C = port (0xC4 for Bank0, 0xC6 for Bank1) +; D = start register, E = count, A = value to write +; Destroys: A, B, D, E +;---------------------------------------------------------------------- +_opl3_fill_range: + ld b, a ; B = value to write +_opl3_fr_lp: + ld a, d + out (c), a ; write register address + inc c + ld a, b + out (c), a ; write value + dec c + inc d + dec e + jr nz, _opl3_fr_lp + ret + +;---------------------------------------------------------------------- +; Общая подпрограмма: silence текущего AY чипа. C уже = 0xFD. +; Reg 7 = 0x3F (mixer off), Regs 8-10 = 0 (volume off) +;---------------------------------------------------------------------- +_ay_silence_core: + ld b, #0xFF + ld a, #7 + out (c), a + ld b, #0xBF + ld a, #0x3F + out (c), a + ld d, #8 +_ayc_lp: + ld b, #0xFF + ld a, d + out (c), a + ld b, #0xBF + xor a, a + out (c), a + inc d + ld a, d + cp a, #11 + jr c, _ayc_lp + ret + +;---------------------------------------------------------------------- +; ay_write_reg(uint8_t reg, uint8_t val) — write AY chip 1 register +; reg in A, val on stack +;---------------------------------------------------------------------- + .globl _ay_write_reg +_ay_write_reg:: + ld c, #0xFD + ld b, #0xFF + out (c), a ; select register + ld hl, #2 + add hl, sp + ld a, (hl) + ld b, #0xBF + out (c), a ; write value + ret + +;---------------------------------------------------------------------- +; ay2_write_reg(uint8_t reg, uint8_t val) — write AY chip 2 register +;---------------------------------------------------------------------- + .globl _ay2_write_reg +_ay2_write_reg:: + ld c, #0xFD + ld b, #0xFF + push af ; save reg + ld a, #0xFE + out (c), a ; select chip 2 + pop af + ld b, #0xFF + out (c), a ; select register + ld hl, #2 + add hl, sp + ld a, (hl) + ld b, #0xBF + out (c), a ; write value + ld b, #0xFF + ld a, #0xFF + out (c), a ; switch back to chip 1 + ret + +;---------------------------------------------------------------------- +; saa_write_reg(uint8_t reg, uint8_t val) — SAA1099 chip 1 +; addr=#00FF, data=#01FF +;---------------------------------------------------------------------- + .globl _saa_write_reg +_saa_write_reg:: + ld c, #0xFF + ld b, #0x00 + out (c), a ; addr = reg + ld hl, #2 + add hl, sp + ld a, (hl) + inc b ; B = 0x01 + out (c), a ; data = val + ret + +;---------------------------------------------------------------------- +; saa2_write_reg(uint8_t reg, uint8_t val) — SAA1099 chip 2 +; addr=#02FF, data=#03FF +;---------------------------------------------------------------------- + .globl _saa2_write_reg +_saa2_write_reg:: + ld c, #0xFF + ld b, #0x02 + out (c), a ; addr = reg + ld hl, #2 + add hl, sp + ld a, (hl) + inc b ; B = 0x03 + out (c), a ; data = val + ret + +;---------------------------------------------------------------------- +; ay_silence() — AY chip 1 (FFFD/BFFD) +;---------------------------------------------------------------------- + .globl _ay_silence +_ay_silence:: + ld c, #0xFD + jr _ay_silence_core + +;---------------------------------------------------------------------- +; ay2_silence() — TurboSound chip 2: select → silence → switch back +;---------------------------------------------------------------------- + .globl _ay2_silence +_ay2_silence:: + ld c, #0xFD + ld b, #0xFF + ld a, #0xFE + out (c), a ; select chip 2 + call _ay_silence_core + ld b, #0xFF + ld a, #0xFF + out (c), a ; switch back to chip 1 + ret + +;---------------------------------------------------------------------- +; Общая подпрограмма: silence SAA чипа. C=0xFF, B=addr port hi. +; Data port = B+1 (inc b / dec b). +; Reset (0x1C=02), disable (0x1C=00), amplitude regs 0x00-0x05=0. +;---------------------------------------------------------------------- +_saa_silence_core: + ld a, #0x1C + out (c), a ; addr + inc b + ld a, #0x02 + out (c), a ; data: reset + dec b + ld a, #0x1C + out (c), a ; addr + inc b + xor a, a + out (c), a ; data: sound off + dec b + ld d, #0 +_saac_lp: + ld a, d + out (c), a ; addr: reg N + inc b + xor a, a + out (c), a ; data: 0 + dec b + inc d + ld a, d + cp a, #6 + jr c, _saac_lp + ret + +;---------------------------------------------------------------------- +; saa_silence() — SAA1099 chip 1 (addr=#00FF, data=#01FF) +;---------------------------------------------------------------------- + .globl _saa_silence +_saa_silence:: + ld c, #0xFF + ld b, #0x00 + jr _saa_silence_core + +;---------------------------------------------------------------------- +; saa2_silence() — SAA1099 chip 2 (addr=#02FF, data=#03FF) +;---------------------------------------------------------------------- + .globl _saa2_silence +_saa2_silence:: + ld c, #0xFF + ld b, #0x02 + jr _saa_silence_core diff --git a/src_sdcc/asm/pos_table.s b/src_sdcc/asm/pos_table.s new file mode 100644 index 0000000..7686429 --- /dev/null +++ b/src_sdcc/asm/pos_table.s @@ -0,0 +1,58 @@ +;============================================================================ +; pos_table.s — Таблица позиций INT (СГЕНЕРИРОВАНО, не редактировать) +; +; entries=14, step=5120, line_len=224 +; ISR freq: 3,500,000 / 5120 = 684 Hz (~44100/16) +;============================================================================ + + .module pos_table_mod + .globl pos_table + + .area _CODE + +pos_table: +_pt_0: + .db 0x00, 0x00, 0x00 ; [ 0] line= 0, hpos= 0 + .dw _pt_1 +_pt_1: + .db 0x00, 0x16, 0xC0 ; [ 1] line= 22, hpos=192 + .dw _pt_2 +_pt_2: + .db 0x00, 0x2D, 0xA0 ; [ 2] line= 45, hpos=160 + .dw _pt_3 +_pt_3: + .db 0x00, 0x44, 0x80 ; [ 3] line= 68, hpos=128 + .dw _pt_4 +_pt_4: + .db 0x00, 0x5B, 0x60 ; [ 4] line= 91, hpos= 96 + .dw _pt_5 +_pt_5: + .db 0x00, 0x72, 0x40 ; [ 5] line=114, hpos= 64 + .dw _pt_6 +_pt_6: + .db 0x00, 0x89, 0x20 ; [ 6] line=137, hpos= 32 + .dw _pt_7 +_pt_7: + .db 0x00, 0xA0, 0x00 ; [ 7] line=160, hpos= 0 + .dw _pt_8 +_pt_8: + .db 0x00, 0xB6, 0xC0 ; [ 8] line=182, hpos=192 + .dw _pt_9 +_pt_9: + .db 0x00, 0xCD, 0xA0 ; [ 9] line=205, hpos=160 + .dw _pt_10 +_pt_10: + .db 0x00, 0xE4, 0x80 ; [10] line=228, hpos=128 + .dw _pt_11 +_pt_11: + .db 0x00, 0xFB, 0x60 ; [11] line=251, hpos= 96 + .dw _pt_12 +_pt_12: + .db 0x01, 0x12, 0x40 ; [12] line=274, hpos= 64 + .dw _pt_13 +_pt_13: + .db 0x01, 0x29, 0x20 ; [13] line=297, hpos= 32 + .dw pos_table + + .globl pos_table_end +pos_table_end: diff --git a/src_sdcc/asm/spectrum.s b/src_sdcc/asm/spectrum.s new file mode 100644 index 0000000..8878153 --- /dev/null +++ b/src_sdcc/asm/spectrum.s @@ -0,0 +1,953 @@ +;============================================================================== +; spectrum.s — Spectrum analyzer: render + hot-loop helpers (Z80 ASM) +; +; Hand-optimised replacements for C functions in main.c / vgm.c: +; spectrum_render() — 4-row bar graph, direct VRAM write +; spectrum_opl_b0(r,v) — OPL B0-B8 key-on + 0xBD percussion +; spectrum_opl_b1(r,v) — OPL Bank1 B0-B6 key-on +; spectrum_ay(r,v) — AY period shadow + vol trigger + YM2203 FM (chip1) +; spectrum_ay2(r,v) — same for chip2 (base offset = 3) +; spectrum_saa(r,v) — SAA octave shadow + amp trigger +; +; sdcccall(1) conventions: +; 1st u8 → A, 2nd u8 → L, 3rd u8 → stack+2 +; 1st u16 → HL, return u8 → A, return u16 → HL +; +; Port 0x13AF = Page3 window (TSConfig) +;============================================================================== + + .module spectrum + +; ── Exports ────────────────────────────────────────────────────────── + .globl _spectrum_render + .globl _spectrum_decay + .globl _spectrum_decay_reset + .globl _spectrum_opl_b0 + .globl _spectrum_opl_b1 + .globl _spectrum_ay + .globl _spectrum_ay2 + .globl _spectrum_saa + .globl _spectrum_saa2 + .globl _spectrum_wave + .globl _spec_period_to_bar + .globl _spectrum_font_init + .globl _spectrum_font_restore + +; ── Imports ────────────────────────────────────────────────────────── + .globl _spectrum_levels ; uint8_t[16] in isr.s _DATA + .globl _isr_tick_ctr ; volatile uint8_t in isr.s _DATA + .globl _s_pb_text_pg ; uint8_t in main.c _DATA + .globl _s_spec_char_ofs ; uint16_t[4] in main.c _DATA + .globl _spec_mask ; uint16_t in vgm.c _DATA + .globl _spec_ay_period ; uint16_t[6] in vgm.c _DATA + .globl _spec_saa_oct ; uint8_t[6] in vgm.c _DATA + .globl _spec_saa_oct2 ; uint8_t[6] in vgm.c _DATA (chip 2) + .globl _spec_saa_dual ; uint8_t in vgm.c _DATA + .globl _spec_opl_bd ; uint8_t in vgm.c _DATA + .globl _spec_fm_block ; uint8_t[6] in vgm.c _DATA + .globl _spec_wave_active ; uint8_t[3]: 24-bit KeyOn mask + +; ── Constants ──────────────────────────────────────────────────────── +PORT_PAGE3 = 0x13AF +SPEC_BARS = 16 +SPEC_LEFT_PAD = 5 +SPEC_BAR_W = 3 ; 2 filled + 1 space +CHAR_FULL = 0x02 ; custom glyph: full-height striped bar +CHAR_HALF = 0x01 ; custom glyph: half-height striped bar +CLR_WIN = 0x70 ; WC_COLOR(WC_WHITE, WC_BLACK) +WC_PAGE_FONT0 = 0x01 +FONT_BASE = 0xC000 +; ── Timing: must match variant_cfg.h ──────────────────────────────── +ISR_TICKS_PER_FRAME = 14 ; = ISR_TICKS_PER_FRAME in variant_cfg.h +; ── Decay: must match DECAY_FRAMES_* in variant_cfg.h ──────────────── +DECAY_FAST = 1 ; = DECAY_FRAMES_FAST +DECAY_MED = 2 ; = DECAY_FRAMES_MED +DECAY_SLOW = 4 ; = DECAY_FRAMES_SLOW + +;====================================================================== +; Scratch variables in _DATA (kept minimal) +;====================================================================== + .area _DATA + +say_base: .db 0 ; temp: chip base index (0 or 3) +sr_row_ctr: .db 0 ; temp: row iteration counter +sr_rp_ptr: .dw 0 ; temp: current row-params pointer +sd_last_tick: .db 0 ; isr_tick_ctr snapshot for frame gate +sd_fast_ctr: .db DECAY_FAST ; countdown: decay levels >= 6 +sd_med_ctr: .db DECAY_MED ; countdown: decay levels 3-5 +sd_slow_ctr: .db DECAY_SLOW ; countdown: decay levels 1-2 +sr_prev_hash: .db 0 ; XOR-rotate hash of spectrum_levels from previous render + + .area _CODE + +; Row parameter table: [thresh_full, thresh_half, color] × 4 rows +; Row 0 (top=peak): thresholds 8,7 color=BRIGHT_RED +; Row 1: thresholds 6,5 color=BRIGHT_YELLOW +; Row 2: thresholds 4,3 color=BRIGHT_GREEN +; Row 3 (bottom): thresholds 2,1 color=GREEN +sr_row_params: + .db 8, 7, 0x7A ; row 0: white bg + bright red + .db 6, 5, 0x7E ; row 1: white bg + bright yellow + .db 4, 3, 0x7C ; row 2: white bg + bright green + .db 2, 1, 0x74 ; row 3: white bg + green + +;====================================================================== +; spectrum_render(void) +; +; Optimised Z80 register layout — zero push/pop in inner bar loop: +; IX = char_ptr (stride +3 per bar via: inc ix x3) +; IY = attr_ptr (stride +3 per bar via: inc iy x3) +; HL = spectrum_levels + bar_index +; B = bar counter (DJNZ) +; C = thresh_full (row-constant, set ONCE before bar loop) +; D = thresh_half (row-constant, set ONCE before bar loop) +; E = color (row-constant, set ONCE before bar loop) +; +; Old inner loop loaded 0(ix)/1(ix)/2(ix) on every bar = 3x19=57 T +; per bar x 16 bars x 4 rows = 3648 wasted T-states per frame. +; New inner loop: C/D/E loaded once per row, never reloaded per bar. +; Total savings: ~3500 T-states per spectrum_render call. +;====================================================================== + +_spectrum_render:: + ; XOR-rotate hash over spectrum_levels[16] (~100T). + ; Detects ANY change versus previous frame, not just zero/non-zero. + ; Hash = seed, then for each byte: RLCA + XOR (hl). + ld hl, #_spectrum_levels + xor a + .rept 16 + rlca + xor (hl) + inc hl + .endm + ; A = rolling hash of current levels + ld hl, #sr_prev_hash + cp (hl) ; same as last frame? + ret z ; unchanged → skip render (~7000T saved) + ld (hl), a ; store new hash + + push ix + push iy + + ld bc, #PORT_PAGE3 + in a, (c) + push af + push bc + + ld a, (_s_pb_text_pg) + out (c), a + + ld a, #4 + ld (sr_row_ctr), a + ld hl, #sr_row_params + ld (sr_rp_ptr), hl + +sr_row_loop: + ;-------------------------------------------------------------- + ; row_index = 4 - row_ctr (0..3) + ; Compute IX = char_ptr, IY = attr_ptr for this row. + ;-------------------------------------------------------------- + ld a, (sr_row_ctr) + ld e, a + ld a, #4 + sub e + add a, a ; A = row_index * 2 + ld e, a + ld d, #0 + + ; IX = 0xC000 + s_spec_char_ofs[row_index] + SPEC_LEFT_PAD + ld hl, #_s_spec_char_ofs + add hl, de + ld a, (hl) + inc hl + ld h, (hl) + ld l, a ; HL = s_spec_char_ofs[row_index] + ld bc, #(0xC000 + SPEC_LEFT_PAD) + add hl, bc + push hl + pop ix ; IX = char_ptr + + ; IY = IX + 0x0080 (attr_ofs = char_ofs | 0x0080 in TSConf) + push ix + pop hl + ld de, #0x0080 + add hl, de + push hl + pop iy ; IY = attr_ptr + + ;-------------------------------------------------------------- + ; Load row params ONCE into C/D/E: all three are row-constants. + ; C, D, E survive the bar loop untouched (djnz only changes B). + ;-------------------------------------------------------------- + ld hl, (sr_rp_ptr) + ld c, (hl) ; C = thresh_full + inc hl + ld d, (hl) ; D = thresh_half + inc hl + ld e, (hl) ; E = color + inc hl + ld (sr_rp_ptr), hl ; advance for next row + + ;-------------------------------------------------------------- + ; Bar loop: 16 bars — all comparisons use stable C,D,E regs. + ; No push/pop needed: all state lives in IX, IY, HL, B, C, D, E. + ;-------------------------------------------------------------- + ld hl, #_spectrum_levels + ld b, #SPEC_BARS + +sr_bar_loop: + ld a, (hl) + cp c ; NC if level >= thresh_full + jr nc, sr_full + cp d ; NC if level >= thresh_half + jr nc, sr_half + +sr_empty: + ld a, #' ' + ld 0(ix), a + ld 1(ix), a + ld a, #CLR_WIN + ld 0(iy), a + ld 1(iy), a + jr sr_next + +sr_half: + ld a, #CHAR_HALF + ld 0(ix), a + ld 1(ix), a + ld 0(iy), e ; E = row color (stable per row) + ld 1(iy), e + jr sr_next + +sr_full: + ld a, #CHAR_FULL + ld 0(ix), a + ld 1(ix), a + ld 0(iy), e ; E = row color (stable per row) + ld 1(iy), e + +sr_next: + inc hl ; next spectrum level + inc ix ; char_ptr += SPEC_BAR_W (3) + inc ix + inc ix + inc iy ; attr_ptr += SPEC_BAR_W (3) + inc iy + inc iy + djnz sr_bar_loop + + ld a, (sr_row_ctr) + dec a + ld (sr_row_ctr), a + jp nz, sr_row_loop + + pop bc + pop af + out (c), a + + pop iy + pop ix + ret + +;====================================================================== +; spectrum_decay_reset() — initialize decay timers at song start +;====================================================================== +_spectrum_decay_reset:: + ld a, (_isr_tick_ctr) + ld (sd_last_tick), a + ld a, #DECAY_FAST + ld (sd_fast_ctr), a + ld a, #DECAY_MED + ld (sd_med_ctr), a + ld a, #DECAY_SLOW + ld (sd_slow_ctr), a + ; Reset render hash so first frame always renders + ld a, #0xFF + ld (sr_prev_hash), a + ret + +;====================================================================== +; spectrum_decay() — frame-gated non-linear level decay +; +; Called every main-loop iteration; returns immediately if less than +; ISR_TICKS_PER_FRAME ticks have elapsed since the last frame. +; +; Three countdown timers control decay speed per level range: +; Levels >= 6 : -1 every 3 frames (~60 ms) +; Levels 3-5 : -1 every 6 frames (~180 ms) +; Levels 1-2 : -1 every 10 frames (~200 ms) +; +; Register layout: +; C = decay-flags bitmask (bit2=fast, bit1=med, bit0=slow) +; B = bar loop counter +; HL = pointer into spectrum_levels[] +;====================================================================== +_spectrum_decay:: + ; ── Frame gate ────────────────────────────────────────────── + ld a, (_isr_tick_ctr) + ld hl, #sd_last_tick + sub (hl) ; elapsed = now - last + cp #ISR_TICKS_PER_FRAME + ret c ; < 1 frame, skip + + ; Advance by one frame + ld a, (hl) + add a, #ISR_TICKS_PER_FRAME + ld (hl), a + + ; ── Build decay-flags in C ────────────────────────────────── + ld c, #0 + + ld hl, #sd_fast_ctr ; 5-frame timer (levels >= 6) + dec (hl) + jr nz, sd_nf + ld (hl), #DECAY_FAST + set 2, c +sd_nf: + ld hl, #sd_med_ctr ; med-frame timer (levels 3-5) + dec (hl) + jr nz, sd_nm + ld (hl), #DECAY_MED + set 1, c +sd_nm: + ld hl, #sd_slow_ctr ; slow-frame timer (levels 1-2) + dec (hl) + jr nz, sd_ns + ld (hl), #DECAY_SLOW + set 0, c +sd_ns: + ld a, c + or a + ret z ; no tier expired → nothing to decay + + ; ── Single loop over 16 bars ──────────────────────────────── + ld hl, #_spectrum_levels + ; Quick all-zero check: if all bars are 0, skip decay loop + ld a, (hl) + inc hl + .rept 15 + or (hl) + inc hl + .endm + ret z ; all bars zero → nothing to decay + + ld hl, #_spectrum_levels + ld b, #SPEC_BARS +sd_bar_loop: + ld a, (hl) + or a + jr z, sd_next ; level 0 → skip + + cp #6 + jr nc, sd_high ; level >= 6 → fast tier + + cp #3 + jr nc, sd_mid ; level >= 3 → medium tier + + ; level 1-2: slow tier + bit 0, c + jr z, sd_next + dec (hl) + jr sd_next + +sd_mid: + bit 1, c + jr z, sd_next + dec (hl) + jr sd_next + +sd_high: + bit 2, c + jr z, sd_next + dec (hl) + +sd_next: + inc hl + djnz sd_bar_loop + ret + +;====================================================================== +; spectrum_opl_b0(reg, val) — A=reg, L=val +; +; OPL Bank0: B0-B8 key-on + 0xBD percussion (rising edge). +; Sets bits in spec_mask. +;====================================================================== +_spectrum_opl_b0:: + ; Save reg and val + ld d, a ; D = reg + ld e, l ; E = val + + ; ── Check B0-B8 key-on ── + cp a, #0xB0 + jr c, sob0_check_bd ; reg < 0xB0 + cp a, #0xB9 + jr nc, sob0_check_bd ; reg > 0xB8 + bit 5, e ; KeyOn bit in val + jr z, sob0_check_bd ; not keyed on + + ; bar = (val >> 1) & 0x0F + ld a, e + srl a + and a, #0x0F + ; spec_mask |= (1 << bar) + call _set_spec_bit + ; Fall through to check BD + +sob0_check_bd: + ld a, d + cp a, #0xBD + ret nz ; not reg 0xBD, done + + ; ── 0xBD percussion handling ── + bit 5, e ; rhythm mode ON? + jr z, sob0_save_bd ; no → just save shadow + + ; rising = val & ~old & 0x1F + ld a, (_spec_opl_bd) + cpl ; ~old + and a, e ; val & ~old + and a, #0x1F ; mask lower 5 bits + jr z, sob0_save_bd ; no rising edges + + ; Check each percussion bit + ld b, a ; B = rising edges mask + bit 4, b ; BD? + jr z, sob0_no_bd + ld a, #2 ; BD → bar 2 + call _set_spec_bit +sob0_no_bd: + bit 3, b ; SD? + jr z, sob0_no_sd + ld a, #8 ; SD → bar 8 + call _set_spec_bit +sob0_no_sd: + bit 2, b ; TOM? + jr z, sob0_no_tom + ld a, #5 ; TOM → bar 5 + call _set_spec_bit +sob0_no_tom: + bit 1, b ; CY? + jr z, sob0_no_cy + ld a, #12 ; CY → bar 12 + call _set_spec_bit +sob0_no_cy: + bit 0, b ; HH? + jr z, sob0_save_bd + ld a, #14 ; HH → bar 14 + call _set_spec_bit + +sob0_save_bd: + ld a, e + ld (_spec_opl_bd), a + ret + +;====================================================================== +; spectrum_opl_b1(reg, val) — A=reg, L=val +; +; OPL Bank1: B0-B6 key-on only. Same logic, smaller range. +;====================================================================== +_spectrum_opl_b1:: + cp a, #0xB0 + ret c ; reg < 0xB0 + cp a, #0xB7 + ret nc ; reg > 0xB6 + bit 5, l ; KeyOn bit + ret z + ; bar = (val >> 1) & 0x0F + ld a, l + srl a + and a, #0x0F + jp _set_spec_bit ; tail-call + +;====================================================================== +; spectrum_wave(reg, val) — A=reg, L=val +; +; YMF278B wave activity tracker. Key registers 0x68-0x7F maintain a +; 24-bit KeyOn mask. asm_emit_skip_spec folds channels 16-23 onto bars +; 0-7 and transports the current activity together with every wait. +;====================================================================== +_spectrum_wave:: + ld e, l ; E = key register value + cp #0x68 + ret c + cp #0x80 + ret nc + sub #0x68 ; A = channel 0..23 + ld d, a + and #0x07 + ld hl, #sw_bit_mask + add a, l + ld l, a + jr nc, sw_mask_nc + inc h +sw_mask_nc: + ld c, (hl) ; C = bit within mask byte + + ld a, d + rrca + rrca + rrca + and #0x03 ; byte index 0..2 + ld hl, #_spec_wave_active + add a, l + ld l, a + jr nc, sw_ptr_nc + inc h +sw_ptr_nc: + bit 7, e + jr z, sw_key_off + ld a, (hl) + or c + ld (hl), a + ret +sw_key_off: + ld a, c + cpl + and (hl) + ld (hl), a + ret + +sw_bit_mask: + .db 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 + +;====================================================================== +; _set_spec_bit — set bit A (0..15) in spec_mask +; Input: A = bit number (0..15) +; Trashes: A, D, E, HL +; Preserves: B, C (no push/pop needed) +; Reuses the 8-byte wave-channel bit table; bit3 selects low/high byte. +;====================================================================== +_set_spec_bit: + ld d, a ; preserve bar number + and #0x07 + ld hl, #sw_bit_mask + add a, l + ld l, a + jr nc, ssb_ptr_ok + inc h +ssb_ptr_ok: + ld e, (hl) ; E = 8-bit mask + bit 3, d + jr z, ssb_low_byte + ld d, e ; bars 8..15: mask in high byte + ld e, #0 + jr ssb_merge +ssb_low_byte: + ld d, #0 ; bars 0..7: mask in low byte +ssb_merge: + ld hl, (_spec_mask) + ld a, l + or e + ld l, a + ld a, h + or d + ld h, a + ld (_spec_mask), hl + ret + +;====================================================================== +; spectrum_ay(reg, val) — A=reg, L=val (chip1, base=0) +; spectrum_ay2(reg, val) — A=reg, L=val (chip2, base=3) +; +; AY: shadow tone period (regs 0-5), trigger bar on volume (regs 8-10) +; YM2203 FM: shadow block (0xA4-0xA6), key-on (0x28) +;====================================================================== +_spectrum_ay2:: + ld d, a ; D = reg + ld e, l ; E = val + ld a, #3 + jr say_base_set + +_spectrum_ay:: + ld d, a ; D = reg + ld e, l ; E = val + xor a, a ; base = 0 +say_base_set: + ld (say_base), a ; save base + + ; ── Check reg <= 5 (tone period shadow) ── + ld a, d ; A = reg + cp a, #6 + jr nc, say_check_vol + + ; ch = base + (reg >> 1) + srl a ; A = reg >> 1 + ld hl, #say_base + add a, (hl) ; A = base + (reg>>1) = ch index + ; Offset into spec_ay_period[ch] (uint16_t array) + add a, a ; A = ch * 2 + ld hl, #_spec_ay_period + add a, l + ld l, a + jr nc, say_nc1 + inc h +say_nc1: + ; HL → spec_ay_period[ch] + bit 0, d ; reg & 1 ? + jr nz, say_hi_nibble + + ; Low byte: period = (period & 0x0F00) | val + ld a, e ; A = val + ld (hl), a ; write low byte + ret + +say_hi_nibble: + ; High nibble: period = (period & 0x00FF) | ((val & 0x0F) << 8) + inc hl ; point to high byte + ld a, e + and a, #0x0F + ld (hl), a + ret + + ; ── Check reg 8-10 (volume → trigger bar) ── +say_check_vol: + ld a, d + cp a, #8 + jr c, say_check_fm_block + cp a, #11 + jr nc, say_check_fm_block + ; Check val & 0x0F != 0 + ld a, e + and a, #0x0F + ret z ; volume=0, skip + + ; ch_index = base + (reg - 8) + ld a, d + sub a, #8 + ld hl, #say_base + add a, (hl) ; A = array index + ; Load spec_ay_period[index] + add a, a ; * 2 + ld hl, #_spec_ay_period + add a, l + ld l, a + jr nc, say_nc2 + inc h +say_nc2: + ld a, (hl) + inc hl + ld h, (hl) + ld l, a ; HL = period + ; Fall through to spec_period_to_bar + set_spec_bit + call _spec_period_to_bar ; A = bar index + jp _set_spec_bit ; tail-call + + ; ── Check FM block shadow (regs 0xA4-0xA6) ── +say_check_fm_block: + ld a, d + cp a, #0xA4 + jr c, say_check_keyon + cp a, #0xA7 + jr nc, say_check_keyon + ; spec_fm_block[base + (reg - 0xA4)] = (val >> 3) & 0x07 + sub a, #0xA4 + ld hl, #say_base + add a, (hl) ; A = index + ld hl, #_spec_fm_block + add a, l + ld l, a + jr nc, say_nc3 + inc h +say_nc3: + ld a, e + srl a + srl a + srl a + and a, #0x07 + ld (hl), a + ret + + ; ── Check FM key-on (reg 0x28) ── +say_check_keyon: + ld a, d + cp a, #0x28 + ret nz + ; Check operators keyed on (val & 0xF0) + ld a, e + and a, #0xF0 + ret z + ; ch = val & 0x03 + ld a, e + and a, #0x03 + cp a, #3 + ret nc ; ch > 2 invalid + ; bar = spec_fm_block[base + ch] << 1 + ld hl, #say_base + add a, (hl) ; A = index + ld hl, #_spec_fm_block + add a, l + ld l, a + jr nc, say_nc4 + inc h +say_nc4: + ld a, (hl) ; A = block (0..7) + add a, a ; A = block * 2 = bar + jp _set_spec_bit ; tail-call + +;====================================================================== +; spec_period_to_bar(period) — HL=period, returns A=bar (0..15) +; +; Cascaded compare on high byte first, then low byte. +; Optimised: single compare chain, no function calls. +;====================================================================== +_spec_period_to_bar:: + ld a, h ; A = period >> 8 + cp a, #12 + jr c, sptb_lt12 + xor a, a ; return 0 + ret +sptb_lt12: + cp a, #8 + jr c, sptb_lt8 + ld a, #1 + ret +sptb_lt8: + cp a, #6 + jr c, sptb_lt6 + ld a, #2 + ret +sptb_lt6: + cp a, #4 + jr c, sptb_lt4 + ld a, #3 + ret +sptb_lt4: + cp a, #3 + jr c, sptb_lt3 + ld a, #4 + ret +sptb_lt3: + cp a, #2 + jr c, sptb_lt2 + ld a, #5 + ret +sptb_lt2: + cp a, #1 + jr c, sptb_lt1 + ; h == 1: check low byte + ld a, l + cp a, #128 + ld a, #6 + ret nc ; period >= 384 (1*256+128) + ld a, #7 + ret ; period >= 256 + +sptb_lt1: + ; h == 0: use low byte + ld a, l + cp a, #192 + jr c, sptb_p1 + ld a, #8 + ret +sptb_p1: + cp a, #128 + jr c, sptb_p2 + ld a, #9 + ret +sptb_p2: + cp a, #96 + jr c, sptb_p3 + ld a, #10 + ret +sptb_p3: + cp a, #64 + jr c, sptb_p4 + ld a, #11 + ret +sptb_p4: + cp a, #48 + jr c, sptb_p5 + ld a, #12 + ret +sptb_p5: + cp a, #32 + jr c, sptb_p6 + ld a, #13 + ret +sptb_p6: + cp a, #16 + jr c, sptb_p7 + ld a, #14 + ret +sptb_p7: + ld a, #15 + ret + +;====================================================================== +; spectrum_saa(reg, val) — A=reg, L=val +; +; SAA1099 chip 0: shadow octave (regs 0x10-0x12), trigger on amplitude (0-5). +; Bar mapping: if dual → bar=octave (0-7); if single → bar=octave*2 (0-14). +;====================================================================== +_spectrum_saa:: + ld d, a ; D = reg + ld e, l ; E = val + + ; ── Check octave shadow (regs 0x10-0x12) ── + cp a, #0x10 + jr c, ssaa_check_amp + cp a, #0x13 + jr nc, ssaa_check_amp + + ; ch = (reg - 0x10) * 2 + sub a, #0x10 + add a, a ; A = (reg-0x10)*2 = ch_base + ld hl, #_spec_saa_oct + add a, l + ld l, a + jr nc, ssaa_nc1 + inc h +ssaa_nc1: + ; spec_saa_oct[ch] = val & 0x07 + ld a, e + and a, #0x07 + ld (hl), a + ; spec_saa_oct[ch+1] = (val >> 4) & 0x07 + inc hl + ld a, e + rrca + rrca + rrca + rrca + and a, #0x07 + ld (hl), a + ret + + ; ── Check amplitude (regs 0x00-0x05) ── +ssaa_check_amp: + ld a, d + cp a, #0x06 + ret nc ; reg >= 6, not amplitude + ; Check val != 0 + ld a, e + or a, a + ret z + ; bar = spec_saa_oct[reg] + ld a, d ; A = reg (0..5) + ld hl, #_spec_saa_oct + add a, l + ld l, a + jr nc, ssaa_nc2 + inc h +ssaa_nc2: + ld a, (hl) ; A = octave (0..7) + ; if dual: bar = octave; if single: bar = octave * 2 + ld hl, #_spec_saa_dual + bit 0, (hl) + jr nz, ssaa_bar_done + add a, a ; A = octave * 2 +ssaa_bar_done: + jp _set_spec_bit ; tail-call + +;====================================================================== +; spectrum_saa2(reg, val) — A=reg, L=val +; +; SAA1099 chip 1: shadow octave to spec_saa_oct2[], +; trigger on amplitude with bar = octave + 8 (bars 8-15). +;====================================================================== +_spectrum_saa2:: + ld d, a ; D = reg + ld e, l ; E = val + + ; ── Check octave shadow (regs 0x10-0x12) ── + cp a, #0x10 + jr c, ssaa2_check_amp + cp a, #0x13 + jr nc, ssaa2_check_amp + + ; ch = (reg - 0x10) * 2 + sub a, #0x10 + add a, a + ld hl, #_spec_saa_oct2 + add a, l + ld l, a + jr nc, ssaa2_nc1 + inc h +ssaa2_nc1: + ld a, e + and a, #0x07 + ld (hl), a + inc hl + ld a, e + rrca + rrca + rrca + rrca + and a, #0x07 + ld (hl), a + ret + + ; ── Check amplitude (regs 0x00-0x05) ── +ssaa2_check_amp: + ld a, d + cp a, #0x06 + ret nc + ld a, e + or a, a + ret z + ; bar = spec_saa_oct2[reg] + 8 + ld a, d + ld hl, #_spec_saa_oct2 + add a, l + ld l, a + jr nc, ssaa2_nc2 + inc h +ssaa2_nc2: + ld a, (hl) ; A = octave (0..7) + add a, #8 ; bars 8-15 for chip 2 + jp _set_spec_bit ; tail-call + +;====================================================================== +; spectrum_font_init() — install custom glyphs for chars 0x01-0x02 +; +; Maps font page into page3 window, saves original 16 bytes (2 glyphs), +; writes striped-bar patterns from glyph_half / glyph_full. +; Must be called once before playback. Pairs with spectrum_font_restore. +;====================================================================== +_spectrum_font_init:: + ld bc, #PORT_PAGE3 + in a, (c) + push af + push bc + ld a, #WC_PAGE_FONT0 + out (c), a + ld hl, #(FONT_BASE + 8) + ld de, #sf_font_save + ld bc, #16 + ldir + ld hl, #sf_glyph_half + ld de, #(FONT_BASE + 8) + ld bc, #16 + ldir + pop bc + pop af + out (c), a + ret + +;====================================================================== +; spectrum_font_restore() — restore original glyphs for chars 0x01-0x02 +; +; Must be called on exit to avoid corrupting the system font. +;====================================================================== +_spectrum_font_restore:: + ld bc, #PORT_PAGE3 + in a, (c) + push af + push bc + ld a, #WC_PAGE_FONT0 + out (c), a + ld hl, #sf_font_save + ld de, #(FONT_BASE + 8) + ld bc, #16 + ldir + pop bc + pop af + out (c), a + ret + +sf_glyph_half: + .db 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00 +sf_glyph_full: + .db 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00 +sf_font_save: + .ds 16 diff --git a/src_sdcc/asm/txtlib.s b/src_sdcc/asm/txtlib.s new file mode 100644 index 0000000..dce535d --- /dev/null +++ b/src_sdcc/asm/txtlib.s @@ -0,0 +1,561 @@ +;============================================================================== +; txtlib.s — Text buffer library (hand-optimised Z80, sdcccall(1)) +; +; Replaces lib/txtlib.c. Every public function matches the ABI +; generated by SDCC for the prototypes in inc/txtlib.h. +; +; Buffer layout: buf[0] = max capacity, buf[1] = current length, +; buf[2..] = character data. +; +; sdcccall(1) register conventions: +; 1st ptr/u16 → HL 2nd ptr/u16 → DE +; 1st u8 → A 2nd u8 (when 1st is u8) → L +; Further args → stack (callee-cleans) +; Return u8 → A Return u16 → HL +;============================================================================== + + .module txtlib_mod + + .globl _buf_append_str + .globl _buf_append_char + .globl _buf_append_u8_hex + .globl _buf_append_u16_dec + .globl _buf_append_u32_dec + .globl _buf_append_mmss + .globl _print_line + + .globl _wc_prsrw_attr ; from wc_api.s + + .area _CODE + +;---------------------------------------------------------------------- +; Helper: nibble (0–15 in A) → ASCII char ('0'–'F') in A +;---------------------------------------------------------------------- +_nib2asc: + cp a, #10 + jr c, 001$ + add a, #7 +001$: add a, #0x30 + ret + +;====================================================================== +; uint8_t buf_append_str(char *buf, const char *src) +; HL=buf DE=src → A=added (no stack params) +;====================================================================== +_buf_append_str:: + ld c, (hl) ; C = max + inc hl + ld b, (hl) ; B = curlen + ld a, c + sub a, b ; A = remaining + jr z, bas_z + jr c, bas_z + push hl ; save &buf[1] + ld c, a ; C = remaining + inc hl ; HL → buf[2] + ld a, b + add a, l + ld l, a + jr nc, bas_nc + inc h +bas_nc: ld b, #0 ; B = added +bas_lp: ld a, (de) + or a, a + jr z, bas_dn + ld (hl), a + inc hl + inc de + inc b + dec c + jr nz, bas_lp +bas_dn: pop hl ; &buf[1] + ld a, (hl) + add a, b + ld (hl), a + ld a, b + ret +bas_z: xor a, a + ret + +;====================================================================== +; uint8_t buf_append_char(char *buf, char c) +; HL=buf stack: c(1) → A=0|1 callee-cleans 1 +;====================================================================== +_buf_append_char:: + push ix + ld ix, #0 + add ix, sp + ; 4(ix) = c + ld c, (hl) ; max + inc hl + ld a, (hl) ; curlen + cp a, c + jr nc, bac_f + ld e, a + inc a + ld (hl), a ; buf[1]++ + inc hl ; → buf[2] + ld a, e + add a, l + ld l, a + jr nc, bac_nc + inc h +bac_nc: ld a, 4 (ix) + ld (hl), a + ld a, #1 + jr bac_r +bac_f: xor a, a +bac_r: pop ix + pop hl + inc sp ; skip c (1 byte) + jp (hl) + +;====================================================================== +; uint8_t buf_append_u8_hex(char *buf, uint8_t val) +; HL=buf stack: val(1) → A=0|2 callee-cleans 1 +;====================================================================== +_buf_append_u8_hex:: + push ix + ld ix, #0 + add ix, sp + ; 4(ix) = val + ld c, (hl) ; max + inc hl + ld a, c + sub a, (hl) ; remaining + cp a, #2 + jr c, bh8_f + ld b, (hl) ; curlen + ld a, b + add a, #2 + ld (hl), a ; buf[1] += 2 + inc hl + ld a, b + add a, l + ld l, a + jr nc, bh8_nc + inc h +bh8_nc: ld a, 4 (ix) + rrca + rrca + rrca + rrca + and a, #0x0F + call _nib2asc + ld (hl), a + inc hl + ld a, 4 (ix) + and a, #0x0F + call _nib2asc + ld (hl), a + ld a, #2 + jr bh8_r +bh8_f: xor a, a +bh8_r: pop ix + pop hl + inc sp + jp (hl) + +;---------------------------------------------------------------------- +; Shared helper: emit one decimal digit. +; A = digit (0–9) +; B = "added" counter (0 = not-yet-started → suppress leading 0) +; C = remaining buffer space +; IY = destination pointer (advanced on write) +;---------------------------------------------------------------------- +_emit_digit: + push af + or a, b ; z iff digit==0 AND not started + jr z, ed_skip + ld a, c + or a, a + jr z, ed_skip ; no space + dec c + pop af + add a, #0x30 + ld 0 (iy), a + inc iy + inc b + ret +ed_skip: + pop af + ret + +;---------------------------------------------------------------------- +; Shared helper: extract one 32-bit decimal digit via repeated +; subtraction of a 4-byte constant taken from inline data after CALL. +; +; Usage: call _d32_digit +; .db lo, b1, b2, hi ; 32-bit subtrahend (LE) +; +; In: DEHL = 32-bit value, -3(ix) = scratch, +; B = started/added, C = remaining, IY = dest ptr +; Out: DEHL = remainder, digit emitted via _emit_digit +;---------------------------------------------------------------------- +_d32_digit: + ex (sp), hl ; HL = inline-data ptr, (SP)=val_lo + ld a, (hl) + ld (d32c0 + 1), a + inc hl + ld a, (hl) + ld (d32c1 + 1), a + inc hl + ld a, (hl) + ld (d32c2 + 1), a + inc hl + ld a, (hl) + ld (d32c3 + 1), a + inc hl ; HL past data = true ret addr + ex (sp), hl ; (SP)=true ret, HL=val_lo restored + ld -3 (ix), #0xFF +d32d_lp:inc -3 (ix) + push de + push hl + ld a, l +d32c0: sub a, #0x00 ; patched: subtrahend byte 0 + ld l, a + ld a, h +d32c1: sbc a, #0x00 ; patched: byte 1 + ld h, a + ld a, e +d32c2: sbc a, #0x00 ; patched: byte 2 + ld e, a + ld a, d +d32c3: sbc a, #0x00 ; patched: byte 3 + ld d, a + jr c, d32d_un + pop af + pop af + jr d32d_lp +d32d_un:pop hl + pop de + ld a, -3 (ix) + jp _emit_digit ; tail-call, ret goes past inline data + +;---------------------------------------------------------------------- +; Shared 16-bit decimal division: 10000/1000/100/10/ones. +; Entry _div_10k_ones: full 16-bit decimal (10000..1). +; Entry _div_1k_ones: skip 10000, do 1000..1. +; In: DE=value, B=added counter, C=remaining, IY=dst. +; Out: B=updated, DE=remainder. A,HL clobbered. +;---------------------------------------------------------------------- +_div_10k_ones: + ;-- 10000 (neg 0xD8F0) -- + ld a, #0xFF +_d10k: inc a + ld hl, #0xD8F0 + add hl, de + jr nc, _d10ke + ex de, hl + jr _d10k +_d10ke: call _emit_digit +_div_1k_ones: + ;-- 1000 (neg 0xFC18) -- + ld a, #0xFF +_d1k: inc a + ld hl, #0xFC18 + add hl, de + jr nc, _d1ke + ex de, hl + jr _d1k +_d1ke: call _emit_digit + ;-- 100 (neg 0xFF9C) -- + ld a, #0xFF +_d100: inc a + ld hl, #0xFF9C + add hl, de + jr nc, _d100e + ex de, hl + jr _d100 +_d100e: call _emit_digit + ;-- 10 (neg 0xFFF6) -- + ld a, #0xFF +_d10: inc a + ld hl, #0xFFF6 + add hl, de + jr nc, _d10e + ex de, hl + jr _d10 +_d10e: call _emit_digit + ;-- ones (always emit) -- + ld a, c + or a, a + ret z + ld a, e + add a, #0x30 + ld 0 (iy), a + inc iy + inc b + ret + +;====================================================================== +; uint8_t buf_append_u16_dec(char *buf, uint16_t val) +; HL=buf DE=val → A=added (no stack params) +;====================================================================== +_buf_append_u16_dec:: + push iy + ld c, (hl) + inc hl + ld a, c + sub a, (hl) + jr z, d16_z + jr c, d16_z + ld c, a ; C = remaining + ld b, (hl) ; B = curlen (temp) + push hl ; save &buf[1] + inc hl + ld a, b + add a, l + ld l, a + jr nc, d16_nc + inc h +d16_nc: push hl + pop iy ; IY = dst + ld b, #0 ; B = added/started + call _div_10k_ones + pop hl ; &buf[1] + ld a, (hl) + add a, b + ld (hl), a + ld a, b + pop iy + ret +d16_z: xor a, a + pop iy + ret + +;====================================================================== +; uint8_t buf_append_u32_dec(char *buf, uint32_t val) +; HL=buf stack: val(4) → A=added callee-cleans 4 +; 4(ix)=byte0(LSB) 5(ix)=byte1 6(ix)=byte2 7(ix)=byte3(MSB) +;====================================================================== +_buf_append_u32_dec:: + push ix + ld ix, #0 + add ix, sp + push iy ; -2,-1(ix) + push af ; -4,-3(ix) scratch + push af ; -6,-5(ix) scratch + ; -3(ix) used as digit counter per power + ld c, (hl) + inc hl + ld a, c + sub a, (hl) + jp z, d32_z + jp c, d32_z + ld c, a ; C = remaining + ld b, (hl) ; B = curlen (temp) + push hl ; save &buf[1] → -8,-7(ix) + inc hl + ld a, b + add a, l + ld l, a + jr nc, d32_nc + inc h +d32_nc: push hl + pop iy ; IY = dst + ; Load 32-bit val into DEHL + ld l, 4 (ix) + ld h, 5 (ix) + ld e, 6 (ix) + ld d, 7 (ix) + ld b, #0 ; B = added/started + ;-- 1,000,000 / 100,000 / 10,000 via shared subroutine -- + call _d32_digit + .db 0x40, 0x42, 0x0F, 0x00 ; 1,000,000 + call _d32_digit + .db 0xA0, 0x86, 0x01, 0x00 ; 100,000 + call _d32_digit + .db 0x10, 0x27, 0x00, 0x00 ; 10,000 + ;-- remainder < 10000, fits in HL → move to DE -- + ex de, hl + call _div_1k_ones + pop hl ; &buf[1] + ld a, (hl) + add a, b + ld (hl), a + ld a, b ; return value +d32_exit: + ; clean locals, restore, callee-clean 4 + pop de ; eat -6,-5 + pop de ; eat -4,-3 + pop iy + pop ix + pop hl ; ret addr + pop de ; skip val lo + pop de ; skip val hi + jp (hl) +d32_z: xor a, a + jr d32_exit + +;====================================================================== +; void print_line(wc_window_t *win, uint8_t y, const char *buf, +; uint8_t attr) +; HL=win stack: y(1) buf(2) attr(1) callee-cleans 4 +; Prints buf content + spaces padding to fill content width. +;====================================================================== +_print_line:: + push ix + ld ix, #0 + add ix, sp + ; 4(ix)=y 5,6(ix)=buf 7(ix)=attr + push af ; -2,-1(ix): cw at -1, slen at -2 + push hl ; -4,-3(ix): win + ;-- cw = get_content_width(win) -- + ld de, #4 + add hl, de + ld a, (hl) + or a, a + jr nz, pl_nz + ld a, #96 +pl_nz: sub a, #4 + ld -1 (ix), a + ;-- slen = buf[1] -- + ld l, 5 (ix) + ld h, 6 (ix) + inc hl + ld a, (hl) + ld -2 (ix), a + ;-- print_len = min(slen, cw) -- + cp a, -1 (ix) + jr c, pl_min + ld a, -1 (ix) +pl_min: ld c, a ; C = print_len + ;-- 1st wc_prsrw_attr(win, data, y, 2, print_len, attr) -- + ld a, 7 (ix) + push af + inc sp + ld b, #0 + push bc + ld h, #2 + ld l, 4 (ix) + push hl + ; DE = buf + 2 + ld a, 5 (ix) + add a, #2 + ld e, a + ld a, 6 (ix) + adc a, #0 + ld d, a + ; HL = win + ld l, -4 (ix) + ld h, -3 (ix) + call _wc_prsrw_attr + ;-- padding? -- + ld a, -2 (ix) + cp a, -1 (ix) + jr nc, pl_done + ; pad = cw − slen + ld a, -1 (ix) + sub a, -2 (ix) + ld c, a + ; Fill buf[2..2+pad-1] with 0x20 (runtime space fill) + ld l, 5 (ix) + ld h, 6 (ix) + inc hl + inc hl ; HL = &buf[2] + ld b, c ; B = pad count + ld a, #0x20 +pf_fill:ld (hl), a + inc hl + djnz pf_fill ; B=0 after loop + ;-- 2nd wc_prsrw_attr(win, &buf[2], y, 2+slen, pad, attr) -- + ld a, 7 (ix) + push af + inc sp + push bc ; len=pad (B=0, C=pad) + ld a, -2 (ix) + add a, #2 + ld h, a ; x = 2 + slen + ld l, 4 (ix) ; y + push hl + ld e, 5 (ix) + ld d, 6 (ix) + inc de + inc de ; DE = &buf[2] + ld l, -4 (ix) + ld h, -3 (ix) + call _wc_prsrw_attr +pl_done: + ld sp, ix + pop ix + pop hl + pop af + pop af ; callee-clean 4 + jp (hl) + +;---------------------------------------------------------------------- +; buf_append_mmss(buf, sec) — append "MM:SS" (5 chars) to buffer +; +; sdcccall(1): HL = buf, DE = sec (uint16_t, 0..5999) +; No division — subtraction loops only (~30 bytes code). +;---------------------------------------------------------------------- +_buf_append_mmss: + push hl + pop ix ; IX = buf + ; DE = total seconds. Divide by 60 via subtraction → B=min, E=sec + ld b, #0 +mmss_d60: + ld a, e + sub a, #60 + ld c, a + ld a, d + sbc a, #0 + jr c, mmss_d60_done + ld d, a + ld e, c + inc b + jr mmss_d60 +mmss_d60_done: + ; B = minutes, E = seconds + push de ; save seconds (mmss_2dig clobbers DE) + ld a, b + call mmss_2dig ; appends 2 digits of A + ; Append ':' + ld a, 1 (ix) + ld c, a + ld b, #0 + inc a + ld 1 (ix), a + push ix + pop hl + add hl, bc + inc hl ; skip buf[0] (max) + inc hl ; skip buf[1] (len) + ld (hl), #0x3A ; ':' + ; Append seconds + pop de ; restore E = seconds + ld a, e + ; fall through to mmss_2dig — but IX is still buf + ; call mmss_2dig + call mmss_2dig + ret + +; Subroutine: append 2 ASCII digits of A (0..99) to buf at IX +; Destroys A, C, B, HL +mmss_2dig: + ld c, #0x30 ; tens digit = '0' +mmss_t10: + sub a, #10 + jr c, mmss_t10_done + inc c + jr mmss_t10 +mmss_t10_done: + add a, #10 ; undo last sub + add a, #0x30 ; units → ASCII + ld b, a ; save units + ; Append tens (C) then units (B) + ld a, 1 (ix) ; len + ld l, a + ld h, #0 + add a, #2 + ld 1 (ix), a ; len += 2 + push ix + pop de ; DE = buf base + add hl, de + inc hl ; skip buf[0] (max) + inc hl ; skip buf[1] (len) + ld (hl), c ; tens + inc hl + ld (hl), b ; units + ret diff --git a/src_sdcc/asm/wc_api.s b/src_sdcc/asm/wc_api.s new file mode 100644 index 0000000..402276b --- /dev/null +++ b/src_sdcc/asm/wc_api.s @@ -0,0 +1,1338 @@ +;============================================================================== +; wc_api.s — Полные обёртки Wild Commander API для SDCC (sdcccall(1)) +; +; ── Соглашение sdcccall(1) — SDCC 4.x Z80 ───────────────────────────── +; Первый аргумент: uint8 → A +; uint16 / ptr → HL (ВАЖНО: HL, а не DE!) +; uint32 → DEHL +; Второй аргумент: uint16 / ptr → DE (если 1-й ≤ 16 бит) +; uint8 → +; - если первый аргумент uint8: второй → L +; - иначе (если первый uint16/ptr): второй → НА СТЕК +; Остальные: стек правым аргументом первым +; uint8 = 1 байт на стеке, uint16 = 2 байта (без выравнивания!) +; callee обязан очистить стековые аргументы (callee-cleans) +; Возврат: uint8 → A, uint16 → DE +; +; ── Стековый фрейм (типичный, внутри функции после PUSH IX, PUSH DE) ── +; IX+0: win_lo (сохранённый DE = win ptr, если PUSH DE производился) +; IX+1: win_hi +; IX+2: IX_save_lo +; IX+3: IX_save_hi +; IX+4: return address lo +; IX+5: return address hi +; IX+6: первый стековый аргумент (левый из оставшихся) +; IX+8: второй стековый аргумент +; ... и т.д. (каждый uint8 padding'ом до 2 байт) +; +; ── Трюк PUSH / POP для IX = регистр ────────────────────────────────── +; PUSH HL; POP IX → IX = HL (нет прямого LD IX, HL) +; +; ── Трюк EXX для сохранения BC/DE/HL в теневых регистрах ────────────── +; EXX — обменяет BC/DE/HL ↔ BC'/DE'/HL', не затрагивая IX и AF +; EX AF,AF' — поменяет A/F ↔ A'/F' (для передачи A' в WC) +; +;============================================================================== + + .module wc_api + +WC_ENTRY = 0x6006 + + .area _CODE + +;================================================================== +; СЕКЦИЯ 1: INIT / СТРАНИЧНАЯ КОММУТАЦИЯ +;================================================================== + +;------------------------------------------------------------------ +; void wc_mngc_pl(uint8_t page) — плагин-страница → #C000 (#00) +; sdcccall(1): A = page → A' +;------------------------------------------------------------------ + .globl _wc_mngc_pl +_wc_mngc_pl:: + ex af, af' + push ix + ld a, #0x00 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_mng0_pl(uint8_t page) — плагин-страница → #0000 (#4E) +;------------------------------------------------------------------ + .globl _wc_mng0_pl +_wc_mng0_pl:: + ex af, af' + push ix + ld a, #0x4E + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_mng8_pl(uint8_t page) — плагин-страница → #8000 (#4F) +;------------------------------------------------------------------ + .globl _wc_mng8_pl +_wc_mng8_pl:: + ex af, af' + push ix + ld a, #0x4F + call WC_ENTRY + pop ix + ret + +;================================================================== +; СЕКЦИЯ 2: UI / ОКНА +;================================================================== + +;------------------------------------------------------------------ +; void wc_gedpl(void) — восстановить дисплей WC (#0F) +;------------------------------------------------------------------ + .globl _wc_gedpl +_wc_gedpl:: + push ix + ld a, #0x0F + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_prwow(wc_window_t *win) — нарисовать окно (#01) +; sdcccall(1): HL = win → IX +;------------------------------------------------------------------ + .globl _wc_prwow +_wc_prwow:: + push ix + push hl ; IX = HL = win ptr + pop ix + ld a, #0x01 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_rresb(wc_window_t *win) — удалить окно (#02) +;------------------------------------------------------------------ + .globl _wc_rresb +_wc_rresb:: + push ix + push hl ; IX = HL = win ptr + pop ix + ld a, #0x02 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_cursor(wc_window_t *win) — нарисовать курсор (#06) +;------------------------------------------------------------------ + .globl _wc_cursor +_wc_cursor:: + push ix + push hl ; IX = HL = win ptr + pop ix + ld a, #0x06 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_curser(wc_window_t *win) — стереть курсор (#07) +;------------------------------------------------------------------ + .globl _wc_curser +_wc_curser:: + push ix + push hl ; IX = HL = win ptr + pop ix + ld a, #0x07 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_prsrw(win, str, y, x, len) — напечатать строку (#03) +; sdcccall(1): HL=win(1st ptr), DE=str(2nd ptr) +; stack: y(1 byte), x(1 byte), len(2 bytes) = 4 bytes +; WC: IX=win, HL=str, D=y, E=x, BC=len +; Callee cleans 4 bytes. +; Stack after push ix, push hl, push de: +; 0,1=str 2,3=win 4,5=IX 6,7=ret 8=y 9=x 10,11=len +;------------------------------------------------------------------ + .globl _wc_prsrw +_wc_prsrw:: + push ix + push hl ; save win (HL = 1st arg) + push de ; save str (DE = 2nd arg) + ld ix, #0 + add ix, sp + ld b, 11(ix) ; B = len_hi + ld c, 10(ix) ; C = len_lo + ld d, 8(ix) ; D = y + ld e, 9(ix) ; E = x + ld l, 0(ix) + ld h, 1(ix) ; HL = str + exx + ld l, 2(ix) + ld h, 3(ix) ; HL = win + push hl + pop ix ; IX = win + exx ; BC=len, DE=y|x, HL=str + ld a, #0x03 + call WC_ENTRY + pop de ; remove saved str + pop hl ; remove saved win + pop ix ; restore caller IX + ; callee cleans: y(1)+x(1)+len(2) = 4 bytes + pop hl ; HL = return address + inc sp + inc sp + inc sp + inc sp + jp (hl) + +;------------------------------------------------------------------ +; void wc_prsrw_attr(win, str, y, x, len, attr) — напечатать строку + атрибут (#03 + #04) +; sdcccall(1): HL=win(1st ptr), DE=str(2nd ptr) +; stack: y(1), x(1), len(2), attr(1) = 5 bytes +; WC #03: IX=win, HL=str, D=y, E=x, BC=len +; WC #04: IX=win, A'=attr, D=y, E=x, BC=len +; Callee cleans 5 bytes. +; Stack after push ix, push hl, push de: +; 0,1=str 2,3=win 4,5=IX 6,7=ret 8=y 9=x 10=len_lo 11=len_hi 12=attr +;------------------------------------------------------------------ + .globl _wc_prsrw_attr +_wc_prsrw_attr:: + push ix + push hl ; save win (HL = 1st arg) + push de ; save str (DE = 2nd arg) + ld ix, #0 + add ix, sp + + ; ── вызов #03 (prsrw) ────────────────────────────────── + ld b, 11(ix) ; B = len_hi + ld c, 10(ix) ; C = len_lo + ld d, 8(ix) ; D = y + ld e, 9(ix) ; E = x + ld l, 0(ix) + ld h, 1(ix) ; HL = str + exx + ld l, 2(ix) + ld h, 3(ix) ; HL = win + ld a, 12(ix) ; A = attr (IX всё ещё → фрейм) + push af ; сохраняем attr на стеке + push hl + pop ix ; IX = win + exx ; BC=len, DE=y|x, HL=str; HL'=win + ld a, #0x03 + call WC_ENTRY + ; после возврата: BC/DE=len/y|x (WC #03 не портит), + ; HL'=win (WC сохраняет alternate regs) + + ; ── вызов #04 (priat) ────────────────────────────────── + pop af ; A = attr + ex af, af' ; A' = attr + exx ; HL = win (из HL'), BC=len, DE=y|x + push hl + pop ix ; IX = win + exx ; restore BC=len, DE=y|x + ld a, #0x04 + call WC_ENTRY + + pop de ; remove saved str + pop hl ; remove saved win + pop ix ; restore caller IX + ; callee cleans 5 bytes: y(1)+x(1)+len(2)+attr(1) + pop hl ; HL = return address + inc sp + inc sp + inc sp + inc sp + inc sp + jp (hl) + +;------------------------------------------------------------------ +; uint16_t wc_gadrw(win, y, x) — адрес ячейки экрана (#05) +; sdcccall(1): HL=win(1st ptr), stack: y(1 byte), x(1 byte) = 2 bytes +; WC: IX=win, D=y, E=x → HL=адрес +; sdcccall(1) returns uint16_t in DE. +; Callee cleans 2 bytes. +; Stack after push ix, push hl: 0,1=win 2,3=IX 4,5=ret 6=y 7=x +;------------------------------------------------------------------ + .globl _wc_gadrw +_wc_gadrw:: + push ix + push hl ; save win (HL = 1st arg) + ld ix, #0 + add ix, sp + ld d, 6(ix) ; D = y + ld e, 7(ix) ; E = x + ld l, 0(ix) + ld h, 1(ix) ; HL = win + push hl + pop ix ; IX = win + ld a, #0x05 + call WC_ENTRY ; HL = result addr + ex de, hl ; DE = result (sdcccall(1) uint16) + pop hl ; remove saved win (use HL, keep DE) + pop ix ; restore caller IX + ; callee cleans 2 bytes (y+x); preserve DE = return value + pop hl ; HL = return address + inc sp + inc sp ; discard y(1)+x(1) + jp (hl) ; return, DE = result + +;------------------------------------------------------------------ +; uint8_t wc_yn(uint8_t mode) — меню Да/Нет (#08) +; sdcccall(1): A=mode → A' +;------------------------------------------------------------------ + .globl _wc_yn +_wc_yn:: + ex af, af' + push ix + ld a, #0x08 + call WC_ENTRY + ; Возвращаем: Z=ok → A=1, NZ → A=0 + ld a, #0 + jr nz, _wc_yn_end + inc a +_wc_yn_end: + pop ix + ret + +;------------------------------------------------------------------ +; void wc_istr(wc_window_t *win, uint8_t mode) — редактор строки (#09) +; sdcccall(1): HL=win(1st ptr), stack: mode(1 byte) +; WC: IX=win, A'=mode +; Callee cleans 1 byte. +;------------------------------------------------------------------ + .globl _wc_istr +_wc_istr:: + push ix + ld ix, #0 + add ix, sp + ld a, 4(ix) ; A = mode (from stack) + ex af, af' ; A' = mode + push hl + pop ix ; IX = win (HL = 1st arg) + ld a, #0x09 + call WC_ENTRY + pop ix + ; callee cleans 1 byte (mode) + pop hl + inc sp + jp (hl) + +;------------------------------------------------------------------ +; void wc_nork(uint16_t addr, uint8_t val) — вывести HEX байт (#0A) +; sdcccall(1): HL=addr(1st uint16), stack: val(1 byte) +; WC: HL=addr, A'=val +; Callee cleans 1 byte. +;------------------------------------------------------------------ + .globl _wc_nork +_wc_nork:: + push ix + ld ix, #0 + add ix, sp + ld a, 4(ix) ; A = val (from stack) + ex af, af' ; A' = val + ; HL = addr (1st arg, already in HL; WC needs HL) + ld a, #0x0A + call WC_ENTRY + pop ix + ; callee cleans 1 byte (val) + pop hl + inc sp + jp (hl) + +;------------------------------------------------------------------ +; uint8_t wc_txtpr(win, str, y, x) — печать текста (#0B) +; sdcccall(1): HL=win, DE=str; стек: push bc где B=y, C=x +; WC: IX=win, HL=str, D=y, E=x → D=след. строка +; Callee cleans 2 stack bytes (push bc). +; Stack from IX after push ix, push hl, push de: +; IX+0,1=str IX+2,3=win IX+4,5=IX IX+6,7=ret IX+8=C=x IX+9=B=y +;------------------------------------------------------------------ + .globl _wc_txtpr +_wc_txtpr:: + push ix ; (1) save caller IX + push hl ; (2) save win ptr (HL = 1st arg) + push de ; (3) save str ptr (DE = 2nd arg) + ld ix, #0 + add ix, sp + ld d, 9(ix) ; D = y (B from caller's push bc, hi byte) + ld e, 8(ix) ; E = x (C from caller's push bc, lo byte) + ld l, 0(ix) + ld h, 1(ix) ; HL = str ptr + exx ; shadow: HL'=str, D'=y, E'=x + ld l, 2(ix) + ld h, 3(ix) ; HL = win ptr + push hl + pop ix ; IX = win ptr + exx ; restore HL=str, D=y, E=x + ld a, #0x0B + call WC_ENTRY + ld a, d ; A = return: next Y + pop de ; (3) restore DE + pop hl ; (2) restore HL + pop ix ; (1) restore IX + pop hl ; HL = return address + inc sp ; } discard caller's push bc (2 bytes: y, x) + inc sp ; } + jp (hl) ; return (callee cleans stack args) + +;------------------------------------------------------------------ +; uint8_t wc_mezz(win, msg_num, str, y, x) — сообщение WC (#0C) +; sdcccall(1): HL=win(1st ptr), stack: msg_num(1), str(2), y(1), x(1) = 5 bytes +; WC: IX=win, A'=msg_num, HL=str, D=y, E=x → D=след. строка +; Callee cleans 5 bytes. +; Stack after push ix, push hl: +; 0,1=win 2,3=IX 4,5=ret 6=msg_num 7,8=str 9=y 10=x +;------------------------------------------------------------------ + .globl _wc_mezz +_wc_mezz:: + push ix + push hl ; save win (HL = 1st arg) + ld ix, #0 + add ix, sp + ld a, 6(ix) ; A = msg_num + ld l, 7(ix) + ld h, 8(ix) ; HL = str + ld d, 9(ix) ; D = y + ld e, 10(ix) ; E = x + exx + ex af, af' ; A' = msg_num + ld l, 0(ix) + ld h, 1(ix) ; HL = win + push hl + pop ix ; IX = win + exx ; HL=str, D=y, E=x + ld a, #0x0C + call WC_ENTRY + ld a, d ; return: next row + pop hl ; remove saved win + pop ix + ; callee cleans 5 bytes + pop hl ; HL = return address + inc sp + inc sp + inc sp + inc sp + inc sp + jp (hl) + +;------------------------------------------------------------------ +; void wc_scrlwow(win, y, x, h, w, flags) — прокрутка (#54) +; sdcccall(1): HL=win(1st ptr), stack: y(1),x(1),h(1),w(1),flags(1) = 5 bytes +; WC: IX=win, D=y, E=x, B=h, C=w, A'=flags +; Callee cleans 5 bytes. +; Stack after push ix, push hl: +; 0,1=win 2,3=IX 4,5=ret 6=y 7=x 8=h 9=w 10=flags +;------------------------------------------------------------------ + .globl _wc_scrlwow +_wc_scrlwow:: + push ix + push hl ; save win (HL = 1st arg) + ld ix, #0 + add ix, sp + ld a, 10(ix) ; A = flags + ld b, 8(ix) ; B = h + ld c, 9(ix) ; C = w + ld d, 6(ix) ; D = y + ld e, 7(ix) ; E = x + exx + ex af, af' ; A' = flags + ld l, 0(ix) + ld h, 1(ix) + push hl + pop ix ; IX = win + exx ; BC=h/w, DE=y/x + ld a, #0x54 + call WC_ENTRY + pop hl ; remove saved win + pop ix + ; callee cleans 5 bytes + pop hl + inc sp + inc sp + inc sp + inc sp + inc sp + jp (hl) + pop de + pop ix + ret + +;================================================================== +; СЕКЦИЯ 3: КЛАВИАТУРА +; Шаблон для клавиш без аргументов: +; push ix; ld a,#N; call WC_ENTRY; pop ix; ret +;================================================================== + +;------------------------------------------------------------------ +; Макро-генерация функций проверки клавиш (push ix не нужен для WC, +; но сохраняем на случай, если WC его трогает в редких случаях) +;------------------------------------------------------------------ + + .globl _wc_key_space +_wc_key_space:: + push ix + ld a, #0x10 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_up +_wc_key_up:: + push ix + ld a, #0x11 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_down +_wc_key_down:: + push ix + ld a, #0x12 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_left +_wc_key_left:: + push ix + ld a, #0x13 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_right +_wc_key_right:: + push ix + ld a, #0x14 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_tab +_wc_key_tab:: + push ix + ld a, #0x15 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_enter +_wc_key_enter:: + push ix + ld a, #0x16 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_esc +_wc_key_esc:: + push ix + ld a, #0x17 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_bspc +_wc_key_bspc:: + push ix + ld a, #0x18 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_pgup +_wc_key_pgup:: + push ix + ld a, #0x19 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_pgdn +_wc_key_pgdn:: + push ix + ld a, #0x1A + call WC_ENTRY + pop ix + ret + + .globl _wc_key_home +_wc_key_home:: + push ix + ld a, #0x1B + call WC_ENTRY + pop ix + ret + + .globl _wc_key_end +_wc_key_end:: + push ix + ld a, #0x1C + call WC_ENTRY + pop ix + ret + +; F1–F10 (#1D–#26) + .globl _wc_key_f1 +_wc_key_f1:: + push ix + ld a, #0x1D + call WC_ENTRY + pop ix + ret + + .globl _wc_key_f2 +_wc_key_f2:: + push ix + ld a, #0x1E + call WC_ENTRY + pop ix + ret + + .globl _wc_key_f3 +_wc_key_f3:: + push ix + ld a, #0x1F + call WC_ENTRY + pop ix + ret + + .globl _wc_key_f4 +_wc_key_f4:: + push ix + ld a, #0x20 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_f5 +_wc_key_f5:: + push ix + ld a, #0x21 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_f6 +_wc_key_f6:: + push ix + ld a, #0x22 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_f7 +_wc_key_f7:: + push ix + ld a, #0x23 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_f8 +_wc_key_f8:: + push ix + ld a, #0x24 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_f9 +_wc_key_f9:: + push ix + ld a, #0x25 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_f10 +_wc_key_f10:: + push ix + ld a, #0x26 + call WC_ENTRY + pop ix + ret + +; Alt/Shift/Ctrl (без автоповтора) + .globl _wc_key_alt +_wc_key_alt:: + push ix + ld a, #0x27 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_shift +_wc_key_shift:: + push ix + ld a, #0x28 + call WC_ENTRY + pop ix + ret + + .globl _wc_key_ctrl +_wc_key_ctrl:: + push ix + ld a, #0x29 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; uint8_t wc_kbscn(uint8_t mode) — сканировать клавишу (#2A) +; sdcccall(1): A=mode → A' +; Возврат: A=keycode (0=нет) +;------------------------------------------------------------------ + .globl _wc_kbscn +_wc_kbscn:: + ex af, af' ; A' = mode + push ix + ld a, #0x2A + call WC_ENTRY + pop ix + ret + +; Del/Caps/AnyKey/WaitRelease/WaitAny/Ins + .globl _wc_key_del +_wc_key_del:: + push ix + ld a, #0x2B + call WC_ENTRY + pop ix + ret + + .globl _wc_key_caps +_wc_key_caps:: + push ix + ld a, #0x2C + call WC_ENTRY + pop ix + ret + + .globl _wc_key_any +_wc_key_any:: + push ix + ld a, #0x2D + call WC_ENTRY + pop ix + ret + + .globl _wc_key_wait_release +_wc_key_wait_release:: + push ix + ld a, #0x2E + call WC_ENTRY + pop ix + ret + + .globl _wc_key_wait_any +_wc_key_wait_any:: + push ix + ld a, #0x2F + call WC_ENTRY + pop ix + ret + + .globl _wc_key_ins +_wc_key_ins:: + push ix + ld a, #0x55 + call WC_ENTRY + pop ix + ret + +;================================================================== +; СЕКЦИЯ 4: ФАЙЛОВЫЕ ОПЕРАЦИИ +;================================================================== + +;------------------------------------------------------------------ +; uint8_t wc_load512(uint16_t dest, uint8_t blocks) — загрузить (#30) +; sdcccall(1): HL=dest; стек: blocks (1 байт, push af + inc sp) +; WC: HL=dest, B=blocks → A=0 ok / 0x0F EOF +; Callee cleans 1 stack byte (blocks). +; Stack после push ix: IX+2=ret, IX+4=blocks +;------------------------------------------------------------------ + .globl _wc_load512 +_wc_load512:: + push ix + ld ix, #0 + add ix, sp + ld b, 4(ix) ; B = blocks (1 byte on stack) + ; HL = dest (sdcccall(1) first uint16 → HL — do NOT ex de,hl!) + ld a, #0x30 + call WC_ENTRY + pop ix + pop hl ; HL = return address + inc sp ; discard 1-byte 'blocks' arg + jp (hl) ; return (callee cleans stack arg) + +;------------------------------------------------------------------ +; void wc_save512(uint16_t src, uint8_t blocks) — сохранить (#31) +; sdcccall(1): HL=src; стек: blocks (1 байт, callee cleans) +;------------------------------------------------------------------ + .globl _wc_save512 +_wc_save512:: + push ix + ld ix, #0 + add ix, sp + ld b, 4(ix) + ; HL = src (DON'T ex de, hl!) + ld a, #0x31 + call WC_ENTRY + pop ix + pop hl + inc sp + jp (hl) + +;------------------------------------------------------------------ +; void wc_gipagpl(void) — позиционировать на начало файла (#32) +;------------------------------------------------------------------ + .globl _wc_gipagpl +_wc_gipagpl:: + push ix + ld a, #0x32 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_tentry(uint16_t addr) — прочитать ENTRY в буфер (#33) +; sdcccall(1): HL=addr (1st uint16 → HL) +; WC: DE=addr +;------------------------------------------------------------------ + .globl _wc_tentry +_wc_tentry:: + push ix + ex de, hl ; DE = addr (SDCC HL → WC DE) + ld a, #0x33 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_chtosep(uint16_t buf, uint16_t bufend) (#34) +; sdcccall(1): HL=buf(1st uint16), DE=bufend(2nd uint16) +; WC: DE=buf, BC=bufend +; No stack args. +;------------------------------------------------------------------ + .globl _wc_chtosep +_wc_chtosep:: + push ix + ld b, d + ld c, e ; BC = bufend (from DE) + ex de, hl ; DE = buf (from HL) + ld a, #0x34 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_tmrkdfl(panel, filenum, namebuf) — заголовок помеч. файла (#36) +; sdcccall(1): HL=panel(1st ptr), DE=filenum(2nd uint16) +; stack: namebuf(2 bytes) +; WC: IX=panel, HL=filenum, DE=namebuf +; Callee cleans 2 bytes. +; Stack after push ix, push hl, push de: +; 0,1=filenum 2,3=panel 4,5=IX 6,7=ret 8,9=namebuf +;------------------------------------------------------------------ + .globl _wc_tmrkdfl +_wc_tmrkdfl:: + push ix + push hl ; save panel (HL = 1st arg) + push de ; save filenum (DE = 2nd arg) + ld ix, #0 + add ix, sp + ld e, 8(ix) + ld d, 9(ix) ; DE = namebuf (from stack) + ld l, 0(ix) + ld h, 1(ix) ; HL = filenum (from saved DE) + exx + ld l, 2(ix) + ld h, 3(ix) ; HL = panel (from saved HL) + push hl + pop ix ; IX = panel + exx ; HL=filenum, DE=namebuf + ld a, #0x36 + call WC_ENTRY + pop de ; remove saved filenum + pop hl ; remove saved panel + pop ix + ; callee cleans 2 bytes (namebuf) + pop hl + inc sp + inc sp + jp (hl) + +;------------------------------------------------------------------ +; void wc_adir(uint8_t mode) — позиционирование директории (#38) +; sdcccall(1): A=mode → A' +;------------------------------------------------------------------ + .globl _wc_adir +_wc_adir:: + ex af, af' + push ix + ld a, #0x38 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_stream(uint8_t mode) — переключить поток (#39) +; sdcccall(1): A=mode +; WC: D=mode +;------------------------------------------------------------------ + .globl _wc_stream +_wc_stream:: + push ix + ld d, a + ld a, #0x39 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; uint8_t wc_findnext(uint16_t entry_buf, uint8_t flags) — FindNext (#3A) +; sdcccall(1): HL=entry_buf(1st uint16), stack: flags(1 byte) +; WC: DE=entry_buf, A'=flags → Z=конец, NZ=найден +; Callee cleans 1 byte. +;------------------------------------------------------------------ + .globl _wc_findnext +_wc_findnext:: + push ix + ld ix, #0 + add ix, sp + ld a, 4(ix) ; A = flags (from stack) + ex af, af' ; A' = flags + ex de, hl ; DE = entry_buf (SDCC HL → WC DE) + ld a, #0x3A + call WC_ENTRY + ld a, #0 + jr z, _wc_findnext_end + inc a +_wc_findnext_end: + pop ix + ; callee cleans 1 byte (flags) + pop hl + inc sp + jp (hl) + +;------------------------------------------------------------------ +; uint8_t wc_fentry(uint16_t name_with_flag) — найти файл (#3B) +; sdcccall(1): HL=name (1st uint16 → HL) +; WC: HL=name → Z=не найден, NZ=найден +;------------------------------------------------------------------ + .globl _wc_fentry +_wc_fentry:: + push ix + ; HL = name (sdcccall(1); WC нужен HL — уже на месте!) + ld a, #0x3B + call WC_ENTRY + ld a, #0 + jr z, _wc_fentry_end + inc a +_wc_fentry_end: + pop ix + ret + +;------------------------------------------------------------------ +; void wc_loadnone(uint8_t sectors) — пропустить секторы (#3D) +; sdcccall(1): A = sectors +; WC: B=sectors (предположительно) +;------------------------------------------------------------------ + .globl _wc_loadnone +_wc_loadnone:: + push ix + ld b, a + ld a, #0x3D + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_gfile(void) — открыть найденный файл (#3E) +;------------------------------------------------------------------ + .globl _wc_gfile +_wc_gfile:: + push ix + ld a, #0x3E + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_gdir(void) — войти в найденный каталог (#3F) +;------------------------------------------------------------------ + .globl _wc_gdir +_wc_gdir:: + push ix + ld a, #0x3F + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; uint8_t wc_mkfile(uint16_t name_with_flag) — создать файл (#48) +; sdcccall(1): HL=name (1st uint16 → HL) +; WC: HL=name → A=ошибка (0=ok) +;------------------------------------------------------------------ + .globl _wc_mkfile +_wc_mkfile:: + push ix + ld a, #0x48 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; uint8_t wc_mkdir(uint16_t name) — создать каталог (#49) +; sdcccall(1): HL=name; WC: HL=name +;------------------------------------------------------------------ + .globl _wc_mkdir +_wc_mkdir:: + push ix + ld a, #0x49 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; uint8_t wc_rename(uint16_t old_name, uint16_t new_name) (#4A) +; sdcccall(1): HL=old_name(1st), DE=new_name(2nd) +; WC: HL=old_name, DE=new_name (уже на месте!) +; No stack args. +;------------------------------------------------------------------ + .globl _wc_rename +_wc_rename:: + push ix + ld a, #0x4A + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; uint8_t wc_delfl(uint16_t name_with_flag) — удалить файл (#4B) +; sdcccall(1): HL=name; WC: HL=name +;------------------------------------------------------------------ + .globl _wc_delfl +_wc_delfl:: + push ix + ld a, #0x4B + call WC_ENTRY + pop ix + ret + +;================================================================== +; СЕКЦИЯ 5: ГРАФИКА +;================================================================== + +;------------------------------------------------------------------ +; void wc_mngv_pl(uint8_t mode) — видеорежим WC (#40) +; A=mode → A' +;------------------------------------------------------------------ + .globl _wc_mngv_pl +_wc_mngv_pl:: + ex af, af' + push ix + ld a, #0x40 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_mngcvpl(uint8_t vpage) — видеостраница → #C000 (#41) +; A=vpage → A' +;------------------------------------------------------------------ + .globl _wc_mngcvpl +_wc_mngcvpl:: + ex af, af' + push ix + ld a, #0x41 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_gvmod(uint8_t mode) — установить видеорежим TSConfig (#42) +; A=mode → A' +;------------------------------------------------------------------ + .globl _wc_gvmod +_wc_gvmod:: + ex af, af' + push ix + ld a, #0x42 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_gyoff(uint16_t y) — Y-смещение прокрутки (#43) +; sdcccall(1): HL=y (1st uint16 → HL) +; WC: HL=y (уже на месте!) +;------------------------------------------------------------------ + .globl _wc_gyoff +_wc_gyoff:: + push ix + ld a, #0x43 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_gxoff(uint16_t x) — X-смещение прокрутки (#44) +; sdcccall(1): HL=x; WC: HL=x +;------------------------------------------------------------------ + .globl _wc_gxoff +_wc_gxoff:: + push ix + ld a, #0x44 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_gvtm(uint8_t page) — страница тайловой карты (#45) +; WC: C=page +;------------------------------------------------------------------ + .globl _wc_gvtm +_wc_gvtm:: + push ix + ld c, a + ld a, #0x45 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_gvtl(uint8_t plane, uint8_t page) — тайл-графика (#46) +; sdcccall(1): A=plane(1st uint8), stack: page(1 byte) +; WC: B=plane, C=page +; Callee cleans 1 byte. +;------------------------------------------------------------------ + .globl _wc_gvtl +_wc_gvtl:: + push ix + ld ix, #0 + add ix, sp + ld c, 4(ix) ; C = page + ld b, a ; B = plane + ld a, #0x46 + call WC_ENTRY + pop ix + ; callee cleans 1 byte (page) + pop hl + inc sp + jp (hl) + +;------------------------------------------------------------------ +; void wc_gvsgp(uint8_t page) — страница спрайт-графики (#47) +; WC: C=page +;------------------------------------------------------------------ + .globl _wc_gvsgp +_wc_gvsgp:: + push ix + ld c, a + ld a, #0x47 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_mng0vpl(uint8_t vpage) — видеостраница → #0000 (#50) +;------------------------------------------------------------------ + .globl _wc_mng0vpl +_wc_mng0vpl:: + ex af, af' + push ix + ld a, #0x50 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_mng8vpl(uint8_t vpage) — видеостраница → #8000 (#51) +;------------------------------------------------------------------ + .globl _wc_mng8vpl +_wc_mng8vpl:: + ex af, af' + push ix + ld a, #0x51 + call WC_ENTRY + pop ix + ret + +;================================================================== +; СЕКЦИЯ 6: DMA +;================================================================== + +;------------------------------------------------------------------ +; void wc_dmapl(uint8_t subfunc) — операции DMA (#0D) +; A=subfunc → A' +;------------------------------------------------------------------ + .globl _wc_dmapl +_wc_dmapl:: + ex af, af' + push ix + ld a, #0x0D + call WC_ENTRY + pop ix + ret + +;================================================================== +; СЕКЦИЯ 7: РАЗНОЕ +;================================================================== + +;------------------------------------------------------------------ +; void wc_turbopl(uint8_t mode, uint8_t param) — частота CPU/AY (#0E) +; sdcccall(1): A=mode(1st uint8), L=param(2nd uint8) +; WC: B=mode, C=param +; Нет стековых аргументов. +;------------------------------------------------------------------ + .globl _wc_turbopl +_wc_turbopl:: + push ix + ld c, l ; C = param (2-й аргумент) + ld b, a ; B = mode (1-й аргумент) + ld a, #0x0E + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; uint8_t wc_prm_pl(uint8_t param_num) — параметр из INI (#53) +; A=param_num → A' +; Возврат: 0xFF = нет параметра, иное = номер опции +;------------------------------------------------------------------ + .globl _wc_prm_pl +_wc_prm_pl:: + ex af, af' + push ix + ld a, #0x53 + call WC_ENTRY + ; Z=нет параметра → вернуть 0xFF, иначе A=номер опции + jr nz, _wc_prm_pl_end + ld a, #0xFF +_wc_prm_pl_end: + pop ix + ret + +;------------------------------------------------------------------ +; void wc_int_pl(uint8_t mode) — управление прерываниями WC (#56) +; A=mode → A' (режимы: WC_INT_*) +;------------------------------------------------------------------ + .globl _wc_int_pl +_wc_int_pl:: + ex af, af' + push ix + ld a, #0x56 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; void wc_int_pl_handler(uint16_t handler_addr) — установить ISR (#56) +; sdcccall(1): HL=handler_addr (1st uint16 → HL) +; WC: A'=0xFF, HL=addr (HL уже на месте!) +;------------------------------------------------------------------ + .globl _wc_int_pl_handler +_wc_int_pl_handler:: + push ix + ; HL = handler_addr (sdcccall(1); WC тоже нужен HL) + ld a, #0xFF + ex af, af' ; A' = 0xFF + ld a, #0x56 + call WC_ENTRY + pop ix + ret + +;------------------------------------------------------------------ +; uint8_t wc_load256(uint16_t dest, uint8_t blocks) — загрузить 256б блоков (#3C) +; sdcccall(1): HL=dest; стек: blocks (1 байт, callee cleans) +; WC: HL=addr, B=blocks → A=EndOfChain(#0F) +;------------------------------------------------------------------ + .globl _wc_load256 +_wc_load256:: + push ix + ld ix, #0 + add ix, sp + ld b, 4(ix) ; B = blocks (1 byte on stack) + ; HL = dest (already in HL) + ld a, #0x3C + call WC_ENTRY + pop ix + pop hl ; HL = return address + inc sp ; discard 1-byte 'blocks' arg + jp (hl) ; return (callee cleans stack arg) + +;------------------------------------------------------------------ +; void wc_resident_jump(uint8_t page, uint16_t addr) — переход в другую страницу +; sdcccall(1): A=page, DE=addr (uint16 2nd → DE) +; Нет stack args. +; Реализация: LD HL,DE; LD A,page; JP #6020 +;------------------------------------------------------------------ + .globl _wc_resident_jump +_wc_resident_jump:: + ex de, hl ; HL = addr (from DE = 2nd arg) + ; A = page (1st arg, already in A) + jp 0x6020 ; resident jump — never returns! + +;------------------------------------------------------------------ +; void wc_resident_call(uint8_t page, uint16_t addr) — вызов в другой странице +; sdcccall(1): A=page, DE=addr (uint16 2nd → DE) +; Нет stack args. +; Реализация: LD HL,DE; LD A,page; CALL #6028 +;------------------------------------------------------------------ + .globl _wc_resident_call +_wc_resident_call:: + ex de, hl ; HL = addr (from DE = 2nd arg) + ; A = page (1st arg, already in A) + call 0x6028 ; resident call — returns here + ret + +;------------------------------------------------------------------ +; void wc_strset(char *dsr, uint16_t len, char c) +; Заполняет строку символом c на длину len +; sdcccall(1): HL=dst(1st ptr), DE=len(2nd uint16), +; stack: c(1 byte) +; Callee cleans 1 byte +;------------------------------------------------------------------- + .globl _wc_strset +_wc_strset:: + push ix + ld ix, #0 + add ix, sp + + ld a, 4(ix) ; c (из-за push ix) + pop ix + + ; записываем первый байт + ld (hl), a + + ld b, d + ld c, e + + ; len == 1 → всё + dec bc + ld a, b + or c + jr z, done + + ; DE = HL + 1 + push hl + pop de + inc de + + ; BC = len - 1 уже + + ldir + +done: + ; очистка стека + pop bc + inc sp + push bc + ret diff --git a/src_sdcc/build.bat b/src_sdcc/build.bat new file mode 100644 index 0000000..f028d01 --- /dev/null +++ b/src_sdcc/build.bat @@ -0,0 +1,172 @@ +@echo off +chcp 65001 >nul +REM =========================================================================== +REM build.bat — Сборка VGM Player Plugin (SDCC/Z80) +REM +REM Инструменты: +REM sdcc — компилятор C (sdcccall(1), mz80, без frame pointer) +REM sdas — ассемблер SDCC (ASM файлы) +REM sdld / sdcc --link-cmd-file — линковщик +REM node — JS-скрипты (gen_pos_table, ihx2wmf) +REM +REM Структура вывода: build/ (временные .rel) → vgmplay.wmf +REM =========================================================================== + +setlocal + +cd /d "%~dp0" + +REM --- Папка для временных файлов --- +if not exist build mkdir build + +REM --- Флаги SDCC --- +REM -mz80 : целевая архитектура Z80 +REM --sdcccall 1 : соглашение вызова sdcccall(1) (регистровая передача) +REM --no-std-crt0 : не линковать стандартный crt0 (наш в asm/crt0.s) +REM --opt-code-size : оптимизация по размеру (важно для 8-бит) +REM --no-peep-asm : не трогать __naked asm-блоки +set CFLAGS=-mz80 --sdcccall 1 --no-std-crt0 --opt-code-size -I inc --disable-warning 85 + +REM --- Флаги ассемблера (sdas) --- +set ASFLAGS=-plosff + +REM --- Читаем параметры варианта из variant_cfg.h --- +for /f "tokens=3" %%a in ('findstr /C:"#define VARIANT_POS_ENTRIES" inc\variant_cfg.h') do set POS_ENTRIES=%%a +for /f "tokens=3" %%a in ('findstr /C:"#define VARIANT_POS_STEP" inc\variant_cfg.h') do set POS_STEP=%%a +for /f "tokens=3" %%a in ('findstr /C:"#define VARIANT_DATA_LOC" inc\variant_cfg.h') do set DATA_LOC=%%a +echo variant_cfg: entries=%POS_ENTRIES% step=%POS_STEP% data-loc=%DATA_LOC% + +echo [1/8] Генерация pos_table.s... +node scripts\gen_pos_table.js --entries %POS_ENTRIES% --step %POS_STEP% +if errorlevel 1 ( echo FAIL gen_pos_table && goto :err ) + +echo [2/8] Генерация freq_tables.bin... +node scripts\gen_freq_tables.js --page-base 1 --binary build\freq_tables.bin --cheader inc\freq_lut_map.h +if errorlevel 1 ( echo FAIL gen_freq_tables && goto :err ) + +echo [3/9] Сборка inflate.asm (sjasmplus)... +..\tools\sjasm\sjasmplus.exe asm\inflate.asm >nul 2>&1 +if errorlevel 1 ( echo FAIL inflate.asm && goto :err ) +REM inflate.asm имеет SAVEBIN "build/inflate.bin" внутри +REM Паддинг inflate.bin до 16KB (чтобы cmdblocks.bin начинался на границе страницы) +powershell -NoProfile -Command "$f=[IO.File]::ReadAllBytes('build\inflate.bin'); if($f.Length -lt 16384){$pad=New-Object byte[] (16384-$f.Length); [IO.File]::WriteAllBytes('build\inflate.bin',$f+$pad); Write-Host (' inflate.bin padded: '+$f.Length+' -> 16384')}" +if errorlevel 1 ( echo FAIL pad inflate && goto :err ) + +echo [3b/9] Сборка cmdblocks.asm (sjasmplus)... +..\tools\sjasm\sjasmplus.exe asm\cmdblocks.asm >nul 2>&1 +if errorlevel 1 ( echo FAIL cmdblocks.asm && goto :err ) +REM cmdblocks.asm имеет SAVEBIN "build/cmdblocks.bin" внутри + +echo [4/9] Компиляция C файлов... +sdcc %CFLAGS% -c main.c -o build\main.rel >nul +if errorlevel 1 ( echo FAIL main.c && goto :err ) + +sdcc %CFLAGS% -c lib\vgm.c -o build\vgm.rel >nul +if errorlevel 1 ( echo FAIL vgm.c && goto :err ) + +sdcc %CFLAGS% -c lib\keys.c -o build\keys.rel >nul +if errorlevel 1 ( echo FAIL keys.c && goto :err ) + +REM txtlib is now hand-written assembly (asm\txtlib.s), assembled below + +echo [5/9] Сборка ASM файлов... +sdasz80 %ASFLAGS% build\crt0.rel asm\crt0.s >nul +if errorlevel 1 ( echo FAIL crt0.s && goto :err ) + +sdasz80 %ASFLAGS% build\isr.rel asm\isr.s >nul +if errorlevel 1 ( echo FAIL isr.s && goto :err ) + +sdasz80 %ASFLAGS% build\pos_table.rel asm\pos_table.s >nul +if errorlevel 1 ( echo FAIL pos_table.s && goto :err ) + +call lib\wc_api\build_lib.bat >nul +if errorlevel 1 ( echo FAIL wc_api.lib && goto :err ) + +sdasz80 %ASFLAGS% build\txtlib.rel asm\txtlib.s >nul +if errorlevel 1 ( echo FAIL txtlib.s && goto :err ) + +sdasz80 %ASFLAGS% build\inflate_call.rel asm\inflate_call.s >nul +if errorlevel 1 ( echo FAIL inflate_call.s && goto :err ) + +sdasz80 %ASFLAGS% build\spectrum.rel asm\spectrum.s >nul +if errorlevel 1 ( echo FAIL spectrum.s && goto :err ) + +echo [6/9] Линковка... +REM Порядок важен: crt0 первым (entry point), затем модули +REM Layout: CODE #8000–#B8CF, DATA #B8D0–#BFFF +REM Все секции (DATA + GSINIT + HOME + INITIALIZER) должны +REM уместиться до #BFFF (Win2). Если CODE вырастет → +REM понизить --data-loc или оптимизировать код. +sdcc -mz80 --no-std-crt0 --out-fmt-ihx --code-loc 0x8000 --data-loc %DATA_LOC% ^ + build\crt0.rel ^ + build\main.rel ^ + build\vgm.rel ^ + build\keys.rel ^ + build\isr.rel ^ + build\pos_table.rel ^ + build\wc_api.lib ^ + build\txtlib.rel ^ + build\inflate_call.rel ^ + build\spectrum.rel ^ + -o build\vgmplay.ihx >nul +if errorlevel 1 ( echo FAIL link && goto :err ) + +REM --- Вывод карты памяти секций из map-файла --- +powershell -NoProfile -Command ^ + "$m=Get-Content build\vgmplay.map -Raw;" ^ + "function gs($p){if($m -match '([0-9a-f]{8})\s+'+$p){return $Matches[1]}return '00000000'};" ^ + "$secs=@('CODE','DATA','GSINIT','GSFINAL','INITIALIZED','HOME','INITIALIZER');" ^ + "Write-Host '';" ^ + "Write-Host ' Section Start End Size';" ^ + "Write-Host ' -------------- -------- -------- ----------';" ^ + "$lastEnd=0;$codeEnd=0;$dataStart=0;" ^ + "foreach($s in $secs){" ^ + " $st=[Convert]::ToInt32((gs ('s__'+$s)),16);" ^ + " $ln=[Convert]::ToInt32((gs ('l__'+$s)),16);" ^ + " if($ln -eq 0){continue};" ^ + " $en=$st+$ln-1;" ^ + " if($en -gt $lastEnd){$lastEnd=$en};" ^ + " if($s -eq 'CODE'){$codeEnd=$en};" ^ + " if($s -eq 'DATA'){$dataStart=$st};" ^ + " Write-Host (' _'+$s.PadRight(14)+' 0x'+$st.ToString('X4')+' 0x'+$en.ToString('X4')+' '+$ln.ToString().PadLeft(5)+' bytes')" ^ + "};" ^ + "$free=0xBFFF-$lastEnd;" ^ + "Write-Host ' -------------- -------- -------- ----------';" ^ + "Write-Host (' Last byte: 0x'+$lastEnd.ToString('X4')+' Free before C000: '+$free+' bytes');" ^ + "if($codeEnd -ge $dataStart -and $dataStart -gt 0){Write-Host (' *** ERROR: _CODE (end 0x'+$codeEnd.ToString('X4')+') overlaps _DATA (start 0x'+$dataStart.ToString('X4')+')! ***') -ForegroundColor Red; exit 1};" ^ + "if($lastEnd -ge 0xC000){Write-Host ' *** ERROR: sections overflow past 0xBFFF! ***' -ForegroundColor Red; exit 1}" +if errorlevel 1 goto :err + +echo [7/9] Генерация WMF (multi-page)... +REM Склеить freq_tables.bin + inflate page + cmdblocks.bin +copy /b build\freq_tables.bin+build\inflate.bin+build\cmdblocks.bin build\extra_combined.bin >nul +if errorlevel 1 ( echo FAIL combine extra && goto :err ) +node scripts\ihx2wmf.js build\vgmplay.ihx ..\DiskRef\WC\VGMPLAY.WMF --extra build\extra_combined.bin +if errorlevel 1 ( echo FAIL ihx2wmf && goto :err ) +copy /Y ..\DiskRef\WC\VGMPLAY.WMF ..\VGMPLAY.WMF >nul + +echo [8/9] Проверка размеров binary... +powershell -NoProfile -Command "Get-ChildItem build\*.bin | Select-Object Name, Length | Format-Table -AutoSize" + +echo [9/9] Сборка образа диска... +if not exist "..\tools\robimg.exe" ( + echo SKIP: tools\robimg.exe не найден + goto :ok +) +del /Q ..\tsconf.img 2>nul +..\tools\robimg.exe -p="..\tsconf.img" -s=280000 -C="..\DiskRef" >nul 2>&1 +if errorlevel 1 ( echo FAIL robimg && goto :err ) +echo tsconf.img готов + +:ok +echo. +echo === BUILD OK === +goto :end + +:err +echo. +echo === BUILD FAILED === +exit /b 1 + +:end +endlocal diff --git a/src_sdcc/build_variants.ps1 b/src_sdcc/build_variants.ps1 new file mode 100644 index 0000000..a686a4a --- /dev/null +++ b/src_sdcc/build_variants.ps1 @@ -0,0 +1,236 @@ +<# +.SYNOPSIS + Build multiple ISR frequency / command budget variants for A/B testing. + Each variant produces a separate tsconf_*.img disk image. + +.DESCRIPTION + Generates inc\variant_cfg.h for each variant, runs build.bat, copies + the output image. Restores the original variant_cfg.h afterward. + + NO source files are regex-patched — only variant_cfg.h is overwritten. + +.NOTES + C compilation is SLOW (~60 sec per variant). Do NOT interrupt (Ctrl-C). +#> + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$root = Split-Path -Parent $MyInvocation.MyCommand.Path # src_sdcc +$projRoot = Split-Path -Parent $root # project root +$cfgFile = "$root\inc\variant_cfg.h" + +# ── Frequency tiers ────────────────────────────────────────────────── +# ISR_FREQ = 3 500 000 / Step; Entries = 71 680 / Step; TPF = Entries +# Shift = log2(44100 / Freq) rounded down +$freqTiers = @{ + '2734' = @{ Tag=''; Freq=2734; Step=1280; Entries=56; Shift=4; Mask='0x0F'; TPF=56; Budgets=@(8,16) } + '1367' = @{ Tag='hf'; Freq=1367; Step=2560; Entries=28; Shift=5; Mask='0x1F'; TPF=28; Budgets=@(8,16,32) } + '683' = @{ Tag='qf'; Freq=683; Step=5120; Entries=14; Shift=6; Mask='0x3F'; TPF=14; Budgets=@(16,32,64) } +} + +# ── Generate variants: 2+3+3 = 8 base × 2 NS = 16 total ───────────── +$baseVariants = @() +foreach ($ft in $freqTiers.Values | Sort-Object Freq -Descending) { + foreach ($b in $ft.Budgets) { + $suffix = if ($ft.Tag) { "_$($ft.Tag)" } else { '' } + $name = "b${b}${suffix}" + $baseVariants += @{ + Name = $name + Budget = $b + Entries = $ft.Entries + Step = $ft.Step + Freq = $ft.Freq + Shift = $ft.Shift + Mask = $ft.Mask + TPF = $ft.TPF + DataLoc = '0xB600' + NS = $false + } + } +} + +# Auto-generate _ns (no short waits) clones +$nsVariants = foreach ($bv in $baseVariants) { + $nv = $bv.Clone() + $nv.Name = $bv.Name + '_ns' + $nv.NS = $true + $nv +} +$variants = $baseVariants + $nsVariants + +# ── Save original variant_cfg.h ───────────────────────────────────── +$cfgBackup = Get-Content $cfgFile -Raw + +function Restore-Cfg { + Set-Content $script:cfgFile -Value $script:cfgBackup -NoNewline +} + +# ── Helper: generate variant_cfg.h ────────────────────────────────── +function Write-VariantCfg($v) { + $nsLine = if ($v.NS) { "#define VGM_NO_SHORT_WAITS" } else { "/* #define VGM_NO_SHORT_WAITS */" } + $content = @" +/* variant_cfg.h -- AUTO-GENERATED by build_variants.ps1 + * Variant: $($v.Name) Freq=$($v.Freq)Hz Budget=$($v.Budget) NS=$($v.NS) + */ +#ifndef VARIANT_CFG_H +#define VARIANT_CFG_H + +#define ISR_FREQ $($v.Freq) +#define ISR_TICKS_PER_FRAME $($v.TPF) +#define VGM_SAMPLE_SHIFT $($v.Shift) +#define VGM_SAMPLE_MASK $($v.Mask) +#define VGM_FILL_CMD_BUDGET $($v.Budget) + +#define VARIANT_POS_ENTRIES $($v.Entries) +#define VARIANT_POS_STEP $($v.Step) +#define VARIANT_DATA_LOC $($v.DataLoc) + +$nsLine + +#endif /* VARIANT_CFG_H */ +"@ + Set-Content $script:cfgFile -Value $content -NoNewline +} + +# ── Output directory ───────────────────────────────────────────────── +$outDir = "$projRoot\variants" +if (-not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir | Out-Null } + +# ── Build loop ────────────────────────────────────────────────────── +$total = $variants.Count +$idx = 0 +$results = @() + +foreach ($v in $variants) { + $idx++ + $name = $v.Name + Write-Host "`n========== [$idx/$total] Building $name ==========" -ForegroundColor Cyan + Write-Host " Budget=$($v.Budget) Freq=$($v.Freq)Hz Entries=$($v.Entries) Step=$($v.Step) Shift=$($v.Shift) TPF=$($v.TPF) NS=$($v.NS)" + + # --- Generate variant_cfg.h --- + Write-VariantCfg $v + + # --- Clean .rel files to force recompile --- + Remove-Item "$root\build\*.rel" -ErrorAction SilentlyContinue + + # --- Run build --- + Write-Host " Building... (this takes ~60 sec)" -ForegroundColor Yellow + $buildOut = cmd /c "cd /d $root && build.bat 2>&1" + $buildOk = $buildOut | Where-Object { $_ -match 'BUILD OK' } + $buildFail = $buildOut | Where-Object { $_ -match 'FAIL|ERROR|overflow' } + + if ($buildFail -or -not $buildOk) { + Write-Host " *** BUILD FAILED for $name ***" -ForegroundColor Red + $buildOut | Where-Object { $_ -match 'FAIL|error|warning|_CODE|_DATA|Last byte|overflow|variant_cfg' } | + ForEach-Object { Write-Host " $_" } + $results += [pscustomobject]@{ Name=$name; Status='FAIL'; Code=0; Free=0 } + continue + } + + # --- Extract sizes from output --- + $codeLine = $buildOut | Where-Object { $_ -match '_CODE' } | Select-Object -First 1 + $freeLine = $buildOut | Where-Object { $_ -match 'Free before' } | Select-Object -First 1 + $codeSize = if ($codeLine -match '(\d+)\s+bytes') { [int]$Matches[1] } else { 0 } + $freeSize = if ($freeLine -match 'Free before C000:\s*(\d+)') { [int]$Matches[1] } else { 0 } + + # --- Show map --- + $buildOut | Where-Object { $_ -match 'variant_cfg:|Section|_CODE|_DATA|_GSINIT|_HOME|_INITIAL|Last byte|Code:|Written.*WMF' } | + ForEach-Object { Write-Host " $_" } + + # --- Copy output image --- + $srcImg = "$projRoot\tsconf.img" + $dstImg = "$outDir\tsconf_$name.img" + if (Test-Path $srcImg) { + Copy-Item $srcImg $dstImg -Force + Write-Host " -> $dstImg" -ForegroundColor Green + } else { + Write-Host " WARNING: tsconf.img not found" -ForegroundColor Yellow + } + # Also copy WMF + $srcWmf = "$projRoot\DiskRef\WC\VGMPLAY.WMF" + $dstWmf = "$outDir\VGMPLAY_$name.WMF" + if (Test-Path $srcWmf) { + Copy-Item $srcWmf $dstWmf -Force + } + + $results += [pscustomobject]@{ Name=$name; Status='OK'; Code=$codeSize; Free=$freeSize } +} + +# ── Restore original variant_cfg.h ────────────────────────────────── +Write-Host "`n========== Restoring original variant_cfg.h ==========" -ForegroundColor Cyan +Restore-Cfg +# Rebuild baseline to leave workspace clean +Remove-Item "$root\build\*.rel" -ErrorAction SilentlyContinue +Write-Host " Rebuilding baseline..." -ForegroundColor Yellow +$null = cmd /c "cd /d $root && build.bat 2>&1" +Write-Host " Done." -ForegroundColor Green + +# ── Summary ────────────────────────────────────────────────────────── +Write-Host "`n========== VARIANT SUMMARY ==========" -ForegroundColor Cyan +$fmt = "{0,-18} {1,6} {2,6} {3,6} {4,3} {5,5} {6,5}" +Write-Host ($fmt -f 'Name','Budget','Freq','Code','NS','Free','St') +Write-Host ($fmt -f '----','------','----','----','--','----','--') +foreach ($r in $results) { + $v = $variants | Where-Object { $_.Name -eq $r.Name } + $nsTag = if ($v.NS) { 'yes' } else { ' - ' } + $color = if ($r.Status -eq 'OK') { 'Green' } else { 'Red' } + Write-Host ($fmt -f $r.Name, $v.Budget, $v.Freq, $r.Code, $nsTag, $r.Free, $r.Status) -ForegroundColor $color +} +Write-Host "`nImages in: $outDir" + +# ── Generate README.txt ────────────────────────────────────────────── +$readme = @" +Variants of VGM player for A/B testing +======================================= + +Parameters stored in inc\variant_cfg.h (single config file). +build_variants.ps1 regenerates variant_cfg.h for each variant. + +Frequency tiers: + qf - quarter freq (ISR 683 Hz, step 5120, 14 ticks/frame) + (none) - base freq (ISR 2734 Hz, step 1280, 56 ticks/frame) + hf - half freq (ISR 1367 Hz, step 2560, 28 ticks/frame) + +Other tags: + b - command budget (max commands between forced pauses) + ns - no short waits (0x70-0x7F pauses skipped) + +"@ + +$readme += "{0,-30} {1,6} {2,8} {3,3} {4,6} {5,5} {6}`n" -f 'Image','Budget','Freq','NS','Code','Free','Status' +$readme += "{0,-30} {1,6} {2,8} {3,3} {4,6} {5,5} {6}`n" -f '-----','------','----','--','----','----','------' +foreach ($r in $results) { + $v = $variants | Where-Object { $_.Name -eq $r.Name } + $nsTag = if ($v.NS) { 'yes' } else { ' - ' } + $freq = "$($v.Freq)Hz" + $readme += "{0,-30} {1,6} {2,8} {3,3} {4,6} {5,5} {6}`n" -f "tsconf_$($r.Name).img", $v.Budget, $freq, $nsTag, $r.Code, $r.Free, $r.Status +} + +$readme += @" + +What to test +------------ +1. Higher budget helps (b32/b48/b64 vs b8/b16) + -> Increase VGM_FILL_CMD_BUDGET. + +2. Lower freq helps (hf or qf better than base) + -> ISR overhead is the bottleneck, reduce frequency. + +3. Higher freq helps (base better than hf/qf) + -> More granular timing needed, keep higher ISR. + +4. _ns variants better -> short waits 0x70-0x7F are the problem. + +5. Nothing helps -> something else (OPL3 write delay, VGM parsing, etc.) +"@ + +Set-Content "$outDir\README.txt" -Value $readme -NoNewline -Encoding UTF8 +Write-Host "`nREADME.txt written to $outDir" -ForegroundColor Green + +Write-Host "`nLegend:" -ForegroundColor DarkGray +Write-Host " qf = quarter freq (683 Hz)" -ForegroundColor DarkGray +Write-Host " hf = half freq (1367 Hz)" -ForegroundColor DarkGray +Write-Host " (no suffix) = base freq (2734 Hz)" -ForegroundColor DarkGray +Write-Host " bN = budget N commands per ISR tick" -ForegroundColor DarkGray +Write-Host " ns = no short waits (0x70-0x7F skipped)" -ForegroundColor DarkGray diff --git a/src_sdcc/extra/inflate.bin b/src_sdcc/extra/inflate.bin new file mode 100644 index 0000000..c2f07f6 Binary files /dev/null and b/src_sdcc/extra/inflate.bin differ diff --git a/src_sdcc/inc/freq_lut_map.h b/src_sdcc/inc/freq_lut_map.h new file mode 100644 index 0000000..45ff9cb --- /dev/null +++ b/src_sdcc/inc/freq_lut_map.h @@ -0,0 +1,69 @@ +/* Auto-generated by gen_freq_tables.js — DO NOT EDIT */ +#ifndef FREQ_LUT_MAP_H +#define FREQ_LUT_MAP_H + +#include "types.h" + +/* AY/YM2149: bypass |psg_khz - 1750| <= 35 */ +#define FREQ_LUT_HW_KHZ 1750u +#define FREQ_LUT_BYPASS_TOL 35u /* 2% */ +/* YM2203: bypass |ym_khz - 3500| <= 70 */ +#define FREQ_LUT_HW_YM_KHZ 3500u +#define FREQ_LUT_BYPASS_TOL_YM 70u /* 2% */ + +#define FREQ_LUT_AY_COUNT 6u +#define FREQ_LUT_YM_COUNT 5u +#define FREQ_LUT_PSG_ENTRIES 4096u /* 12-bit PSG */ +#define FREQ_LUT_PSG_BYTES 8192u /* per PSG table */ + +typedef struct { + uint16_t clk_khz; /* canonical clock in kHz (PSG or YM master) */ + uint16_t tol_khz; /* ±tolerance in kHz (5%) */ + uint8_t page; /* plugin page (PAGE_BASE-based) */ + uint16_t offset; /* PSG table byte offset within page */ +} freq_lut_entry_t; + +/* AY/YM2149 PSG table map (tolerance = 5% of canonical PSG clock) + * Layout: 2 ratios per page, PSG_A@0x0000, PSG_B@0x2000 */ +static const freq_lut_entry_t freq_lut_map_ay[FREQ_LUT_AY_COUNT] = { + { 1790u, 90u, 1, 0x0000 }, /* NTSC_1790 */ + { 1500u, 75u, 1, 0x2000 }, /* 3MHz_1500 */ + { 2000u, 100u, 2, 0x0000 }, /* 4MHz_2000 */ + { 1000u, 50u, 2, 0x2000 }, /* CPC_1000 */ + { 1250u, 63u, 3, 0x0000 }, /* 1250 */ + { 750u, 38u, 3, 0x2000 } /* 1500_750 */ +}; + +/* YM2203 table map (full master clock, PSG table = same as AY entry) */ +static const freq_lut_entry_t freq_lut_map_ym[FREQ_LUT_YM_COUNT] = { + { 3580u, 179u, 1, 0x0000 }, /* NTSC_1790 (YM2203@3580kHz) */ + { 3000u, 150u, 1, 0x2000 }, /* 3MHz_1500 (YM2203@3000kHz) */ + { 4000u, 200u, 2, 0x0000 }, /* 4MHz_2000 (YM2203@4000kHz) */ + { 2500u, 125u, 3, 0x0000 }, /* 1250 (YM2203@2500kHz) */ + { 1500u, 75u, 3, 0x2000 } /* 1500_750 (YM2203@1500kHz) */ +}; + +/* FM F-Number множитель (YM2203): scaled = fnum × fm_mul / 7. + * 0 = bypass (масштабирование не нужно). + * ASM на Z80: умножение через shifts+adds, деление на 7 binary division */ +static const uint8_t freq_lut_fm_mul[FREQ_LUT_YM_COUNT] = { + 0, /* NTSC_1790: 1.022727 = bypass (2.3%) */ + 6, /* 3MHz_1500: 0.857143 = ×6/7 */ + 8, /* 4MHz_2000: 1.142857 = ×8/7 */ + 5, /* 1250: 0.714286 = ×5/7 */ + 3 /* 1500_750: 0.428571 = ×3/7 */ +}; + +/* + * Usage in vgm.c (parse_header): + * + * 1. Bypass check: |clock_khz - HW| <= BYPASS_TOL → native + * 2. Find matching table → set page, PSG base = offset + * 3. For YM2203: fm_mul = freq_lut_fm_mul[j] + * + * PSG: scaled_period = freq_lut_base[period_12bit] + * FM: asm_scale_fm() : fb_scaled = fnum × fm_mul / 7 + * if (fb_scaled > 2047) block++, fb_scaled >>= 1 + */ + +#endif /* FREQ_LUT_MAP_H */ diff --git a/src_sdcc/inc/isr.h b/src_sdcc/inc/isr.h new file mode 100644 index 0000000..8d3b3ed --- /dev/null +++ b/src_sdcc/inc/isr.h @@ -0,0 +1,203 @@ +/** + * @file isr.h + * @brief ISR (Interrupt Service Routine) для FRAME-прерывания TSConfig + * @version 2.0 + * + * Зависимости: types.h. + * Реализация: asm/isr.s (чистый ASM, hot-path без C). + * + * ═══════════════════════════════════════════════════════════════════ + * АРХИТЕКТУРА + * ═══════════════════════════════════════════════════════════════════ + * + * Частота ISR задаётся вариантом сборки (ISR_FREQ в variant_cfg.h). + * N_ENTRIES прерываний × STEP T-states = 71680 T = 1 TV-кадр (замыкание). + * + * Позиции INT — предрассчитанная таблица POS_TABLE (N_ENTRIES x 5 байт). + * ISR читает 3 байта через OUTI и записывает в VSINTH/VSINTL/HSINT, + * затем переходит по next_ptr (замкнутый цикл). + * + * ═══════════════════════════════════════════════════════════════════ + * ДВОЙНАЯ БУФЕРИЗАЦИЯ КОМАНД + * ═══════════════════════════════════════════════════════════════════ + * + * CMD_BUF_A, CMD_BUF_B — по 512 байт (до 128 команд). + * ISR читает из активного буфера, main loop заполняет свободный. + * Переключение буфера: CMD_END_BUF в конце каждого заполненного. + * + * ═══════════════════════════════════════════════════════════════════ + * ФОРМАТ КОМАНДЫ (4 байта, выровнено) + * ═══════════════════════════════════════════════════════════════════ + * + * Код │ Байт 1 │ Байт 2 │ Байт 3 │ Действие + * ──────────────┼────────┼────────┼────────┼───────────────────── + * CMD_WRITE_AY │ reg │ val │ 0 │ OUT AY8910 chip 1 + * CMD_INC_SEC │ 0 │ 0 │ 0 │ Инкр. isr_play_seconds + * CMD_WRITE_AY2 │ reg │ val │ 0 │ OUT AY8910 chip 2 (TS) + * CMD_CALL_WC │ 0 │ 0 │ 0 │ Вызов WC ISR handler + * CMD_WRITE_B0 │ reg │ val │ 0 │ OUT OPL3 Bank 0 + * CMD_SKIP_TICKS│ N │ smsk_lo│ smsk_hi│ Пропустить N позиций pos_table + * CMD_WRITE_SAA │ reg │ val │ 0 │ OUT SAA1099 chip 0 + * CMD_WRITE_B1 │ reg │ val │ 0 │ OUT OPL3 Bank 1 + * CMD_WRITE_SA2 │ reg │ val │ 0 │ OUT SAA1099 chip 1 + * CMD_WRITE_WAVE│ reg │ val │ 0 │ OUT OPL4 Wave-часть (#7E/#7F) + * CMD_WAIT │ lo │ hi │ 0 │ Ждать (hi<<8)|lo тиков + * CMD_END_BUF │ 0 │ 0 │ 0 │ Переключить буфер + * + * CMD_SKIP_TICKS carries 16-bit spectrum bitmask: bit N → bar N triggered + * + * ═══════════════════════════════════════════════════════════════════ + * ПРИМЕР ИСПОЛЬЗОВАНИЯ + * ═══════════════════════════════════════════════════════════════════ + * + * #include "isr.h" + * + * // 1. Заполнить оба буфера + * fill_buffer(cmd_buf_a); + * fill_buffer(cmd_buf_b); + * + * // 2. Настроить ISR + * isr_enabled = 0; // пока не запускать + * isr_active_buf = 0; // начать с буфера A + * isr_init(); // IM2 + таблица векторов + * + * // 3. Запустить + * isr_enabled = 1; + * __asm__("ei"); + * + * // 4. В main loop: + * if (isr_active_buf != last_buf) { + * fill_buffer(isr_active_buf ? cmd_buf_a : cmd_buf_b); + * last_buf = isr_active_buf; + * } + */ + +#ifndef ISR_H +#define ISR_H + +#include "types.h" + +/* ── Коды команд (8 типов, шаг 0x20 + ISR_DONE) ─────────────────── */ +#define CMD_WRITE_AY 0x00 /* AY8910 chip 1: [reg, val, 0] */ +#define CMD_INC_SEC 0x10 /* Инкрементировать счётчик секунд: [0,0,0] */ +#define CMD_WRITE_AY2 0x20 /* AY8910 chip 2 (TurboSound): [reg,val,0]*/ +#define CMD_CALL_WC 0x30 /* Вызов WC ISR handler: [0,0,0] */ +#define CMD_WRITE_B0 0x40 /* OPL3 Bank 0 write: [reg, val, 0] */ +#define CMD_SKIP_TICKS 0x50 /* Пропустить N pos_table: [N,sml,smh] */ +#define CMD_WRITE_SAA 0x60 /* SAA1099 write: [reg, val, 0] bit7=chip */ +#define CMD_WRITE_B1 0x80 /* OPL3 Bank 1 write: [reg, val, 0] */ +#define CMD_WRITE_WAVE 0x70 /* OPL4 Wave-часть write (ZXM-MoonSound, порты #7E/#7F): [reg, val, 0] */ +#define CMD_WAIT 0xC0 /* Ждать N ISR-тиков: [lo, hi, 0] */ +#define CMD_END_BUF 0xE0 /* Переключить буфер: [0, 0, 0] */ +#define CMD_ISR_DONE 0xF0 /* Заморозить ISR, выставить isr_done: [0,0,0] */ + +/* ── Размер командного буфера ────────────────────────────────────── */ +#define CMD_BUF_SIZE 512 /* байт; 128 команд по 4 байта */ + +/* ── Параметры варианта (ISR_FREQ, VGM_SAMPLE_*, budget) ───── */ +#include "variant_cfg.h" + +/* ── Переменные ISR (volatile = разделяемые между ISR и main) ────── */ + +/** Управляющий байт MultiSound (пишется в #FFFD перед каждой записью AY). + * bit0: chip select (0=chip1, 1=chip2) — ISR управляет автоматически. + * bit1: 1=normal read (всегда 1). + * bit2: 0=FM ON (high-Z), 1=FM OFF (forced low). + * bit3: 0=SAA CLK ON, 1=SAA CLK OFF. + * bit[7:4]: 0xF (маркер MultiSound ctrl). + * Idle = 0xFE (FM OFF, SAA OFF). */ +extern volatile uint8_t isr_ms_ctrl; + +/** Индекс активного буфера: 0 = A, 1 = B. + * Записывается ISR при CMD_END_BUF, читается main loop. */ +extern volatile uint8_t isr_active_buf; + +/** Флаг включения: 0 = ISR пропускает выполнение команд + * (pos_table и tick_ctr продолжают работать). + * Записывается main loop, читается ISR. + * instant_abort() использует это для заморозки без доп. флагов. */ +extern volatile uint8_t isr_enabled; + +/** Счётчик тиков ISR. Инкрементируется каждый ISR-вызов (частота = ISR_FREQ), + * оборачивается при 256 (полный оборот ≈ 256/ISR_FREQ секунд). + * main loop читает для определения прошедшего времени. */ +extern volatile uint8_t isr_tick_ctr; + +/** Цвет бордюра, восстанавливаемый ISR при выходе. + * Записывается main loop (0=black, 2=red и т.д.), читается ISR. */ +extern volatile uint8_t isr_border_color; + +/** Счётчик секунд воспроизведения. + * Инкрементируется ISR по команде CMD_INC_SEC, читается main loop. */ +extern volatile uint16_t isr_play_seconds; + +/** Флаг завершения: 1 = ISR заморожен на CMD_ISR_DONE. + * Записывается ISR, читается main loop. + * Main loop очищает при старте воспроизведения. */ +extern volatile uint8_t isr_done; + +/** Командные буферы A и B. Заполняются main loop, читаются ISR. */ +extern uint8_t cmd_buf_a[CMD_BUF_SIZE]; +extern uint8_t cmd_buf_b[CMD_BUF_SIZE]; + +/* ── Spectrum analyzer (16 bars, 0-8 levels) ─────────────────────── */ +#define SPECTRUM_BARS 16 +#define SPECTRUM_MAX_LEVEL 8 + +/** уровни спектра для каждой из 16 полос, 0..SPECTRUM_MAX_LEVEL. + * Заполняются ISR (через CMD_SKIP_TICKS), читаются main loop для рендеринга. + * Каждый бит в CMD_SKIP_TICKS соответствует одной полосе, устанавливается при активации. + * Например, если бит 0x0001 установлен, то полоса 0 (самая левая) будет на максимальном уровне. + * Если бит 0x0002 установлен, то полоса 1 будет на максимальном уровне, и так далее. + */ +extern volatile uint8_t spectrum_levels[SPECTRUM_BARS]; + +/** Контрольный счётчик текущей задержки CMD_WAIT. + * ISR декрементирует при каждом тике, переходит к следующей команде после достижения 0. + * Не используется напрямую main loop — только ISR. */ +extern volatile uint16_t isr_wait_ctr; + +/** Указатель чтения ISR внутри активного буфера. */ +extern volatile uint16_t isr_read_ptr; + +/* ── Функции ─────────────────────────────────────────────────────── */ + +/** + * Перехват вектора IM2 (#5BFF) и настройка 2734 Hz. + * + * Действия: + * 1. Сохранить вектор WC (#5BFF) → isr_saved_vec + * 2. Записать адрес isr_handler в #5BFF + * 3. Настроить INTMASK = FRAME only, позиции INT + * 4. Инициализировать pos_ptr, wait_ctr, tick_ctr, read_ptr + * 5. IM 2 (EI делается вызывающим кодом) + * + * ВАЖНО: вызывать ПОСЛЕ заполнения обоих буферов! + * ВАЖНО: вызывать при DI (прерывания выключены). + */ +void isr_init(void); + +/** + * Восстановить вектор WC и сбросить INT-позиции. + * + * Действия: + * 1. Восстановить оригинальный вектор из isr_saved_vec → #5BFF + * 2. Сбросить позицию INT на line=0 (один INT/кадр) + * 3. INTMASK = FRAME only (WC default) + * НЕ делает EI — вызывающий код решает сам. + * + * Безопасно вызывать без предшествующего isr_init (no-op). + */ +void isr_deinit(void); + +/** + * Вызов оригинального WC ISR handler из main loop. + * + * WC ожидает периодический вызов своего ISR (обновление таймера, + * клавиатуры, часов). Вызывать ~50 Гц из main loop. + * Делает DI → JP в WC handler → WC делает EI+RET. + * Безопасно вызывать без предшествующего isr_init (no-op). + */ +void call_wc_handler(void); + +#endif /* ISR_H */ diff --git a/src_sdcc/inc/keys.h b/src_sdcc/inc/keys.h new file mode 100644 index 0000000..4320045 --- /dev/null +++ b/src_sdcc/inc/keys.h @@ -0,0 +1,56 @@ +/** + * @file keys.h + * @brief PS/2 клавиатура через TSConfig CMOS FIFO (#EFF7/#DFF7/#BFF7) + * + * ps2_init() — одноразовая инициализация: открытие PS/2 портов, + * активация FIFO, старт FSM в режиме DRAIN. + * ps2_poll() — вызывать из главного цикла; rate-limit (~1 раз в TV-кадр). + * За один вызов полностью сливает FIFO (tight loop). + * Возвращает последний найденный KEY_* или KEY_NONE. + * + * Единственный бит состояния — skip flag (после F0 пропустить 1 байт). + * E0 не требует skip — сами коды не совпадают с нашими, а E0 F0 SC + * корректно обрабатывается через F0→skip. + * + * PS/2 Set 2 скан-коды: + * ESC (#76) → KEY_ESC — выход + * SPACE (#29) → KEY_NEXT — следующий трек + * P (#4D) → KEY_PREV — предыдущий трек + * 1 (#16) → KEY_SAA1 — SAA режим 1 (chip0) + * 2 (#1E) → KEY_SAA2 — SAA режим 2 (chip1) + */ + +#ifndef KEYS_H +#define KEYS_H + +#include "types.h" + +/* ── Коды клавиш (возврат ps2_poll) ──────────────────────────────────── */ +#define KEY_NONE 0 /* нет нажатия */ +#define KEY_ESC 1 /* ESC → выход */ +#define KEY_NEXT 2 /* SPACE → следующий */ +#define KEY_PREV 3 /* P → предыдущий */ +#define KEY_SAA1 4 /* 1 → SAA mode 1 */ +#define KEY_SAA2 5 /* 2 → SAA mode 2 */ +#define KEY_SAA3 6 /* 3 → SAA mode 3 */ + +/** + * Инициализация PS/2: открыть порты, активировать FIFO, слив буфера. + * Вызвать один раз при старте плагина (до первого ps2_poll). + */ +void ps2_init(void); + +/** + * Деинициализация PS/2: слив FIFO, деактивация FIFO, отключение портов. + * Вызвать при выходе из плагина (после главного цикла). + */ +void ps2_deinit(void); + +/** + * Опросить PS/2 FIFO (rate-limited, ~1 раз в кадр). + * Клавиша подтверждается только после полного цикла make + release. + * Возвращает KEY_* при подтверждении, KEY_NONE иначе. + */ +uint8_t ps2_poll(void); + +#endif /* KEYS_H */ diff --git a/src_sdcc/inc/opl3.h b/src_sdcc/inc/opl3.h new file mode 100644 index 0000000..ecdc0fa --- /dev/null +++ b/src_sdcc/inc/opl3.h @@ -0,0 +1,135 @@ +/** + * @file opl3.h + * @brief OPL3 (YMF262) — константы регистров и функции записи + * @version 2.0 + * + * Самодостаточная библиотека: зависит только от types.h. + * Реализация — asm/opl3.s (чистый inline OUT). + * + * ═══════════════════════════════════════════════════════════════════ + * ПОРТЫ TSConfig ДЛЯ OPL3 + * ═══════════════════════════════════════════════════════════════════ + * + * Порт │ Описание + * ──────┼──────────────────────────── + * #C4 │ Bank 0 Address (reg number) + * #C5 │ Bank 0 Data (reg value) + * #C6 │ Bank 1 Address + * #C7 │ Bank 1 Data + * + * Задержка между записью адреса и данных НЕ нужна при 14 МГц + * с wait-states: одна инструкция OUT (C),A занимает достаточно + * тактов чтобы YMF262 обработал адрес до прихода данных. + * + * ═══════════════════════════════════════════════════════════════════ + * КАРТА РЕГИСТРОВ OPL3 (Bank 0, то же для Bank 1) + * ═══════════════════════════════════════════════════════════════════ + * + * Рег. │ Описание + * ───────────┼──────────────────────────────────────── + * 0x01 │ Wavesel enable (бит 5 = 1) + * 0x04 │ Connection select (4-op mode, Bank 0) + * 0x05 (B1) │ OPL3 mode enable (бит 0 = 1) + * 0x08 │ Note select / CSW (только Bank 0) + * 0x20–0x35 │ Operator: AM/VIB/EG type/KSR/MULT + * 0x40–0x55 │ Operator: KSL/Total Level (громкость) + * 0x60–0x75 │ Operator: Attack Rate/Decay Rate + * 0x80–0x95 │ Operator: Sustain Level/Release Rate + * 0xA0–0xA8 │ Channel: F-Number low 8 bits + * 0xB0–0xB8 │ Channel: Key-On + Octave + F-Number hi + * 0xBD │ Percussion mode / AM depth / VIB depth + * 0xC0–0xC8 │ Channel: Feedback + Algorithm + Output + * 0xE0–0xF5 │ Operator: Waveform select + * + * ═══════════════════════════════════════════════════════════════════ + * ПРИМЕР + * ═══════════════════════════════════════════════════════════════════ + * + * #include "opl3.h" + * + * void play_note(void) { + * opl3_init(); // включить OPL3 + * opl3_write_b0(0x20, 0x01); // op1: MULT=1 + * opl3_write_b0(0x40, 0x10); // op1: TL=16 + * opl3_write_b0(0x60, 0xF0); // op1: AR=F, DR=0 + * opl3_write_b0(0x80, 0x77); // op1: SL=7, RR=7 + * opl3_write_b0(0xA0, 0x98); // F-num lo + * opl3_write_b0(0xB0, 0x31); // Key-On, Oct=4, F-hi + * } + */ + +#ifndef OPL3_H +#define OPL3_H + +#include "types.h" + +/* ── Порты OPL3 на шине TSConfig ─────────────────────────────────── */ +#define OPL3_ADDR0 0xC4 /* Bank 0 register address */ +#define OPL3_DATA0 0xC5 /* Bank 0 register data */ +#define OPL3_ADDR1 0xC6 /* Bank 1 register address */ +#define OPL3_DATA1 0xC7 /* Bank 1 register data */ + +/* ── Нумерация регистров (часто используемые) ────────────────────── */ +#define OPL3_REG_WAVESEL 0x01 /* Waveform select enable (бит 5) */ +#define OPL3_REG_CONNSEL 0x04 /* Connection select (4-op, Bank0) */ +#define OPL3_REG_OPL3EN 0x05 /* OPL3 mode reg (Bank 1 only!) */ +#define OPL3_OPL3EN_ON 0x01 /* NEW=1: OPL3 mode (L/R via C0-C8)*/ +#define OPL3_OPL3EN_OFF 0x00 /* NEW=0: OPL2 compat (L+R always) */ +#define OPL3_REG_CSW 0x08 /* CSW / Note select (Bank 0) */ +#define OPL3_REG_BD 0xBD /* Percussion / AM / VIB depth */ + +/* Key-On бит в регистрах 0xB0-0xB8 */ +#define OPL3_KEY_ON 0x20 + +/* ── Функции (реализация: asm/opl3.s) ────────────────────────────── */ + +/** Записать регистр Bank 0: addr -> #C4, data -> #C5 */ +void opl3_write_b0(uint8_t addr, uint8_t data); + +/** Записать регистр Bank 1: addr -> #C6, data -> #C7 */ +void opl3_write_b1(uint8_t addr, uint8_t data); + +/** + * Инициализация OPL3: + * 1. Bank1 reg[0x05] = 0x01 — NEW=1, OPL3 mode (регистры C0-C8 управляют L/R) + * 2. Bank0 reg[0x01] = 0x20 — Waveform Select Enable + * 3. Полный сброс всех 18 каналов: KeyOff, TL, F-Num (opl3_reset) + * + * ⚠ После `opl3_init()` чип находится в режиме OPL3 (NEW=1): + * - Регистры C0-C8 задают маршрутизацию L/R (биты 4-5) + * - OPL2/OPL1 VGM файлы НЕ устанавливают биты L/R → тишина! + * + * Для OPL1/OPL2 файлов после init вызовите: + * opl3_write_b1(OPL3_REG_OPL3EN, 0x00); // NEW=0: OPL2-compat mode + * В этом режиме L+R включены автоматически, биты C0-C8[5:4] игнорируются. + */ +void opl3_init(void); + +/** Заглушить все 18 каналов (KeyOff: B0-B8 = 0, обе банки) */ +void opl3_reset(void); + +/** AY-3-8910 / YM2149 chip 1: mixer off + volume 0 */ +void ay_silence(void); + +/** AY-3-8910 chip 2 (TurboSound): select chip2, silence, switch back */ +void ay2_silence(void); + +/** AY chip 1: write single register */ +void ay_write_reg(uint8_t reg, uint8_t val); + +/** AY chip 2 (TurboSound): write single register */ +void ay2_write_reg(uint8_t reg, uint8_t val); + +/** SAA1099 chip 1: reset + disable + zero amplitudes */ +void saa_silence(void); + +/** SAA1099 chip 2: reset + disable + zero amplitudes */ +void saa2_silence(void); + +/** SAA1099 chip 1: write single register */ +void saa_write_reg(uint8_t reg, uint8_t val); + +/** SAA1099 chip 2: write single register */ +void saa2_write_reg(uint8_t reg, uint8_t val); + +#endif /* OPL3_H */ diff --git a/src_sdcc/inc/tsconf.h b/src_sdcc/inc/tsconf.h new file mode 100644 index 0000000..2160e09 --- /dev/null +++ b/src_sdcc/inc/tsconf.h @@ -0,0 +1,531 @@ +/** + * @file tsconf.h + * @brief Полный справочник регистров TSConfig (TSL FPGA-ускоритель ZX Spectrum) + * @version 2.0 + * + * Самодостаточная библиотека: зависит только от types.h. + * Подходит для любого TSConfig-проекта на SDCC Z80. + * + * ═══════════════════════════════════════════════════════════════════ + * АРХИТЕКТУРА ПОРТОВ + * ═══════════════════════════════════════════════════════════════════ + * + * Все порты TSConfig адресуются по схеме: (REG_NUM << 8) | 0xAF + * Запись: OUT (C),A где BC = порт. + * + * На ASM: + * LD BC, TSCONF_PORT_SYSCONFIG ; BC = 0x20AF + * LD A, TSCONF_CPU_14MHZ + * OUT (C), A + * + * На C: + * tsconf_out(TSCONF_PORT_SYSCONFIG, TSCONF_CPU_14MHZ); + * + * Макросы-обёртки (конец файла) упрощают типичные операции: + * tsconf_set_cpu(TSCONF_CPU_14MHZ); + * tsconf_set_border(1); + * tsconf_set_frame_int(100, 0); + * + * ═══════════════════════════════════════════════════════════════════ + * ТАЙМИНГИ TV-РЕЖИМА (Pentagon-128) + * ═══════════════════════════════════════════════════════════════════ + * + * Строк в кадре: 320 (0–319) + * Строки 0–31: вертикальное гашение + * Строки 32–319: видимая область (бордер + пиксели) + * + * Пикселей в строке: 448 (7 МГц), = 224 T-states при 3.5 МГц + * Пиксели 0–87: горизонтальное гашение + * Пиксели 88–447: видимая область + * + * T-states в кадре: 320 × 224 = 71680 T + * + * ══════════════════════════════════════════════════════════════════════ + * ПРЕРЫВАНИЯ (IM2) + * ══════════════════════════════════════════════════════════════════════ + * + * Три источника, каждый выставляет свой вектор на шину данных: + * FRAME (#FF, приоритет 0) — совпадение растровых счётчиков + * с регистрами HSINT/VSINT + * LINE (#FD, приоритет 1) — каждая строка при HCNT = 0 + * DMA (#FB, приоритет 2) — конец DMA-транзакции + * + * HSINT = HCNT[8:1]: горизонтальная позиция (0–223). >223 = выкл. + * VSINTL/VSINTH = VCNT[8:0]: вертикальная строка (0–319). >319 = выкл. + * + * VINT_INC[3:0] в VSINTH: автоинкремент VCNT после каждого FRAME-INT + * (позволяет менять строку прерывания без записи в порты). + * + * ═══════════════════════════════════════════════════════════════════ + * ВИДЕО + * ═══════════════════════════════════════════════════════════════════ + * + * VM[1:0]: 00=ZX (256x192, атрибуты), 01=16c, 10=256c, 11=txt + * RRES[1:0]: 00=256x192, 01=320x200, 10=320x240, 11=360x288 + * + * ═══════════════════════════════════════════════════════════════════ + * DMA + * ═══════════════════════════════════════════════════════════════════ + * + * Адрес = 22-бит: [X(8)][H(6)][L(7)][0] — младший бит всегда 0. + * + * ═══════════════════════════════════════════════════════════════════ + * ПРИМЕР (минимальный) + * ═══════════════════════════════════════════════════════════════════ + * + * #include "tsconf.h" + * + * void demo(void) { + * tsconf_set_cpu(TSCONF_CPU_14MHZ); // 14 МГц + * tsconf_set_border(2); // красный бордюр + * tsconf_set_vconfig(TSCONF_VM_ZX // ZX-режим + * | TSCONF_RRES_256x192); // 256x192 + * tsconf_set_frame_int(100, 0); // INT на строке 100 + * tsconf_set_intmask(TSCONF_INT_FRAME); // только FRAME + * tsconf_set_cpu(TSCONF_CPU_3_5MHZ); // вернуть 3.5 + * } + */ + +#ifndef TSCONF_H +#define TSCONF_H + +#include "types.h" + +/* ══════════════════════════════════════════════════════════════════════ + * Макрос формирования адреса порта + * ══════════════════════════════════════════════════════════════════════ */ +#define TSCONF_PORT(reg) (((uint16_t)(reg) << 8) | 0xAF) + +/* ══════════════════════════════════════════════════════════════════════ + * ВИДЕО — VConfig (#00AF) + * W: RRES[1:0] | - | NOGFX | NOTSU | GFXOVR* | FT_EN** | VM[1:0] + * R: - | PWR_UP | - | FDRVER | VDVER[2:0] + * * только VDAC, ** только VDAC2. Сброс: 0x00 + * ══════════════════════════════════════════════════════════════════════ */ +#define TSCONF_PORT_VCONFIG TSCONF_PORT(0x00) + +/* VM[1:0] — видеорежим */ +#define TSCONF_VM_ZX 0x00 /* ZX-Spectrum, 256×192, атрибуты */ +#define TSCONF_VM_16C 0x01 /* 16 цветов, 4 бита/пиксель */ +#define TSCONF_VM_256C 0x02 /* 256 цветов, 8 бит/пиксель */ +#define TSCONF_VM_TXT 0x03 /* Текстовый режим */ +#define TSCONF_VM_MASK 0x03 + +/* FT_EN, GFXOVR, NOTSU, NOGFX */ +#define TSCONF_VC_FT_EN 0x04 /* FT812 enable (VDAC2) */ +#define TSCONF_VC_GFXOVR 0x08 /* GFX поверх TSU (VDAC) */ +#define TSCONF_VC_NOTSU 0x10 /* Отключить TSU-графику */ +#define TSCONF_VC_NOGFX 0x20 /* Отключить основную графику */ + +/* RRES[1:0] — разрешение экрана */ +#define TSCONF_RRES_256x192 (0x00 << 6) +#define TSCONF_RRES_320x200 (0x01 << 6) +#define TSCONF_RRES_320x240 (0x02 << 6) +#define TSCONF_RRES_360x288 (0x03 << 6) +#define TSCONF_RRES_MASK (0x03 << 6) + +/* R: Status (то же адрес #00AF, чтение) */ +#define TSCONF_STATUS_VDVER_MASK 0x07 +#define TSCONF_STATUS_FDRVER 0x10 +#define TSCONF_STATUS_PWR_UP 0x40 +#define TSCONF_VDVER_2BIT_PWM 0x00 +#define TSCONF_VDVER_3BIT 0x01 +#define TSCONF_VDVER_4BIT 0x02 +#define TSCONF_VDVER_5BIT 0x03 +#define TSCONF_VDVER_VDAC2 0x07 + +/* ── VPage (#01AF): адрес видеостраницы RA[21:14] ─────────────────── */ +#define TSCONF_PORT_VPAGE TSCONF_PORT(0x01) + +/* ── GXOffsL (#02AF), GXOffsH (#03AF): X-смещение графики ─────────── */ +#define TSCONF_PORT_GXOFFSL TSCONF_PORT(0x02) /* XO[7:0] */ +#define TSCONF_PORT_GXOFFSH TSCONF_PORT(0x03) /* бит 0 = XO[8] */ + +/* ── GYOffsL (#04AF), GYOffsH (#05AF): Y-смещение графики ─────────── */ +#define TSCONF_PORT_GYOFFSL TSCONF_PORT(0x04) /* YO[7:0] */ +#define TSCONF_PORT_GYOFFSH TSCONF_PORT(0x05) /* бит 0 = YO[8] */ + +/* ── TSConfig (#06AF): включение tile/sprite движка ────────────────── */ +#define TSCONF_PORT_TSCONFIG TSCONF_PORT(0x06) +#define TSCONF_TS_EXT 0x01 /* TSU 360×288 (не ZX-Evo) */ +#define TSCONF_T0Z_EN 0x04 /* Тайл [0] виден (плоск. 0) */ +#define TSCONF_T1Z_EN 0x08 /* Тайл [0] виден (плоск. 1) */ +#define TSCONF_T0_EN 0x20 /* Tile-layer 0 enable */ +#define TSCONF_T1_EN 0x40 /* Tile-layer 1 enable */ +#define TSCONF_S_EN 0x80 /* Sprite layers enable */ + +/* ── PalSel (#07AF): выбор палитр ──────────────────────────────────── */ +/* GPAL[7:4] | T0PAL[7:6] | T1PAL[7:6]. Сброс = 0x0F */ +#define TSCONF_PORT_PALSEL TSCONF_PORT(0x07) + +/* ── Border (#0FAF): цвет бордюра ──────────────────────────────────── */ +#define TSCONF_PORT_BORDER TSCONF_PORT(0x0F) + +/* ══════════════════════════════════════════════════════════════════════ + * УПРАВЛЕНИЕ ПАМЯТЬЮ + * ══════════════════════════════════════════════════════════════════════ + * RA[21:14] — номер 16KB-страницы DRAM + * Page0 = W (write-only), Page1 = W, Page2 = RW, Page3 = RW + * ══════════════════════════════════════════════════════════════════════ */ +#define TSCONF_PORT_PAGE0 TSCONF_PORT(0x10) /* окно #0000, W */ +#define TSCONF_PORT_PAGE1 TSCONF_PORT(0x11) /* окно #4000, W */ +#define TSCONF_PORT_PAGE2 TSCONF_PORT(0x12) /* окно #8000, RW */ +#define TSCONF_PORT_PAGE3 TSCONF_PORT(0x13) /* окно #C000, RW */ + +/* ── FMAddr (#14AF): адрес Font Maps ───────────────────────────────── */ +#define TSCONF_PORT_FMADDR TSCONF_PORT(0x14) +#define TSCONF_FMADDR_EN 0x10 /* бит 4 = FM_EN. Биты 3:0 = A[15:12] */ + +/* ── TMPage (#15AF): страница TileMap ──────────────────────────────── */ +#define TSCONF_PORT_TMPAGE TSCONF_PORT(0x15) /* RA[21:14] */ + +/* ── T0GPage (#16AF): страница графики тайлов плоскости 0 ──────────── */ +#define TSCONF_PORT_T0GPAGE TSCONF_PORT(0x16) /* RA[21:17] */ + +/* ── T1GPage (#17AF): страница графики тайлов плоскости 1 ──────────── */ +#define TSCONF_PORT_T1GPAGE TSCONF_PORT(0x17) /* RA[21:17] */ + +/* ── SGPage (#18AF): страница графики спрайтов ─────────────────────── */ +#define TSCONF_PORT_SGPAGE TSCONF_PORT(0x18) /* RA[21:17] */ + +/* ══════════════════════════════════════════════════════════════════════ + * DMA — адреса источника и назначения + * Адрес = 22-бит: [X(8)][H(6)][L(7)][0] + * ══════════════════════════════════════════════════════════════════════ */ +#define TSCONF_PORT_DMASADDRL TSCONF_PORT(0x19) /* Source RA[7:1] */ +#define TSCONF_PORT_DMASADDRH TSCONF_PORT(0x1A) /* Source RA[13:8] */ +#define TSCONF_PORT_DMASADDRX TSCONF_PORT(0x1B) /* Source RA[21:14] */ +#define TSCONF_PORT_DMADADDRL TSCONF_PORT(0x1C) /* Dest RA[7:1] */ +#define TSCONF_PORT_DMADADDRH TSCONF_PORT(0x1D) /* Dest RA[13:8] */ +#define TSCONF_PORT_DMADADDRX TSCONF_PORT(0x1E) /* Dest RA[21:14] */ +#define TSCONF_PORT_DMAWPDEV TSCONF_PORT(0x25) /* DMA Wait Port Dev */ +#define TSCONF_PORT_DMALEN TSCONF_PORT(0x26) /* Burst Length [7:0] */ +#define TSCONF_PORT_DMACTRL TSCONF_PORT(0x27) /* Ctrl W / Status R */ +#define TSCONF_PORT_DMANUM TSCONF_PORT(0x28) /* Bursts Num [7:0] */ +#define TSCONF_PORT_DMANUMH TSCONF_PORT(0x2C) /* Bursts Num [9:8] */ +#define TSCONF_PORT_DMAWPADDR TSCONF_PORT(0x2D) /* Wait Port Addr */ + +/* DMACtrl W — DDEV[3:0]: режим DMA */ +#define TSCONF_DMA_RAM_RAM 0x09 /* RAM→RAM copy */ +#define TSCONF_DMA_BLT1_RAM 0x89 /* RAM→RAM, пропуск нулей (OPT=1) */ +#define TSCONF_DMA_FILL 0x21 /* Fill: src → dest*N */ +#define TSCONF_DMA_RAM_CRAM 0xA1 /* RAM→CRAM (палитра) */ +#define TSCONF_DMA_SPI_RAM 0x11 /* SPI→RAM */ +#define TSCONF_DMA_RAM_SPI 0x91 /* RAM→SPI */ +#define TSCONF_DMA_IDE_RAM 0x19 /* IDE→RAM */ +#define TSCONF_DMA_RAM_IDE 0x99 /* RAM→IDE */ +#define TSCONF_DMA_FDD_RAM 0x29 /* FDD→RAM */ +#define TSCONF_DMA_RAM_WTP 0xB9 /* RAM→Wait Port */ +#define TSCONF_DMA_WTP_RAM 0x39 /* Wait Port→RAM */ +#define TSCONF_DMA_BLT2_RAM 0x31 /* Блиттер с аддером */ + +/* DMACtrl — флаги */ +#define TSCONF_DMA_ASZ_256 0x00 /* Выравнивание 256 байт */ +#define TSCONF_DMA_ASZ_512 0x04 /* Выравнивание 512 байт */ +#define TSCONF_DMA_D_ALGN 0x08 /* Выравнивание назначения */ +#define TSCONF_DMA_S_ALGN 0x10 /* Выравнивание источника */ +/* R: DMAStatus */ +#define TSCONF_DMA_ACT 0x80 /* DMA активна */ + +/* DMAWPDev */ +#define TSCONF_DMAWP_GLUCLOCK 0x00 +#define TSCONF_DMAWP_COMPORT 0x01 + +/* ══════════════════════════════════════════════════════════════════════ + * СИСТЕМНАЯ КОНФИГУРАЦИЯ + * ══════════════════════════════════════════════════════════════════════ */ + +/* ── SysConfig (#20AF): частота CPU и кэш ──────────────────────────── */ +#define TSCONF_PORT_SYSCONFIG TSCONF_PORT(0x20) +#define TSCONF_CPU_3_5MHZ 0x00 +#define TSCONF_CPU_7MHZ 0x01 +#define TSCONF_CPU_14MHZ 0x02 +#define TSCONF_CPU_MASK 0x03 +#define TSCONF_CACHE_EN 0x04 /* Включить кэш (все 4 окна) */ + +/* ── MemConfig (#21AF) ──────────────────────────────────────────────── + * ROM128: бит 0 = то же что бит 4 в #7FFD (BootROM/Basic) + * W0_WE: разрешить запись в окно #0000 + * !W0_MAP: 0=маппинг через Page0, 1=стандартная страница 0 + * W0_RAM: 0=ROM, 1=RAM в #0000 + * LCK128: режим блокировки 128K-совместимости + * ──────────────────────────────────────────────────────────────────── */ +#define TSCONF_PORT_MEMCONFIG TSCONF_PORT(0x21) +#define TSCONF_MEM_ROM128 0x01 +#define TSCONF_MEM_W0_WE 0x02 +#define TSCONF_MEM_W0_MAP_DIS 0x04 +#define TSCONF_MEM_W0_RAM 0x08 +#define TSCONF_MEM_LCK128_512K 0x00 /* 512K, биты 7:6 #7FFD=Page3[4:3] */ +#define TSCONF_MEM_LCK128_128K 0x40 /* 128K, биты 7:6 #7FFD=00 */ +#define TSCONF_MEM_LCK128_AUTO 0x80 /* Auto (!a[13]) */ +#define TSCONF_MEM_LCK128_1M 0xC0 /* 1024K */ + +/* ══════════════════════════════════════════════════════════════════════ + * ПРЕРЫВАНИЯ — позиция FRAME-INT + * ══════════════════════════════════════════════════════════════════════ */ + +/* ── HSINT (#22AF): горизонтальная позиция ─────────────────────────── */ +/* HCNT[8:1] в диапазоне 0–223; >223 = выключить INT в этой строке */ +#define TSCONF_PORT_HSINT TSCONF_PORT(0x22) +#define TSCONF_HSINT_OFF 0xFF + +/* ── VSINTL (#23AF): вертикальная позиция [7:0] ────────────────────── */ +/* VCNT[7:0]; строки 0–319; >319 = FRAME-INT выключен */ +#define TSCONF_PORT_VSINTL TSCONF_PORT(0x23) + +/* ── VSINTH (#24AF): вертикальная позиция [8] + автоинкремент ──────── */ +/* Бит 0 = VCNT[8]; биты [7:4] = VINT_INC[3:0] */ +/* VINT_INC: после каждого FRAME-INT → VCNT += VINT_INC (0 = нет) */ +#define TSCONF_PORT_VSINTH TSCONF_PORT(0x24) +#define TSCONF_VSINTH_VCNT8 0x01 +#define TSCONF_VSINTH_INC_MASK 0xF0 +#define TSCONF_VSINTH_INC(n) (((n) & 0x0F) << 4) + +/* ── INTMask (#2AAF): маска прерываний ─────────────────────────────── */ +/* Сброс: 0x01 (только FRAME включён) */ +#define TSCONF_PORT_INTMASK TSCONF_PORT(0x2A) +#define TSCONF_INT_FRAME 0x01 +#define TSCONF_INT_LINE 0x02 +#define TSCONF_INT_DMA 0x04 +#define TSCONF_INT_WTP 0x08 + +/* Векторы IM2 (значение на шине данных при прерывании) */ +#define TSCONF_IVEC_FRAME 0xFF +#define TSCONF_IVEC_LINE 0xFD +#define TSCONF_IVEC_DMA 0xFB + +/* ══════════════════════════════════════════════════════════════════════ + * КЭШ — CacheConfig (#2BAF) + * ══════════════════════════════════════════════════════════════════════ */ +#define TSCONF_PORT_CACHECONFIG TSCONF_PORT(0x2B) +#define TSCONF_CACHE_EN_0000 0x01 +#define TSCONF_CACHE_EN_4000 0x02 +#define TSCONF_CACHE_EN_8000 0x04 +#define TSCONF_CACHE_EN_C000 0x08 +#define TSCONF_CACHE_ALL 0x0F +#define TSCONF_CACHE_NONE 0x00 + +/* ══════════════════════════════════════════════════════════════════════ + * FDD VIRTUAL DRIVE — FDDVirt (#29AF) + * ══════════════════════════════════════════════════════════════════════ */ +#define TSCONF_PORT_FDDVIRT TSCONF_PORT(0x29) +#define TSCONF_FDD_A 0x01 +#define TSCONF_FDD_B 0x02 +#define TSCONF_FDD_C 0x04 +#define TSCONF_FDD_D 0x08 +#define TSCONF_FDD_VG_OPEN 0x80 + +/* ══════════════════════════════════════════════════════════════════════ + * ТАЙЛОВЫЕ ПЛОСКОСТИ — смещения + * ══════════════════════════════════════════════════════════════════════ */ +#define TSCONF_PORT_T0XOFFSL TSCONF_PORT(0x40) +#define TSCONF_PORT_T0XOFFSH TSCONF_PORT(0x41) +#define TSCONF_PORT_T0YOFFSL TSCONF_PORT(0x42) +#define TSCONF_PORT_T0YOFFSH TSCONF_PORT(0x43) +#define TSCONF_PORT_T1XOFFSL TSCONF_PORT(0x44) +#define TSCONF_PORT_T1XOFFSH TSCONF_PORT(0x45) +#define TSCONF_PORT_T1YOFFSL TSCONF_PORT(0x46) +#define TSCONF_PORT_T1YOFFSH TSCONF_PORT(0x47) + +/* ══════════════════════════════════════════════════════════════════════ + * HUS — Hurricane Sound Engine (#80AF–#87AF) + * ══════════════════════════════════════════════════════════════════════ */ +#define TSCONF_PORT_HUSCTRL TSCONF_PORT(0x80) /* W: управление */ +#define TSCONF_PORT_HUSSAMPRATE TSCONF_PORT(0x81) /* W: SR[7:0] (1750k/(SR+1)) */ +#define TSCONF_PORT_HUSTICKRATEL TSCONF_PORT(0x82) /* W: TR[7:0] */ +#define TSCONF_PORT_HUSTICKRATEH TSCONF_PORT(0x83) /* W: TR[9:8] */ +#define TSCONF_PORT_HUSRLD0 TSCONF_PORT(0x84) /* Reload ch 0–7 */ +#define TSCONF_PORT_HUSRLD1 TSCONF_PORT(0x85) /* Reload ch 8–15 */ +#define TSCONF_PORT_HUSRLD2 TSCONF_PORT(0x86) +#define TSCONF_PORT_HUSRLD3 TSCONF_PORT(0x87) +/* R: HUSStatus (#80AF) */ +#define TSCONF_HUS_ACT 0x01 +#define TSCONF_HUS_TICK 0x02 /* сбрасывается при чтении */ +#define TSCONF_HUS_HALF 0x40 /* буфер наполовину пуст */ +#define TSCONF_HUS_EMPTY 0x80 +/* W: HUSControl */ +#define TSCONF_HUS_PAUSE 0x02 +#define TSCONF_HUS_RESET 0x80 + +/* ══════════════════════════════════════════════════════════════════════ + * OPL3 (YMF262) — порты на шине TSConfig + * ══════════════════════════════════════════════════════════════════════ */ +#define TSCONF_PORT_OPL3_ADDR0 0x00C4 +#define TSCONF_PORT_OPL3_DATA0 0x00C5 +#define TSCONF_PORT_OPL3_ADDR1 0x00C6 +#define TSCONF_PORT_OPL3_DATA1 0x00C7 + +/* ══════════════════════════════════════════════════════════════════════ + * AY-3-8910 / YM2149 — стандартные порты ZX Spectrum 128 + * ══════════════════════════════════════════════════════════════════════ + * #FFFD — выбор регистра (W) / чтение данных (R) + * #BFFD — запись данных (W) + * + * TurboSound (два AY): запись в #FFFD значения #FF/#FE выбирает чип: + * #FF → чип 0 (первый AY); #FE → чип 1 (второй AY). + * ══════════════════════════════════════════════════════════════════════ */ +#define TSCONF_PORT_AY_ADDR 0xFFFD /* register select / read */ +#define TSCONF_PORT_AY_DATA 0xBFFD /* data write */ +#define TSCONF_AY_TS_CHIP0 0xFF /* TurboSound: select chip 0 */ +#define TSCONF_AY_TS_CHIP1 0xFE /* TurboSound: select chip 1 */ + +/* ══════════════════════════════════════════════════════════════════════ + * SAA1099 — порты SAM Coupé / ZX-совместимые + * ══════════════════════════════════════════════════════════════════════ + * #01FF — выбор регистра (address write) + * #00FF — запись данных (data write) + * Второй SAA1099 (если есть): + * #01FE — выбор регистра + * #00FE — запись данных + * ══════════════════════════════════════════════════════════════════════ */ +#define TSCONF_PORT_SAA_ADDR 0x01FF /* SAA1099 #1 address */ +#define TSCONF_PORT_SAA_DATA 0x00FF /* SAA1099 #1 data */ +#define TSCONF_PORT_SAA2_ADDR 0x01FE /* SAA1099 #2 address */ +#define TSCONF_PORT_SAA2_DATA 0x00FE /* SAA1099 #2 data */ + +/* ══════════════════════════════════════════════════════════════════════ + * WC СИСТЕМНЫЕ ПЕРЕМЕННЫЕ — адреса в ROM-области ядра WC + * ══════════════════════════════════════════════════════════════════════ */ +#define WC_ADDR_PAGES 0x6000 /* [3 байта] страницы #0000,#4000,#8000 */ +#define WC_ADDR_ABT 0x6004 /* флаг ESC */ +#define WC_ADDR_ENT 0x6005 /* флаг ENTER */ +#define WC_ADDR_TMN 0x6009 /* таймер (инкр. по INT), uint16 */ +#define WC_ADDR_CNFV 0x600D /* версия конфигурации */ +#define WC_ADDR_HEI 0x600E /* высота экрана (25/30/36 строк) */ + +#define WC_CNFV_PWM 0x00 +#define WC_CNFV_3BIT 0x01 +#define WC_CNFV_4BIT 0x02 +#define WC_CNFV_5BIT 0x03 +#define WC_CNFV_VDAC2 0x07 + +/* ══════════════════════════════════════════════════════════════════════ + * SFR — прямой доступ к портам из SDCC-C + * ══════════════════════════════════════════════════════════════════════ + * + * __sfr __at 0xNN — 8-bit port: out (N), a / in a, (N) + * __sfr __banked __at 0xNNNN — 16-bit port: ld bc,#NNNN; out (c),a + * + * Не создают переменных/символов — чисто compile-time mapping. + * Присваивание sfr_xxx = val; генерирует inline OUT. + * Чтение val = sfr_xxx; генерирует inline IN. + * ══════════════════════════════════════════════════════════════════════ */ + +/* --- Стандартный ZX Spectrum: 8-bit порт ----------------------------- */ +__sfr __at 0xFE sfr_zx_fe; /* border / keyboard */ + +/* --- TSConfig регистры (addr = reg<<8 | 0xAF) ----------------------- */ +__sfr __banked __at 0x00AF sfr_vconfig; /* VConfig */ +__sfr __banked __at 0x01AF sfr_vpage; /* VPage */ +__sfr __banked __at 0x02AF sfr_gxoffsl; /* GXOffsL */ +__sfr __banked __at 0x03AF sfr_gxoffsh; /* GXOffsH */ +__sfr __banked __at 0x04AF sfr_gyoffsl; /* GYOffsL */ +__sfr __banked __at 0x05AF sfr_gyoffsh; /* GYOffsH */ +__sfr __banked __at 0x06AF sfr_tsconfig; /* TSConfig */ +__sfr __banked __at 0x07AF sfr_palsel; /* PalSel */ +__sfr __banked __at 0x0FAF sfr_border; /* Border Color */ +__sfr __banked __at 0x10AF sfr_page0; /* Page0 (#0000) */ +__sfr __banked __at 0x11AF sfr_page1; /* Page1 (#4000) */ +__sfr __banked __at 0x12AF sfr_page2; /* Page2 (#8000) */ +__sfr __banked __at 0x13AF sfr_page3; /* Page3 (#C000) */ +__sfr __banked __at 0x14AF sfr_fmaddr; /* FMAddr */ +__sfr __banked __at 0x15AF sfr_tmpage; /* TMPage */ +__sfr __banked __at 0x16AF sfr_t0gpage; /* T0GPage */ +__sfr __banked __at 0x17AF sfr_t1gpage; /* T1GPage */ +__sfr __banked __at 0x18AF sfr_sgpage; /* SGPage */ +__sfr __banked __at 0x20AF sfr_sysconfig; /* SysConfig (CPU) */ +__sfr __banked __at 0x21AF sfr_memconfig; /* MemConfig */ +__sfr __banked __at 0x22AF sfr_hsint; /* HSINT */ +__sfr __banked __at 0x23AF sfr_vsintl; /* VSINTL */ +__sfr __banked __at 0x24AF sfr_vsinth; /* VSINTH */ +__sfr __banked __at 0x2AAF sfr_intmask; /* INTMask */ +__sfr __banked __at 0x2BAF sfr_cacheconfig; /* CacheConfig */ +__sfr __banked __at 0x29AF sfr_fddvirt; /* FDDVirt */ + +/* --- DMA --- */ +__sfr __banked __at 0x1AAF sfr_dmasaddrh; /* DMA Src Addr H */ +__sfr __banked __at 0x1BAF sfr_dmasaddrx; /* DMA Src Addr X */ +__sfr __banked __at 0x19AF sfr_dmasaddrl; /* DMA Src Addr L */ +__sfr __banked __at 0x1CAF sfr_dmadaddrl; /* DMA Dst Addr L */ +__sfr __banked __at 0x1DAF sfr_dmadaddrh; /* DMA Dst Addr H */ +__sfr __banked __at 0x1EAF sfr_dmadaddrx; /* DMA Dst Addr X */ +__sfr __banked __at 0x26AF sfr_dmalen; /* DMA Burst Length */ +__sfr __banked __at 0x27AF sfr_dmactrl; /* DMA Ctrl / Status */ +__sfr __banked __at 0x28AF sfr_dmanum; /* DMA Bursts Num L */ +__sfr __banked __at 0x2CAF sfr_dmanumh; /* DMA Bursts Num H */ + +/* --- OPL3 (YMF262) --- */ +__sfr __banked __at 0x00C4 sfr_opl3_addr0; /* Bank0 address */ +__sfr __banked __at 0x00C5 sfr_opl3_data0; /* Bank0 data */ +__sfr __banked __at 0x00C6 sfr_opl3_addr1; /* Bank1 address */ +__sfr __banked __at 0x00C7 sfr_opl3_data1; /* Bank1 data */ + +/* --- AY-3-8910 / YM2149 --- */ +__sfr __banked __at 0xFFFD sfr_ay_addr; /* reg select / read */ +__sfr __banked __at 0xBFFD sfr_ay_data; /* data write */ + +/* --- SAA1099 --- */ +__sfr __banked __at 0x01FF sfr_saa_addr; /* SAA1099 #1 address */ +__sfr __banked __at 0x00FF sfr_saa_data; /* SAA1099 #1 data */ +__sfr __banked __at 0x01FE sfr_saa2_addr; /* SAA1099 #2 address */ +__sfr __banked __at 0x00FE sfr_saa2_data; /* SAA1099 #2 data */ + +/* ══════════════════════════════════════════════════════════════════════ + * ФУНКЦИИ — обёртки + * ══════════════════════════════════════════════════════════════════════ */ + +/** Запись значения в произвольный TSConfig-порт (asm, для динамических адресов) + * @param port TSCONF_PORT(reg) + * @param val значение */ +extern void tsconf_out(uint16_t port, uint8_t val); + +/* ── Удобные макросы (через __sfr — inline, без call) ────────────────── */ + +/** Установить скорость CPU (TSCONF_CPU_3_5MHZ / 7MHZ / 14MHZ) */ +#define tsconf_set_cpu(speed) \ + (sfr_sysconfig = (uint8_t)((speed) & TSCONF_CPU_MASK)) + +/** Установить скорость CPU с управлением кэшем */ +#define tsconf_set_cpu_cache(speed, cache_en) \ + (sfr_sysconfig = (uint8_t)(((speed) & TSCONF_CPU_MASK) | ((cache_en) ? TSCONF_CACHE_EN : 0))) + +/** Установить маску прерываний (OR нужных TSCONF_INT_*) */ +#define tsconf_set_intmask(mask) \ + (sfr_intmask = (uint8_t)(mask)) + +/** Настроить позицию FRAME-INT: line=0–319, hpos=0–223 */ +#define tsconf_set_frame_int(line, hpos) do { \ + sfr_vsinth = (uint8_t)(((line) >> 8) & 0x01); \ + sfr_vsintl = (uint8_t)((line) & 0xFF); \ + sfr_hsint = (uint8_t)(hpos); \ +} while(0) + +/** Выключить FRAME-INT полностью */ +#define tsconf_disable_frame_int() \ + (sfr_hsint = TSCONF_HSINT_OFF) + +/** Настроить кэш по окнам: маска из TSCONF_CACHE_EN_* */ +#define tsconf_set_cache(mask) \ + (sfr_cacheconfig = (uint8_t)(mask)) + +/** Установить цвет бордюра (TSConfig-регистр) */ +#define tsconf_set_border(color) \ + (sfr_border = (uint8_t)(color)) + +/** Подключить видеостраницу (номер 0–63) на адрес #C000 */ +#define tsconf_set_vpage(page) \ + (sfr_vpage = (uint8_t)(page)) + +/** Установить видеорежим (OR из TSCONF_VM_*, TSCONF_RRES_*, TSCONF_VC_*) */ +#define tsconf_set_vconfig(cfg) \ + (sfr_vconfig = (uint8_t)(cfg)) + +/** Подключить RAM-страницу page на окно #C000 (Page3) */ +#define tsconf_set_page3(page) \ + (sfr_page3 = (uint8_t)(page)) + +/** Подключить RAM-страницу page на окно #8000 (Page2) */ +#define tsconf_set_page2(page) \ + (sfr_page2 = (uint8_t)(page)) + +#endif /* TSCONF_H */ diff --git a/src_sdcc/inc/txtlib.h b/src_sdcc/inc/txtlib.h new file mode 100644 index 0000000..18f5491 --- /dev/null +++ b/src_sdcc/inc/txtlib.h @@ -0,0 +1,153 @@ +/** + * @file txtlib.h + * @brief 䇃䇐䈜䈛 для вывода текста в окна WC — динамические буферы и печать строк + * @version 1.0 + * + * Зависимости: wc_api.h (wc_prsrw, wc_window_t). + * Реализация: lib/txtlib.c. + * + * Формат буфера: + * buf[0] = capacity — максимальная емкость в символах (задаётся ОДИН РАЗ при buf_init) + * buf[1] = len — текущая длина (количество символов) + * buf[2..] = символы — не завершается \0! + * + * Объявление буфера: + * char work_buf[66]; // capacity = 64 символа + * + * Типичное использование: + * buf_init(work_buf, 64); + * buf_clear(work_buf); + * buf_append_str(work_buf, "Time: "); + * buf_append_u16_dec(work_buf, seconds); + * print_line(&win, 5, work_buf, WC_COLOR(WC_BLACK, WC_BRIGHT_WHITE)); + */ + +#ifndef TXTLIB_H +#define TXTLIB_H + +#include "wc_api.h" + +#define FS_SCREEN_COLS 96U /* макс. ширина экрана (90x36, запас) */ + +/** + * Вернуть (ширина окна - 4) = полезная ширина содержимого. + * Если win->width == 0, используется FS_SCREEN_COLS. + */ +static inline uint8_t get_content_width(const wc_window_t *win) { + uint8_t w = win->width; + return (w ? w : FS_SCREEN_COLS) - 4U; +} + +/* ================================================================== */ +/* ИНИЦИАЛИЗАЦИЯ И БАЗОВЫЕ ОПЕРАЦИИ */ +/* ================================================================== */ + +/** Инициализировать буфер: записать capacity, len=0. Вызывать ОДИН РАЗ! */ +static inline void buf_init(char *buf, uint8_t capacity) { + buf[0] = capacity; + buf[1] = 0; +} + +/** Очистить буфер: len=0 (емкость не меняется). */ +static inline void buf_clear(char *buf) { + buf[1] = 0; +} + +/** Вернуть текущую длину буфера (количество символов). */ +static inline uint8_t buf_len(const char *buf) { + return buf[1]; +} + +/* ================================================================== */ +/* APPEND */ +/* ================================================================== */ + +/** + * Добавить строку (\0-terminated) в конец буфера. + * Тихо усекается при переполнении (len не превышает capacity). + * @return len после добавления + */ +uint8_t buf_append_str(char *buf, const char *src); + +/** + * Добавить один символ c в буфер. + * @return len после добавления + */ +uint8_t buf_append_char(char *buf, char c); + +/** + * Добавить двухсимвольное hex-представление байта ("00".."FF"). + * @param val значение 0..255 + * @return len после добавления (+2) + */ +uint8_t buf_append_u8_hex(char *buf, uint8_t val); + +/** + * Добавить uint16 в десятичном формате (без ведущих нулей). + * @return len после добавления + */ +uint8_t buf_append_u16_dec(char *buf, uint16_t val); + +/** + * Добавить uint32 в десятичном формате. + * @return len после добавления + */ +uint8_t buf_append_u32_dec(char *buf, uint32_t val); + +/** + * Добавить "MM:SS" (5 символов) в буфер. Без деления — вычитание. + * @param sec секунды (0..5999) + */ +void buf_append_mmss(char *buf, uint16_t sec); + + +/* ================================================================== */ +/* ВЫВОД */ +/* ================================================================== */ + +/** + * Вывести одну строку из буфера в окно win заполняя до ширины окна. + *  Передаётся атрибут цвета attr (EGA: bg[7:4] | fg[3:0]). + * + * @param win окно WC + * @param y строка внутри окна (1-базированное) + * @param buf буфер с длиной в buf[1] (формат buf[]) + * @param attr EGA-атрибут: WC_COLOR(bg, fg) + */ +void print_line(wc_window_t *win, uint8_t y, const char *buf, uint8_t attr); + +/* ================================================================== */ +/* ИНИЦИАЛИЗАЦИЯ И ПРИМЕР ИСПОЛЬЗОВАНИЯ */ +/* ================================================================== */ + +/* + // В начале плагина (например в init) + char meta_buf[122]; + char work_buf[66]; // 64 символа + + buf_init(meta_buf, 120); + buf_init(work_buf, 64); // или больше, если используешь для marquee + + // Заполнение метаданных + buf_clear(meta_buf); + buf_append_str(meta_buf, "Artist - Super Long Track Name 2025 (Remix)"); + + // В цикле отрисовки: + uint8_t cw = get_content_width(my_win); + + // Бегущая строка (используем work_buf как временный) + build_marquee(work_buf, meta_buf + 2, meta_buf[1], scroll_pos, cw); + print_marquee(my_win, 3, work_buf, 0x0F); // белый + + scroll_pos++; + + // Обычная строка (время) + buf_clear(work_buf); + buf_append_str(work_buf, "Time: "); + buf_append_u16_dec(work_buf, minutes); + buf_append_char(work_buf, ':'); + buf_append_u16_dec(work_buf, seconds); + print_line(my_win, 5, work_buf, 0x0A); +*/ + +#endif // TXTLIB_H diff --git a/src_sdcc/inc/types.h b/src_sdcc/inc/types.h new file mode 100644 index 0000000..4da4091 --- /dev/null +++ b/src_sdcc/inc/types.h @@ -0,0 +1,75 @@ +/** + * @file types.h + * @brief Базовые типы данных для Z80/SDCC (sdcccall(1)) + * @version 2.0 + * + * Самодостаточный заголовок: не зависит от других файлов проекта. + * Подходит для любого Z80-проекта на SDCC. + * + * ═══════════════════════════════════════════════════════════════════ + * SDCC sdcccall(1) CALLING CONVENTION (Z80) + * ═══════════════════════════════════════════════════════════════════ + * + * Аргумент | Тип | Регистр + * ------------|-------------------|---------- + * 1-й | uint8_t / int8_t | A + * 1-й | uint16_t / ptr | DE + * 2-й и далее | любой | стек (правый первым) + * Возврат | uint8_t | A (расширяется нулём в L) + * Возврат | uint16_t / ptr | HL + * + * ═══════════════════════════════════════════════════════════════════ + * РЕКОМЕНДАЦИИ ДЛЯ Z80 + * ═══════════════════════════════════════════════════════════════════ + * + * - uint32_t в hot-path -> десятки байт кода; избегать. + * - volatile — только для переменных, разделяемых с ISR. + * - __sfr / __xdata — НЕ использовать (это 8051, не Z80). + * - Маленькие inline-функции быстрее, чем call + стек. + * - Массивы до 256 элементов — индексировать uint8_t. + * + * ═══════════════════════════════════════════════════════════════════ + * ПРИМЕР + * ═══════════════════════════════════════════════════════════════════ + * + * #include "types.h" + * + * static volatile uint8_t flag; // ISR -> main + * static uint16_t counter; + * + * void timer_tick(void) { + * if (++counter >= 2734) { counter = 0; flag = 1; } + * } + */ + +#ifndef TYPES_H +#define TYPES_H + +/* ── Целочисленные типы фиксированной ширины ─────────────────────── */ +typedef unsigned char uint8_t; +typedef signed char int8_t; +typedef unsigned int uint16_t; /* SDCC Z80: int = 16 бит */ +typedef signed int int16_t; +typedef unsigned long uint32_t; /* SDCC Z80: long = 32 бит */ +typedef signed long int32_t; + +/* ── Булев тип ───────────────────────────────────────────────────── */ +/* uint8_t = один байт; сравнения JR Z / JR NZ без масок. */ +typedef uint8_t bool_t; +#define TRUE ((bool_t)1) +#define FALSE ((bool_t)0) + +/* ── NULL ────────────────────────────────────────────────────────── */ +#ifndef NULL +#define NULL ((void *)0) +#endif + +/* ── Короткие псевдонимы ─────────────────────────────────────────── */ +typedef uint8_t u8; +typedef uint16_t u16; +typedef int8_t i8; +typedef int16_t i16; +typedef uint32_t u32; +typedef int32_t i32; + +#endif /* TYPES_H */ diff --git a/src_sdcc/inc/variant_cfg.h b/src_sdcc/inc/variant_cfg.h new file mode 100644 index 0000000..5d47420 --- /dev/null +++ b/src_sdcc/inc/variant_cfg.h @@ -0,0 +1,51 @@ +/** + * @file variant_cfg.h + * @brief Единственный файл конфигурации варианта сборки. + * + * build_variants.ps1 перегенерирует этот файл целиком для каждого + * варианта. Никакие другие исходники НЕ патчатся. + * + * build.bat читает VARIANT_POS_ENTRIES / VARIANT_POS_STEP / + * VARIANT_DATA_LOC из этого файла (парсинг через for /f). + */ +#ifndef VARIANT_CFG_H +#define VARIANT_CFG_H + +/* ── Частота ISR (тиков/сек) ─────────────────────────────────────── */ +/* ISR_FREQ = 3 500 000 / VARIANT_POS_STEP */ +#define ISR_FREQ 683 + +/* Тиков ISR на TV-кадр (= 71680 / STEP) */ +/* 14@683, 28@1367, 56@2734 */ +#define ISR_TICKS_PER_FRAME 14 + +/* ── Маппинг VGM 44100→ISR тиков ─────────────────────────────────── */ +/* SHIFT = log2(44100/ISR_FREQ): 6@683, 5@1367, 4@2734 */ +#define VGM_SAMPLE_SHIFT 6 +#define VGM_SAMPLE_MASK 0x3F + +/* ── Лимит команд между принудительными yield ──────────────────────── */ +#define VGM_FILL_CMD_BUDGET 16 + +/* ── Параметры pos_table (для build.bat → gen_pos_table.js) ──────── */ +#define VARIANT_POS_ENTRIES 14 +#define VARIANT_POS_STEP 5120 + +/* ── Адрес начала сегмента данных (для линковщика) ────────────────── */ +/* Плотная раскладка: полная версия spectrum.s + загрузчик OPL4. + * Проверяется build.bat: _CODE должен закончиться раньше _DATA, + * а последняя секция — раньше 0xC000. */ +#define VARIANT_DATA_LOC 0xB8D0 + +/* ── Спектроанализатор: скорости затухания (в TV-кадрах) ────────── */ +/* Меньше = быстрее затухание. 1 = каждый кадр (~20 мс). */ +/* Синхронизировать с EQU в spectrum.s! */ +#define DECAY_FRAMES_FAST 1 /* уровни >= 6 */ +#define DECAY_FRAMES_MED 2 /* уровни 3-5 */ +#define DECAY_FRAMES_SLOW 4 /* уровни 1-2 */ + +/* ── Отключение обработки коротких пауз 0x70-0x7F ────────────────── */ +/* Раскомментировать для «no short waits» варианта: */ +/* #define VGM_NO_SHORT_WAITS */ + +#endif /* VARIANT_CFG_H */ diff --git a/src_sdcc/inc/version.h b/src_sdcc/inc/version.h new file mode 100644 index 0000000..99828c2 --- /dev/null +++ b/src_sdcc/inc/version.h @@ -0,0 +1,15 @@ +/** + * @file version.h + * @brief Версия приложения — редактируйте только этот файл + * + * Примеры: + * #define APP_VERSION "v0.5" + * #define APP_VERSION "v0.5-beta" + * #define APP_VERSION "v1.0-rc1" + */ +#ifndef VERSION_H +#define VERSION_H + +#define APP_VERSION "v0.9.03-beta" + +#endif /* VERSION_H */ diff --git a/src_sdcc/inc/vgm.h b/src_sdcc/inc/vgm.h new file mode 100644 index 0000000..c40cfc7 --- /dev/null +++ b/src_sdcc/inc/vgm.h @@ -0,0 +1,335 @@ +/** + * @file vgm.h + * @brief VGM-парсер для OPL1/OPL2/OPL3 — заголовок + потоковое чтение + * @version 3.0 + * + * Зависимости: types.h, isr.h (cmd_buf_a/b), wc_api.h (wc_mngcvpl). + * Реализация: lib/vgm.c. + * + * ═══════════════════════════════════════════════════════════════════ + * ОПИСАНИЕ + * ═══════════════════════════════════════════════════════════════════ + * + * Преобразует VGM-поток в ISR-команды (CMD_WRITE_B0/B1, CMD_WAIT, + * CMD_END_BUF). VGM данные хранятся в VPL-страницах WC, доступных + * через окно #C000. + * + * Поддерживаемые VGM-команды (OPL1 / OPL2 / OPL3 / OPL4 / AY / SAA): + * 0x5A rr dd — YM3812 (OPL2) write → CMD_WRITE_B0 + * 0x5B rr dd — YM3526 (OPL1) write → CMD_WRITE_B0 + * 0x5C rr dd — Y8950 (OPL) write → CMD_WRITE_B0 + * 0x5E rr dd — YMF262 (OPL3) Bank 0 write → CMD_WRITE_B0 + * 0x5F rr dd — YMF262 (OPL3) Bank 1 write → CMD_WRITE_B1 + * 0xD0 pp aa dd — YMF278B (OPL4) write, port pp (бит7 = chip select, игн.): + * pp&0x7F==0 → Bank 0 (как 0x5E) → CMD_WRITE_B0 + * pp&0x7F==1 → Bank 1 (как 0x5F) → CMD_WRITE_B1 + * pp&0x7F==2 → Wave-часть (#7E/#7F) → CMD_WRITE_WAVE + * 0x55 rr dd — YM2203 PSG-часть (AY), reg 0-15 → CMD_WRITE_AY + * 0xA0 rr dd — AY8910 (чип 1/2 по [7]) → CMD_WRITE_AY / CMD_WRITE_AY2 + * 0xBD rr dd — SAA1099 (чип 1/2 по [7]) → CMD_WRITE_SAA (bit7 = chip select) + * 0x61 nn nn — Wait N samples (16-bit LE) + * 0x62 — Wait 735 samples (1/60 сек) + * 0x63 — Wait 882 samples (1/50 сек) + * 0x66 — End of data + * 0x67 ... — Data block (пропускается) + * 0x70–0x7F — Wait 1–16 samples + * Прочие — пропускаются по таблице VGM spec (1-байт / 2-байт / 3-байт аргументы) + * + * Масштабирование задержек: + * VGM sample rate = 44100 Hz, ISR freq = 2734 Hz -> делим на 16. + * Задержки < 16 сэмплов игнорируются (округление вниз). + * Неизвестные команды пропускаются по таблице размеров VGM spec. + * + * ═══════════════════════════════════════════════════════════════════ + * VGM ЗАГОЛОВОК — ключевые смещения (little-endian) + * ═══════════════════════════════════════════════════════════════════ + * + * Offset Size Описание Мин.версия + * ------ ---- -------------------------------- ---------- + * 0x00 4 Сигнатура "Vgm " (0x206D6756) 1.00 + * 0x08 4 Версия (BCD): 0x0151 = v1.51 1.00 + * 0x14 4 GD3 offset (отн. 0x14) 1.00 + * 0x18 4 Total samples 1.00 + * 0x1C 4 Loop offset (отн. 0x1C) 1.00 + * 0x20 4 Loop samples 1.00 + * 0x34 4 Data offset (отн. 0x34) 1.50 + * 0x50 4 YM3812 (OPL2) clock 1.51 + * 0x54 4 YM3526 (OPL1) clock 1.51 + * 0x58 4 Y8950 (OPL) clock 1.51 + * 0x5C 4 YMF262 (OPL3) clock 1.51 + * + * Для версий < 1.50: data offset фиксирован = 0x40. + * Для версий < 1.51: поля чипов за пределами заголовка. + * + * ~93% OPL VGM файлов — YM3812 (OPL2), команда 0x5A. + * ~5% — YMF262 (OPL3), команды 0x5E/0x5F. + * + * ═══════════════════════════════════════════════════════════════════ + * ПРИМЕР ИСПОЛЬЗОВАНИЯ + * ═══════════════════════════════════════════════════════════════════ + * + * #include "vgm.h" + * #include "isr.h" + * #include "wc_api.h" + * + * // 1. Загрузить VGM в VPL-страницы + * wc_mngcvpl(0); + * wc_load512(0xC000, 32); // 16 КБ + * + * // 2. Разобрать заголовок + * if (vgm_parse_header() != VGM_OK) return; + * + * // 3. Заполнить оба буфера ISR + * vgm_fill_buffer(0); // буфер A + * vgm_fill_buffer(1); // буфер B + * + * // 4. В main loop: + * if (cur_buf != last_buf) { + * vgm_fill_buffer(cur_buf ^ 1); // свободный + * last_buf = cur_buf; + * } + * if (vgm_song_ended && !vgm_rewind_to_loop()) break; + */ + +#ifndef VGM_H +#define VGM_H + +#include "types.h" + +/* ── Масштабирование частот PSG под реальное железо ─────────────────── */ +#define FREQ_MODE_NATIVE 0 /* HW clock ≈ VGM clock (±2%), без пересчёта */ +#define FREQ_MODE_TABLE 1 /* LUT table match (±5%) */ + +/** Текущий режим масштабирования (FREQ_MODE_xxx) */ +extern uint8_t vgm_freq_mode; + +/** Базовый адрес LUT таблицы в Window 0 (#0000-#3FFF). + * Используется в TABLE mode: freq_lut_base[period] → scaled period. + * NULL (0) если режим не TABLE. + * PSG: индекс 0..4095 (12-bit). + * Таблица в plugin page N, подключённой через wc_mng0_pl(N). */ +extern uint16_t *freq_lut_base; + +/** Clock (кГц) подобранной LUT таблицы (для отображения в UI). + * 0 если режим NATIVE. */ +extern uint16_t vgm_freq_lut_khz; + +/* ══════════════════════════════════════════════════════════════════════ + * ID чипов = смещение clock-поля в VGM-заголовке. + * Имена определены только для поддерживаемых чипов. + * ══════════════════════════════════════════════════════════════════════ */ +#define VGM_OFF_SN76489 0x0C /* PSG SN76489 / SN76496 */ +#define VGM_OFF_YM2413 0x10 /* OPLL */ +#define VGM_OFF_YM2203 0x44 /* OPN (PC-88) */ +#define VGM_OFF_YM3812 0x50 /* OPL2 (AdLib / SB) */ +#define VGM_OFF_YM3526 0x54 /* OPL (ISA AdLib) */ +#define VGM_OFF_Y8950 0x58 /* MSX-AUDIO / OPL */ +#define VGM_OFF_YMF262 0x5C /* OPL3 (SB Pro 2) */ +#define VGM_OFF_YMF278B 0x60 /* OPL4 */ +#define VGM_OFF_AY8910 0x74 /* AY-3-8910 / YM2149 */ +#define VGM_OFF_SAA1099 0xC8 /* SAA1099 (SAM Coupé) */ + +/* ── Тип OPL-чипа (обратная совместимость) ───────────────────────── */ +#define VGM_CHIP_NONE 0 +#define VGM_CHIP_OPL 1 /* YM3526 (OPL1) + Y8950: cmd 0x5B/0x5C */ +#define VGM_CHIP_OPL2 2 /* YM3812 (OPL2): cmd 0x5A */ +#define VGM_CHIP_OPL3 3 /* YMF262 (OPL3): cmd 0x5E/5F */ +#define VGM_CHIP_OPL4 4 /* YMF278B (OPL4): cmd 0xD0 (FM порты 0/1 — + * те же регистры, что и OPL3; порт 2 — + * wave-часть через #7E/#7F, ZXM-MoonSound) */ + +/* Приоритет определения типа: YMF278B > YMF262 > YM3812 > YM3526/Y8950 + * Используется в build_playback_queue() (main.c) для выбора cmdblock init: + * VGM_CHIP_OPL4 → CMDBLK_INIT_OPL4 (NEW=1 + NEW2=1, разблокирует wave-часть) + * VGM_CHIP_OPL3 → CMDBLK_INIT_OPL3 (NEW=1, L/R через C0-C8) + * иначе → CMDBLK_INIT_OPL2 (NEW=0, L+R авто, OPL2-compat) */ + +/* ── Коды результата ─────────────────────────────────────────────── */ +#define VGM_OK 0 +#define VGM_ERR_HEADER 1 /* Нет сигнатуры "Vgm " или повреждён */ +#define VGM_ERR_NOOPL 2 /* Нет OPL-чипа (clock = 0 для всех) */ + +/* ── Флаги чипа (vgm_chip_entry_t.flags) ─────────────────────────── */ +#define VGM_CF_DUAL 0x01 /* Bit 30 clock: два экземпляра чипа */ + +/* ══════════════════════════════════════════════════════════════════════ + * Структура обнаруженного чипа + * ══════════════════════════════════════════════════════════════════════ */ +typedef struct { + uint8_t id; /* VGM_OFF_xxx (header offset of clock) */ + uint8_t flags; /* VGM_CF_DUAL и пр. */ + uint16_t clock_khz; /* clock / 1000 (0–65535 кГц) */ +} vgm_chip_entry_t; /* 4 байта */ + +#define VGM_MAX_CHIPS 8 /* макс. чипов в vgm_chip_list[] */ + +/* ── Состояние парсера (глобальные переменные) ───────────────────── */ + +/** 1 = достигнут конец VGM-данных (команда 0x66 или конец файла) */ +extern volatile uint8_t vgm_song_ended; + +/** 1 = пауза (буферы заполняются тишиной) */ +extern uint8_t vgm_paused; + +/** Текущая VPL-страница, отображённая на #C000 */ +extern uint8_t vgm_cur_page; + +/** Указатель чтения VGM (#C000–#FFFF) */ +extern uint16_t vgm_read_ptr; + +/** Номер последней VPL-страницы (0-based) */ +extern uint8_t vgm_end_page; + +/** Адрес конца данных в последней странице */ +extern uint16_t vgm_end_addr; + +/** Версия VGM (BCD): 0x0151 = v1.51, 0x0171 = v1.71 */ +extern uint16_t vgm_version; + +/** Тип обнаруженного OPL-чипа (VGM_CHIP_xxx) */ +extern uint8_t vgm_chip_type; + +/** Массив обнаруженных чипов (для вывода информации о файле) */ +extern uint8_t vgm_chip_count; +extern vgm_chip_entry_t vgm_chip_list[VGM_MAX_CHIPS]; + +/** Адрес точки петли (#C000–#FFFF); 0 = нет петли */ +extern uint16_t vgm_loop_addr; + +/** VPL-страница точки петли */ +extern uint8_t vgm_loop_page; + +/** Общая длительность воспроизведения (секунды), с учётом loop rewinds */ +extern uint16_t vgm_total_seconds; + +/** Флаг: использовать ли loop при воспроизведении. + * Устанавливается в vgm_parse_header() с учётом loop policy. */ +extern uint8_t vgm_loop_enabled; + +/* ── Loop/duration policy (читаются из INI, устанавливаются main.c) ── */ +extern uint8_t cfg_loop_rewinds; /* кол-во повторов петли (default 1) */ +extern uint8_t cfg_min_duration; /* ниже этой длительности (сек) петля запрещена (default 10) */ +extern uint16_t cfg_max_duration; /* выше этой длительности (сек) петля запрещена (default 240) */ + +/* ── GD3 метаданные (только English, ASCII) ─────────────────────── */ +#define VGM_GD3_LEN 48 /* макс. длина одного поля (+1 для '\0') */ + +extern char vgm_gd3_track[VGM_GD3_LEN]; /* Название трека (EN) */ +extern char vgm_gd3_game[VGM_GD3_LEN]; /* Название игры (EN) */ +extern char vgm_gd3_system[VGM_GD3_LEN]; /* Название системы (EN) */ +extern char vgm_gd3_author[VGM_GD3_LEN]; /* Автор (EN) */ + +/* ── Функции ─────────────────────────────────────────────────────── */ + +/** + * Разобрать заголовок VGM из #C000 (страница 0 должна быть загружена). + * Заполняет vgm_read_ptr, vgm_cur_page, vgm_loop_addr/page, + * vgm_version, vgm_chip_type, vgm_chip_list[]/vgm_chip_count. + * @return VGM_OK (0) или VGM_ERR_HEADER (1) + */ +uint8_t vgm_parse_header(void); + +/** + * Заполнить командный буфер buf_idx (0 = A, 1 = B). + * + * Поддерживаемые VGM-команды: + * 0x5A/0x5B/0x5C/0x5E → CMD_WRITE_B0 (OPL1/OPL2/Y8950/OPL3 bank 0) + * 0x5F → CMD_WRITE_B1 (OPL3 bank 1) + * 0xD0 (port 0/1/2) → CMD_WRITE_B0 / CMD_WRITE_B1 / CMD_WRITE_WAVE (YMF278B/OPL4) + * 0x55 → CMD_WRITE_AY (YM2203 PSG-часть, reg 0-15) + * 0xA0 → CMD_WRITE_AY / CMD_WRITE_AY2 (AY8910, dual) + * 0xBD → CMD_WRITE_SAA (SAA1099, bit7 = chip select) + * 0x61/0x62/0x63/0x70-0x7F → CMD_WAIT + * 0x66 → end; 0x67 → data block skip + * + * Гарантирует CMD_END_BUF в конце буфера. + * @param buf_idx 0 = cmd_buf_a, 1 = cmd_buf_b + */ +void vgm_fill_buffer(uint8_t buf_idx); + +/** + * Перемотать VGM на точку петли (если установлена). + * Сбрасывает vgm_song_ended = 0 при успехе. + * @return 1 = петля есть (перемотано), 0 = нет петли + */ +uint8_t vgm_rewind_to_loop(void); + +/** + * Разобрать GD3-тег (метаданные) из VGM файла. + * Вызывать после vgm_parse_header(). + * Заполняет vgm_gd3_track/game/system/author (ASCII, EN only). + * Переключает VPL-страницу — после вызова нужно восстановить + * vgm_cur_page через wc_mngcvpl(). + */ +void vgm_parse_gd3(void); + +/** + * Получить имя чипа по header offset (VGM_OFF_xxx). + * @return указатель на строку (ROM), например "YM3812", "AY8910". + * Для неизвестных чипов возвращает NULL. + */ +const char *vgm_chip_name(uint8_t id); + +/* ─── VGM-format command blocks (cmdblocks page) ────────────────── */ + +/** Plugin page содержащая VGM-format command blocks */ +#define CMDBLK_PAGE 5 + +/** Индексы блоков в таблице указателей по адресу 0xC000 */ +#define CMDBLK_INIT_OPL3 0 /* OPL3 init: NEW=1, 4-op=0, test=0 */ +#define CMDBLK_INIT_OPL2 1 /* OPL2 compat: NEW=0, 4-op=0, test=0 */ +#define CMDBLK_SILENCE_OPL 2 /* OPL silence: KeyOff+TL+waveform+BD */ +#define CMDBLK_SILENCE_AY 3 /* AY chip 1: mixer off, vol=0 */ +#define CMDBLK_SILENCE_AY2 4 /* AY chip 2 (TS): mixer off, vol=0 */ +#define CMDBLK_SILENCE_SAA 5 /* SAA1099 chip 1: reset + amp=0 */ +#define CMDBLK_SILENCE_SAA2 6 /* SAA1099 chip 2: reset + amp=0 */ +#define CMDBLK_SAA_CLK_ON 7 /* MultiSound: SAA clock enable */ +#define CMDBLK_SAA_CLK_OFF 8 /* MultiSound: SAA clock disable */ +#define CMDBLK_SILENCE_YM2203 9 /* YM2203 chip 1: SSG+FM silence */ +#define CMDBLK_SILENCE_YM2203_2 10 /* YM2203 chip 2 (TS): SSG+FM silence */ +#define CMDBLK_SAA_INIT 11 /* SAA1099 chip 1: Sound Enable ON */ +#define CMDBLK_SAA2_INIT 12 /* SAA1099 chip 2: Sound Enable ON+clk */ +#define CMDBLK_INIT_OPL4 13 /* OPL4 init: NEW=1 + NEW2=1 (wave unlock) */ +#define CMDBLK_SILENCE_WAVE 14 /* OPL4 wave-часть silence: 24 канала KeyOff+Damp+TL max */ + +/* ─── Очередь высокоуровневых команд (HL queue) ───────────────── */ + +/** Типы HL-команд */ +#define HLCMD_CMDBLK 1 /* Выполнить VGM-format cmdblock (param = CMDBLK_xxx) */ +#define HLCMD_PLAY 2 /* Воспроизведение VGM с текущей позиции до 0x66 */ +#define HLCMD_LOOP 3 /* Перемотка к loop-точке, затем play (морфируется→PLAY) */ +#define HLCMD_ISR_DONE 4 /* Записать CMD_ISR_DONE в буфер, ISR замирает */ + +#define HL_QUEUE_MAX 16 + +typedef struct { uint8_t cmd, param; } hl_entry_t; + +/** Глобальные переменные HL queue — заполняются в main.c (build_playback_queue), + * используются внутри vgm_fill_buffer(). */ +extern hl_entry_t vgm_hl_queue[HL_QUEUE_MAX]; +extern uint8_t vgm_hl_len; /* всего записей */ +extern uint8_t vgm_hl_pos; /* текущая запись */ +extern uint8_t vgm_hl_abort_pos; /* переход при abort пользователя */ +extern uint8_t vgm_loop_count; /* инкрементируется при каждом LOOP */ + +/* ─── VGZ (gzip-сжатый VGM) ─────────────────────────────────────── */ + +/** + * Копировать n_pages страниц из мегабуфера (#20+) в TAP (#A1+). + * DI на время работы, восстанавливает Win 0 / Win 3 и EI при выходе. + * + * @param n_pages количество страниц (макс. 31 — размер TAP области) + */ +void copy_pages_to_tap(uint8_t n_pages); + +/** + * Распаковать VGZ из TAP-страниц в мегабуфер. + * Вызывается из C — ассемблерная обёртка (inflate_call.s). + * + * @param src_page первая физ. страница источника (TAP, напр. 0xA1) + * @param dst_page первая физ. страница назначения (мегабуфер, напр. 0x20) + * @return 0 — успех, 1 — ошибка (неверные данные / не gzip) + */ +uint8_t inflate_vgz(uint8_t src_page, uint8_t dst_page); + +#endif /* VGM_H */ diff --git a/src_sdcc/inc/wc_api.h b/src_sdcc/inc/wc_api.h new file mode 100644 index 0000000..75e8911 --- /dev/null +++ b/src_sdcc/inc/wc_api.h @@ -0,0 +1,1883 @@ +/** + * @file wc_api.h + * @brief Полное API Wild Commander (WC) для SDCC Z80 + * @version 5.0 + * + * Самодостаточная библиотека: зависит только от types.h. + * Реализация: asm/wc_api.s (ASM-обёртки вокруг CALL 0x6006). + * + * ══════════════════════════════════════════════════════════════════════ + * МЕХАНИЗМ ВЫЗОВА + * ══════════════════════════════════════════════════════════════════════ + * + * Единственная точка входа ко всем функциям WC: + * + * CALL 0x6006 ; A = номер функции, остальные регистры — аргументы + * + * WC минимально гробит: AF, BC, DE, HL, IX (при каждом вызове!) + * WC сохраняет: IY, AF', BC', DE', HL', SP + * + * + * ══════════════════════════════════════════════════════════════════════ + * СОГЛАШЕНИЕ ВЫЗОВА (sdcccall(1), SDCC 4.x) + * ══════════════════════════════════════════════════════════════════════ + * + * Первый аргумент: uint8 → A + * uint16 / ptr → HL (ВАЖНО: именно HL, не DE!) + * Второй аргумент: uint16 / ptr → DE + * Остальные: на стек, крайний правый аргумент первым (push) + * callee обязан очистить стек сам (callee-cleans) + * Возврат: uint8 → A; uint16 → HL + * + * ══════════════════════════════════════════════════════════════════════ + * СТАРТОВЫЕ УСЛОВИЯ ПЛАГИНА + * ══════════════════════════════════════════════════════════════════════ + * + * WC вызывает плагин как CALL #8000. На входе: + * + * A = тип вызова: + * 0x00 — по расширению файла + * 0x01 — при загрузке плагина (по ENTER) + * 0x02 — по таймеру скринсейвера / из меню F10 + * 0x03 — из меню запуска плагинов (F10) + * 0x04 — из меню выбора просмотрщика (Shift+F3 / Shift+Enter) + * 0x12 — по F2 + * 0x14 — по F4 + * [DE,HL] = размер файла (DE = старший, HL = младший; 32-бит) + * BC = адрес имени файла: name(1−255), 0x00 (только для 0x00/0x04/0x14) + * IX = адрес структуры активной панели (нужен для некоторых функций!) + * + * Дополнительно для 0x00/0x04/0x12/0x14: + * A' — номер расширения (0–31), по которому файл передан плагину + * DE' — количество файлов/каталогов в активной панели (включая "..") + * HL' — номер объекта из панели (от 1) + * если HL' == DE', переход на следующий файл не имеет смысла + * + * Значение A при выходе из плагина (записывать в wc_exit_code): + * 0 — выход (ESC) + * 1 — файл не распознан, передать другому просмотрщику/плагину + * (только для типа вызова 0x00) + * 2 — перейти к следующему файлу в каталоге (только 0x00) + * 3 — перечитать каталог (не для типов 0x01, 0x02) + * 4 — перейти к предыдущему файлу в каталоге (только 0x00) + * + * ══════════════════════════════════════════════════════════════════════ + * КАРТА АДРЕСНОГО ПРОСТРАНСТВА CPU (4 окна × 16 КБ) + * ══════════════════════════════════════════════════════════════════════ + * + * TSConfig даёт CPU 64 КБ через 4 переключаемых окна, каждое может + * отображать любую из 256 физических страниц (по 16 КБ = 4 МБ всего). + * Текущие номера страниц читаются из #6000–#6003 (WC_SYS_PG0–PGC). + * + * Адреса CPU Окно Назначение при работе плагина + * ─────────────── ────── ────────────────────────────────────────── + * #0000 – #3FFF Win 0 ★ Плагин может переключать через mng0_pl() + * По умолчанию: TXT-буфер / системная стр. + * WC API: сохраняет и восстанавливает при + * каждом CALL #6006 (P8000 dispatch) + * ⚠ GEDPL затирает область #1000–#1447! + * ⚠ Нельзя хранить здесь параметры для WC API + * (SOW-структуры, строки, буферы файлов), + * т.к. WC ремапит это окно внутри вызовов. + * ✓ Можно использовать для своих данных МЕЖДУ + * вызовами WC (например, VGM-буферы). + * + * #4000 – #7FFF Win 1 ✖ СИСТЕМНАЯ ОБЛАСТЬ — НЕ ТРОГАТЬ! + * Содержит: FAT engine (#4000+), стек WC, + * ядро WC, точка входа API (#6006). + * Плагин НЕ имеет права ремапить это окно. + * + * #8000 – #BFFF Win 2 ★ КОД И ДАННЫЕ ПЛАГИНА + * WC загружает сюда .WMF-файл при запуске. + * Точка входа: CALL #8000. + * WC API: сохраняет/восстанавливает страницу + * при каждом вызове P8000. + * ✓ Структуры SOW, строки для UI-функций, + * буферы файловых операций — всё здесь. + * Рекомендуемая раскладка SDCC: + * #8000–#B7FF CODE (до 14 КБ) + * #B800–#BFFF DATA (до 2 КБ) + * + * #C000 – #FFFF Win 3 ★ Плагин может переключать через mngc_pl() + * ⚠ WC API СБРАСЫВАЕТ это окно на TXT-стр. + * (#00) при КАЖДОМ вызове P8000 и + * НЕ ВОССТАНАВЛИВАЕТ после возврата! + * → После любого вызова UI-функции (gedpl, + * prwow, txtpr, rresb...) плагин обязан + * заново вызвать mngc_pl() если ему нужны + * свои данные в #C000–#FFFF. + * ✓ Удобно для загрузки данных: + * mngcvpl(N) → load512(0xC000, 32) + * загружает 16 КБ в VPL-страницу N. + * ✓ Через mng0_pl() потом можно прочитать + * ту же страницу в #0000–#3FFF. + * + * ══════════════════════════════════════════════════════════════════════ + * МЕХАНИЗМ P8000 DISPATCH (внутренний CALL #6006) + * ══════════════════════════════════════════════════════════════════════ + * + * При каждом вызове WC API (CALL #6006) происходит: + * + * 1. Сохранение текущей страницы Win 0 → переменная PG0BU + * 2. Сохранение IX → переменная IXBU + * 3. Установка Win 3 (#C000) = TXT-страница (#00 / MTXPG) + * 4. Выполнение запрошенной функции WC + * 5. Восстановление Win 0 из PG0BU + * 6. Восстановление IX из IXBU + * 7. RET к плагину + * + * Важные следствия: + * • Win 0: после вызова — та же страница, что была до вызова ✓ + * • Win 3: после вызова — ВСЕГДА TXT-страница (#00) ✗ + * • IX: сохраняется через вызов (но A, BC, DE, HL — портятся!) + * + * ══════════════════════════════════════════════════════════════════════ + * БУФЕР СОХРАНЕНИЯ ОКОН (RESB — страница #1D) + * ══════════════════════════════════════════════════════════════════════ + * + * WC использует страницу #1D (WC_PAGE_RESB) как стек для сохранения + * фона под окнами. Размер — 16 КБ, что хватает для нескольких окон. + * + * Поле buf_addr в структуре SOW (wc_window_t): + * + * 0x0000 → "Автоматическое выделение" — WC при вызове prwow() + * сохранит фон в RESB-стек и запишет реальный адрес. + * При rresb() — фон будет корректно восстановлен. + * ЭТО РЕКОМЕНДУЕМОЕ ЗНАЧЕНИЕ. Все штатные плагины + * WC (gsplayer, mounter, clock и т.д.) используют 0. + * + * 0xFFFF → "Не сохранять фон" — prwow() нарисует окно поверх, + * но rresb() не сможет восстановить содержимое. + * Используется только если фон не нужен (fullscreen). + * + * иное → ручной адрес буфера (не рекомендуется). + * + * ══════════════════════════════════════════════════════════════════════ + * GEDPL — ПОВРЕЖДЕНИЕ ОБЛАСТИ #1000–#1447 + * ══════════════════════════════════════════════════════════════════════ + * + * Функция wc_gedpl() (восстановление дисплея WC) при работе + * затирает область #1000–#1447 (1096 байт) в окне 0. + * Это используется для внутренних буферов восстановления палитры + * и атрибутов текстового экрана. + * + * Последствия для плагина: + * • Нельзя хранить критичные данные по адресам #1000–#1447 + * • Если плагин использует #0000–#3FFF через mng0_pl(), + * данные в этом диапазоне будут повреждены после gedpl() + * • Безопасные области в Win 0: #0000–#0FFF и #1448–#3FFF + * (но только МЕЖДУ вызовами WC API, не во время!) + * + * ══════════════════════════════════════════════════════════════════════ + * АДРЕСНЫЕ ОГРАНИЧЕНИЯ ПАРАМЕТРОВ WC API + * ══════════════════════════════════════════════════════════════════════ + * + * Структуры и буферы, передаваемые в функции WC, ОБЯЗАНЫ находиться + * в определённых адресных диапазонах: + * + * Файловые функции (load512, save512, fentry, mkfile, findnext...): + * Буферы и имена файлов: #8000–#FFFF + * (WC ремапит Win 0 и Win 1 для FAT — #0000–#7FFF недоступен!) + * + * UI функции (prwow, txtpr, rresb, scrlwow, mezz...): + * Структура SOW: #8000–#BFFF (предпочтительно) + * Строки текста: #8000–#BFFF + * (Win 3 сброшен на TXT — #C000+ тоже допустим, но это TXT-стр.) + * + * Общее правило: размещайте ВСЕ данные WC API в #8000–#BFFF. + * Это единственный диапазон, гарантированно стабильный во время + * и после любых вызовов WC API. + * + * ══════════════════════════════════════════════════════════════════════ + * МНОГОСТРАНИЧНЫЕ ПЛАГИНЫ (до 64 страниц) + * ══════════════════════════════════════════════════════════════════════ + * + * Плагин может занимать до 64 страниц (#61–#A0) в пуле WC. + * Для переключения между страницами используются резиденты: + * + * Резидент перехода (#6020, PLRESAD): + * A=номер страницы (0=первая), HL=адрес → JP (HL) + * Точка невозврата: управление передаётся навсегда. + * + * Резидент вызова (#6028, PLRESCALL): + * A=номер страницы, HL=адрес → CALL (HL), затем возврат + * Сохраняет все регистры кроме A и HL. + * + * Функции mng0_pl(page) и mngc_pl(page) принимают ЛОГИЧЕСКИЙ + * номер страницы (от 0), WC добавляет базу PLGPG (#61) автоматически. + * + * Типичная архитектура многостраничного плагина: + * Стр. 0: код + данные (постоянно в #8000–#BFFF) + * Стр. 1+: данные, загружаемые в #0000 через mng0_pl() + * или в #C000 через mngc_pl() / mngcvpl() + * + * ══════════════════════════════════════════════════════════════════════ + * РЕКОМЕНДУЕМАЯ РАСКЛАДКА ПАМЯТИ SDCC-ПЛАГИНА + * ══════════════════════════════════════════════════════════════════════ + * + * Адреса Содержимое + * ─────────────── ─────────────────────────────────────────────── + * #0000 – #3FFF VGM/данные через mng0_pl() (только между + * вызовами WC! GEDPL затирает #1000–#1447) + * #4000 – #7FFF ✖ WC SYSTEM — не трогать + * #8000 – #B7FF _CODE: код плагина (до 14 КБ) + * #B800 – #BFFF _DATA: переменные, SOW, строки (до 2 КБ) + * #C000 – #FFFF Загрузка данных через mngcvpl()/mngc_pl() + * (сбрасывается WC после каждого API-вызова!) + * + * build.bat: sdcc --code-loc 0x8000 --data-loc 0xB800 + * + * ══════════════════════════════════════════════════════════════════════ + * СИСТЕМНЫЕ ПЕРЕМЕННЫЕ WC (читаются напрямую по фиксированным адресам) + * ══════════════════════════════════════════════════════════════════════ + * + * #6000 [4] — номера страниц в окнах #0000, #4000, #8000, #C000 + * #6004 [1] — ABT: 1 = был нажат ESC + * #6005 [1] — ENT: 1 = был нажат ENTER + * #6009 [2] — TMN: таймер INT (uint16_t, инкрементируется каждым INT) + * #600D [1] — CNFv: тип ЦАП/видео: + * 0=ШИМ/IDE, 1=3-bit VDAC, 2=4-bit VDAC, + * 3=5-bit VDAC, 7=VDAC2 (FT812) + * #600E [1] — HEI: высота текстового экрана в строках (25 / 30 / 36) + * + * ══════════════════════════════════════════════════════════════════════ + * СТРУКТУРА SOW — Structure Of Window (16 байт) + * ══════════════════════════════════════════════════════════════════════ + * + * Поле type (+0): + * [7] = 1 → тень вокруг окна + * [6] = 1 → курсор во всю ширину окна + * [5] = зарезервировано + * [4] = 1 → шапка-заголовок (инверсная строка вверху) + * [3:0] = стиль рамки: + * 0 — без рамки (стандартное окно, рамка 1-го типа) + * 1 — с заголовками и текстом (рамка 1-го типа, без курсора) + * 2 — с заголовками и текстом + курсор (рамка 1-го типа) + * 3 — стандартное окно, рамка 2-го типа + * 4 — с заголовками и текстом (рамка 2-го типа, без курсора) + * 5 — с заголовками и текстом + курсор (рамка 2-го типа) + * 6 — стандартное окно, рамка 3-го типа + * 7 — с заголовками и текстом (рамка 3-го типа, без курсора) + * 8 — с заголовками и текстом + курсор (рамка 3-го типа) + * + * Примечание: WC заменяет x=255→реальное значение при первом рисовании; + * аналогично y=255, width=0, height=0. + * + * Поле buf_addr (+8): — см. раздел «БУФЕР СОХРАНЕНИЯ ОКОН (RESB)» + * 0x0000 — авто-выделение в RESB-стеке (РЕКОМЕНДУЕТСЯ!) + * 0xFFFF — не сохранять фон (нельзя восстановить через rresb) + * иное — ручной адрес буфера (не рекомендуется) + * + * Линии-разделители (+10, +11): + * Задают Y-смещение от нижнего края окна. + * Если != 0, WC рисует горизонтальную линию внутри рамки. + * + * Поля +12–+15 зависят от стиля (type[3:0]): + * Стиль 0/3/6 (стандартное окно): + * +12 — позиция курсора (от 1) + * +13 — нижний ограничитель курсора + * +14 — цвет курсора (накладывается по маске cur_mask) + * +15 — цвет окна под курсором + * Стиль 1/4/7 (заголовочное окно без курсора): + * +12,+13 — адрес строки верхнего заголовка (0 = не выводить) + * +14,+15 — адрес строки нижнего заголовка (0 = не выводить) + * + * Адресные ограничения: структура SOW должна находиться в + * #8000–#BFFF или #0000–#3FFF (недоступна из #C000!). + * + * ══════════════════════════════════════════════════════════════════════ + * ТЕКСТОВАЯ РАЗМЕТКА (для TXTPR, MEZZ, PRWOW) + * ══════════════════════════════════════════════════════════════════════ + * + * Строки могут содержать управляющие коды: + * + * 0x00 — конец строки и абзаца + * 0x00, 0x00 — разделитель сообщений (только для MEZZ) + * 0xFE, lo, hi — ссылка на строку по адресу lo|hi<<8 + * (абзацы связывать бесполезно — выводит только первую строку) + * 0x01–0x08 — цвет символов (INK): 1=синий ... 7=белый, 8=чёрный + * 0x01–0x08, 0x01–0x08— цвет PAPER+INK (первый=фон, второй=символ) + * если оба одинаковы → ярко-фоновый INK + * 0x09 — инверсия цвета (PAPER ↔ INK) + * 0x0B, XX — сдвиг вправо на XX символов + * 0x0C, YY — сдвиг вниз на YY строк (X и цвет не трогает) + * 0x0D — перенос строки, INK → фоновый цвет + * 0x0E — центрирование строки (X сбрасывается) + * не работает если строка >= ширины окна + * или если есть ссылка (#FE) + * + * ══════════════════════════════════════════════════════════════════════ + * АТРИБУТ ЦВЕТА (EGA-формат, 1 байт) + * ══════════════════════════════════════════════════════════════════════ + * + * [7:4] = BACKGROUND (фон): 16 цветов (0–15) + * [3:0] = FOREGROUND (текст): 16 цветов (0–15) + * + * Кодировка YGRB (Bright, Green, Red, Blue): + * 0=чёрный, 1=синий, 2=красный, 3=пурпурный, + * 4=зелёный, 5=циановый, 6=жёлтый, 7=белый, + * 8=тёмно-серый, 9..15 = яркие варианты 1..7 + * + * Используй макрос WC_COLOR(bg, fg) и константы WC_BLACK..WC_BRIGHT_WHITE + * + * ══════════════════════════════════════════════════════════════════════ + * ПРИМЕР ИСПОЛЬЗОВАНИЯ + * ══════════════════════════════════════════════════════════════════════ + * + * #include "wc_api.h" + * + * static wc_window_t win; + * + * void show_message(void) { + * win.type = WC_WIN_SINGLE; // однолинейная рамка + * win.cur_mask = 0x07; + * win.x = 255; win.y = 255; // по центру + * win.width = 40; win.height = 5; + * win.color = WC_COLOR(WC_BLACK, WC_BRIGHT_WHITE); + * win.buf_addr = 0xFFFF; // без сохранения фона + * + * wc_gedpl(); // восстановить дисплей WC + * wc_prwow(&win); // нарисовать окно + * wc_txtpr(&win, "Hello!", 1, 1); // вывести текст + * wc_key_wait_any(); // ждать любую клавишу + * wc_rresb(&win); // убрать окно + * } + * + * void load_data(void) { + * uint8_t rc; + * wc_mngcvpl(0); // VPL-страница 0 → #C000 + * rc = wc_load512(0xC000, 32); // загрузить 16384 байт + * if (rc && rc != WC_EOF) { } // error + * } + */ + +#ifndef WC_API_H +#define WC_API_H + +#include "types.h" + +/* ══════════════════════════════════════════════════════════════════════ + * Константы + * ══════════════════════════════════════════════════════════════════════ */ + +#define WC_ENTRY 0x6006 /* адрес точки входа WC */ + +/* Стили рамки окна (type[3:0]): стандартные окна с курсором */ +#define WC_WIN_NO_BORDER 0x00 /* без рамки, стандартное с курсором */ +#define WC_WIN_SINGLE 0x01 /* однолинейная рамка, без курсора */ +#define WC_WIN_DOUBLE 0x02 /* двойная рамка + курсор */ +#define WC_WIN_TYPE3 0x03 /* рамка 2-го типа, с курсором */ +#define WC_WIN_TYPE4 0x04 /* рамка 2-го типа, с заголовком/текстом, без курсора */ +#define WC_WIN_TYPE5 0x05 /* рамка 2-го типа + курсор */ +#define WC_WIN_TYPE6 0x06 /* рамка 3-го типа, с курсором */ +#define WC_WIN_TYPE7 0x07 /* рамка 3-го типа, с заголовком/текстом, без курсора */ +#define WC_WIN_TYPE8 0x08 /* рамка 3-го типа + курсор */ + +/* Флаги type[7:4] */ +#define WC_WIN_HEADER 0x10 /* инверсная строка-заголовок вверху */ +#define WC_WIN_WIDE_CURSOR 0x40 /* курсор на всю ширину окна */ +#define WC_WIN_SHADOW 0x80 /* тень вокруг окна */ + +/* wc_yn() mode */ +#define WC_YN_OK_CANCEL 0x01 +#define WC_YN_YES_NO 0x02 +#define WC_YN_POLL 0x00 /* опросить без ожидания */ +#define WC_YN_EXIT 0xFF /* закрыть меню YN */ + +/* wc_kbscn() mode */ +#define WC_KBSCN_NORMAL 0x00 /* учитывать SHIFT / CL / Lang */ +#define WC_KBSCN_RAW 0x01 /* сырой скан-код */ + +/* wc_mngv_pl() mode */ +#define WC_VIDEO_TXT 0x00 /* текстовый режим WC */ +#define WC_VIDEO_BUF1 0x01 /* видеобуфер 1 */ +#define WC_VIDEO_BUF2 0x02 +#define WC_VIDEO_BUF3 0x03 +#define WC_VIDEO_BUF4 0x04 + +/* wc_adir() mode */ +#define WC_ADIR_SEEK_START 0x00 /* установить позицию на начало */ +#define WC_ADIR_RESET_NEXT 0x01 /* сброс FindNext */ + +/* wc_stream() mode */ +#define WC_STREAM_ROOT 0xFF /* корневой поток */ +#define WC_STREAM_CLONE 0xFE /* клонировать текущий поток */ +#define WC_STREAM_WCDIR 0xFD /* каталог WC */ +/* 0x00, 0x01 = номер потока 0 или 1 */ + +/* wc_int_pl() mode */ +#define WC_INT_DISABLE_ALL 0x00 /* отключить все прерывания WC */ +#define WC_INT_NO_TIME 0x01 /* отключить таймерное INT WC */ +#define WC_INT_NO_PS2 0x02 /* отключить PS/2 INT WC */ +#define WC_INT_PLUGIN 0xFF /* установить обработчик плагина */ + +/* wc_turbopl() mode */ +#define WC_TURBO_CPU 0x00 /* B=0: настройка CPU; C=частота */ +#define WC_TURBO_AY 0x01 /* B=1: AY частота */ +#define WC_TURBO_RESTORE 0xFF /* восстановить оригинальный режим */ + +/* Коды ошибок файловых операций */ +#define WC_EOF 0x0F /* конец файла / конец цепочки (LOAD512) — не ошибка! */ + +/* Флаги типа файла (wc_fentry, wc_mkfile, wc_delfl, wc_rename) */ +#define WC_FILE_FLAG 0x00 /* объект является файлом */ +#define WC_DIR_FLAG 0x10 /* объект является каталогом */ + +/* Коды ошибок FAT-операций */ +#define WC_ERR_LONG_NAME 1 /* длинное имя некорректно */ +#define WC_ERR_SHORT_IDX 2 /* короткое имя требует индекса */ +#define WC_ERR_LONG_EXISTS 3 /* длинное имя уже существует */ +#define WC_ERR_SHORT_EXISTS 4 /* короткое имя уже существует */ +#define WC_ERR_NOT_FOUND 8 /* источник не найден */ +#define WC_ERR_NO_SPACE 16 /* нет свободного места */ +#define WC_ERR_UNKNOWN 255 /* неизвестная ошибка */ + +/* Константы прокрутки wc_scrlwow (flags) */ +#define WC_SCRL_DOWN 0x00 /* направление — вниз */ +#define WC_SCRL_UP 0x01 /* направление — вверх */ +/* правка/влево (0x02, 0x03) зарезервированы, не работают */ +#define WC_SCRL_WITH_ATTRS 0x80 /* прокручивать атрибуты вместе с символами */ +#define WC_SCRL_CLEAR_SRC 0x40 /* очищать исходную позицию после прокрутки */ +/* [5:2] = шаг: 0=1 символ, 0xF=16 символов (сдвинуть на N+1) */ +/* При step=0 и без флагов [7:2] — автоматически использует DMA */ + +/* ────────────────────────────────────────────────────────────────────── + * СТРАНИЦЫ ПАМЯТИ WC (физические номера страниц #00–#FF) + * ────────────────────────────────────────────────────────────────────── */ + +/* Текстовые экраны */ +#define WC_PAGE_MTXPG 0x00 /* основной текстовый экран */ +#define WC_PAGE_FONT0 0x01 /* шрифт основного TXT + Hrust/MegaLZ*/ +#define WC_PAGE_BUFPG 0x08 /* второй текстовый экран / буфер */ +#define WC_PAGE_FONT1 0x09 /* шрифт второго TXT */ + +/* Код WC */ +#define WC_PAGE_CODE 0x02 /* основной код WC (#8000–#AFFF) */ +#define WC_PAGE_FAT_ENGINE 0x05 /* FAT engine (#4000) + stack + код */ + +/* Каталоги панелей */ +#define WC_PAGE_CTZPG 0x0B /* начало каталога LEFT панели */ +#define WC_PAGE_CAT_LEFT 0x0B /* стр. #0B-#13 = 9 стр. */ +#define WC_PAGE_CAT_RIGHT 0x14 /* стр. #14-#1C = 9 стр. */ +#define WC_CAT_MAX_PAGES 9 /* макс. страниц на каталог */ +#define WC_PAGE_RESB 0x1D /* буфер сохранения окон */ +#define WC_PAGE_CPPG1 0x1E /* позиции каталога LEFT */ +#define WC_PAGE_CPPG2 0x1F /* позиции каталога RIGHT */ + +/* Megabuffer / видеобуферы */ +#define WC_PAGE_TVBPG 0x20 /* начало MEGABUFFER (64 стр.) */ +#define WC_PAGE_TVEPG 0x5F /* конец MEGABUFFER */ +#define WC_PAGE_GV1 0x20 /* видеобуфер 1 (16 стр.) */ +#define WC_PAGE_GV2 0x30 /* видеобуфер 2 (16 стр.) */ +#define WC_PAGE_GV3 0x40 /* видеобуфер 3 (16 стр.) */ +#define WC_PAGE_GV4 0x50 /* видеобуфер 4 (16 стр.) */ +#define WC_VIDEO_PAGES_PER_BUF 16 /* страниц в одном видеобуфере */ + +/* Плагины */ +#define WC_PAGE_PLHPG 0x60 /* заголовки плагинов (96) */ +#define WC_PAGE_PLGPG 0x61 /* начало кода плагинов (97) */ +#define WC_PAGE_PLEPG 0xA0 /* конец кода плагинов (160) */ +#define WC_PLUGIN_MAX_PAGES 64 /* макс. страниц для всех плагинов */ + +/* TAP */ +#define WC_PAGE_TAP_START 0xA1 /* смонтированный TAP-файл */ +#define WC_PAGE_TAP_END 0xBF /* конец TAP (31 стр.) */ + +/* vDOS RAM Disk */ +#define WC_PAGE_VDOS_RAM 0xC0 /* начало RAM-диска vDOS */ +#define WC_PAGE_VDOS_END 0xE7 /* конец RAM-диска (40 стр. = 640 КБ) */ +#define WC_PAGE_VDOS_FREE 0xE8 /* свободные стр. (#E8–#EF) */ + +/* Системные потоки */ +#define WC_PAGE_PNLPG 0xF0 /* поток ЛЕВОЙ панели */ +#define WC_PAGE_PNL_RIGHT 0xF1 /* поток ПРАВОЙ панели */ +#define WC_PAGE_INIPG 0xF2 /* поток плагина #00 / INI */ +#define WC_PAGE_STRPG 0xF3 /* поток плагина #01 */ +#define WC_PAGE_DBPG 0xF4 /* буфер SPG-загрузчика */ +#define WC_PAGE_D5PG 0xF5 /* загрузочная стр. 5 */ +#define WC_PAGE_D2PG 0xF6 /* загрузочная стр. 2 */ +#define WC_PAGE_RPPG 0xF7 /* пути панелей + флаг активной */ + +/* vDOS системные */ +#define WC_PAGE_VROM_START 0xF8 /* Virtual ROM (4 стр.) */ +#define WC_PAGE_VROM_END 0xFB +#define WC_PAGE_VDOS_TABLES 0xFC /* vDOS Tables (#FC–#FD) */ +#define WC_PAGE_VDOS_DRV 0xFE /* vDOS Drivers */ +#define WC_PAGE_VDOS_TRAPS 0xFF /* vDOS Traps */ + +/* ────────────────────────────────────────────────────────────────────── + * СИСТЕМНАЯ ОБЛАСТЬ WC (#6000–#602F) + * ────────────────────────────────────────────────────────────────────── */ + +#define WC_SYS_PG0 0x6000 /* [1] страница в окне #0000 */ +#define WC_SYS_PG4 0x6001 /* [1] страница в окне #4000 */ +#define WC_SYS_PG8 0x6002 /* [1] страница в окне #8000 */ +#define WC_SYS_PGC 0x6003 /* [1] страница в окне #C000 */ +#define WC_SYS_ABT 0x6004 /* [1] ABT: флаг ESC (1=нажат) */ +#define WC_SYS_ENT 0x6005 /* [1] ENT: флаг ENTER (1=нажат) */ +#define WC_SYS_FUN 0x6006 /* [3] API entry point (JP instr) */ +#define WC_SYS_TMN 0x6009 /* [2] таймер INT (uint16, ++/frame) */ +#define WC_SYS_FEP 0x600B /* [1] страница FAT engine в окне 0 */ +#define WC_SYS_XPP 0x600C /* [1] сохранённая страница */ +#define WC_SYS_CNFV 0x600D /* [1] конфиг видео (%000–%111) */ +#define WC_SYS_HEI 0x600E /* [1] высота экрана (25/30/36) */ +#define WC_SYS_TXTMD 0x600F /* [1] текстовый режим */ +#define WC_SYS_INIFLG 0x6010 /* [1] INI загружен */ +#define WC_SYS_RSQBC 0x6011 /* [2] RSQ buffer counter */ +#define WC_SYS_RSQB2 0x6013 /* [2] RSQ buffer 2 */ +#define WC_SYS_SDBSF 0x6015 /* [1] SD1 driver state */ +#define WC_SYS_SDBSF2 0x6016 /* [1] SD2 driver state */ +#define WC_SYS_F3ENTFL 0x6017 /* [1] F3/ENTER execution flag */ +#define WC_SYS_FEP2 0x6018 /* [2] FAT engine page extra */ +#define WC_SYS_PLRESAD 0x6020 /* [8] резидент перехода (JP page+HL) */ +#define WC_SYS_PLRESCALL 0x6028 /* [8] резидент вызова (CALL page+HL) */ + +/* ────────────────────────────────────────────────────────────────────── + * КОДЫ УСТРОЙСТВ (для wc_stream, регистр B) + * ────────────────────────────────────────────────────────────────────── */ + +#define WC_DEV_SD_ZC 0 /* SD (Z-Controller) */ +#define WC_DEV_IDE_NEMO_M 1 /* IDE Nemo Master */ +#define WC_DEV_IDE_NEMO_S 2 /* IDE Nemo Slave */ +#define WC_DEV_SD_NGS 3 /* SD (NeoGS) */ +#define WC_DEV_IDE_SMUC_M 4 /* IDE SMUC Master */ +#define WC_DEV_IDE_SMUC_S 5 /* IDE SMUC Slave */ +#define WC_DEV_SD2_ZC 6 /* SD2 (Z-Controller) */ +#define WC_DEV_COUNT 7 /* всего устройств */ + +/* ────────────────────────────────────────────────────────────────────── + * КОНФИГУРАЦИЯ ВИДЕО (CNFv, #600D) + * ────────────────────────────────────────────────────────────────────── */ + +#ifndef WC_CNFV_PWM +#define WC_CNFV_PWM 0 /* ШИМ (IDE) */ +#endif +#ifndef WC_CNFV_VDAC3 +#define WC_CNFV_VDAC3 1 /* 3-bit VDAC */ +#endif +#ifndef WC_CNFV_VDAC4 +#define WC_CNFV_VDAC4 2 /* 4-bit VDAC */ +#endif +#ifndef WC_CNFV_VDAC5 +#define WC_CNFV_VDAC5 3 /* 5-bit VDAC */ +#endif +#ifndef WC_CNFV_VDAC2 +#define WC_CNFV_VDAC2 7 /* VDAC2 (FT812) */ +#endif + +/* ────────────────────────────────────────────────────────────────────── + * ВИДЕОРЕЖИМЫ TSConfig (для wc_gvmod) + * ────────────────────────────────────────────────────────────────────── */ + +/* Разрешение [7:6] */ +#define WC_RRES_256x192 0x00 +#define WC_RRES_320x200 0x40 +#define WC_RRES_320x240 0x80 +#define WC_RRES_360x288 0xC0 + +/* Графический режим [1:0] */ +#define WC_VMODE_ZX 0x00 /* ZX Spectrum (6912 байт) */ +#define WC_VMODE_16C 0x01 /* 16 цветов (4 бит/пиксель) */ +#define WC_VMODE_256C 0x02 /* 256 цветов (8 бит/пиксель) */ +#define WC_VMODE_TXT 0x03 /* текстовый режим */ + +/* Прочие биты */ +#define WC_VMODE_NOGFX 0x20 /* выключить графику */ +#define WC_VMODE_NOTSU 0x10 /* выключить TSU */ + +/* Размеры текстового экрана */ +#define WC_SCREEN_80x25 25 +#define WC_SCREEN_80x30 30 +#define WC_SCREEN_90x36 36 +#define WC_SCREEN_W80 80 +#define WC_SCREEN_W90 90 + +/* ────────────────────────────────────────────────────────────────────── + * АППАРАТНЫЕ ПОРТЫ TSConfig (для __sfr / inline asm) + * ────────────────────────────────────────────────────────────────────── */ + +#define WC_PORT_PW0 0x10AF /* Page Window 0 */ +#define WC_PORT_PW1 0x11AF /* Page Window 1 */ +#define WC_PORT_PW2 0x12AF /* Page Window 2 */ +#define WC_PORT_PW3 0x13AF /* Page Window 3 */ +#define WC_PORT_VMOD 0x00AF /* Video mode */ +#define WC_PORT_VPAG 0x01AF /* Video page */ +#define WC_PORT_VPAL 0x07AF /* Palette */ +#define WC_PORT_BORDER 0x0FAF /* Border color */ +#define WC_PORT_SYSCONF 0x20AF /* System config (turbo [1:0]) */ +#define WC_PORT_CACHECONF 0x2BAF /* Cache config */ +#define WC_PORT_FDDVIRT 0x29AF /* FDD Virtualization (vDOS) */ + +/* DMA порты */ +#define WC_PORT_DMA_SL 0x1AAF /* DMA Source Low */ +#define WC_PORT_DMA_SH 0x1BAF /* DMA Source High */ +#define WC_PORT_DMA_SX 0x1CAF /* DMA Source Page */ +#define WC_PORT_DMA_DL 0x1DAF /* DMA Dest Low */ +#define WC_PORT_DMA_DH 0x1EAF /* DMA Dest High */ +#define WC_PORT_DMA_DX 0x1FAF /* DMA Dest Page */ +#define WC_PORT_DMA_N 0x26AF /* DMA burst size */ +#define WC_PORT_DMA_C 0x27AF /* DMA command / status */ +#define WC_PORT_DMA_T 0x28AF /* DMA burst count */ + +/* ────────────────────────────────────────────────────────────────────── + * FAT32 ENTRY (стандартная 32-байтная структура каталога) + * ────────────────────────────────────────────────────────────────────── */ + +typedef struct { + uint8_t name[11]; /* +0: короткое имя 8.3 (без точки) */ + uint8_t attr; /* +11: атрибуты (EFLG) */ + uint8_t _res1; /* +12: reserved (NTRes) */ + uint8_t crt_time_ms; /* +13: время создания (0.1 сек) */ + uint16_t crt_time; /* +14: время создания (FAT format) */ + uint16_t crt_date; /* +16: дата создания (FAT format) */ + uint16_t acc_date; /* +18: дата последнего доступа */ + uint16_t cluster_hi; /* +20: старшее слово кластера (CLSDE) */ + uint16_t mod_time; /* +22: время модификации */ + uint16_t mod_date; /* +24: дата модификации */ + uint16_t cluster_lo; /* +26: младшее слово кластера (CLSHL) */ + uint32_t file_size; /* +28: размер файла (SIZIK) */ +} wc_fat_entry_t; + +/* Атрибуты FAT entry (attr поле) */ +#define WC_FAT_ATTR_RDONLY 0x01 /* Read-only */ +#define WC_FAT_ATTR_HIDDEN 0x02 /* Hidden */ +#define WC_FAT_ATTR_SYSTEM 0x04 /* System */ +#define WC_FAT_ATTR_VOLLBL 0x08 /* Volume label */ +#define WC_FAT_ATTR_DIR 0x10 /* Directory */ +#define WC_FAT_ATTR_ARCHIVE 0x20 /* Archive */ +#define WC_FAT_ATTR_LFN 0x0F /* Long File Name entry */ + +/* Макросы для FAT date/time */ +#define WC_FAT_TIME(h,m,s) (uint16_t)(((h)<<11)|((m)<<5)|((s)>>1)) +#define WC_FAT_DATE(y,m,d) (uint16_t)((((y)-1980)<<9)|((m)<<5)|(d)) + +#define WC_FAT_TIME_HOUR(t) (((t) >> 11) & 0x1F) +#define WC_FAT_TIME_MIN(t) (((t) >> 5) & 0x3F) +#define WC_FAT_TIME_SEC(t) (((t) & 0x1F) << 1) +#define WC_FAT_DATE_YEAR(d) ((((d) >> 9) & 0x7F) + 1980) +#define WC_FAT_DATE_MONTH(d) (((d) >> 5) & 0x0F) +#define WC_FAT_DATE_DAY(d) ((d) & 0x1F) + +/* ────────────────────────────────────────────────────────────────────── + * FindNext выходной буфер (формат записи) + * ────────────────────────────────────────────────────────────────────── */ + +/** + * Структура записи, возвращаемой wc_findnext. + * Порядок полей зависит от флагов (size/date/time опциональны). + * При всех включённых флагах ([4:2]=111): + */ +typedef struct { + uint32_t size; /* [2]=1: размер файла */ + uint16_t date; /* [3]=1: дата создания (FAT формат) */ + uint16_t time; /* [4]=1: время создания (FAT формат) */ + uint8_t flag; /* Entry Flag: 0x10=DIR, 0x00=file */ + char name[1]; /* имя файла (переменная длина, ZS) */ +} wc_findnext_entry_t; + +/* Флаги для wc_findnext */ +#define WC_FIND_ALL 0x00 /* все записи [1:0]=00 */ +#define WC_FIND_FILES 0x01 /* только файлы [1:0]=01 */ +#define WC_FIND_DIRS 0x02 /* только каталоги [1:0]=10 */ +#define WC_FIND_SHORT_ONLY 0x80 /* только короткие имена [7]=1 */ +#define WC_FIND_WITH_SIZE 0x04 /* включить поле size [2]=1 */ +#define WC_FIND_WITH_DATE 0x08 /* включить поле date [3]=1 */ +#define WC_FIND_WITH_TIME 0x10 /* включить поле time [4]=1 */ +#define WC_FIND_FULL (WC_FIND_WITH_SIZE | WC_FIND_WITH_DATE | WC_FIND_WITH_TIME) + +/* ────────────────────────────────────────────────────────────────────── + * КАТАЛОЖНАЯ ЗАПИСЬ ПАНЕЛИ WC (внутренний формат) + * ────────────────────────────────────────────────────────────────────── */ + +/** + * INDEX entry (4 байта): ссылка из индекса на DATA entry. + */ +typedef struct { + uint8_t page; /* +0: логическая страница (0–8) */ + uint16_t addr; /* +1: адрес в странице (#C000–#FDFF) */ + uint8_t name_len; /* +3: длина имени */ +} wc_cat_index_t; + +/** + * DATA entry (переменная длина): полная запись файла в каталоге. + */ +typedef struct { + uint8_t mark; /* +0: Mark flags (бит 0: помечен) */ + char ext[3]; /* +1: расширение (без точки) */ + uint32_t cluster; /* +4: первый кластер FAT32 */ + uint32_t size; /* +8: размер файла */ + uint16_t date; /* +12: дата (FAT формат) */ + uint16_t time; /* +14: время (FAT формат) */ + uint8_t flag; /* +16: #10=файл, #00=каталог */ + char name[1]; /* +17: имя (переменная длина, ZS) */ +} wc_cat_data_t; + +/* ────────────────────────────────────────────────────────────────────── + * ЗАГОЛОВОК ПЛАГИНА (+0..+260) + * ────────────────────────────────────────────────────────────────────── */ + +#define WC_PLUGIN_ID "WildCommanderMDL" +#define WC_PLUGIN_VERSION 0x10 /* текущая версия формата */ + +/* Типы вызова плагина (поле +197) */ +#define WC_PCOND_EXT_ONLY 0x00 /* только по расширению */ +#define WC_PCOND_ON_LOAD 0x01 /* при загрузке (ENTER) */ +#define WC_PCOND_TIMER 0x02 /* по таймеру / F10 */ +#define WC_PCOND_F10 0x03 /* из меню F10 */ +#define WC_PCOND_VIEWER 0x04 /* из меню Shift+F3 */ +#define WC_PCOND_F10_VIEW 0x05 /* F10 или Shift+F3 */ +#define WC_PCOND_F10_F2 0x11 /* F10 или F2 */ +#define WC_PCOND_F2 0x12 /* F2 */ +#define WC_PCOND_F10_F4 0x13 /* F10 или F4 */ +#define WC_PCOND_F4 0x14 /* F4 */ +#define WC_PCOND_F10_V_F4 0x15 /* F10, Shift+F3 или F4 */ + +/* ────────────────────────────────────────────────────────────────────── + * DMA операции (коды для wc_dmapl и прямого доступа) + * ────────────────────────────────────────────────────────────────────── */ + +/* DMA device codes (биты [2:0] регистра DMA_C) */ +#define WC_DMA_RAM_RAM 0x01 /* W=0: RAM→RAM copy */ +#define WC_DMA_BLT_RAM 0x01 /* W=1: RAM blit (transparency) */ +#define WC_DMA_SPI_RAM_R 0x02 /* W=0: SPI→RAM */ +#define WC_DMA_SPI_RAM_W 0x02 /* W=1: RAM→SPI */ +#define WC_DMA_IDE_RAM_R 0x03 /* W=0: IDE→RAM */ +#define WC_DMA_IDE_RAM_W 0x03 /* W=1: RAM→IDE */ +#define WC_DMA_FILL 0x04 /* W=0: FILL RAM */ +#define WC_DMA_RAM_CRAM 0x04 /* W=1: RAM→CRAM (палитра) */ +#define WC_DMA_RAM_SFILE 0x05 /* W=1: RAM→SFILE (спрайты) */ + +/* Подфункции dmapl (А' регистр) */ +#define WC_DMA_INIT_SD 0x00 /* задать src + dst */ +#define WC_DMA_INIT_SRC 0x01 /* задать только src */ +#define WC_DMA_INIT_DST 0x02 /* задать только dst */ +#define WC_DMA_INIT_SRC_WIN 0x03 /* src из окна WC */ +#define WC_DMA_INIT_DST_WIN 0x04 /* dst из окна WC */ +#define WC_DMA_SET_T 0x05 /* задать DMA_T */ +#define WC_DMA_SET_N 0x06 /* задать DMA_N */ +#define WC_DMA_SET_TN 0x07 /* задать DMA_T + DMA_N */ +#define WC_DMA_ALIGN_SRC 0x08 /* выравнивание src */ +#define WC_DMA_ALIGN_DST 0x09 /* выравнивание dst */ +#define WC_DMA_ALIGN_SIZE 0x0A /* размер выравнивания */ +#define WC_DMA_ALIGN_ALL 0x0B /* все параметры выравнивания */ +#define WC_DMA_RUN_RAM 0xFC /* RAM→RAM с ожиданием */ +#define WC_DMA_RUN_BLT 0xFB /* BLT→RAM */ +#define WC_DMA_RUN_FILL 0xFA /* FILL */ +#define WC_DMA_RUN_CRAM 0xF9 /* RAM→CRAM */ +#define WC_DMA_RUN_SFILE 0xF8 /* RAM→SFILE */ +#define WC_DMA_RUN_NOWAIT 0xFD /* запустить без ожидания */ +#define WC_DMA_RUN_WAIT 0xFE /* запустить с ожиданием */ +#define WC_DMA_BUSY_WAIT 0xFF /* ждать готовности DMA */ + +/* DMA page flags (для subfunc #00–#02: бит 7 регистров B/C) */ +#define WC_DMA_PAGE_PLUGIN 0x80 /* страница из блока плагина (0–5) */ +/* WC_DMA_PAGE_VIDEO 0x00 страница из видеобуферов (0–63) */ + +/* ══════════════════════════════════════════════════════════════════════ + * SOW — Structure of Window (16 байт) + * ══════════════════════════════════════════════════════════════════════ */ + +/* ══════════════════════════════════════════════════════════════════════ + * КАРТА ПАМЯТИ TSConfig (4 МБ = 256 страниц × 16 КБ) + * ══════════════════════════════════════════════════════════════════════ + * + * Z80 CPU имеет 4 окна по 16 КБ. Каждое окно может адресовать любую + * из 256 страниц (8-бит page register). Управление через порты: + * + * Окно 0: #0000–#3FFF порт PW0 = #10AF (при сбросе = #00) + * Окно 1: #4000–#7FFF порт PW1 = #11AF (при сбросе = #05) + * Окно 2: #8000–#BFFF порт PW2 = #12AF (при сбросе = #02) + * Окно 3: #C000–#FFFF порт PW3 = #13AF (при сбросе = #00) + * + * ┌─────────┬─────────┬─────────────────────────────────────────────┐ + * │ Стр. │ Дес. │ Назначение │ + * ├─────────┼─────────┼─────────────────────────────────────────────┤ + * │ #00 │ 0 │ MTXPG — основной текстовый экран (TXT page) │ + * │ #01 │ 1 │ Шрифт (основного TXT) + Hrust/MegaLZ │ + * │ │ │ (#FE00+) │ + * │ #02 │ 2 │ Основной код WC (#8000–#AFFF) │ + * │ #03–04 │ 3–4 │ Не используются │ + * │ #05 │ 5 │ FAT Engine (#4000) + Stack (#5BFF) + │ + * │ │ │ код WC (#6000–#7FFF) │ + * │ #06–07 │ 6–7 │ Не используются │ + * │ #08 │ 8 │ BUFPG — второй текст. экран / буфер 16 КБ │ + * │ #09 │ 9 │ Шрифт (второго TXT экрана) │ + * │ #0A │ 10 │ (зарезервирован) │ + * │ #0B–13 │ 11–19 │ CTZPG — каталог ЛЕВОЙ панели (9 стр.) │ + * │ #14–1C │ 20–28 │ Каталог ПРАВОЙ панели (9 стр.) │ + * │ #1D │ 29 │ RESB — буфер сохранения окон (WOW save) │ + * │ #1E │ 30 │ CPPG1 — позиции каталога ЛЕВОЙ панели │ + * │ #1F │ 31 │ CPPG2 — позиции каталога ПРАВОЙ панели │ + * │ #20–5F │ 32–95 │ TVBPG — MEGABUFFER (1 МБ, 64 стр.) │ + * │ #60 │ 96 │ PLHPG — заголовки плагинов (512 б каждый) │ + * │ #61–A0 │ 97–160 │ PLGPG — код плагинов (до 1 МБ, 64 стр.) │ + * │ #A1–BF │ 161–191 │ Смонтированный TAP-файл (31 стр.) │ + * │ #C0–E7 │ 192–231 │ vDOS RAM Disk (40 стр. = 640 КБ) │ + * │ #E8–EF │ 232–239 │ Свободно (дополнит. дорожки RAM-диска) │ + * │ #F0 │ 240 │ PNLPG — поток ЛЕВОЙ панели │ + * │ #F1 │ 241 │ Поток ПРАВОЙ панели │ + * │ #F2 │ 242 │ INIPG — поток плагина #00 / INI │ + * │ #F3 │ 243 │ STRPG — поток плагина #01 │ + * │ #F4 │ 244 │ DBPG — буфер SPG-загрузчика │ + * │ #F5 │ 245 │ D5PG — загрузочная страница 5 │ + * │ #F6 │ 246 │ D2PG — загрузочная страница 2 │ + * │ #F7 │ 247 │ RPPG — пути панелей: │ + * │ │ │ LEFT (#1000), RIGHT (#2000), │ + * │ │ │ флаг активной панели (#1FFC) │ + * │ #F8–FB │ 248–251 │ Virtual ROM (vDOS, системная прошивка) │ + * │ #FC–FD │ 252–253 │ vDOS Tables (таблицы TR-DOS) │ + * │ #FE │ 254 │ vDOS Drivers (драйверы дисководов) │ + * │ #FF │ 255 │ vDOS Traps (перехватчик команд TR-DOS) │ + * └─────────┴─────────┴─────────────────────────────────────────────┘ + * + * ВИДЕОБУФЕРЫ (4 × 16 страниц = 256 КБ каждый, 1 МБ всего): + * Буфер 1: страницы #20–#2F (GVvPG=32) + * Буфер 2: страницы #30–#3F (GV2PG=48) + * Буфер 3: страницы #40–#4F (GV3PG=64) + * Буфер 4: страницы #50–#5F (GV4PG=80) + * (МEGABUFFER #20–#5F включает все 4 видеобуфера) + * + * Функция MNGCVPL (65): A' = 0x00–0x0F → buf1, + * A' = 0x10–0x1F → buf2, + * A' = 0x20–0x2F → buf3, + * A' = 0x30–0x3F → buf4 + * + * ТЕКСТОВЫЙ ЭКРАН WC: страница #00 (MTXPG) занимает 2 страницы: + * стр. #00 = символы + атрибуты (EGA-формат: [7:4]=bg, [3:0]=fg) + * стр. #01 = шрифт 8×8 (используется TSConfig text mode) + * Размеры: 80×25, 80×30 или 90×36 (HEI = 25/30/36) + * + * АДРЕСНОЕ ПРОСТРАНСТВО процессора (runtime плагина): + * #0000–#3FFF Окно 0: обычно FAT engine / буферы (page #05) + * ⚠ #0000–#1FFF гробится FindNext! + * ⚠ #1000–#1447 гробится GEDPL! + * #4000–#5FFF Окно 1: FAT engine (page #05), код #6000–#7FFF + * #6000–#602F Системная область WC (переменные, API entry point) + * #8000–#BFFF Окно 2: код плагина (page из PLGPG) + * #C000–#FFFF Окно 3: переключаемая (TXT/данные/видео) + * + * ══════════════════════════════════════════════════════════════════════ + * vDOS (Virtual DOS) — эмуляция TR-DOS на TSConfig + * ══════════════════════════════════════════════════════════════════════ + * + * vDOS обеспечивает совместимость с TR-DOS (Beta Disk Interface) на + * аппаратном уровне TSConfig FPGA. Состоит из: + * + * Страница #FF — vDOS Traps: + * Перехватывает обращения Z80 к стандартным адресам TR-DOS ROM + * (#3D13, #3D2F и др.) и эмулирует дисковые операции. + * + * Страница #FE — vDOS Drivers: + * Низкоуровневые драйверы виртуального дисковода: + * чтение/запись секторов, форматирование, позиционирование. + * + * Страницы #FC–#FD — vDOS Tables: + * Таблицы трансляции: соответствие виртуальных дорожек/секторов + * физическим секторам на SD/IDE. Каталоги дисков A–D. + * + * Страницы #F8–#FB — Virtual ROM (4 × 16 КБ = 64 КБ): + * Прошивка: Service ROM, DOS ROM, 128 ROM, 48 ROM. + * Переключение через биты DOS/ROM128 в page register окна 0. + * + * Страницы #C0–#E7 — vDOS RAM Disk (40 × 16 КБ = 640 КБ): + * RAM-диск для эмуляции дисководов. SCL-образы распаковываются + * сюда целиком. TRD-образы монтируются напрямую с FAT32. + * Страницы #E8–#EF — резерв (дополнительные дорожки). + * + * Инициализация vDOS: + * vDOS инициализируется автоматически при старте TSConfig (прошивка). + * Плагин не может и не должен напрямую управлять vDOS — только + * через плагин MOUNTER (монтирование/размонтирование образов). + * Регистр FDDVirt (#29AF) управляет виртуализацией FDD на уровне FPGA. + * + * ══════════════════════════════════════════════════════════════════════ + * FAT32 ENGINE — файловая подсистема WC + * ══════════════════════════════════════════════════════════════════════ + * + * FAT-движок (KERNEL.ASM + CORE32.ASM) располагается на странице #05 + * в адресах #4000–#5BFF (окно 1). Системная переменная FEP (#600B) + * хранит номер страницы FAT engine в окне #0000. + * + * Буферы FAT engine (адреса относительно окна 0, когда FEP=#05): + * #0000–#1FFF GENBU/NXTBU — общий буфер / буфер FindNext + * #3000–#31FF SECBU — секторный буфер (512 б) + * #3200–#33FF LOBU — буфер загрузки (512 б) + * #3400–#35FF LOBU2 — второй буфер загрузки (512 б) + * #3800+ DRVR — область драйвера устройства + * + * Jump Table FAT Engine (ORG #4000): + * +0 SELDEV (заглушка) + * +3 DEVINI — инициализация устройства + * +6 REINI (заглушка) + * +9 HDD — поиск FAT32 раздела + * +12 RDD — чтение секторов с позиционированием + * +15 SDD — запись секторов с позиционированием + * +18 GIPAG — позиционирование на первый кластер + * +21 LOAD512 — потоковое чтение (HL=addr, B=blocks) + * +24 SAVE512 — потоковая запись + * +27 DOS_SWP — выбор устройства + загрузка драйвера + * +30 GLSTCAT — установить активный каталог + * +33 TLSTCAT — получить кластер активного каталога + * +36 SRHFCL — найти свободный кластер + * +42 MKSG — создать FAT-цепочку + * +48 RFRH — обновить FSInfo + * +51 GENTRY — скопировать entry в буфер + * +54 TENTRY — скопировать entry из буфера + * +57 MKFILE — создать файл + * +60 MKDIR — создать каталог + * +63 DELFL — удалить файл/каталог + * +66 RENAME — переименовать + * +69 DLSG — удалить FAT-цепочку + * +72 CHTOSE — цепочка кластер→сектор + * +78 SRHDRN — поиск entry в каталоге + * +81 LOAD256 — чтение 256 байт + * +84 LOADNON — пропуск секторов + * +87 NXTETY — следующий entry (полный) + * +90 NXTETY2 — следующий entry (фильтрованный) + * + * FAT32 Directory Entry (32 байта, стандарт): + * +0 [11] — короткое имя 8.3 + * +11 [1] — EFLG: атрибуты (0x10=DIR, 0x20=ARCHIVE, ...) + * +14 [2] — время создания (FAT time format) + * +16 [2] — дата создания (FAT date format) + * +20 [2] — CLSDE: старшее слово кластера + * +22 [2] — дата модификации + * +24 [2] — время модификации + * +26 [2] — CLSHL: младшее слово кластера + * +28 [4] — SIZIK: размер файла (32-бит LE) + * + * ══════════════════════════════════════════════════════════════════════ + * ПОТОКОВАЯ ПОДСИСТЕМА (STREAM) + * ══════════════════════════════════════════════════════════════════════ + * + * Каждый поток — полный FAT32-контекст (устройство, раздел, каталог). + * WC поддерживает 4 потока: + * + * Страница #F0 (PNLPG) — поток ЛЕВОЙ панели + * Страница #F1 — поток ПРАВОЙ панели + * Страница #F2 (INIPG) — поток плагина #00 / INI-файл + * Страница #F3 (STRPG) — поток плагина #01 + * + * Типичный сценарий работы с файлами из плагина: + * + * // 1. Клонировать поток панели → потоки #00 и #01 + * wc_stream(WC_STREAM_CLONE); // D=#FE + * + * // 2. Перейти в root + * wc_stream(WC_STREAM_ROOT); // D=#FF + * + * // 3. Найти файл + * static uint8_t buf[] = { WC_FILE_FLAG, 'm','y','f','i','l','e','.','t','x','t', 0 }; + * if (wc_fentry((uint16_t)buf)) { + * wc_gfile(); // открыть файл + * wc_load512(0xC000, 32); // прочитать + * } + * + * Устройства (код в регистре B при создании потока): + * 0 — SD (Z-Controller) + * 1 — IDE Nemo Master + * 2 — IDE Nemo Slave + * 3 — SD (NeoGS) + * 4 — IDE SMUC Master + * 5 — IDE SMUC Slave + * 6 — SD2 (Z-Controller) + * + * ⚠ GDIR нельзя вызывать на потоке активной панели! + * ⚠ MKfile/MKdir/LOAD512/SAVE512/FENTRY разрушают кеш FindNext! + * ⚠ GIPAGPL можно использовать со STREAM только после клонирования! + * + * ══════════════════════════════════════════════════════════════════════ + * КАТАЛОГ ПАНЕЛИ (CAT) — внутренняя структура WC + * ══════════════════════════════════════════════════════════════════════ + * + * Каждая панель хранит каталог до 4095 файлов в 9 страницах RAM: + * Левая: стр. #0B–#13 (CTZPG), позиции: стр. #1E (CPPG1) + * Правая: стр. #14–#1C, позиции: стр. #1F (CPPG2) + * + * INDEX entry (4 байта): + * +0 [1] — Логическая страница (0–8) + * +1 [2] — Адрес в странице (#C000–#FDFF) + * +3 [1] — Длина имени (без заголовка и терминатора) + * + * DATA entry (переменная длина): + * +0 [1] — Mark flags (бит 0: %1=помечен, %0=нет) + * +1 [3] — Расширение (без точки, верхний регистр) + * +4 [4] — Первый кластер FAT32 + * +8 [4] — Размер файла + * +12 [2] — Дата (FAT формат) + * +14 [2] — Время (FAT формат) + * +16 [1] — Flag: #10=файл, #00=каталог (инвертировано для сортировки) + * +17 [1–255]— Имя файла ("file.bin" / "megadir") + * +xx [1] — Терминатор #00 + * + * ══════════════════════════════════════════════════════════════════════ + * ДРАЙВЕРЫ УСТРОЙСТВ + * ══════════════════════════════════════════════════════════════════════ + * + * Драйвер загружается в область DRVR (#3800) FAT engine. + * Jump Table драйвера: + * +0 SELDEV — выбор устройства (master/slave) + * +3 DEVINI — инициализация + * +6 REINI — переинициализация + * +9 XPOZI — установить позицию (LBA) + * +12 PROZ — альтернативная позиция + * +15 RDDSE — чтение секторов (HL=addr, A=count) + * +18 SDDSE — запись секторов (HL=addr, A=count) + * +21 RDD256 — чтение 256 байт + * + * SD Card порты (Z-Controller): CONF=#77, DATA=#57 + * IDE SMUC порты: CMD=#FFBE, DRV=#FEBE, + * CYLH=#FDBE, CYLL=#FCBE, SECR=#FBBE + * + * ══════════════════════════════════════════════════════════════════════ + * АППАРАТНЫЕ РЕГИСТРЫ TSConfig (частые) + * ══════════════════════════════════════════════════════════════════════ + * + * #00AF Vmod — видеорежим (RRES[7:6] | NOGFX | NOTSU | TYPE[1:0]) + * #01AF Vpag — видеостраница + * #07AF Vpal — индекс палитры + * #0FAF BORDER — цвет бордюра + * #10AF PW0 — страница окна #0000 + * #11AF PW1 — страница окна #4000 + * #12AF PW2 — страница окна #8000 + * #13AF PW3 — страница окна #C000 + * #15AF Vfm — режим шрифта + * #16AF Vtm — страница Tile Map + * #17AF Vtl0 — Tile Layer 0 + * #18AF Vtl1 — Tile Layer 1 + * #19AF Vsgp — Sprite Graphics page + * #1AAF DMASL — DMA Source Low + * #1BAF DMASH — DMA Source High + * #1CAF DMASX — DMA Source Page + * #1DAF DMADL — DMA Dest Low + * #1EAF DMADH — DMA Dest High + * #1FAF DMADX — DMA Dest Page + * #20AF SYC — System Config (turbo bits [1:0]) + * #26AF DMA_N — DMA burst size + * #27AF DMA_C — DMA command / status + * #28AF DMA_T — DMA burst count + * #29AF FDDVirt — FDD Virtualization (vDOS) + * #2BAF CYC — Cache Config (per-window enable) + */ +typedef struct { + uint8_t type; /* +0: стиль+флаги (WC_WIN_*) */ + uint8_t cur_mask; /* +1: маска цвета курсора */ + uint8_t x; /* +2: позиция X в символах (255=центр) */ + uint8_t y; /* +3: позиция Y в символах (255=центр) */ + uint8_t width; /* +4: ширина (0=весь экран) */ + uint8_t height; /* +5: высота (0=весь экран) */ + uint8_t color; /* +6: атрибут цвета (EGA: bg[7:4]|fg[3:0]) */ + uint8_t _reserved; /* +7: ЗАРЕЗЕРВИРОВАНО (всегда 0!) */ + uint16_t buf_addr; /* +8: адрес буфера фона (0xFFFF=нет) */ + uint8_t divider1; /* +10: Y-смещение разделит. линии 1 от низа окна (0=нет) */ + uint8_t divider2; /* +11: Y-смещение разделит. линии 2 от низа окна (0=нет) */ + uint8_t _ext[12]; /* +12–15: расширение (заголовок/курсор) */ +} wc_window_t; + +/* ══════════════════════════════════════════════════════════════════════ + * Вспомогательный макрос инициализации окна + * ══════════════════════════════════════════════════════════════════════ + * Пример: + * static wc_window_t my_win = WC_WIN_INIT(1, 0, 5, 5, 30, 10, 0x47, 0xFFFF); + * (border=1, x=5, y=5, w=30, h=10, color=bright white on black, no buf) + */ +/* WC_WIN_INIT: инициализация структуры окна. + * Поля +12..+15 (_ext) = 0 → подходит для типов без курсора (SINGLE, DOUBLE). + * Для типов 0/3/6 (с курсором) нужно вручную инициализировать _ext[0..3]. + */ +#define WC_WIN_INIT(style, flags, _x, _y, _w, _h, _color, _buf) \ + { (uint8_t)((style) | (flags)), 0x07, (_x), (_y), (_w), (_h), \ + (_color), 0, (_buf), 0, 0, {0,0,0,0,0,0,0,0,0,0,0,0} } + +/* ══════════════════════════════════════════════════════════════════════ + * INIT / СТРАНИЧНОЕ УПРАВЛЕНИЕ + * ══════════════════════════════════════════════════════════════════════ */ + +/** + * Переключить страницу плагина на адрес #C000. + * @param page номер страницы плагина: + * 0x00–0x3F — страницы RAM плагина + * 0xFF — страница с шрифтом WC (не использовать #E000–#FFFF!) + * 0xFE — первый текстовый экран WC + * Остальные страницы #8000 и #0000 при этом НЕ изменяются. + */ +void wc_mngc_pl(uint8_t page); + +/** Отобразить страницу плагина (page) на адрес #0000 */ +void wc_mng0_pl(uint8_t page); + +/** Отобразить страницу плагина (page) на адрес #8000 */ +void wc_mng8_pl(uint8_t page); + +/* ══════════════════════════════════════════════════════════════════════ + * UI / ОКНА + * ══════════════════════════════════════════════════════════════════════ */ + +/** + * Восстановить дисплей WC: палитру, смещения, текстовый режим. + * + * Обязательно вызывать при старте плагина, прежде чем вызывать + * любые функции вывода окон (wc_prwow и т.д.). + * + * ⚠ ВНИМАНИЕ: ГРОБИТ область #1000–#1447 на странице, отображённой + * на #0000! Сохранить данные в этом диапазоне до вызова! + * + * НЕ переключает страницу в окне #0000–#3FFF. + * НЕ переключает страницу в окне #C000. + */ +void wc_gedpl(void); + +/** + * Нарисовать окно win на экране (сохранить фон в buf_addr). + * + * ⚠ Включает основной текстовый экран на #C000 и оставляет его + * включённым после возврата. Вызывать при 3.5 МГц! + * + * Структура win и строки заголовков должны находиться в #8000–#BFFF + * или #0000–#3FFF (не в #C000–#FFFF!). + * + * После первого вызова WC записывает реальный адрес буфера в win->buf_addr + * (если был 0). При buf_addr=0xFFFF фон не сохраняется. + */ +void wc_prwow(wc_window_t *win); + +/** + * Удалить окно win с экрана (восстановить фон из buf_addr). + * + * ⚠ Включает основной текстовый экран на #C000 и оставляет его + * включённым после возврата. Вызывать при 3.5 МГц! + */ +void wc_rresb(wc_window_t *win); + +/** + * Напечатать строку str в окне win. + * + * ⚠ Включает основной текстовый экран на #C000 и оставляет его + * включённым после возврата. + * + * @param y строка внутри окна (1-based от верха содержимого) + * @param x столбец внутри окна (1-based) + * @param len длина строки в символах + * + * Адрес win и строки str должны быть в #8000–#BFFF или #0000–#3FFF. + */ +void wc_prsrw(wc_window_t *win, const char *str, uint8_t y, uint8_t x, uint16_t len); + +/** + * Напечатать строку str в окне win c атрибутом цвета attr (EGA-формат: bg[7:4] | fg[3:0]). + * + * ⚠ Включает основной текстовый экран на #C000 и оставляет его + * включённым после возврата. + * + * @param y строка внутри окна (1-based от верха содержимого) + * @param x столбец внутри окна (1-based) + * @param len длина строки в символах + * @param attr EGA-атрибут: bg[7:4] | fg[3:0] + * + * Адрес win и строки str должны быть в #8000–#BFFF или #0000–#3FFF. + */ +void wc_prsrw_attr(wc_window_t *win, const char *str, uint8_t y, uint8_t x, uint16_t len, uint8_t attr); + +/** + * Вернуть физический адрес ячейки символа (y, x) внутри окна win. + * + * ⚠ Включает основной текстовый экран на #C000 и оставляет его + * включённым после возврата. + * + * @param y строка (1-based) + * @param x столбец (1-based) + * @return адрес байта символа в видеобуфере + */ +uint16_t wc_gadrw(wc_window_t *win, uint8_t y, uint8_t x); + +/** + * Нарисовать курсор в текущей позиции окна win. + * + * ⚠ Включает основной текстовый экран на #C000 и оставляет его + * включённым после возврата. + */ +void wc_cursor(wc_window_t *win); + +/** + * Стереть курсор (восстановить исходный символ/атрибут). + * + * ⚠ Включает основной текстовый экран на #C000 и оставляет его + * включённым после возврата. + */ +void wc_curser(wc_window_t *win); + +/** + * Меню выбора Да/Нет или ОК/Отмена. + * + * Работа в цикле (вызывать раз в кадр): + * 1. wc_yn(WC_YN_OK_CANCEL) — инициализация меню "ОК / Отмена" + * 2. wc_yn(WC_YN_YES_NO) — инициализация меню "Да / Нет" + * 3. В цикле: wc_yn(WC_YN_POLL) — опросить и перерисовать кнопки + * (вызывать каждый кадр, пока не будет нажата клавиша) + * 4. wc_yn(WC_YN_EXIT) — закрыть меню (скрыть кнопки) + * + * После WC_YN_POLL: + * Z=1 → нажато ОК / Да (A = 0) + * Z=0 → нажато Отмена / Нет (A != 0) + * Если клавиша ещё не нажата — никакого результата нет, + * нужно продолжать вызывать WC_YN_POLL в следующих кадрах. + * + * @param mode WC_YN_OK_CANCEL (0x01), WC_YN_YES_NO (0x02), + * WC_YN_POLL (0x00), WC_YN_EXIT (0xFF) + * @return 0 = ОК/Да нажато или ещё не нажато (Z=1), + * ненулевое = Отмена/Нет (Z=0) + */ +uint8_t wc_yn(uint8_t mode); + +/** + * Встроенный редактор строки ввода. + * + * Имеет несколько режимов работы (передаются как mode): + * + * 0xFF — инициализировать тип 0: редактировать экранный буфер. + * win=SOW, DE=CURMAX<<8|CURNOW (макс.длина и текущая позиция). + * Рисует курсор. Дальнейшие вызовы: 0x00 (опрос). + * + * 0x00 — опросить клавиатуру. Вызывать раз в кадр. + * Возврат: A=0 — продолжает ввод, A!=0 — завершён. + * HL = текущий адрес курсора на экране. + * + * 0x01 — завершить ввод: стереть курсор. + * HL = адрес курсора на экране. + * + * 0xFD — инициализировать тип 2: редактировать буфер в памяти. + * HL=addr буфера, HL'=размер буфера, + * D=Y, E=X (позиция на экране), + * BC=CURMAX<<8|CURNOW. + * + * 0x02 — перерисовать блок редактора (обновить экран из буфера). + * + * 0xFE — инициализировать индикаторы (CAPS/INS/Lang/OVR). + * H=0: нижний заголовок, H=1: верхний. + * + * 0x03 — обновить индикаторы (вызывать раз в кадр). + * + * 0xFC — опросить состояние Caps/Ins/Ctrl+Shift. + * Возврат A: [7]=OVR, [6]=CAPS, [1:0]=язык. + * + * ⚠ НЕЛЬЗЯ одновременно вызывать 0x00 и 0xFC в одном кадре! + * + * @param win SOW-структура окна + * @param mode режим (0xFF, 0x00, 0x01, 0xFD, 0x02, 0xFE, 0x03, 0xFC) + */ +void wc_istr(wc_window_t *win, uint8_t mode); + +/** + * Записать байт val в виде 2 шестнадцатеричных символов в экранный буфер. + * @param addr адрес в видеопамяти (в текущем экранном буфере) + * @param val значение, которое нужно отобразить как HEX (00–FF) + * + * Используется, например, для вывода шестнадцатеричного редактора. + */ +void wc_nork(uint16_t addr, uint8_t val); + +/** + * Напечатать форматированный текст str в окне win начиная с (y, x). + * + * ⚠ Включает основной текстовый экран на #C000 и оставляет его + * включённым после возврата. Вызывать при 3.5 МГц! + * + * Текст может содержать управляющие коды (см. раздел «Текстовая разметка» + * в заголовке файла): #0D (перенос строки), #0E (центрирование), + * #0B,XX (сдвиг вправо), #0C,YY (сдвиг вниз), #0x01-08 (цвета), + * #09 (инверсия), #FE,addr (ссылка). + * + * Структура win и строка str должны быть в #8000–#BFFF или #0000–#3FFF. + * + * @param y начальная строка в окне (1-based) + * @param x начальный столбец в окне (1-based) + * @return номер строки, следующей за последней напечатанной + */ +uint8_t wc_txtpr(wc_window_t *win, const char *str, uint8_t y, uint8_t x); + +/** + * Напечатать n-е сообщение из блока текста str. + * + * ⚠ Включает основной текстовый экран на #C000 и оставляет его + * включённым после возврата. Вызывать при 3.5 МГц! + * + * Блок состоит из абзацев, разделённых двойным нулём (0x00 0x00). + * msg_num задаёт, который по счёту абзац использовать (0-based). + * + * @param msg_num номер абзаца (0-based) в блоке текста + * @param str адрес начала текстового блока + * @param y начальная строка (1-based) + * @param x начальный столбец (1-based) + * @return номер строки после последней напечатанной + */ +uint8_t wc_mezz(wc_window_t *win, uint8_t msg_num, const char *str, uint8_t y, uint8_t x); + +/** + * Прокрутить прямоугольную область внутри окна. + * + * ⚠ Включает основной текстовый экран на #C000 и оставляет его + * включённым после возврата. Вызывать при 3.5 МГц! + * + * Если step=0 и флаги [7:2] = 0 — автоматически используется DMA. + * + * @param y верхняя строка области (1-based) + * @param x левый столбец области (1-based) + * @param h высота области в строках + * @param w ширина области в символах + * @param flags битовые флаги: + * [7] = 1 → прокручивать атрибуты вместе с символами + * [6] = 1 → очистить исходную позицию после прокрутки + * [5:2] = шаг прокрутки (0=1 символ, 0xF=16 символов) + * [1:0] = направление: 00=вниз, 01=вверх, 10/11=зарезерв. + * При [7:2]=0 и step=0 DMA задействуется автоматически. + */ +void wc_scrlwow(wc_window_t *win, uint8_t y, uint8_t x, uint8_t h, uint8_t w, uint8_t flags); + +/* ══════════════════════════════════════════════════════════════════════ + * КЛАВИАТУРА + * Все функции возвращают 0 = не нажато, ненулевое = нажато (или код клавиши). + * Автоповтор: у Space, стрелок, Tab, Enter, Bspc, F1-F10, Pgup/Pgdn, Home, End. + * Без автоповтора: Alt, Shift, Ctrl. + * ══════════════════════════════════════════════════════════════════════ */ + +uint8_t wc_key_space(void); /* Space / Пробел */ +uint8_t wc_key_up(void); /* Стрелка вверх */ +uint8_t wc_key_down(void); /* Стрелка вниз */ +uint8_t wc_key_left(void); /* Стрелка влево */ +uint8_t wc_key_right(void); /* Стрелка вправо */ +uint8_t wc_key_tab(void); /* Tab */ +uint8_t wc_key_enter(void); /* Enter */ +uint8_t wc_key_esc(void); /* Escape */ +uint8_t wc_key_bspc(void); /* Backspace */ +uint8_t wc_key_pgup(void); /* Page Up */ +uint8_t wc_key_pgdn(void); /* Page Down */ +uint8_t wc_key_home(void); /* Home */ +uint8_t wc_key_end(void); /* End */ +uint8_t wc_key_f1(void); /* F1 */ +uint8_t wc_key_f2(void); +uint8_t wc_key_f3(void); +uint8_t wc_key_f4(void); +uint8_t wc_key_f5(void); +uint8_t wc_key_f6(void); +uint8_t wc_key_f7(void); +uint8_t wc_key_f8(void); +uint8_t wc_key_f9(void); +uint8_t wc_key_f10(void); /* F10 */ +uint8_t wc_key_alt(void); /* Alt (без автоповтора) */ +uint8_t wc_key_shift(void); /* Shift (без автоповтора) */ +uint8_t wc_key_ctrl(void); /* Ctrl (без автоповтора) */ + +/** + * Сканировать клавиатуру, вернуть код нажатой клавиши. + * @param mode WC_KBSCN_NORMAL (с учётом SHIFT/CL/Lang) или WC_KBSCN_RAW + * @return код клавиши, 0 = ничего не нажато + */ +uint8_t wc_kbscn(uint8_t mode); + +uint8_t wc_key_del(void); /* Delete */ +uint8_t wc_key_caps(void); /* Caps Lock */ +uint8_t wc_key_any(void); /* Любая клавиша */ +void wc_key_wait_release(void); /* Ждать отпускания всех клавиш */ +uint8_t wc_key_wait_any(void); /* Ждать нажатия любой клавиши */ +uint8_t wc_key_ins(void); /* Insert */ + +/* ══════════════════════════════════════════════════════════════════════ + * ФАЙЛОВЫЕ ОПЕРАЦИИ + * ══════════════════════════════════════════════════════════════════════ */ + +/** + * Загрузить blocks блоков по 512 байт из текущего потока в dest. + * + * @param dest адрес буфера назначения + * @param blocks количество блоков (blocks * 512 = байт) + * @return A = 0 — ОК, продолжение + * A = WC_EOF (0x0F) — нормальный конец цепочки FAT + * (на последней секторной загрузке!) + * A = другое — реальная ошибка чтения + * + * HL при возврате = новое значение dest (указывает на следующий байт + * после загруженных данных). + * + * Пример проверки ошибок: + * rc = wc_load512(addr, n); + * if (rc && rc != WC_EOF) { } // I/O error + */ +uint8_t wc_load512(uint16_t dest, uint8_t blocks); + +/** + * Сохранить blocks блоков по 512 байт из src в текущий поток. + * + * HL при возврате = новое значение src (указывает на следующий байт + * после сохранённых данных). + * + * @param src адрес буфера источника + * @param blocks количество блоков + */ +void wc_save512(uint16_t src, uint8_t blocks); + +/** + * Перейти к началу файла плагина (позиционировать поток на первый блок). + * Используется после открытия файла WC для повторного чтения. + */ +void wc_gipagpl(void); + +/** Прочитать ENTRY (32 байта) файла в буфер по адресу addr */ +void wc_tentry(uint16_t addr); + +/** + * Обойти цепочку секторов, записать в buf список секторов. + * @param buf адрес буфера назначения + * @param bufend адрес конца буфера + */ +void wc_chtosep(uint16_t buf, uint16_t bufend); + +/** + * Получить заголовок помеченного файла. + * @param panel IX = структура панели WC + * @param filenum номер файла в каталоге + * @param namebuf буфер для имени файла + */ +void wc_tmrkdfl(uint16_t panel, uint16_t filenum, uint16_t namebuf); + +/** + * Управление директорией / позиционирование. + * @param mode WC_ADIR_SEEK_START или WC_ADIR_RESET_NEXT + */ +void wc_adir(uint8_t mode); + +/** + * Открыть/переключить файловый поток. + * + * Параметр mode задаёт тип потока (передаётся внутри в регистр D): + * WC_STREAM_ROOT (0xFF) — корневой поток FAT + * WC_STREAM_CLONE (0xFE) — клонировать текущий поток + * WC_STREAM_WCDIR (0xFD) — каталог WC + * 0x00 / 0x01 — номер потока 0 или 1 + * + * Если BC = 0xFFFF --- только переключить поток без пересоздания. + * Если BC ≠ 0xFFFF --- создать или пересоздать поток. + * (BC сейчас захардкожено в wc_api.s; уточните, если нужен другой режим.) + * + * @param mode один из WC_STREAM_* или 0/1 + */ +void wc_stream(uint8_t mode); + +/** + * Найти следующий файл/каталог в текущей директории. + * + * ⚠ ВНИМАНИЕ: ГРОБИТ область #0000–#1FFF на странице с драйвером! + * Не вызывать, если в этом диапазоне хранятся важные данные! + * + * @param entry_buf адрес буфера для заголовка файла (32+ байт) + * @param flags фильтр: + * [7] = 1 → только короткие имена + * [4] = 1 → загрузить время создания + * [3] = 1 → загрузить дату создания + * [2] = 1 → загрузить размер файла + * [1:0] = 00 → все объекты; 01 → только файлы; + * 10 → только каталоги + * @return 0 (Z=1) = конец директории; + * ненулевое = найден файл/каталог + */ +uint8_t wc_findnext(uint16_t entry_buf, uint8_t flags); + +/** + * Найти файл по имени в текущей директории. + * @param name_with_flag адрес строки вида: [1 байт флагов][имя][0] + * @return 0 = не найден, ненулевое = найден; после вызова DE=entry, HL=size + */ +uint8_t wc_fentry(uint16_t name_with_flag); + +/** Пропустить n секторов по 512 байт в текущем потоке */ +void wc_loadnone(uint8_t sectors); + +/** + * Открыть файл, найденный последним wc_fentry(). + * Позиционирует поток чтения на найденный файл. + */ +void wc_gfile(void); + +/** + * Войти в каталог, найденный последним wc_fentry(). + */ +void wc_gdir(void); + +/** + * Создать файл. + * @param name_with_flag [1 байт флагов][длина][имя][0] + * @return 0 = OK, ненулевое = ошибка + */ +uint8_t wc_mkfile(uint16_t name_with_flag); + +/** + * Создать каталог. + * @param name указатель на имя каталога (ZS-строка) + * @return 0 = OK, ненулевое = ошибка + */ +uint8_t wc_mkdir(uint16_t name); + +/** + * Переименовать файл/каталог. + * @param old_name [1 байт флагов][старое имя][0] + * @param new_name [новое имя][0] + * @return 0 = OK (Z), ненулевое = ошибка + */ +uint8_t wc_rename(uint16_t old_name, uint16_t new_name); + +/** + * Удалить файл. + * @param name_with_flag [1 байт флагов][имя][0] + * @return 0 = OK (Z), ненулевое = ошибка + */ +uint8_t wc_delfl(uint16_t name_with_flag); + +/* ══════════════════════════════════════════════════════════════════════ + * ГРАФИКА + * ══════════════════════════════════════════════════════════════════════ */ + +/** + * Переключить видеорежим. + * @param mode WC_VIDEO_TXT / WC_VIDEO_BUF1..4 + */ +void wc_mngv_pl(uint8_t mode); + +/** + * Отобразить видеостраницу vpage на адрес #C000. + * @param vpage номер страницы (0x00–0x3F) + */ +void wc_mngcvpl(uint8_t vpage); + +/** + * Установить видеорежим TSConfig. + * @param mode байт VConfig (TSCONF_VM_* | TSCONF_RRES_* | ...) + */ +void wc_gvmod(uint8_t mode); + +/** Установить Y-смещение прокрутки графики */ +void wc_gyoff(uint16_t y); + +/** Установить X-смещение прокрутки графики */ +void wc_gxoff(uint16_t x); + +/** Установить страницу тайловой карты */ +void wc_gvtm(uint8_t page); + +/** + * Установить страницу графики тайловой плоскости. + * @param plane 0 = плоскость 0, 1 = плоскость 1 + * @param page номер страницы + */ +void wc_gvtl(uint8_t plane, uint8_t page); + +/** Установить страницу графики спрайтов */ +void wc_gvsgp(uint8_t page); + +/** Отобразить видеостраницу vpage на адрес #0000 */ +void wc_mng0vpl(uint8_t vpage); + +/** Отобразить видеостраницу vpage на адрес #8000 */ +void wc_mng8vpl(uint8_t vpage); + +/* ══════════════════════════════════════════════════════════════════════ + * DMA + * ══════════════════════════════════════════════════════════════════════ */ + +/** + * Настройка и запуск DMA TSConfig. + * + * Подфункции DMA (передаются как subfunc): + * + * Конфигурация: + * 0x00 — задать src+dst: B=src_page, HL=src_addr, C=dst_page, DE=dst_addr + * 0x01 — задать только src + * 0x02 — задать только dst + * 0x03 — src из координат окна (адрес → окно WC) + * 0x04 — dst из координат окна + * 0x05 — задать DMA_T (тип тайла/спрайта) + * 0x06 — задать DMA_N (число объектов) + * 0x07 — задать T + N + * 0x08 — выравнивание источника + * 0x09 — выравнивание назначения + * 0x0A — выравнивание размера + * 0x0B — задать все параметры + * + * Запуск: + * 0xFC — RAM→RAM (с ожиданием завершения) + * 0xFB — BLT→RAM + * 0xFA — FILL (заполнение) + * 0xF9 — RAM→CRAM (палитра) + * 0xF8 — RAM→SFILE (спрайты) + * 0xFD — запустить без ожидания (следует вызвать 0xFF для синхронизации) + * 0xFE — запустить с ожиданием + * 0xFF — ждать готовности DMA (busy-wait) + * + * Обычная последовательность: + * // Настройка src/dst выполняется перед вызовом через регистры SDCC + * wc_dmapl(0xFC); // запустить и подождать + */ +void wc_dmapl(uint8_t subfunc); + +/* ══════════════════════════════════════════════════════════════════════ + * РАЗНОЕ + * ══════════════════════════════════════════════════════════════════════ */ + +/** + * Управление тактовой частотой CPU или AY через WC. + * + * WC управляет своими собственными настройками турбо и НЕ влияет + * на отображение экрана. Никаких конфликтов с WC по выбору частоты нет. + * WC запоминает выбранный режим и восстанавливает его при 0xFF. + * + * @param mode WC_TURBO_CPU (0x00) — выбрать частоту процессора: + * param = 0: 3.5 МГц + * param = 1: 7 МГц + * param = 2: 14 МГц ← для быстрых вычислений + * param = 3: 28 МГц + * WC_TURBO_AY (0x01) — выбрать частоту AY: + * param = 0: 1.750 МГц + * param = 1: 1.773 МГц + * param = 2: 3.5 МГц + * param = 3: 3.546 МГц + * WC_TURBO_RESTORE (0xFF) — восстановить режимы CPU+AY + * из настроек WC (игнорирует param) + * + * @param param параметр частоты (0–3); игнорируется при RESTORE + */ +void wc_turbopl(uint8_t mode, uint8_t param); + +/** + * Получить параметр из INI-файла плагина. + * @param param_num номер параметра + * @return номер опции (0-based), 0xFF если параметр не задан + */ +uint8_t wc_prm_pl(uint8_t param_num); + +/** + * Управление прерываниями WC. + * + * ⚠ По выходу из плагина (возврат из main) WC автоматически + * восстанавливает все параметры INT, установленные этой функцией! + * + * @param mode WC_INT_DISABLE_ALL (0x00) — отключить все прерывания WC + * WC_INT_NO_TIME (0x01) — отключить таймерное INT + * WC_INT_NO_PS2 (0x02) — отключить PS/2 INT + * WC_INT_PLUGIN (0xFF) — установить свой обработчик; + * в этом случае использовать wc_int_pl_handler(addr) + */ +void wc_int_pl(uint8_t mode); + +/** + * Установить ASM-обработчик прерываний плагина. + * Вызывать после wc_int_pl(WC_INT_PLUGIN). + * + * Обработчик должен заканчиваться на RET (не RETI). + * Всё, что нужно сохранить — регистры; стек WC НЕ менять. + * + * @param handler_addr адрес ASM-обработчика в памяти плагина. + */ +void wc_int_pl_handler(uint16_t handler_addr); + +/* ══════════════════════════════════════════════════════════════════════ + * ВСПОМОГАТЕЛЬНЫЕ МАКРОСЫ + * ══════════════════════════════════════════════════════════════════════ */ + +/** + * Атрибут цвета (EGA-формат): bg[7:4] | fg[3:0] + * @param bg цвет фона (0–15) + * @param fg цвет текста (0–15) + * Пример: WC_COLOR(WC_BLUE, WC_BRIGHT_WHITE) = 0x1F + */ +#define WC_COLOR(bg, fg) ((uint8_t)(((bg) << 4) | (fg))) + +/* 16 цветов (YGRB: Bright, Green, Red, Blue) */ +#define WC_BLACK 0 +#define WC_BLUE 1 +#define WC_RED 2 +#define WC_MAGENTA 3 +#define WC_GREEN 4 +#define WC_CYAN 5 +#define WC_YELLOW 6 +#define WC_WHITE 7 +#define WC_DARK_GRAY 8 +#define WC_BRIGHT_BLUE 9 +#define WC_BRIGHT_RED 10 +#define WC_BRIGHT_MAGENTA 11 +#define WC_BRIGHT_GREEN 12 +#define WC_BRIGHT_CYAN 13 +#define WC_BRIGHT_YELLOW 14 +#define WC_BRIGHT_WHITE 15 + +/* ══════════════════════════════════════════════════════════════════════ + * Параметры плагина (заполняются crt0.s при вызове плагина WC) + * ══════════════════════════════════════════════════════════════════════ + * + * Передаются WC через регистры: + * A' → wc_file_ext (индекс расширения в списке WC) + * BC → wc_file_name (указатель на z-строку с именем файла) + * HL → wc_file_size (младшее слово, 16 бит) + * DE → wc_file_size+2 (старшее слово, 16 бит; для файлов > 64 КБ) + * + * wc_file_size занимает 4 байта в _DATA (32-bit LE): + * lo = *(uint16_t *)&wc_file_size + * hi = *((uint16_t *)&wc_file_size + 1) + */ +extern uint8_t wc_file_ext; /* индекс расширения */ +extern char * wc_file_name; /* char *: указатель на имя файла */ +extern uint32_t wc_file_size; /* 32-bit LE: [+0]=lo, [+2]=hi */ + +/* Код возврата плагина: запись в wc_exit_code перед return из main() */ +extern uint8_t wc_exit_code; + +/** Номер текущего объекта в панели (от 1). Передаётся WC в HL'. */ +extern uint16_t wc_file_idx; +/** Количество объектов в панели (включая ".."). Передаётся WC в DE'. + * Если wc_file_idx == wc_file_count — это последний файл. */ +extern uint16_t wc_file_count; + +#define WC_EXIT_ESC 0 /* закрыть плагин / выйти (стоп) */ +#define WC_EXIT_UNRECOGNIZED 1 /* файл не распознан → другому плагину */ +#define WC_EXIT_NEXT 2 /* перейти к следующему файлу */ +#define WC_EXIT_RELOAD_DIR 3 /* перечитать каталог WC */ +#define WC_EXIT_PREV 4 /* перейти к предыдущему файлу */ + +/* ══════════════════════════════════════════════════════════════════════ + * СИСТЕМНЫЕ ПЕРЕМЕННЫЕ — inline чтение через volatile-указатели + * ══════════════════════════════════════════════════════════════════════ + * + * Эти макросы читают системную область WC (#6000+) напрямую: + */ + +/** Текущая страница в окне 0 (#0000–#3FFF) */ +#define wc_get_pg0() (*(volatile uint8_t *)WC_SYS_PG0) +/** Текущая страница в окне 1 (#4000–#7FFF) */ +#define wc_get_pg4() (*(volatile uint8_t *)WC_SYS_PG4) +/** Текущая страница в окне 2 (#8000–#BFFF) */ +#define wc_get_pg8() (*(volatile uint8_t *)WC_SYS_PG8) +/** Текущая страница в окне 3 (#C000–#FFFF) */ +#define wc_get_pgc() (*(volatile uint8_t *)WC_SYS_PGC) + +/** Флаг ESC (ABT): 1 = пользователь нажал ESC */ +#define wc_get_abt() (*(volatile uint8_t *)WC_SYS_ABT) +/** Флаг ENTER (ENT): 1 = пользователь нажал ENTER */ +#define wc_get_ent() (*(volatile uint8_t *)WC_SYS_ENT) + +/** Таймер INT (инкрементируется каждым прерыванием) */ +#define wc_get_timer() (*(volatile uint16_t *)WC_SYS_TMN) + +/** Страница FAT engine в окне 0 */ +#define wc_get_fep() (*(volatile uint8_t *)WC_SYS_FEP) + +/** Конфигурация видео (CNFv): 0=PWM, 1=3bit, ..., 7=VDAC2 */ +#define wc_get_cnfv() (*(volatile uint8_t *)WC_SYS_CNFV) + +/** Высота экрана в строках (25, 30 или 36) */ +#define wc_get_height() (*(volatile uint8_t *)WC_SYS_HEI) + +/** Текстовый режим (TXTmd) */ +#define wc_get_txtmode() (*(volatile uint8_t *)WC_SYS_TXTMD) + +/** SD1 driver state */ +#define wc_get_sd1_state() (*(volatile uint8_t *)WC_SYS_SDBSF) +/** SD2 driver state */ +#define wc_get_sd2_state() (*(volatile uint8_t *)WC_SYS_SDBSF2) + +/* ══════════════════════════════════════════════════════════════════════ + * ВСПОМОГАТЕЛЬНЫЕ МАКРОСЫ ДЛЯ FAT + * ══════════════════════════════════════════════════════════════════════ */ + +/** + * Собрать 32-битный кластер из двух 16-битных полей FAT entry. + * @param hi cluster_hi (CLSDE, +20) + * @param lo cluster_lo (CLSHL, +26) + */ +#define WC_FAT_CLUSTER(hi, lo) ((uint32_t)(hi) << 16 | (uint32_t)(lo)) + +/** + * Построить буфер с флагом для wc_fentry / wc_mkfile / wc_delfl. + * Пример: + * static uint8_t buf[] = WC_FAT_NAME(WC_FILE_FLAG, "readme.txt"); + * Раскрывается в: { 0x00, 'r','e','a','d','m','e','.','t','x','t', 0 } + */ +#define WC_FAT_NAME(flag, name) { (flag), name "\0" } + +/** + * Построить буфер для wc_mkfile (flag + size + name). + * Пример: + * static uint8_t buf[32]; + * wc_mkfile_buf(buf, 1024, "output.bin"); + */ +#define wc_mkfile_buf(buf, sz, nm) do { \ + (buf)[0] = WC_FILE_FLAG; \ + *(uint32_t*)&(buf)[1] = (uint32_t)(sz); \ + uint8_t _i = 0; \ + while ((nm)[_i]) { (buf)[5 + _i] = (nm)[_i]; _i++; } \ + (buf)[5 + _i] = 0; \ +} while(0) + +/* ══════════════════════════════════════════════════════════════════════ + * РЕЗИДЕНТНЫЙ ПЕРЕХОД / ВЫЗОВ (для многостраничных плагинов) + * ══════════════════════════════════════════════════════════════════════ + * + * Для плагинов, занимающих несколько страниц в #8000–#BFFF: + * + * Резидент перехода (#6020): + * A = номер страницы плагина (от 0) + * HL = адрес перехода (#8000–#BFFF) + * Выполняет: переключение страницы в окне 2, JP (HL) + * + * Резидент вызова (#6028): + * A = номер страницы плагина (от 0) + * HL = адрес вызова (#8000–#BFFF) + * Выполняет: переключение, CALL (HL), возврат обратно + * Сохраняет все регистры кроме A и HL! + * + * Пример: + * // Вызвать функцию load_data на странице 2 плагина + * wc_resident_call(2, (uint16_t)&load_data); + */ + +/** Перейти (JP) в код на другой странице плагина (без возврата!) */ +void wc_resident_jump(uint8_t page, uint16_t addr); + +/** Вызвать (CALL) код на другой странице плагина (с возвратом) */ +void wc_resident_call(uint8_t page, uint16_t addr); + +/* ══════════════════════════════════════════════════════════════════════ + * LOAD256 / LOADNONE (дополнительные файловые функции) + * ══════════════════════════════════════════════════════════════════════ */ + +/** + * Загрузить blocks блоков по 256 байт (используется видео плеером). + * Параметры и возврат аналогичны wc_load512. + */ +uint8_t wc_load256(uint16_t dest, uint8_t blocks); + +/* ══════════════════════════════════════════════════════════════════════ + * MNG0VPL / MNG8VPL (видеостраницы в другие окна) + * ══════════════════════════════════════════════════════════════════════ + * + * ⚠ MNG0VPL (Win 0, #0000–#3FFF): + * — WC API сохраняет/восстанавливает Win 0 при каждом CALL #6006 + * — Но GEDPL затирает #1000–#1447! + * — Нельзя передавать через #0000–#3FFF параметры WC-функциям + * (SOW, строки, буферы), т.к. WC ремапит окно внутри вызова + * — Безопасно для данных плагина МЕЖДУ вызовами WC API + * + * ⚠ MNGCVPL (Win 3, #C000–#FFFF): + * — WC API сбрасывает Win 3 на TXT-страницу (#00) при выходе + * из КАЖДОГО вызова P8000 и НЕ восстанавливает! + * — После любого CALL #6006 нужно заново вызвать mngc_pl() + * — Удобно для загрузки: mngcvpl(N) → load512(0xC000, 32) + * + * См. разделы «КАРТА АДРЕСНОГО ПРОСТРАНСТВА» и «P8000 DISPATCH». + */ + + + + + + +void wc_strset(char *dsr, uint16_t len, char c); + + +#endif /* WC_API_H */ diff --git a/src_sdcc/lib/keys.c b/src_sdcc/lib/keys.c new file mode 100644 index 0000000..309618f --- /dev/null +++ b/src_sdcc/lib/keys.c @@ -0,0 +1,203 @@ +/** + * @file keys.c + * @brief PS/2 клавиатура через TSConfig CMOS FIFO (порты #EFF7/#DFF7/#BFF7) + * @see inc/keys.h + * + * Аппаратный PS/2 FIFO буфер в FPGA TSConfig, регистр CMOS #F0. + * + * ps2_poll() за один вызов полностью сливает FIFO (tight loop). + * Если среди прочитанных байтов есть make-код нашей клавиши — возвращает + * последний найденный KEY_*. Стая байтов никогда не копится. + * + * Единственный бит состояния — skip flag (после F0 нужно пропустить 1 байт). + * E0 (extended prefix) не требует skip: сам E0 не совпадает с нашими кодами, + * а release extended (E0 F0 SC) корректно обрабатывается через F0→skip. + * + * Опрос ~1 раз в TV-кадр (rate-limited по isr_tick_ctr). + * ps2_poll полностью на asm. + */ + +#include "../inc/types.h" +#include "../inc/keys.h" +#include "../inc/isr.h" /* isr_tick_ctr (volatile uint8_t) */ +#include "../inc/variant_cfg.h" /* ISR_TICKS_PER_FRAME */ + +/* Единственная FSM-переменная: skip flag (0 или 1). */ +static uint8_t ps2_state; /* 0 = normal, 1 = skip next byte */ +static uint8_t ps2_last_tick; + +/* ── ps2_init: активация PS/2 FIFO + одноразовый слив ────────────── */ +void ps2_init(void) +{ + __asm + ; Разрешить PS/2 порты (бит 7 #EFF7) + ld bc, #0xEFF7 + ld a, #0x80 + out (c), a + ; Выбрать CMOS reg #F0, записать 2 (активация), re-select #F0 + ld bc, #0xDFF7 + ld a, #0xF0 + out (c), a + ld bc, #0xBFF7 + ld a, #0x02 + out (c), a + ld bc, #0xDFF7 + ld a, #0xF0 + out (c), a + ; Слив FIFO (tight loop, макс 256 итераций) + ld bc, #0xBFF7 + ld e, #0 + _pi_drain: + in a, (c) + or a, a + jr z, _pi_done ; 0x00 = пусто + inc a + jr z, _pi_ovf ; 0xFF = overflow + dec e + jr nz, _pi_drain + jr _pi_done + _pi_ovf: + ld bc, #0xDFF7 + ld a, #0x0C + out (c), a + ld bc, #0xBFF7 + ld a, #0x01 + out (c), a + ld bc, #0xDFF7 + ld a, #0xF0 + out (c), a + _pi_done: + __endasm; + ps2_state = 0; + ps2_last_tick = isr_tick_ctr; +} + +/* ── ps2_deinit: слив FIFO перед выходом ──────────────────────────── */ +void ps2_deinit(void) +{ + __asm + ld bc, #0xDFF7 + ld a, #0xF0 + out (c), a + ld bc, #0xBFF7 + ld e, #0 + _pd_loop: + in a, (c) + or a, a + jr z, _pd_done ; 0x00 = пусто + inc a + jr z, _pd_ovf ; 0xFF = overflow + dec e + jr nz, _pd_loop + jr _pd_done + _pd_ovf: + ld bc, #0xDFF7 + ld a, #0x0C + out (c), a + ld bc, #0xBFF7 + ld a, #0x01 + out (c), a + ld bc, #0xDFF7 + ld a, #0xF0 + out (c), a + _pd_done: + __endasm; +} + +/* ── ps2_poll: tight-drain FIFO, rate-limited, полностью asm ──────── */ +uint8_t ps2_poll(void) __naked +{ + __asm + + ;; ── Rate limit: ~1 TV-кадр ──────────────────────── + ld a, (_isr_tick_ctr) + ld hl, #_ps2_last_tick + ld e, a + sub a, (hl) + cp a, #ISR_TICKS_PER_FRAME + jp c, _pp_none + ld (hl), e + + ;; ── Подготовка: выбрать FIFO, init регистров ────── + ld bc, #0xDFF7 + ld a, #0xF0 + out (c), a + ld bc, #0xBFF7 ; BC = порт FIFO на весь цикл + ld a, (_ps2_state) + ld d, a ; D = skip flag (от пред. вызова) + ld e, #0 ; E = результат (KEY_NONE) + + ;; ── Цикл: читаем FIFO до пустого ───────────────── + _pp_loop: + in a, (c) ; BC = #BFF7 + or a, a + jr z, _pp_done ; 0x00 = пусто → выход + inc a + jr z, _pp_ovf ; 0xFF = overflow + dec a ; восстановить байт + + bit 0, d + jr nz, _pp_clr_skip ; skip-флаг → пропустить байт + + cp a, #0xF0 + jr z, _pp_set_skip ; F0 → пропустить след. байт + + ;; ── Трансляция scan code → KEY_* ────────────────── + cp a, #0x76 ; ESC + jr z, _pp_k_esc + cp a, #0x29 ; SPACE + jr z, _pp_k_next + cp a, #0x4D ; P + jr z, _pp_k_prev + cp a, #0x16 ; 1 + jr z, _pp_k_saa1 + cp a, #0x1E ; 2 + jr z, _pp_k_saa2 + jr _pp_loop ; неизвестная → продолжаем слив + + _pp_k_esc: ld e, #KEY_ESC + jr _pp_loop + _pp_k_next: ld e, #KEY_NEXT + jr _pp_loop + _pp_k_prev: ld e, #KEY_PREV + jr _pp_loop + _pp_k_saa1: ld e, #KEY_SAA1 + jr _pp_loop + _pp_k_saa2: ld e, #KEY_SAA2 + jr _pp_loop + + _pp_set_skip: + ld d, #1 + jr _pp_loop + + _pp_clr_skip: + ld d, #0 + jr _pp_loop + + ;; ── Overflow: сброс FIFO ───────────────────────── + _pp_ovf: + ld bc, #0xDFF7 + ld a, #0x0C + out (c), a + ld bc, #0xBFF7 + ld a, #0x01 + out (c), a + ld bc, #0xDFF7 + ld a, #0xF0 + out (c), a + ld d, #0 ; сбросить skip + ;; fall-through + + ;; ── Готово: сохранить skip, вернуть KEY ─────────── + _pp_done: + ld a, d + ld (_ps2_state), a ; skip flag для след. вызова + ld a, e ; A = KEY_* (sdcccall(1)) + ret + + _pp_none: + xor a, a ; KEY_NONE = 0 + ret + + __endasm; +} \ No newline at end of file diff --git a/src_sdcc/lib/vgm.c b/src_sdcc/lib/vgm.c new file mode 100644 index 0000000..ae059c1 --- /dev/null +++ b/src_sdcc/lib/vgm.c @@ -0,0 +1,2017 @@ +/** + * @file vgm.c + * @brief VGM-парсер для ISR-буферов (OPL/AY/SAA + dual-chip) + * @version 4.0 + * + * Читает VGM-поток из VPL-страниц WC (окно #C000). + * + * Поддержка чипов (воспроизведение): + * YM3812 (OPL2): cmd 0x5A → CMD_WRITE_B0 (clock @ header 0x50) + * YM3526 (OPL1): cmd 0x5B → CMD_WRITE_B0 (clock @ header 0x54) + * Y8950 (OPL): cmd 0x5C → CMD_WRITE_B0 (clock @ header 0x58) + * YMF262 (OPL3): cmd 0x5E → CMD_WRITE_B0 (clock @ header 0x5C) + * cmd 0x5F → CMD_WRITE_B1 + * AY8910 (PSG): cmd 0xA0 → CMD_WRITE_AY / CMD_WRITE_AY2 (dual) + * YM2203 (OPN): cmd 0x55 → CMD_WRITE_AY (PSG-часть) + * SAA1099: cmd 0xBD → CMD_WRITE_SAA (bit7 = chip select) + * + * Распознавание (parse_header, vgm_chip_list[]): + * Все 40 чипов VGM spec, включая dual-chip (бит 30 clock). + * + * Масштабирование: 44100 / ISR_FREQ ≈ 2^VGM_SAMPLE_SHIFT. + * + * Оптимизации: + * - vgm_read_byte() — чистый ASM, экономит ~20 байт и ~30T на вызов. + * - vgm_skip() — арифметический пропуск без чтения (data block). + * - chip_scan[] — компактная ROM-таблица (4 bytes × 40 entries). + */ + +#include "../inc/types.h" +#include "../inc/vgm.h" +#include "../inc/isr.h" +#include "../inc/wc_api.h" +#include "../inc/freq_lut_map.h" + +/* ── Состояние парсера ──────────────────────────────────────────────── */ +volatile uint8_t vgm_song_ended; // 1 = достигнут конец (0x66), main loop может остановить воспроизведение +uint8_t vgm_paused; // 1 = пауза (остановить таймер, не обнулять vgm_sec_budget) +uint8_t vgm_cur_page; // текущая VPL-страница для чтения данных (0..N) +uint16_t vgm_read_ptr; // смещение в окне #C000 для чтения следующего байта VGM-потока +uint8_t vgm_end_page; // страница, на которой заканчивается VGM-поток (для оптимизации проверки конца) +uint16_t vgm_end_addr; // смещение в окне #C000, на котором заканчивается VGM-поток (для оптимизации проверки конца) +uint16_t vgm_version; // версия VGM в BCD (например, 0x0151 для v1.51) +uint8_t vgm_chip_type; // битовая маска типа чипов (VGM_CHIP_xxx), например VGM_CHIP_OPL2 | VGM_CHIP_AY +uint8_t vgm_chip_count; // количество обнаруженных чипов (для вывода информации о файле) + +vgm_chip_entry_t vgm_chip_list[VGM_MAX_CHIPS]; + +uint16_t vgm_loop_addr; // адрес точки петли в окне #C000 (0 = нет петли) +uint8_t vgm_loop_page; // страница, на которой находится точка петли +uint16_t vgm_total_seconds; // общее время воспроизведения в секундах +uint8_t vgm_loop_enabled; // 1 = петля включена, 0 = нет + +// параметры политики петли (читаются из INI, устанавливаются main.c) +uint8_t cfg_loop_rewinds = 1; +uint8_t cfg_min_duration = 10; +uint16_t cfg_max_duration = 240; + +// очередь высокоуровневых команд для vgm_fill_buffer (play, loop, init, silence) +hl_entry_t vgm_hl_queue[HL_QUEUE_MAX]; +uint8_t vgm_hl_len; +uint8_t vgm_hl_pos; +uint8_t vgm_hl_abort_pos; +uint8_t vgm_loop_count; + + +/* ── Аккумулятор остатка задержки (VGM samples, 0..15) ─────────────── */ +static uint16_t vgm_wait_accum = 0; + +/* ── Битовая маска spectrum analyzer (fill_buffer → pad bytes → ISR) ── */ +/* Не static: используется ASM-хелперами spectrum (asm/spectrum.s) */ +uint16_t spec_mask; /* 16-бит: bit N → полоса N на максимум */ +uint16_t spec_ay_period[6]; /* AY period shadow: ch0-2(chip1), ch0-2(chip2) */ +uint8_t spec_saa_oct[6]; /* SAA octave shadow: chip 0 каналы 0-5 */ +uint8_t spec_saa_oct2[6]; /* SAA octave shadow: chip 1 каналы 0-5 */ +uint8_t spec_saa_dual; /* 1=dual SAA: bars 0-7 / 8-15 split */ +uint8_t cfg_saa_mode = 0; /* 0=chip0 only(default), 1=chip1 on chip0, 2=turbo(both) */ +uint8_t spec_opl_bd; /* OPL 0xBD shadow для детекции фронта */ +uint8_t spec_fm_block[6]; /* YM2203 FM block shadow: ch0-2(chip1/2)*/ +uint8_t spec_wave_active[3]; /* 24-bit KeyOn mask, wave ch0..23 */ + +/* ── Масштабирование частот PSG/FM (LUT + asm) ──────────────────────── */ +uint8_t vgm_freq_mode; +uint16_t *freq_lut_base; +uint16_t vgm_freq_lut_khz; /* clock (кГц) подобранной LUT таблицы */ +static uint8_t freq_lut_page; /* plugin page для Window 0 (LUT) */ + +/* FM F-Number: множитель N для формулы fnum × N / 7. + * 0 = bypass (масштабирование не нужно), иначе 3/5/6/8. */ +static uint8_t fm_mul; + +/* Shadow-регистры PSG: полный 12-бит tone period на канал. + * Обновляются при записи lo/hi, используются для LUT lookup. */ +static uint16_t psg_shadow[3]; /* каналы 0,1,2 — chip 1 */ +static uint16_t psg_shadow2[3]; /* каналы 0,1,2 — chip 2 (0xA5) */ + +/* Shadow-регистры FM: 11-бит F-Number + 3-бит block на канал. + * Обновляются при записи 0xA0-0xA2 (lo) / 0xA4-0xA6 (hi+block). */ +static uint16_t fm_fnum[3]; /* F-Number каналы 0,1,2 — chip 1 */ +static uint16_t fm_fnum2[3]; /* F-Number каналы 0,1,2 — chip 2 */ +static uint8_t fm_block[3]; /* block каналы 0,1,2 — chip 1 */ +static uint8_t fm_block2[3]; /* block каналы 0,1,2 — chip 2 */ + +/* ── Бюджеты для вставки служебных ISR-команд ──────────────────────── */ +static uint16_t vgm_sec_budget = ISR_FREQ; /* тиков до CMD_INC_SEC */ + +/* ── Статические локальные для vgm_fill_buffer / emit_wait ────────── + * Устраняет IX-frame SDCC (32 байта стека + 19T на ld r,-N(ix)). + * Static доступ: ld a,(nn) = 13T, ld hl,(nn) = 16T. */ +static uint8_t *fb_buf; /* база текущего cmd_buf */ +static uint8_t *fb_wp; /* указатель записи в cmd_buf */ +static uint8_t *fb_rp; /* указатель чтения VGM (#C000-#FFFF) */ +static uint8_t fb_cpg; /* локальная копия vgm_cur_page */ +static uint8_t fb_budget; /* команд до принудительного yield */ +static uint8_t fb_yield_ticks; /* тиков потрачено на budget yield-ы */ +static uint16_t fb_wacc; /* аккумулятор задержки (0..VGM_SAMPLE_MASK) */ +static uint8_t fb_op; /* текущий VGM opcode */ +static uint8_t fb_b1; /* байт операнда 1 */ +static uint8_t fb_b2; /* байт операнда 2 */ +static uint8_t fb_is_vgm; /* 1 = VGM mega-buffer, 0 = cmdblk */ +static uint16_t fb_tk; /* temp: счётчик тиков для emit_wait */ +static uint32_t fb_t32; /* temp: 32-бит размер data-block (0x67) */ +static uint16_t fb_w; /* temp: промежуточное значение emit_wait */ +static uint8_t fb_n; /* temp: число байт для пропуска */ +static uint8_t *fb_end; /* предвычисленный fb_buf + (CMD_BUF_SIZE-48) */ +static hl_entry_t *fb_e; /* temp: указатель на запись HL queue */ +static uint8_t fb_ch; /* temp: индекс канала PSG/FM */ +static uint16_t fb_scaled; /* temp: масштабированная частота */ +static uint8_t fb_reg; /* temp: AY рег. (0xA0) / YMF278B port (0xD0) / + * тип data-block (0x67) — ветки взаимоисключающие */ + +/* ── Патч заголовков wave-таблицы после загрузки блока 0x84 (ROM Image) ── + * См. wave_upload_block(): референсный NedoOS-драйвер (vgm/opl4.asm, + * opl4loadromdatablock) после заливки ROM-блока, чей адрес попадает в + * первые 64КБ RAM-региона (где живёт таблица заголовков сэмплов, 128 + * записей по 12 байт), принудительно взводит бит RAM-адресации (#20) в + * первом байте КАЖДОЙ записи — иначе встроенные в заголовки адреса + * сэмплов остаются ROM-относительными, и чип читает не тот PCM-семпл. */ +static uint8_t hdr_recs; /* оставшиеся записи таблицы (0-128); 0=не патчим */ +static uint8_t hdr_sub; /* позиция внутри 12-байтной записи (0-11) */ +static uint8_t opl4_rom_loaded; /* 0x84 ROM image перенесён в SRAM; номера + * волн надо отображать в диапазон 384-511 */ + +/* VGM_FILL_CMD_BUDGET определён в variant_cfg.h (через isr.h) */ + +/* ── Хелперы spectrum analyzer (ASM: asm/spectrum.s) ────────────── */ +/* 16 полос = 8 октав × 2 поддиапазона (по OPL block*2+fnum_msb). + * Все реализации на оптимизированном Z80 ASM для горячего цикла. */ +extern void spectrum_opl_b0(uint8_t reg, uint8_t val); +extern void spectrum_opl_b1(uint8_t reg, uint8_t val); +extern void spectrum_ay(uint8_t reg, uint8_t val); +extern void spectrum_ay2(uint8_t reg, uint8_t val); +extern void spectrum_saa(uint8_t reg, uint8_t val); +extern void spectrum_saa2(uint8_t reg, uint8_t val); +extern void spectrum_wave(uint8_t reg, uint8_t val); + + +/* ── Таблица сканирования чипов (ROM-const) ──────────────────────── */ +/* Каждая запись: { offset_in_header, min_version_lo } + * offset — байтовое смещение 32-bit clock-поля в VGM заголовке. + * minv_lo — младший байт минимальной VGM-версии (старший всегда 0x01). + * chip ID в chip_list = offset. */ +typedef struct +{ + uint8_t off; + uint8_t minv_lo; +} chip_scan_t; + +static const chip_scan_t chip_scan[] = { + {0x0C, 0x00}, {0x10, 0x00}, /* 1.00 */ + {0x2C, 0x50}, {0x30, 0x50}, /* 1.50 */ + {0x38, 0x51}, {0x40, 0x51}, {0x44, 0x51}, {0x48, 0x51}, /* 1.51 */ + {0x4C, 0x51}, {0x50, 0x51}, {0x54, 0x51}, {0x58, 0x51}, + {0x5C, 0x51}, {0x60, 0x51}, {0x64, 0x51}, {0x68, 0x51}, + {0x6C, 0x51}, {0x70, 0x51}, {0x74, 0x51}, + {0x80, 0x61}, {0x84, 0x61}, {0x88, 0x61}, {0x8C, 0x61}, /* 1.61 */ + {0x90, 0x61}, {0x98, 0x61}, {0x9C, 0x61}, {0xA0, 0x61}, + {0xA4, 0x61}, {0xA8, 0x61}, {0xAC, 0x61}, {0xB0, 0x61}, + {0xB4, 0x61}, + {0xB8, 0x71}, {0xC0, 0x71}, {0xC4, 0x71}, {0xC8, 0x71}, /* 1.71 */ + {0xCC, 0x71}, {0xD0, 0x71}, {0xD8, 0x71}, {0xDC, 0x71}, + {0xE0, 0x71}, +}; +#define CHIP_SCAN_COUNT (sizeof(chip_scan)/sizeof(chip_scan[0])) + +/* ── Локальный хелпер ───────────────────────────────────────────────── */ + +/* Пропустить n байт в VGM-потоке (арифметически, без чтения). + * Используется для data block 0x67, PCM RAM write 0x68 и др. */ +static void vgm_skip(uint32_t n) +{ + uint16_t ptr = vgm_read_ptr; + uint32_t avail = 0x10000UL - (uint32_t)ptr; + + if (n < avail) { + vgm_read_ptr = ptr + (uint16_t)n; + return; + } + + n -= avail; + vgm_cur_page++; + + while (n >= 0x4000UL) { + n -= 0x4000UL; + vgm_cur_page++; + } + + vgm_read_ptr = 0xC000 + (uint16_t)n; + wc_mngcvpl(vgm_cur_page); +} + +/* Деление числа VGM-сэмплов на 44100 вызывается только при разборе + * заголовка. Небольшой цикл вычитания заметно компактнее универсальных + * 32-битных __divulong/__mullong SDCC (358 байт резидентного кода). */ +static uint16_t samples_to_seconds(uint32_t samples) +{ + uint16_t seconds = 0; + + while (samples >= 44100UL) { + samples -= 44100UL; + if (++seconds == 0xFFFF) + break; + } + return seconds; +} + +/* clock/1000 с ошибкой менее 0.2% для типичных звуковых частот: + * 1/1024 + 1/65536 + 1/131072 = 0.00099945. Для выбора LUT допуск + * равен 2%, зато не нужна 131-байтная библиотечная 32-битная делилка. */ +static uint16_t clock_to_khz(uint32_t clock) +{ + uint16_t hi = (uint16_t)(clock >> 16); + return (uint16_t)(clock >> 10) + hi + (hi >> 1); +} + +/* ───────────────────────────────────────────────────────────────────── + * vgm_parse_header + * ───────────────────────────────────────────────────────────────────── */ +uint8_t vgm_parse_header(void) +{ + uint8_t *base = (uint8_t *)0xC000; + uint32_t data_off_field; + uint32_t data_start_abs; + uint32_t loop_off_field; + uint32_t loop_start_abs; + uint32_t eof_off_field; + uint32_t end_file_off; + uint8_t i; + uint8_t off_hdr; + uint32_t clk; + vgm_chip_entry_t *e; + + vgm_song_ended = 0; + vgm_paused = 0; + vgm_chip_count = 0; + vgm_chip_type = VGM_CHIP_NONE; + opl4_rom_loaded = 0; + for (i = 0; i < 3; i++) + spec_wave_active[i] = 0; + vgm_loop_addr = 0; + vgm_loop_page = 0; + vgm_wait_accum = 0; + vgm_sec_budget = ISR_FREQ; + + /* Сигнатура "Vgm " */ + if (base[0] != 'V' || base[1] != 'g' || base[2] != 'm' || base[3] != ' ') + return VGM_ERR_HEADER; + + /* Версия */ + vgm_version = (uint16_t)base[0x08] | ((uint16_t)base[0x09] << 8); + + /* ── Data offset ────────────────────────────────────────────────── + * VGM >= 1.50: поле в 0x34 — относительное смещение от 0x34. + * Если поле == 0, для совместимости используем 0x40. + * VGM < 1.50: data начинается с 0x40. + */ + if (vgm_version >= 0x0150) { + data_off_field = (uint32_t)base[0x34] + | ((uint32_t)base[0x35] << 8) + | ((uint32_t)base[0x36] << 16) + | ((uint32_t)base[0x37] << 24); + + data_start_abs = data_off_field ? (0x34UL + data_off_field) : 0x40UL; + } else { + data_start_abs = 0x40UL; + } + + /* Если data_start выходит за EOF — файл сломан */ + eof_off_field = (uint32_t)base[0x04] + | ((uint32_t)base[0x05] << 8) + | ((uint32_t)base[0x06] << 16) + | ((uint32_t)base[0x07] << 24); + end_file_off = eof_off_field + 4UL; + + if (data_start_abs >= end_file_off) + return VGM_ERR_HEADER; + + vgm_cur_page = (uint8_t)(data_start_abs >> 14); + vgm_read_ptr = 0xC000 + (uint16_t)(data_start_abs & 0x3FFF); + + /* ── Конец файла ────────────────────────────────────────────────── */ + vgm_end_page = (uint8_t)(end_file_off >> 14); + vgm_end_addr = 0xC000 + (uint16_t)(end_file_off & 0x3FFF); + + /* ── Scan чипов ─────────────────────────────────────────────────── */ + for (i = 0; i < CHIP_SCAN_COUNT; i++) { + off_hdr = chip_scan[i].off; + + if (vgm_version < (0x0100u | chip_scan[i].minv_lo)) + continue; + + /* Clock fields должны лежать до начала VGM data stream */ + if ((uint32_t)off_hdr + 4UL > data_start_abs) + continue; + + /* Читаем clock (32-bit LE) */ + clk = (uint32_t)base[off_hdr]; + clk |= (uint32_t)base[off_hdr + 1] << 8; + clk |= (uint32_t)base[off_hdr + 2] << 16; + clk |= (uint32_t)base[off_hdr + 3] << 24; + + if (clk == 0) + continue; + + /* Сохраняем */ + if (vgm_chip_count < VGM_MAX_CHIPS) { + e = &vgm_chip_list[vgm_chip_count]; + e->id = off_hdr; + e->flags = (clk & 0x40000000UL) ? VGM_CF_DUAL : 0; + clk &= 0x3FFFFFFFUL; + e->clock_khz = clock_to_khz(clk); + vgm_chip_count++; + } + + /* Определяем тип OPL для fill_buffer. + * chip_scan[] идёт по возрастанию offset, поэтому YMF278B (0x60) + * всегда проверяется после YMF262 (0x5C) и корректно перекрывает + * его результат — приоритет YMF278B > YMF262 соблюдён. */ + if (off_hdr == VGM_OFF_YMF278B) + vgm_chip_type = VGM_CHIP_OPL4; + else if (off_hdr == VGM_OFF_YMF262) + vgm_chip_type = VGM_CHIP_OPL3; + else if ((off_hdr == VGM_OFF_YM3812 || off_hdr == VGM_OFF_Y8950) && vgm_chip_type < VGM_CHIP_OPL2) + vgm_chip_type = VGM_CHIP_OPL2; + else if (off_hdr == VGM_OFF_YM3526 && vgm_chip_type < VGM_CHIP_OPL) + vgm_chip_type = VGM_CHIP_OPL; + } + + /* ── Подбор LUT для пересчёта частот PSG/FM ──────────────────────── + * YM2203: psg_khz = полный master clock → ищем в freq_lut_map_ym[] + * AY8910: psg_khz = clock как есть → ищем в freq_lut_map_ay[] + * + * PSG таблица в 16KB странице (2 per page, PSG_A@0x0000, PSG_B@0x2000). + * PSG ratio = target/source (period scaling). + * FM ratio = source/target (F-Number scaling, ОБРАТНЫЙ!) — ASM ×N/7. */ + vgm_freq_mode = FREQ_MODE_NATIVE; + freq_lut_base = (uint16_t *)0; + freq_lut_page = 0; + vgm_freq_lut_khz = 0; + fm_mul = 0; + psg_shadow[0] = 0; psg_shadow[1] = 0; psg_shadow[2] = 0; + psg_shadow2[0] = 0; psg_shadow2[1] = 0; psg_shadow2[2] = 0; + fm_fnum[0] = 0; fm_fnum[1] = 0; fm_fnum[2] = 0; + fm_fnum2[0] = 0; fm_fnum2[1] = 0; fm_fnum2[2] = 0; + fm_block[0] = 0; fm_block[1] = 0; fm_block[2] = 0; + fm_block2[0] = 0; fm_block2[1] = 0; fm_block2[2] = 0; + + for (i = 0; i < vgm_chip_count; i++) { + uint16_t psg_khz; + uint16_t hw_khz; + uint16_t bypass_tol; + const freq_lut_entry_t *lut_map; + uint8_t lut_count; + uint8_t is_ym; + + if (vgm_chip_list[i].id == VGM_OFF_YM2203) { + psg_khz = vgm_chip_list[i].clock_khz; /* полный master clock */ + hw_khz = FREQ_LUT_HW_YM_KHZ; /* 3500 */ + bypass_tol = FREQ_LUT_BYPASS_TOL_YM; /* 70 */ + lut_map = freq_lut_map_ym; + lut_count = FREQ_LUT_YM_COUNT; + is_ym = 1; + } else if (vgm_chip_list[i].id == VGM_OFF_AY8910) { + psg_khz = vgm_chip_list[i].clock_khz; + hw_khz = FREQ_LUT_HW_KHZ; /* 1750 */ + bypass_tol = FREQ_LUT_BYPASS_TOL; /* 35 */ + lut_map = freq_lut_map_ay; + lut_count = FREQ_LUT_AY_COUNT; + is_ym = 0; + } else { + continue; + } + + /* ── Определяем режим масштабирования ───────────────────── + * 1) bypass: |psg_khz - HW| ≤ 2% → native + * 2) LUT table match (±5%) → TABLE mode (Window 0) + * Нет match → остаётся native (экзотика <1% файлов) */ + { + uint16_t diff = (psg_khz > hw_khz) + ? (psg_khz - hw_khz) + : (hw_khz - psg_khz); + if (diff <= bypass_tol) { + /* ±2% — native, пересчёт не нужен */ + vgm_freq_mode = FREQ_MODE_NATIVE; + } else { + /* Ищем подходящую предрасчитанную таблицу (±5%) */ + uint8_t j; + for (j = 0; j < lut_count; j++) { + uint16_t d = (psg_khz > lut_map[j].clk_khz) + ? (psg_khz - lut_map[j].clk_khz) + : (lut_map[j].clk_khz - psg_khz); + if (d <= lut_map[j].tol_khz) { + /* Матч! Подключаем LUT страницу в Window 0 */ + freq_lut_page = lut_map[j].page; + wc_mng0_pl(freq_lut_page); + freq_lut_base = (uint16_t *)lut_map[j].offset; + /* FM множитель: fnum × N / 7 (в ASM) */ + if (is_ym) { + fm_mul = freq_lut_fm_mul[j]; + } + vgm_freq_lut_khz = lut_map[j].clk_khz; + vgm_freq_mode = FREQ_MODE_TABLE; + break; + } + } + /* Нет match → native (без масштабирования) */ + } + } + break; + } + + /* ── Loop offset ────────────────────────────────────────────────── + * Relative offset from 0x1C. Если значение невалидно — loop отключаем. + */ + loop_off_field = (uint32_t)base[0x1C] + | ((uint32_t)base[0x1D] << 8) + | ((uint32_t)base[0x1E] << 16) + | ((uint32_t)base[0x1F] << 24); + + if (loop_off_field) { + loop_start_abs = 0x1CUL + loop_off_field; + + if (loop_start_abs >= data_start_abs && loop_start_abs < end_file_off) { + vgm_loop_page = (uint8_t)(loop_start_abs >> 14); + vgm_loop_addr = 0xC000 + (uint16_t)(loop_start_abs & 0x3FFF); + } else { + vgm_loop_addr = 0; + vgm_loop_page = 0; + } + } + + /* ── Общая длительность + политика лупа ─────────────────────────── + * total_samples (0x18) = число сэмплов всего файла (до лупа). + * loop_samples (0x20) = число сэмплов одной итерации лупа. + * + * Loop отключается если: + * 1) loop_addr == 0 (нет лупа в файле) + * 2) full-track loop (loop_start == data_start) + * 3) базовый трек < cfg_min_duration + * 4) с лупом получается > cfg_max_duration + */ + { + uint32_t ts = (uint32_t)base[0x18] + | ((uint32_t)base[0x19] << 8) + | ((uint32_t)base[0x1A] << 16) + | ((uint32_t)base[0x1B] << 24); + uint16_t base_sec = samples_to_seconds(ts); + + vgm_loop_enabled = 0; + + if (vgm_loop_addr && + !(vgm_loop_page == vgm_cur_page && + vgm_loop_addr == vgm_read_ptr) && + base_sec >= cfg_min_duration) { + /* Частичный loop, трек достаточно длинный — пробуем */ + uint32_t ls = (uint32_t)base[0x20] + | ((uint32_t)base[0x21] << 8) + | ((uint32_t)base[0x22] << 16) + | ((uint32_t)base[0x23] << 24); + uint32_t total_with_loop = ts; + uint8_t repeats = cfg_loop_rewinds; + while (repeats--) + total_with_loop += ls; + uint16_t total_sec = samples_to_seconds(total_with_loop); + + if (total_sec <= cfg_max_duration) { + vgm_loop_enabled = 1; + base_sec = total_sec; + } + } + + vgm_total_seconds = base_sec; + } + + return VGM_OK; +} + +/* ───────────────────────────────────────────────────────────────────── + * vgm_chip_name — имя чипа по header offset + * ───────────────────────────────────────────────────────────────────── */ +const char *vgm_chip_name(uint8_t id) +{ + switch (id) { + case 0x0C: return "SN76489"; + case 0x10: return "YM2413"; + case 0x44: return "YM2203"; + case 0x50: return "YM3812"; + case 0x54: return "YM3526"; + case 0x58: return "Y8950"; + case 0x5C: return "YMF262"; + case 0x60: return "YMF278B"; + case 0x74: return "AY8910"; + case 0xC8: return "SAA1099"; + default: return 0; + } +} + +/* ───────────────────────────────────────────────────────────────────── + * asm_wait_63 / asm_wait_62 — добавляет 882/735 к fb_wacc, делает + * >> VGM_SAMPLE_SHIFT, пишет fb_tk и fb_wacc. + * + * Заменяет цепочку: C-сложение → fb_t → asm_shift_mask. + * ~165 T (62) / ~175 T (63) vs ~246 T (C + shift_mask). + * + * VGM_SAMPLE_SHIFT = 6. Максимальный результат: (63+882)>>6 = 14. + * Clobbers: A, B, D, E, H, L. + * ───────────────────────────────────────────────────────────────────── */ +static void asm_wait_62(void) __naked; +static void asm_wait_63(void) __naked; + +static void asm_wait_63(void) __naked { + __asm + ld de, #882 ;10T + jp _asm_wait_common ;10T + __endasm; +} +static void asm_wait_62(void) __naked { + __asm + ld de, #735 ;10T +_asm_wait_common: + ld hl, (_fb_wacc) ;16T + add hl, de ;11T max 63+882=945=0x03B1, CF=0 + ;; fb_wacc = L & VGM_SAMPLE_MASK + ld a, l ; 4T + and a, #VGM_SAMPLE_MASK ; 7T + ld (_fb_wacc), a ;13T + ;; fb_tk = HL >> VGM_SAMPLE_SHIFT via H<<(8-SHIFT) | L>>(SHIFT) + ld a, h ; 4T + add a, a ; 4T <<1 + add a, a ; 4T <<2 +#if VGM_SAMPLE_SHIFT < 6 + add a, a ; 4T <<3 +#endif +#if VGM_SAMPLE_SHIFT < 5 + add a, a ; 4T <<4 +#endif + ld b, a ; 4T B = H<<(8-SHIFT) + ld a, l ; 4T + rlca ; 4T + rlca ; 4T +#if VGM_SAMPLE_SHIFT < 6 + rlca ; 4T +#endif +#if VGM_SAMPLE_SHIFT < 5 + rlca ; 4T +#endif +#if VGM_SAMPLE_SHIFT == 6 + and a, #0x03 ; 7T L>>6 +#elif VGM_SAMPLE_SHIFT == 5 + and a, #0x07 ; 7T L>>5 +#elif VGM_SAMPLE_SHIFT == 4 + and a, #0x0F ; 7T L>>4 +#endif + or a, b ; 4T A = fb_tk (max 14, fits u8) + ld (_fb_tk), a ;13T + xor a, a ; 4T + ld (_fb_tk+1), a ;13T + ld (_fb_wacc+1), a ;13T + ret ;10T + __endasm; +} + +/* ───────────────────────────────────────────────────────────────────── + * asm_short_wait — для 0x70-0x7F: fb_wacc += (fb_op & 0x0F) + 1, + * >> VGM_SAMPLE_SHIFT, пишет fb_tk и fb_wacc. + * + * Максимум fb_wacc(63) + 16 = 79 → H=0, fb_tk = 0 или 1. + * ~175 T total vs ~304 T (C + shift_mask). + * Clobbers: A, D, E, H, L. + * ───────────────────────────────────────────────────────────────────── */ +static void asm_short_wait(void) __naked { + __asm + ld a, (_fb_op) ;13T + and a, #0x0F ; 7T + inc a ; 4T A = (op & 0x0F) + 1 (1..16) + ld e, a ; 4T + ld d, #0 ; 7T DE = addend + ld hl, (_fb_wacc) ;16T + add hl, de ;11T max 79, CF=0, H=0 + ;; fb_wacc = L & VGM_SAMPLE_MASK + ld a, l ; 4T + and a, #VGM_SAMPLE_MASK ; 7T + ld (_fb_wacc), a ;13T + ;; fb_tk = L >> VGM_SAMPLE_SHIFT (H guaranteed 0) + ld a, l ; 4T + rlca ; 4T + rlca ; 4T +#if VGM_SAMPLE_SHIFT < 6 + rlca ; 4T +#endif +#if VGM_SAMPLE_SHIFT < 5 + rlca ; 4T +#endif +#if VGM_SAMPLE_SHIFT == 6 + and a, #0x03 ; 7T +#elif VGM_SAMPLE_SHIFT == 5 + and a, #0x07 ; 7T +#elif VGM_SAMPLE_SHIFT == 4 + and a, #0x0F ; 7T +#endif + ld (_fb_tk), a ;13T + xor a, a ; 4T + ld (_fb_tk+1), a ;13T + ld (_fb_wacc+1), a ;13T + ret ;10T + __endasm; +} + +/* ───────────────────────────────────────────────────────────────────── + * asm_arb_wait — для 0x61: fb_wacc += fb_b2:fb_b1 (16-бит), + * >> VGM_SAMPLE_SHIFT (= 6) с учётом переноса (17-бит). + * Записывает fb_tk (max 1024), fb_wacc. + * + * ~236 T total vs SDCC 32-бит IX-frame + DJNZ ~725 T. (3× быстрее) + * + * Вызывать ПОСЛЕ asm_read_2bytes() (fb_b1, fb_b2 уже прочитаны). + * Clobbers: A, B, C, D, E, H, L. + * ───────────────────────────────────────────────────────────────────── */ +static void asm_arb_wait(void) __naked { + __asm + ;; sample_count = fb_b2:fb_b1 + ld a, (_fb_b2) ;13T + ld h, a ; 4T + ld a, (_fb_b1) ;13T + ld l, a ; 4T + ;; sum = fb_wacc + sample_count (17-bit: CF:H:L) + ld de, (_fb_wacc) ;20T + add hl, de ;11T CF = bit16 + ;; Save carry to B (ld does not touch flags!) + ld b, #0 ; 7T + jr nc, 80$ ;12/7T usually no carry +#if VGM_SAMPLE_SHIFT == 6 + ld b, #4 ; 7T CF << (8-6) = CF<<2 = 4 +#elif VGM_SAMPLE_SHIFT == 5 + ld b, #8 ; 7T CF << (8-5) = CF<<3 = 8 +#elif VGM_SAMPLE_SHIFT == 4 + ld b, #16 ; 7T CF << (8-4) = CF<<4 = 16 +#endif +80$: + ;; fb_wacc = L & VGM_SAMPLE_MASK + ld a, l ; 4T + and a, #VGM_SAMPLE_MASK ; 7T + ld (_fb_wacc), a ;13T + xor a, a ; 4T + ld (_fb_wacc+1), a ;13T + ;; fb_tk = (CF:H:L) >> VGM_SAMPLE_SHIFT + ;; low byte: (H<<(8-SHIFT)) | (L>>SHIFT) + ld a, l ; 4T + rlca ; 4T + rlca ; 4T +#if VGM_SAMPLE_SHIFT < 6 + rlca ; 4T +#endif +#if VGM_SAMPLE_SHIFT < 5 + rlca ; 4T +#endif +#if VGM_SAMPLE_SHIFT == 6 + and a, #0x03 ; 7T L>>6 +#elif VGM_SAMPLE_SHIFT == 5 + and a, #0x07 ; 7T L>>5 +#elif VGM_SAMPLE_SHIFT == 4 + and a, #0x0F ; 7T L>>4 +#endif + ld c, a ; 4T + ld a, h ; 4T + rlca ; 4T + rlca ; 4T +#if VGM_SAMPLE_SHIFT < 6 + rlca ; 4T +#endif +#if VGM_SAMPLE_SHIFT < 5 + rlca ; 4T +#endif + ld d, a ; 4T D = rotated H +#if VGM_SAMPLE_SHIFT == 6 + and a, #0xFC ; 7T H<<2 +#elif VGM_SAMPLE_SHIFT == 5 + and a, #0xF8 ; 7T H<<3 +#elif VGM_SAMPLE_SHIFT == 4 + and a, #0xF0 ; 7T H<<4 +#endif + or a, c ; 4T lo = (H<<(8-SHIFT))|(L>>SHIFT) + ld l, a ; 4T + ;; high byte: (CF<<(8-SHIFT)) | (H>>SHIFT) + ld a, d ; 4T rotated H +#if VGM_SAMPLE_SHIFT == 6 + and a, #0x03 ; 7T H>>6 +#elif VGM_SAMPLE_SHIFT == 5 + and a, #0x07 ; 7T H>>5 +#elif VGM_SAMPLE_SHIFT == 4 + and a, #0x0F ; 7T H>>4 +#endif + or a, b ; 4T + CF<<(8-SHIFT) + ld h, a ; 4T + ld (_fb_tk), hl ;16T + ret ;10T + __endasm; +} + +/* ───────────────────────────────────────────────────────────────────── + * asm_read_byte — чтение одного байта из fb_rp, обработка page-cross + * Возвращает A = byte. Clobbers C, HL. + * Fast-path: 74 T (+ call 17 + ret 10 = 101 T итого) + * ───────────────────────────────────────────────────────────────────── */ +static uint8_t asm_read_byte(void) __naked +{ + __asm + ld hl, (_fb_rp) ;16 load read pointer + ld c, (hl) ; 7 byte -> C + inc hl ; 6 + ld a, h ; 4 + or a, l ; 4 Z if HL wrapped to 0x0000 + jr Z, 150$ ; 7 page-cross (rare) + ld (_fb_rp), hl ;16 store updated pointer + ld a, c ; 4 result -> A + ret ;10 fast-path 74 T +150$: + ld hl, #_fb_cpg ;10 + inc (hl) ;11 + ld a, (hl) ; 7 + push bc ;11 save result + call _wc_mngcvpl ;17+ switch VPL page (A = fb_cpg) + pop bc ;10 + ld hl, #0xC000 ;10 + ld (_fb_rp), hl ;16 + ld a, c ; 4 result -> A + ret ;10 + __endasm; +} + +/* ───────────────────────────────────────────────────────────────────── + * asm_read_2bytes — чтение двух последовательных байт → fb_b1, fb_b2 + * Пишет напрямую в static переменные. Обрабатывает page-cross побайтно. + * Fast-path: 124 T (+ call 17 = 141 T итого) + * ───────────────────────────────────────────────────────────────────── */ +static void asm_read_2bytes(void) __naked +{ + __asm + ld hl, (_fb_rp) ;16 + ld a, (hl) ; 7 byte1 + ld (_fb_b1), a ;13 + inc hl ; 6 + ld a, h ; 4 + or a, l ; 4 + jr Z, 161$ ; 7 page-cross after byte1 (rare) +160$: + ld a, (hl) ; 7 byte2 + ld (_fb_b2), a ;13 + inc hl ; 6 + ld a, h ; 4 + or a, l ; 4 + jr Z, 163$ ; 7 page-cross after byte2 (rare) +162$: + ld (_fb_rp), hl ;16 + ret ;10 fast-path 124 T +161$: + ld hl, #_fb_cpg ;10 + inc (hl) ;11 + ld a, (hl) ; 7 + call _wc_mngcvpl ;17+ + ld hl, #0xC000 ;10 + jr 160$ ;12 +163$: + ld hl, #_fb_cpg ;10 + inc (hl) ;11 + ld a, (hl) ; 7 + call _wc_mngcvpl ;17+ + ld hl, #0xC000 ;10 + ld (_fb_rp), hl ;16 + ret ;10 + __endasm; +} + +/* ───────────────────────────────────────────────────────────────────── + * OPL4 (ZXM-MoonSound) — прямая (не через cmd-buffer) запись в wave-порты + * #7E (выбор регистра) / #7F (данные), плюс переключение turbo-режима. + * Используются ТОЛЬКО в wave_upload_block() — разовая синхронная заливка + * PCM-сэмплов при загрузке трека, до старта ISR-плейбека (не hot-path). + * ───────────────────────────────────────────────────────────────────── */ +/* out (n),a — короткая форма (2 байта). Старший байт адреса (A15-A8) + * при этом равен A (значению-аргументу), но плата его не видит: линия + * CA[7..0] у CPLD DD2 — единственный адресный вход декодера портов + * #7E/#7F/#C4-#C7 (8 бит), верхняя половина шины к нему не подключена + * (см. схему ZXM-MoonSound, dd2.tdf) — как и в родном moon_driver.asm. + * + * Перед каждой записью — опрос busy-флага (бит0 порта #C4 — тот же + * порт, что и FM Bank1 addr-select, но на чтение отдаёт статус чипа). + * Без этого ожидания запись может быть потеряна чипом (особенно в + * режиме прямого доступа к памяти) — подтверждено рабочим NedoOS- + * драйвером (common/moonsound.asm: opl4_wait), а не только datasheet. */ +static void chip_wait(void) __naked { + __asm + push af +cw_loop: + in a, (0xC4) + rrca + jr c, cw_loop + pop af + ret + __endasm; +} + +static void wave_out_reg(uint8_t reg) __naked { + __asm + call _chip_wait + out (0x7E), a + ret + __endasm; + (void)reg; +} + +static void wave_out_dat(uint8_t val) __naked { + __asm + call _chip_wait + out (0x7F), a + ret + __endasm; + (void)val; +} + +/* Быстрая заливка fb_w байт из текущей VPL-страницы. В отличие от + * asm_read_byte()+wave_out_dat() указатели и счётчик держатся в регистрах; + * обязательный busy-wait перед каждым байтом сохранён. Вызывающий код не + * даёт циклу пересечь границу страницы. */ +static void wave_upload_fast(void) __naked { + __asm + ld hl, (_fb_rp) + ld bc, (_fb_w) +wuf_loop: + in a, (0xC4) + rrca + jr c, wuf_loop + ld a, (hl) + out (0x7F), a + inc hl + dec bc + ld a, b + or a, c + jr nz, wuf_loop + ld (_fb_rp), hl + ret + __endasm; +} + +/* ───────────────────────────────────────────────────────────────────── + * wave_upload_block — залить payload VGM data-block 0x67 типа 0x84 + * (YMF278B ROM Image) / 0x87 (RAM Image) в SRAM карты ZXM-MoonSound. + * + * Вызывается из обработчика 0x67 в vgm_fill_buffer() ДО начала реальной + * ISR-подачи звука (все такие блоки в реальных файлах идут одним пакетом + * в самом начале потока) — поэтому синхронная блокирующая заливка здесь + * безопасна: играть ещё нечему. + * + * Формат payload (VGM spec, "ROM/RAM Image" data block, offset от начала + * data-payload, т.е. после opcode+0x66+type+4-байт общего размера): + * [0..3] ROM/RAM Size (информационное, не используется) + * [4..7] Start Address — смещение внутри SRAM карты (0-based) + * [8..] сами байты сэмпла/инструмент-заголовков + * + * Адресация чипа YMF278B: 2МБ встроенный ROM (General MIDI) + 1МБ SRAM, + * физически идущие в чипе одним адресным пространством — SRAM начинается + * сразу по границе 0x200000 (аппаратный CS чипа по адресному биту A21, + * см. datasheet, от платы не зависит). wavetblhdr=4 (4×0x80000=0x200000) + * выставлен так, чтобы номер сэмпла (wave) 384 указывал ровно на начало + * этой SRAM — тем самым абсолютный адрес = 0x200000 + Start Address. + * + * len = полный размер data-block (4-байтовое поле "size" из потока), + * включает 8-байтовый под-заголовок выше. + * ───────────────────────────────────────────────────────────────────── */ +static void wave_upload_block(void) +{ + /* Длина payload — прямо через fb_t32 (уже посчитан вызывающим кодом), + * без промежуточной локальной переменной — короче код (без спилла + * в стек-фрейм IX; ничто другое fb_t32 в течение этой функции не + * трогает). */ + /* Порядок записи регистров адреса — high, mid, low (как в референсном + * NedoOS-драйвере, opl4setmemoryaddress) — на реальном чипе порядок, + * похоже, важен, в отличие от того, что можно было бы предположить + * по документации эмулятора. Граница SRAM 0x200000 имеет нулевые + * младшие 2 байта, поэтому "+0x200000" — это "+0x20" к старшему + * байту адреса (предполагает startAddr < 0xE00000 — с большим + * запасом для реальных VGM). */ + asm_read_2bytes(); asm_read_2bytes(); /* ROM/RAM Size (4 байта) — не используется */ + + /* Синхронно (не через cmd-buffer) включить NEW2 в FM Bank1 (рег.#05=3): + * без этого чип игнорирует ЛЮБЫЕ обращения к wave-части, включая + * доступ к памяти. CMDBLK_INIT_OPL4 делает то же самое через ISR- + * буфер — но применится только когда реально запустится ISR, а эта + * заливка идёт синхронно ДО этого момента. Заинлайнено — вызывается + * только отсюда. Регистр #02 переключается ниже непосредственно + * перед заливкой и сразу возвращается в sound-generation mode. */ + __asm + call _chip_wait + ld a, #0x05 + out (0xC6), a + call _chip_wait + ld a, #0x03 + out (0xC7), a + __endasm; + + /* Переиспользуем существующие static (fb_ch/fb_b1/fb_b2) вместо + * локальных переменных — короче код (без спилла в стек-фрейм IX), + * ветки, где они обычно используются (AY/YM2203, чтение 2 байт), + * с этой не пересекаются. */ + /* Start Address в VGM записан в Intel byte order (little-endian). + * Регистры YMF278B #03/#04/#05, наоборот, принимают адрес как + * high/mid/low. Поэтому читаем младший байт в fb_b2, а старший из + * используемых 24 бит — в fb_ch. Прежний порядок случайно работал + * для адресов вида 0x00xx00 (например, 0x001800), но перемешивал + * почти все последующие куски ROM/RAM image. */ + fb_b2 = asm_read_byte(); /* младший байт адреса */ + fb_b1 = asm_read_byte(); /* средний байт адреса */ + fb_ch = asm_read_byte(); /* старший байт (до добавления базы SRAM) */ + asm_read_byte(); /* старший байт 32-битного поля (>16 МБ) */ + + /* Блок покрывает таблицу заголовков, только если это 0x84 (ROM Image) + * И его начало — самый старт RAM-региона (адрес 0). Именно так + * выглядят все реальные файлы этого типа (заголовки всегда идут + * первыми). Более общий случай (старт не с нуля) не поддерживаем — + * не встречался ни в одном реальном VGM. */ + hdr_recs = (fb_reg == 0x84 && (fb_ch | fb_b1 | fb_b2) == 0) ? 128 : 0; + hdr_sub = 0; + + /* Порядок записи регистров #03/#04/#05 (hi/mid/lo) — как в референсном + * NedoOS-драйвере (opl4setmemoryaddress): high, mid, low. */ + wave_out_reg(0x03); wave_out_dat(fb_ch + 0x20); /* + база SRAM (0x200000) */ + wave_out_reg(0x04); wave_out_dat(fb_b1); + wave_out_reg(0x05); wave_out_dat(fb_b2); + + /* Регистр #02: 0x11 = WT header base 0x200000 + memory access mode. + * Ранее адрес и значение были перепутаны (#11 <- #02), поэтому SRAM + * фактически не переходила в режим записи. */ + wave_out_reg(0x02); wave_out_dat(0x11); + + wave_out_reg(0x06); /* регистр данных памяти — дальше только данные, + * memaddr авто-инкрементируется самим чипом */ + fb_t32 -= 8; + + if (hdr_recs) + opl4_rom_loaded = 1; + + /* Первые 128 заголовков ROM image требуют побайтного патча адреса. */ + while (hdr_recs && fb_t32) { + uint8_t b = asm_read_byte(); + if (hdr_sub == 0) + b |= 0x20; /* взвести адресный бит 0x200000 */ + if (++hdr_sub == 12) { + hdr_sub = 0; + hdr_recs--; + } + wave_out_dat(b); + fb_t32--; + } + + /* Остаток блока — быстрыми кусками до границы VPL-страницы. */ + while (fb_t32) { + fb_w = (uint16_t)(0u - (uint16_t)fb_rp); /* байт до wrap #FFFF->#0000 */ + if (fb_t32 < fb_w) + fb_w = (uint16_t)fb_t32; + wave_upload_fast(); + fb_t32 -= fb_w; + if (!(uint16_t)fb_rp) { + fb_cpg++; + wc_mngcvpl(fb_cpg); + fb_rp = (uint8_t *)0xC000; + } + } + + wave_out_reg(0x02); wave_out_dat(0x10); /* sound generation, WT base 0x200000 */ +} + +/* ───────────────────────────────────────────────────────────────────── + * asm_emit_skip_spec — запись CMD_SKIP_TICKS + spec_mask в cmd buffer + * + * Пишет: [CMD_SKIP_TICKS, val, activity_lo, activity_hi]. + * OPL4 KeyOn mask складывается с обычным spec_mask: каналы 16..23 + * сворачиваются на полосы 0..7, каналы 8..15 остаются на 8..15. + * Обнуляет spec_mask, продвигает fb_wp на 4. + * Заменяет 6-строчный C-паттерн, встречающийся 9× в fill_buffer/emit_wait. + * + * Вход: A = byte1 (tick count минус 1, или литерал). + * Clobbers: A, C, HL. + * Стоимость: ~119 T (+ call 17 = 136 T итого). + * ───────────────────────────────────────────────────────────────────── */ +static void asm_emit_skip_spec(uint8_t val) __naked { + __asm + ld c, a ; 4 C = val (byte1) + ld hl, (_fb_wp) ;16 + ld (hl), #0x50 ;10 CMD_SKIP_TICKS = 0x50 + inc hl ; 6 + ld (hl), c ; 7 val + inc hl ; 6 + ld a, (_spec_wave_active) + ld c, a + ld a, (_spec_wave_active+2) + or c ; wave ch0..7 | ch16..23 + ld c, a + ld a, (_spec_mask) + or c + ld (hl), a ; activity bits 0..7 + inc hl ; 6 + ld a, (_spec_mask+1) + ld c, a + ld a, (_spec_wave_active+1) + or c + ld (hl), a ; activity bits 8..15 + inc hl ; 6 + ld (_fb_wp), hl ;16 advance write pointer + ;; spec_mask = 0 + ld hl, #0x0000 ;10 + ld (_spec_mask), hl ;16 + ret ;10 total: 153 T + __endasm; + (void)val; +} + +/* ───────────────────────────────────────────────────────────────────── + * asm_emit_opl_b0 — запись CMD_WRITE_B0 + tail-call spectrum_opl_b0 + * + * Читает fb_b1, fb_b2 (установлены asm_read_2bytes). + * Пишет [CMD_WRITE_B0, reg, val, x] в fb_wp, продвигает fb_wp += 4. + * Tail-call spectrum_opl_b0(A=reg, L=val) — его RET возвращает + * управление вызывающему. + * + * Стоимость: ~128 T тело + 17 call + 10 jp = 155 T итого. + * Было ~223 T (SDCC codegen): экономия ~68 T на каждую запись OPL Bank0. + * ───────────────────────────────────────────────────────────────────── */ +static void asm_emit_opl_b0(void) __naked { + __asm + ld hl, (_fb_wp) ;16 + ld (hl), #0x40 ;10 CMD_WRITE_B0 + inc hl ; 6 + ld a, (_fb_b1) ;13 + ld d, a ; 4 save reg + ld (hl), a ; 7 [1] = reg + inc hl ; 6 + ld a, (_fb_b2) ;13 + ld (hl), a ; 7 [2] = val + inc hl ; 6 + inc hl ; 6 skip [3] padding + ld (_fb_wp), hl ;16 fb_wp += 4 + ld l, a ; 4 L = val (fb_b2 still in A) + ld a, d ; 4 A = reg (fb_b1) + jp _spectrum_opl_b0 ;10 tail-call + __endasm; +} + +/* ───────────────────────────────────────────────────────────────────── + * asm_emit_opl_b1 — запись CMD_WRITE_B1 + tail-call spectrum_opl_b1 + * Аналогично asm_emit_opl_b0, но для OPL3 Bank 1. + * ───────────────────────────────────────────────────────────────────── */ +static void asm_emit_opl_b1(void) __naked { + __asm + ld hl, (_fb_wp) ;16 + ld (hl), #0x80 ;10 CMD_WRITE_B1 + inc hl ; 6 + ld a, (_fb_b1) ;13 + ld d, a ; 4 + ld (hl), a ; 7 + inc hl ; 6 + ld a, (_fb_b2) ;13 + ld (hl), a ; 7 + inc hl ; 6 + inc hl ; 6 + ld (_fb_wp), hl ;16 + ld l, a ; 4 + ld a, d ; 4 + jp _spectrum_opl_b1 ;10 tail-call + __endasm; +} + +/* ───────────────────────────────────────────────────────────────────── + * asm_emit_saa — SAA1099 0xBD handler (inline asm) + * + * Вызывается ПОСЛЕ asm_read_2bytes() (fb_b1 = reg, fb_b2 = val). + * + * Логика: + * fb_is_vgm==0 (cmdblk) → emit raw CMD_WRITE_SAA, return + * VGM mode: + * bit7=1 (chip1): + * cfg_saa_mode==0 → skip (return) + * cfg_saa_mode==1 → strip bit7, emit, spectrum_saa + * cfg_saa_mode==2 → emit raw, spectrum_saa2 + * bit7=0 (chip0): + * cfg_saa_mode==1 → skip (return) + * else → emit raw, spectrum_saa + * + * CMD_WRITE_SAA = 0x60. spectrum_saa/saa2: A=reg, L=val. + * Clobbers: A, B, C, D, E, H, L. + * ───────────────────────────────────────────────────────────────────── */ +static void asm_emit_saa(void) __naked { + __asm + + ;; D = reg (fb_b1), E = val (fb_b2) + ld a, (_fb_b1) ;13 + ld d, a ; 4 + ld a, (_fb_b2) ;13 + ld e, a ; 4 + + ;; cmdblk (fb_is_vgm==0) → emit raw, no spectrum + ld a, (_fb_is_vgm) ;13 + or a, a ; 4 + jr nz, 200$ ;12/7 + call 250$ + ret + +200$: + ;; VGM mode — A = cfg_saa_mode + ld a, (_cfg_saa_mode) ;13 + bit 7, d ; 8 + jr nz, 210$ ;12/7 chip 1 + + ;; ── Chip 0: skip if mode==1 ── + cp a, #1 ; 7 + ret z ; skip chip0 in mode 1 + jr 230$ ; → emit + spectrum_saa + +210$: + ;; ── Chip 1: skip if mode==0 ── + or a, a ; 4 + ret z ; skip chip1 in mode 0 + cp a, #2 ; 7 + jr z, 220$ ; → turbo + + ;; mode 1: strip bit7 → play on chip 0 + res 7, d ; 8 + ;; fall through to 230$ (emit + spectrum_saa) + +230$: + ;; emit D,E → spectrum_saa(A=reg, L=val) + call 250$ + ld a, d ; 4 + ld l, e ; 4 + jp _spectrum_saa ;10 + +220$: + ;; turbo: emit raw → spectrum_saa2(A=reg&0x7F, L=val) + call 250$ + ld a, d ; 4 + and a, #0x7F ; 7 + ld l, e ; 4 + jp _spectrum_saa2 ;10 + + ;; ── shared: write [0x60, D, E, pad] to fb_wp ── +250$: + ld hl, (_fb_wp) ;16 + ld (hl), #0x60 ;10 CMD_WRITE_SAA + inc hl ; 6 + ld (hl), d ; 7 + inc hl ; 6 + ld (hl), e ; 7 + inc hl ; 6 + inc hl ; 6 + ld (_fb_wp), hl ;16 + ret ;10 + + __endasm; +} + +/* ───────────────────────────────────────────────────────────────────── + * asm_scale_fm — FM F-Number масштабирование: fb_scaled = fb_scaled × fm_mul / 7 + * + * Все используемые FM ratios сводятся к N/7: + * fm_mul=0 → bypass (NTSC 45/44 ≈ 1.023, в рамках 5% допуска) + * fm_mul=3 → ×3/7 (YM2203 @ 1.5 МГц) + * fm_mul=5 → ×5/7 (YM2203 @ 2.5 МГц) + * fm_mul=6 → ×6/7 (YM2203 @ 3 МГц) + * fm_mul=8 → ×8/7 (YM2203 @ 4 МГц) + * + * Умножение: shifts + adds (~30 T). + * Деление на 7: binary long division 16 итераций (~770 T). + * Итого ~840 T — против ~1200 T у SDCC __muluint + __divuint. + * + * Вход: fb_scaled = fnum (11-bit, 0..2047). + * Выход: fb_scaled = fnum × fm_mul / 7. + * Clobbers: A, B, D, E, H, L. + * ───────────────────────────────────────────────────────────────────── */ +static void asm_scale_fm(void) __naked { + __asm + ld a, (_fm_mul) + or a + ret z ; fm_mul=0 → bypass + + ld hl, (_fb_scaled) ; HL = fnum + + ;; ── Умножение fnum x N (dispatch по fm_mul) ── + cp #3 + jr z, sfm_mul3 + cp #5 + jr z, sfm_mul5 + cp #6 + jr z, sfm_mul6 + ;; fm_mul=8: x8 + add hl, hl ; x2 + add hl, hl ; x4 + add hl, hl ; x8 + jr sfm_div7 + +sfm_mul3: + ld d, h + ld e, l ; DE = fnum + add hl, hl ; x2 + add hl, de ; x3 + jr sfm_div7 + +sfm_mul5: + ld d, h + ld e, l ; DE = fnum + add hl, hl ; x2 + add hl, hl ; x4 + add hl, de ; x5 + jr sfm_div7 + +sfm_mul6: + ld d, h + ld e, l ; DE = fnum + add hl, hl ; x2 + add hl, de ; x3 + add hl, hl ; x6 + ;; fall through + + ;; ── Деление HL на 7, результат в HL ────────── + ;; Binary long division: 16-bit dividend, 8-bit divisor. + ;; Остаток в A (всегда 0..6, помещается в 8 бит). + ;; Максимум: 2047x8 = 16376; 16376/7 = 2339. +sfm_div7: + xor a ; A = remainder = 0 + ld b, #16 ; 16 бит +sfm_dloop: + add hl, hl ; shift dividend/quotient left, MSB → CF + rla ; CF → remainder bit 0 + cp #7 ; remainder >= 7? + jr c, sfm_dskip ; нет → пропуск + sub #7 ; remainder -= 7 + inc l ; set quotient bit +sfm_dskip: + djnz sfm_dloop ; next bit + + ld (_fb_scaled), hl ; store result + ret + __endasm; +} + +/* ───────────────────────────────────────────────────────────────────── + * emit_wait — вставить паузу tk тиков (ISR) + * + * Использует два механизма паузы: + * CMD_SKIP_TICKS(N) — N=0..ISR_TICKS_PER_FRAME-1, пауза N+1 тик. + * ISR не вызывается (экономия CPU for main loop). + * CMD_WAIT(val) — обычная пауза val+1 тик (ISR функционирует). + * + * ИНВАРИАНТ: значение N для CMD_SKIP_TICKS строго < ISR_TICKS_PER_FRAME. + * N ≥ ISR_TICKS_PER_FRAME → полный оборот pos_table → ISR промахивается + * мимо своей INT-позиции → каскадный пропуск кадров, падение темпа ×10+. + * + * Стратегия: + * Для пауз ≤ ISR_TICKS_PER_FRAME: CMD_SKIP_TICKS (1 команда, ISR молчит) + * Для пауз > ISR_TICKS_PER_FRAME: CMD_WAIT(основная часть) + CMD_SKIP_TICKS(≤ ISR_TICKS_PER_FRAME-1) + * Перед секундным рубежом: аналогично + CMD_INC_SEC + * + * Макс. расход буфера на один вызов (для tk≤4095): + * 2 sec boundary × 12 + remainder 8 = 32 байт + * Зазор: вызывающий обязан обеспечить off < CMD_BUF_SIZE - 48 + * ───────────────────────────────────────────────────────────────────── */ +static void emit_wait(uint16_t tk) +{ + while (tk > 0) + { + /* ── SEC-рубеж: бюджет помещается в остаток ─────────── */ + if (vgm_sec_budget <= tk) + { + fb_w = vgm_sec_budget; + + if (fb_w > ISR_TICKS_PER_FRAME) { + /* Большой бюджет: CMD_WAIT(основная часть) + CMD_SKIP_TICKS */ + fb_w -= ISR_TICKS_PER_FRAME; + fb_wp[0] = CMD_WAIT; + fb_wp[1] = (uint8_t)(fb_w - 1); + fb_wp[2] = (uint8_t)((fb_w - 1) >> 8); + fb_wp += 4; + asm_emit_skip_spec(ISR_TICKS_PER_FRAME - 1); + } else if (fb_w >= 1) { + asm_emit_skip_spec((uint8_t)(fb_w - 1)); + } + /* CMD_INC_SEC */ + fb_wp[0] = CMD_INC_SEC; + fb_wp += 4; + + tk -= vgm_sec_budget; + vgm_sec_budget = ISR_FREQ; + continue; + } + + /* ── Остаток: tk < sec_budget ───────────────────────── */ + if (tk > ISR_TICKS_PER_FRAME) { + /* Более кадра: CMD_WAIT(основная часть) + CMD_SKIP_TICKS(остаток) */ + fb_w = tk - ISR_TICKS_PER_FRAME; + fb_wp[0] = CMD_WAIT; + fb_wp[1] = (uint8_t)(fb_w - 1); + fb_wp[2] = (uint8_t)((fb_w - 1) >> 8); + fb_wp += 4; + vgm_sec_budget -= fb_w; + tk = ISR_TICKS_PER_FRAME; + } + /* Последние ≤ISR_TICKS_PER_FRAME тиков через SKIP (экономия CPU) */ + asm_emit_skip_spec((uint8_t)(tk - 1)); + vgm_sec_budget -= tk; + tk = 0; + } +} + +/* ───────────────────────────────────────────────────────────────────── + * vgm_fill_buffer (v5 — HL queue integrated) + * + * Заполняет один командный буфер ISR целиком (512 байт), управляя + * переходами между высокоуровневыми командами (HL queue): + * + * HLCMD_CMDBLK — подключает страницу cmdblocks, парсит VGM-опкоды + * блока в ISR-команды; при 0x66 переходит к следующей + * HL-команде и продолжает заполнять ТОТ ЖЕ буфер. + * HLCMD_PLAY — воспроизводит VGM из мегабуфера; при заполнении + * буфера сохраняет состояние и выходит; при 0x66 + * переходит дальше по очереди. + * HLCMD_LOOP — перематывает к loop-точке, морфирует в PLAY. + * HLCMD_ISR_DONE— пишет CMD_ISR_DONE в буфер, выходит досрочно. + * + * EOF-проверка удалена из горячего цикла — при загрузке в конец + * VGM-данных вписывается 0x66-сентинел (см. main.c write_vgm_sentinel). + * ───────────────────────────────────────────────────────────────────── */ + +void vgm_fill_buffer(uint8_t buf_idx) +{ + fb_buf = buf_idx ? cmd_buf_b : cmd_buf_a; + fb_wp = fb_buf; + fb_end = fb_buf + (CMD_BUF_SIZE - 48); + + /* Пауза — заполнить тишиной */ + if (vgm_paused) { + fb_buf[0] = CMD_SKIP_TICKS; fb_buf[1] = ISR_TICKS_PER_FRAME - 1; + fb_buf[4] = CMD_END_BUF; + return; + } + + fb_wacc = vgm_wait_accum; + + /* ── Макрос: проверка границы страницы после fb_rp++ ─────────── + * При inc HL через #FFFF -> #0000 переключаем VPL-страницу. + * Fast-path (99.99 %): одна проверка HL==0, ~15 T-states. */ +#define PAGE_CHK() do { \ + if (!(uint16_t)(fb_rp)) { fb_cpg++; wc_mngcvpl(fb_cpg); fb_rp = (uint8_t *)0xC000; } \ +} while(0) + + /* ═══════════════════════════════════════════════════════════════ + * next_hl — диспетчер высокоуровневых команд + * Вызывается при входе и при каждом 0x66 (конец блока/файла). + * НЕ выходит из функции — продолжает заполнять тот же буфер. + * ═══════════════════════════════════════════════════════════════ */ +next_hl: + if (vgm_hl_pos >= vgm_hl_len) { + /* Очередь исчерпана — заполнить остаток тишиной */ + if (fb_wp == fb_buf) { + fb_buf[0] = CMD_SKIP_TICKS; fb_buf[1] = ISR_TICKS_PER_FRAME - 1; + fb_wp = fb_buf + 4; + } + goto finish; + } + + fb_e = &vgm_hl_queue[vgm_hl_pos]; + + // обработка высокоуровневой команды — установка fb_rp, fb_cpg, fb_is_vgm + switch (fb_e->cmd) { + + case HLCMD_CMDBLK: + /* Подключить страницу cmdblocks, найти адрес блока */ + wc_mngc_pl(CMDBLK_PAGE); + fb_rp = (uint8_t *)((const uint16_t *)0xC000)[fb_e->param]; + fb_cpg = 0; /* не используется для cmdblk, PAGE_CHK не сработает */ + fb_is_vgm = 0; + break; + + case HLCMD_PLAY: + fb_cpg = vgm_cur_page; + fb_rp = (uint8_t *)vgm_read_ptr; + wc_mngcvpl(fb_cpg); + /* Переподключить LUT: WC API (print_line и т.д.) мог перемапить Window 0 */ + if (vgm_freq_mode == FREQ_MODE_TABLE) + wc_mng0_pl(freq_lut_page); + fb_is_vgm = 1; + break; + + case HLCMD_LOOP: + if (!vgm_rewind_to_loop()) { + /* Нет loop-точки — пропустить */ + vgm_hl_pos++; + goto next_hl; + } + vgm_loop_count++; + fb_e->cmd = HLCMD_PLAY; /* чтобы не перематывать снова */ + fb_cpg = vgm_cur_page; + fb_rp = (uint8_t *)vgm_read_ptr; + fb_wacc = vgm_wait_accum; + wc_mngcvpl(fb_cpg); + if (vgm_freq_mode == FREQ_MODE_TABLE) + wc_mng0_pl(freq_lut_page); + fb_is_vgm = 1; + break; + + case HLCMD_ISR_DONE: + fb_wp[0] = CMD_ISR_DONE; + fb_wp += 4; + vgm_hl_pos++; + goto finish; + + default: + vgm_hl_pos++; + goto next_hl; + } + + fb_budget = VGM_FILL_CMD_BUDGET; + fb_yield_ticks = 0; + + /* ═══════════════════════════════════════════════════════════════ + * Горячий цикл — разбор VGM-опкодов → ISR-команды + * EOF-проверка удалена (гарантирован 0x66 в данных). + * ═══════════════════════════════════════════════════════════════ */ + while (fb_wp < fb_end) // зазор 48 байт для emit_wait (макс. 32 байта) + CMD_END_BUF (4 байта) + safety + { + /* ── Читаем опкод ──────────────────────────────────────── */ + fb_op = asm_read_byte(); + + /* ═══════ OPL2 запись (0x5A) — самая частая ═══════ */ + if (fb_op == 0x5A) + { + asm_read_2bytes(); + asm_emit_opl_b0(); + goto do_budget; + } + + /* ═══════ OPL3 Bank 1 (0x5F) ═══════ */ + if (fb_op == 0x5F) + { + asm_read_2bytes(); + asm_emit_opl_b1(); + goto do_budget; + } + + /* ═══════ OPL Bank 0: 0x5B(YM3526) / 0x5C(Y8950) / 0x5E(OPL3bk0) ═══════ + * Диапазон 0x5B..0x5E — одна проверка, все → bank0. + * 0x5D (YMZ280B) не поддерживается — если встретится, запись в bank0 безвредна. */ + if ((uint8_t)(fb_op - 0x5B) <= (0x5E - 0x5B)) + { + asm_read_2bytes(); + asm_emit_opl_b0(); + goto do_budget; + } + + /* ═══════ YMF278B / OPL4 (0xD0) — port pp, reg aa, data dd ═══════ + * pp bit7 = chip select (dual-chip; на ZXM-MoonSound один чип — игн.) + * pp & 0x7F: 0 = FM Bank0 (те же регистры, что 0x5E) → CMD_WRITE_B0 + * 1 = FM Bank1 (те же регистры, что 0x5F) → CMD_WRITE_B1 + * 2 = Wave-часть (порты #7E/#7F) → CMD_WRITE_WAVE + * прочее — зарезервировано, молча пропускается */ + if (fb_op == 0xD0) + { + fb_reg = asm_read_byte(); + asm_read_2bytes(); + fb_reg &= 0x7F; + if (fb_reg == 0) + asm_emit_opl_b0(); + else if (fb_reg == 1) + asm_emit_opl_b1(); + else if (fb_reg == 2) { + /* Управляющие регистры #00-#07 не отдаём из VGM на карту: + * загрузчик сам держит #02=0x10. После переноса ROM image + * отображаем исходные номера волн на 128 SRAM-заголовков + * (точно как opl4writewavemusiconly в NedoOS). */ + if (fb_b1 < 0x08) + goto do_budget; + if (opl4_rom_loaded) { + if (fb_b1 < 0x20) + fb_b2 |= 0x80; /* low byte: 0x80..0xFF */ + else if (fb_b1 < 0x38) + fb_b2 |= 0x01; /* high bit: wave 384..511 */ + } + fb_wp[0] = CMD_WRITE_WAVE; + fb_wp[1] = fb_b1; + fb_wp[2] = fb_b2; + fb_wp += 4; + spectrum_wave(fb_b1, fb_b2); + } + goto do_budget; + } + + /* ═══════ Frame wait 1/60 (0x62) ═══════ */ + if (fb_op == 0x62) + { asm_wait_62(); goto do_wait; } + + /* ═══════ Frame wait 1/50 (0x63) ═══════ */ + if (fb_op == 0x63) + { asm_wait_63(); goto do_wait; } + + /* ═══════ Короткая пауза 1-16 samples (0x70-0x7F) ═══════ */ +#ifndef VGM_NO_SHORT_WAITS + if ((fb_op & 0xF0) == 0x70) + { asm_short_wait(); goto do_wait; } +#else + /* VGM_NO_SHORT_WAITS: пропуск коротких пауз 0x70-0x7F */ + if ((fb_op & 0xF0) == 0x70) continue; +#endif + + /* ═══════ Произвольная пауза (0x61) ═══════ */ + if (fb_op == 0x61) + { + asm_read_2bytes(); + asm_arb_wait(); + goto do_wait; + } + + /* ═══════ YM2203 SSG+FM (0x55) ═══════ */ + if (fb_op == 0x55) + { + asm_read_2bytes(); + spectrum_ay(fb_b1, fb_b2); + if (vgm_freq_mode != FREQ_MODE_NATIVE) { + /* ── PSG tone period (reg 0x00-0x05): 12-bit, парами lo/hi ── */ + if (fb_b1 <= 0x05) { + fb_ch = fb_b1 >> 1; /* 0,1,2 */ + if (fb_b1 & 1) { + /* hi-nibble → обновить shadow, пересчитать полные 12 бит */ + psg_shadow[fb_ch] = (psg_shadow[fb_ch] & 0x00FF) + | ((uint16_t)(fb_b2 & 0x0F) << 8); + } else { + /* lo-byte → обновить shadow */ + psg_shadow[fb_ch] = (psg_shadow[fb_ch] & 0x0F00) | fb_b2; + } + fb_scaled = freq_lut_base[psg_shadow[fb_ch]]; + if (fb_scaled > 0x0FFF) fb_scaled = 0x0FFF; + /* Отправляем оба lo+hi: scaled меняет оба байта одновременно */ + fb_wp[0] = CMD_WRITE_AY; + fb_wp[1] = fb_ch << 1; /* reg = 0,2,4 */ + fb_wp[2] = (uint8_t)(fb_scaled & 0xFF); + fb_wp += 4; + fb_wp[0] = CMD_WRITE_AY; + fb_wp[1] = (fb_ch << 1) | 1; /* reg = 1,3,5 */ + fb_wp[2] = (uint8_t)((fb_scaled >> 8) & 0x0F); + fb_wp += 4; + goto do_budget; + } + /* ── Noise period (reg 0x06): 5-bit, clamp ── */ + else if (fb_b1 == 0x06) { + fb_b2 = (uint8_t)freq_lut_base[fb_b2 & 0x1F]; + if (fb_b2 > 31) fb_b2 = 31; + } + /* ── FM F-Number (regs 0xA0-0xA2 lo, 0xA4-0xA6 hi+block) ── */ + else if (fm_mul && fb_b1 >= 0xA0 && fb_b1 <= 0xA6) { + uint8_t fmr = fb_b1 - 0xA0; /* 0..6 */ + if (fmr <= 2) { + fb_ch = fmr; + fm_fnum[fb_ch] = (fm_fnum[fb_ch] & 0x0700) | fb_b2; + } else if (fmr >= 4) { + fb_ch = fmr - 4; + fm_block[fb_ch] = (fb_b2 >> 3) & 0x07; + fm_fnum[fb_ch] = (fm_fnum[fb_ch] & 0x00FF) + | ((uint16_t)(fb_b2 & 0x07) << 8); + } else { + goto ym1_fm_passthru; /* 0xA3 */ + } + { + uint8_t blk = fm_block[fb_ch]; + fb_scaled = fm_fnum[fb_ch]; + asm_scale_fm(); + if (fb_scaled > 0x7FF) { + fb_scaled >>= 1; + if (fb_scaled > 0x7FF) fb_scaled = 0x7FF; + blk++; + if (blk > 7) blk = 7; + } + fb_wp[0] = CMD_WRITE_AY; + fb_wp[1] = 0xA4 + fb_ch; + fb_wp[2] = (blk << 3) | (uint8_t)((fb_scaled >> 8) & 0x07); + fb_wp += 4; + if (fmr <= 2) { + fb_wp[0] = CMD_WRITE_AY; + fb_wp[1] = 0xA0 + fb_ch; + fb_wp[2] = (uint8_t)(fb_scaled & 0xFF); + fb_wp += 4; + } + goto do_budget; + } + } +ym1_fm_passthru:; + } + fb_wp[0] = CMD_WRITE_AY; + fb_wp[1] = fb_b1; fb_wp[2] = fb_b2; + fb_wp += 4; + goto do_budget; + } + + /* ═══════ YM2203 #2 SSG+FM (0xA5) ═══════ + * VGM dual chip: 0x55 → 0xA5 (последняя цифра 0x5n → 0xAn) */ + if (fb_op == 0xA5) + { + asm_read_2bytes(); + spectrum_ay2(fb_b1, fb_b2); + if (vgm_freq_mode != FREQ_MODE_NATIVE) { + if (fb_b1 <= 0x05) { + fb_ch = fb_b1 >> 1; + if (fb_b1 & 1) { + psg_shadow2[fb_ch] = (psg_shadow2[fb_ch] & 0x00FF) + | ((uint16_t)(fb_b2 & 0x0F) << 8); + } else { + psg_shadow2[fb_ch] = (psg_shadow2[fb_ch] & 0x0F00) | fb_b2; + } + fb_scaled = freq_lut_base[psg_shadow2[fb_ch]]; + if (fb_scaled > 0x0FFF) fb_scaled = 0x0FFF; + fb_wp[0] = CMD_WRITE_AY2; + fb_wp[1] = fb_ch << 1; + fb_wp[2] = (uint8_t)(fb_scaled & 0xFF); + fb_wp += 4; + fb_wp[0] = CMD_WRITE_AY2; + fb_wp[1] = (fb_ch << 1) | 1; + fb_wp[2] = (uint8_t)((fb_scaled >> 8) & 0x0F); + fb_wp += 4; + goto do_budget; + } + else if (fb_b1 == 0x06) { + fb_b2 = (uint8_t)freq_lut_base[fb_b2 & 0x1F]; + if (fb_b2 > 31) fb_b2 = 31; + } + /* ── FM F-Number чип 2 (regs 0xA0-0xA6) ── */ + else if (fm_mul && fb_b1 >= 0xA0 && fb_b1 <= 0xA6) { + uint8_t fmr = fb_b1 - 0xA0; + if (fmr <= 2) { + fb_ch = fmr; + fm_fnum2[fb_ch] = (fm_fnum2[fb_ch] & 0x0700) | fb_b2; + } else if (fmr >= 4) { + fb_ch = fmr - 4; + fm_block2[fb_ch] = (fb_b2 >> 3) & 0x07; + fm_fnum2[fb_ch] = (fm_fnum2[fb_ch] & 0x00FF) + | ((uint16_t)(fb_b2 & 0x07) << 8); + } else { + goto ym2_fm_passthru; + } + { + uint8_t blk = fm_block2[fb_ch]; + fb_scaled = fm_fnum2[fb_ch]; + asm_scale_fm(); + if (fb_scaled > 0x7FF) { + fb_scaled >>= 1; + if (fb_scaled > 0x7FF) fb_scaled = 0x7FF; + blk++; + if (blk > 7) blk = 7; + } + fb_wp[0] = CMD_WRITE_AY2; + fb_wp[1] = 0xA4 + fb_ch; + fb_wp[2] = (blk << 3) | (uint8_t)((fb_scaled >> 8) & 0x07); + fb_wp += 4; + if (fmr <= 2) { + fb_wp[0] = CMD_WRITE_AY2; + fb_wp[1] = 0xA0 + fb_ch; + fb_wp[2] = (uint8_t)(fb_scaled & 0xFF); + fb_wp += 4; + } + goto do_budget; + } + } +ym2_fm_passthru:; + } + fb_wp[0] = CMD_WRITE_AY2; + fb_wp[1] = fb_b1; fb_wp[2] = fb_b2; + fb_wp += 4; + goto do_budget; + } + + /* ═══════ AY8910 dual (0xA0) ═══════ */ + if (fb_op == 0xA0) + { + asm_read_2bytes(); + if (fb_b1 & 0x80) + spectrum_ay2(fb_b1 & 0x7F, fb_b2); + else + spectrum_ay(fb_b1 & 0x7F, fb_b2); + if (vgm_freq_mode != FREQ_MODE_NATIVE) { + fb_reg = fb_b1 & 0x7F; + /* PSG tone period (reg 0-5): полный 12-бит через shadow */ + if (fb_reg <= 0x05) { + fb_ch = fb_reg >> 1; + if (fb_reg & 1) { + psg_shadow[fb_ch] = (psg_shadow[fb_ch] & 0x00FF) + | ((uint16_t)(fb_b2 & 0x0F) << 8); + } else { + psg_shadow[fb_ch] = (psg_shadow[fb_ch] & 0x0F00) | fb_b2; + } + fb_scaled = freq_lut_base[psg_shadow[fb_ch]]; + if (fb_scaled > 0x0FFF) fb_scaled = 0x0FFF; + /* AY (0xA0): отправляем оба байта сразу */ + fb_wp[0] = (fb_b1 & 0x80) ? CMD_WRITE_AY2 : CMD_WRITE_AY; + fb_wp[1] = fb_ch << 1; /* lo reg */ + fb_wp[2] = (uint8_t)(fb_scaled & 0xFF); + fb_wp += 4; + fb_wp[0] = (fb_b1 & 0x80) ? CMD_WRITE_AY2 : CMD_WRITE_AY; + fb_wp[1] = (fb_ch << 1) | 1; /* hi reg */ + fb_wp[2] = (uint8_t)((fb_scaled >> 8) & 0x0F); + fb_wp += 4; + goto do_budget; + } + /* Noise period (reg 6) */ + else if (fb_reg == 0x06) { + fb_b2 = (uint8_t)freq_lut_base[fb_b2 & 0x1F]; + if (fb_b2 > 31) fb_b2 = 31; + } + } + + fb_wp[0] = (fb_b1 & 0x80) ? CMD_WRITE_AY2 : CMD_WRITE_AY; + fb_wp[1] = fb_b1 & 0x7F; + fb_wp[2] = fb_b2; + fb_wp += 4; + goto do_budget; + } + + /* ═══════ SAA1099 dual (0xBD) ═══════ */ + /* Bit7 of reg byte selects chip (0=chip0, 1=chip1). + * cfg_saa_mode: 0=chip0 only(default), 1=chip1 on chip0, 2=turbo(both). + * cmdblk mode (fb_is_vgm==0): bypass filtering, pass raw. */ + if (fb_op == 0xBD) + { + asm_read_2bytes(); + asm_emit_saa(); + goto do_budget; + } + + /* ═══════ Конец данных (0x66) ═══════ */ + if (fb_op == 0x66) + { + if (fb_is_vgm) { + vgm_song_ended = 1; + vgm_cur_page = fb_cpg; + vgm_read_ptr = (uint16_t)fb_rp; + vgm_wait_accum = fb_wacc; + } + vgm_hl_pos++; + goto next_hl; + } + + /* ═══════ Data block (0x67) — редко ═══════ */ + if (fb_op == 0x67) + { + fb_rp++; PAGE_CHK(); /* 0x66 (compat байт) */ + fb_reg = asm_read_byte(); /* тип блока */ + asm_read_2bytes(); + fb_t32 = (uint16_t)fb_b1 | ((uint16_t)fb_b2 << 8); + asm_read_2bytes(); + fb_t32 |= ((uint32_t)fb_b1 << 16) | ((uint32_t)fb_b2 << 24); + + if (fb_reg == 0x84 || fb_reg == 0x87) { + /* YMF278B ROM/RAM Image — залить payload в SRAM карты */ + wave_upload_block(); + } else { + /* сохранить → vgm_skip → восстановить */ + vgm_read_ptr = (uint16_t)fb_rp; + vgm_cur_page = fb_cpg; + vgm_skip(fb_t32); + fb_rp = (uint8_t *)vgm_read_ptr; + fb_cpg = vgm_cur_page; + } + continue; + } + + /* ═══════ PCM RAM write (0x68) — редко ═══════ */ + if (fb_op == 0x68) + { + vgm_read_ptr = (uint16_t)fb_rp; + vgm_cur_page = fb_cpg; + vgm_skip(11); + fb_rp = (uint8_t *)vgm_read_ptr; + fb_cpg = vgm_cur_page; + continue; + } + + /* ═══════ DAC Stream (0x90-0x95) — редко ═══════ */ + if (fb_op >= 0x90 && fb_op <= 0x95) + { + static const uint8_t dac_len[] = {4, 4, 5, 10, 1, 4}; + fb_n = dac_len[fb_op - 0x90]; + while (fb_n--) { fb_rp++; PAGE_CHK(); } + continue; + } + + /* ═══════ Общий пропуск (все остальные команды) ═══════ */ + /* Размеры операндов по VGM spec v1.71. + * Все явно обработанные опкоды (0x5A,0x5B,..,0xA0,0xBD, + * 0x61-0x63,0x66-0x68,0x7n,0x90-0x95) сюда не попадают. */ + { + fb_n = 0; + if (fb_op < 0x30) fb_n = 0; /* ниже 0x30 — нет данных */ + else if (fb_op < 0x40) fb_n = 1; /* 0x30-0x3F: reserved 1-op */ + else if (fb_op < 0x4F) fb_n = 2; /* 0x40-0x4E: reserved 2-op */ + else if (fb_op <= 0x50) fb_n = 1; /* 0x4F GG PSG / 0x50 SN76489 */ + else if (fb_op < 0x60) fb_n = 2; /* 0x51-0x5F: chip writes 2-op */ + else if (fb_op == 0x64) fb_n = 3; /* 0x64: wait override (cc nn nn) */ + else if (fb_op < 0xA0) fb_n = 0; /* 0x8n DAC+wait, прочее 0x6x/9x */ + else if (fb_op < 0xC0) fb_n = 2; /* 0xA0-0xBF: chip writes 2-op */ + else if (fb_op < 0xE0) fb_n = 3; /* 0xC0-0xDF: chip writes 3-op */ + else fb_n = 4; /* 0xE0-0xFF: chip writes 4-op */ + while (fb_n--) { fb_rp++; PAGE_CHK(); } + } + continue; + + /* ═══════ Обработка задержек (wait paths устанавливают fb_tk и fb_wacc напрямую) ═══════ */ + do_wait: + /* Компенсация: бюджетные yield-ы уже потратили реальные ISR-тики. + * Вычитаем их из VGM-задержки, чтобы общий темп не замедлялся. */ + if (fb_tk > fb_yield_ticks) + // реальная пауза меньше заявленной на fb_yield_ticks, компенсируем + fb_tk -= fb_yield_ticks; + else + // пауза полностью съедена накладными расходами, не ждем вообще + fb_tk = 0; + + // сбросить счетчик накладных расходов, они уже компенсированы + fb_yield_ticks = 0; + + if (fb_tk) { + // есть что ждать + + if (fb_tk <= ISR_TICKS_PER_FRAME && fb_tk < vgm_sec_budget) { + // короткая пауза, помещается в один кадр — пропустить через SKIP + asm_emit_skip_spec((uint8_t)(fb_tk - 1)); + vgm_sec_budget -= fb_tk; // учесть в сек. бюджете + } else { + emit_wait(fb_tk); + } + + // после паузы бюджет обновлен, можно продолжать заполнять буфер + fb_budget = VGM_FILL_CMD_BUDGET; + } + continue; + + do_budget: + // Бюджет команд: после каждого опкода уменьшаем, при 0 — пауза или yield + if (--fb_budget == 0) + { + // бюджет исчерпан — сделать паузу, восстановить бюджет + + if (vgm_sec_budget > 1) { + // Остаток бюджета больше одного кадра — сделать yield (пауза через CMD_INC_SEC) + asm_emit_skip_spec(0); + vgm_sec_budget--; + fb_yield_ticks++; /* запомнить: этот тик — накладной расход */ + } else { + // Остаток бюджета меньше одного кадра — сделать реальную паузу + emit_wait(1); + } + fb_budget = VGM_FILL_CMD_BUDGET; + } + } + +#undef PAGE_CHK + + /* Буфер заполнен — сохранить VGM-состояние если читали файл */ + if (fb_is_vgm) { + vgm_cur_page = fb_cpg; + vgm_read_ptr = (uint16_t)fb_rp; + vgm_wait_accum = fb_wacc; + } + +finish: + /* Принудительная пауза перед CMD_END_BUF — гарантирует, что ISR + * не обработает хвост этого буфера + голову следующего за один тик + * (иначе пропуск INT-позиции и падение темпа в десятки раз). */ + asm_emit_skip_spec(0); + + fb_wp[0] = CMD_END_BUF; +} + +/* ── Буферы GD3 metadata ─────────────────────────────────────────── */ +char vgm_gd3_track[VGM_GD3_LEN]; +char vgm_gd3_game[VGM_GD3_LEN]; +char vgm_gd3_system[VGM_GD3_LEN]; +char vgm_gd3_author[VGM_GD3_LEN]; + +/* ───────────────────────────────────────────────────────────────────── + * vgm_parse_gd3 — разбор GD3-тега (UTF-16LE → ASCII, только English) + * + * GD3 v1.00 содержит 11 UTF-16LE строк: + * 0: Track title (EN) 1: Track title (JP) + * 2: Game name (EN) 3: Game name (JP) + * 4: System name (EN) 5: System name (JP) + * 6: Author (EN) 7: Author (JP) + * 8: Date 9: Ripper 10: Notes + * + * Нам нужны только EN: индексы 0, 2, 4, 6. + * ───────────────────────────────────────────────────────────────────── */ +void vgm_parse_gd3(void) +{ + uint8_t *base = (uint8_t *)0xC000; + uint32_t gd3_off_field; + uint32_t gd3_abs; + uint8_t page; + uint16_t ptr; + uint8_t str_idx; + char *dst; + uint8_t dst_max; + uint8_t dst_pos; + + /* Очистить все поля */ + vgm_gd3_track[0] = '\0'; + vgm_gd3_game[0] = '\0'; + vgm_gd3_system[0] = '\0'; + vgm_gd3_author[0] = '\0'; + + /* Страница 0 для чтения заголовка */ + wc_mngcvpl(0); + base = (uint8_t *)0xC000; + + /* GD3 offset: 32-bit LE в 0x14, относительно 0x14 */ + gd3_off_field = (uint32_t)base[0x14] + | ((uint32_t)base[0x15] << 8) + | ((uint32_t)base[0x16] << 16) + | ((uint32_t)base[0x17] << 24); + if (gd3_off_field == 0) + return; + + gd3_abs = 0x14UL + gd3_off_field; + + /* Переключаемся на страницу с GD3 */ + page = (uint8_t)(gd3_abs >> 14); + wc_mngcvpl(page); + base = (uint8_t *)0xC000; + ptr = (uint16_t)(gd3_abs & 0x3FFF); + + /* Проверяем сигнатуру "Gd3 " */ + if (base[ptr] != 'G' || base[ptr+1] != 'd' || + base[ptr+2] != '3' || base[ptr+3] != ' ') + return; + + /* Пропускаем: signature(4) + version(4) + data_length(4) = 12 */ + ptr += 12; + if (ptr >= 0x4000) { page++; wc_mngcvpl(page); ptr -= 0x4000; } + + /* Читаем UTF-16LE строки, нам нужны EN: #0, #2, #4, #6 */ + for (str_idx = 0; str_idx < 8; str_idx++) { + /* Выбираем буфер для EN-строк (чётные) */ + dst = 0; + dst_max = 0; + if (str_idx == 0) { dst = vgm_gd3_track; dst_max = VGM_GD3_LEN - 1; } + if (str_idx == 2) { dst = vgm_gd3_game; dst_max = VGM_GD3_LEN - 1; } + if (str_idx == 4) { dst = vgm_gd3_system; dst_max = VGM_GD3_LEN - 1; } + if (str_idx == 6) { dst = vgm_gd3_author; dst_max = VGM_GD3_LEN - 1; } + dst_pos = 0; + + /* Читаем UTF-16LE до нулевого терминатора 0x0000 */ + for (;;) { + uint8_t lo, hi; + lo = base[ptr]; + hi = base[ptr + 1]; + ptr += 2; + if (ptr >= 0x4000) { page++; wc_mngcvpl(page); ptr -= 0x4000; } + + if (lo == 0 && hi == 0) + break; /* конец строки */ + + /* ASCII: берём только lo если hi==0 и lo < 128 */ + if (dst && dst_pos < dst_max) { + if (hi == 0 && lo >= 0x20 && lo < 0x7F) + dst[dst_pos++] = (char)lo; + else + dst[dst_pos++] = '?'; /* non-ASCII → '?' */ + } + } + if (dst) + dst[dst_pos] = '\0'; + } +} + +/* ───────────────────────────────────────────────────────────────────── + * vgm_rewind_to_loop + * Перематывает к loop-точке, если она есть, и сбрасывает флаг окончания песни. + * Возвращает 1 если перемотано, 0 если loop-точки нет. + * Loop-точка устанавливается при загрузке VGM-файла, если в нём есть loop_offset. + * Loop-точка — абсолютный адрес в VGM-данных, который может быть в любом месте файла + * (обычно после заголовка, но может быть и в середине данных). + * При перемотке к loop-точке нужно восстановить состояние чтения (текущая страница, указатель чтения, накопленные паузы), + * чтобы продолжить воспроизведение оттуда без сбоев. + * ───────────────────────────────────────────────────────────────────── */ +uint8_t vgm_rewind_to_loop(void) +{ + if (!vgm_loop_addr) + return 0; + + vgm_cur_page = vgm_loop_page; + vgm_read_ptr = vgm_loop_addr; + vgm_song_ended = 0; + vgm_wait_accum = 0; + vgm_sec_budget = ISR_FREQ; + wc_mngcvpl(vgm_cur_page); + return 1; +} diff --git a/src_sdcc/lib/wc_api/build_lib.bat b/src_sdcc/lib/wc_api/build_lib.bat new file mode 100644 index 0000000..bf2496e --- /dev/null +++ b/src_sdcc/lib/wc_api/build_lib.bat @@ -0,0 +1,38 @@ +@echo off +REM =========================================================================== +REM build_lib.bat — Сборка статической библиотеки wc_api.lib +REM +REM Каждая функция WC API — отдельный .rel модуль. +REM Линковщик SDCC подтягивает из .lib только модули с нужными символами. +REM =========================================================================== + +setlocal + +cd /d "%~dp0" + +set ASFLAGS=-plosff +set BUILDDIR=..\..\build\wc_api +set LIBFILE=..\..\build\wc_api.lib + +REM --- Папка для .rel файлов --- +if not exist "%BUILDDIR%" mkdir "%BUILDDIR%" + +REM --- Сборка всех .s → .rel --- +for %%f in (*.s) do ( + sdasz80 %ASFLAGS% "%BUILDDIR%\%%~nf.rel" "%%f" >nul + if errorlevel 1 ( echo FAIL %%f && exit /b 1 ) +) + +REM --- Удалить старую библиотеку --- +if exist "%LIBFILE%" del "%LIBFILE%" + +REM --- Создать .lib архив (Windows cmd не раскрывает *.rel для sdar) --- +setlocal enabledelayedexpansion +set RELFILES= +for %%f in ("%BUILDDIR%\*.rel") do set RELFILES=!RELFILES! "%%f" +sdar -rc "%LIBFILE%" %RELFILES% >nul +if errorlevel 1 ( echo FAIL sdar && exit /b 1 ) +endlocal + +echo wc_api.lib OK +exit /b 0 diff --git a/src_sdcc/lib/wc_api/wc_adir.s b/src_sdcc/lib/wc_api/wc_adir.s new file mode 100644 index 0000000..9f9eb75 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_adir.s @@ -0,0 +1,11 @@ + .module wc_adir + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_adir +_wc_adir:: + ex af, af' + push ix + ld a, #0x38 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_chtosep.s b/src_sdcc/lib/wc_api/wc_chtosep.s new file mode 100644 index 0000000..673f8cf --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_chtosep.s @@ -0,0 +1,13 @@ + .module wc_chtosep + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_chtosep +_wc_chtosep:: + push ix + ld b, d + ld c, e ; BC = bufend (from DE) + ex de, hl ; DE = buf (from HL) + ld a, #0x34 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_curser.s b/src_sdcc/lib/wc_api/wc_curser.s new file mode 100644 index 0000000..5dc5bd3 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_curser.s @@ -0,0 +1,12 @@ + .module wc_curser + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_curser +_wc_curser:: + push ix + push hl + pop ix + ld a, #0x07 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_cursor.s b/src_sdcc/lib/wc_api/wc_cursor.s new file mode 100644 index 0000000..90e3b8e --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_cursor.s @@ -0,0 +1,12 @@ + .module wc_cursor + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_cursor +_wc_cursor:: + push ix + push hl + pop ix + ld a, #0x06 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_delfl.s b/src_sdcc/lib/wc_api/wc_delfl.s new file mode 100644 index 0000000..d831c0a --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_delfl.s @@ -0,0 +1,10 @@ + .module wc_delfl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_delfl +_wc_delfl:: + push ix + ld a, #0x4B + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_dmapl.s b/src_sdcc/lib/wc_api/wc_dmapl.s new file mode 100644 index 0000000..cfdcf54 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_dmapl.s @@ -0,0 +1,11 @@ + .module wc_dmapl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_dmapl +_wc_dmapl:: + ex af, af' + push ix + ld a, #0x0D + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_fentry.s b/src_sdcc/lib/wc_api/wc_fentry.s new file mode 100644 index 0000000..21bfa5e --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_fentry.s @@ -0,0 +1,15 @@ + .module wc_fentry + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_fentry +_wc_fentry:: + push ix + ; HL = name (sdcccall(1); WC нужен HL — уже на месте!) + ld a, #0x3B + call WC_ENTRY + ld a, #0 + jr z, _wc_fentry_end + inc a +_wc_fentry_end: + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_findnext.s b/src_sdcc/lib/wc_api/wc_findnext.s new file mode 100644 index 0000000..2de488b --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_findnext.s @@ -0,0 +1,22 @@ + .module wc_findnext + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_findnext +_wc_findnext:: + push ix + ld ix, #0 + add ix, sp + ld a, 4(ix) ; A = flags (from stack) + ex af, af' ; A' = flags + ex de, hl ; DE = entry_buf (SDCC HL → WC DE) + ld a, #0x3A + call WC_ENTRY + ld a, #0 + jr z, _wc_findnext_end + inc a +_wc_findnext_end: + pop ix + ; callee cleans 1 byte (flags) + pop hl + inc sp + jp (hl) diff --git a/src_sdcc/lib/wc_api/wc_gadrw.s b/src_sdcc/lib/wc_api/wc_gadrw.s new file mode 100644 index 0000000..909b6cc --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_gadrw.s @@ -0,0 +1,25 @@ + .module wc_gadrw + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_gadrw +_wc_gadrw:: + push ix + push hl ; save win (HL = 1st arg) + ld ix, #0 + add ix, sp + ld d, 6(ix) ; D = y + ld e, 7(ix) ; E = x + ld l, 0(ix) + ld h, 1(ix) ; HL = win + push hl + pop ix ; IX = win + ld a, #0x05 + call WC_ENTRY ; HL = result addr + ex de, hl ; DE = result (sdcccall(1) uint16) + pop hl ; remove saved win (use HL, keep DE) + pop ix ; restore caller IX + ; callee cleans 2 bytes (y+x); preserve DE = return value + pop hl ; HL = return address + inc sp + inc sp ; discard y(1)+x(1) + jp (hl) ; return, DE = result diff --git a/src_sdcc/lib/wc_api/wc_gdir.s b/src_sdcc/lib/wc_api/wc_gdir.s new file mode 100644 index 0000000..4508fe2 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_gdir.s @@ -0,0 +1,10 @@ + .module wc_gdir + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_gdir +_wc_gdir:: + push ix + ld a, #0x3F + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_gedpl.s b/src_sdcc/lib/wc_api/wc_gedpl.s new file mode 100644 index 0000000..c60ed14 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_gedpl.s @@ -0,0 +1,10 @@ + .module wc_gedpl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_gedpl +_wc_gedpl:: + push ix + ld a, #0x0F + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_gfile.s b/src_sdcc/lib/wc_api/wc_gfile.s new file mode 100644 index 0000000..a347aa7 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_gfile.s @@ -0,0 +1,10 @@ + .module wc_gfile + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_gfile +_wc_gfile:: + push ix + ld a, #0x3E + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_gipagpl.s b/src_sdcc/lib/wc_api/wc_gipagpl.s new file mode 100644 index 0000000..4764a0f --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_gipagpl.s @@ -0,0 +1,10 @@ + .module wc_gipagpl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_gipagpl +_wc_gipagpl:: + push ix + ld a, #0x32 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_gvmod.s b/src_sdcc/lib/wc_api/wc_gvmod.s new file mode 100644 index 0000000..ae52084 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_gvmod.s @@ -0,0 +1,11 @@ + .module wc_gvmod + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_gvmod +_wc_gvmod:: + ex af, af' + push ix + ld a, #0x42 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_gvsgp.s b/src_sdcc/lib/wc_api/wc_gvsgp.s new file mode 100644 index 0000000..48212d2 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_gvsgp.s @@ -0,0 +1,11 @@ + .module wc_gvsgp + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_gvsgp +_wc_gvsgp:: + push ix + ld c, a + ld a, #0x47 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_gvtl.s b/src_sdcc/lib/wc_api/wc_gvtl.s new file mode 100644 index 0000000..3305a2e --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_gvtl.s @@ -0,0 +1,17 @@ + .module wc_gvtl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_gvtl +_wc_gvtl:: + push ix + ld ix, #0 + add ix, sp + ld c, 4(ix) ; C = page + ld b, a ; B = plane + ld a, #0x46 + call WC_ENTRY + pop ix + ; callee cleans 1 byte (page) + pop hl + inc sp + jp (hl) diff --git a/src_sdcc/lib/wc_api/wc_gvtm.s b/src_sdcc/lib/wc_api/wc_gvtm.s new file mode 100644 index 0000000..d6f4cf5 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_gvtm.s @@ -0,0 +1,11 @@ + .module wc_gvtm + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_gvtm +_wc_gvtm:: + push ix + ld c, a + ld a, #0x45 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_gxoff.s b/src_sdcc/lib/wc_api/wc_gxoff.s new file mode 100644 index 0000000..543cffa --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_gxoff.s @@ -0,0 +1,10 @@ + .module wc_gxoff + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_gxoff +_wc_gxoff:: + push ix + ld a, #0x44 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_gyoff.s b/src_sdcc/lib/wc_api/wc_gyoff.s new file mode 100644 index 0000000..384cabb --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_gyoff.s @@ -0,0 +1,10 @@ + .module wc_gyoff + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_gyoff +_wc_gyoff:: + push ix + ld a, #0x43 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_int_pl.s b/src_sdcc/lib/wc_api/wc_int_pl.s new file mode 100644 index 0000000..913907d --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_int_pl.s @@ -0,0 +1,11 @@ + .module wc_int_pl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_int_pl +_wc_int_pl:: + ex af, af' + push ix + ld a, #0x56 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_int_pl_handler.s b/src_sdcc/lib/wc_api/wc_int_pl_handler.s new file mode 100644 index 0000000..fc2f4ed --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_int_pl_handler.s @@ -0,0 +1,13 @@ + .module wc_int_pl_handler + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_int_pl_handler +_wc_int_pl_handler:: + push ix + ; HL = handler_addr (sdcccall(1); WC тоже нужен HL) + ld a, #0xFF + ex af, af' ; A' = 0xFF + ld a, #0x56 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_istr.s b/src_sdcc/lib/wc_api/wc_istr.s new file mode 100644 index 0000000..f2a8f18 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_istr.s @@ -0,0 +1,19 @@ + .module wc_istr + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_istr +_wc_istr:: + push ix + ld ix, #0 + add ix, sp + ld a, 4(ix) ; A = mode (from stack) + ex af, af' ; A' = mode + push hl + pop ix ; IX = win (HL = 1st arg) + ld a, #0x09 + call WC_ENTRY + pop ix + ; callee cleans 1 byte (mode) + pop hl + inc sp + jp (hl) diff --git a/src_sdcc/lib/wc_api/wc_kbscn.s b/src_sdcc/lib/wc_api/wc_kbscn.s new file mode 100644 index 0000000..abb71e9 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_kbscn.s @@ -0,0 +1,11 @@ + .module wc_kbscn + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_kbscn +_wc_kbscn:: + ex af, af' ; A' = mode + push ix + ld a, #0x2A + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_alt.s b/src_sdcc/lib/wc_api/wc_key_alt.s new file mode 100644 index 0000000..26799c9 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_alt.s @@ -0,0 +1,10 @@ + .module wc_key_alt + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_alt +_wc_key_alt:: + push ix + ld a, #0x27 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_any.s b/src_sdcc/lib/wc_api/wc_key_any.s new file mode 100644 index 0000000..6b467d8 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_any.s @@ -0,0 +1,10 @@ + .module wc_key_any + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_any +_wc_key_any:: + push ix + ld a, #0x2D + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_bspc.s b/src_sdcc/lib/wc_api/wc_key_bspc.s new file mode 100644 index 0000000..725ccc9 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_bspc.s @@ -0,0 +1,10 @@ + .module wc_key_bspc + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_bspc +_wc_key_bspc:: + push ix + ld a, #0x18 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_caps.s b/src_sdcc/lib/wc_api/wc_key_caps.s new file mode 100644 index 0000000..5f3731e --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_caps.s @@ -0,0 +1,10 @@ + .module wc_key_caps + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_caps +_wc_key_caps:: + push ix + ld a, #0x2C + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_ctrl.s b/src_sdcc/lib/wc_api/wc_key_ctrl.s new file mode 100644 index 0000000..8815bea --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_ctrl.s @@ -0,0 +1,10 @@ + .module wc_key_ctrl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_ctrl +_wc_key_ctrl:: + push ix + ld a, #0x29 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_del.s b/src_sdcc/lib/wc_api/wc_key_del.s new file mode 100644 index 0000000..221b802 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_del.s @@ -0,0 +1,10 @@ + .module wc_key_del + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_del +_wc_key_del:: + push ix + ld a, #0x2B + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_down.s b/src_sdcc/lib/wc_api/wc_key_down.s new file mode 100644 index 0000000..1affe5d --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_down.s @@ -0,0 +1,10 @@ + .module wc_key_down + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_down +_wc_key_down:: + push ix + ld a, #0x12 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_end.s b/src_sdcc/lib/wc_api/wc_key_end.s new file mode 100644 index 0000000..2dcfdec --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_end.s @@ -0,0 +1,10 @@ + .module wc_key_end + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_end +_wc_key_end:: + push ix + ld a, #0x1C + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_enter.s b/src_sdcc/lib/wc_api/wc_key_enter.s new file mode 100644 index 0000000..85b59f8 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_enter.s @@ -0,0 +1,10 @@ + .module wc_key_enter + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_enter +_wc_key_enter:: + push ix + ld a, #0x16 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_esc.s b/src_sdcc/lib/wc_api/wc_key_esc.s new file mode 100644 index 0000000..ca19a78 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_esc.s @@ -0,0 +1,10 @@ + .module wc_key_esc + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_esc +_wc_key_esc:: + push ix + ld a, #0x17 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_f1.s b/src_sdcc/lib/wc_api/wc_key_f1.s new file mode 100644 index 0000000..42164e9 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_f1.s @@ -0,0 +1,10 @@ + .module wc_key_f1 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_f1 +_wc_key_f1:: + push ix + ld a, #0x1D + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_f10.s b/src_sdcc/lib/wc_api/wc_key_f10.s new file mode 100644 index 0000000..b320faf --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_f10.s @@ -0,0 +1,10 @@ + .module wc_key_f10 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_f10 +_wc_key_f10:: + push ix + ld a, #0x26 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_f2.s b/src_sdcc/lib/wc_api/wc_key_f2.s new file mode 100644 index 0000000..8c77074 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_f2.s @@ -0,0 +1,10 @@ + .module wc_key_f2 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_f2 +_wc_key_f2:: + push ix + ld a, #0x1E + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_f3.s b/src_sdcc/lib/wc_api/wc_key_f3.s new file mode 100644 index 0000000..44dddb7 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_f3.s @@ -0,0 +1,10 @@ + .module wc_key_f3 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_f3 +_wc_key_f3:: + push ix + ld a, #0x1F + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_f4.s b/src_sdcc/lib/wc_api/wc_key_f4.s new file mode 100644 index 0000000..9f257c6 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_f4.s @@ -0,0 +1,10 @@ + .module wc_key_f4 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_f4 +_wc_key_f4:: + push ix + ld a, #0x20 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_f5.s b/src_sdcc/lib/wc_api/wc_key_f5.s new file mode 100644 index 0000000..59674b0 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_f5.s @@ -0,0 +1,10 @@ + .module wc_key_f5 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_f5 +_wc_key_f5:: + push ix + ld a, #0x21 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_f6.s b/src_sdcc/lib/wc_api/wc_key_f6.s new file mode 100644 index 0000000..17f376a --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_f6.s @@ -0,0 +1,10 @@ + .module wc_key_f6 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_f6 +_wc_key_f6:: + push ix + ld a, #0x22 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_f7.s b/src_sdcc/lib/wc_api/wc_key_f7.s new file mode 100644 index 0000000..4b9266e --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_f7.s @@ -0,0 +1,10 @@ + .module wc_key_f7 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_f7 +_wc_key_f7:: + push ix + ld a, #0x23 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_f8.s b/src_sdcc/lib/wc_api/wc_key_f8.s new file mode 100644 index 0000000..be200e7 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_f8.s @@ -0,0 +1,10 @@ + .module wc_key_f8 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_f8 +_wc_key_f8:: + push ix + ld a, #0x24 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_f9.s b/src_sdcc/lib/wc_api/wc_key_f9.s new file mode 100644 index 0000000..fbb1d29 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_f9.s @@ -0,0 +1,10 @@ + .module wc_key_f9 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_f9 +_wc_key_f9:: + push ix + ld a, #0x25 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_home.s b/src_sdcc/lib/wc_api/wc_key_home.s new file mode 100644 index 0000000..6a6801c --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_home.s @@ -0,0 +1,10 @@ + .module wc_key_home + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_home +_wc_key_home:: + push ix + ld a, #0x1B + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_ins.s b/src_sdcc/lib/wc_api/wc_key_ins.s new file mode 100644 index 0000000..d5d3b6e --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_ins.s @@ -0,0 +1,10 @@ + .module wc_key_ins + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_ins +_wc_key_ins:: + push ix + ld a, #0x55 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_left.s b/src_sdcc/lib/wc_api/wc_key_left.s new file mode 100644 index 0000000..ac5c7ba --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_left.s @@ -0,0 +1,10 @@ + .module wc_key_left + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_left +_wc_key_left:: + push ix + ld a, #0x13 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_pgdn.s b/src_sdcc/lib/wc_api/wc_key_pgdn.s new file mode 100644 index 0000000..7137c9a --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_pgdn.s @@ -0,0 +1,10 @@ + .module wc_key_pgdn + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_pgdn +_wc_key_pgdn:: + push ix + ld a, #0x1A + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_pgup.s b/src_sdcc/lib/wc_api/wc_key_pgup.s new file mode 100644 index 0000000..2bd9c3e --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_pgup.s @@ -0,0 +1,10 @@ + .module wc_key_pgup + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_pgup +_wc_key_pgup:: + push ix + ld a, #0x19 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_right.s b/src_sdcc/lib/wc_api/wc_key_right.s new file mode 100644 index 0000000..489bba5 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_right.s @@ -0,0 +1,10 @@ + .module wc_key_right + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_right +_wc_key_right:: + push ix + ld a, #0x14 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_shift.s b/src_sdcc/lib/wc_api/wc_key_shift.s new file mode 100644 index 0000000..eb9fdff --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_shift.s @@ -0,0 +1,10 @@ + .module wc_key_shift + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_shift +_wc_key_shift:: + push ix + ld a, #0x28 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_space.s b/src_sdcc/lib/wc_api/wc_key_space.s new file mode 100644 index 0000000..c45d74f --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_space.s @@ -0,0 +1,10 @@ + .module wc_key_space + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_space +_wc_key_space:: + push ix + ld a, #0x10 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_tab.s b/src_sdcc/lib/wc_api/wc_key_tab.s new file mode 100644 index 0000000..aebe9a0 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_tab.s @@ -0,0 +1,10 @@ + .module wc_key_tab + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_tab +_wc_key_tab:: + push ix + ld a, #0x15 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_up.s b/src_sdcc/lib/wc_api/wc_key_up.s new file mode 100644 index 0000000..4354314 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_up.s @@ -0,0 +1,10 @@ + .module wc_key_up + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_up +_wc_key_up:: + push ix + ld a, #0x11 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_wait_any.s b/src_sdcc/lib/wc_api/wc_key_wait_any.s new file mode 100644 index 0000000..0cc7bf7 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_wait_any.s @@ -0,0 +1,10 @@ + .module wc_key_wait_any + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_wait_any +_wc_key_wait_any:: + push ix + ld a, #0x2F + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_key_wait_release.s b/src_sdcc/lib/wc_api/wc_key_wait_release.s new file mode 100644 index 0000000..dbc586c --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_key_wait_release.s @@ -0,0 +1,10 @@ + .module wc_key_wait_release + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_key_wait_release +_wc_key_wait_release:: + push ix + ld a, #0x2E + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_load256.s b/src_sdcc/lib/wc_api/wc_load256.s new file mode 100644 index 0000000..05d4d5c --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_load256.s @@ -0,0 +1,16 @@ + .module wc_load256 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_load256 +_wc_load256:: + push ix + ld ix, #0 + add ix, sp + ld b, 4(ix) ; B = blocks (1 byte on stack) + ; HL = dest (already in HL) + ld a, #0x3C + call WC_ENTRY + pop ix + pop hl ; HL = return address + inc sp ; discard 1-byte 'blocks' arg + jp (hl) ; return (callee cleans stack arg) diff --git a/src_sdcc/lib/wc_api/wc_load512.s b/src_sdcc/lib/wc_api/wc_load512.s new file mode 100644 index 0000000..42d3f6c --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_load512.s @@ -0,0 +1,16 @@ + .module wc_load512 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_load512 +_wc_load512:: + push ix + ld ix, #0 + add ix, sp + ld b, 4(ix) ; B = blocks (1 byte on stack) + ; HL = dest (sdcccall(1) first uint16 → HL) + ld a, #0x30 + call WC_ENTRY + pop ix + pop hl ; HL = return address + inc sp ; discard 1-byte 'blocks' arg + jp (hl) ; return (callee cleans stack arg) diff --git a/src_sdcc/lib/wc_api/wc_loadnone.s b/src_sdcc/lib/wc_api/wc_loadnone.s new file mode 100644 index 0000000..0604e90 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_loadnone.s @@ -0,0 +1,11 @@ + .module wc_loadnone + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_loadnone +_wc_loadnone:: + push ix + ld b, a + ld a, #0x3D + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_mezz.s b/src_sdcc/lib/wc_api/wc_mezz.s new file mode 100644 index 0000000..56d248d --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_mezz.s @@ -0,0 +1,34 @@ + .module wc_mezz + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_mezz +_wc_mezz:: + push ix + push hl ; save win (HL = 1st arg) + ld ix, #0 + add ix, sp + ld a, 6(ix) ; A = msg_num + ld l, 7(ix) + ld h, 8(ix) ; HL = str + ld d, 9(ix) ; D = y + ld e, 10(ix) ; E = x + exx + ex af, af' ; A' = msg_num + ld l, 0(ix) + ld h, 1(ix) ; HL = win + push hl + pop ix ; IX = win + exx ; HL=str, D=y, E=x + ld a, #0x0C + call WC_ENTRY + ld a, d ; return: next row + pop hl ; remove saved win + pop ix + ; callee cleans 5 bytes + pop hl ; HL = return address + inc sp + inc sp + inc sp + inc sp + inc sp + jp (hl) diff --git a/src_sdcc/lib/wc_api/wc_mkdir.s b/src_sdcc/lib/wc_api/wc_mkdir.s new file mode 100644 index 0000000..d197c21 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_mkdir.s @@ -0,0 +1,10 @@ + .module wc_mkdir + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_mkdir +_wc_mkdir:: + push ix + ld a, #0x49 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_mkfile.s b/src_sdcc/lib/wc_api/wc_mkfile.s new file mode 100644 index 0000000..ad47d4f --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_mkfile.s @@ -0,0 +1,10 @@ + .module wc_mkfile + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_mkfile +_wc_mkfile:: + push ix + ld a, #0x48 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_mng0_pl.s b/src_sdcc/lib/wc_api/wc_mng0_pl.s new file mode 100644 index 0000000..4ee9634 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_mng0_pl.s @@ -0,0 +1,11 @@ + .module wc_mng0_pl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_mng0_pl +_wc_mng0_pl:: + ex af, af' + push ix + ld a, #0x4E + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_mng0vpl.s b/src_sdcc/lib/wc_api/wc_mng0vpl.s new file mode 100644 index 0000000..da98814 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_mng0vpl.s @@ -0,0 +1,11 @@ + .module wc_mng0vpl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_mng0vpl +_wc_mng0vpl:: + ex af, af' + push ix + ld a, #0x50 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_mng8_pl.s b/src_sdcc/lib/wc_api/wc_mng8_pl.s new file mode 100644 index 0000000..072505d --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_mng8_pl.s @@ -0,0 +1,11 @@ + .module wc_mng8_pl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_mng8_pl +_wc_mng8_pl:: + ex af, af' + push ix + ld a, #0x4F + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_mng8vpl.s b/src_sdcc/lib/wc_api/wc_mng8vpl.s new file mode 100644 index 0000000..04d0242 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_mng8vpl.s @@ -0,0 +1,11 @@ + .module wc_mng8vpl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_mng8vpl +_wc_mng8vpl:: + ex af, af' + push ix + ld a, #0x51 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_mngc_pl.s b/src_sdcc/lib/wc_api/wc_mngc_pl.s new file mode 100644 index 0000000..606abd0 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_mngc_pl.s @@ -0,0 +1,11 @@ + .module wc_mngc_pl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_mngc_pl +_wc_mngc_pl:: + ex af, af' + push ix + ld a, #0x00 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_mngcvpl.s b/src_sdcc/lib/wc_api/wc_mngcvpl.s new file mode 100644 index 0000000..b088e99 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_mngcvpl.s @@ -0,0 +1,11 @@ + .module wc_mngcvpl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_mngcvpl +_wc_mngcvpl:: + ex af, af' + push ix + ld a, #0x41 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_mngv_pl.s b/src_sdcc/lib/wc_api/wc_mngv_pl.s new file mode 100644 index 0000000..0d272ea --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_mngv_pl.s @@ -0,0 +1,11 @@ + .module wc_mngv_pl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_mngv_pl +_wc_mngv_pl:: + ex af, af' + push ix + ld a, #0x40 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_nork.s b/src_sdcc/lib/wc_api/wc_nork.s new file mode 100644 index 0000000..e213feb --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_nork.s @@ -0,0 +1,18 @@ + .module wc_nork + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_nork +_wc_nork:: + push ix + ld ix, #0 + add ix, sp + ld a, 4(ix) ; A = val (from stack) + ex af, af' ; A' = val + ; HL = addr (1st arg, already in HL) + ld a, #0x0A + call WC_ENTRY + pop ix + ; callee cleans 1 byte (val) + pop hl + inc sp + jp (hl) diff --git a/src_sdcc/lib/wc_api/wc_prm_pl.s b/src_sdcc/lib/wc_api/wc_prm_pl.s new file mode 100644 index 0000000..a56ddfe --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_prm_pl.s @@ -0,0 +1,15 @@ + .module wc_prm_pl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_prm_pl +_wc_prm_pl:: + ex af, af' + push ix + ld a, #0x53 + call WC_ENTRY + ; Z=нет параметра → вернуть 0xFF, иначе A=номер опции + jr nz, _wc_prm_pl_end + ld a, #0xFF +_wc_prm_pl_end: + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_prsrw.s b/src_sdcc/lib/wc_api/wc_prsrw.s new file mode 100644 index 0000000..fb9dbaa --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_prsrw.s @@ -0,0 +1,34 @@ + .module wc_prsrw + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_prsrw +_wc_prsrw:: + push ix + push hl ; save win (HL = 1st arg) + push de ; save str (DE = 2nd arg) + ld ix, #0 + add ix, sp + ld b, 11(ix) ; B = len_hi + ld c, 10(ix) ; C = len_lo + ld d, 8(ix) ; D = y + ld e, 9(ix) ; E = x + ld l, 0(ix) + ld h, 1(ix) ; HL = str + exx + ld l, 2(ix) + ld h, 3(ix) ; HL = win + push hl + pop ix ; IX = win + exx ; BC=len, DE=y|x, HL=str + ld a, #0x03 + call WC_ENTRY + pop de ; remove saved str + pop hl ; remove saved win + pop ix ; restore caller IX + ; callee cleans: y(1)+x(1)+len(2) = 4 bytes + pop hl ; HL = return address + inc sp + inc sp + inc sp + inc sp + jp (hl) diff --git a/src_sdcc/lib/wc_api/wc_prsrw_attr.s b/src_sdcc/lib/wc_api/wc_prsrw_attr.s new file mode 100644 index 0000000..e90816e --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_prsrw_attr.s @@ -0,0 +1,50 @@ + .module wc_prsrw_attr + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_prsrw_attr +_wc_prsrw_attr:: + push ix + push hl ; save win (HL = 1st arg) + push de ; save str (DE = 2nd arg) + ld ix, #0 + add ix, sp + + ; ── вызов #03 (prsrw) ────────────────────────────────── + ld b, 11(ix) ; B = len_hi + ld c, 10(ix) ; C = len_lo + ld d, 8(ix) ; D = y + ld e, 9(ix) ; E = x + ld l, 0(ix) + ld h, 1(ix) ; HL = str + exx + ld l, 2(ix) + ld h, 3(ix) ; HL = win + ld a, 12(ix) ; A = attr (IX всё ещё → фрейм) + push af ; сохраняем attr на стеке + push hl + pop ix ; IX = win + exx ; BC=len, DE=y|x, HL=str; HL'=win + ld a, #0x03 + call WC_ENTRY + + ; ── вызов #04 (priat) ────────────────────────────────── + pop af ; A = attr + ex af, af' ; A' = attr + exx ; HL = win (из HL'), BC=len, DE=y|x + push hl + pop ix ; IX = win + exx ; restore BC=len, DE=y|x + ld a, #0x04 + call WC_ENTRY + + pop de ; remove saved str + pop hl ; remove saved win + pop ix ; restore caller IX + ; callee cleans 5 bytes: y(1)+x(1)+len(2)+attr(1) + pop hl ; HL = return address + inc sp + inc sp + inc sp + inc sp + inc sp + jp (hl) diff --git a/src_sdcc/lib/wc_api/wc_prwow.s b/src_sdcc/lib/wc_api/wc_prwow.s new file mode 100644 index 0000000..7f4412c --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_prwow.s @@ -0,0 +1,12 @@ + .module wc_prwow + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_prwow +_wc_prwow:: + push ix + push hl + pop ix + ld a, #0x01 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_rename.s b/src_sdcc/lib/wc_api/wc_rename.s new file mode 100644 index 0000000..2033b9e --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_rename.s @@ -0,0 +1,10 @@ + .module wc_rename + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_rename +_wc_rename:: + push ix + ld a, #0x4A + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_resident_call.s b/src_sdcc/lib/wc_api/wc_resident_call.s new file mode 100644 index 0000000..11a8a04 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_resident_call.s @@ -0,0 +1,8 @@ + .module wc_resident_call + .area _CODE + .globl _wc_resident_call +_wc_resident_call:: + ex de, hl ; HL = addr (from DE = 2nd arg) + ; A = page (1st arg, already in A) + call 0x6028 ; resident call — returns here + ret diff --git a/src_sdcc/lib/wc_api/wc_resident_jump.s b/src_sdcc/lib/wc_api/wc_resident_jump.s new file mode 100644 index 0000000..53884b6 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_resident_jump.s @@ -0,0 +1,7 @@ + .module wc_resident_jump + .area _CODE + .globl _wc_resident_jump +_wc_resident_jump:: + ex de, hl ; HL = addr (from DE = 2nd arg) + ; A = page (1st arg, already in A) + jp 0x6020 ; resident jump — never returns! diff --git a/src_sdcc/lib/wc_api/wc_rresb.s b/src_sdcc/lib/wc_api/wc_rresb.s new file mode 100644 index 0000000..d552fe4 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_rresb.s @@ -0,0 +1,12 @@ + .module wc_rresb + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_rresb +_wc_rresb:: + push ix + push hl + pop ix + ld a, #0x02 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_save512.s b/src_sdcc/lib/wc_api/wc_save512.s new file mode 100644 index 0000000..6a552be --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_save512.s @@ -0,0 +1,16 @@ + .module wc_save512 + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_save512 +_wc_save512:: + push ix + ld ix, #0 + add ix, sp + ld b, 4(ix) + ; HL = src (DON'T ex de, hl!) + ld a, #0x31 + call WC_ENTRY + pop ix + pop hl + inc sp + jp (hl) diff --git a/src_sdcc/lib/wc_api/wc_scrlwow.s b/src_sdcc/lib/wc_api/wc_scrlwow.s new file mode 100644 index 0000000..973b8f6 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_scrlwow.s @@ -0,0 +1,33 @@ + .module wc_scrlwow + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_scrlwow +_wc_scrlwow:: + push ix + push hl ; save win (HL = 1st arg) + ld ix, #0 + add ix, sp + ld a, 10(ix) ; A = flags + ld b, 8(ix) ; B = h + ld c, 9(ix) ; C = w + ld d, 6(ix) ; D = y + ld e, 7(ix) ; E = x + exx + ex af, af' ; A' = flags + ld l, 0(ix) + ld h, 1(ix) + push hl + pop ix ; IX = win + exx ; BC=h/w, DE=y/x + ld a, #0x54 + call WC_ENTRY + pop hl ; remove saved win + pop ix + ; callee cleans 5 bytes + pop hl + inc sp + inc sp + inc sp + inc sp + inc sp + jp (hl) diff --git a/src_sdcc/lib/wc_api/wc_stream.s b/src_sdcc/lib/wc_api/wc_stream.s new file mode 100644 index 0000000..2b60b50 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_stream.s @@ -0,0 +1,11 @@ + .module wc_stream + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_stream +_wc_stream:: + push ix + ld d, a + ld a, #0x39 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_strset.s b/src_sdcc/lib/wc_api/wc_strset.s new file mode 100644 index 0000000..12b2661 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_strset.s @@ -0,0 +1,38 @@ + .module wc_strset + .area _CODE + .globl _wc_strset +_wc_strset:: + push ix + ld ix, #0 + add ix, sp + + ld a, 4(ix) ; c (из-за push ix) + pop ix + + ; записываем первый байт + ld (hl), a + + ld b, d + ld c, e + + ; len == 1 → всё + dec bc + ld a, b + or c + jr z, _wc_strset_done + + ; DE = HL + 1 + push hl + pop de + inc de + + ; BC = len - 1 уже + + ldir + +_wc_strset_done: + ; очистка стека + pop bc + inc sp + push bc + ret diff --git a/src_sdcc/lib/wc_api/wc_tentry.s b/src_sdcc/lib/wc_api/wc_tentry.s new file mode 100644 index 0000000..6e8fa92 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_tentry.s @@ -0,0 +1,11 @@ + .module wc_tentry + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_tentry +_wc_tentry:: + push ix + ex de, hl ; DE = addr (SDCC HL → WC DE) + ld a, #0x33 + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_tmrkdfl.s b/src_sdcc/lib/wc_api/wc_tmrkdfl.s new file mode 100644 index 0000000..a2abdfa --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_tmrkdfl.s @@ -0,0 +1,30 @@ + .module wc_tmrkdfl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_tmrkdfl +_wc_tmrkdfl:: + push ix + push hl ; save panel (HL = 1st arg) + push de ; save filenum (DE = 2nd arg) + ld ix, #0 + add ix, sp + ld e, 8(ix) + ld d, 9(ix) ; DE = namebuf (from stack) + ld l, 0(ix) + ld h, 1(ix) ; HL = filenum (from saved DE) + exx + ld l, 2(ix) + ld h, 3(ix) ; HL = panel (from saved HL) + push hl + pop ix ; IX = panel + exx ; HL=filenum, DE=namebuf + ld a, #0x36 + call WC_ENTRY + pop de ; remove saved filenum + pop hl ; remove saved panel + pop ix + ; callee cleans 2 bytes (namebuf) + pop hl + inc sp + inc sp + jp (hl) diff --git a/src_sdcc/lib/wc_api/wc_turbopl.s b/src_sdcc/lib/wc_api/wc_turbopl.s new file mode 100644 index 0000000..512ba66 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_turbopl.s @@ -0,0 +1,12 @@ + .module wc_turbopl + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_turbopl +_wc_turbopl:: + push ix + ld c, l ; C = param (2-й аргумент) + ld b, a ; B = mode (1-й аргумент) + ld a, #0x0E + call WC_ENTRY + pop ix + ret diff --git a/src_sdcc/lib/wc_api/wc_txtpr.s b/src_sdcc/lib/wc_api/wc_txtpr.s new file mode 100644 index 0000000..37a5bec --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_txtpr.s @@ -0,0 +1,30 @@ + .module wc_txtpr + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_txtpr +_wc_txtpr:: + push ix ; (1) save caller IX + push hl ; (2) save win ptr (HL = 1st arg) + push de ; (3) save str ptr (DE = 2nd arg) + ld ix, #0 + add ix, sp + ld d, 9(ix) ; D = y + ld e, 8(ix) ; E = x + ld l, 0(ix) + ld h, 1(ix) ; HL = str ptr + exx ; shadow: HL'=str, D'=y, E'=x + ld l, 2(ix) + ld h, 3(ix) ; HL = win ptr + push hl + pop ix ; IX = win ptr + exx ; restore HL=str, D=y, E=x + ld a, #0x0B + call WC_ENTRY + ld a, d ; A = return: next Y + pop de ; (3) restore DE + pop hl ; (2) restore HL + pop ix ; (1) restore IX + pop hl ; HL = return address + inc sp ; } discard caller's push bc (2 bytes: y, x) + inc sp ; } + jp (hl) ; return (callee cleans stack args) diff --git a/src_sdcc/lib/wc_api/wc_yn.s b/src_sdcc/lib/wc_api/wc_yn.s new file mode 100644 index 0000000..9575c28 --- /dev/null +++ b/src_sdcc/lib/wc_api/wc_yn.s @@ -0,0 +1,15 @@ + .module wc_yn + .area _CODE +WC_ENTRY = 0x6006 + .globl _wc_yn +_wc_yn:: + ex af, af' + push ix + ld a, #0x08 + call WC_ENTRY + ld a, #0 + jr nz, _wc_yn_end + inc a +_wc_yn_end: + pop ix + ret diff --git a/src_sdcc/main.c b/src_sdcc/main.c new file mode 100644 index 0000000..dfa1cad --- /dev/null +++ b/src_sdcc/main.c @@ -0,0 +1,1171 @@ +/* + * main.c — VGM Player Plugin для Wild Commander (SDCC/Z80) + * + * Поддерживаемые форматы: + * VGM (Raw) — OPL1/OPL2/OPL3/OPL4(YMF278B, cmd 0xD0), AY-3-8910/YM2149/YM2203, SAA1099 + * VGZ (gzip) — автораспаковка через inflate + * + * Функции: + * - Отображение метаданных (GD3: автор, игра, трек, система) + * - Воспроизведение с двойной буферизацией (ISR 1367 Гц) + * - OPL3/OPL4: NEW=1 (L/R через C0-C8); OPL1/OPL2: NEW=0 (compat mode) + * - OPL4 (ZXM-MoonSound): NEW2=1 дополнительно разблокирует wave-часть + * (порты #7E/#7F); FM-часть (порты 0/1 команды 0xD0) идёт через те же + * регистры/порты #C4-#C7, что и OPL3 + * + * Управление (PS/2 клавиатура): + * ESC — выход + * SPACE — следующий файл в каталоге + * P — предыдущий файл + * 1/2 — SAA режим (chip0 / chip1) для dual музыки + */ + +#include "inc/types.h" +#include "inc/tsconf.h" +#include "inc/wc_api.h" +#include "inc/txtlib.h" +#include "inc/vgm.h" +#include "inc/isr.h" +#include "inc/keys.h" +#include "inc/version.h" +#include + +/* Хелперы stringify для compile-time ISR_FREQ / budget в заголовке */ +#define _STR(x) #x +#define STR(x) _STR(x) + +/* ── Параметры progress bar для inflate (определены в inflate_call.s _DATA) ── */ +extern uint8_t ifl_pb_text_pg; +extern uint16_t ifl_pb_attr_ofs; +extern uint8_t ifl_pb_total; +extern uint8_t ifl_pb_bw; +extern uint8_t ifl_pb_green; + +/* ── Бордюр-отладка ─────────────────────────────────────────────────── */ +/* Включить DEBUG_BORDER для отображения времени fill_buffer через цвет бордюра */ +/* #define DEBUG_BORDER */ + +#ifdef DEBUG_BORDER +static void border(uint8_t c) { sfr_zx_fe = c; } +#endif + +/* ── Отладка через порт (emulator "Illegal port!" log) ────────────── */ +/* Включите DBG_ENABLE для отладочного вывода в порт; по умолчанию выкл */ +/* #define DBG_ENABLE */ + +#ifdef DBG_ENABLE +#define DBG_STATUS 0xFFAF +#define DBG_DATA1 0xFEAF +#define DBG_DATA2 0xFDAF + +static void dbg_trace(uint8_t code) __naked { + (void)code; + __asm + ; A = code (sdcccall) + ld bc, #0xFFAF + out (c), a + ret + __endasm; +} + +static void dbg_trace2(uint8_t code, uint8_t d1, uint8_t d2) { + (void)d1; (void)d2; + __asm + ; A=code, stack: d1, d2 (sdcccall: A=first, stack rest) + ld bc, #0xFFAF + out (c), a + ; d1 = 2(sp), d2 = 3(sp) — behind ret addr + ld hl, #2 + add hl, sp + ld a, (hl) + ld bc, #0xFEAF + out (c), a + inc hl + ld a, (hl) + ld bc, #0xFDAF + out (c), a + __endasm; +} +#else +#define dbg_trace(code) ((void)0) +#define dbg_trace2(code, d1, d2) ((void)0) +#endif + +/* ── Кешированные флаги активных чипов ────────────────────────────── */ +static uint8_t s_has_opl, s_has_ay, s_has_ay2; +static uint8_t s_has_saa, s_has_saa2; +static uint8_t s_has_ym2203, s_has_ym2203_2; + +/* SAA dual mode config (defined in vgm.c) */ +extern uint8_t cfg_saa_mode; +static uint8_t s_saa_mode_user; /* user setting from INI (preserved across tracks) */ +static uint8_t s_board_dual_saa; /* 0=одночиповая(default), 1=двухчиповая (INI param 4) */ + +static void detect_active_chips(void) +{ + s_has_opl = s_has_ay = s_has_ay2 = 0; + s_has_saa = s_has_saa2 = 0; + s_has_ym2203 = s_has_ym2203_2 = 0; + for (uint8_t i = 0; i < vgm_chip_count; i++) { + uint8_t id = vgm_chip_list[i].id; + uint8_t dual = vgm_chip_list[i].flags & VGM_CF_DUAL; + switch (id) { + case VGM_OFF_YM2203: + s_has_ym2203 = 1; + if (dual) s_has_ym2203_2 = 1; + /* fallthrough (проваливаемся) */ + case VGM_OFF_AY8910: + s_has_ay = 1; + if (dual) s_has_ay2 = 1; + break; + case VGM_OFF_YM3526: + case VGM_OFF_YM3812: + case VGM_OFF_Y8950: + case VGM_OFF_YMF262: + case VGM_OFF_YMF278B: + s_has_opl = 1; + break; + case VGM_OFF_SAA1099: + s_has_saa = 1; + if (dual) s_has_saa2 = 1; + break; + default: + break; + } + } +} + +/* cfg_loop_rewinds, cfg_min_duration, cfg_max_duration определены в vgm.c */ + +/* ── HL queue helpers ──────────────────────────────────────────────── + * The queue itself and its state live in vgm.c (vgm_hl_queue etc.) + * vgm_fill_buffer() reads them internally; main.c only builds + * the queue and sets vgm_hl_pos on abort (Q/N/P). */ + +static void hl_push(uint8_t cmd, uint8_t param) +{ + if (vgm_hl_len < HL_QUEUE_MAX) { + vgm_hl_queue[vgm_hl_len].cmd = cmd; + vgm_hl_queue[vgm_hl_len].param = param; + vgm_hl_len++; + } +} + +/* CMDBLK_SILENCE_OPL (+ CMDBLK_SILENCE_WAVE для OPL4) — общий кусок, + * нужен и в начале трека, и в abort-последовательности выхода. */ +static void hl_push_opl_silence(void) +{ + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_OPL); + if (vgm_chip_type == VGM_CHIP_OPL4) + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_WAVE); +} + +/* ── Построение очереди воспроизведения по обнаруженным чипам / loop info ─ */ +static void build_playback_queue(void) +{ + vgm_hl_len = 0; + + /* Фаза инициализации: silence (очистка остаточного состояния) + init */ + if (s_has_opl) { + hl_push_opl_silence(); + hl_push(HLCMD_CMDBLK, + (vgm_chip_type == VGM_CHIP_OPL4) ? CMDBLK_INIT_OPL4 + : (vgm_chip_type == VGM_CHIP_OPL3) ? CMDBLK_INIT_OPL3 + : CMDBLK_INIT_OPL2); + } + if (s_has_ym2203) + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_YM2203); + if (s_has_ym2203_2) + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_YM2203_2); + + /* SAA: гасим saa2, потом saa1 (даже на одночиповой плате), + * затем clock ON, затем Sound Enable по режиму. + * cmdblk bypass гарантирует прохождение команд chip2. */ + if (s_has_saa) { + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_SAA2); + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_SAA); + hl_push(HLCMD_CMDBLK, CMDBLK_SAA_CLK_ON); + hl_push(HLCMD_CMDBLK, CMDBLK_SAA_INIT); + if (cfg_saa_mode == 2) + hl_push(HLCMD_CMDBLK, CMDBLK_SAA2_INIT); + } + + /* Основное воспроизведение */ + hl_push(HLCMD_PLAY, 0); + + /* Loop repeats — только если vgm_parse_header разрешил loop. + * Policy: без full-track loop, без коротких (<10с), без длинных (>4мин). */ + if (vgm_loop_enabled) { + for (uint8_t i = 0; i < cfg_loop_rewinds; i++) + hl_push(HLCMD_LOOP, 0); + } + + /* ── Последовательность завершения (abort target) ──────────────── */ + vgm_hl_abort_pos = vgm_hl_len; + + if (s_has_opl) + hl_push_opl_silence(); + if (s_has_ym2203) + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_YM2203); + else if (s_has_ay) + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_AY); + if (s_has_ym2203_2) + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_YM2203_2); + else if (s_has_ay2) + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_AY2); + if (s_has_saa) { + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_SAA2); + hl_push(HLCMD_CMDBLK, CMDBLK_SILENCE_SAA); + hl_push(HLCMD_CMDBLK, CMDBLK_SAA_CLK_OFF); + } + + hl_push(HLCMD_ISR_DONE, 0); + + vgm_hl_pos = 0; +} + +/* ── Размеры и цвета окна ───────────────────────────────────────────── */ +/* Координаты вычисляются динамически в main() через wc_get_height(). + * TextMode=0: 80×25, TextMode=1: 80×30, TextMode=2: 90×36. */ +#define WND_W 62 +#define WND_H 22 + +#define CLR_WIN WC_COLOR(WC_WHITE, WC_BLACK) +#define CLR_TITLE WC_COLOR(WC_BLUE, WC_YELLOW) + +static const char s_win_title[] = " VGM Player " APP_VERSION " "; + +/* ── Фиксированные строки окна (1-based content rows) ─────────────── + * Высота=23 → строки контента 1..21, рамка 0 и 22. + * Стр 1: Заголовок + * Стр 2-4: Информация о файле + * Стр 5-6: (пусто) + * Стр 7: ← разделитель 1 + * Стр 8-14: Информация VGM (ver, чипы, луп, freq, GD3) + * Стр 15: ← разделитель 2 + * Стр 16: Прогресс-бар + * Стр 17-20: Спектро-анализатор (4 строки) + * Стр 21: Подсказки клавиш */ +#define ROW_TITLE 1 +#define ROW_FILE_START 2 +#define ROW_VGM_START 6 +#define ROW_VGM_END 13 /* максимальная строка для VGM/GD3 info */ +#define ROW_PROGRESS 15 +#define ROW_SPECTRUM 16 /* 4 rows: 16,17,18,19 */ +#define ROW_HELP 20 +#define DIV1_FROM_BOTTOM 16 /* row 7 = 23-16 */ +#define DIV2_FROM_BOTTOM 7 /* row 15 = 23-8 */ + +static wc_window_t s_wnd; +static uint8_t s_pages; +/* pbinfo_start_row заменён константой ROW_PROGRESS */ + +static uint8_t s_playback_inited = 0; +static uint8_t s_last_active_buf = 0; +static uint8_t s_buf_ready[2] = {0, 0}; +static uint8_t s_last_loop_count = 0; +static uint16_t s_last_displayed_sec = 0; /* 0 = совпадает с начальным sec */ + +/* BCD-цифры времени (инкремент вместо делений) ─────────────────────── */ +static uint8_t s_pb_d_min10 = '0'; +static uint8_t s_pb_d_min1 = '0'; +static uint8_t s_pb_d_sec10 = '0'; +static uint8_t s_pb_d_sec1 = '0'; + +/* ── Progress bar state (предвычисленные параметры) ───────────────── */ +uint8_t s_pb_text_pg; /* физ. страница текстового экрана (не static: ASM) */ +static uint16_t s_pb_char_ofs; /* смещение символов в текстовой странице */ +static uint16_t s_pb_attr_ofs; /* смещение атрибутов в текстовой странице */ +static uint8_t s_pb_width; /* ширина полосы в колонках */ +static uint16_t s_pb_err; /* аккумулятор ошибки Bresenham */ +static uint8_t s_pb_col; /* текущее кол-во зелёных колонок */ + +/* Позиции изменяемых символов внутри строки progress bar: + * "Playing MM:SS / MM:SS Loop N" + * 0 9 17 27 29 */ +#define PB_OFS_MIN10 9 +#define PB_OFS_MIN1 10 +#define PB_OFS_SEC10 12 +#define PB_OFS_SEC1 13 +#define PB_OFS_LOOP 29 + +/* ── Рендеринг spectrum analyzer (ASM: asm/spectrum.s) ────────────── + * 16 полос, 4 строки (ROW_SPECTRUM..ROW_SPECTRUM+3), снизу вверх. + * spectrum_render() и хелперы горячего цикла — в asm/spectrum.s. + * Переменные ниже не static, чтобы ASM мог обращаться по _symbol. */ + +/* Предвычисленные адреса для прямого доступа к текстовой странице */ +uint16_t s_spec_char_ofs[4]; /* смещения символов для 4 строк */ +uint16_t s_spec_attr_ofs[4]; /* смещения атрибутов для 4 строк */ + +/* spectrum_render/font/decay() находятся в asm/spectrum.s */ +void spectrum_render(void); +void spectrum_decay(void); +void spectrum_decay_reset(void); +void spectrum_font_init(void); +void spectrum_font_restore(void); + + +/* Информация VGZ для отображения */ +static uint8_t s_is_vgz; +static uint32_t s_vgm_unpacked_size; +static uint8_t s_vgz_compressed_pages; + +static inline void ints_disable(void) +{ + __asm di + __endasm; +} + +static inline void ints_enable(void) +{ + __asm ei + __endasm; +} + +/* ── Имя OPL-типа ────────────────────────────────────────────────────── */ +static const char *opl_name(void) +{ + if (vgm_chip_type == VGM_CHIP_OPL) + return "OPL1 YM3526"; + if (vgm_chip_type == VGM_CHIP_OPL2) + return "OPL2 YM3812"; + if (vgm_chip_type == VGM_CHIP_OPL3) + return "OPL3 YMF262"; + if (vgm_chip_type == VGM_CHIP_OPL4) + return "OPL4 YMF278B"; + return "--- (no OPL)"; +} + +char work_buf[96 + 2]; +char error_buf[96 + 2]; + +enum state_t +{ + STATE_INIT, + STATE_LOADING, + STATE_ERROR, + STATE_PARSE_HEADER, + STATE_PLAYBACK +} state; + +/* ── Предварительный вывод информации (до загрузки/распаковки) ──────── */ +static void draw_pre_load_info(void) +{ + uint8_t row = ROW_FILE_START; + + buf_clear(work_buf); + buf_append_str(work_buf, "File name : "); + buf_append_str(work_buf, wc_file_name); + print_line(&s_wnd, row++, work_buf, CLR_WIN); + + buf_clear(work_buf); + buf_append_str(work_buf, "File size : "); + buf_append_u32_dec(work_buf, wc_file_size); + print_line(&s_wnd, row++, work_buf, CLR_WIN); + + buf_clear(work_buf); + buf_append_str(work_buf, "16Kb pages : "); + buf_append_u16_dec(work_buf, (uint16_t)((wc_file_size + 16383U) >> 14)); + print_line(&s_wnd, row, work_buf, CLR_WIN); +} + +uint8_t drow_ui(void) +{ + uint8_t row; + uint8_t gd3_count = 0; + + /* ── Секция 1: Информация о файле (строки 1-3) ────────────── */ + row = ROW_FILE_START; + + buf_clear(work_buf); + buf_append_str(work_buf, "File name : "); + buf_append_str(work_buf, wc_file_name); + print_line(&s_wnd, row++, work_buf, CLR_WIN); + + buf_clear(work_buf); + buf_append_str(work_buf, "File size : "); + buf_append_u32_dec(work_buf, wc_file_size); + if (s_is_vgz) { + buf_append_str(work_buf, " / "); + buf_append_u32_dec(work_buf, s_vgm_unpacked_size); + } + print_line(&s_wnd, row++, work_buf, CLR_WIN); + + buf_clear(work_buf); + buf_append_str(work_buf, "16Kb pages : "); + if (s_is_vgz) { + buf_append_u16_dec(work_buf, s_vgz_compressed_pages); + buf_append_str(work_buf, " / "); + } + buf_append_u16_dec(work_buf, s_pages); + print_line(&s_wnd, row, work_buf, CLR_WIN); + + /* ── Секция 2: Данные VGM (строки 5..ROW_VGM_END) ────────── */ + row = ROW_VGM_START; + + buf_clear(work_buf); + buf_append_str(work_buf, "VGM version : "); + buf_append_u8_hex(work_buf, vgm_version >> 8); + buf_append_char(work_buf, '.'); + buf_append_u8_hex(work_buf, vgm_version & 0xFF); + print_line(&s_wnd, row++, work_buf, CLR_WIN); + + for (uint8_t i = 0; i < vgm_chip_count && row <= ROW_VGM_END; i++) + { + /* Компактно: макс. 2 полных строки чипов; остальные как "+ N more" */ + if (i >= 2 && vgm_chip_count > 3) { + buf_clear(work_buf); + buf_append_str(work_buf, " + "); + buf_append_u16_dec(work_buf, (uint16_t)(vgm_chip_count - 2)); + buf_append_str(work_buf, " more"); + print_line(&s_wnd, row++, work_buf, CLR_WIN); + break; + } + + const vgm_chip_entry_t *chip = &vgm_chip_list[i]; + + buf_clear(work_buf); + buf_append_str(work_buf, "Chip : "); + { + const char *cname = vgm_chip_name(chip->id); + if (cname) { + buf_append_str(work_buf, cname); + } else { + buf_append_str(work_buf, "? (0x"); + buf_append_u8_hex(work_buf, chip->id); + buf_append_char(work_buf, ')'); + } + } + buf_append_str(work_buf, " @ "); + buf_append_u16_dec(work_buf, chip->clock_khz); + buf_append_str(work_buf, " kHz"); + if (chip->flags & VGM_CF_DUAL) + buf_append_str(work_buf, " x2"); + + print_line(&s_wnd, row++, work_buf, CLR_WIN); + } + + /* Информация о loop */ + if (row <= ROW_VGM_END) { + buf_clear(work_buf); + buf_append_str(work_buf, "Loop : "); + if (vgm_loop_addr && vgm_loop_enabled) + buf_append_str(work_buf, "Yes"); + else if (vgm_loop_addr && !vgm_loop_enabled) + buf_append_str(work_buf, "Skip"); + else + buf_append_str(work_buf, "No"); + print_line(&s_wnd, row++, work_buf, + (vgm_loop_addr && !vgm_loop_enabled) + ? WC_COLOR(WC_YELLOW, WC_BLACK) + : CLR_WIN); + } + + /* Информация о режиме частот */ + if (row <= ROW_VGM_END) { + buf_clear(work_buf); + buf_append_str(work_buf, "Freq : "); + if (vgm_freq_mode == FREQ_MODE_TABLE) { + buf_append_str(work_buf, "Table"); + } else { + buf_append_str(work_buf, "Native"); + } + print_line(&s_wnd, row++, work_buf, + (vgm_freq_mode == FREQ_MODE_NATIVE) + ? CLR_WIN + : WC_COLOR(WC_WHITE, WC_BLUE)); + } + + /* ── GD3 метаданные (English, max 4) ──────────────────────────── */ + if (vgm_gd3_track[0] && row <= ROW_VGM_END && gd3_count < 4) { + buf_clear(work_buf); + buf_append_str(work_buf, "Title : "); + buf_append_str(work_buf, vgm_gd3_track); + print_line(&s_wnd, row++, work_buf, WC_COLOR(WC_YELLOW, WC_BLACK)); + gd3_count++; + } + if (vgm_gd3_game[0] && row <= ROW_VGM_END && gd3_count < 4) { + buf_clear(work_buf); + buf_append_str(work_buf, "Game : "); + buf_append_str(work_buf, vgm_gd3_game); + print_line(&s_wnd, row++, work_buf, WC_COLOR(WC_YELLOW, WC_BLACK)); + gd3_count++; + } + if (vgm_gd3_author[0] && row <= ROW_VGM_END && gd3_count < 4) { + buf_clear(work_buf); + buf_append_str(work_buf, "Author : "); + buf_append_str(work_buf, vgm_gd3_author); + print_line(&s_wnd, row++, work_buf, WC_COLOR(WC_YELLOW, WC_BLACK)); + gd3_count++; + } + if (vgm_gd3_system[0] && row <= ROW_VGM_END && gd3_count < 4) { + buf_clear(work_buf); + buf_append_str(work_buf, "System : "); + buf_append_str(work_buf, vgm_gd3_system); + print_line(&s_wnd, row++, work_buf, WC_COLOR(WC_YELLOW, WC_BLACK)); + gd3_count++; + } + + /* ── Строка подсказок (фиксированная позиция) ─────────────── */ + buf_clear(work_buf); + buf_append_str(work_buf, " [Space] Next [P]rev [Esc] Exit"); + if (s_has_saa2 && !s_board_dual_saa) { + buf_append_str(work_buf, " SAA:"); + buf_append_char(work_buf, '1' + cfg_saa_mode); + } + print_line(&s_wnd, ROW_HELP, work_buf, CLR_TITLE); + + return ROW_PROGRESS; +} + +/* render_static_info() удалена — заменена на drow_ui() */ + +/* Проверить расширение файла: .vgz / .VGZ */ +static uint8_t is_vgz_filename(void) +{ + const char *p = wc_file_name; + const char *dot = 0; + while (*p) { + if (*p == '.') dot = p; + p++; + } + if (!dot) return 0; + dot++; + if ((dot[0]=='v'||dot[0]=='V') && + (dot[1]=='g'||dot[1]=='G') && + (dot[2]=='z'||dot[2]=='Z') && + dot[3]==0) + return 1; + return 0; +} + +uint8_t load_vgm(void) +{ + uint8_t i; + uint32_t fsize = wc_file_size; + + /* Минимальный размер — хотя бы 256 байт */ + if (fsize < 256UL) + return 0; + + s_pages = (uint8_t)((fsize + 16383UL) >> 14); + if (!s_pages) + s_pages = 1; + + /* vgm_end_page / vgm_end_addr устанавливаются в vgm_parse_header() + из поля EOF offset заголовка VGM — здесь задавать не нужно. */ + + for (i = 0; i < s_pages; i++) + { + uint8_t rc; + wc_mngcvpl(i); + rc = wc_load512(0xC000, 32); + + /* DBG: 0xB0 = loaded page i, rc */ + dbg_trace2(0xB0, i, rc); + + /* WC_EOF нормален на последнем секторе */ + if (rc && rc != WC_EOF) + return 0; + } + + wc_mngcvpl(0); /* страница 0 для проверки заголовка */ + + /* ── VGZ: gzip magic 0x1F 0x8B ──────────────────────────────── */ + /* ВАЖНО: между wc_mngcvpl и inflate_vgz НЕ ДОЛЖНО быть WC API * + * вызовов (print_line и др.), иначе WC сбрасывает Win3 mapping * + * и inflate может упасть. */ + if (*(volatile uint8_t *)0xC000 == 0x1F && + *(volatile uint8_t *)0xC001 == 0x8B) { + + uint8_t vgz_pages = s_pages; + uint32_t vgm_size; + uint8_t ifl_rc; + + s_is_vgz = 1; + s_vgz_compressed_pages = s_pages; + + /* DBG: 0xA0 = VGZ detected, pages count */ + dbg_trace2(0xA0, vgz_pages, 0); + + if (vgz_pages > 31) /* TAP: макс. 31 страница */ + return 0; + + /* DBG: 0xA1 = before copy_pages_to_tap */ + dbg_trace(0xA1); + + /* Копировать VGZ из мегабуфера → TAP-страницы (DI/EI внутри) */ + copy_pages_to_tap(vgz_pages); + + /* Установить total для progress bar (оценка выходных страниц) */ + { + uint8_t est = (uint8_t)(vgz_pages * 3u); + if (est > 64) est = 64; + ifl_pb_total = est; + } + + /* DBG: 0xA2 = after copy_pages_to_tap, before inflate_vgz */ + dbg_trace2(0xA2, WC_PAGE_TAP_START, WC_PAGE_TVBPG); + + /* Inflate: TAP (#A1+) → мегабуфер (#20+) */ + ifl_rc = inflate_vgz(WC_PAGE_TAP_START, WC_PAGE_TVBPG); + + /* DBG: 0xA3 = after inflate_vgz, result */ + dbg_trace2(0xA3, ifl_rc, 0); + + if (ifl_rc != 0) + return 0; + + /* DBG: 0xA4 = inflate OK, reading VGM size */ + dbg_trace(0xA4); + + /* Прочитать размер распакованного VGM из заголовка */ + wc_mngcvpl(0); + { + /* VGM offset 0x04: EOF offset (отн. 0x04) — LE uint32 */ + uint16_t lo = *(volatile uint16_t *)0xC004; + uint16_t hi = *(volatile uint16_t *)0xC006; + vgm_size = ((uint32_t)hi << 16) | lo; + vgm_size += 4; /* total = eof_offset + 4 */ + } + s_vgm_unpacked_size = vgm_size; + s_pages = (uint8_t)((vgm_size + 16383UL) >> 14); + if (!s_pages) + s_pages = 1; + + /* DBG: 0xA5 = VGZ done, final pages */ + dbg_trace2(0xA5, s_pages, 0); + } + + return s_pages; +} + +void start_playback(void) +{ + ints_disable(); + + /* Включение 512-байт кэша для Window 2 (#8000-#BFFF). + * Ускоряет code fetch и доступ к static fb_* переменным. + * ДОЛЖНО быть после inflate/load — DMA/page I/O испортят cache. + * SysConfig bit 2 = global enable, CacheConfig bit 2 = win2. */ + //tsconf_set_cache(TSCONF_CACHE_EN_8000); + //tsconf_set_cpu_cache(TSCONF_CPU_14MHZ, 1); + + vgm_paused = 0; + + if (!s_playback_inited) + { + s_playback_inited = 1; + + /* Сброс счётчиков */ + isr_play_seconds = 0; + s_last_displayed_sec = 0; + s_pb_d_min10 = '0'; + s_pb_d_min1 = '0'; + s_pb_d_sec10 = '0'; + s_pb_d_sec1 = '0'; + vgm_loop_count = 0; + + /* ISR-состояние */ + isr_active_buf = 0; + isr_wait_ctr = 0; + isr_done = 0; + + /* MultiSound ctrl: включить FM/SAA только для присутствующих чипов. + * 0xF2 = FM ON + SAA ON + normal read + chip1 (base). + * bit2: 1 = FM OFF (установить если нет YM2203) + * bit3: 1 = SAA OFF (установить если нет SAA1099) */ + isr_ms_ctrl = 0xF2 + | (s_has_ym2203 ? 0x00 : 0x04) + | (s_has_saa ? 0x00 : 0x08); + + /* Применить конфиг MultiSound к железу немедленно + * (нужно для SAA-only музыки, где AY-записей нет) */ + __asm + ld bc, #0xFFFD + ld a, (_isr_ms_ctrl) + out (c), a + __endasm; + + // SAA1099: effective mode computation. + // saa1/saa2 = микросхемы на плате, chip0/chip1 = чипы в VGM. + // Двухчиповая (saa_chips=2): single→saa1, dual→saa1+saa2 (turbo). + // Одночиповая (saa_chips=1): всегда saa1; dual → chip0 или chip1 по выбору. + if (s_board_dual_saa) + cfg_saa_mode = s_has_saa2 ? 2 : 0; + else + cfg_saa_mode = s_has_saa2 ? s_saa_mode_user : 0; + + // Set spectrum dual mode flag (only in turbo mode with dual VGM). + extern uint8_t spec_saa_dual; + spec_saa_dual = (s_has_saa2 && cfg_saa_mode == 2) ? 1 : 0; + + /* SAA init/deinit is now handled entirely via cmdblocks + * in build_playback_queue() — no inline asm needed. */ + + /* Построить очередь HL-команд для этого трека */ + build_playback_queue(); + + /* Инициализация IM2: перехват вектора WC #5BFF */ + isr_init(); + + /* Заполнить оба буфера из HL queue. + * vgm_fill_buffer обрабатывает HL-переходы внутри — + * init cmdblocks + начало VGM-данных могут попасть в buf 0. */ + vgm_fill_buffer(0); + s_buf_ready[0] = 1; + + vgm_fill_buffer(1); + s_buf_ready[1] = 1; + + s_last_active_buf = 0; + } + + isr_enabled = 1; + ints_enable(); +} + +void stop_playback(void) +{ + if (!s_playback_inited) { + ints_disable(); + ints_enable(); + return; + } + + ints_disable(); + isr_enabled = 0; + isr_deinit(); /* восстановить вектор WC */ + + /* MultiSound: отключить FM + SAA, выбрать chip1 (железо + переменная) */ + isr_ms_ctrl = 0xFE; + __asm + ld bc, #0xFFFD + ld a, #0xFE ;; chip1 + FM OFF + SAA CLK OFF + out (c), a + __endasm; + + /* Отключить cache перед возвратом в WC. + * CacheConfig = 0 отключает все per-window caches. + * SysConfig: оставить 14 МГц, очистить global cache bit. */ + //tsconf_set_cpu_cache(TSCONF_CPU_14MHZ, 0); + //tsconf_set_cache(TSCONF_CACHE_NONE); + + + vgm_paused = 0; + s_playback_inited = 0; + s_buf_ready[0] = 0; + s_buf_ready[1] = 0; + s_last_active_buf = 0; + isr_done = 0; + + ints_enable(); +} + +/** + * Мгновенный abort воспроизведения. + * + * При обычном abort через vgm_hl_abort_pos main loop ждёт, пока ISR + * доиграет активный буфер (включая CMD_WAIT, до ~1 сек задержки). + * + * instant_abort() обходит это за ~1.5 мс: + * 1. isr_enabled = 0 → ISR пропускает команды (pos_table работает). + * 2. HALT — CPU спит до следующего INT. После пробуждения ISR + * гарантированно отработал один проход с enabled=0. + * 3. Обнуляем isr_wait_ctr (отменяем CMD_WAIT). + * 4. Заполняем НЕАКТИВНЫЙ буфер shutdown-цепочкой + * (silence + CMD_ISR_DONE) — ISR его не читает. + * 5. Переключаем isr_active_buf + isr_read_ptr на этот буфер. + * 6. isr_enabled = 1 → ISR мгновенно выполняет shutdown. + * + * Вызывать ПОСЛЕ установки vgm_hl_pos = vgm_hl_abort_pos. + * После возврата isr_done будет установлен через 1-2 ISR-тика. + * + * Нулевой overhead на горячем пути ISR — без дополнительных флагов. + */ +static void instant_abort(void) +{ + uint8_t free_idx; + + /* ── 1. Заморозить ISR (команды не выполняются) ─────────── */ + isr_enabled = 0; + + /* ── 2. HALT: спать до следующего INT ──────────────────── * + * После HALT ISR гарантированно отработал с enabled=0, * + * не тронул ни один буфер. ~1.5 мс максимум. */ + __asm__( "halt" ); + + /* ── 3. Обнулить wait_ctr (отменить CMD_WAIT) ─────────── */ + isr_wait_ctr = 0; + + /* ── 4. Заполнить НЕАКТИВНЫЙ буфер shutdown-цепочкой ───── * + * vgm_hl_pos уже установлен на vgm_hl_abort_pos → * + * vgm_fill_buffer выдаст silence + CMD_ISR_DONE. */ + free_idx = (isr_active_buf & 1) ^ 1; + vgm_fill_buffer(free_idx); + + /* ── 5. Переключить ISR на заполненный буфер ─────────── */ + isr_active_buf = free_idx; + isr_read_ptr = (uint16_t)(free_idx ? cmd_buf_b : cmd_buf_a); + + /* ── 6. Разморозить → ISR обработает shutdown ───────── */ + isr_enabled = 1; +} + +void update_buffer(void) +{ + uint8_t active; + uint8_t free_idx; + + if (!s_playback_inited || !isr_enabled || isr_done) + return; + + active = (uint8_t)(isr_active_buf & 1U); + + /* Если ISR переключился на другой буфер — старый считается освобождённым */ + if (active != s_last_active_buf) + { + s_buf_ready[s_last_active_buf] = 0; + s_last_active_buf = active; + } + + /* Всегда держим готовым неактивный буфер */ + free_idx = (uint8_t)(active ^ 1U); + + if (s_buf_ready[free_idx]) + return; + + /* Заполнить свободный буфер — vgm_fill_buffer обрабатывает HL-переходы */ +#ifdef DEBUG_BORDER + isr_border_color = 2; + border(2); +#endif + + vgm_fill_buffer(free_idx); + s_buf_ready[free_idx] = 1; + +#ifdef DEBUG_BORDER + isr_border_color = 0; + border(0); +#endif +} + +void update_playback_info(void) +{ + uint16_t sec; + uint8_t sec_changed, loop_changed; + + if (state != STATE_PLAYBACK) + return; + + sec = isr_play_seconds; + + sec_changed = (sec != s_last_displayed_sec); + loop_changed = (vgm_loop_count != s_last_loop_count); + + if (!sec_changed && !loop_changed) + return; + + /* ── Переключаем Window3 на текстовую страницу напрямую через порт. + * Не используем wc_mngcvpl — она вызывает WC_ENTRY и обновляет + * WC_SYS_PGC, что может корраптить состояние WC при перехваченном ISR. + * sfr_page3 = прямая запись в порт 0x13AF. */ + { + uint8_t saved_pg = sfr_page3; + sfr_page3 = s_pb_text_pg; + + volatile uint8_t *base = + (volatile uint8_t *)(0xC000 + s_pb_char_ofs); + + /* ── Время: инкрементальный BCD-счётчик. + * Секунды всегда растут на 1 → вместо 6 делений (sec/60, + * sec%60, min/10, min%10, s/10, s%10) — только INC + CP. + * ~20 тактов вместо ~6000. */ + if (sec_changed) { + s_last_displayed_sec = sec; + if (++s_pb_d_sec1 > '9') { + s_pb_d_sec1 = '0'; + if (++s_pb_d_sec10 > '5') { + s_pb_d_sec10 = '0'; + if (++s_pb_d_min1 > '9') { + s_pb_d_min1 = '0'; + s_pb_d_min10++; + } + } + } + base[PB_OFS_MIN10] = s_pb_d_min10; + base[PB_OFS_MIN1] = s_pb_d_min1; + base[PB_OFS_SEC10] = s_pb_d_sec10; + base[PB_OFS_SEC1] = s_pb_d_sec1; + } + + if (loop_changed) { + s_last_loop_count = vgm_loop_count; + base[PB_OFS_LOOP] = '1' + vgm_loop_count; + } + + /* ── Progress bar: Bresenham-аккумулятор. + * Каждую секунду добавляем s_pb_width к ошибке, + * когда ошибка >= total — красим колонку и вычитаем. + * Ноль делений. Точно для любого соотношения sec/width. */ + if (sec_changed && vgm_total_seconds) { + s_pb_err += s_pb_width; + while (s_pb_col < s_pb_width && + s_pb_err >= vgm_total_seconds) { + *(volatile uint8_t *)(0xC000 + s_pb_attr_ofs + s_pb_col) = + WC_COLOR(WC_GREEN, WC_BLACK); + s_pb_col++; + s_pb_err -= vgm_total_seconds; + } + } + + sfr_page3 = saved_pg; + } +} + +/* ── main() ──────────────────────────────────────────────────────────── */ +void main(void) +{ + // Инициализация буферов для строк + buf_init(work_buf, 96); + buf_init(error_buf, 96); + + state = STATE_INIT; + + wc_turbopl(WC_TURBO_CPU, 0x02); /* 14 МГц */ + + /* ── Чтение INI-параметров ─────────────────────────────────────── + * Формат в wc.ini: VGMPLAY.WMF -min_dur=10 -max_dur=4 -loops=1 -saa=1 -saa_chips=1 + * param 0: min_duration секунд (default 10) + * param 1: max_duration в минутах (default 4 → 240 сек) + * param 2: loop_count (default 1) + * param 3: saa mode 1=chip0(default), 2=chip1 (только 1-чип + dual музыка) + * param 4: saa_chips 1=один SAA(default), 2=два SAA (эксп. плата) + * wc_prm_pl() возвращает 0xFF если параметр не задан. */ + { + uint8_t v; + v = wc_prm_pl(0); + if (v != 0xFF) cfg_min_duration = v; + + v = wc_prm_pl(1); + if (v != 0xFF) cfg_max_duration = (uint16_t)v * 60u; + + v = wc_prm_pl(2); + if (v != 0xFF) cfg_loop_rewinds = v; + + v = wc_prm_pl(3); + if (v >= 1 && v <= 2) cfg_saa_mode = v - 1; + s_saa_mode_user = cfg_saa_mode; + + v = wc_prm_pl(4); + s_board_dual_saa = (v == 2) ? 1 : 0; + } + + wc_strset((char *)&s_wnd, sizeof(s_wnd), 0); + + // SOW — рамка с тенью (TYPE3, заголовок рисуем вручную) + s_wnd.type = WC_WIN_TYPE3 | WC_WIN_SHADOW; + s_wnd.cur_mask = 0x05; + + /* Динамическое центрирование по WC text mode */ + { + uint8_t scr_h = wc_get_height(); /* 25/30/36 */ + uint8_t scr_w = (scr_h >= 36) ? 90 : 80; + s_wnd.x = (scr_w > WND_W) ? (scr_w - WND_W) / 2 : 0; + s_wnd.y = (scr_h > WND_H) ? (scr_h - WND_H) / 2 : 0; + } + + s_wnd.width = WND_W; + s_wnd.height = WND_H; + s_wnd.color = CLR_WIN; + s_wnd.buf_addr = 0; + s_wnd.divider1 = DIV1_FROM_BOTTOM; + s_wnd.divider2 = DIV2_FROM_BOTTOM; + + /* Дождаться отпускания всех клавиш с точки зрения WC. + * WC ISR ещё работает штатно (PSD2 обновляет TAB нормально), + * поэтому wc_key_wait_release даст WC привести TAB в чистое + * состояние. Только после этого отключаем WC PS/2 handler — + * FIFO переходит под наш контроль без артефактов. */ + wc_key_wait_release(); + wc_int_pl(WC_INT_NO_PS2); + + // Восстановить дисплей WC и нарисовать окно + wc_gedpl(); + wc_prwow(&s_wnd); + + /* Заголовок окна синим текстом */ + buf_clear(work_buf); + buf_append_str(work_buf, s_win_title); + print_line(&s_wnd, ROW_TITLE, work_buf, CLR_TITLE); + + /* Предварительный вывод: имя, размер, страницы (до загрузки) */ + s_is_vgz = 0; + s_vgm_unpacked_size = 0; + s_vgz_compressed_pages = 0; + draw_pre_load_info(); + + /* Если файл .vgz — показать "Unpacking..." ДО начала загрузки. * + * Это последний WC API вызов перед inflate — после него load_vgm * + * работает только через порты, без обращений к WC. */ + if (is_vgz_filename()) { + buf_clear(work_buf); + buf_append_str(work_buf, " Unpacking..."); + print_line(&s_wnd, ROW_VGM_START, work_buf, WC_COLOR(WC_YELLOW, WC_BLACK)); + + /* Настройка progress bar: получить адрес текст. экрана для VGM секции col 2 */ + { + uint16_t bar_addr = wc_gadrw(&s_wnd, ROW_VGM_START, 2); + ifl_pb_text_pg = wc_get_pgc(); /* физ. страница текст. экрана */ + ifl_pb_attr_ofs = (bar_addr & 0x3FFF) | 0x0080; /* SET 7,L = область атрибутов */ + ifl_pb_bw = get_content_width(&s_wnd); + ifl_pb_green = WC_COLOR(WC_GREEN, WC_BLACK); + ifl_pb_total = 0; /* будет установлено в load_vgm() */ + } + } + + uint8_t loaded_pages = load_vgm(); + + if (!loaded_pages) + { + state = STATE_ERROR; + wc_exit_code = WC_EXIT_ESC; + buf_append_str(error_buf, "Failed to load VGM file."); + } + else + { + state = STATE_PLAYBACK; + + if (vgm_parse_header() != VGM_OK) + { + state = STATE_ERROR; + wc_exit_code = WC_EXIT_ESC; + buf_append_str(error_buf, "Invalid VGM header."); + } + else + { + /* Парсим GD3 и восстанавливаем VPL-страницу */ + vgm_parse_gd3(); + + /* ── 0x66 sentinel ─────────────────────────────────── + * Пишем VGM end-marker (0x66) сразу после конца данных, + * чтобы hot-loop в vgm_fill_buffer мог работать без + * проверки EOF на каждой итерации. */ + wc_mngcvpl(vgm_end_page); + *(volatile uint8_t *)vgm_end_addr = 0x66; + + wc_mngcvpl(vgm_cur_page); + + /* Определяем типы чипов и длительность трека */ + detect_active_chips(); + } + } + + drow_ui(); + + /* Кешируем параметры progress bar (строка playback info) */ + { + uint16_t bar_addr = wc_gadrw(&s_wnd, ROW_PROGRESS, 2); + s_pb_text_pg = wc_get_pgc(); + s_pb_char_ofs = bar_addr & 0x3FFF; + s_pb_attr_ofs = s_pb_char_ofs | 0x0080; + s_pb_width = get_content_width(&s_wnd); + s_pb_col = 0; + s_pb_err = 0; + } + + /* Кешируем адреса строк спектроанализатора */ + { + for (uint8_t r = 0; r < 4; r++) { + uint16_t a = wc_gadrw(&s_wnd, ROW_SPECTRUM + r, 2); + s_spec_char_ofs[r] = a & 0x3FFF; + s_spec_attr_ofs[r] = s_spec_char_ofs[r] | 0x0080; + } + spectrum_font_init(); + spectrum_decay_reset(); + } + + /* Печатаем полную строку progress bar ОДИН РАЗ через WC API. + * "Playing 00:00 / MM:SS Loop 1" + чёрный фон/белый текст. + * В главном цикле обновляются только 5 символов + атрибуты. */ + { + buf_clear(work_buf); + buf_append_str(work_buf, "Playing 00:00 / "); + buf_append_mmss(work_buf, vgm_total_seconds); + buf_append_str(work_buf, " Loop 1"); + print_line(&s_wnd, ROW_PROGRESS, work_buf, WC_COLOR(WC_BRIGHT_WHITE, WC_BLACK)); + } + + if (state == STATE_PLAYBACK) + start_playback(); + + uint8_t key = 0; + wc_exit_code = (wc_file_idx == wc_file_count) ? WC_EXIT_ESC : WC_EXIT_NEXT; + + /* PS/2 FSM: init выполняется один раз в main(), здесь + * только ре-drain для слива событий от предыдущего трека. */ + ps2_init(); + + /* Главный цикл: приоритет — update_buffer (буфер не должен голодать), + клавиатура + UI — по остаточному принципу. */ + while (state == STATE_PLAYBACK) + { + /* ── 1. Буфер: максимальный приоритет ──────────────── */ + update_buffer(); + + /* ISR замер на CMD_ISR_DONE → чистый выход */ + if (isr_done) + break; + + /* ── 2. Клавиатура: PS/2 FIFO (~1 раз в кадр) ────── */ + key = ps2_poll(); + + if (key == KEY_NEXT) + { + vgm_hl_pos = vgm_hl_abort_pos; + vgm_song_ended = 0; + wc_exit_code = WC_EXIT_NEXT; + instant_abort(); + } + else if (key == KEY_PREV) + { + vgm_hl_pos = vgm_hl_abort_pos; + vgm_song_ended = 0; + wc_exit_code = WC_EXIT_PREV; + instant_abort(); + } + else if (key == KEY_ESC) + { + vgm_hl_pos = vgm_hl_abort_pos; + vgm_song_ended = 0; + wc_exit_code = WC_EXIT_ESC; + instant_abort(); + } + else if (key >= KEY_SAA1 && key <= KEY_SAA2 && s_has_saa2 && !s_board_dual_saa) + { + uint8_t new_mode = key - KEY_SAA1; /* 0 or 1 */ + s_saa_mode_user = new_mode; + cfg_saa_mode = new_mode; + extern uint8_t spec_saa_dual; + spec_saa_dual = 0; + /* Обновить подсказку SAA mode */ + buf_clear(work_buf); + buf_append_str(work_buf, " [Space] Next [P]rev [Esc] Exit SAA:"); + buf_append_char(work_buf, '1' + cfg_saa_mode); + print_line(&s_wnd, ROW_HELP, work_buf, CLR_TITLE); + } + + if (isr_play_seconds != s_last_displayed_sec || vgm_loop_count != s_last_loop_count) + update_playback_info(); + + /* ── 3. Спектроанализатор ──────────────────────────── */ + spectrum_decay(); + spectrum_render(); + } + + ps2_deinit(); + stop_playback(); + spectrum_font_restore(); + wc_rresb(&s_wnd); +} diff --git a/src_sdcc/scripts/gen_freq_tables.js b/src_sdcc/scripts/gen_freq_tables.js new file mode 100644 index 0000000..3c6a124 --- /dev/null +++ b/src_sdcc/scripts/gen_freq_tables.js @@ -0,0 +1,465 @@ +/** + * gen_freq_tables.js — Генерация LUT таблиц масштабирования частот PSG + * + * Использование: + * node scripts/gen_freq_tables.js [--binary ] [--asm ] [--info] + * + * Каждая таблица: 4096 entries × uint16_t LE = 8192 байт (8 КБ). + * + * Покрывает: + * - AY/YM2149: 12-bit tone period (regs 0x00–0x05), 0..4095 + * - Noise: 5-bit (reg 0x06), 0..31 — первые 32 записи + * + * Формула масштабирования PSG: + * F = CLK / (16 × period) → scaled = period × (target / source) + * + * FM F-Number масштабирование (YM2203) требует ОБРАТНОГО ratio (source/target) + * и выполняется runtime через дробные коэффициенты (fm_frac_t в C-хедере). + * + * Layout: 2 ratios per 16 КБ page: + * 0x0000–0x1FFF : PSG таблица A (8 КБ) + * 0x2000–0x3FFF : PSG таблица B (8 КБ) + * + * Итого: 3 pages × 16 КБ = 48 КБ (3 доп. страницы) + */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +/* ── Целевые частоты нашего железа (TSConfig MultiSound) ─────────────── */ +const HW_CLK_AY = 1750000; /* AY/YM2149: 1.75 МГц */ +const HW_CLK_YM2203 = 3500000; /* YM2203 OPN: 3.5 МГц */ + +/* ── Порог округления при сравнении частот ──────────────────────────── + * Если VGM clock отличается от канонического ≤ MATCH_TOLERANCE_PCT, + * считаем что это тот же коэффициент. Отдельно: если ratio ≈ 1.0 + * (±BYPASS_PCT), масштабирование не нужно вообще. */ +const MATCH_TOLERANCE_PCT = 5.0; /* % — порог совпадения таблиц */ +const BYPASS_PCT = 2.0; /* % — порог bypass (≈1:1) */ + +/* ── Предопределённые варианты VGM-клоков ───────────────────────────── + * Для YM2203: PSG (SSG) = fclk/2, FM = fclk + * Для AY8910: PSG = fclk + * + * PSG ratio = HW_CLK_AY / srcPsgHz (target/source → period scaling) + * FM ratio = srcYM2203 / HW_CLK_YM2203 (source/target → fnum scaling, INVERSE!) + * + * PSG ratio и FM ratio НЕ равны друг другу — нужны ОТДЕЛЬНЫЕ таблицы! + * PSG: scaled_period = period × (HW_CLK_AY / srcPsgHz) + * FM: scaled_fnum = fnum × (srcYM2203 / HW_CLK_YM2203) + * + * FM масштабирование: все используемые FM ratios сводятся к N/7, + * поэтому fm_scale хранит множитель N (0=bypass, 3, 5, 6, 8). + * В ASM на Z80: fnum × N через shifts+adds, затем /7 binary division. + * NTSC (45/44 ≈ 1.023) — bypass, погрешность 2.3% в рамках 5% допуска. + * + * Близкие частоты (≤ MATCH_TOLERANCE_PCT) объединяются: + * 1996800 Hz ≈ 2000000 Hz (0.16%) → используют одну таблицу '4MHz_2000' + * 3993600 Hz ≈ 4000000 Hz (0.16%) → то же самое + */ +const RATIOS = [ + { + name: 'NTSC_1790', + srcPsgHz: 1789773, /* AY8910 @ 1789773 или YM2203 @ 3579545 (SSG=1789773) */ + srcYM2203: 3579545, + fm_scale: 0, /* 45/44 ≈ 1.023 — bypass (2.3% в рамках 5% допуска) */ + desc: 'NTSC-derived (NES, many JP computers)', + }, + { + name: '3MHz_1500', + srcPsgHz: 1500000, /* AY8910 @ 1.5M или YM2203 @ 3M */ + srcYM2203: 3000000, + fm_scale: 6, /* 6/7 — fnum × 6 / 7 */ + desc: 'PC-88, MSX (3 MHz YM2203)', + }, + { + name: '4MHz_2000', + srcPsgHz: 2000000, /* AY8910 @ 2M или YM2203 @ 4M (≈1996800/3993600) */ + srcYM2203: 4000000, + fm_scale: 8, /* 8/7 — fnum × 8 / 7 */ + desc: 'Arcade boards (4 MHz YM2203, covers 3.9936 MHz)', + }, + { + name: 'CPC_1000', + srcPsgHz: 1000000, /* Amstrad CPC AY @ 1 MHz */ + srcYM2203: 0, /* нет FM */ + desc: 'Amstrad CPC (1 MHz AY)', + }, + { + name: '1250', + srcPsgHz: 1250000, + srcYM2203: 2500000, + fm_scale: 5, /* 5/7 — fnum × 5 / 7 */ + desc: 'Some boards (1.25 MHz AY / 2.5 MHz YM2203)', + }, + { + name: '1500_750', + srcPsgHz: 750000, /* YM2203 @ 1.5M → SSG = 750 kHz */ + srcYM2203: 1500000, + fm_scale: 3, /* 3/7 — fnum × 3 / 7 */ + desc: 'Arcade/custom boards (1.5 MHz YM2203, SSG=750 kHz)', + }, +]; + +/** + * matchTable(psgClkHz) → { index, ratio, bypass } + * + * Ищет ближайшую таблицу для заданного PSG clock. + * Если расхождение ratio от 1.0 ≤ BYPASS_PCT → bypass (масштабирование не нужно). + * Если ближайшая таблица ≤ MATCH_TOLERANCE_PCT → возвращает её индекс. + * Иначе → index = -1 (нужна runtime-генерация или таблицы нет). + */ +function matchTable(psgClkHz) { + const ratio = psgClkHz / HW_CLK_AY; + + /* Bypass: частоты почти совпадают с HW */ + if (Math.abs(ratio - 1.0) * 100 <= BYPASS_PCT) { + return { index: -1, ratio, bypass: true, name: 'BYPASS' }; + } + + let bestIdx = -1; + let bestDiffPct = Infinity; + + for (let i = 0; i < RATIOS.length; i++) { + const diffPct = Math.abs(psgClkHz - RATIOS[i].srcPsgHz) / RATIOS[i].srcPsgHz * 100; + if (diffPct < bestDiffPct) { + bestDiffPct = diffPct; + bestIdx = i; + } + } + + if (bestDiffPct <= MATCH_TOLERANCE_PCT) { + return { + index: bestIdx, + ratio: RATIOS[bestIdx].srcPsgHz / HW_CLK_AY, + bypass: false, + name: RATIOS[bestIdx].name, + diffPct: bestDiffPct, + }; + } + + /* Нет подходящей таблицы — runtime generation needed */ + return { index: -1, ratio, bypass: false, name: 'RUNTIME' }; +} + +const TABLE_ENTRIES = 4096; /* 12-bit PSG range */ +const TABLE_BYTES = TABLE_ENTRIES * 2; /* uint16_t LE = 8 КБ */ +const PAGE_SIZE = 16384; /* 16 КБ */ + +/* ── Генерация PSG таблицы ─────────────────────────────────────────── + * ratio = target / source (period scales up/down to preserve frequency) + * PSG: F = CLK / (16 × period) → scaled_period = period × (target/source) + */ +function generateTable(srcPsgHz, targetHz) { + const buf = Buffer.alloc(TABLE_BYTES); + const ratio = targetHz / srcPsgHz; + + for (let i = 0; i < TABLE_ENTRIES; i++) { + let scaled = Math.round(i * ratio); + if (scaled > 0xFFFF) scaled = 0xFFFF; /* clamp uint16 */ + buf.writeUInt16LE(scaled, i * 2); + } + + return buf; +} + +/* ── Вывод информации о таблицах ─────────────────────────────────────── */ +function printInfo() { + console.log('=== Frequency Scale LUT Tables ==='); + console.log(`Target PSG: ${HW_CLK_AY} Hz`); + console.log(`PSG table: ${TABLE_ENTRIES} entries × 2 bytes = ${TABLE_BYTES} bytes`); + console.log(`Layout: 2 ratios per 16 КБ page`); + console.log(`Total: ${Math.ceil(RATIOS.length / 2)} pages × 16 КБ = ${Math.ceil(RATIOS.length / 2) * PAGE_SIZE} bytes`); + console.log(''); + + for (let r = 0; r < RATIOS.length; r++) { + const rt = RATIOS[r]; + const psgRatio = HW_CLK_AY / rt.srcPsgHz; + const page = Math.floor(r / 2) + PAGE_BASE; + const slot = r % 2; + const offset = slot * TABLE_BYTES; + + console.log(`[${r}] ${rt.name}: PSG ${rt.srcPsgHz} Hz → ratio = ${psgRatio.toFixed(6)}`); + console.log(` ${rt.desc}`); + console.log(` Page ${page}, slot ${slot} (offset 0x${offset.toString(16).toUpperCase()})`); + if (rt.srcYM2203) { + const fmRatio = rt.srcYM2203 / HW_CLK_YM2203; + const fmDesc = rt.fm_scale ? `×${rt.fm_scale}/7` : 'bypass'; + console.log(` FM: YM2203 @ ${rt.srcYM2203} Hz → fm_ratio = ${fmRatio.toFixed(6)} (${fmDesc})`); + } + + /* Проверка нескольких ключевых значений */ + const checks = [ + { name: 'PSG tone 440 Hz equiv', val: Math.round(rt.srcPsgHz / (16 * 440)) }, + { name: 'PSG max 12-bit', val: 4095 }, + { name: 'Noise max 5-bit', val: 31 }, + ]; + for (const c of checks) { + if (c.val >= TABLE_ENTRIES) continue; + const scaled = Math.round(c.val * psgRatio); + console.log(` PSG ${c.name}: ${c.val} → ${scaled}`); + } + console.log(''); + } + + /* ── Тест: сопоставление «диких» частот из реальных VGM ────────── */ + console.log('--- Clock matching test (bypass ' + BYPASS_PCT + '%, tolerance ' + MATCH_TOLERANCE_PCT + '%) ---'); + const testClocks = [ + /* PSG clocks (AY8910 fclk, или YM2203 SSG = fclk/2) */ + { name: 'TSconf HW (bypass)', hz: 1750000 }, + { name: 'Close to HW (bypass)', hz: 1773400 }, + { name: 'NTSC exact', hz: 1789773 }, + { name: 'NTSC rounded', hz: 1789750 }, + { name: 'YM2203 3.58MHz SSG', hz: 1789772 }, + { name: 'PAL-derived', hz: 1773448 }, + { name: 'MSX 3MHz SSG', hz: 1500000 }, + { name: 'MSX variant', hz: 1536000 }, + { name: '4MHz SSG exact', hz: 2000000 }, + { name: '3.9936MHz SSG', hz: 1996800 }, + { name: '4.028MHz SSG', hz: 2014000 }, + { name: 'CPC exact', hz: 1000000 }, + { name: 'CPC-ish', hz: 1023000 }, + { name: '2.5MHz SSG', hz: 1250000 }, + { name: 'Weird 1.3MHz', hz: 1300000 }, + { name: 'Weird 1.6MHz', hz: 1600000 }, + { name: 'Weird 900kHz', hz: 900000 }, + { name: 'YM2203 1.5MHz SSG', hz: 750000 }, + { name: 'ZX 128 (1.7734 MHz)', hz: 1773400 }, + ]; + for (const t of testClocks) { + const m = matchTable(t.hz); + const tag = m.bypass ? 'BYPASS' : + m.index >= 0 ? `table[${m.index}]=${m.name} (${m.diffPct.toFixed(2)}%)` : + 'NO MATCH → runtime'; + console.log(` ${t.hz.toString().padStart(7)} Hz ${t.name.padEnd(26)} → ${tag}`); + } + + /* Подсказка по Z80 маппингу */ + console.log(''); + console.log('--- Z80 matching: compare clock_khz ±5% ---'); + for (let r = 0; r < RATIOS.length; r++) { + const khz = Math.round(RATIOS[r].srcPsgHz / 1000); + const tol = Math.round(khz * MATCH_TOLERANCE_PCT / 100); + const page = Math.floor(r / 2) + PAGE_BASE; + const slot = r % 2; + console.log(` [${r}] ${RATIOS[r].name.padEnd(12)} ${khz} kHz ± ${tol} kHz → page ${page}, slot ${slot}`); + } +} + +/* ── Генерация бинарного файла ───────────────────────────────────────── * + * Layout: 2 ratios per 16 КБ page. + * + * Page N+0: PSG[0] (0x0000-0x1FFF) + PSG[1] (0x2000-0x3FFF) + * Page N+1: PSG[2] (0x0000-0x1FFF) + PSG[3] (0x2000-0x3FFF) + * ... + * + * Итого: ceil(RATIOS.length / 2) × 16 КБ + */ +function generateBinary(outPath) { + const numPages = Math.ceil(RATIOS.length / 2); + const totalSize = numPages * PAGE_SIZE; + const out = Buffer.alloc(totalSize, 0x00); + + for (let r = 0; r < RATIOS.length; r++) { + const page = Math.floor(r / 2); + const slot = r % 2; + const offset = page * PAGE_SIZE + slot * TABLE_BYTES; + + const table = generateTable(RATIOS[r].srcPsgHz, HW_CLK_AY); + table.copy(out, offset); + } + + fs.writeFileSync(outPath, out); + console.log(`Written: ${outPath} (${out.length} bytes, ${numPages} pages)`); + + /* Вывод маппинга для кода */ + console.log('\nC mapping (for vgm.c):'); + console.log('// After determining VGM PSG clock, select table:'); + for (let r = 0; r < RATIOS.length; r++) { + const page = Math.floor(r / 2) + PAGE_BASE; + const offset = (r % 2) * TABLE_BYTES; + console.log(`// ${RATIOS[r].srcPsgHz} Hz → wc_mng0_pl(${page}), offset = 0x${offset.toString(16)}`); + } +} + +/* ── Генерация ASM include (для верификации) ─────────────────────────── */ +function generateAsm(outPath) { + let asm = '; Auto-generated by gen_freq_tables.js\n'; + asm += '; Frequency scale LUT tables for PSG/FM\n'; + asm += `; Target: PSG=${HW_CLK_AY} Hz, FM=${HW_CLK_YM2203} Hz\n`; + asm += `; ${TABLE_ENTRIES} entries × 2 bytes = ${TABLE_BYTES} bytes per table\n\n`; + + for (let r = 0; r < RATIOS.length; r++) { + const rt = RATIOS[r]; + const ratio = rt.srcPsgHz / HW_CLK_AY; + asm += `; Table ${r}: ${rt.name} (${rt.srcPsgHz} Hz, ratio=${ratio.toFixed(6)})\n`; + asm += `_freq_lut_${r}::\n`; + + const table = generateTable(rt.srcPsgHz, HW_CLK_AY); + for (let i = 0; i < TABLE_ENTRIES; i += 8) { + const values = []; + for (let j = 0; j < 8 && (i + j) < TABLE_ENTRIES; j++) { + values.push('0x' + table.readUInt16LE((i + j) * 2).toString(16).padStart(4, '0')); + } + asm += ` .dw ${values.join(', ')}\n`; + } + asm += '\n'; + } + + fs.writeFileSync(outPath, asm); + console.log(`Written: ${outPath}`); +} + +/* ── Генерация C-хедера для Z80 маппинга ───────────────────────────── + * Содержит таблицу {clk_khz, tol_khz, page, offset} для линейного + * поиска ближайшей LUT при парсинге VGM заголовка. + * + * Layout: 2 ratios per page — PSG_A@0x0000 (8 КБ) + PSG_B@0x2000 (8 КБ). + * FM масштабирование — runtime через fm_frac_t (дробные коэффициенты). + * + * Алгоритм на Z80 (в vgm.c): + * 1. bypass: |psg_khz - 1750| ≤ 35 → нет масштабирования + * 2. Линейный поиск: |psg_khz - entry.clk_khz| ≤ entry.tol_khz → match + * 3. Нет match → native (без масштабирования) + * + * Все сравнения — 16 бит (uint16_t kHz), дёшево на Z80. + */ +function generateCHeader(outPath) { + let h = ''; + h += '/* Auto-generated by gen_freq_tables.js — DO NOT EDIT */\n'; + h += '#ifndef FREQ_LUT_MAP_H\n'; + h += '#define FREQ_LUT_MAP_H\n\n'; + h += '#include "types.h"\n\n'; + + /* Count YM2203 entries (those with srcYM2203 > 0) */ + const ymEntries = RATIOS.filter(r => r.srcYM2203 > 0); + + h += `/* AY/YM2149: bypass |psg_khz - ${Math.round(HW_CLK_AY/1000)}| <= ${Math.round(HW_CLK_AY/1000 * BYPASS_PCT / 100)} */\n`; + h += `#define FREQ_LUT_HW_KHZ ${Math.round(HW_CLK_AY/1000)}u\n`; + h += `#define FREQ_LUT_BYPASS_TOL ${Math.round(HW_CLK_AY/1000 * BYPASS_PCT / 100)}u /* ${BYPASS_PCT}% */\n`; + h += `/* YM2203: bypass |ym_khz - ${Math.round(HW_CLK_YM2203/1000)}| <= ${Math.round(HW_CLK_YM2203/1000 * BYPASS_PCT / 100)} */\n`; + h += `#define FREQ_LUT_HW_YM_KHZ ${Math.round(HW_CLK_YM2203/1000)}u\n`; + h += `#define FREQ_LUT_BYPASS_TOL_YM ${Math.round(HW_CLK_YM2203/1000 * BYPASS_PCT / 100)}u /* ${BYPASS_PCT}% */\n\n`; + + h += `#define FREQ_LUT_AY_COUNT ${RATIOS.length}u\n`; + h += `#define FREQ_LUT_YM_COUNT ${ymEntries.length}u\n`; + h += `#define FREQ_LUT_PSG_ENTRIES ${TABLE_ENTRIES}u /* 12-bit PSG */\n`; + h += `#define FREQ_LUT_PSG_BYTES ${TABLE_BYTES}u /* per PSG table */\n\n`; + + h += 'typedef struct {\n'; + h += ' uint16_t clk_khz; /* canonical clock in kHz (PSG or YM master) */\n'; + h += ' uint16_t tol_khz; /* \u00b1tolerance in kHz (5%) */\n'; + h += ' uint8_t page; /* plugin page (PAGE_BASE-based) */\n'; + h += ' uint16_t offset; /* PSG table byte offset within page */\n'; + h += '} freq_lut_entry_t;\n\n'; + + + + /* AY map — PSG only */ + h += `/* AY/YM2149 PSG table map (tolerance = ${MATCH_TOLERANCE_PCT}% of canonical PSG clock)\n`; + h += ` * Layout: 2 ratios per page, PSG_A@0x0000, PSG_B@0x${TABLE_BYTES.toString(16).toUpperCase()} */\n`; + h += 'static const freq_lut_entry_t freq_lut_map_ay[FREQ_LUT_AY_COUNT] = {\n'; + + for (let r = 0; r < RATIOS.length; r++) { + const khz = Math.round(RATIOS[r].srcPsgHz / 1000); + const tol = Math.round(khz * MATCH_TOLERANCE_PCT / 100); + const page = Math.floor(r / 2) + PAGE_BASE; + const offset = (r % 2) * TABLE_BYTES; + const comma = (r < RATIOS.length - 1) ? ',' : ' '; + h += ` { ${khz.toString().padStart(4)}u, ${tol.toString().padStart(3)}u, ${page}, 0x${offset.toString(16).toUpperCase().padStart(4, '0')} }${comma} /* ${RATIOS[r].name} */\n`; + } + h += '};\n\n'; + + /* YM2203 map — uses same PSG table as AY, plus FM frac for runtime */ + h += '/* YM2203 table map (full master clock, PSG table = same as AY entry) */\n'; + h += 'static const freq_lut_entry_t freq_lut_map_ym[FREQ_LUT_YM_COUNT] = {\n'; + + let ymIdx = 0; + for (let r = 0; r < RATIOS.length; r++) { + if (!RATIOS[r].srcYM2203) continue; + const khz = Math.round(RATIOS[r].srcYM2203 / 1000); + const tol = Math.round(khz * MATCH_TOLERANCE_PCT / 100); + const page = Math.floor(r / 2) + PAGE_BASE; + const offset = (r % 2) * TABLE_BYTES; + ymIdx++; + const comma = (ymIdx < ymEntries.length) ? ',' : ' '; + h += ` { ${khz.toString().padStart(4)}u, ${tol.toString().padStart(3)}u, ${page}, 0x${offset.toString(16).toUpperCase().padStart(4, '0')} }${comma} /* ${RATIOS[r].name} (YM2203@${khz}kHz) */\n`; + } + h += '};\n\n'; + + /* FM multiplier table — indexed same as freq_lut_map_ym[] */ + h += '/* FM F-Number \u043c\u043d\u043e\u0436\u0438\u0442\u0435\u043b\u044c (YM2203): scaled = fnum \u00d7 fm_mul / 7.\n'; + h += ' * 0 = bypass (\u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e).\n'; + h += ' * ASM \u043d\u0430 Z80: \u0443\u043c\u043d\u043e\u0436\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 shifts+adds, \u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043d\u0430 7 binary division */\n'; + h += 'static const uint8_t freq_lut_fm_mul[FREQ_LUT_YM_COUNT] = {\n'; + + ymIdx = 0; + for (let r = 0; r < RATIOS.length; r++) { + if (!RATIOS[r].srcYM2203) continue; + const rt = RATIOS[r]; + const fmRatio = rt.srcYM2203 / HW_CLK_YM2203; + ymIdx++; + const comma = (ymIdx < ymEntries.length) ? ',' : ' '; + const fmDesc = rt.fm_scale ? `\u00d7${rt.fm_scale}/7` : 'bypass (2.3%)'; + h += ` ${rt.fm_scale}${comma} /* ${rt.name}: ${fmRatio.toFixed(6)} = ${fmDesc} */\n`; + } + h += '};\n\n'; + + h += '/*\n'; + h += ' * Usage in vgm.c (parse_header):\n'; + h += ' *\n'; + h += ' * 1. Bypass check: |clock_khz - HW| <= BYPASS_TOL \u2192 native\n'; + h += ' * 2. Find matching table \u2192 set page, PSG base = offset\n'; + h += ' * 3. For YM2203: fm_mul = freq_lut_fm_mul[j]\n'; + h += ' *\n'; + h += ' * PSG: scaled_period = freq_lut_base[period_12bit]\n'; + h += ' * FM: asm_scale_fm() : fb_scaled = fnum \u00d7 fm_mul / 7\n'; + h += ' * if (fb_scaled > 2047) block++, fb_scaled >>= 1\n'; + h += ' */\n\n'; + + h += '#endif /* FREQ_LUT_MAP_H */\n'; + + fs.writeFileSync(outPath, h); + console.log(`Written: ${outPath}`); +} + +/* ── Номер первой plugin-страницы с LUT данными ───────────────────── + * Зависит от числа страниц кода: code_pages = ceil(code_size / 16384). + * Передаётся через --page-base N. По умолчанию 2 (типичный случай: + * CODE+DATA ≈ 15-16 КБ, занимает 2 страницы #8000-#FFFF). */ +let PAGE_BASE = 2; + +/* ── CLI ─────────────────────────────────────────────────────────────── */ +function main() { + const args = process.argv.slice(2); + let doBinary = null; + let doAsm = null; + let doCheader = null; + let doInfo = false; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--binary' && args[i + 1]) { doBinary = args[++i]; } + else if (args[i] === '--asm' && args[i + 1]) { doAsm = args[++i]; } + else if (args[i] === '--cheader' && args[i + 1]) { doCheader = args[++i]; } + else if (args[i] === '--page-base' && args[i + 1]) { PAGE_BASE = parseInt(args[++i], 10); } + else if (args[i] === '--info') { doInfo = true; } + else { + console.error(`Unknown arg: ${args[i]}`); + console.error('Usage: node gen_freq_tables.js [--binary out.bin] [--asm out.s] [--cheader out.h] [--page-base N] [--info]'); + process.exit(1); + } + } + + if (!doBinary && !doAsm && !doCheader && !doInfo) { + doInfo = true; /* default: print info */ + } + + if (doInfo) printInfo(); + if (doBinary) generateBinary(doBinary); + if (doAsm) generateAsm(doAsm); + if (doCheader) generateCHeader(doCheader); +} + +main(); diff --git a/src_sdcc/scripts/gen_pos_table.js b/src_sdcc/scripts/gen_pos_table.js new file mode 100644 index 0000000..5dd62c8 --- /dev/null +++ b/src_sdcc/scripts/gen_pos_table.js @@ -0,0 +1,97 @@ +/** + * @file gen_pos_table.js + * @brief Генератор таблицы позиций INT для ISR + * + * Параметры: + * entries = 56 (прерываний на кадр) + * step = 1280 (T-states между INT) + * line_len = 224 (T-states в строке = 448 пикс / 2) + * + * Частота ISR: 3,500,000 / 1280 = 2734.375 Гц (~44100/16) + * Замыкание: 56 x 1280 = 71680 T = 320 строк x 224 T (1 TV-кадр) + * + * Формат записи (5 байт): + * [VSINTH, VSINTL, HSINT, next_lo, next_hi] + * + * OUTI-цикл в ISR: BC начинается с #24AF: + * OUTI #24AF -> VSINTH (бит0 = VCNT[8]) + * OUTI #23AF -> VSINTL (VCNT[7:0]) + * OUTI #22AF -> HSINT (HCNT[8:1], 0–223) + * + * Использование: + * node scripts/gen_pos_table.js + * Вывод: asm/pos_table.s + */ + +'use strict'; + +const path = require('path'); +const fs = require('fs'); + +/* CLI: --entries N --step N */ +let entries = 56, step = 1280; +for (let i = 2; i < process.argv.length; i++) { + if (process.argv[i] === '--entries' && process.argv[i+1]) entries = parseInt(process.argv[++i], 10); + else if (process.argv[i] === '--step' && process.argv[i+1]) step = parseInt(process.argv[++i], 10); +} + +const ENTRIES = entries; +const STEP = step; +const LINE_LEN = 224; /* T-states per line at 3.5 MHz */ +const ISR_FREQ = Math.round(3_500_000 / STEP); + +const BASE_ADDR_STR = 'pos_table'; /* метка в ASM */ + +/* Вычислить символьный адрес записи по индексу. + * В sdas каждая запись занимает 5 байт: 3 байта DB + 2 байта DW. + * sdas не умеет $+N в секции _CODE напрямую, поэтому используем + * явные метки _pt_0, _pt_1, ... для ссылок next_ptr. */ + +const lines = [ + ';============================================================================', + '; pos_table.s — Таблица позиций INT (СГЕНЕРИРОВАНО, не редактировать)', + ';', + `; entries=${ENTRIES}, step=${STEP}, line_len=${LINE_LEN}`, + `; ISR freq: 3,500,000 / ${STEP} = ${ISR_FREQ} Hz (~44100/16)`, + ';============================================================================', + '', + ' .module pos_table_mod', + ' .globl pos_table', + '', + ' .area _CODE', + '', + 'pos_table:', +]; + +for (let i = 0; i < ENTRIES; i++) { + const pos = i * STEP; + const line = Math.floor(pos / LINE_LEN); + const hpos = pos % LINE_LEN; + + const vsinth = (line >> 8) & 0x01; /* только бит 0 (VCNT[8]) */ + const vsintl = line & 0xFF; + const hsint = hpos & 0xFF; + + const nextLabel = (i < ENTRIES - 1) + ? `_pt_${i + 1}` + : `pos_table`; + + lines.push(`_pt_${i}:`); + lines.push( + ` .db 0x${vsinth.toString(16).padStart(2,'0').toUpperCase()}` + + `, 0x${vsintl.toString(16).padStart(2,'0').toUpperCase()}` + + `, 0x${hsint .toString(16).padStart(2,'0').toUpperCase()}` + + ` ; [${String(i).padStart(2)}] line=${String(line).padStart(3)}, hpos=${String(hpos).padStart(3)}` + ); + lines.push(` .dw ${nextLabel}`); +} + +lines.push(''); +lines.push(' .globl pos_table_end'); +lines.push('pos_table_end:'); +lines.push(''); + +const outPath = path.join(__dirname, '..', 'asm', 'pos_table.s'); +fs.writeFileSync(outPath, lines.join('\n'), 'utf8'); + +console.log(`OK: ${ENTRIES} entries, ${ENTRIES * 5} bytes → ${outPath}`); diff --git a/src_sdcc/scripts/ihx2wmf.js b/src_sdcc/scripts/ihx2wmf.js new file mode 100644 index 0000000..58bd139 --- /dev/null +++ b/src_sdcc/scripts/ihx2wmf.js @@ -0,0 +1,257 @@ +/** + * ihx2wmf.js — Конвертер SDCC .ihx (Intel HEX) + патч заголовка WC → .wmf + * + * Использование: + * node scripts/ihx2wmf.js [--extra ] + * + * Алгоритм: + * 1. Парсить Intel HEX, получить бинарные данные (относительные адреса). + * 2. Определить реальный размер кода (до последнего не-нулевого байта). + * 3. Вычислить PAGES = ceil(code_size / 16384). + * 4. Вычислить BLKSIZE = ceil(code_size / 512). + * 5. Подготовить заголовок плагина (512 байт) с правильными PAGES/BLKSIZE. + * 6. Если задан --extra: добавить доп. страницы данных (LUT таблицы и т.п.). + * 7. Записать: [header 512 байт][код][extra data pages]. + * + * Формат WMF-заголовка (Wild Commander Plugin): + * +0 16 байт: зарезервировано + * +16 16 байт: "WildCommanderMDL" + * +32 1 байт: версия системы плагинов (#10) + * +33 1 байт: зарезервировано + * +34 1 байт: число 16KB страниц (PAGES) + * +35 1 байт: начальная страница (0) + * +36 12 байт: 6 × 2 байта блоков данных (blockN: [page, blkcount]) + * +48 15 байт: зарезервировано + * +63 1 байт: флаги (0 = активировать по Enter и F3) + * +64 96 байт: расширения (32 × 3 байта, "VGM" + нули) + * +160 1 байт: маркер конца расширений (0) + * +161 4 байта: макс. размер файла (0xFFFFFFFF = без ограничений) + * +165 32 байта: имя плагина (дополнить пробелами) + * +197 1 байт: тип активации (0 = по расширению) + * +198 6 байт: текст кнопок F2/F4 + * +204 24 байта: текст меню + * +228 32 байта: зарезервировано + * +260 1 байт: флаги INI + * +261 ... паддинг до 512 байт + */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +/* ── Константы заголовка ─────────────────────────────────────────────── */ +const HEADER_SIZE = 512; +const PLUGIN_ORG = 0x8000; /* виртуальный адрес загрузки WC */ +const PLUGIN_NAME = 'VGM Player OPL3 v2.0 SDCC '; /* 32 байта */ + +/* ── Парсер Intel HEX ────────────────────────────────────────────────── */ +function parseIHX(src) { + const buf = Buffer.alloc(0x10000, 0x00); + let minA = 0x10000; + let maxA = 0; + + for (const rawLine of src.split('\n')) { + const line = rawLine.trim(); + if (!line.startsWith(':')) continue; + + const byteCount = parseInt(line.slice(1, 3), 16); + const address = parseInt(line.slice(3, 7), 16); + const recordType = parseInt(line.slice(7, 9), 16); + + if (recordType === 0x00) { /* Data */ + for (let i = 0; i < byteCount; i++) { + const b = parseInt(line.slice(9 + i * 2, 11 + i * 2), 16); + const a = address + i; + buf[a] = b; + if (a < minA) minA = a; + if (a > maxA) maxA = a; + } + } + /* recordType 01 = EOF, игнорируем остальное */ + } + + return { buf, minAddr: minA, maxAddr: maxA }; +} + +/* ── Построить заголовок WC ──────────────────────────────────────────── */ +/** + * @param {number} totalPages — общее число 16KB страниц + * @param {Array<{page: number, blocks: number}>} blockEntries — блоки + */ +function buildHeader(totalPages, blockEntries) { + const hdr = Buffer.alloc(HEADER_SIZE, 0x00); + + /* +16: сигнатура */ + Buffer.from('WildCommanderMDL').copy(hdr, 16); + + /* +32: версия системы плагинов */ + hdr[32] = 0x10; + + /* +34: число страниц */ + hdr[34] = totalPages; + /* +35: начальная страница */ + hdr[35] = 0; + + /* +36: block table — до 6 записей × 2 байта */ + for (let i = 0; i < Math.min(blockEntries.length, 6); i++) { + hdr[36 + i * 2] = blockEntries[i].page; + hdr[36 + i * 2 + 1] = blockEntries[i].blocks; + } + + /* +63: флаги */ + hdr[63] = 0; + + /* +64: расширения: "VGM", "VGZ" */ + hdr[64] = 0x56; hdr[65] = 0x47; hdr[66] = 0x4D; /* V G M */ + hdr[67] = 0x56; hdr[68] = 0x47; hdr[69] = 0x5A; /* V G Z */ + /* остальные 30 × 3 = 90 нулей уже есть */ + + /* +160: маркер конца */ + hdr[160] = 0; + + /* +161: макс. размер файла = 0xFFFFFFFF */ + hdr[161] = 0xFF; hdr[162] = 0xFF; hdr[163] = 0xFF; hdr[164] = 0xFF; + + /* +165: имя плагина (32 байта, пробелами) */ + const name = PLUGIN_NAME.slice(0, 32).padEnd(32, ' '); + Buffer.from(name, 'ascii').copy(hdr, 165); + + /* +197: тип активации */ + hdr[197] = 0; + + return hdr; +} + +/* ── Основная логика ─────────────────────────────────────────────────── */ +function main() { + const args = process.argv.slice(2); + let inFile = null, outFile = null, extraFile = null; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--extra' && args[i + 1]) { + extraFile = args[++i]; + } else if (!inFile) { + inFile = args[i]; + } else if (!outFile) { + outFile = args[i]; + } + } + + if (!inFile || !outFile) { + console.error('Usage: node ihx2wmf.js [--extra ]'); + process.exit(1); + } + + const src = fs.readFileSync(inFile, 'utf8'); + const { buf, minAddr, maxAddr } = parseIHX(src); + + if (minAddr > maxAddr) { + console.error('ERROR: empty IHX'); + process.exit(1); + } + + /* Код начинается с PLUGIN_ORG = 0x8000 */ + if (minAddr < PLUGIN_ORG) { + console.error(`ERROR: code starts at 0x${minAddr.toString(16)}, expected >= 0x${PLUGIN_ORG.toString(16)}`); + process.exit(1); + } + + const codeSize = maxAddr - PLUGIN_ORG + 1; + const codePages = Math.max(1, Math.ceil(codeSize / 16384)); + const codeBlocks = Math.max(1, Math.ceil(codeSize / 512)); + + console.log(`Code: 0x${PLUGIN_ORG.toString(16)} - 0x${maxAddr.toString(16)} (${codeSize} bytes)`); + console.log(`Code pages: ${codePages}, Code blocks: ${codeBlocks}`); + + /* Блоки: каждая 16KB-страница = один block entry (max 32 секторов). + * + * WC читает WMF последовательно и загружает каждый block в свою страницу. + * Максимум 32 сектора на запись (= 16384 байт = 1 страница). + * Если код занимает >1 страницы — разбиваем на отдельные block entry. + * + * При наличии extra: код паддится до границы 16KB перед extra, + * поэтому последняя страница кода всегда = 32 сектора (кроме случая + * когда это единственная страница и extra нет). + */ + let extraBuf = null; + + if (extraFile) { + extraBuf = fs.readFileSync(extraFile); + } + + /* ── Block entries для кода: по одной на страницу ────────────── */ + const blockEntries = []; + for (let p = 0; p < codePages; p++) { + if (p < codePages - 1) { + /* Промежуточные страницы всегда полные */ + blockEntries.push({ page: p, blocks: 32 }); + } else { + /* Последняя (или единственная) страница кода */ + if (extraBuf) { + /* С extra: код паддится до 16KB → полная страница */ + blockEntries.push({ page: p, blocks: 32 }); + } else { + /* Без extra: реальный размер */ + const pageStart = p * 16384; + const remaining = codeSize - pageStart; + blockEntries.push({ page: p, blocks: Math.ceil(remaining / 512) }); + } + } + } + + /* ── Block entries для extra data: по одной на страницу ──────── */ + if (extraBuf) { + const extraPages = Math.ceil(extraBuf.length / 16384); + console.log(`Extra: ${extraFile} (${extraBuf.length} bytes, ${extraPages} pages)`); + + for (let p = 0; p < extraPages; p++) { + const pageStart = p * 16384; + const remaining = extraBuf.length - pageStart; + const pageSize = Math.min(remaining, 16384); + const blk = Math.ceil(pageSize / 512); + blockEntries.push({ page: codePages + p, blocks: blk }); + } + } + + const totalPages = blockEntries.reduce((sum, e) => Math.max(sum, e.page + 1), 0); + + if (blockEntries.length > 6) { + console.error(`ERROR: too many block entries (${blockEntries.length}, max 6)`); + process.exit(1); + } + + const hdr = buildHeader(totalPages, blockEntries); + const codeSlice = buf.slice(PLUGIN_ORG, PLUGIN_ORG + codeSize); + + /* Итоговый файл: [512 header][код][padding до 16KB boundary][extra pages] */ + const parts = [hdr, codeSlice]; + + if (extraBuf) { + /* Паддинг кода до границы 16KB-страницы + * (WC загружает каждый блок в свою страницу) */ + const codePadded = codePages * 16384; + const pad = codePadded - codeSize; + if (pad > 0) { + parts.push(Buffer.alloc(pad, 0x00)); + } + parts.push(extraBuf); + /* Паддинг extra до конца последней страницы */ + const extraPadded = Math.ceil(extraBuf.length / 16384) * 16384; + const extraPad = extraPadded - extraBuf.length; + if (extraPad > 0) { + parts.push(Buffer.alloc(extraPad, 0x00)); + } + } + + const out = Buffer.concat(parts); + fs.writeFileSync(outFile, out); + + console.log(`Pages: ${totalPages}, Block entries: ${blockEntries.length}`); + for (let i = 0; i < blockEntries.length; i++) { + console.log(` block[${i}]: page=${blockEntries[i].page}, sectors=${blockEntries[i].blocks}`); + } + console.log(`Written: ${outFile} (${out.length} bytes)`); +} + +main(); diff --git a/src_sdcc/vgmplayer.lk b/src_sdcc/vgmplayer.lk new file mode 100644 index 0000000..1a77f4b --- /dev/null +++ b/src_sdcc/vgmplayer.lk @@ -0,0 +1,25 @@ +; vgmplayer.lk — линкер-скрипт SDCC +; +; Плагин занимает окно #8000–#BFFF (16КБ = 0x4000 байт) +; +; Размещение: +; _CODE : 0x8000 — код + постоянные данные +; _DATA : после _CODE — BSS-переменные +; _IM2TABLE: 0xBE00 — таблица IM2 (257 байт, ALIGN 256!) +; I = 0xBE; вектор 0xFF → читает [0xBEFF..0xBF00] +; +; Предупреждение: менять _IM2TABLE только сохраняя выравнивание по 256! + +-mjwx +-b _CODE = 0x8000 +-b _GSINIT = 0x8000 +-b _GSFINAL = 0x8000 +-b _DATA +-b _BSS +-b _INITIALIZED +-b _INITIALIZER +-b _IM2TABLE = 0xBE00 +-b _DABS +-b _CABS +-l z80 +-e diff --git a/tools/create wcimg_example.bat b/tools/create wcimg_example.bat new file mode 100644 index 0000000..8731139 --- /dev/null +++ b/tools/create wcimg_example.bat @@ -0,0 +1,5 @@ +@echo off + +..\..\tools\robimg\robimg.exe -p="wc.img" -s=102400 -C="..\..\soft\wc\exe" + +pause diff --git a/tools/robimg.exe b/tools/robimg.exe new file mode 100644 index 0000000..40222b7 Binary files /dev/null and b/tools/robimg.exe differ diff --git a/tools/sjasm/INSTALL.md b/tools/sjasm/INSTALL.md new file mode 100644 index 0000000..100ca85 --- /dev/null +++ b/tools/sjasm/INSTALL.md @@ -0,0 +1,114 @@ + +## Installation Instructions + +Requirements: + +- GNU/Linux / Unix / macOS / BSD with bash compatible shell +- all common system progs (grep, cat, etc...) +- all common build requirements (libc, libstdc++, g++, GNU make, etc...) +- optionally CMake + +or + +- complete MinGW environment for MS Windows, configured with MinGW bin directories added in `PATH` variable (typically `C:\MinGW\bin;` `C:\MinGW\msys\1.0\bin`) or possibly with any BASH emulator (e.g. the one which comes with [git for windows](https://gitforwindows.org/)) will do it instead of msys is missing / not set. +- CMake installed into MinGW bin directory (optionally) + +Compilation is tested with GCC 5.5.0, it should run also with older 5.x versions. It will not work with GCC 4.x and older. + +## Getting full source code of sjasmplus + +Use `git` to clone the github repository and all git-submodules: + + git clone --recursive -j8 https://github.com/z00m128/sjasmplus.git + +If you already have cloned repository without submodules, you can update submodules only by: + + git submodule update --init --recursive + +If you are not using `git`, you can download and extract archive from releases, the manually added `.tar.xz` one (the automatically github provided zip files are missing the submodules sources and will fail to build). Or you can download the github provided archives, but then you need to download each submodule individually and extract them to correct folders inside sjasmplus folder. + +## Default method for GNU/Linux / Unix / macOS / BSD + +Go to folder with sjasmplus project. Edit install path `PREFIX` in file `Makefile` according your preferences (default `/usr/local`). Run following commands: + + make clean + make + +Then run as root or use sudo: + + make install + +### Gentoo GNU/Linux + +Check [cizo2000's github gentoo-overlay/dev-util/sjasmplus](https://github.com/cizo2000/gentoo-overlay/tree/master/dev-util/sjasmplus) for ebuild files. + +### Arch Linux + +Recently (2022) it seems leo72 started providing AUR for z00m's fork of sjasmplus: [aur.archlinux.org/packages/sjasmplus-z00m128](https://aur.archlinux.org/packages/sjasmplus-z00m128) (AFAIK there's no connection with any maintainer of this project, but hopefully this will continue and provide good experience to AUR users, thanks leo72). + +## Default method for MS Windows + +Go to folder with sjasmplus project. Edit install path `PREFIX` in file `Makefile.win` according your preferences (default `c:\mingw\usr\local\bin`). Remove `-static` parameter in `CFLAGS` if you don't need standalone Windows executable (binary is MinGW dependant then, but it's smaller). Run following commands: + + make -f Makefile.win clean + make -f Makefile.win + make -f Makefile.win install + +## CMAKE method for Linux / Unix / macOS / BSD + +Go to folder with sjasmplus project and run following set of commands: + + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + make + make install + +The project will use internal copy of Lua 5.4.4 by default, but you can use system Lua 5.4 by using `-DSYSTEM_LUA=ON`. + +For disabling of LUA scripting support add `-DENABLE_LUA=OFF` option: + + cmake -DENABLE_LUA=OFF .. + +Binary sjasmplus file will be placed in `/usr/bin` by default. + +To change install directory prefix add `-DCMAKE_INSTALL_PREFIX:PATH` option with specified prefix: + + cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr/local .. + +## CMake method for MS Windows + +Go to folder with sjasmplus project. Delete or rename file `Makefile`. Rename file `Makefile.win` to `Makefile`. Create `build` subdirectory and enter to it. Run following command: + + cmake-gui + +Click `Browse Source...` button, select extracted tarball folder. Click `Browse Build...` button, select the `build` folder. Click `Configure` button, select `MinGW Makefiles`, select `Use default native compilers`. Click `Finish` and wait until configuration is done. Change `CMAKE_INSTALL_PREFIX` install path according your preferences (click on path). Click `Generate`. Run `cmd.exe`, enter the build directory and run following commands: + + make + make install + +The CMake can generate also VS project files, and sources should compile with VS compiler, since +v1.14.1 the VS built executable should mostly work as well as MinGW, although there are still +corner-case bugs which need to be fixed, see issues #77 and #78 for details/progress and to help. + +The official binary "exe" is built with MinGW toolchain and that's recommended option. + +## Helping with sjasmplus development + +There are few extra recommendations if you want to join sjasmplus development and use all the features: + +- work with git clone of repository ideally, so you can easily update to latest source base, track your changes or review changes of other developers +- within the git repository init and update the submodules too, this will clone the UnitTest++ and LuaBridge2.8 repository which are required to build sjasmplus and unit tests (`git submodule init`, `git submodule update`) +- linux + gcc + Makefile is the config of Ped7g, having access to the same config to re-create his workflow locally may be of help (when troubleshooting some issue or comparing results with different platform) +- but having different local configuration would be very helpful to keep the source base cross-platform and in good shape +- you can check `.cirrus.yml` file and accompanying scripts/batch-files in `ContinuousIntegration` folder to see how different environments and different build tasks are prepared and executed. If you are not familiar with CI setup and configuration yet, you should take at least a brief glimpse on it, even if you want just to contribute small patch to the sjasmplus, because any pull request will be scrutinized by the CI build system automatically. + +My (Ped7g) local workflow is: + +1. KDevelop IDE set up to use CMake import of project to build+install debug binary in my `~/.local/bin` folder, which is the first binary found by my search path (to build + install the sjasmplus I use Project/Install Selection [Shift+F8]). +1. with new debug binary locally installed, I run in terminal `ContinuousIntegration/test_folder_tests.sh` to verify if it works (adding new tests exercising the feature/change which I am working at). Sometimes I limit the scope of tests being run by adding argument to the script like "module" to run only `tests/modules/*` tests. +1. when I'm finished with a change, and I'm preparing commit, I use `make clean && make` to verify the release can be built too, and `make DEBUG=1 clean && make DEBUG=1 coverage` to run full test suite, C++ unit tests and create coverage report (text files in build directory) so I can manually check if everything did change as expected. +1. after this review I commit + push the changes to the repository, where the Cirrus CI setup will try to replicate my local experience in different environments of the CI, if some of these fails, I check the Cirrus logs to see why build failed, and work on the fix commit. +1. when working on small/medium change where I'm sure I can get main repository into "green" state whenever I push changes to github, I tend to work directly on `master` branch, but when experimenting with things with unclear outcome, I use experimental branches, later merging them into `master` when they are ready (or even my fork of the main repository, if the experiment is really just experiment, and I am not even sure if it will ever land in the main repository). + +Enjoy! diff --git a/tools/sjasm/README.md b/tools/sjasm/README.md new file mode 100644 index 0000000..9c199ff --- /dev/null +++ b/tools/sjasm/README.md @@ -0,0 +1,72 @@ +# sjasmplus +[![Build Status](https://api.cirrus-ci.com/github/z00m128/sjasmplus.svg)](https://cirrus-ci.com/github/z00m128/sjasmplus/master) +[![GitHub repo size in bytes](https://img.shields.io/github/repo-size/z00m128/sjasmplus.svg)](https://github.com/z00m128/sjasmplus/) +[![BSD 3-Clause License](https://img.shields.io/github/license/z00m128/sjasmplus.svg)](https://github.com/z00m128/sjasmplus/blob/master/LICENSE.md) +[![Coverage Status](https://coveralls.io/repos/github/z00m128/sjasmplus/badge.svg?branch=master)](https://coveralls.io/github/z00m128/sjasmplus?branch=master) +[![CodeQL](https://github.com/z00m128/sjasmplus/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/z00m128/sjasmplus/actions/workflows/codeql-analysis.yml) +[![GNU/Linux](docs/img/linux-logo-24px.png)](https://www.linux.org/) +[![FreeBSD](docs/img/freeBSD-logo-24px.png)](https://www.freebsd.org/) +[![NetBSD](docs/img/NetBSD-logo-24px.png)](https://www.netbsd.org/) +[![Raspberry Pi](docs/img/raspberry-pi-logo-24px.png)](https://www.raspberrypi.org/) +[![macOS](docs/img/apple-logo-24px.png)](https://www.apple.com/lae/macos/) +[![Windows](docs/img/microsoft-windows-logo-24px.png)](https://www.microsoft.com/en-us/windows) + +Command-line cross-compiler of assembly language for [Z80 CPU](https://en.wikipedia.org/wiki/Zilog_Z80). + +Supports many [ZX-Spectrum](https://en.wikipedia.org/wiki/ZX_Spectrum) specific directives, has built-in Lua scripting engine and 3-pass design. + +For GNU/Linux, BSD, Raspberry Pi, macOS and [Windows (click for exe)](https://github.com/z00m128/sjasmplus/releases/latest). + +GNU make or CMake [installation methods](INSTALL.md) for your convenience. + +Online [documentation](http://z00m128.github.io/sjasmplus/documentation.html) (it is also included in the binary-release zip for offline viewing). + +Post issues, feedback, feature requests, etc on [github](https://github.com/z00m128/sjasmplus/issues). + +Main Features +============= + +* Full source of assembler available under BSD license, modify and extend as you wish +* Z80/R800/Z80N/i8080/LR35902 documented and undocumented opcodes support +* Macro language, defines, array of defines +* Built-in Lua scripting engine +* Conditional assembly, block repeating +* Modules (namespaces), local and temporary labels +* Source and binary file inclusion, include paths +* Multi file output, file updating, various types of exports +* Structures to work easily with structured data in memory (`STRUCT` pseudo-op) +* Relocation data generator to support SymbOS-like relocation of executables +* Virtual device mode for common machines: ZX 128, ZX Next, Amstrad CPC, … (pseudo op `DEVICE`) +* ZX Spectrum specific directives and pseudo ops (SAVESNA, SAVETAP, SAVEHOB, INCHOB, INCTRD…) +* ZX Spectrum Next specific features and directives (Z80N, 8ki memory paging, `SAVENEX`) +* Amstrad CPC 464/6128 specific directives (`SAVECPCSNA`) +* Correctness is assured by Cirrus-CI with 380+ automated tests (that's also 380+ examples of usage!) +* Fake instructions as `LD HL,DE` (`LD H,D:LD L,E`) and more +* Code inlining through colon (`LD A,C:INC A:PUSH AF:IFDEF FX:LD A,D:ENDIF`…) +* Very fast compilation: 1 million lines by 2-3 seconds on modern computer +* Multiline block comments and user’s messages + +This repository was created by import from original Aprisobal's repository @ https://sourceforge.net/projects/sjasmplus/. + +### Other useful tools for sjasmplus users + +Some of these may be useful for sjasmplus users (depending on the platform and tools you are using): + +[Support for Z80 macro-assemblers in Visual Studio Code](https://github.com/mborik/z80-macroasm-vscode) - syntax highlighting, "problem matcher" for assembler output, on hover info about labels, symbols, completion proposals, +renaming provider, macro documenter and arguments definitions. + +This can be further complemented by [Z80 Assembly meter](https://github.com/theNestruo/z80-asm-meter-vscode) plugin for Visual Studio Code. + +[#CSpect](http://cspect.org/) is ZX Spectrum Next emulator with built-in debugger and map-files support to show labels from source code. + +[ZEsarUX](https://github.com/chernandezba/zesarux) is multi-machine emulator focusing on ZX Spectrum family of machines, including also ZX Spectrum Next, having many many options, lot of menus, and lot debugging capabilities - if you are particularly interested into ZX Spectrum Next SW development, the #CSpect is often a bit ahead in overall emulation fidelity, but ZEsarUX is often more accurate in subtle edge-case details, so you may want to have both of them around for different occasions. Also you may want to have ZEsarUX around for: + +[DeZog - Z80 Debugger (for Visual Studio Code)](https://github.com/maziac/DeZog) for debugging ZX Spectrum code, writing unit tests for it, time the execution, etc. + +(if you are not excited about using VSC as IDE and want to try some alternative) Syntax highlight (only) +for [![Kate icon](docs/img/kate-logo-24px.png)Kate editor](https://kate-editor.org/) is +included directly in sjasmplus [sources](https://github.com/z00m128/sjasmplus/blob/master/asm-z80-sj.xml). + +-------------------- + +_All product names, trademarks and registered trademarks are property of their respective owners. All company, product and service names used in this website are for identification purposes only. Use of these names,trademarks and brands does not imply endorsement._ diff --git a/tools/sjasm/docs/docbook.css b/tools/sjasm/docs/docbook.css new file mode 100644 index 0000000..c710118 --- /dev/null +++ b/tools/sjasm/docs/docbook.css @@ -0,0 +1,119 @@ + +body { + background-color: #CDF; +} + +/********************************/ +/* start of styles in block.xsl */ + +.formalpara-title { + font-weight: bold; +} + +div.blockquote-title { + font-weight: bold; + margin-top: 1em; + margin-bottom: 1em; +} + +span.msgmain-title { + font-weight: bold; +} + +span.msgsub-title { + font-weight: bold; +} + +span.msgrel-title { + font-weight: bold; +} + +div.msglevel, div.msgorig, div.msgaud { + margin-top: 1em; + margin-bottom: 1em; +} + +span.msglevel-title, span.msgorig-title, span.msgaud-title { + font-weight: bold; +} + +div.msgexplan { + margin-top: 1em; + margin-bottom: 1em; +} + +span.msgexplan-title { + font-weight: bold; +} + +/* end of styles in block.xsl */ +/********************************/ + +/********************************/ +/* start of styles in autotoc.xsl */ + + +/* end of styles in autotoc.xsl */ +/********************************/ + +/********************************/ +/* start of styles in formal.xsl */ + +div.figure-title { + font-weight: bold; +} + +div.example-title { + font-weight: bold; +} + +div.equation-title { + font-weight: bold; +} + +div.table-title { + font-weight: bold; +} + +div.sidebar-title { + font-weight: bold; +} + + +/* end of styles in formal.xsl */ +/********************************/ + +/********************************/ +/* start of styles in verbatim.xsl */ + +pre.programlisting { + font-family: monospace; + font-size: 1.1em; + white-space: pre; + background: #EEE; + border: 1px solid gray; + padding: 1em; + margin: 1em; + + max-width: 80em; + overflow: auto; /* add scrollbar if the listing doesn't fit on screen*/ +} + +pre.synopsis { + white-space: pre; + font-family: monospace; + overflow: auto; /* add scrollbar if the listing doesn't fit on screen*/ +} + +div.screen { + white-space: pre; + font-family: monospace; +} + +/* end of styles in verbatim.xsl */ +/********************************/ + +/* footnote rule */ +hr.footnote-hr { + width: 100; +} diff --git a/tools/sjasm/docs/documentation.html b/tools/sjasm/docs/documentation.html new file mode 100644 index 0000000..7c99b35 --- /dev/null +++ b/tools/sjasm/docs/documentation.html @@ -0,0 +1,3953 @@ +SjASMPlus 1.20.3 Documentation [2023-06-23]

SjASMPlus 1.20.3 Documentation [2023-06-23]


+ + +

Chapter 1. Introduction

+ + +

License

+ + +

SjASMPlus is licensed under BSD license.

+
+ +

What is it?

+ + +

+ SjASMPlus is Z80 Assembly Language Cross Compiler. It is available + for Win32, Linux and FreeBSD (mainly 5.x) systems. It is based on SjASM + source code by Sjoerd Mastijn (http://xl2s.tk). +

+
+ +

Main Features

+ + +
  • +

    Full source of assembler available under BSD license, modify and extend as you wish

    +
  • +

    Z80/R800/Z80N/i8080/LR35902 documented and undocumented opcodes support

    +
  • +

    Macro language, defines, array of defines

    +
  • +

    Built-in Lua scripting engine

    +
  • +

    Conditional assembly, block repeating

    +
  • +

    Modules (namespaces), local and temporary labels

    +
  • +

    Source and binary file inclusion, include paths

    +
  • +

    Multi file output, file updating, various types of exports

    +
  • +

    Structures to work easily with structured data in memory

    +
  • +

    Relocation data generator to support SymbOS-like relocation of executables

    +
  • +

    Virtual device mode for common machines: ZX 128, ZX Next, Amstrad CPC, … (pseudo op DEVICE)

    +
  • +

    ZX Spectrum specific directives and pseudo ops (SAVESNA, SAVETAP, SAVEHOB, INCHOB, INCTRD…)

    +
  • +

    ZX Spectrum Next specific features and directives (Z80N, 8ki memory paging, SAVENEX)

    +
  • +

    Amstrad CPC 464/6128 specific directives (SAVECPCSNA)

    +
  • +

    Correctness is assured by + Cirrus-CI with 400+ automated tests (that's also 400+ examples of usage!)

    +
  • +

    Fake instructions as LD HL,DE (LD H,D:LD L,E) and more

    +
  • +

    Code inlining through colon (LD A,C:INC A:PUSH AF:IFDEF FX:LD A,D:ENDIF…)

    +
  • +

    Very fast compilation: 1 million lines by 2-3 seconds on modern computer

    +
  • +

    Multiline block comments and user’s messages

    +
+
+ +

Credits

+ + +

Special thanks to Sjoerd Mastijn, the author of SjASM.

+ +

Aprisobal - main programming, documentation, etc.

+ +

Thanks to:

  • +

    Kurles/HS/CPU, Alexander Kovalenko, Ped7g, Dart Alver, Oli Wilkinson - additional + programming;

    +
  • +

    Krystian Wlosek + <kwlosek(at)gmail.com> - bug fix patches, Linux + makefile;

    +
  • +

    Ric Horne <Ric.Hohne@eads-ts.com> + - bug fix patches.

    +
  • +

    breeze <breeze@tut.by> - bug fix + patches.

    +
  • +

    psndcj <psndcj.tbk@gmail.com> - + bug reporting, beta-testing.

    +
  • +

    elfh <elphecy@gmail.com> - bug + reporting.

    +
  • +

    bugsy <bugsy@ya.ru> - bug + reporting.

    +
  • +

    skrju <sq-@mail.ru> - bug + reporting.

    +
  • +

    Tygrys, UB880D, Cizo, mborik, z00m, otis - + compilation errors and warnings clean up, makefiles, testing.

    +
  • +

    Antipod, boo_boo, PulkoMandy, Busy, Liniya, LVD - + bug fix patches, testing.

    +
  • +

    CKirby - SLD export and support in his tools.

    +
  • +

    Simon Brattel - for creating mother of all assemblers Zeus + and bringing all the good and peace to the mankind, giving infinite inspiration + to sjasmplus contributors who stole his tremendously amazing features without + even crediting him - till now, fixed (not sure what the particular list of features + was copied from Zeus, I guess DG/DH, I believe some other similarities are rather + copied from the ALASM which had many features before Zeus, but who knows, too late + to track it all precisely, what happened in 90's and when).

    +
+ +

Big thanks to all people, who helped on development of the compiler!

+
+ +

Feedback

+ + +

WWW: https://github.com/z00m128/sjasmplus + (newer versions maintained by z00m and others)

+ +

WWW: https://sourceforge.net/projects/sjasmplus/ + (original Aprisobal's source)

+ +

E-Mail: zoom@centrum.sk, my@aprisobal.by

+
+ +

What's new?

+ + +
23.6.2023 - 1.20.3
+
- added alias `--define` for `-D`
+- added string-literals suffixes Z and C to add zero or set high bit of last char
+- end of line backslash continues source line (limited support, not recommended)
+- Lua: minor version upgrade to 5.4.6 (from 5.4.4)
+- minor updates to Makefile
+
+
14.2.2023 - 1.20.2
+
- added optional second argument for `DUP` to have index variable
+- option `--exp` will create file even when no `EXPORT` is used
+- fixing variable name-clash when compiling against musl-clib
+- LuaBridge updated, CMake and Makefile updated a bit
+- minor bugfixes/improvements in parser in specific edge cases
+
+
29.7.2022 - 1.20.1
+
- parse decimal numeric constants with warning (for easier Lua 5.4 life)
+- added `SAVEAMSDOS` (like SAVEBIN with AMSDOS header)
+- added "smart" SMC offset syntax for self-modify-code labels: `abc+*: or 123`
+- added `DEFDEVICE` to define custom devices
+- Makefile cleanup
+
+
13.6.2022 - 1.20.0
+
- Lua: upgrade to 5.4, replacing tolua++ bindings with LuaBridge2.6 library, extending some bindings
+- Lua: bindings slightly modified (required by upgrade), refreshed docs, added test coverage
+- Lua: the 3rd party extensions (BTW not working for many years) are obsolete in 5.4 and removed
+- Lua: more accurate errors/warning location reported even in complex cases
+- warnings: added -Wall, --help=warnings shows on/off status, rdlow off by default
+- Added HIGH mode to relocation data generator (MSB-only relocation mode)
+- many open-file "fatal" errors become "non-fatal", assembling will continue
+- deprecated features removed: --syntax=m, label `abs` in expressions
+- `--color=auto` will stay no-color when env.var. `NO_COLOR` is defined
+- refactorings, improving some error messages and parsing, small fixes in parsing logic
+- fix listing of Lua's sj.parse_code (eol-comments), minor memory leaks fixed
+- fix relocation of temporary labels in expressions
+- invalid CLI options are reported as regular errors (also changing exit code)
+- errors are colored similarly to gcc (only keyword has color), console input name is `<stdin>`
+
+
31.3.2022 - 1.19.0
+
- added Amstrad CPC devices ("AMSTRADCPC464", "AMSTRADCPC6128") - by Oli Wilkinson
+- added Amstrad CPC save snapshot and CDT (`SAVECPCSNA`, `SAVECDT`) - by Oli Wilkinson
+- added `SAVE3DOS` (like SAVEBIN with +3DOS header)
+- the deprecated "ok" warning suppression is removed, use "<warning-id>-ok" comment or -Wno-...
+- new temporary label suffix syntax "_b" and "_f", enabling them for all expressions
+- fix `--longptr` mode to keep 32b address when `DS 0` is used
+- added fake instructions adc|add|sbc|sub de,bc|de|hl|sp
+- dec|inc|pop|push will accept also single-comma multiarg in --syntax=a mode
+- DUP/REPT will now accept also zero count (skipping the block)
+- DEFL labels can be defined even as late as in last pass
+- bugfixes (macros, listing, file names in errors, SLD reversepop data)
+
+
29.8.2021 - 1.18.3
+
- added `--color` to enable/disable ANSI coloring of errors/warnings
+- added `--syntax=s` mode to disable sub-word substitutions of DEFINEs
+- added at-sign prefix for macro local labels to act as non-macro local label
+- `SAVETRD` accepts names containing dot ("z.x.B" is "z.x" with extension "B") - by Dart Alver
+- `SAVETRD` has optional argument to save BASIC with variables (length_minus_variables)
+- minor bugfixes (conditional block parser)
+
+
14.2.2021 - 1.18.2
+
- [may break old sources] new exist operator to check label existence
+- the --syntax=i mode makes now also register parsing case insensitive
+- minor bugfixes (predefined values, savenex BMP loader less strict about "colors used" content)
+
+
23.1.2021 - 1.18.1
+
- Big-Endian hosts support (experimental and not tested continuously)
+- added "listall", "listact" commands to OPT - to switch between listing types
+- `WHILE` has optional argument to set explicit guardian-counter
+- `ASSERT` has optional argument (to add description/notes for expression)
+- `SLOT` and `MMU` will now accept also starting address of slot instead of its number
+- fix: option --sym was not exporting labels starting with underscore
+- fix: SAVENEX BMP-loader bug when certain builds of sjasmplus were unable to open BMP files
+- fix: after STRUCT instance the "main" label is not polluted by last field of STRUCT
+- minor bugfixes in parser, windows cmake-builds have now icon
+- docs: adding "Index" section
+- docs: adding some missing information (__DATE__, __TIME__), fixing HTML anchor names
+
+
12.12.2020 - 1.18.0
+
- [may break old sources] the colon between end of EQU/DEFL/= expression and instruction is mandatory
+- [may break old sources] new abs operator for absolute integer value
+- new system of warnings (and suppression), the "; ok" comments are now deprecated
+- `DISPLAY` has now also binary and char formatting
+- `DEFINE+` added to [re]define identifier without error
+- `ELSEIF` added to conditional assembling arsenal
+- `WHILE` added for conditional loops
+- added "NOSLOT64K" device with 2MiB of virtual memory
+- `LABELSLIST` has new optional argument to dump 16bit "virtual labels"
+- `CSPECTMAP` exports STRUCT symbols with more detail (instance labels with physical address)
+- SMC offset syntax for self-modify-code labels for source brevity
+- added exclamation mark prefix for labels to not affect following local labels
+- added "listmc" command to OPT - to list only lines with machine code bytes
+- added `--lstlab=sort` variant to have symbols in listings in predictable order
+- minor bugfixes in parser and listing-line-numbering, refactored symbols/labels implementation
+- Added example (chargfx2asm) how to use sjasmplus as byte-processor for binary files
+- SLD data improvements based on Maziac's feedback and DeZog's needs
+
+
8.8.2020 - 1.17.0
+
- `STRUCT` has new `TEXT` pseudo-instruction to define "DB-like" data (Issue #93)
+- `STRUCT` initializer block can be now multi-line (when correctly enclosed in curly braces)
+- `EQU` now allows for optional override of page number assigned to the new symbol
+- new $$$ and $$$$ operators to retrieve "physical" address/page inside DISP block
+- instruction `out (c),0` now emits warning (can be suppressed by the "; ok" comment)
+- fixed listing of structures using long BLOCK fields (machine code was correct, but listing not)
+- fixed some memory leaks, undefined behaviour and unaligned memory access
+
+
27.7.2020 - 1.16.0
+
- `LUA` the new emit warning (v1.15.1) is now suppressible
+- Predefined defines extended and renamed (following gcc/clang ones)
+- Added relocation data generator (similar to WinAPE feature), check also example
+- bugfixes/improvements in parser like: operators `not`, `low`, `high` can be followed also by "("
+
+
+ +

See CHANGELOG.md for full list of changes.

+
+
+ +

Chapter 2. Where to get and how to use

+ + +

Packages

+ + +

+ The latest release of this sjasmplus variant is + available at + https://github.com/z00m128/sjasmplus/releases/latest + - there is available zip archive with windows binary + (toward bottom of the page), and zip archives with + full source code of the sjasmplus. +

+ +

Win32 package has:

  • +

    sjasmplus.exe - the Win32 executable.

    +
  • +

    examples directory - some examples of use

    +
  • +

    documentation directory - documentation in various + formats

    +

+

+ +

+ You may want to download also the full source package, as it contains more than 400 + automated tests used to verify correctness of executable, + and these can be often helpful to better understand how specific feature of sjasmplus works, + and how to use it effectively. +

+ +

+ Linux, Unix, MacOS and BSD + version can be built from the full source archive. You can compile it using GCC + and included Makefile. There is an option to use CMake + for compilation. See + INSTALL.md for details. +

+ +

Windows binaries are compiled with MinGW environment and included + Makefile.win file.

+ +

You can grab older (up to v1.07) binaries and sources at SourceForge project page: + https://sourceforge.net/projects/sjasmplus/

+ +
+ +

Command line

+ + +

Usage:

+ +
sjasmplus [options] sourcefile(s)
+ +

Option flags as follows:

+ +
  -h or --help[=warnings]  Help information
+  --version                Basic info (with --nologo only raw version string)
+  --zxnext[=cspect]        Enable ZX Next Z80 extensions (CSpect emulator has
+  extra "exit" DD00 and "break" DD01 fake instructions)
+  --i8080                  Limit valid instructions to i8080 only (+ no fakes)
+  --lr35902                Sharp LR35902/SM83 CPU instructions mode (+ no fakes)
+  --outprefix=<path>       Prefix for save/output/.. filenames in directives
+  Note: if prefix is folder, it must already exist and add trailing slash. Prefix
+        is applied only to filenames defined in source (not to CLI arguments).
+  -i<path> or -I<path> or --inc=<path> ( --inc without "=" to empty the list)
+                           Include path (later defined have higher priority)
+  --lst[=<filename>]       Save listing to <filename> (<source>.lst is default)
+  --lstlab[=sort]          Append [sorted] symbol table to listing
+  --sym=<filename>         Save symbol table to <filename>
+  --exp=<filename>         Save exports to <filename> (see EXPORT pseudo-op)
+  --raw=<filename>         Machine code saved also to <filename> (- is STDOUT)
+  --sld[=<filename>]       Save Source Level Debugging data to <filename>
+                           Default name is: "<first input filename>.sld.txt"
+  Note: use OUTPUT,LUA/ENDLUA and other pseudo-ops to control output
+ Logging:
+  --nologo                 Do not show startup message
+  --msg=[all|war|err|none|lst|lstlab]  Stderr messages verbosity ("all" is default)
+  Note: "lst" or "lstlab" will turn STDERR into listing file (this will clash with
+  `--lst`, use only one of the options) and some diagnostic messages of "all"
+  are omitted, as they are not part of listing files. "lstlab" does sort symbols.
+  --fullpath               Show full path to file in errors
+  Note: the "fullpath" starts from current working directory, not from file system
+		root (the MS_VS build was fixed to behave this way since v1.15.0)
+  --color=[on|off|auto]    Enable or disable ANSI coloring of warnings/errors
+  Note: auto detection checks for existence of environment variable TERM, and its
+		content is scanned for sub-string "color" (like "xterm-256color")
+ Other:
+  -D<NAME>[=<value>] or --define <NAME>[=<value>]
+                           Define <NAME> as <value> (--define since v1.20.3)
+  -W[no-]<warning_id>      Disables/enabled particular warning type
+  -                        Reads STDIN as source (even in between regular files)
+  --longptr                No device: program counter $ can go beyond 0x10000
+  --reversepop             Enable reverse POP order (as in base SjASM version)
+  --dirbol                 Enable directives processing from the beginning of line
+  --nofakes                Disable fake instructions (same as --syntax=F)
+  --dos866                 Encode from Windows codepage to DOS 866 (Cyrillic)
+  --syntax=<...>           Adjust parsing syntax, read details below.
+ +

+ The option --lstlab=sort will cause also other symbol dumps being sorted (along + listing): --sym, CSPECTMAP and LABELSLIST. + The --msg=lstlab sets always sorting "on" (no "unsorted" option). +

+

Value for --syntax option may consist of multiple letters, omitting letter + for particular feature will use the default setting: +

  a      Multi-argument delimiter ",," (default is ",") (but dec|inc|pop|push accept also ",")
+  b      Whole expression parentheses are legal for memory access only (default = immediate or memory)
+  B      memory access brackets [] required (default = relaxed syntax, [] allowed as extra)
+  f F    Fake instructions: warning / disabled (default = enabled)
+  i      Case insensitive instructions/directives (default = same case required)
+† l L    Keyword labels (registers, instructions, ...): warning / error (default = silent)
+  M      Alias "m" and "M" for "(hl)" to cover 8080-like syntax: ADD A,M
+  s      Use only "simple" whole-word substitutions, no sub-words (since v1.18.3)
+  w      Warnings option: report warnings as errors
+  m      *REMOVED* in v1.20.0, use -Wno-rdlow

+ † work in progress: options "l" and "L" are not implemented yet, following example is then + not working correctly either. +

+

+ I.e. --syntax=faBil will modify parser to process source line +

hl:  Ld a,(hl),,de,hl

+ in a way to produce warnings about keyword "hl" being used for label, about fake instruction + being used (ld de,hl) and assemble (hl) as numeric expression, not memory access. + Warnings on fake instructions can be suppressed for particular line by adding any end-of-line + comment containing string "fake", i.e. "ld de,hl ; fake DE=HL" will assemble + without warning. The "F" option is identical to "--nofakes" and preferred. +

+ +

+ The recommended setup for new projects is --syntax=abfw which makes syntax less relaxed, + so some typos and mistakes are easier to catch, for example: +

        OPT reset --syntax=abfw
+label:  dw 15
+        ld b,(label)
+        sub a,b

will produce "error: Illegal instruction + (can't access memory): (label)" message for the ld b,(label) and the sub a,b + will produce only the sub b instruction (to give the sub multi-argument + with syntax option "a" the line would have to be sub a,,b). +

+ +

+ The assembler will also read the environment variable SJASMPLUSOPTS (if available), + and process its content as part of command line options (before the actual options), so you + can pre-configure certain options in your environment, for example: +

export SJASMPLUSOPTS="--zxnext=cspect --msg=war"
+sjasmplus --lst --lstlab example.asm

+ will execute the assembling as if command line "sjasmplus --zxnext=cspect --msg=war --lst --lstlab example.asm" + was used. Known issue: parser of environment variable delimits each option on any white-space character, so + option containing space character will be incorrectly parsed (like "-Ifile-path with space" = fails and + there is no way to escape/quote the path in the SJASMPLUSOPTS variable to make it work). +

+
+ +

Warnings with id

+ + +

+ Some warnings have own "id" - these can be switched off or suppressed for particular line. + The line suppression mechanism requires you to add end-of-line comment with "id-ok" string + somewhere in the comment (exception: for fake instructions and --syntax=f mode the word + "fake" is enough without -ok suffix) (the "ok" comment suppression mechanism from previous + versions of sjasmplus is removed since v1.19.0). +

+

+ The id-warning is emitted together with its id in brackets: +

    ld a,(10-2)    ; read memory address 8 - emits warning "rdlow"
+    ld a,(10-2)    ; same instruction, but suppress warning: rdlow-ok
+
+file.asm(1): warning[rdlow]: Reading memory at low address: 8

+ To list all warning-ids, their on/off state and short description of each use: +

<command line>$ sjasmplus --help=warnings

+ Most of the extra warnings are by default enabled, but to enable/disable all of them you can use (also in source with OPT directive): +

<command line>$ sjasmplus -Wall
+<command line>$ sjasmplus -Wno-all

+ To disable particular warning globally you can use option -Wno-<id>, + either on command line, or from source by using OPT: +

<command line>$ sjasmplus -Wno-out0
+
+; in source code:
+    OPT -Wno-trdext -Wno-trdext3 ; disable "trdext" and "trdext3" warnings
+        SAVETRD "image.trd", "file.txt", 30000, 100
+    OPT -Wtrdext3                ; re-enable "trdext3" warning for following code
+; ...

+

+

+ Warnings without id can't be suppressed and generally should happen only when the source + code has actual bug (in our opinion) and can be modified to fix the warning, or when you + are hitting limits of sjasmplus architecture and/or implementation. +

+

+ If you believe you are hitting warning without id on correct code, which can be safely + ignored (the machine code produced is stable and as expected), open issue on github with + example source, so we can assign the warning new id (or explain why it is problematic :) ). +

+

+ Warnings with id usually signal bug in the code (in common scenarios), but may be misleading + in more specialized cases - the decision is left to the author, whether to modify + the code to avoid the warning or suppress it and use the resulting machine code "as is". +

+
+ +

Source file format

+ + +

+ Lines in the source file should have the following form: +

Label Operator Operand Comment

+ Lines without label must start with whitespace. + All fields are optional. + End of line backslash continues source line (since 1.20.3, limited support, not recommended). + Operators and operands can be inlined with colon: +

      Operator Operand:Operator Operand:Operator Operand... Comment

+ Comments should start with ';' or '//'. + Comment blocks start with '/*' and end with '*/' (work in "nested" way, + i.e. comment block started inside comment block must be also ended, before main block ends). + +

Example 2.1. 

+ + +
; comment
+// comment
+ ld /* comment */ a,80
+/*
+ comment /* nested comment block */
+*/
+ ld /*
+ but this won't be ld a,3!
+ */ a,3
+


+

+
+
+ +

Chapter 3. Labels

+ + +

Labels

+ + +

Labels are case-sensitive and may be of any reasonable length, + that is: up to about 70 characters. Label definitions must start on + the beginning of a line, but don't have to be followed by a colon ':'. + Generally labels should start with a letter or a underscore ('_'), the + following characters may be chosen from letters, numbers and the + following special symbols: '_', '.', '!', '?', '#' and '@'. Note that + the '.' has special meaning, as it is used between module names, labels + and local labels. The following are all legal and distinct + labels:

Kip
+KIP
+Kip@@
+MAIN.loop?

It is possible to use mnemonics, pseudo-ops and + register names as labels but it is not advised to do so (expression operator keyword used + as label will emit warning, and is also not advised). Also note that + the identifiers defined with the DEFINE pseudo-op use another name + space.

+ +

(since v1.18.0) Right after label (before the optional colon) you + can write extra "SMC offset" (Self Modify Code offset) in the form of plus + sign and single decimal digit. The defined label will become equal to current + address plus the SMC offset: +

    ; SMC-offset syntax is very strict, no spacing, single '+', single digit
+answer+1:   ld      a,13        ; "answer" points second opcode byte
+    ; this helps to keep the SMC-label together with the target instruction
+            ld      hl,answer
+            ld      (hl),42     ; fix immediate in `ld a,*` to correct answer
+    ; compare with classic syntax:
+classic equ $+1 : ld a,7        ; often even split to two lines

+

+ +

(since v1.20.1) Smart SMC labels: you can write "plus asterisk" to let sjasmplus adjust + self-modify label by most likely offset for following instruction. This works only + for instructions with "obvious" target immediate value: +

    ; smart-SMC syntax is very strict, no spacing, single '+', single '*'
+answer+*:   ld      a,13        ; "answer" points second opcode byte
+    ; this helps to keep the SMC-label together with the target instruction
+            ld      hl,answer
+            ld      (hl),42     ; fix immediate in `ld a,*` to correct answer
+    ; few more examples which byte the "smart SMC" will target in the opcode
+jptarget+*: jp      nz,12345    ; "jptarget" points at second opcode byte (word value 12345)
+ixvalue+*:  ld      ix,12345    ; "ixvalue" points at third opcode byte (word value 12345)
+nxtrval+*:  nextreg $44,123     ; "nxtrval" points at fourth opcode byte (byte value 123)
+    ; unsupported situations, will cause error
+invalid1+*: inc     (ix+123)    ; IXY displacements are not considered (not significant enough)
+invalid2+*: cpl : ld a,14       ; only first instruction after label is considered

+

+
+ +

Local labels

+ + +

When there is a module definition all + labels (except those starting with a '@') are local to that module. To + use a label from outside the module use modulename.labelname, in this + example: 'vdp.Cls'. Labels starting with a '.' are also local to the + previous non-local label.

+ +

(since v1.18.0) If you start the label with an ! (exclamation mark), it will not + affect following local labels (same syntax as sjasm).

+ +

(since v1.18.3) Local label inside macro prefixed with @ (at sign) acts as regular local label, + otherwise (older behaviour) the local label is local to each macro's instance.

+ +

Example 3.1. docs_examples/s_local_labels.asm

+ + +
    MODULE main             ; module "main"
+Main:                       ; main.Main
+        CALL SetScreen      ; SetScreen
+        CALL vdp.Cls        ; main.vdp.Cls
+.loop:                      ; main.Main.loop
+        LD A,(.event)       ; main.Main.event
+        CALL ProcessEvent   ; label not found: main.ProcessEvent
+        DJNZ .loop          ; main.Main.loop
+
+        MODULE vdp          ; module "main.vdp"
+@SetScreen:                 ; SetScreen
+.loop:                      ; main.vdp.SetScreen.loop
+            RET
+Cls:                        ; main.vdp.Cls
+!KeepClsForLocal:           ; main.vdp.KeepClsForLocal (since v1.18.0)
+.loop:      DJNZ .loop      ; main.vdp.Cls.loop
+            RET
+        ENDMODULE
+
+Main.event DB 0             ; main.Main.event
+    ENDMODULE
+


+ +

Example 3.2. local labels inside macro

+ + +
  macro test
+.kip0       ; this is local to every instance of macro
+@.kip1      ; syntax added in v1.18.3
+  endm
+
+  module main
+hoi test    ; produce labels: main.hoi, 0>kip0, main.hoi.kip1
+  endmodule
+


+
+ +

@ Labels

+ + +

Labels starting with a '@' are not touched by the label processing + and used 'as-is'. See 'SetScreen' in the previous example code.

Example 3.3. docs_examples/s_at_labels.asm

+ + +
    MODULE xxx
+Label      ; xxx.Label
+.Local     ; xxx.Label.Local
+@Label     ; Label
+.Local     ; xxx.Label.Local => duplicate label error
+@Label2    ; Label2
+.Local     ; xxx.Label2.Local
+@yyy.Local ; yyy.Local
+yyy.Local  ; xxx.yyy.Local
+


+
+ +

Temporary labels

+ + +

To keep the number of used labels reasonable it is possible to use + numbers as labels. These labels can only be used as labels to jump to. + To jump to these labels, use the number followed by an 'F' for forward + branches or a 'B' for backward branches. Temporary labels should be defined + in the same order during every pass of assembling, but they can be used + within macro, or repeating blocks (old sjasmplus versions didn't allow usage within macro).

+ +

Since v1.19.0 there is alternative syntax using underscore before 'B' and 'F', this + alternative underscored suffix enables temporary labels also for regular instructions + (in all expressions, same way as other labels).

+ +

Example 3.4. docs_examples/s_temp_labels.asm

+ + +
        ADD A,E
+        JR NC,1F
+        INC D
+1       LD E,A
+2       LD B,4
+        LD A,(DE)
+        OUT (152),A
+        DJNZ 2B
+
+        MACRO zing
+            DUP 2
+                JR 1F
+1               DJNZ    1B
+            EDUP
+        ENDM
+
+        .4 zing
+
+        ; since v1.19.0
+        ld hl,1B        ; *old* HL = binary 1, NOT temporary label
+        ld hl,1_B       ; *new* HL = previous temporary label 1
+        ld hl,1_B*3     ; *new* works also in expressions
+


+
+
+ +

Chapter 4. Constants, expressions and other features

+ + +

Numeric constants

+ + +

Numeric constants should always start with a digit or $, # or %. + The following formats are supported:

+ +
12     decimal
+12d    decimal
+0xc    hexadecimal
+$c     hexadecimal
+#c     hexadecimal
+0ch    hexadecimal
+0b1100 binary (v1.12.1)
+%1100  binary
+1100b  binary
+0q14   octal (v1.12.1)
+14q    octal
+14o    octal
+ +

(v1.12.1) Optional single quotes(') may be inserted between the digits as a separator + (example: ld a,%11'01'11'00 ). + They are ignored by the assembler.

+ +

(v1.20.1) decimal parts are now parsed and ignored (with optional warning), instead of + causing syntax error - this should make life easier when formatting values from Lua script. + It is also possible to fix all Lua scripts to produce only integer values, but if you prefer + to silently ignore the extra decimal part, you can.

+
+ +

Character and string constants

+ + +

Character constants are characters surrounded by single quotes. It + is possible to use double quotes in some cases, but in general it is + better to use single quotes. String constants are characters surrounded + by double quotes. When double quotes are used, the following escape + sequences are recognized:

+
\\ 92
+\? 63
+\' 39
+\" 34
+\0 0     ; since v1.11
+\A 7
+\B 8
+\D 127
+\E 27
+\F 12
+\N 10
+\R 13
+\T 9
+\V 11

Inside single quotes two quotes after each other are + parsed as the apostrophe itself (since v1.11).

+

Adding Z or C after quotes/apostrophes will make string zero-terminated or set top bit of last character (since v1.20.3).

Example 4.1. 

+ + +
    BYTE "stringconstant\n" ; escape sequence assembles to newline
+    BYTE 'stringconstant\n' ; \n assembles literally as two bytes: '\', 'n'
+    LD HL,'hl'  ; hl = 0x686C = 'l', 'h'
+    LD HL,"hl"  ; hl = 0x686C = 'l', 'h'
+    LD A,"7"    ; not recommended (but works)
+    LD A,'8'    ; recommended
+    LD A,'\E'   ; warning + truncating value to 'E' (0x45)
+    LD A,'"'    ; A = 0x22
+    LD A,"'"    ; A = 0x27
+    LD A,''''   ; A = 0x27 ; since v1.11
+    BYTE "AB"Z, "CD"C, 'E'Z, 'F'C ; hex: 41 42 00 43 C4 45 00 C6
+


+
+ +

Expressions

+ + +

Expressions are evaluated in signed 32 bits in this version of SjASMPlus (unless explicitly + specified as unsigned, like ">>>" operator), so intermediate values have range -2147483648 + to +2147483647. True for boolean results is equal to value -1, false is equal to 0 (so + result of compare or logical operators can be used as all-bit-mask further).

+ +

'$' represents the current program counter. '$$' represents the + current page in the current slot in the real device emulation mode, '$$label' evaluates to number of page + where the "label" was defined (only regular labels have meaningful value, labels defined + under DISP mode may return the page specified in DISP itself, EQU/DEFL/... will produce mostly + irrelevant values). '$$$' and '$$$$' can be used inside DISP block to retrieve the "physical" + memory address and page (where the displaced machine code is written to). '{address}' can be used + to read WORD from virtual device memory (correct value is read only in last pass of assembling, + in early passes the zero value is always returned), '{b address}' reads only BYTE.

+ +

It is possible to use parenthesis '(' and ')' to override the + precedence of the operators. The following operators may be used in + expressions:

+ +
norel norel L  Label/symbol L will be treated as non-relocatable
+exist exist L  is label/symbol L defined in source (even when not used) (since v1.18.2)
+
+!     !x       logical not
+~     ~x       complement
++     +x       does "nothing", can be used to make "+(...)" parse as value (not as memory)
+-     -x       minus
+low   low x    low 8 bits of 16 bit value or lower part of register pair
+high  high x   high 8 bits of 16 bit value or higher part of register pair
+not   not x    logical not
+abs   abs x    absolute value (since v1.18.0)
+
+*     x*y      multiplication
+/     x/y      division
+%     x%y      modulo
+mod   x mod y  modulo
+
++     x+y      addition
+-     x-y      subtraction
+
+<<    x<<y     shift left
+>>    x>>y     shift right signed
+>>>   x>>>y    shift right unsigned
+shl   x shl y  shift left
+shr   x shr y  shift right signed
+
+<?    x<?y     minimum
+>?    x>?y     maximum
+
+<     x<y      less than
+>     x>y      greater than
+<=    x<=y     equal or less than
+>=    x>=y     equal or greater than
+
+=     x=y      equal
+==    x==y     equal
+!=    x!=y     not equal
+
+&     x&y      bitwise and
+and   x and y  bitwise and
+
+^     x^y      bitwise xor
+xor   x xor y  bitwise xor
+
+|     x|y      bitwise or
+or    x or y   bitwise or
+
+&&    x&&y     logical and
+
+||    x||y     logical or
+
+$     $        current program counter
+$$    $$       current page at program counter (in virtual device mode)
+$$$   $$$      "physical" program counter (inside DISP block)
+$$$$  $$$$     "physical" memory page (inside DISP block in virtual device mode)
+label label    value of label (aka symbol), usually memory address
+$$lab $$lab    page of "lab" label (in virtual device mode)
+{}    {x}      reads WORD from address x (in virtual device mode, in last pass)
+{b}   {b x}    reads BYTE from address x (in virtual device mode, in last pass)
+
+
+ +

Assembly language

+ + +

This version only accepts Z80 mnemonics. There are some additions + to what I think is standard Z80:

  • +

    '[' and ']' can be used instead of '(' and ')' for + indirection. So LD A,[HL] is the same as LD A,(HL) (does not + apply to IN/OUT ports, those must use '(...)' form)

    +
  • +

    IN F,(C) and OUT (C),0 and SLL/SLI can be used + (warning: on some CPU versions of Z80 the OUT (C),0 is working as OUT (C),255).

    +
  • +

    IXL (or LX, XL), IYL (or LY, YL), IXH (or HX, XH) and IYH + (or HY, YH) registers are supported.

    +
  • +

    Can write code throught colon: ORG 100h:LD A,10:LD B,10:SUB + B:RET:IFDEF AA:.....

    +
  • +

    JP HL, JP IX and JP IY may be used instead of JP (HL), + etc.

    +
  • +

    EX AF,AF or EX AF or EXA may be used instead of EX + AF,AF'.

    +
  • +

    R800's MULUB and MULUW are recognised (but won't work on + Z80, of course:)

    +
  • +

    Z80N, i8080 and LR35902 (SM83) modes use the identical Z80 sjasmplus syntax (!), for the + correct (and incorrect) syntax examples of extended opcodes, please check the test files: + Z80N test 1 + test 2 + and LR35902 (SM83) test 1 + test 2 + (also for the Z80 syntax examples you can check + Z80 tests). +

    +
  • +

    RLC, RRC, RL, RR, SLA, SRA, SLL (SLI), RES, SET undocumented + instructions added.

    +
    SET 4,(IX+4),C ; (aka LD C,SET 4,(IX+4)) is LD C,(IX+4) / SET 4,C / LD (IX+4),C
+    RRC (IY),A     ; (aka LD A,RRC (IY+0))   is LD A,(IY)   / RRC A   / LD (IY),A
  • +

    PUSH and POP can take register lists:

    +
    PUSH AF,BC  ; push af / push bc
+    POP  AF,BC  ; pop  af / pop  bc
  • +

    and many other instructions support this "multi-argument" syntax:

    +
    LD A,B,B,D,D,H
+   /* this is:
+     LD A,B
+     LD B,D
+     LD D,H
+   */
+   ;or you can write  LD A,B:LD B,D:LD D,H
+
+   ; since v1.13.1 it is possible and *recommended* to change the multi-arg delimiter
+   ; into ",,", to avoid some ambiguities with certain instructions.
+   OPT --syntax=a
+   LD A,B,,B,D,,D,H ; same as example above in default syntax
+   SUB A,B,,C  ; = SUB B, SUB C (the default syntax does for SUB A,B two SUBs!)
+
+   ; since v1.19.0 DEC|INC|POP|PUSH accepts also single-comma in --syntax=a mode
+   POP bc,de,hl,,af ; both single/double comma works for these four instructions
+
+
+ +

Fake instructions

+ + +

Of course the Z80 is only an 8 bit cpu, but sometimes ld hl,de + would be nice. SjASMPlus now 'fakes' some instructions like that. This + improves the readability of the source, but it might not be the fastest + way to get the result. Also possibly some 'new' load instructions do + affect the flags in ways you wouldn't expect. You can use option --syntax=f + to get warnings when fake instruction is used, to avoid using them by accident. Here's the + list:

+ +
+  rl bc           ; rl c : rl b
+  rl de           ; rl e : rl d
+  rl hl           ; rl l : rl h
+  rr bc           ; rr b : rr c
+  rr de           ; rr d : rr e
+  rr hl           ; rr h : rr l
+  sla bc          ; sla c : rl b
+  sla de          ; sla e : rl d
+  sla hl          ; add hl,hl
+  sll bc          ; sli c : rl b
+  sll de          ; sli e : rl d
+  sll hl          ; sli l : rl h
+  sli bc          ; sli c : rl b
+  sli de          ; sli e : rl d
+  sli hl          ; sli l : rl h
+  sra bc          ; sra b : rr c
+  sra de          ; sra d : rr e
+  sra hl          ; sra h : rr l
+  srl bc          ; srl b : rr c
+  srl de          ; srl d : rr e
+  srl hl          ; srl h : rr l
+
+  ld bc,bc        ; ld b,b : ld c,c
+  ld bc,de        ; ld b,d : ld c,e
+  ld bc,hl        ; ld b,h : ld c,l
+  ld bc,ix        ; ld b,xh : ld c,xl
+  ld bc,iy        ; ld b,yh : ld c,yl
+  ld bc,(hl)      ; ld c,(hl) : inc hl : ld b,(hl) : dec hl
+  ld bc,(ix+nn)   ; ld c,(ix+nn) : ld b,(ix+nn+1)
+  ld bc,(iy+nn)   ; ld c,(iy+nn) : ld b,(iy+nn+1)
+
+  ld de,bc        ; ld d,b : ld e,c
+  ld de,de        ; ld d,d : ld e,e
+  ld de,hl        ; ld d,h : ld e,l
+  ld de,ix        ; ld d,xh : ld e,xl
+  ld de,iy        ; ld d,yh : ld e,yl
+  ld de,(hl)      ; ld e,(hl) : inc hl : ld d,(hl) : dec hl
+  ld de,(ix+nn)   ; ld e,(ix+nn) : ld d,(ix+nn+1)
+  ld de,(iy+nn)   ; ld e,(iy+nn) : ld d,(iy+nn+1)
+
+  ld hl,bc        ; ld h,b : ld l,c
+  ld hl,de        ; ld h,d : ld l,e
+  ld hl,hl        ; ld h,h : ld l,l
+  ld hl,ix        ; push ix : pop hl
+  ld hl,iy        ; push iy : pop hl
+  ld hl,(ix+nn)   ; ld l,(ix+nn) : ld h,(ix+nn+1)
+  ld hl,(iy+nn)   ; ld l,(iy+nn) : ld h,(iy+nn+1)
+
+  ld ix,bc        ; ld xh,b : ld xl,c
+  ld ix,de        ; ld xh,d : ld xl,e
+  ld ix,hl        ; push hl : pop ix
+  ld ix,ix        ; ld xh,xh : ld xl,xl
+  ld ix,iy        ; push iy : pop ix
+
+  ld iy,bc        ; ld yh,b : ld yl,c
+  ld iy,de        ; ld yh,d : ld yl,e
+  ld iy,hl        ; push hl : pop iy
+  ld iy,ix        ; push ix : pop iy
+  ld iy,iy        ; ld yh,yh : ld yl,yl
+
+  ld (hl),bc      ; ld (hl),c : inc hl : ld (hl),b : dec hl
+  ld (hl),de      ; ld (hl),e : inc hl : ld (hl),d : dec hl
+
+  ld (ix+nn),bc   ; ld (ix+nn),c : ld (ix+nn+1),b
+  ld (ix+nn),de   ; ld (ix+nn),e : ld (ix+nn+1),d
+  ld (ix+nn),hl   ; ld (ix+nn),l : ld (ix+nn+1),h
+
+  ld (iy+nn),bc   ; ld (iy+nn),c : ld (iy+nn+1),b
+  ld (iy+nn),de   ; ld (iy+nn),e : ld (iy+nn+1),d
+  ld (iy+nn),hl   ; ld (iy+nn),l : ld (iy+nn+1),h
+
+  ldi bc,(hl)     ; ld c,(hl) : inc hl : ld b,(hl) : inc hl
+  ldi bc,(ix+nn)  ; ld c,(ix+nn) : inc ix : ld b,(ix+nn) : inc ix
+  ldi bc,(iy+nn)  ; ld c,(iy+nn) : inc iy : ld b,(iy+nn) : inc iy
+
+  ldi de,(hl)     ; ld e,(hl) : inc hl : ld d,(hl) : inc hl
+  ldi de,(ix+nn)  ; ld e,(ix+nn) : inc ix : ld d,(ix+nn) : inc ix
+  ldi de,(iy+nn)  ; ld e,(iy+nn) : inc iy : ld d,(iy+nn) : inc iy
+
+  ldi hl,(ix+nn)  ; ld l,(ix+nn) : inc ix : ld h,(ix+nn) : inc ix
+  ldi hl,(iy+nn)  ; ld l,(iy+nn) : inc iy : ld h,(iy+nn) : inc iy
+
+  ldi (hl),bc     ; ld (hl),c : inc hl : ld (hl),b : inc hl
+  ldi (hl),de     ; ld (hl),e : inc hl : ld (hl),d : inc hl
+
+  ldi (ix+nn),bc  ; ld (ix+nn),c : inc ix : ld (ix+nn),b : inc ix
+  ldi (ix+nn),de  ; ld (ix+nn),e : inc ix : ld (ix+nn),d : inc ix
+  ldi (ix+nn),hl  ; ld (ix+nn),l : inc ix : ld (ix+nn),h : inc ix
+
+  ldi (iy+nn),bc  ; ld (iy+nn),c : inc iy : ld (iy+nn),b : inc iy
+  ldi (iy+nn),de  ; ld (iy+nn),e : inc iy : ld (iy+nn),d : inc iy
+  ldi (iy+nn),hl  ; ld (iy+nn),l : inc iy : ld (iy+nn),h : inc iy
+
+  ldi a,(bc)      ; ld a,(bc) : inc bc
+  ldi a,(de)      ; ld a,(de) : inc de
+  ldi a,(hl)      ; ld a,(hl) : inc hl
+  ldi b,(hl)      ; ld b,(hl) : inc hl
+  ldi c,(hl)      ; ld c,(hl) : inc hl
+  ldi d,(hl)      ; ld d,(hl) : inc hl
+  ldi e,(hl)      ; ld e,(hl) : inc hl
+  ldi h,(hl)      ; ld h,(hl) : inc hl
+  ldi l,(hl)      ; ld l,(hl) : inc hl
+  ldi a,(ix+nn)   ; ld a,(ix+nn) : inc ix
+  ldi b,(ix+nn)   ; ld b,(ix+nn) : inc ix
+  ldi c,(ix+nn)   ; ld c,(ix+nn) : inc ix
+  ldi d,(ix+nn)   ; ld d,(ix+nn) : inc ix
+  ldi e,(ix+nn)   ; ld e,(ix+nn) : inc ix
+  ldi h,(ix+nn)   ; ld h,(ix+nn) : inc ix
+  ldi l,(ix+nn)   ; ld l,(ix+nn) : inc ix
+  ldi a,(iy+nn)   ; ld a,(iy+nn) : inc iy
+  ldi b,(iy+nn)   ; ld b,(iy+nn) : inc iy
+  ldi c,(iy+nn)   ; ld c,(iy+nn) : inc iy
+  ldi d,(iy+nn)   ; ld d,(iy+nn) : inc iy
+  ldi e,(iy+nn)   ; ld e,(iy+nn) : inc iy
+  ldi h,(iy+nn)   ; ld h,(iy+nn) : inc iy
+  ldi l,(iy+nn)   ; ld l,(iy+nn) : inc iy
+
+  ldd a,(bc)      ; ld a,(bc) : dec bc
+  ldd a,(de)      ; ld a,(de) : dec de
+  ldd a,(hl)      ; ld a,(hl) : dec hl
+  ldd b,(hl)      ; ld b,(hl) : dec hl
+  ldd c,(hl)      ; ld c,(hl) : dec hl
+  ldd d,(hl)      ; ld d,(hl) : dec hl
+  ldd e,(hl)      ; ld e,(hl) : dec hl
+  ldd h,(hl)      ; ld h,(hl) : dec hl
+  ldd l,(hl)      ; ld l,(hl) : dec hl
+  ldd a,(ix+nn)   ; ld a,(ix+nn) : dec ix
+  ldd b,(ix+nn)   ; ld b,(ix+nn) : dec ix
+  ldd c,(ix+nn)   ; ld c,(ix+nn) : dec ix
+  ldd d,(ix+nn)   ; ld d,(ix+nn) : dec ix
+  ldd e,(ix+nn)   ; ld e,(ix+nn) : dec ix
+  ldd h,(ix+nn)   ; ld h,(ix+nn) : dec ix
+  ldd l,(ix+nn)   ; ld l,(ix+nn) : dec ix
+  ldd a,(iy+nn)   ; ld a,(iy+nn) : dec iy
+  ldd b,(iy+nn)   ; ld b,(iy+nn) : dec iy
+  ldd c,(iy+nn)   ; ld c,(iy+nn) : dec iy
+  ldd d,(iy+nn)   ; ld d,(iy+nn) : dec iy
+  ldd e,(iy+nn)   ; ld e,(iy+nn) : dec iy
+  ldd h,(iy+nn)   ; ld h,(iy+nn) : dec iy
+  ldd l,(iy+nn)   ; ld l,(iy+nn) : dec iy
+
+  ldi (bc),a      ; ld (bc),a : inc bc
+  ldi (de),a      ; ld (de),a : inc de
+  ldi (hl),a      ; ld (hl),a : inc hl
+  ldi (hl),b      ; ld (hl),b : inc hl
+  ldi (hl),c      ; ld (hl),c : inc hl
+  ldi (hl),d      ; ld (hl),d : inc hl
+  ldi (hl),e      ; ld (hl),e : inc hl
+  ldi (hl),h      ; ld (hl),h : inc hl
+  ldi (hl),l      ; ld (hl),l : inc hl
+  ldi (ix+nn),a   ; ld (ix+nn),a : inc ix
+  ldi (ix+nn),b   ; ld (ix+nn),b : inc ix
+  ldi (ix+nn),c   ; ld (ix+nn),c : inc ix
+  ldi (ix+nn),d   ; ld (ix+nn),d : inc ix
+  ldi (ix+nn),e   ; ld (ix+nn),e : inc ix
+  ldi (ix+nn),h   ; ld (ix+nn),h : inc ix
+  ldi (ix+nn),l   ; ld (ix+nn),l : inc ix
+  ldi (iy+nn),a   ; ld (iy+nn),a : inc iy
+  ldi (iy+nn),b   ; ld (iy+nn),b : inc iy
+  ldi (iy+nn),c   ; ld (iy+nn),c : inc iy
+  ldi (iy+nn),d   ; ld (iy+nn),d : inc iy
+  ldi (iy+nn),e   ; ld (iy+nn),e : inc iy
+  ldi (iy+nn),h   ; ld (iy+nn),h : inc iy
+  ldi (iy+nn),l   ; ld (iy+nn),l : inc iy
+
+  ldd (bc),a      ; ld (bc),a : dec bc
+  ldd (de),a      ; ld (de),a : dec de
+  ldd (hl),a      ; ld (hl),a : dec hl
+  ldd (hl),b      ; ld (hl),b : dec hl
+  ldd (hl),c      ; ld (hl),c : dec hl
+  ldd (hl),d      ; ld (hl),d : dec hl
+  ldd (hl),e      ; ld (hl),e : dec hl
+  ldd (hl),h      ; ld (hl),h : dec hl
+  ldd (hl),l      ; ld (hl),l : dec hl
+  ldd (ix+nn),a   ; ld (ix+nn),a : dec ix
+  ldd (ix+nn),b   ; ld (ix+nn),b : dec ix
+  ldd (ix+nn),c   ; ld (ix+nn),c : dec ix
+  ldd (ix+nn),d   ; ld (ix+nn),d : dec ix
+  ldd (ix+nn),e   ; ld (ix+nn),e : dec ix
+  ldd (ix+nn),h   ; ld (ix+nn),h : dec ix
+  ldd (ix+nn),l   ; ld (ix+nn),l : dec ix
+  ldd (iy+nn),a   ; ld (iy+nn),a : dec iy
+  ldd (iy+nn),b   ; ld (iy+nn),b : dec iy
+  ldd (iy+nn),c   ; ld (iy+nn),c : dec iy
+  ldd (iy+nn),d   ; ld (iy+nn),d : dec iy
+  ldd (iy+nn),e   ; ld (iy+nn),e : dec iy
+  ldd (iy+nn),h   ; ld (iy+nn),h : dec iy
+  ldd (iy+nn),l   ; ld (iy+nn),l : dec iy
+
+  ldi (hl),mm     ; ld (hl),mm : inc hl
+  ldi (ix+nn),mm  ; ld (ix+nn),mm : inc ix
+  ldi (iy+nn),mm  ; ld (iy+nn),mm : inc iy
+
+  ldd (hl),mm     ; ld (hl),mm : dec hl
+  ldd (ix+nn),mm  ; ld (ix+nn),mm : dec ix
+  ldd (iy+nn),mm  ; ld (iy+nn),mm : dec iy
+
+  adc de,bc       ; ex de,hl : adc hl,bc : ex de,hl
+  adc de,de       ; ex de,hl : adc hl,hl : ex de,hl         ;; consider alternative: rl e : rl d (-7T)
+  adc de,hl       ; ex de,hl : adc hl,de : ex de,hl
+  adc de,sp       ; ex de,hl : adc hl,sp : ex de,hl
+
+  add de,bc       ; ex de,hl : add hl,bc : ex de,hl
+  add de,de       ; ex de,hl : add hl,hl : ex de,hl         ;; consider alternative: srl e : rl d (-3T)
+  add de,hl       ; ex de,hl : add hl,de : ex de,hl
+  add de,sp       ; ex de,hl : add hl,sp : ex de,hl
+
+  sbc de,bc       ; ex de,hl : sbc hl,bc : ex de,hl
+  sbc de,de       ; ex de,hl : sbc hl,hl : ex de,hl
+  sbc de,hl       ; ex de,hl : sbc hl,de : ex de,hl
+  sbc de,sp       ; ex de,hl : sbc hl,sp : ex de,hl
+
+  sub de,bc       ; or a : ex de,hl : sbc hl,bc : ex de,hl
+  sub de,de       ; or a : ex de,hl : sbc hl,hl : ex de,hl  ;; consider alternative: ld de,0 (-17T)
+  sub de,hl       ; or a : ex de,hl : sbc hl,de : ex de,hl
+  sub de,sp       ; or a : ex de,hl : sbc hl,sp : ex de,hl
+  sub hl,bc       ; or a : sbc hl,bc
+  sub hl,de       ; or a : sbc hl,de
+  sub hl,hl       ; or a : sbc hl,hl                        ;; consider alternative: ld hl,0 (-9T)
+  sub hl,sp       ; or a : sbc hl,sp
+
+  ; Z80N only
+  mul             ; mul de ; to avoid warning specify registers

+ LDI increases the data pointer after the data + access, so LDI A,(HL) is the same as LD A,(HL):INC HL. + Likewise, LDD A,(DE) is LD A,(DE):DEC DE.

+
+ +

Real device emulation mode

+ + +

To enable this mode you must use pseudo-op DEVICE.

+ +

In this mode the assembler is assembling program into virtual memory (as + MSX's WB-ASS2, ZX-Spectrum's GENS, ZEUS, ALASM etc). You can also use pseudo-ops as + MMU, + SLOT, + PAGE, + SAVEBIN, + SAVEDEV, + SAVETAP, + SAVESNA, + SAVETRD, + SAVENEX, + SAVECDT, + SAVECPCSNA, + SAVEAMSDOS, + SAVE3DOS, + SAVEHOB, + BPLIST, + SETBP, + CSPECTMAP, + LABELSLIST, + use special functions in Lua scripts and use operators + {address}, {b address} to read WORD/BYTE from the virtual memory.

+ +

If only single DEVICE is used in whole source batch, the device + becomes "global" and will affect also source ahead of the DEVICE line. +

Example 4.2. docs_examples/s_realdevice.asm

+ + +
    DEVICE ZXSPECTRUM128
+    ; in this device the default slot is SLOT 3 with PAGE 0 paged in.
+
+    ORG 32768
+StartProg:
+    JP $
+
+    DEVICE NONE
+    ;do something, if you don't want to corrupt virtual
+    ;memory with other code, for example, loader of code.
+    ;...code...
+
+    ;return to our virtual device:
+    DEVICE ZXSPECTRUM128
+
+    SAVESNA "snapshotname.sna", StartProg
+


Predefined devices:

NONE
+

Disable real device emulation mode. By default.

+
ZXSPECTRUM48
+

Has 4 slots (0-3) with size 4000h, 4 pages (0-3) with size + 4000h. Slot 3 (it from 0C000h) enables to current by + default.

+
ZXSPECTRUM128
+

Has 8 RAM pages (0-7) with size 4000h. Default slot is 3.

+
ZXSPECTRUM256
+

Same as Russian clone Scorption 256. Has 16 RAM pages + (0-15) with size 4000h.

+
ZXSPECTRUM512
+

Same as Russian clones ATM Turbo 512 and Pentagon 512. Has + 32 RAM pages (0-31) with size 4000h.

+
ZXSPECTRUM1024
+

Same as Russian clones ATM Turbo 2 and Pentagon 1024 SL. + Has 64 RAM pages (0-63) with size 4000h.

+
ZXSPECTRUM2048
+

Similar to other spectrum devices, has 128 RAM pages (0-127) with size 4000h.

+
ZXSPECTRUM4096
+

Similar to other spectrum devices, has 256 RAM pages (0-255) with size 4000h.

+
ZXSPECTRUM8192
+

Similar to other spectrum devices, has 512 RAM pages (0-511) with size 4000h.

+
ZXSPECTRUMNEXT
+

ZX Spectrum Next, has 8 slots (0-7) of size 0x2000 and 224 + RAM pages (0-223) totalling at 1.75MiB of memory. The default mapping + is similar to ZX128, paging in: {14, 15, 10, 11, 4, 5, 0, 1} pages. + Default slot is 7 (memory range 0xE000..0xFFFF). + All memory is zeroed during initialization.

+
NOSLOT64K
+

Has only single slot of size 64kiB (slot 0), covering full Z80 CPU addressing range. + There are 32 memory pages available, by default page 0 is mapped. + That makes in total 2MiB of memory available (32 * 64 ki = 2 Mi).

+
AMSTRADCPC464
+

Has 4 slots (0-3) of size 0x4000 and 4 pages (0-3) of 0x4000, totalling 64K. These pages are mapped linearly as + { 0, 1, 2, 3 }.

+
AMSTRADCPC6128
+

Has 4 slots (0-3) of size 0x4000 and 8 RAM pages (0-7) of 0x4000 (128K total). The default page mapping is + { 0, 1, 2, 3 }.

+
+ +

If you want to see other devices you must write to us. See Feedback chapter.

+
+ +

Predefined defines

+ + +

SjASMPlus has predefined defines.

__SJASMPLUS__ = <24bit number>
+

Current version split into three 8bit values, ie. 0x010F02 for "1.15.2".

+
__VERSION__ = "version string"
+

String value (with quotes around it) of current version like "1.15.2".

+
__ERRORS__ = <number>
+

Number of errors.

+
__WARNINGS__ = <number>
+

Number of warnings.

+
__DATE__ = "YYYY-MM-DD"
+

Date of assembling (timestamp before first pass).

+
__TIME__ = "hh:mm:ss"
+

Time of assembling (timestamp before first pass, 24h form).

+
__PASS__ = <number>
+

Current assembling pass (1, 2 or 3).

+
__INCLUDE_LEVEL__ = <number>
+

Current include-nesting level.

+
__BASE_FILE__ = <name of base file>
+

Name of base file (include-level zero).

+

This is raw value without quotes, practically unusable by asm source, but LUA + provides means to operate with such string value.

+
__FILE__ = <name of current file>
+

Name of current file.

+

This is raw value without quotes, practically unusable by asm source, but LUA + provides means to operate with such string value.

+
__LINE__ = <number>
+

Current line number.

+
__COUNTER__ = <incrementing number>
+

Does increment upon each usage (starting as value 0). Currently difficult to + use for labels/symbols, because the starting `_` of the define name prevents + mid-word substitution, will make more sense with new substitution rules (if ever + the sjasmplus v2.x happens, would be too big change for v1.x). Right now call + LUA for the rescue.

+

Example 4.3. __COUNTER__ usage in LUA script

+ + +
    DB __COUNTER__   ; DB 0
+    LUA ALLPASS
+        sj.insert_label("label_" .. sj.get_define("__COUNTER__"), sj.current_address)
+                -- creates "label_1" at "$" (0x0001)
+        sj.insert_label("label_" .. sj.get_define("__COUNTER__"), _c("$+10"))
+                -- creates "label_2" at "$+10" (0x000B)
+    ENDLUA
+label__COUNTER__: ; does *NOT* substitute in current sjasmplus, sorry
+    DB __COUNTER__   ; DB 3
+
+    ; also macro arguments substitution can be used
+    MACRO createLabelWithSuffix label?, suffix?
+label?_suffix? ; define global label
+    ENDM
+    createLabelWithSuffix label, __COUNTER__    ; label_4
+    createLabelWithSuffix label, __COUNTER__    ; label_5
+

+
Deprecated predefined values from sjasmplus till version 1.15.1:
+

The predefined values were renamed and extended to be more like gcc/clang pre-defines, + the following ones are deprecated originals.

+
_SJASMPLUS = 1
+

Deprecated, consider using similar __SJASMPLUS__

Example 4.4. 

+ + +
   IFDEF _SJASMPLUS
+     ;code for sjasmplus
+   ELSE
+     ;code for other compiler
+   ENDIF
+


+
_VERSION = "version"
+

Deprecated, renamed to __VERSION__

Example 4.5. 

+ + +
   IF _VERSION = "1.07"
+     ;code for 1.07
+   ELSE
+     ;code for other version
+   ENDIF
+


+
_RELEASE = releasenumber
+

Deprecated, consider using similar __SJASMPLUS__

Example 4.6. 

+ + +
   IF _RELEASE = 1 ; 0 - is stable version
+     ;code for Release Candidate 1
+   ELSE
+     ;code for other version
+   ENDIF
+


+
_ERRORS = <number>
+

Number of errors. Deprecated, renamed to __ERRORS__

+
_WARNINGS = <number>
+

Number of warnings. Deprecated, renamed to __WARNINGS__

+
+
+
+ +

Chapter 5. Pseudo-ops (aka Pseudo-instructions, Directives etc)

+ + +

Simple example of usage

+ + +
     .SOMEPSEUDOOP ;or
+     SOMEPSEUDOOP  ;or
+     somepseudoop
+ +

[Almost] complete list of pseudo-ops follows:

+
+ +

Pseudo-ops

+ + +

+ +
.<repeat-count> <single instruction>
+

Repeat <single instruction> <repeat-count> many times. Doesn't work + in the beginning of line. The <repeat-count> must be either simple integer + number or expression fully enclosed in parentheses.

Example 5.1. docs_examples/po_dot_repeat.asm

+ + +
    .3          INC A    ;will be compiled to INC A:INC A:INC A
+len             EQU 10
+    .(12-len)   BYTE 0   ;will be compiled to BYTE 0,0
+    .2 .3       RET      ;will be compiled to 6x RET
+


+
ABYTE <offset> <bytes>
+

Defines a byte or a string of bytes. The offset is added + to each of the following bytes.

Example 5.2. 

+ + +
    ABYTE 2 4,9    ; Same as BYTE 6,11
+    ABYTE 3 "ABC"  ; Same as BYTE "DEF"
+


+
ABYTEC <offset> <bytes>
+

Defines a byte or a string of bytes, where the last byte + of the string will have bit 7 set. The offset is added to each + of the following bytes.

Example 5.3. 

+ + +
    ABYTEC 0 "KIP"        ; Same as BYTE "KI",'P'|128
+    ABYTEC 1 "ABC",0,"DE" ; Same as BYTE "BC",'D'|128,1,'E','F'|128
+


+
ABYTEZ <offset> <bytes>
+

Defines a byte or a string of bytes, followed by a zero. + The offset is added to each of the following bytes.

Example 5.4. 

+ + +
    ABYTEZ 0 "KIP"        ; Same as BYTE "KIP",0
+


+
ALIGN + [<expression equal to 1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384 or + 32768>[, <byte>]]
+

Align advances to nearest address where <new address> modulo <expression> (default 4) + equals zero (stays at current address if possible).

+

If <byte> is specified, memory advanced over is set to it. +

Example 5.5. 

+ + +
    ALIGN         ; => ALIGN 4 - simply align by 4
+    ALIGN 2       ; by 2 (preserves value of "device" memory)
+    ALIGN 2,0     ; + fills memory with zero
+


+
ASSERT <expression>[, <anything>]
+

An 'assertion failed' error is issued if the expression evaluates to zero. + The "anything" (optional argument since v1.18.1) is then also visible in the error + message, so you can add description/notes about expression there.

Example 5.6. 

+ + +
STACKPOINTER=0D500H
+    ASSERT END_OF_PROGRAM < STACKPOINTER, Program code leaks into stack area
+END_OF_PROGRAM
+


+
BINARY <filename>[,<offset>[,<length>]]
+

Synonym of INCBIN.

+
BLOCK <length>[,<fill + byte>]
+

Defines space. Has to be followed by the number of byte to + reserve, optionally followed by the value to fill these bytes + with.

Example 5.7. 

+ + +
    BLOCK 500     ; define a block of 500 bytes of zero
+    BLOCK 500,0   ; define a block of 500 bytes of zero
+    BLOCK 400,-1  ; define a block of 400 bytes of 255
+


+
BPLIST <filename> [unreal|zesarux]
+

+ Works only in real device emulation mode. See + DEVICE. +

+

Opens file to export breakpoints info directly from ASM source, currently two + flavours of export are supported: unreal (default) and + zesarux. For latest Unreal emulators use filename "bpx.ini", you + can then load the file from the emulator UI. For ZEsarUX the file will contain + command-line options, which you can add to the command when launching the emulator. +

+

(current version of ZEsarUX will not catch the very first instruction of freshly + loaded snapshot/NEX/... file, or it will even disable breakpoints when changing + machine parameters and ignore the --enable-breakpoints option - will + be hopefully improved in the future) +

Example 5.8. 

+ + +
    BPLIST "bpx.ini" unreal  ; open breakpoints list in "Unreal" format
+    ; or (only one file per assembling-unit can be specified)
+    BPLIST "cmd_line_options.txt" zesarux ; open breakpoints list in "ZEsarUX" format
+


+

+
BYTE <bytes>
+

Defines a byte or a string of bytes. Each value should be + between -129 and 256.

Example 5.9. 

+ + +
    BYTE 0x56
+    BYTE 1,-78,'@'
+    BYTE "Format C:? ",0h
+


+
CSPECTMAP [<filename>]
+

Useful for ZX-Spectrum Emulator + #CSpect by Mike Dailly.

+ +

Works only in real device emulation mode. See + DEVICE.

+ +

Saves labels list in format:

+ +
HEXA_16BIT_ADDRESS HEXA_LONG_ADDRESS TYPE LABELNAME

+ where TYPE is: 00 = regular label, 01 = EQU or struct defined, 02 = DEFL defined, + 03 = ROM page/"none" device, 04 = STRUCT definition.

+ +

If no filename is provided, default is created by appending ".map" to source name.

+
D24
+

Defines three bytes by 24b constant. Values should be between + -16777217 and 16777216.

Example 5.10. 

+ + +
    D24 0x123456   ; define three bytes 0x56, 0x34, 0x12
+


+
DB
+

Synonym of BYTE.

+
DC
+

Same as BYTE, but every + last character of a string will have bit 7 set. Single character in apostrophes is NOT considered as string. +

Example 5.11. 

+ + +
    DC "kip" ; same as BYTE "ki",'p'|128
+    DC 'A'   ; not a string, will produce 0x41 like BYTE 'A'
+    DC "A", 'AB', "AB" ; three strings producing: C1 41 C2 41 C2
+


+
DD
+

Synonym of DWORD.

+
DEFARRAY <id> <replacements>
+

Array of DEFINEs.

+

Use id[#] to retrieve current size of array. +

Example 5.12. docs_examples/po_defarray.asm

+ + +
    DEFARRAY myarray 10*20,"A",20,</D,40>,50,70
+CNT DEFL 0 ;or CNT=0
+    DUP myarray[#]      ; 6
+    DISPLAY myarray[CNT]
+CNT DEFL CNT+1 ;or CNT=CNT+1
+    EDUP
+


+
DEFARRAY+ <id> <additional replacements>
+

Appending more DEFINEs to already defined array

Example 5.13. 

+ + +
    DEFARRAY   myarray 'A', 'B', 'C'
+    DEFARRAY+  myarray 'D', 'E'            ; now "myarray" has 5 items
+    DUP 3 : DEFARRAY+ myarray '!' : EDUP   ; "DEFARRAYFILL" adding 3x '!'
+


+
DEFB
+

Synonym of BYTE.

+
DEFD
+

Synonym of DWORD.

+
DEFDEVICE + <deviceid>, <slot_size $100..$10000>, <page_count 1..1024>[, <slot_0_initial_page>[, ...]]
+

(since v1.20.1) Add custom device definition. The 64ki Z80 address space is divided evenly by slot_size + to form N slots and the total device memory is slot_size * page_count bytes.

+ +

The initial pages are implicitly going from 0 to page_count-1 + (repeating page page_count-1 for remaining slots when there's not enough pages), but you + can specify initial page layout explicitly.

+ +

Use DEVICE to select the newly defined device.

+
DEFG
+

Synonym of DG.

+
DEFH
+

Synonym of DH.

+
DEFINE+ <id> <replacement>
DEFINE <id> <replacement>
+

The identifier <id> will be replaced with the <replacement>. + The DEFINE+ (since v1.18.0) will also redefine the identifier <id> + if it already exists. The replacement could be omitted, + in such case it is still possible to check if the identifier was defined + with IFDEF or IFNDEF.

Example 5.14. 

+ + +
    DEFINE str_honderd "Honderd"
+    BYTE str_honderd,0             ; BYTE "Honderd",0
+    DEFINE+ do_stuff set 4,e
+    do_stuff                       ; set 4,e
+    DEFINE+ do_stuff set 2,e
+    do_stuff                       ; set 2,e
+


+
<label> DEFL <expression>
+

Assigns value of <expression> to symbol <label>. New label defined + by DEFL is marked internally as "modifiable", allowing to re-assign new values to it with + further DEFL statements (you can use also = instead of DEFL). +

Example 5.15. 

+ + +
counter DEFL 0
+    DUP 4
+        DB 0xAA, counter
+counter = counter + 1
+    EDUP
+; machine code produced: AA 00 AA 01 AA 02 AA 03
+


+
DEFM
+

Synonym of BYTE.

+
DEFS
+

Synonym of BLOCK.

+
DEFW
+

Synonym of WORD.

+
DEPHASE
+

Synonym of ENT.

+
DEVICE <deviceid>[, <RAMTOP>]
+

Enables real device emulation + mode by it identifier. If there is only single DEVICE directive + in whole source batch, it becomes "global" and the device affects all + lines of source, otherwise the DEVICE is applied for lines following it.

+ +

For ZXSPECTRUM-like devices (except ZX Next) you can provide RAMTOP value, + which will init the device memory in similar way as "CLEAR <RAMTOP>" + in BASIC would do, putting top of the fake-stack at the RAMTOP address. The + default RAMTOP is since v1.15.0 0x5D5B (it was 0xFF57 before).

+ +

Predefined devices' identifiers list:

+ +
 NONE ; off real device emulation mode
+ ZXSPECTRUM48   ; ZX-Spectrum 48 (4 slots, 4 pages, slot/page size 0x4000, default map: 0, 1, 2, 3)
+ ZXSPECTRUM128  ; ZX-Spectrum 128 (like 48 with 8 pages, default map: 7, 5, 2, 0)
+ ZXSPECTRUM256  ; e.g. Scorpion 256 (exUSSR clone of ZX-Spectrum 128)
+ ZXSPECTRUM512  ; e.g. ATM-Turbo 512 (another clone)
+ ZXSPECTRUM1024
+ ZXSPECTRUM2048
+ ZXSPECTRUM4096
+ ZXSPECTRUM8192
+ ZXSPECTRUMNEXT ; ZX Spectrum Next (8 slots, 224 pages, slot size 0x2000 = 1.75MiB RAM)
+                ; (default pages map: 14, 15, 10, 11, 4, 5, 0, 1) (default slot: 7 (0xE000..0xFFFF))
+ NOSLOT64K      ; Single slot (slot 0) covering full 64kiB address range of Z80, 32 pages
+ AMSTRADCPC464  ; Amstrad CPC 464 (4 slots, 4 pages, slot/page size 0x4000, default map: 0, 1, 2, 3)
+ AMSTRADCPC6128 ; Amstrad CPC 6128 (like 464 with 8 pages)
+
+ ;disable:
+   DEVICE NONE
+ ;enable:
+   DEVICE ZXSPECTRUM128
+
DG <data encoded in bits>
+

Data comprises of characters in multiples of eight, each block + is converted to a byte value.

+ +

A hyphen '-' (also '.' and '_') represents 0 and any other non-whitespace character + represents 1. It ignores spaces, use them for formatting if you like. Warning, "DG 10100001" is value 255, because character '0' is not a dash '-'. (since v1.11)

Example 5.16. 

+ +
    DG 1-1----1  ; store 161 at the current location
+    DG ...# #... .##. .... ; store two bytes: 0x18, 0x60
+


+
DH "<data>"[,"<data2>"...]
+

The data string comprises pairs of hexadecimal digits, each pair is converted to + a byte value. You can add spaces between pairs as you like. (since v1.11)

Example 5.17. 

+ +
    DH "0123456789ABCDEF"   ; eight bytes #01 #23 ...
+    DH "01 23  45 67"       ; four bytes #01 #23 #45 #67
+


+
DISP <address>[,<page_number>]
+

Set the address in which the part of code should work. + PHASE and TEXTAREA are synonyms of DISP. + ENT will restore current address. + UNPHASE, DEPHASE and ENDT are synonyms of ENT. DISP blocks can NOT be nested, and to change + the displacement address within current DISP block use the ordinary ORG. + When in device mode, you can specify fixed value for "fake" page of emitted + instructions and regular labels, but to avoid warning, you must also map-in + the target page into the target memory slot (MMU). + When no fixed page in DISP is specified, the current mapping of memory pages is used. +

Example 5.18. docs_examples/po_disp.asm

+ + +
    DEVICE ZXSPECTRUM48
+SCREEN  EQU $4000
+        ORG $8000
+        LD HL,BEGIN
+        LD DE,SCREEN
+        LD BC,ENDOFPROG-BEGIN
+        LDIR
+        JP SCREEN
+BEGIN   DISP SCREEN ;code will compile for address $4000, but to the current ORG
+MARKA       DEC A
+            HALT
+            JP NZ,MARKA
+            DI
+            HALT
+        ENT
+ENDOFPROG
+
+    ASSERT $800E == BEGIN && $8015 == ENDOFPROG && $4000 == MARKA
+    ASSERT $76 == {B $800F}     ; HALT instruction lands at $800F (BEGIN+1)
+


+
DISPLAY <bytes>
+

This pseudo-op comes from ZX-Spectrum assembler + ALASM.

+ +

Out to console a string of bytes. Each value should be + between -129 and 256. Keys /D, /B, /C, /H and /A set format of output of + numbers:

/D - out only in Decimal
+/B - out only in Binary (truncated to 8 bit)
+/C - out as character in apostrophes (truncated to 8 bit)
+/H - out only in Hexadecimal
+/A - out both in Hexadecimal and Decimal

+

Example 5.19. docs_examples/po_display.asm

+ + +
    ORG 100h
+TESTLABEL:
+    ;...some code...
+    RET
+    DISPLAY "--the some program-- by me"
+    DISPLAY "TESTLABEL address is:",/A,TESTLABEL
+/*
+will output to the console strings:
+> --the some program-- by me
+> TESTLABEL address is:0x100, 256
+*/
+


+
DM
+

Synonym of BYTE.

+
DS
+

Synonym of BLOCK.

+
DUP <count>[, <index_variable>]
+

DUP specifies the number of times to generate the + following lines until an EDUP pseudo-op is encountered. DUP can be used in macro's.

+

Optional second argument (since v1.20.2) is label name to have repetition index + available inside the block (values 0, 1, 2, 3, ...). +

Example 5.20. 

+ + +
    DUP 3 ; zero count would skip whole block
+    NOP
+    EDUP
+
+/*this will expand to:
+    NOP
+    NOP
+    NOP
+*/
+


+
DW
+

Synonym of WORD.

+
DWORD
+

Defines a so called doubleword. Values should be between + -2147483649 and 4294967296.

Example 5.21. 

+ + +
    DWORD 4000h,0d000h
+    DWORD 4
+


+
DZ
+

Same as BYTE, but an extra + zero will be added at the end.

Example 5.22. 

+ + +
    DZ 1      ; same as BYTE 1,0
+    DZ "kip"  ; same as BYTE "kip",0
+


+
EMPTYTAP <filenameoftapefile>
+

Useful only for ZX-Spectrum + users

+ +

Create the new or truncate existing standard tape file + for emulators of ZX-Spectrum. See example of + SAVETAP.

+
EMPTYTRD <filenameoftrdimage>[,<disclabel>]
+

Useful only for ZX-Spectrum users

+ +

Create the empty TRD image for emulators of ZX-Spectrum. + See example of SAVETRD.

+
ENCODING <encoding>
+

Useful only for non English + users

+ +

Set the current encoding, i.e. if you set "DOS", SjASMPlus + will automatically convert strings from ANSI to DOS-866. + Encoding may be "DOS"(DOS-866) or "WIN"(ANSI/Win-1251). Default + is "WIN".

Example 5.23. 

+ + +
    ENCODING "WIN"
+    DB "тексттекст" ;will be тексттекст
+    ENCODING "DOS"
+    DB "тексттекст" ;will be ⥪бв⥪бв
+


+
END [<startaddress>]
+

The assembler will stop at this point. The pseudo-op + END does NOT work in the beginning of line (even with --dirbol). + The optional argument is used by SAVESNA, SAVECPCSNA, SAVETAP and SAVENEX.

+
ENDLUA
+

See LUA for more + information.

+
ENDMOD
+

Synonym of ENDMODULE.

+
ENDMODULE
+

To indicate the end of a module (see MODULE).

+
ENDT
+

Synonym of ENT.

+
ENT
+

Restore current address. See DISP for more information.

+
<label> EQU <expression>[,<pageNumber>]
+

To give the label a value other than the current program + counter. The label should not already exist (you can assign only one value to it). + The optional pageNumber can enforce explicit page value for the label + (for the $$label operator). + For modifiable labels holding temporary values use DEFL.

Example 5.24. 

+ + +
Label EQU 3
+Kip   EQU 0x23*256 + low $
+


+
EXPORT <label>
+

The named label will be written to the export-file, in the + form 'label: EQU value'. This way the export-file can be + included in other sources. Order of labels in file follows the order of EXPORT statements. +

Example 5.25. 

+ + +
DRIE=3
+    EXPORT DRIE  ; adds into export file line: "DRIE: EQU 0x00000003"
+


+
FPOS <position>
+

The FPOS directive makes it possible to set the file + position to anywhere in the output file. Position value without sign + is used as absolute one to be set, position starting with + or - sign will be + used as relative position.

+ +

In combination with OUTPUT <filename>,r it is + possible to update existing files.

+ +

Example 5.26. 

+ + +
; This example will result in a file with a length of one byte:
+    BYTE 0
+    FPOS 0
+    BYTE 1
+    END
+


+
HEX
+

Synonym of DH, usually used without quotes around data.

+
INCBIN + <filename>[,<offset>[,<length>]]
+

To include a binary file into the outputfile. The offset + and length are optional. Added in v1.12.1: if negative offset or length is provided, + it counts relatively from the end of the file.

Example 5.27. 

+ + +
    INCBIN "gfx.scc",7        ; include gfx.scc, skip first 7 bytes
+    INCBIN "rantab.com",3,256 ; include 256 bytes from offset 3
+    INCBIN gfx.scc ,,7        ; 7 bytes from offset 0 (unquoted filename must end with space)
+    INCBIN "48.rom",-768,-256 ; include (from 16kiB file) 512 bytes 15616..16127
+


+
INCHOB <filename>[,<offset>[,<length>]]
+

To include a data from a hobeta file into the outputfile. + The offset and length are optional.

Example 5.28. 

+ + +
    INCHOB "gfx.$c",7        ; include gfx.scc, skip first 7 bytes
+    INCHOB "sprs.$c",3,256   ; include 256 bytes from offset 3
+    INCHOB gfx.$c ,7        ; note the space between the filename and the ',7' here :)
+


+
INCLUDE <filename>
+

To include another sourcefile into the current. + Sourcefiles can be nested 20 levels deep. If the file cannot be + found in the current directory (the current directory is the + directory the current asm file comes from!) the file will be searched + for in the directories specified at the commandline. When angle + brackets are used, the commandline directories are searched + before the current directory.

+

The directory used to launch the assembling process is automatically added + to the list (as if "-i." was added to command line manually) + (v1.14.0 and v1.14.1 don't add it, reverted back for v1.14.2). If you want to start + with completely empty include-path list, use "--inc" option + early (order matters) without the "=" to empty the current list, like: + sjasmplus --inc --inc=path1 --inc=path2 file.asm +

Example 5.29. 

+ + +
    INCLUDE <VDP.I>     ; search for file "VDP.I" in the include directories, then in current
+    INCLUDE MORE.I      ; search for "MORE.I" in current directory, then in include directories
+    INCLUDE "MORE.I"
+


+
INCLUDELUA <filename>
+

To include another LUA script in first pass(!). If the + file cannot be found in the current directory (the current + directory is the directory the current file comes from) the file + will be searched for in the directories specified at the + commandline. When angle brackets are used, the commandline + directories are searched before the current directory.

Example 5.30. 

+ + +
    INCLUDELUA <mylibrary1.lua>
+    INCLUDELUA mylibrary2.lua
+    INCLUDELUA "library_for_zx.lua"
+


+
INCTRD + <filenameoftrdimage>,<filenameintrdimage>[,<offset>[,<length>]]
+

To include a file from a TRD image into the outputfile. + The offset and length are optional.

Example 5.31. 

+ + +
    INCTRD "test.trd","mygfx.C" ; include mygfx.C from test.trd
+    INCTRD "test.trd","mygfx.C",12 ; include mygfx.C from test.trd, skip first 12 bytes
+


+
INSERT <filename>[,<offset>[,<length>]]
+

INSERT is a synonym of INCBIN. See above.

+
LABELSLIST <filename>[,<virtual labels>]
+

Useful only for ZX-Spectrum Emulator + UNREALSPECCY.

+ +

Works only in real device emulation mode. See + DEVICE.

+ +

Save labels list in format:

+ +
NN:ADDRESS LABELNAME

+ where NN is number of RAM page and ADDRESS is truncated to 0000..3FFF range

+ +

If <virtual labels> is non zero, then the page number NN is not + part of output, and ADDRESS is truncated to 0000..FFFF range.

+ +

Example 5.32. 

+ + +
    LABELSLIST "x:/somepath/user.l"
+


+
LUA [pass]
+

Using pseudo-ops LUA and ENDLUA you can insert Lua + scripts. See more in the chapter "Lua scripting".

+ +

Parameter is optional. It may be:

PASS1   -  interpret Lua script in first pass only.
+PASS2   -  interpret Lua script in second pass only.
+PASS3   -  interpret Lua script in third pass only. By default.
+ALLPASS -  interpret Lua script in all passes. It is needed if you generate some Z80 code.
+ +

Example 5.33. 

+ + +
    LUA
+-- some comments
+        print "Hi, man! This is Lua!"
+    ENDLUA
+; some code now:
+    LUA ALLPASS
+        _pl("LABEL LD A,10")
+        _pc("RET")
+    ENDLUA
+


+
MEMORYMAP
+

Not available yet.

+
MMU <first slot number or address> [<last slot number or address>|<single slot option>], <page number>[,<address>]
+

Maps memory page(s) to slot(s), similar to SLOT + PAGE combination, but allows + to set up whole range of consecutive slots (with consecutive memory pages). Or when + only single slot is specified, extra option can be used to extend particular slot + functionality. The slot behaviour will stay set in the current DEVICE until reset + by another MMU specifying same slot (even as part of range, that will clear the option + to "default").

+ +

The optional third argument is address for ORG + functionality.

+ +

(since v1.18.1) You can also use starting address of particular slot instead + of its number, ie. for ZX128: MMU $8000,3 is alias of MMU 2,3

+ +

Single slot option (default state is: no error/warning and no wrap = nothing special): +

e = error on writing beyond last byte of slot
+w = warning on writing beyond last byte of slot
+n = wrap address back to start of slot, map next page

+

Example 5.34. docs_examples/po_mmu.asm

+ + +
    DEVICE ZXSPECTRUM128 : LABELSLIST "po_mmu.lbl"  ; to check label pages
+    MMU 1 3, 5      ; maps slots 1, 2, 3 with pages 5, 6, 7
+    ORG 0xBFFF
+label1_p6: scf      ; last byte of page 6 (in slot 2)
+label2_p7: scf      ; first byte of page 7 (in slot 3)
+
+    MMU 3 e, 0      ; page 0 into slot 3, write beyond slot will cause error
+    ORG 0xFFFF
+    ld  a,1         ; error: Write outside of memory slot: 65536 (65536 = address outside)
+
+    MMU 3 n, 1      ; page 1 into slot 3, make it wrap + map next page automatically
+    ORG 0xFFFF      ; ! also the $ address was truncated by MMU from $10001 to $0001 !
+label3_p1: scf      ; last byte of page 1, then wrapping back to 0xC000 with page 2
+label4_p2: scf      ; first byte of page 2 at 0xC000
+


+
MODULE <name>
+

+ Labels has to be unique only whithin the current module (module is added as prefix to them). + Also note the use of '@' operator to suppress all this label-processing. Modules can + be nested, and module has to be ended by ENDMODULE. +

Example 5.35. docs_examples/po_module.asm

+ + +
    MODULE xxx
+Kip:                ; label xxx.Kip
+    ld  hl,@Kip     ; global Kip
+    ld  hl,@Kop     ; global Kop
+    ld  hl,Kop      ; xxx.Kop
+Kop:                ; label xxx.Kop
+    ld  hl,Kip      ; xxx.Kip
+    ld  hl,yyy.Kip  ; yyy.Kip
+    ld  hl,nested.Kip   ; xxx.nested.Kip
+        MODULE nested
+Kip:        ret     ; label xxx.nested.Kip
+        ENDMODULE
+    ENDMODULE
+
+    MODULE yyy
+Kip:    ret         ; label yyy.Kip
+@Kop:   ret         ; label Kop (global one, no module prefix)
+@xxx.Kop: nop       ; ERROR: duplicate: label xxx.Kop
+    ENDMODULE
+
+Kip     ret         ; global label Kip
+


+ Since v1.14.0 the module <name> can NOT contain dot character. You can + use nested modules to get identical identifier as in older versions, or please rename with + underscores/etc: +

    ; invalid since v1.14.0
+        MODULE older.version
+fn1:        ret        ; final label: @older.version.fn1
+        ENDMODULE
+    ; can be replaced in v1.14.0 with
+        MODULE new
+            MODULE version
+fn1:            ret    ; final label: @new.version.fn1
+            ENDMODULE
+        ENDMODULE

+ Since v1.14.0 the MODULE and ENDMODULE also resets the current "non-local" +label prefix back to "_": +

Kep:    ; "Kep" label (global one), and also works as "non-local" prefix for local labels
+    MODULE zzz
+.local: ; in v1.14.0 this will be "zzz._.local" label, previously it was "zzz.Kep.local"
+Kup:    ; this is "zzz.Kup", but also defines "non-local" prefix as "Kup"
+.local  ; this is "zzz.Kup.local"
+    ENDMODULE
+.local: ; in v1.14.0 this will be "_.local" label, previously it was "Kup.local"

+

+

OPT [push] [reset] [listoff] [liston] [listall] [listact] [listmc] [<command line options>]

+

OPT pop

+

Allows to reset and modify options affecting syntax and + parsing (for lines of source following the OPT). The options allowed for OPT + are: -W[no-]<warning_id>, --syntax, --zxnext, + --reversepop and --dirbol. +

+

+ Ahead of options you can use OPT commands: push, pop, reset, listoff, liston, listall, listact, listmc. + The "push" command will make OPT to preserve current state of options. The "reset" + command will reset OPT-related options to default state. The "listoff" command will + suspend the listing for following lines until "liston" command is used (listing + availability is part of the push/pop state, so to "nest" listing-off you can use + "OPT push listoff : ... code ... : OPT pop" code sequence). The "listall", "listact" + and "listmc" commands will select the filter for listing. The "listmc" lists only + lines containing machine code bytes, "listact" will remove the "skipped" blocks + (like false-blocks of IF/ELSE conditional assembling, or inner definitions of macros + and structures), "listall" will switch back to default listing without filtering. +

+

Then the provided options are applied. The default values are: fake instructions + enabled (no warning), multi-argument delimiter is ",", both () and [] brackets + can be used to access memory, labels can have any name, ZX Next instructions are OFF, + POP with multiple arguments doesn't reverse them and pseudo-ops at beginning of + line are OFF (to just reset to these defaults you can use OPT reset). +

+

The "pop" command: the previously preserved state of options is restored (states + are preserved in "stack" way, so further OPT with "pop" will restore older states). +

+

The id-warning system state is NOT saved/restored + by OPT push/pop/reset and can be modified only by -W option. +

+

Example 5.36. docs_examples/po_opt.asm

+ + +
    POP bc, hl   ; pops BC first
+    OPT push reset --reversepop --syntax=af
+    POP bc,,hl   ; pops HL first
+    LD  bc,hl    ; warning about Fake instruction
+    LD  bc,hl    ; warning supressed by lowercase "fake" in this comment
+    OPT reset --syntax=a
+    POP bc,,hl   ; pop BC first (--reversepop was reset)
+    OPT pop      ; restoring syntax to original state
+

+
ORG <address>[,<page_number>]
+

Set the program counter to a specific address. If the second argument is + provided, it will change memory page in the current slot, see + PAGE and SLOT (it will + warn you when <address> is outside of it).

+

When used inside DISP block, only the virtual + "displaced" program counter is affected, but the machine code will be still + sequentially emitted in the original physical location and warning is emitted. +

Example 5.37. docs_examples/po_org.asm

+ + +
    ORG 100h ; or 0x100, or $100, or #100
+
+    ; useful macro that padding code
+    MACRO PADORG addr
+         ; add padding
+         IF $ < addr
+         BLOCK addr-$
+         ENDIF
+         ORG addr
+    ENDM
+
+    MACRO PADORG2 addr ; z80asm "FORG" replacement
+         ; add padding + display warning
+         IF $ > addr
+           ; no padding
+           DISPLAY /L, "Warning! PADORG failed! ", $, " is more than ", addr
+         ELSE
+           ; add padding
+           BLOCK addr-$
+         ENDIF
+         ORG addr
+    ENDM
+


+
OUTEND
+

Ends generating compiler output to file specified in OUTPUT and resets + SIZE value to default "none".

+
OUTPUT + [<filename>[,<mode>]]
+

With OUTPUT it is possible to create multiple files from + one source. All following instructions will be assembled to this + file. It will also close (OUTEND) + and finalize any previously opened output.

+ +

There are three possible output modes: truncate (overwrite + existing files, this is the default), rewind (open and execute + FPOS 0) and append (open and leave the file pointer at the end + of the file).

OUTPUT <filename>,t  ; truncate (default)
+OUTPUT <filename>,r  ; rewind
+OUTPUT <filename>,a  ; append

+

Example 5.38. bigfile.asm

+ + +
    OUTPUT loader.com
+    ORG 100H
+    INCLUDE loader.asm
+    INCLUDE bios.asm
+
+    OUTPUT bigfile.dat
+    ORG 4000H
+    INCLUDE main.asm
+    ORG 8000H
+    INCLUDE data.asm
+    OUTEND
+    INCLUDE next.asm
+


This will create two files: loader.com and + bigfile.dat.

+ +
PAGE <number>
+

Works only in real device emulation mode. See + DEVICE.

+ +

Set the current memory page to current slot.

+ +

Example 5.39. 

+ + +
    PAGE 7 ;set 7 page
+    SAVEBIN "ram7.bin",$C000,$4000 ;- save $4000 begin from $C000 of RAM to file
+


+
PHASE
+

Synonym of DISP.

+
RELOCATE_END
+

Ends the block of code for which relocation data are produced (started by + RELOCATE_START). To get the relocation + data themselves, use RELOCATE_TABLE and + related symbols.

+

For more details/examples check the examples/relocation, + examples/SymbOS_nslookup and the + relocation tests.

+
RELOCATE_START [HIGH]
+

Marks start of block for which relocation data are produced. Any *regular* label + (not EQU/DEFL symbols) defined inside the RELOCATE_START .. RELOCATE_END block will + be treated as "relocatable" label. Any instruction/directive within the block which + needs resulting opcode adjusted when relocated to different address will produce + relocation-data for the RELOCATE_TABLE. +

+

The RELOCATE_START HIGH variant checks only high-byte relocation. + The produced table points to high byte (MSB) of the machine code. This mode is + for OS/loaders which relocate only to 256-byte aligned boundaries (keeping LSB intact).

+

The relocation feature of regular label is transitive and EQU value based on already + relocatable label will be tagged as relocatable too. Also structures instantiated in + relocatable block have their labels tagged as relocatable and their initial values may + become part of relocation data if the initial value is based on relocatable value.

+

You can prepend operator norel to label to strip it of its relocatable + property for particular expression.

+

The internal heuristic is not bullet-proof, some complex expression may fail + to be recognised as fixed/relocatable value and instead warn about unstable result. + It is recommended to assemble relocatable blocks in lower regions of address space + (like ORG 0 or ORG $1000) and to keep expressions simple enough.

+
RELOCATE_TABLE [<subtract_offset>]
+

Emits relocation data gathered across all relocation blocks. There are also + two built-in symbols relocate_count and relocate_size + containing the size of the table (only word-type tables are supported in sjasmplus, + so size is always doubled count).

+

The table is raw array of WORD (16 bit) values with memory offsets of machine + code which should be adjusted (when the code is relocated to different than original + base address). To get offsets from particular base address, use ORG + ahead of relocation block to land instructions and labels from there.

+

The <subtract_offset> is subtracted from every offset recorded in the relocation table + (same what WinAPE does with its optional argument "base_address" for RELOCATE_TABLE).

+
REPT <count>[, <index_variable>]
+

Synonym of DUP. There is also synonym ENDR to end REPT block (EDUP works too).

+
SAVE3DOS + <filename>,<address>,<size>[,<type>[,<w2_line>[,<w3>]]]
+

Works only in virtual device emulation mode. See + DEVICE.

+ +

Like SAVEBIN, saves the block of device RAM + from "address", but with +3DOS 128 byte header prepended.

Example 5.40. 

+ + +
    DEVICE ZXSPECTRUM48
+    ...
+
+    ; +3 BASIC header (8 bytes starting from offset 15 in +3DOS header):
+    ; type is first byte (0 = basic, 1, 2 = arrays, 3 = code/screen$)
+    ; size is "first word" (bytes 1 and 2)
+    ; w2_line argument is "second word" (bytes 3 and 4)
+    ; w3 argument is "third word" (bytes 5 and 6) (default value = size)
+
+    ; by default the type=3 (CODE/SCREEN$) is saved
+    SAVE3DOS "screen.bin", $4000, $1b00
+    ; type 3: w2 argument $C000 is target load-address
+    SAVE3DOS "code.bin", $8000, $1234, 3, $C000
+    ; type 0: BASIC, default w2 = $8000 (no auto-run)
+    SAVE3DOS "prg1.bas", $5c00, 1234, 0
+    ; type 0: w2_line is LINE auto-run (30 in example)
+    SAVE3DOS "prg2.bas", $5c00, 1234, 0, 30
+    ; type 1: Numeric array (refer to +3 manual for these)
+    SAVE3DOS "b.dat", $8000, 30, 1
+    ; type 2: Character array (or maybe just ignore these)
+    SAVE3DOS "c.dat", $8000, 30, 2
+


+
SAVEAMSDOS + <filename>,<address>,<size>[,<start = 0>[,<type = 2>]]
+

Works only in virtual device emulation mode. See + DEVICE.

+ +

Like SAVEBIN, saves the block of device RAM + from "address", but with AMSDOS 128 byte header prepended.

Example 5.41. 

+ + +
    DEVICE AMSTRADCPC464
+    ... some code ...
+
+    ; by default the type=2 (BINARY) is saved
+    ; "start" (entry) value is by default 0, overriden here to $1234
+    SAVEAMSDOS "code.bin", $1000, $1b00, $1234
+


+
SAVEBIN + <filename>,<startadress>,<lengthofcode>
+

Works only in real device emulation mode. See + DEVICE.

+ +

Save the block of RAM.

+ +

Example 5.42. 

+ + +
    PAGE 7 ;set 7 page to current slot
+    SAVEBIN "ram7.bin",$C000,$4000 ;- save 4000h begin from C000h of RAM to file
+    SAVEBIN "ram2.bin",$8000,$3000 ;- save 3000h begin from 8000h of RAM to file
+


+

SAVECDT + FULL <cdtname>[,<startaddr>[,<screenmode>[,<border>[,<ink0>...<ink15>]]]]

+

SAVECDT EMPTY <cdtname>

+

SAVECDT BASIC <cdtname>,<name>,<start>,<length>

+

SAVECDT CODE <cdtname>,<name>,<start>,<length>[,<customstartaddress>]

+

SAVECDT HEADLESS <cdtname>,<start>,<length>[,<sync>[,<format>]]

+

Works only in Amstrad device emulation mode. See + DEVICE.

+ +

Manipulate CDT file (tape/TZX file format used by Amstrad CPC emulators).

Example 5.43. 

+ + +
    DEVICE AMSTRADCPC6128
+    ...
+    ; create empty CDT file (truncates already existing file to make it empty)
+    SAVECDT EMPTY "output.cdt"
+
+    ; snapshot-like dump with default loader, including loading screen, all banks, ...
+    SAVECDT FULL "output.cdt" ; defaults: END start addres, screenmode=1, border=1
+                              ; ink# = 1, 24, 20, 6, 26, 0, 2, 8, 10, 12, 14, 16, 18, 22, 24, 16
+
+    ; BASIC program prepared at address "label"
+    SAVECDT BASIC "output.cdt","A",label,length
+
+    ; block of binary data at address "label" (default "entry" = "label")
+    SAVECDT CODE "output.cdt","C",label,length,entry
+
+    ; headless block with custom sync and format at address "label", formats: 0 = AMSTRAD, 1 = ZX
+    SAVECDT HEADLESS "output.cdt",label,length,sync,format ; default sync = 0x16, format = 0
+


+
SAVECPCSNA <filename>[,<startadressofprogram>]
+

Works only in AMSTRADCPC464 / AMSTRADCPC6128 real device emulation mode. See + DEVICE.

+ +

Save the snapshot for emulators of Amstrad CPC machines. (If start address is omitted, + the one provided by END is used)

+ +

The snapshot header content is hard-wired in the sjasmplus binary, with the CRTC, Palette and Gate Array registers + set to those of a CPC after a boot into the main ROM. Similarly to the ZX-Spectrum SAVESNA directive, interrupts are disabled and must + be re-enabled by your program. A full list of the settings can be found in the + source. + If you absolutely need different defaults and can't afford init-code modifying the state, you can post-patch the resulting SNA + file directly from source, see Issue #139 for example. +

Example 5.44. 

+ + +
    DEVICE AMSTRADCPC464
+    ORG $1200
+START  .... ;some code
+    RET
+    SAVECPCSNA "game.sna",START ;save snapshot to file game.sna. Start address is START ($1200)
+


+
SAVEDEV + <filename>,<startPage>,<startOffset>,<length>
+

Works only in real device emulation mode. See + DEVICE.

+ +

Like SAVEBIN, saves the block of device RAM.

+ +

But it allows lengths over 64ki, and the offset value goes directly into device + virtual memory (where pages are allocated consecutively), ignoring current slot + "mapping". I.e. page=2,offset=0 will start saving data from page 2 at its beginning, + going through pages 3, 4, 5, ... until the requested length of data is saved.

+ +

The offset is not limited to page size, i.e. arguments page=1,offset=0x500 are equal + to arguments page=0,offset=0x4500 for ZXSPECTRUM128 device (has page size 0x4000).

+ +

Example 5.45. 

+ + +
    DEVICE ZXSPECTRUM128 : SAVEDEV "fullram.bin",0,0,0x20000 ; save full 128kiB
+

+
SAVEHOB + <filename>,<filename_in_trdos>,<startadress>,<lengthofcode>
+

Works only in real device emulation mode. See + DEVICE.

+ +

Save the block of RAM in Hobeta format.

+ +

Example 5.46. 

+ + +
    PAGE 7 ;set 7 page to current slot
+    SAVEHOB "ram7.$c","myfile1.C",$C000,$4000 ;- save 4000h begin from C000h of RAM to file
+    SAVEHOB "ram2.$c","myfile2.C",$8000,$3000 ;- save 3000h begin from 8000h of RAM to file
+


+
SAVENEX <command> <command arguments>
+

Commands to build NEX file, for details check SAVENEX + chapter.

+ +

Works only in ZXSPECTRUMNEXT device emulation mode. See + DEVICE.

+
SAVESNA <filename>[,<startadressofprogram>]
+

Works only in real device emulation mode. See + DEVICE.

+ +

Save the snapshot for emulators of ZX-Spectrum. (If start address is omitted, + the one provided by END is used)

+ +

The snapshot header content is hard-wired in sjasmplus binary (disabled interrupts may surprise some users, you can check + the source to see all the values). + If you absolutely need different defaults and can't afford init-code modifying the state, you can post-patch the resulting SNA + file directly from source, see Issue #139 for example. +

Example 5.47. 

+ + +
    DEVICE ZXSPECTRUM128
+    ORG $8000
+START  .... ;something code
+    RET
+    SAVESNA "game.sna",START ;save snapshot to file game.sna. Start address is START ($8000)
+


+

SAVETAP <filename>,BASIC,<fileintapeheader>,<start>,<length>[,<autorunline>[,<lengthwithoutvars>]]

+

SAVETAP <filename>,CODE,<fileintapeheader>,<start>,<length>[,<customstartaddress>[,<optional3rdparam>]]

+

SAVETAP <filename>,NUMBERS,<fileintapeheader>,<start>,<length>[,<variableletter(A..Z)>]

+

SAVETAP <filename>,CHARS,<fileintapeheader>,<start>,<length>[,<variableletter(A..Z)>]

+

SAVETAP <filename>,HEADLESS,<start>,<length>[,<customblockflag(0..255)>]

+

Works only in real device emulation mode. See + DEVICE.

+ +

Append the tape header or block of data to the end of the + standard tape file for emulators of ZX-Spectrum.

Example 5.48. 

+ + +
    DEVICE ZXSPECTRUM48
+    ...
+    EMPTYTAP "output.tap"
+
+    SAVETAP "output.tap",BASIC,"noAutorun",label,100
+    SAVETAP "output.tap",BASIC,"w/Autorun",label,100,9999
+    SAVETAP "output.tap",BASIC,"withVars",label,123,9999,100
+    SAVETAP "output.tap",CODE,"bank17",screen,6912
+    SAVETAP "output.tap",CODE,"screen",demo,length,org
+    SAVETAP "output.tap",NUMBERS,"dimArray",label,100      ; a()
+    SAVETAP "output.tap",NUMBERS,"othernum",label,200,'b'  ; b()
+    SAVETAP "output.tap",CHARS,"charArray",label,300       ; a$()
+    SAVETAP "output.tap",CHARS,"nextone",label,400,'m'     ; m$()
+    SAVETAP "output.tap",HEADLESS,start,length
+


+
SAVETAP <filename>[,<startadressofprogram>]
+

Works only in real device emulation mode. See + DEVICE.

+ +

Save the tape file for emulators of ZX-Spectrum as a + "snapshot" of almost-whole memory. Generated tape file supports the + ZX-Spectrum clones with extended RAM such as ATM Turbo 512, etc. + (If start address is omitted, the one provided by END is used)

+ +

The stored memory starts at $5E00 (with screen data at $4000 auto-detected + and optionally stored into the TAP file). The stored memory is automatically + trimmed of zero values at start and end of the region (put non-zero byte markers + around region you want to store, if you have zero-ed bytes at beginning or end + of your code). + +

Example 5.49. 

+ + +
    DEVICE ZXSPECTRUM48
+    ORG $7FFF
+    DB $01      ; non-zero marker to store the following zero-ed data
+DAT DS 1024, 0  ; zero-ed data at $8000 (1024 bytes)
+START  ....     ; some code
+    RET
+    SAVETAP "game.tap", START ; save tape-snapshot to file game.tap. Start address is $8400
+


+
SAVETRD <filename_of_trd_image>,<filename_in_trdos>,<address>,<length>[,<autostart_BASIC_line>[,<length_minus_variables>]]
SAVETRD <filename_of_trd_image>,|<filename_in_trdos>,<address>,<length>[,<autostart_BASIC_line>]
SAVETRD <filename_of_trd_image>,&<filename_in_trdos>,<address>,<length>
+

Works only in real device emulation mode. See + DEVICE.

+ +

Save the device memory into TRD disk image. Saving two files with same filename will + emit warning, but two files (with same name) will be created in the disk directory.

+ +

Adding pipe character "|" ahead of file + name will make sjasmplus to delete old file(s) with the same name before writing + the new one => replace-like functionality. If the deleted file did occupy all + sectors till the free space position in disc info, sjasmplus will salvage those sectors + back and save new file over them (but it will not do full reshuffle/defrag in more + complex cases, sjasmplus is just assembler, not full featured TRD images manipulation tool).

+ +

Adding ampersand character "&" ahead of file name will make sjasmplus to look + for existing file with the requested name (last of them, any earlier duplicates are deleted). + The new content is appended to the file (sector aligned append) and the catalog entry gets + only number of sectors patched, up to 255 sectors at most. This is special mode for + single-file big-loaders.

+ +

The unofficial three-letter extensions are also supported (official extensions + are only: B, C, D and #). + +

Example 5.50. 

+
    EMPTYTRD "test.trd" ;create empty TRD image
+    PAGE 7 ;set 7 page to current slot
+    SAVETRD "test.trd","myfile1.C",$C000,$4000 ;- save 4000h begin from C000h of RAM to file to TRD image
+    SAVETRD "test.trd","myfile2.C",$8000,$3000 ;- save 3000h begin from 8000h of RAM to file to TRD image
+    SAVETRD "test.trd",|"myfile1.C",$B000,$400 ;- replace "myfile1.C" with new file
+    SAVETRD "test.trd",&"myfile1.C",$9000,$734 ;- sector-append new data to "myfile1.C"
+


+

+
SETBP [<expression>]
SETBREAKPOINT [<expression>]
+

+ Works only in real device emulation mode. See + DEVICE. +

+

Will add execution-type breakpoint at address <expression> to the + BPLIST export file. If no expression is specified, + the current program counter will be used. +

Example 5.51. 

+ + +
    BPLIST "cmd_line.options.txt" zesarux ; open breakpoints list in "ZEsarUX" format
+start:
+        nop     ; ZEsarUX will not catch very first instruction of snapshot file loaded
+        SETBP   ; so breakpoint ahead of the second instruction (after first "nop")
+        xor  a
+        SETBP some_routine_label  ; set also some breakpoint to other code by its label
+        SETBREAKPOINT $0D6B       ; alias of SETBP, works identically
+        ; ... your code of app
+					  
+


+

+
SHELLEXEC <filename>[,<parameters>]
+

Execute external program <filename> using optional + command line <parameters>.

Example 5.52. 

+ + +
    OUTPUT "mybin.bin"
+    ;some code
+    IF ((_ERRORS = 0) && (_WARNINGS = 0))
+        SHELLEXEC "x:/somepath/bin2tap.exe mybin.bin mytap.tap"
+       ; or SHELLEXEC "x:/somepath/bin2tap.exe","mybin.bin mytap.tap"
+    ENDIF
+


+
SIZE <filesize in bytes>
+

If the resulting file is less than the given length, as + many zero bytes are added as necessary. See OUTPUT for more.

Example 5.53. 

+ + +
    SIZE 32768       ; make sure file will be 32K
+


+
SLDOPT <type> <arguments>
+

Type `COMMENT` - add comma delimited list of case sensitive keywords + to be detected in end-of-line comments and exported to + Source Level Debugging (SLD) data. + The particular keywords depend on the debugger you are using to process SLD data.

+ +

Example 5.54. 

+ + +
    DEVICE ZXSPECTRUM128
+    SLDOPT COMMENT WPMEM, Log
+    ORG $8000
+        call init ; Log("calling init") - gets exported to SLD
+        ret       ; no keyword here - not exported to SLD
+var1    DB 1      ; WPMEM - exported
+var2    DB 2      ; WpMem - not exported, case sensitive!
+


+
SLOT <number_or_address>
+

Works only in real device emulation mode. See + DEVICE.

+ +

Set current slot. Slots are defined by DEVICE pseudo-op. + Use pseudo-op PAGE to change page + in the current slot.

+ +

(since v1.18.1) You can also use starting address of particular slot instead + of its number, ie. for ZX128: SLOT $8000 is alias of SLOT 2

+ +

Example 5.55. 

+ + +
    DEVICE ZXSPECTRUM128
+    SLOT 3 ;from 0C000h to 0FFFFh
+    PAGE 1 ;set page 1 to slot 3
+    ORG 0C000h
+    ;your program here
+    PAGE 2
+    INCBIN "somegfx.bin"
+    ;....
+


+
TAPEND
+

Ends generating compiler output to tape file block specified in TAPOUT.

+
TAPOUT <filename>[,<flagbyte>]
+

Appends one tape block at the end of specified file. + All following code will be assembled to this tape file block.

+ +

Default value of flagbyte is 255.

Example 5.56. bigfile.asm

+ + +
    EMPTYTAP screen.tap
+
+    TAPOUT screen.tap,0
+    DB 3
+    DB 'ScreenName'
+    DW 6912
+    DW 16384
+    DW 32768
+    TAPEND
+
+    TAPOUT screen.tap
+    INCBIN screen.bin
+    TAPEND
+


This will create tap file with the screen.

+
TEXTAREA <address>
+

Synonym of DISP.

+
UNDEFINE <id>
+

Removes the identifier defined by DEFINE

+ +

Example 5.57. 

+ + +
    DEFINE Release 1
+
+    IFDEF Release
+      DISPLAY "Building release version"
+    ENDIF
+
+    UNDEFINE Release
+
+    IFNDEF Release
+      DISPLAY "It's works!"
+    ENDIF
+
+    IFDEF _SJASMPLUS
+      DISPLAY "Yes, it's the sjasmplus!"
+    ENDIF
+
+    UNDEFINE *  ; undefine all identifiers
+
+    IFNDEF _SJASMPLUS
+      DISPLAY "It's not the sjasmplus??"
+    ENDIF
+


+
UNPHASE
+

Synonym of ENT.

+
WHILE <expression>[, <guardian-counter>]
+

While expression evaluates to non-zero value, generates following lines until + an ENDW (EDUP/ENDR alias) pseudo-op is encountered.

+

The optional guardian-counter value will abort the WHILE if there are + more iterations, default guardian-counter is set to 100k. +

Example 5.58. 

+ + +
ptr = $4000
+    WHILE ptr < $4020, 500 ; max 500 loops before error
+        DB low ptr
+ptr = ptr + 1
+    ENDW    ; EDUP or ENDR works too, but ENDW is advised for WHILE
+
+/* will emit bytes $00, $01, ..., $1F */
+


+

+
WORD <words>
+

Defines a word. Values should be between -32787 and + 65536.

Example 5.59. 

+ + +
    WORD 4000h,0d000h
+    WORD 4,"HA"
+


+
+
+ +

Conditional assembly

+ + +

It may be useful to assemble a part (or not) based on a certain + condition.

+ +
IF <expression>
+

If <expression> is non-zero the following lines are + assembled until an ELSE, ELSEIF or ENDIF.

+ +

Forward reference of label will cause warning - as any machine code emitted inside + the IF/IFN block with such expression can lead to unstable results. If you are sure + the late definition of label will not affect resulting machine code (the IF block + does not emit any machine code and does not define any label), you can suppress the + warning.

+ +

Example 5.60. 

+ + +
    IF (aaa == 0) || (2 = aaa && ((bbb % 13) & 0x01))
+        ; some code to assemble only if the symbol `aaa` is zero
+        ; or (logical OR) when symbol `aaa` is two
+        ; and (logical AND) modulo 13 of `bbb` is odd number
+    ENDIF

+

+

+
IFN <expression>
+

If <expression> is zero the following lines are + assembled until an ELSE, ELSEIF or ENDIF.

+
IFDEF <id>
+

The condition is true if there is an id defined. These are + NOT labels.

+ +

Example 5.61. 

+ + +
    IFDEF MSX_LEAN_AND_MEAN
+        CALL InitOwnMM
+    ELSE
+        CALL InitDos2MemMan
+    ENDIF
+


+
IFNDEF <id>
+

The condition is true if there isn't an id defined. These + are NOT labels.

+ +

Example 5.62. 

+ + +
1   IN A,(0C4H)
+    AND 2
+    IFNDEF DEBUG
+        JR NC,1B
+    ENDIF
+


+
IFUSED <label>
+

The condition is true if there is an label used somewhere + in the code. You can create libraries of useful functions using + IFUSED pseudo-op

+ +

Example 5.63. (similar to tests/misc/ifused_test.asm)

+ + +
    OUTPUT "TEST.OUT"
+
+    CALL LABEL3 ; LABEL3 - yes
+    LD A,(LABEL1) ; LABEL1 - yes
+
+    IFUSED LABEL1
+LABEL1:
+    DB 1
+    ENDIF
+
+    IFUSED LABEL2
+LABEL2:
+    DB 2
+    ENDIF
+
+    IFUSED LABEL3
+LABEL3:
+    DB 3
+    ENDIF
+
+    IFUSED LABEL4
+LABEL4:
+    DB 4
+    ENDIF
+
+    LD A,LABEL2 ; LABEL2 - yes
+
+    RET
+
+; Output will contain bytes from LABEL1 to LABEL3 (1, 2, 3), but not contain from LABEL4, because this label is not used.
+
+; Alternative syntax:
+LABEL5:
+    IFUSED ; sjasmplus will use name of previous label, i.e. LABEL5
+
+    ENDIF
+    
+


+

Known bug: when code is using label inside module "moduleX", + like call labelY, only usage of moduleX.labelY label is noted. + Then if you define "labelY" outside of module and hide it inside IFUSED labelY + block, the call from module will be unable to find the routine. +

+

+ Workaround: you can use the global-label operator @: "call @labelY" to + trigger usage of the global "labelY", or you can use the alternative IFUSED syntax + "labelY: IFUSED" which does not only check condition, but also does define the label. + Once the label is defined, the "call labelY" line inside module will find the global + variant and mark it as "used" correctly. +

+

+ Local labels inside the IFUSED block are not visible until the main label is used, if you want to call only + local label, you can force the visibility of the routine by "ASSERT main_label" - this will + trigger usage of the global "main_label", and then the IFUSED block will be assembled, and local labels + will become visible too. +

+
IFNUSED <label>
+

The condition is true if there is an label + not used somewhere in the code.

+
ELSE
+

See IF. If the IF condition is + false (in all previous IF/ELSEIF blocks), the else-part is assembled.

+
ELSEIF <expression>
+

If the previous IF-block had false condition, the ELSEIF expression is evaluated + and the ELSEIF acts as new IF.

+
ENDIF
+

Every IF should be followed + by an ENDIF.

+
+
+ +

Macros

+ + +

The MACRO pseudo-op defines a macro. It should be followed by the + name of the macro, optionally followed by the parameters. The following + lines will be stored as the macro-body until an ENDM pseudo-op is + encountered. Macro's have to be defined before their use. Macro's name + must be unique (even if two macros differ by required arguments). +

Example 5.64. Macro without parameters (docs_examples/s_macros.asm)

+ + +
  MACRO ADD_HL_A
+    ADD A,L
+    JR NC,.hup
+    INC H
+.hup
+    LD L,A
+  ENDM
+


+ +

Labels in a macro starting with a dot are local to each macro + expansion.

Example 5.65. A macro with parameters (docs_examples/s_macros.asm)

+ + +
  MACRO WAVEOUT reg, data
+    LD A,reg
+    OUT (7EH),A
+    LD A,data
+    OUT (7FH),A
+  ENDM
+; this macro will make
+  WAVEOUT 2,17
+; expand to:
+  LD A,2
+  OUT (7EH),A
+  LD A,17
+  OUT (7FH),A
+


+

Example 5.66. Another example (docs_examples/s_macros.asm)

+ + +
    MACRO LOOP
+      IF $-.lus<127
+        DJNZ .lus
+      ELSE
+        DEC B
+        JP NZ,.lus
+      ENDIF
+    ENDM
+
+Main
+.lus
+    CALL DoALot
+    LOOP
+; This will expand to:
+Main
+.lus                  ; Main.lus
+    CALL DoALot
+    DJNZ .lus         ; Main.lus
+


+ +

Angle brackets can be used when the arguments contain commas. +

Example 5.67. Argument in angle brackets (docs_examples/s_macros.asm)

+ + +
    MACRO UseLess data
+      DB data
+    ENDM
+
+    UseLess <10,12,13,0>
+; expands to:
+    DB 10,12,13,0
+
+; use '!' to include '!' and '>' in those strings.
+
+    UseLess <5, 6 !> 3>
+; expands to:
+    DB 5, 6 > 3
+
+    UseLess <"Kip!!",3>
+; expands to:
+    DB "Kip!",3
+


+ +

As compatibility convenience to make porting from different assemblers somewhat + easier, there is alternative syntax, where the macro name is written at beginning + of line (as if label, but MODULE part is NOT applied to macro name). +

Example 5.68. Macro name at beginning of line (docs_examples/s_macros.asm)

+
LabelAsMacroName    MACRO  arg1?, arg2?
+                        ld  a,arg1?
+                        ld  hl,arg2?
+                    ENDM
+
+                LabelAsMacroName 1,$1234
+            ; expands to:
+                ld a,1 : ld hl,$1234
+


+

+ +

+ If some macro over-shadows regular instruction or directive name, the @ + character in front of instruction/directive name can be used to inhibit macro expansion. +

Example 5.69. Inhibit macro expansion operator (docs_examples/s_macros.asm)

+
djnz    MACRO   arg1?
+            dec c
+            jr  nz,arg1?
+            @djnz arg1? ; avoid self-reference and use real instruction
+        ENDM
+
+1:      djnz    1B      ; macro replacement will be used here
+1:      @djnz   1B      ; original djnz instruction here
+


+

+ +
+
+ +

Chapter 6. Structures

+ + +

What is it?

+ + +

Structures can be used to define data structures in memory more + easily. The name of the structure is set to the total size of the + structure.

+
+ +

Defining structure

+ + +

A structure definition starts with: STRUCT + <name>[,<initial offset>] and ends with + ENDS. Structure definitions are local to the current + module, but, as with labels, '@' can be used to override this.

+ +

Lines between STRUCT and ENDS should have the following + format:

+ +

membername pseudo-operation operands

+ +

All fields are optional. Lines without label should start with + whitespace.

+ +

When non zero offset is used, it acts as if + BLOCK with length equal to + offset was defined as first member of structure.

+
+ +

STRUCT instructions

+ + +

Between the STRUCT and ENDS pseudo-instructions the following + instructions can be used:

+ +
BYTE [<defaultvalue>]
+

To define a one byte member. The defaultvalue is used when + no initialisation value is given when the structure is declared. + (DB and DEFB may be used instead of BYTE).

+
WORD [<defaultvalue>]
+

To define a two byte member. The defaultvalue is used when + no initialisation value is given when the structure is declared. + (DW and DEFW may be used instead of WORD).

+
D24 [<defaultvalue>]
+

To define a three byte member. The defaultvalue is used + when no initialisation value is given when the structure is + declared.

+
DWORD [<defaultvalue>]
+

To define a four byte member. The defaultvalue is used + when no initialisation value is given when the structure is + declared. (DD and DEFD may be used instead of DWORD).

+
TEXT <length>[,{<DB-like operand(s)>}]
+

(since v1.17) To define a member of the specified length of bytes. + The operands are DB-like formatted, and the last + byte explicitly defined will fill the remaining bytes reserved upon definition + (zeroed when no value is specified at all).

+

The default value can be modified with init values when structure is used, but + then only the given bytes overwrite default content, NOT filling remainder of it.

+

While the operands are DB-like formatted, they must be enclosed in curly braces. + Also make sure the braces are not mistaken with the optional braces on the sub-structure + boundaries, when using TEXT member in sub-structure, you may need to use curly braces + for the sub-structure itself around of curly braces used for TEXT values (making the + sub-structure braces mandatory in such case). The length can be from 1 to 8192 bytes + (if you need even longer "text", use two members and split your arguments).

+
BLOCK <length>[,<fillbyte>]
+

To define a member of the specified number of bytes. Arguments are set + when defining the current structure, and are not part of init values when + the structure is later used. + ('#', DS and DEFS may be used instead of BLOCK).

+ +

(since v1.11) If fillbyte is omitted, the device memory + content in the block area is preserved (not zeroed).

+
ALIGN [<expression>[, <byte>]]
+

To align the offset. If the expression + is omitted, 4 is assumed. ('##' May be used instead of ALIGN).

+ +

(since v1.11) If the byte is omitted, device memory content is preserved (not zeroed).

+
<structure name> [<init values>]
+

It is possible to nest structures, and give new defaults + for the BYTE and WORD members.

+
+
+ +

Usage of defined structure

+ + +

[<label>] <struct_name> [<initial values>] will emit full + structure into machine code, either using default values from structure definition, + or overriding them with explicit value from the <initial values> list. In initial + values you can use curly brackets {} to group particular initial values for particular + sub-structure, any missing values in particular sub-structure init-list are set up by + default values of particular field. See "SDOT" example below or tests/struct asm files + for more examples.

+ +

<label> <struct_name> = <expression> will only set up + <label>.<struct_field> labels starting from designed address provided by + expression, but there will be no machine code emitted (and current address "$" will not + advance).

+ +

(since v1.17) The initial values inside curly braces block can now span over multiple + lines with new line ending current value expression (the comma after expression is optional + in such case). Please bear in mind this is pushing original sjasmplus architecture beyond + its original design (multi-line definitions) and may fail in complex scenarios (for + example the "dot-repeater" macro mechanics will repeat only the remainder of current + line, ignoring the curly-braces block spanning across following lines). If you have + example of code which fails but you believe it's reasonable use case, please open the + issue on github with such example, so it is possible to improve the feature in future + (the dot-repeater is "won't fix" case, but otherwise the feature should work reliably). +

+ +
+ +

Examples

+ + +

Example 6.1. docs_examples/c_structures.asm

+ + +
	STRUCT SCOLOR
+RED	BYTE 4
+GREEN	BYTE 5
+BLUE	BYTE 6
+	ENDS
+ +

This is identical to:

+ +
SCOLOR		EQU 3 ; length
+SCOLOR.RED	EQU 0 ; offset
+SCOLOR.GREEN	EQU 1 ; offset
+SCOLOR.BLUE	EQU 2 ; offset
+

+

Example 6.2. docs_examples/c_structures.asm

+ + +
	STRUCT SDOT
+X	BYTE
+Y	BYTE
+C	SCOLOR 0,0,0 ; use new default values
+	ENDS
+
+ +

This is identical to:

+ +
SDOT		EQU 5 ; length
+SDOT.X		EQU 0 ; offset
+SDOT.Y		EQU 1 ; offset
+SDOT.C		EQU 2 ; offset
+SDOT.C.RED	EQU 2 ; offset
+SDOT.C.GREEN	EQU 3 ; offset
+SDOT.C.BLUE	EQU 4 ; offset
+
+

+

Example 6.3. docs_examples/c_structures.asm

+ + +
	STRUCT SPOS,4
+X	WORD
+Y	BYTE
+	ALIGN 2
+AD	WORD
+	ENDS
+ +

This is identical to:

+ +
SPOS	EQU 10 ; length
+SPOS.X	EQU  4 ; offset
+SPOS.Y	EQU  6 ; offset
+SPOS.AD	EQU  8 ; offset
+

+

Example 6.4. docs_examples/c_structures.asm

+ + +

When a structure is defined it is possible to declare labels + with it

COLOR SCOLOR

This is + identical to:

COLOR
+COLOR.RED   BYTE 4
+COLOR.GREEN BYTE 5
+COLOR.BLUE  BYTE 6
+

Note the default values.

+ +

Or without label:

COLORTABLE
+  SCOLOR 0,0,0
+  SCOLOR 1,2,3
+  SCOLOR ,2
+  ; etc.

This is identical to:

COLORTABLE
+  BYTE 0,0,0
+  BYTE 1,2,3
+  BYTE 4,2,6
+  ; etc.

+ +

DOT1 SDOT 0,0, 0,0,0     ; or 0,0,0,0,0 or {0,0,{0,0,0}}

Only + BYTE and WORD members can be initialised.

+ +

The resulting labels can be used as any other + label:

  ld b,(ix+SCOLOR.RED)
+  ld a,(COLOR.GREEN)
+  ld de,COLOR
+  ; etc.
+ +

+

Example 6.5. docs_examples/st_usage_example.asm

+ + +
	STRUCT BIN_FILE_MAP, 256
+value1	BYTE
+value2	WORD
+	ENDS
+
+        ORG  0x8000
+binData BIN_FILE_MAP = $        ; set up label values only (no bytes)
+        INCBIN "some_data.bin"  ; load the bytes from file instead
+
+        ; using the data through struct definition
+        ld  a,(binData.value1)
+        ld  hl,(binData.value2)
+ +

This is identical to:

+ +
BIN_FILE_MAP            EQU 259      ; length
+BIN_FILE_MAP.value1     EQU 256      ; offset
+BIN_FILE_MAP.value2     EQU 257      ; offset
+; labels to access binary data loaded by INCBIN
+binData                 EQU 0x8000   ; address
+binData.value1          EQU 0x8100   ; address
+binData.value2          EQU 0x8101   ; address
+

+ +

Example 6.6. docs_examples/st_text.asm

+ + +
        STRUCT BLOCK_HEADER
+length      WORD    BLOCK_HEADER
+type        BYTE    $AB
+name        TEXT    10, { "none", 32, '!' } ; will produce "none !!!!!" default data
+                    ; because this is definition of the field, here last byte is "filler"
+datastart   WORD
+datalen     WORD
+checksum    BYTE    $CC
+        ENDS
+
+        ORG  0x8000
+head1   BLOCK_HEADER {      ; Multi-line initialization requires curly braces.
+    , ,                     ; Keeping default length and type by specifying empty values.
+    { 'New',                ; The final `name` data will be "New Name!!"
+        32,                 ; overwriting only 8 bytes of default data.
+        "Name" },           ; The last "e" is NOT "filler" in the non-default value.
+        $8000, $1234        ; Explicit datastart and datalen values.
+}                           ; End of initial data block ("checksum" keeps default value).
+
+; machine code (struct data):
+; 12 00 AB 4E 65 77 20 4E 61 6D 65 21 21 00 80 34 12 CC
+; = { $0012, $AB, "New Name!!", $8000, $1234, $CC }
+

+ +

Warning

+

Do not use the offset labels in indirections + like:

LD A,(SDOT.X)

This will + conflict with futher 'improvements' ;-)

+ +

If this is absolutely necessary (why?) use something like + this:

LD A,(+SDOT.X)
+
+
+
+ +

Chapter 7. Lua scripting

+ + +

Why?

+ + +

Why is scripting engine + as Lua embedded to the assembler? Answer is simple: it can be used to add extra + features by users. And Lua is small enough, fast and powerful scripting engine.

+ +

Sjasmplus currently includes Lua 5.4.6 (since v1.20.3). The upgrade from Lua 5.1 in v1.20.0 + was done along replacing tolua++ with LuaBridge2.6 and while there was lot of effort + to maintain original Lua bindings to sjasmplus, there were few changes to the API.

+ +

If your old source does not work any more after upgrade, please carefully check + against the bindings documentation below.

+ +

Check if your code does use only official API, and if you were using secret extra + arguments, check the test files and examples how those lua scripts were adjusted to + build with v1.20.0. The bindings API documentation has been cleaned up, some API + extended and has better test-coverage to make it less likely to change in the future.

+ +

Also please mind the breaking changes of the Lua itself, and adjust your scripts + to Lua 5.4 syntax and library functions, there is example below with common issues + already encountered (open github issues with others so we can add them to docs).

+
+ +

How to use?

+ + +

You must use LUA and ENDLUA pseudo-ops.

Example 7.1. Hello World!

+ + +
    LUA
+        print ("Hello World!")
+    ENDLUA
+


+
+ +

SjASMPlus functions bindings to Lua

+ + +

From Lua you can control some variables and use functions of the + compiler. Complete list:

+ +
[integer] _c("expression")
+

Calculate expression using calculator of the compiler (32-bit only). + Example: val = _c("SOMELABEL+12")

+
[void] _pc("code")
+

Parse string of Z80 assembly. Example: _pc("ADD A,B")

+
[void] _pl("label code")
+

Parse line of Z80 assembly. Example: _pl("SOMELABEL ADD A,B")

+ +

This binding makes almost all pseudo-instructions (directives) available from Lua script + with all options and features. In the future the bindings API will be mostly frozen, extended + only in case where using _pl(...) is too cumbersome.

+
[integer] sj.calc("expression")
+

See _c

+
[void] sj.parse_code("code")
+

See _pc

+
[void] sj.parse_line("label code")
+

See _pl

+
[void] sj.error("message","bad_value" = nil)
+

Print error message.

+
[void] sj.warning("message","bad_value" = nil)
+

Print warning message.

+
[boolean] sj.file_exists("filename")
+

Check for file exists.

+
[string] sj.get_define("name", "include_macro_args" = false)
+

Get define value, returns nil if define is not found. Can optionally search also macro arguments (with higher priority).

+
[boolean] sj.insert_define("id", "value" = "")
+

Add new define. Returns true if the define is new, false when define already existed and value was replaced or when id is invalid.

+
[integer] sj.get_label("name")
+

Get label address. Returns -1 if name is invalid, 0 when not found or has still undefined value (you can use also _c("name") to get symbol value and do some calculations with it).

+
[boolean] sj.insert_label("name", address)
+

Add new label. Returns true when label was successfully inserted (or value modified first time in next assembling pass), false when name is invalid or label was already defined.

+
[integer] sj.current_address
+

Read-only variable. Current address.

+
[integer] sj.error_count
+

Read-only variable. Count of Errors.

+
[integer] sj.warning_count
+

Read-only variable. Count of Warnings.

+
[void] sj.exit(errorcode = 1)
+

Shutdown the compiler.

+
[void] sj.add_byte(byte)
+

Add byte to output (or to memory) and increase sj.current_address

+
[void] sj.add_word(word)
+

Add word to output (or to memory) and twice increase sj.current_address

+
[integer] sj.get_byte(address)
+

Get byte from memory. Works only in real device + emulation mode and only in last assembling pass.

+
[integer] sj.get_word(address)
+

Get word from memory. Works only in real device + emulation mode and only in last assembling pass.

+
[string] sj.get_device()
+

Return current emulating device's identifier. Returns + "NONE" if no emulation mode.

+
[boolean] sj.set_device("id" = "NONE", ramtop = 0)
+

Set current emulating device's identifier. Returns false + if requested device id is not found and current device set is lost ("NONE" is set).

+
[boolean] sj.set_page(number)
+

Set page "number" to the current slot. Works as pseudo-op PAGE.

+
[boolean] sj.set_slot(number_or_address)
+

Set current slot to "number". Works as pseudo-op SLOT.

+

(since v1.20.0) You can also use starting address of slot instead, ie. for ZX128 + sj.set_slot(0x8000) is alias of sj.set_slot(2).

+
[void] sj.shellexec("programname")
+

See pseudo-op SHELLEXEC.

+
[boolean] zx.trdimage_create("trd_name", "label" = "")
+

Creates empty TRD image file.

+
[boolean] zx.trdimage_add_file("trd_name", "somenameC", + startaddress, length, autostart = -1, replace = false)
+

Save block of memory to TRD image file. Work + only in real device emulation mode.

+
[boolean] zx.save_snapshot_sna("filename.sna", + startaddressofprogram)
+

Save snapshot of memory in SNA format. Works only + in real device emulation mode and only for ZXSPECTRUM48 and + ZXSPECTRUM128..

+
+
+ +

Third-party embedded library(ies)

+ + +

None at this moment.

+ +

Since v1.20.0 the sjasmplus integrates Lua 5.4.4 (now 5.4.6).

+ +

The libraries included in older versions of sjasmplus: lpack.c, bit (bitwise operators) + and hex (convert to/from hexa numbers) are now all part of the standard Lua 5.4.

+ +

Refer to Lua documentation + for full list of functions and detailed help.

+ +

For small example check the test tests/lua_examples/trivial/lua_standard_libs.asm

+ +
+ +

Example

+ + +

+ +

Example 7.2. Variables doesn't clear in new passes of the compiler

+ + +
    LUA PASS1
+       v = 1
+    ENDLUA
+
+    LUA PASS2
+       print (v)
+-- out to console: 1
+       v = v + 1
+    ENDLUA
+
+    LUA PASS3
+       print (v)
+-- out to console: 2
+    ENDLUA
+


Example 7.3. To generate some code you need to generate it in all + passes

+ + +
    LUA ALLPASS
+        _pl("ClearScreen LD (.savesp+1),SP")
+        _pc("LD SP,16384+6144")
+        _pc("LD HL,0")
+        for i = 32768, 38912, 2 do
+            _pc("PUSH HL")
+        end
+        _pl(".savesp: LD SP,0")
+        _pc("RET")
+    ENDLUA
+
+    LUA PASS2 ; if you fully understand what you are doing
+        sj.add_byte(123) -- and you need emit bytes in other mode
+    ENDLUA ; luamc-ok ; you can suppress warning here or at start of block
+


Example 7.4. Declare function and use it

+ + +
     LUA
+         function savetape_mytype(filename, startaddress)
+             local fp
+             fp = assert(io.open(filename, "wb"))
+             for i = 16384, 32767, 4 do
+                 assert(fp:write( string.pack("bbbb",
+                                sj.get_byte(i),
+                                sj.get_byte(i+1),
+                                sj.get_byte(i+2),
+                                sj.get_byte(i+3)) ))
+             end
+             assert(fp:flush())
+             assert(fp:close())
+         end
+     ENDLUA
+
+ ;somewhere in your program
+     LUA
+         savetape_mytype("tapefiles/myprogram.tape", _c("StartGameLabel"))
+     ENDLUA
+


Example 7.5. Simple sample :)

+ + +
	LUA
+-- Function reads number from file <fname>, increases it, creates define "BUILD" with the number and saves the number to <fname>.
+-- With this function you can control count of compilations.
+	function increase_build(fname)
+		local fp
+		local build
+		fp = assert(io.open(fname, "rb"))
+		build = tonumber(fp:read("*all"))
+		assert(fp:close())
+		if type(build) == "nil" then
+		    build = 0
+		end
+		build = build + 1;
+		sj.insert_define("BUILD", build)
+		fp = assert(io.open(fname, "wb"))
+		assert(fp:write( build ))
+		assert(fp:flush())
+		assert(fp:close())
+	end
+
+-- Before using you must create empty file "build.txt"!
+	increase_build("build.txt")
+
+-- Creates define "TIME" with current time
+	sj.insert_define("TIME", '"' .. os.date("%Y-%m-%d %H:%M:%S") .. '"')
+	ENDLUA
+
+; print to console our time and build number
+	DISPLAY "Build time: ", TIME
+	DISPLAY "Build number: ", /D, BUILD
+


+ +

Example 7.6. Common issues with upgrade to Lua 5.4

+ + +
  LUA ALLPASS
+    x = 2.3 ^ 4.5             -- old syntax: math.exp(2.3, 4.5)
+    _pc("db " .. math.floor(x))
+    _pc("db " .. 35//7)       -- old syntax: 35/7
+      -- floating point values are now formatted rigorously with ".0" from the Lua
+      -- which leads to error in sjasmplus parser: Unexpected: .0
+      -- and requires to enforce integer type already at lua side
+      -- you can use either `math.floor()` function, or integer
+      -- operators like integer divide `//` or left shift `1<<7`
+
+    -- the floating point values (like "12.0") are silently truncated since v1.20.1 (to "12"),
+    -- but v1.20.0 errors out on these, unless you patch the lua script to produce integers
+  ENDLUA
+
+


+

+ +

+ +

+
+
+ +

Chapter 8. SAVENEX guide

+ + +

NEX File Format

+ + +

NEX is binary format for ZX Spectrum Next, aiming to provide simple delivery of software + for the platform. For file format details check + https://specnext.dev/wiki/NEX_file_format. In short it is header + loading screen + + like-snapshot binary and remaining resources appended after.

+ +

Sjasmplus currently supports V1.2 and V1.3 of NEX file format (see wiki for details).

+ +

As such, the SAVENEX commands are available only in ZXSPECTRUMNEXT device emulation mode, + see DEVICE.

+ +

As the file is designed for self-contained distribution of whole applications/games, + its configuration and assembling is a bit too complex for single directive, and the + configuration is instead divided into multiple commands, and the assembling goes + through multiple stages, so some commands must be used in correct sequence.

+ +

While the format technically allows to include multiple screen types data, they are all + loaded at the beginning over each other, so it makes sense to provide only single loading + screen (sjasmplus enforces that).

+
+ +

Detailed description of each SAVENEX command

+ +
+ SAVENEX OPEN <filename>[,<startAddress>[,<stackAddress>[,<entryBank16k 0..111>[,<fileVersion 2..3>]]]] +
+

+ Opens a NEX file, defines start address, stack address and 16k bank to be mapped + at 0xC000 before code is executed (if values are omitted, start address is zero + = no start, stack address is 0xFFFE, entryBank is zero, fileVersion is 2). +

+

+ Only single NEX file can be open at the same time, and to finalize the header + content the command CLOSE has to be used (does auto-close if source ends). +

+

+ Entry bank is number of 16k bank (0..111), not native 8k page, default is zero, + i.e. the default memory map is identical to ZX 128 (ROM, RAM banks 5, 2 and 0). +

+

+ Make sure your new stack has at least tens of bytes available as those will be + used already by the NEX loader before executing your entry point (although + released back). +

+

+ The fileVersion can be 2 (NEX V1.2) or 3 (NEX V1.3), which will enforce the + specified version of file. Otherwise the file is V1.2 by default and will + auto-switch to V1.3 when some V1.3 feature is configured/used. When version 2 + is enforced, any usage of V1.3 feature will emit error. +

+
+ SAVENEX CORE <major 0..15>,<minor 0..15>,<subminor 0..255> +
+

+ Set minimum required Next core version, can be set any time before CLOSE. +

+
+ SAVENEX CFG <border 0..7>[,<fileHandle 0/1/$4000+>[,<PreserveNextRegs 0/1>[,<2MbRamReq 0/1>]]] +
+

+ Set border colour (during loading), whether the machine should be set to default + state (PreserveNextRegs = 0 = default), if the app requires extended RAM + (224 8k pages) and how the file handle of the NEX file should be treated: + 0 = default = close, 1 = keep open and pass in BC, $4000..$FFFE = keep open, + and write into memory at provided address (after entry bank is paged in). This + can be set any time before CLOSE. +

+
+ SAVENEX CFG3 <DoCRC 0/1>[,<PreserveExpansionBus 0/1>[,<CLIbufferAdr>,<CLIbufferSize>]] +
+

+ All of these are NEX format V1.3 features and using "CFG3" command will change + the version to V1.3 automatically (if not specified by OPEN). +

+

+ DoCRC: default = 1, sjasmplus will checksum the file (if you know you will be + further patching file afterwards, switch it off, otherwise keep 1). +

+

+ PreserveExpansionBus: default = 0, 0 = the NEX loader will disable expansion + bus through NextReg $80 / 1 = the NEX loader will not do anything. +

+

+ CLIbufferAdr, CLIbufferSize: default = [0, 0], address and size of buffer for + NEX loader to copy the arguments line into. The buffer is copied after the + "entryBank" is mapped, so you can allocate buffer in your entryBank. The + loader/format has hard limit 2048 bytes, the argument line will be truncated + if longer. If your reserved buffer is shorter than 2048 bytes, the copy will + be also truncated to your buffer size only. If not truncated, the argument line + can end with any of these: Enter (13), colon or zero byte (truncated has no + terminator character, just fills the full buffer). +

+
+ SAVENEX BAR <loadBar 0/1>,<barColour 0..255>[,<startDelay 0..255>[,<bankDelay 0..255>[,<posY 0..255>]]] +
+

+ Loading-bar related setup ("colour" usage depends on screen mode), can be set + any time before CLOSE. +

+

+ The posY argument will apply only to V1.3 Layer2 screens in 320x256 and + 640x256 resolution (then defaults to 254 = bottom of screen, 2px tall bar). +

+
+

SAVENEX PALETTE NONE

+

SAVENEX PALETTE DEFAULT

+

SAVENEX PALETTE MEM <palPage8kNum 0..223>,<palOffset>

+

SAVENEX PALETTE BMP <filename>

+
+

+ This is optional command to set palette in alternative way ahead of SCREEN + command, with higher priority (if the PALETTE command is used, following + SCREEN commands will ignore the palette arguments and keep the palette defined + by this command). You can be use it between OPEN + and SCREEN command. But appropriate SCREEN + type supporting palette must be defined by SCREEN command too. +

+

+ Palette consists of 512 bytes (256 palette items from index 0), in 9b colour + format: first byte is %RRRGGGBB, second byte is %P000000B (P is priority flag + for Layer 2 colours). +

+

+ The DEFAULT palette will generate colour values from the colour index, the same + way how default Layer 2 palette is initialized on the ZX Spectrum Next. +

+

+ The NONE palette will force the "no palette" flag even if SCREEN command later + does specify some palette (it will be still ignored). +

+
+ SAVENEX SCREEN L2 [<Page8kNum 0..223>,<offset>[,<palPage8kNum 0..223>,<palOffset>]] +
+

+ Layer 2 loading screen, can be used between OPEN + and first AUTO/BANK command. +

+

+ Palette consists of 512 bytes (256 palette items from index 0), in 9b colour + format: first byte is %RRRGGGBB, second byte is %P000000B (P is priority flag + for Layer 2 colours). +

+

+ Image data are 48kiB block of memory, the loader will use always banks 9..11 to display + it (8k pages 18..23), but if you will prepare the data there, it will be also re-saved + by AUTO command, so either use other banks, and overwrite + them with valid data/code after using the SCREEN command, or reset pages 18..23 to zero + after SCREEN. +

+

+ If no memory address is specified, the pages 18..23 are stored in file, and if + no palette address is specified, no-palette flag is set in NEX file. +

+
+

SAVENEX SCREEN L2_320 [<Page8kNum 0..223>,<offset>[,<palPage8kNum 0..223>,<palOffset>]]

+

SAVENEX SCREEN L2_640 [<Page8kNum 0..223>,<offset>[,<palPage8kNum 0..223>,<palOffset>]]

+
+

+ Works same way as "L2" variant, but will set up screen for new resolutions 320x256x8 + and 640x256x4. The difference is that the required image data are 80kiB (five 16kiB + banks; equals ten 8kiB pages), the banks used to show the screen in loader are + 9..13 (8k pages 18..27 - that's also the default address of data if not specified) + - avoid these in regular banks stored in the file. +

+

+ These are NEX format V1.3 features and using them will change the version to V1.3 + automatically (if not specified by OPEN). This + command doesn't allow to set specific palette offset value (too lazy to add it, + use BMP variant if you really need it). +

+

+ The data has to be already in correct format and organized as if displayed (the + "transposed" bitmap where +1 address goes to pixel below, and +256 goes to pixel + on the right (or pair of pixels in case of 640x256x4bpp mode), this command will + just dump them into file "as is". +

+

+ The loading bar colour byte will be also used "as is", which in 4bpp mode means + the byte does define pair of 4 bit pixels, i.e. if you want solid-colour "3" + loading bar in 4bpp mode, define it as value $33. Keep also in mind the default + loading bar position is Y=254, which on most of the TV/LCD displays is outside + of visible range, the reasonably "safe" (visible on almost all of the screens) + resolution is about 288x224 (+16px around PAPER area, +24px is visible on many + displays too), you may want to organize your screen in a way to show all important + information within this area, and make the rest "unimportant" so it can hide + beyond the edge of screen. +

+
+ SAVENEX SCREEN LR [<Page8kNum 0..223>,<offset>[,<palPage8kNum 0..223>,<palOffset>]] +
+

+ LoRes (128x96) loading screen, can be used between OPEN + and first AUTO/BANK command. +

+

+ Palette is similar to Layer 2 mode, just LoRes mode doesn't have priority bit. +

+

+ Image data are 12288 bytes memory block - either consecutive block if specific + address is provided, or without address the actual bank 5 memory is stored + (taking 6144 bytes from address 0x4000 and 6144 bytes from address 0x6000). +

+
+ SAVENEX SCREEN BMP <filename>[,<savePalette 0/1>[,<paletteOffset 0..15>]] +
+

+ Only small subset of BMP files can be used: 256x192 (Layer 2) or 128x96 (LoRes), + indexed (8bit image data with palette) and palette data will be truncated + to 3:3:3 color space directly (no smart colour quantization or dithering is applied). + And the file must be uncompressed. +

+

+ For V1.3 NEX files you can include also 320x256 and 640x256 files (Layer 2), the + 640x256 should be also 8bit indexed, but only 4 bits of pixel data will be used + (256 colour palette is legitimate and will be stored "as is" in NEX file). + These two new modes can also include paletteOffset argument 0..15 (does not apply + to V1.2 BMP files above), the offset is added to top four bits of pixel value. +

+

+ The BMP will be included as loading screen, can be used between OPEN + and first AUTO/BANK command. +

+

+ By default the palette from BMP file is used, but you can override that by savePalette = 0. +

+
+ SAVENEX SCREEN (SCR|SHC|SHR) [<hiResColour 0..7>] +
+

+ ULA/Timex modes loading screen, can be used between OPEN + and first AUTO/BANK command. +

+

+ The actual bank 5 memory (pages 10..11) is stored as if the image is displayed, + in these modes the palette can't be specified. +

+

+ SCR is classic ZX 6912 bytes long screen from address 0x4000 (page 10 is used, + even if the slot 1 is modified to other page, so you must prepare the image "in place"). +

+

+ SHC and SHR are Timex HiColor (8x1 attribute) and HiRes (512x192 bitmap) modes, + prepare data "in place", i.e. 6144 bytes into page 10 and 6144 bytes into page + 11 (0x4000 and 0x6000 addresses in default memory setup). For HiRes mode you + should specify ink colour (the paper is complement of ink). +

+
+ SAVENEX SCREEN TILE <NextReg $6B>,<NextReg $6C>,<NextReg $6E>,<NextReg $6F>[,<AlsoStoreBank5 0/1 = 1>] +
+

+ NEX V1.3 tilemap loading screen, can be used between OPEN + and first AUTO/BANK command. +

+

+ To define palette use the PALETTE command. +

+

+ The image data are stored as regular Bank 5 of the NEX file (which is the first bank + to be loaded by loader), depending on AlsoStoreBank5 value (default 1), this SCREEN + command will also execute BANK 5 command to store + the image data. +

+

+ The NextRegisters $6B, $6C, $6E and $6F should be enough to specify any variant + of tilemap mode, so the precise sub-type and image data layout is under control + of user, the sjasmplus doesn't enforce any particular configuration. +

+

+ If you want to use also COPPER command, use it + either ahead of the SCREEN TILE command, or use "AlsoStoreBank5 = 0" to delay the + storage of Bank 5 data. In such case you must then later explicitly store + the Bank 5 as regular bank, either with BANK or AUTO command. +

+
+ SAVENEX COPPER <Page8kNum 0..223>,<offset> +
+

+ Can be used after OPEN and before first AUTO or BANK + command (the copper data are stored between screen and bank data). +

+

+ Exactly 2048 bytes are stored (full Copper memory), and the loader will start + the copper code in mode %01 (reset CPC to 0, then starts the copper), so the + copper code will wrap around infinitely after the 1024th instruction executed. + The copper is started after the screen block is loaded and displayed. +

+
+ SAVENEX BANK <bank16k 0..111>[,...] +
+

+ Can be used after OPEN or + SCREEN and before CLOSE, but the 16ki + banks must be saved in correct order: 5, 2, 0, 1, 3, 4, 6, 7, 8, 9, 10, ..., 111 +

+
+ SAVENEX AUTO [<fromBank16k 0..111>[,<toBank16k 0..111>]] +
+

+ Can be used after OPEN or + SCREEN and before CLOSE. The sjasmplus + will save every 16k bank containing at least one non-zero byte; detected in the correct order (automatically + will save every non-zero 16k bank detected in the correct order (automatically + starting from first possible bank after previous BANK/AUTO commands, or from + provided "fromBank"). +

+

+ For "fromBank" value use the specified order above in BANK + command, i.e. 5, 2, 0, ... +

+
+ SAVENEX CLOSE [<fileToAppend>] +
+

+ Can be used after OPEN. The currently open NEX + file will be finalized (header adjusted), and optional extra file just appended + to the end of NEX file. +

+
+ +
+ +

Examples

+ + +

Example 8.1. docs_examples/s_savenex_examples.asm

+ +

+ Creating NEX file which will have Layer2 loading screen (stripes), progress bar, and will + enter infinite loop with calling stack (used by IM 1 interrupt handler) visible on the + Layer 2 screen. +

+
    DEVICE ZXSPECTRUMNEXT
+    ORG $7E00
+start:  ei : jr $           ; app code entry point, BC = NEX file handle
+    ; Layer2 screen (top 1/3 defined, bottom of it will be used also as "visible" stack)
+    ORG $C000 : DUP 64*32 : DB $90,$91,$92,$93,$94,$95,$96,$97 : EDUP
+
+    ; write everything into NEX file
+    SAVENEX OPEN "example.nex", start, $FFFE, 9  ; stack will go into Layer2
+    SAVENEX CORE 2, 0, 0        ; Next core 2.0.0 required as minimum
+    SAVENEX CFG 4, 1, 0, 1      ; green border, file handle in BC, reset NextRegs, 2MB required
+    SAVENEX BAR 1, $E0, 50, 25  ; do load bar, red colour, start/load delays 50/25 frames
+    SAVENEX SCREEN L2 0, 0      ; store the data from C000 (page 0, offset 0), no palette
+    SAVENEX BANK 5, 100, 101    ; store the 16ki banks 5 (contains the code at 0x7E00), 100, 101
+    SAVENEX CLOSE               ; (banks 100 and 101 are added just as example)
+		  
+

+
+ +
+ +

Chapter 9. Source Level Debugging (SLD) data

+ + +

What is it?

+ + +

+ SLD data are extra "tracing" data produced during assembling for debuggers and IDEs, + similar to "map" files already supported by sjasmplus ( + LABELSLIST and CSPECTMAP). +

+ +

+ The debugger can read these data, and with non-tricky source producing machine code + with correct device memory mapping, the debugger can trace the origins of every + instruction back to the original source code line, and display the source instead/along + the disassembly view (the "map" files mentioned above provide only list of labels + which is usually already super helpful, but can't track the source origins of each + instruction). +

+ +

+ The original impulse and working patch for this feature came from Chris Kirby, adding the + single-instruction-step feature to his development tools: + Next Development System (currently in 2019 under heavy development). +

+
+ +

Usage

+ + +

+ On the sjasmplus side all you need is to add another command line option when starting + the assembler. +

prompt$ sjasmplus --sld=project.sld.txt file1.asm file2.asm

+ This will produce along regular files also file `project.sld.txt` containing the + tracing data. The file format is text-like, so the content can be viewed + in any text editor, but it's supposed to be processed by the debugger. +

+ +

+ The SLD data are being exported only for machine code produced + within one of the virtual devices (see DEVICE). + And the accuracy of the data directly depends on the state of the virtual device at + the moment when the particular instruction is assembled, see next section for further + advice how to get best tracing data. +

+ +

+ If the option `--sld` without explicit filename is used, the first input source filename + will be copied and its extension changed to `.sld.txt`, which should work well for + single-main file type of projects. +

+ +
+ +

How to write "non tricky" source

+ + +

+ For best results (tracing data covering all your instructions and making source-level + debugging available all the time): +

+ +

+ Generate machine code only with instructions, i.e. while `db 0` in source will produce + `nop` instruction, the tracing data for such `nop` will be missing. Only source line + containing the "nop" will emit both the zero byte into machine code and tracing data. +

+ +

+ Keep the memory map of the virtual device as it will be set at run-time when writing + particular code. While using only regular ORG to place + the code in desired memory area, keeping only the modified area with memory pages + mapped-in is enough to get correct SLD data - but when using DISP + directive to generate code in displaced way, you need to map-in correct pages in both + memory areas (where the code is currently assembled, and where it is supposed to be + operational at run-time - currently impossible if the two areas share the same SLOT area). +

+ +

+ To map-in correct pages you can use directives: + SLOT, PAGE, + MMU and ORG +

+ +

+ Only labels defined in regular way get also the memory-page data in SLD file, EQU + values are exported without the page information, even if they represent memory + address: +

+regularLabel:   nop             ; current page exported to SLD data
+equLabel        EQU   $8000     ; only value without page is exported
+varSymbol       =     14        ; =/DEFL labels are omitted completely

+

+ +

+ Re-using the same memory area (page + address) for two different pieces of code (for + example by having multiple routines targeting the same address with DISP, and run-time + code uploading the correct variant dynamically at the destination) will highly likely + confuse the debugger, producing two source-origins data for the identical address. + You may want to avoid this, especially with early version of tools not being able to + resolve such ambiguity in reasonable way (the sjasmplus will generate tracing data ok + even in this case, but they are tricky to interpret). +

+ +

+ If you are using the colons to put multiple instructions on single line, verify the + debugger can cope with the tracing data containing full line + column begin/end + information, the simple/early tools will likely highlight only whole line of source. + For a start keeping single instruction per line may keep things simple. +

+ +

+ The various code generators written in macros or Lua scripts will produce accurate + tracing data in case of macros, and condensed (pointing at "ENDLUA" line) data for + Lua generators, but it still depends on the debugger if it can display both top + level source code triggering the generator and source lines containing definitions + of particular instructions. +

+ +
+ +

SLD File Format definition (version "1")

+ + +

The file format is still under development. If you are working on support in debugger, stay + in touch with us, so we can discuss any modifications to current format with you. +

+ +

The SLD data is text-file (sjasmplus is using UNIX-like newlines 0x0A, but parsers + should rather cope with any of common EOL scheme). The general format is CSV-like + using pipe character as delimiter between fields. +

+ +

+ If the first field is empty, the line is one of the special control lines - the second + field selects type of control line. If also the second field is empty, the remaining + part of line should be ignored (comment line). + Currently only one type of "control line" exists, the "SLD.data.version" (third + field is integer number): +

|SLD.data.version|1
+||anything ... comment-like line

+ The file format version line should be always first line of the SLD data file. +

+ +

+ The regular tracing data lines have fixed amount of fields: eight. The seventh field + contains single uppercase letter defining type of the line, which affects sub-variants + of format for eighth field, but first six fields share the same formatting and meaning + across all types of regular lines: +

+<source file>|<src line>|<definition file>|<def line>|<page>|<value>|<type>|<data>
+

+

+ +

+ <source file> - file name of top-level source emitting the + instruction/label/device. +

+ +

+ <src line> - line and characters in top-level source file, the precise + format is "<line>[:<column begin>[:<column end>]]" where + first number is line number (starting at 1). The two following numbers delimited by colon + are optional, representing the column where the segment starts/ends at the line. The + column values are starting from 1 too, the "end" column is pointing beyond the current + segment. The columns are in "bytes", i.e. tabulator does increase the column value by +1 + only, and the "end" may actually point well beyond "strlen(line)". +

+ +

+ <definition file> - file name of the source defining the particular + instruction (for example where the instruction inside MACRO was originally defined). + If empty, the definition-file name is identical to <source file> + (common case for single-source projects). +

+ + +

+ <def line> - zero when there is no extra "definition" of instruction + involved. When non-zero, the format is identical to <src line>, but + with regard to the file specified in <definition file> field. +

+ +

+ <page> - number of memory page where the <value> + address points to, or -1 when value is not memory address. +

+ +

+ <value> - any 32 bit integer value (EQU), but when representing memory + address, it is full 16 bit value from Z80 address space (the top bits beyond the memory + page size contains the number of "slot" where the instruction/label resides). +

+ +

+ <type> - single uppercase letter representing type of line, current + types: T (instruction Trace), L (Label), F [deprecated, parse L], D [deprecated, parse L], Z (device memory model), + K (keyword comment, see `SLDOPT COMMENT`). +

+ +

+ <data> - extra data specific for particular line type. Empty for + "T" lines. For "L" a list of items separated by comma, where first three items + represent sub-parts of symbol name: module name, main label, local label (sub-part + may be empty), optional remaining items are usage-traits giving away various internal info about + usage of symbol (currently exported traits: +equ, +macro, +reloc, +reloc_high, +used, +module, +endmod, + +struct_def, +struct_data). + For type "K" the comment containing one of they defined keywords is present. +

+ +

+ For type "Z" the data field contains string describing the device memory model which + is being selected in source and should be applied for following SLD data lines. The + memory model string format is: +

+pages.size:<page size>,pages.count:<page count>,slots.count:<slots count>[,slots.adr:<slot0 adr>,...,<slotLast adr>]
+// unsigned <page size> is also any-slot size in current version.
+// unsigned <page count> and <slots count> define how many pages/slots there are
+// uint16_t <slotX adr> is starting address of slot memory region in Z80 16b addressing
+

+

+ +

Example 9.1. Example of SLD file

+ + +
+|SLD.data.version|1
+|| ZX Spectrum Next device description:
+toplevel.asm|59||0|-1|-1|Z|pages.size:8192,pages.count:224,slots.count:8,slots.adr:0,8192,16384,24576,32768,40960,49152,57344
+|| label "main" points to 32768, with page 14 mapped-in (defined in toplevel.asm:62)
+|| the label "main" is used by other code (IFUSED-like), and has relocation data
+toplevel.asm|62||0|14|32768|L|,main,,+reloc,+used
+|| instruction opcode at 32768 (page 14) was created by toplevel.asm:64
+toplevel.asm|64||0|14|32768|T|
+|| instruction opcode at 32769 (p 14) was created by toplevel.asm:67
+|| (but it is a line using macro, the instruction was defined by toplevel.asm:52)
+toplevel.asm|67||52|14|32769|T|
+|| instruction opcode at 32770 (p 18) was created by toplevel.asm:68:12-24
+toplevel.asm|68:12:24||0|18|32770|T|
+|| instruction opcode at 32771 (p 18) was created by toplevel.asm:68:24 (till EOL)
+toplevel.asm|68:24||0|18|32771|T|
+|| "PORT_NUMBER EQU 254" defined at toplevel.asm:69
+toplevel.asm|69||0|-1|254|L|,PORT_NUMBER,,+equ
+|| label+instruction emitted from toplevel.asm:70, but defined in include.asm:3
+|| with macro label "0>macro_defined_in_include_asm" (source ".macro_defined_in_include_asm:")
+toplevel.asm|70|include.asm|3|37|40976|L|,0,macro_defined_in_include_asm,+macro,+used
+toplevel.asm|70|include.asm|3|37|40976|T|
+
+ +

+ +
+ +
+ +

Index

C

Conditional assembly, Conditional assembly
CSPECTMAP, Pseudo-ops

D

D24, Pseudo-ops, STRUCT instructions
DB (see BYTE)
DC, Pseudo-ops
DD (see DWORD)
DEFARRAY, Pseudo-ops
DEFARRAY+, Pseudo-ops
DEFB (see BYTE)
DEFD (see DWORD)
DEFDEVICE, Pseudo-ops
(see also DEVICE)
DEFG (see DG)
DEFH (see DH)
DEFINE, Pseudo-ops
DEFINE+, Pseudo-ops
DEFL, Pseudo-ops
(see also EQU)
DEFM (see BYTE)
DEFS (see BLOCK)
DEFW (see WORD)
DEPHASE (see ENT)
(see also DISP)
DEVICE, Pseudo-ops
(see also DEFDEVICE)
(see also MMU)
(see also ZXSPECTRUM128)
(see also ZXSPECTRUMNEXT)
DG, Pseudo-ops
DH, Pseudo-ops
DISP, Pseudo-ops
(see also ENT)
DISPLAY, Pseudo-ops
DM (see BYTE)
DS (see BLOCK)
DUP, Pseudo-ops
DW (see WORD)
DWORD, Pseudo-ops, STRUCT instructions
DZ, Pseudo-ops

E

ELSE, Conditional assembly
(see also IF)
ELSEIF, Conditional assembly
(see also IF)
EMPTYTAP, Pseudo-ops
EMPTYTRD, Pseudo-ops
ENCODING, Pseudo-ops
END, Pseudo-ops
ENDIF, Conditional assembly
ENDLUA, Pseudo-ops
(see also LUA)
ENDMOD (see ENDMODULE)
ENDMODULE, Pseudo-ops
(see also MODULE)
ENDT (see ENT)
ENT, Pseudo-ops
(see also DISP)
EQU, Pseudo-ops
(see also DEFL)
exist, Expressions
EXPORT, Pseudo-ops

F

FPOS, Pseudo-ops

H

HEX, Pseudo-ops
(see also DH)
high, Expressions

L

LABELSLIST, Pseudo-ops
low, Expressions
LUA, Pseudo-ops

M

MMU, Pseudo-ops
(see also DEVICE)
(see also PAGE)
(see also SLOT)
mod, Expressions
MODULE, Pseudo-ops

O

OPT, Pseudo-ops
or, Expressions
ORG, Pseudo-ops
(see also DEVICE)
OUTEND, Pseudo-ops
(see also OUTPUT)
(see also SIZE)
OUTPUT, Pseudo-ops
(see also SIZE)

P

PAGE, Pseudo-ops
(see also DEVICE)
(see also MMU)
(see also SLOT)
PHASE (see DISP)

R

RELOCATE_END, Pseudo-ops
RELOCATE_START, Pseudo-ops
RELOCATE_TABLE, Pseudo-ops
REPT (see DUP)

S

SAVE3DOS, Pseudo-ops
SAVEAMSDOS, Pseudo-ops
SAVEBIN, Pseudo-ops
SAVECDT, Pseudo-ops
SAVECPCSNA, Pseudo-ops
SAVEDEV, Pseudo-ops
SAVEHOB, Pseudo-ops
SAVENEX, Pseudo-ops
SAVESNA, Pseudo-ops
SAVETAP, Pseudo-ops
SAVETRD, Pseudo-ops
SETBP, Pseudo-ops
(see also BPLIST)
SETBREAKPOINT (see SETBP)
SHELLEXEC, Pseudo-ops
shl, Expressions
shr, Expressions
SIZE, Pseudo-ops
(see also OUTPUT)
sj.add_byte(..), SjASMPlus functions bindings to Lua
sj.add_word(..), SjASMPlus functions bindings to Lua
sj.calc(..) (see _c(..))
sj.current_address, SjASMPlus functions bindings to Lua
sj.error(..), SjASMPlus functions bindings to Lua
sj.error_count, SjASMPlus functions bindings to Lua
sj.exit(..), SjASMPlus functions bindings to Lua
sj.file_exists(..), SjASMPlus functions bindings to Lua
sj.get_byte(..), SjASMPlus functions bindings to Lua
sj.get_define(..), SjASMPlus functions bindings to Lua
sj.get_device(), SjASMPlus functions bindings to Lua
sj.get_label(..), SjASMPlus functions bindings to Lua
sj.get_word(..), SjASMPlus functions bindings to Lua
sj.insert_define(..), SjASMPlus functions bindings to Lua
sj.insert_label(..), SjASMPlus functions bindings to Lua
sj.parse_code(..) (see _pc(..))
sj.parse_line(..) (see _pl(..))
sj.set_device(..), SjASMPlus functions bindings to Lua
sj.set_page(..), SjASMPlus functions bindings to Lua
sj.set_slot(..), SjASMPlus functions bindings to Lua
sj.shellexec(..), SjASMPlus functions bindings to Lua
sj.warning(..), SjASMPlus functions bindings to Lua
sj.warning_count, SjASMPlus functions bindings to Lua
SLDOPT, Pseudo-ops
SLOT, Pseudo-ops
(see also DEVICE)
(see also MMU)
(see also PAGE)

T

TAPEND, Pseudo-ops
(see also TAPOUT)
TAPOUT, Pseudo-ops
TEXT, STRUCT instructions
TEXTAREA (see DISP)

U

UNDEFINE, Pseudo-ops
(see also DEFINE)
UNPHASE (see ENT)
+ +
diff --git a/tools/sjasm/docs/lua.gif b/tools/sjasm/docs/lua.gif new file mode 100644 index 0000000..2f5e4ac Binary files /dev/null and b/tools/sjasm/docs/lua.gif differ diff --git a/tools/sjasm/mhmt.exe b/tools/sjasm/mhmt.exe new file mode 100644 index 0000000..3b81e62 Binary files /dev/null and b/tools/sjasm/mhmt.exe differ diff --git a/tools/sjasm/ninja.exe b/tools/sjasm/ninja.exe new file mode 100644 index 0000000..8cac6b9 Binary files /dev/null and b/tools/sjasm/ninja.exe differ diff --git a/tools/sjasm/sjasmplus.exe b/tools/sjasm/sjasmplus.exe new file mode 100644 index 0000000..148ea25 Binary files /dev/null and b/tools/sjasm/sjasmplus.exe differ diff --git a/tools/unreal/CMOS b/tools/unreal/CMOS new file mode 100644 index 0000000..082ba3c Binary files /dev/null and b/tools/unreal/CMOS differ diff --git a/tools/unreal/NVRAM b/tools/unreal/NVRAM new file mode 100644 index 0000000..e9784eb Binary files /dev/null and b/tools/unreal/NVRAM differ diff --git a/tools/unreal/RAGE.$Z b/tools/unreal/RAGE.$Z new file mode 100644 index 0000000..1400315 Binary files /dev/null and b/tools/unreal/RAGE.$Z differ diff --git a/tools/unreal/RAGE.SCL b/tools/unreal/RAGE.SCL new file mode 100644 index 0000000..42c893f Binary files /dev/null and b/tools/unreal/RAGE.SCL differ diff --git a/tools/unreal/Unreal.exe b/tools/unreal/Unreal.exe new file mode 100644 index 0000000..d1c1cb0 Binary files /dev/null and b/tools/unreal/Unreal.exe differ diff --git a/tools/unreal/Unreal.ini b/tools/unreal/Unreal.ini new file mode 100644 index 0000000..b90d8f6 --- /dev/null +++ b/tools/unreal/Unreal.ini @@ -0,0 +1,1370 @@ +[*] unreal speccy configuration file + +UNREAL=0.37.9 ; make sure you don't have old INI version + +[MISC] +; give idle timeslices to other applications +ShareCPU=1 ; 1 - only for fast CPUs (incompatible with SoundDrv=none) +; +HighPriority=1 ; boost process priority, disabled on ShareCPU=0 +; +; high memory: PENTAGON, SCORPION, PROFSCORP, GMX, PROFI, ATM450, ATM710, KAY, ATM3, TSL, LSY +HIMEM=TSL +; memory size, KB +RAMSize=4096 + +; ULA+: NONE, TYPE1 (Blue is 000 011 101 111), TYPE2 (Blue is 000 011 100 111) +ULAPLUS=TYPE1 + +; ZX-Evo Video DAC resolution: NONE, 3BIT, 4BIT, 5BIT +TS_VDAC=5BIT + +; ZX-Evo Video DAC2 using FT812 (0 - disabled, 1 - enabled, 2 - VDAC2 + ESP32) +TS_VDAC2=0 + +; OUT (C), 0 value +OUT_C_0=0 + +; KR1858VM1 halt handling +Z80_VM1=0 + +TapeTraps=1 +; start tape on ROM load routine + +TapeAutoStart=1 +; cmos on DFF7/BFF7: none, DALLAS (pc chip), or 512Bu1 (russian chip) + +CMOS=DALLAS +; cache size: 16,32 or 0 - disabled. no effect for ATM-4.50 model + +Cache=0 +; bits, that can't be updated by Z80 and cleared by RESET (decimal value) +; ex: mask=3 - disable HWMC and 512x192, mask=255 - disable all additional hardware + +EFF7mask=0 ; EFF7mask=0 - all pentagon features on +; +SMUC=0 ; enable/disable NVRAM + CMOS RTC +; +RESET=SYS ; may be: BASIC,DOS,MENU,SYS + +ZC=1 ; enable/disable Z-Controller + +HideConsole=0 ; for gui freaks, who annoyed by main console window, set to 1 + +ConfirmExit=0 ; 1 for exit confirmation message + +; help filename +Help=help_eng.html + +; directory with zx-files +DIR=. + +; SPG Memory init: 0 - none, 1 - random data fill, 2 - zero data fill +SPGMemInit=0 + +; Kondrat'yev ISA modem emulated through windows modem on COM-port +; if you own internal PCI modem, use "settings/input" dialog to find out port +Modem=NONE ; NONE or COM1-COM255 + +; ZiFi API layer1 emulated as bridge to COM-port +ZiFi=NONE ; NONE or COM1-COM255 + +[AUTOLOAD] +; disk images and snapshot, loaded at startup +; file may be compressed, but only one file in archive is allowed + + +diskA=boot.$b +diskB= +diskC= +diskD= +;snapshot=demo1.sna +DefaultDrive=Auto ; Auto, A, B, C, D + + +[INPUT] +; default/bk08/quorum +ZXKeyMap=default +; you can define own layout, naming section in this INI file [ZX.KEYS.myname] +; create layouts for xas/storm/zxasm/alasm/zxword/sts/etc yourself, i'm bored +KeybLayout=default +KeybPCMode=0 ; when enabled, using pc defaults, ex: SHIFT+7 => & +Mouse=KEMPSTON ; mouse type: NONE/KEMPSTON/AY +Wheel=KEMPSTON ; mouse wheel: NONE/KEMPSTON/KEYBOARD +JoyMouse=0 ; emulate kempston joystick on mouse +MouseScale=0 ; logarithmic scale: ZX_mouse=PC_mouse*2^(Scale), valid: [-3;3] +SwapMouse=0 ; swap left and right mouse buttons +KJoystick=1 ; emulate kempston joystick on keyboard and/or gameport +Matrix=1 ; emulate keymatrix effect +Fire=KFIRE ; autofire key -> kempston +FireRate=1 ; autofire button up/down delay (INTs) +AltLock=1 ; does not update ZX-SPECTRUM keyboard while ALT pressed +ATMKBD=0 ; ATM PC/XT keyboard controller +Joy=0 ; Joystick id (0, 1, ...) + +; clipboad paste delays +HoldDelay=2 +ReleaseDelay=5 +NewlineDelay=20 + + +[ULA] +Frame=71680 ; t-states in frame +; Frame=250000 ; t-states in frame - 14MHz +Line=224 ; t-states in line +; Line=784 ; t-states in line - 14MHz +int=50 ; int freq +intstart=13 ; t-states before int +intlen=32 ; int length in t-states +EvenM1=0 ; align M1 cycle to even T-state (for scorpion) +4TBorder=0 ; update border every 4T states (for scorpion) +FloatBus=0 ; unstable data bus +FloatDOS=0 ; unstable data bus in TR-DOS mode +PortFF=0 ; enable/disable port FF + +Preset= ; don't use above settings and load preset + +; PRESET.NAME=FRAME,PAPER,LINE,INT,INTLEN,EvenM1,4TBorder,floatBus,floatDOS,PortFF +PRESET.PENTAGON=71680,13,224,50,32,0,0,0,0,0 +;PRESET.PENTAGON=143360,17989,224,50,32,0,0,0,0,0 +PRESET.SCORPION=69888,14344,224,50,32,1,1,0,0,1 +PRESET.ATM1_2_3.5MHz=69888,17989,224,50,32,0,0,0,0,1 +PRESET.ATM1_2_7.0MHz=99880,17989,224,50,32,0,0,0,0,1 +PRESET.PROFI=69888,12580,224,50,28,0,0,0,0,0 ; thanks to DDp + +; ATM 7.10 hi-res video modes swap RAM/CPU address bus A5-A7<=>A8-A10 +AtmMemSwap=0 +; reprogram color DACs with ATM palette. with this option +; noflic filter works only in fullscreen 16/32-bit, +; colors of system leds are also affected +UsePalette=1 +ProfiMonochrome=0 + +[VIDEO] + +VSync=0 ; enable for slower, but less jerky screen repaint. + ; uses backbuffer - requires twice more video memory + ; WARNING: eats a lot of cpu time! + +FullScr=0 ; start at full-screen. windowed mode can be used only for + ; GDI, Blitter or Overlay video filter / driver + +Refresh=0 ; monitor refresh rate in fullscreen mode (0-default). + ; for video=resampler, the more - the better + +Border=4 ; border size - 0: 256x192, 1: 320x200, 2: 320x240, 3: 360x288, 4: 384x304, 5: 448x320 +NoFlic=0 ; apply noflic filter +AltNoFlic=0 ; use interlaced algorithm for video=double +FlashColor=0 ; + +MonScale=1 ; scale debugger window (2 for 2X, 3 for 3X and so on...) + +MinRes=480 ; minimal screen y-resolution in fullscreen mode + ; use 480 or higher to avoid switching mode when entering monitor + ; use 1024 to force 1280x1024 on LCD instead of 1280x960 + + +ChunkSize=auto ; 2,4, or auto-detect (only for ch* videofilters) +PixelScroll=0 ; detect per-pixel scrolling for video=text + + +; frame resampler options: +rsm.frames=4 ; frames to mix (2-8) + +; fir interpolation mode: +; 2C - 12hz cut-off (gigascreen), 3C - 8hz cut-off (3-color) +; FULL - full details (scrollers), SIMPLE - average of last 2 frames +rsm.mode=2C + + +; scale factor for 'video=advmame'. accepted values: 2,3,4 +; 4x scale requires 1280x960 pixels. use border=none, if your monitor displays only 1024x768 +scale=2 + +; window scale factor (for hardware stretching) +winscale=1 + +; video filter/renderer +; values for video filter: +; normal - fastest, but supports only standart spectrum screen, noflic, flash-color and HWMC +; double - slower, but offers better quality and works in: pentagon 512x192, profi 512x240, ... +; triple - 3x picture scale +; quad - 4x picture scale, designed to use in LCD with 1280x1024 to avoid hardware stretching +; use with minres=1024 to avoid 1280x960 mode +; text - converts 4x8 fonts to 8x8 or 8x16 (for reading e-zines) +; resampler - converts spectrum 50hz-int into 60/75/80/... hz display refresh +; bilinear - color interpolation (MMX) +; scale - fake 'vector' scaling +; advmame - 2x,3x,4x scaling algorithm from AdvanceMAME project +; tv - color TV emulation, windowed mode +; ch_ov - chunky (fast), windowed mode (overlay, 16bit) +; ch_hw - chunky (fast), windowed mode (hardware accel, 32bit) +; ch_bl - chunky (filtered,320x240x16) +; ch_b - chunky (filtered,640x480x16) +; ch4true - 32bit 4x4 chunky, full color precision (slow, but no special hardware reqd) + +video=double + +; debugger ray painting mode +; 0 - overdraw previous frame, 1 - clear to black, 2 - dim previous frame and overdraw it +raypaint_mode=2 + +; driver for video=normal/double/triple/quad/text + +driver=blt + +; video driver: +; ddraw - direct access to video memory, fullscreen only, fastest +; gdi - standart windows driver (slowest, but most compatible) +; blt - hardware blitter. recommended for accelerated cards (geforce4/etc) +; ovr - video overlay. recommended in windowed mode for slow video cards +; ddrawh, ddrawt - fullscreen 16- and 32-bit modes + +fastlines=0 ; for video=double - draw twice faster in expense of visual quality + ; for video=anti64 - 4x8->8x8, else 4x8->8x16 + +; use overlay is faster, but you'll get wrong colors if videocard can't +; use RGB555/RGB565 pixels formats for overlay (such as all nVidia cards) +; some video cards do interpolation in overlay mode, +; even if we don't requested it + +ScanIntens=100;45 ; (%) bright for scan lines when video=double,fastlines=0 + ; only for true-color and high-color modes + ; 8-bit modes use palette values ZN,ZB + +; ignore these filters +Hide= ;ch_bl,ch_ov,ch4true + +; update border for CPU expensive filters +Update=1 +; how much frames to skip before displaying frame (for PCI video set to 2) +SkipFrame=0 +; don't wastetime on drawing when running on max speed +SkipFrameMaxSpeed=64 +; screenshot format: SCR, BMP, PNG +ScrShot=PNG +; screenshot directory. +ScrShotDir=. +; when enabled, displays videocard vendor id, device id, card name +; (warning: sometimes uses driver database at http://crl.microsoft.com !!!) +; when disabled, faster startup but no additional information +DetectModel=0 + +; apply filter for 1x2 Atari chunks. situable for games, converted from XL, +; like SpyVsSpy, Flying Shark. presets for games defined in [COLORS] section +; (for now, filter works in 8-bit and 16-bit-normal /not-double/ modes) +;AtariPreset=fl_shark_red_palette +AtariPreset=none + +; ffmpeg settings +; exec: path and name of ffmpeg executable (without quotes) +; newconsole: 1 - open new console for ffmpeg output, +; 0 - print ffmpeg output into emul's console (useful for debug) +; parm: encoding settings (see ffmpeg doc) +; vout: output video file name (use '#' sign for sequence number substitution) +ffmpeg.exec=ffmpeg.exe +ffmpeg.newconsole=1 + +; ffmpeg encoding options +; -ss 00:00:00.500 - skip first 500 ms (skip 25 frames with status line) +; -af volume=3.0 - gain sound volume by 3 +; -vf "..." - apply video filter +; -r 25 - set 25 fps +; -c:a ... - set audio codec ("copy" for uncompressed wav) +; -c:v ... - set video codec ("copy" for uncompressed rgb24) +; -b:a 320k - set bitrate for audio +; -b:v 2800k - set bitrate for video + +;# mp3:320kbps, x264:baseline,10, fast +ffmpeg.parm=-c:a libmp3lame -b:a 320k -af volume=3.000000 -c:v libx264 -pix_fmt yuvj420p -preset:v veryfast -profile:v baseline -crf 10 -ss 00:00:00.500 +ffmpeg.vout=video#.avi + +;# aac:256kbps, x264:baseline,10, fast +;ffmpeg.parm=-c:a libvo_aacenc -b:a 256k -af volume=3.000000 -c:v libx264 -pix_fmt yuvj420p -preset:v veryfast -profile:v baseline -crf 10 -ss 00:00:00.500 +;ffmpeg.vout=video#.avi + +;# aac:256kbps, x264:baseline,10, slow +;ffmpeg.parm=-c:a libvo_aacenc -b:a 256k -af volume=3.000000 -c:v libx264 -pix_fmt yuvj420p -preset:v veryslow -profile:v baseline -crf 10 -ss 00:00:00.500 +;ffmpeg.vout=video#.avi + +;# aac:256kbps, x264:lossless, fast +;ffmpeg.parm=-c:a libvo_aacenc -b:a 256k -af volume=3.000000 -c:v libx264 -pix_fmt rgb24 -preset:v ultrafast -qp 0 -ss 00:00:00.500 +;ffmpeg.vout=video#.avi + +;# aac:256kbps, x264:lossless, slow +;ffmpeg.parm=-c:a libvo_aacenc -b:a 256k -af volume=3.000000 -c:v libx264 -pix_fmt rgb24 -preset:v veryslow -qp 0 -ss 00:00:00.500 +;ffmpeg.vout=video#.avi + +;# wav, x264:lossless, fast +;ffmpeg.parm=-c:a copy -c:v libx264 -pix_fmt yuvj420p -preset:v ultrafast -qp 0 -ss 00:00:00.500 +;ffmpeg.vout=video#.avi + +;# wav, x264:lossless, slow +;ffmpeg.parm=-c:a copy -c:v libx264 -pix_fmt yuvj420p -preset:v veryslow -qp 0 -ss 00:00:00.500 +;ffmpeg.vout=video#.avi + +;# uncompressed +;ffmpeg.parm=-c:a copy -c:v copy -ss 00:00:00.500 +;ffmpeg.vout=video#.avi + +;# Image sequence: PNG +;ffmpeg.parm= +;ffmpeg.vout=video#_%06d.png + +;# PAL DVD video +; MUST use picture size greater than 720x576 (for example, DOUBLE SIZE and COMPLETE border)! +;ffmpeg.parm=-af volume=3.000000 -bf 2 -vf "crop=720:576,tinterlace=mode=interlacex2" -aspect 4:3 -target pal-dvd -r 25 -b:v 2800k -ss 00:00:00.500 +;ffmpeg.vout=video#.mpg + +;# PAL DVD video +; MUST use picture size lower than 720x576 (and DOUBLE SIZE for correct interlace)! +;ffmpeg.parm=-af volume=3.000000 -bf 2 -vf "pad=720:576:(ow-iw)/2:(oh-ih)/2,tinterlace=mode=interlacex2" -aspect 4:3 -target pal-dvd -r 25 -b:v 2800k -ss 00:00:00.500 +;ffmpeg.vout=video#.mpg + +;# RTMP streaming: justin.tv +;ffmpeg.parm=-af volume=3.000000 -r 10 -ss 00:00:00.500 -f flv +;ffmpeg.vout=rtmp://live.justin.tv/app/live_74829844_mfrdpoeg034tn03gn03ring3f0rngc + +;# RTMP streaming: own3d.tv +;ffmpeg.parm=-af volume=3.000000 -r 10 -ss 00:00:00.500 -c:v libx264 -pix_fmt yuvj420p -preset:v veryfast -profile:v baseline -crf 10 -f flv +;ffmpeg.vout=rtmp://live.eu.own3d.tv/live/b8493921_949585_kjhb5632l3l1 + +;# RTMP streaming: twitch.tv +;ffmpeg.parm=-af volume=3.000000 -r 10 -ss 00:00:00.500 -c:v libx264 -pix_fmt yuvj420p -preset:v veryfast -profile:v baseline -crf 10 -bufsize 1835k -f flv +;ffmpeg.vout=rtmp://live.justin.tv/app/live_sub_5b8ed4fae83fe83e920227337beab7e73b7a3ba3 + +[BETA128] +Beta128=1 +Noise=1 ; seek noise +Traps=1 ; intercept sector save/load routines in TR-DOS ROM for faster loading +Fast=1 ; disable all FDD/FDC delays. some programs will not work +IL=1 ; sector interleave (1=1:1, 2=2:1, 3=3:1) applied when loading TRD/SCL + ; if you prefer fast=0, set to 2 for 3d13 loaders, to 1 for turbo-loaders +BOOT=boot.$b + +[SOUND] +SoundFilter=0 ; lowpass filter (good for beeper) +SoundDrv=ds ; wave, ds or none +RejectDC=1 ; reject DC component of output sound + +; sound buffer size for SoundDrv=wave +; the more buffer, the more delay before sound playing... +; if sound is jerky, try SoundBuffer=16 +; for really fast computers with windows 2000, try SoundBuffer=4 +SoundBuffer=auto;10 ; only for SoundDrv=wave + +DSPrimary=0;1 ; use primary buffer for DSound. no sound in background. + ; useful only for troubleshooting + + +; when [SOUND] Enabled=0, emulator runs on maximum speed +Enabled=1 + +Fq=44100 ; check maximum sampling rate in your soundcard manual + ; setting freq not supported by hardware + ; will cause resampling and degrade quality and performance + +GSType=BASS ; BASS, Z80 or NONE +GSReset=1 ; reinit GS on reset + +SD=1 ; enable/disable soundrive +CovoxFB=1 ; enable/disable pentagon covox +CovoxDD=1 ; enable/disable scorpion covox + +Saa1099=0 ; enable/disable saa1099 +MoonSound=1 ; enable/disable moonsound + +; max sound volume is 8192. set to 0 to completely disable sound device +BeeperVol=8192 ; most popular sound device +MicOutVol=1000 ; tape saving +MicInVol=1000 ; tape loading +AYVol=6144 ; volume of each channel +CovoxFBVol=8192 ; pentagon covox +CovoxDDVol=6000 ; scorpion covox +CovoxProfiVol=6000 ; profi covox +SDVol=4000 ; soundrive +GSVol=8000 ; general sound (for both high- and low- level emulation modes) +BASSVol=8000 ; MOD-player of BASS.DLL +MoonSoundVol=8000 ; ZXM-MoonSound + +[NGS] ; neo gs settings +RamSize=2048 ; ngs ram size 2048, 4096 + +[ZC] ; Z-Controller settings +; sdcard image +SDCARD=../../tsconf.img +SDDelay=1500 ; delay before data token, T-states 3.5MHz + +[SAA1099] +FQ=8000000 + +[AY] +Chip=YM2203 ; bus reaction (none/AY/YM/YM2203) +FQ=1774400 ; chip clock fq +Stereo=ABC ; AY stereo mode - any of STEREO.* +VolTab=default.YM ; volume table - any of VOLTAB.* +UseSamples=0 ; 0 - square tones, 1 - digital samples (low quality) +; double-AY scheme: +; none - no soundchip +; default - single AY/YM +; PSEUDO - pseudo-turbo (one port for two devices + some phase shift) +; QUADRO - pentagon 2nd AY on #AFFD,#EFFD +; POS - power of sound turbo-AY (bit 0 of port #1F) +; CHRV - NedoPC turbo-sound (switch chip on selection reg #FE/FF) +; AYX32 - AYX32 chip +Scheme=CHRV + +; STEREO.PRESET_NAME=A_left,A_right,B_left,B_right,C_left,C_right +; (you can define your custom presets, all numbers are from [0; 100]) +STEREO.ABC=100,10,66,66,10,100 +STEREO.ACB=100,10,10,100,66,66 +STEREO.BAC=66,66,100,10,10,100 +STEREO.mono=66,66,66,66,66,66 +STEREO.unreal=90,20,66,66,20,90 + +; volume tables +VOLTAB.default.AY=0000,0340,04C0,06F2,0A44,0F13,1510,227E,289F,414E,5B21,7258,905E,B550,D7A0,FFFF +VOLTAB.default.YM=0000,0000,00EF,01D0,0290,032A,03EE,04D2,0611,0782,0912,0A36,0C31,0EB6,1130,13A0,1751,1BF5,20E2,2594,2CA1,357F,3E45,475E,5502,6620,7730,8844,A1D2,C102,E0A2,FFFF +VOLTAB.BULBA_OPEN.AY=0000,1111,2222,3333,4444,5555,6666,7777,8888,9999,AAAA,BBBB,CCCC,DDDD,EEEE,FFFF +VOLTAB.BULBA_OPEN.YM=0000,0000,041A,08D4,11A8,1A7B,234F,2C23,34F7,3DCB,469F,4F72,5846,611A,69EE,72C2,7B96,8469,8D3D,9611,9EE5,A7B9,B08D,B960,C234,CB08,D3DB,DCB0,E584,EE57,F72B,FFFF +VOLTAB.DELPHISPEC=0136,0170,01B5,0208,026A,02DF,0369,040E,04D2,05BB,06CF,0818,099F,0B6F,0D97,1026,1332,16D0,1B1D,203A,264D,2D85,361A,404D,4C6C,5AD4,6BF3,804D,987C,B53B,D764,FFFF +VOLTAB.X128=0000,0200,0500,0700,0A00,0C00,1200,1600,1D00,2200,2600,2C00,3F00,3F00,3F00,FF00 +VOLTAB.YASE=0000,0200,0500,0700,0A00,0C00,1200,1600,1D00,2200,2600,2C00,3F00,3F00,3F00,FF00 +VOLTAB.ZXMAK=0000,0000,00F8,01C2,029E,033A,03F2,04D7,0610,077F,090A,0A42,0C3B,0EC2,1137,13A7,1750,1BF9,20DF,2596,2C9D,3579,3E55,4768,54FF,6624,773B,883F,A1DA,C0FC,E094,FFFF +;original Lion17.YM=0000,0156,0228,033A,0502,086F,0CD4,17B9,1AF0,2E8D,4047,4F47,6528,7B19,92E1,AAAA +;original Lion17.AY=0000,0014,007F,00BF,00FA,0139,0175,01BB,0241,02F1,03F2,04B2,05DE,076B,08BA,0A0F +VOLTAB.Lion17.YM=0000,0201,033C,04D7,0783,0CA6,133E,2395,2868,45D3,606A,76EA,97BC,B8A5,DC51,FFFF +VOLTAB.Lion17.AY=0000,01FD,0CA0,12FD,18DA,1F1E,2515,2C0A,395C,4ADC,6468,777F,9552,BCCA,DE18,FFFF +;original Vaggelis Kapartzianis=00,03,04,05,06,07,08,0A,0C,0E,11,15,19,1D,23,2A +VOLTAB.Vaggelis Kapartzianis=0000,1249,1861,1E79,2492,2AAA,30C2,3CF3,4924,5555,679E,7FFF,9860,B0C2,D554,FFFF +VOLTAB.RAMSOFT.AY=0000,0364,0500,0700,0A80,1000,1600,2300,2A00,4300,5C00,7300,9180,B400,D800,FFFF +VOLTAB.RAMSOFT.YM=0000,0000,00F8,01C2,029E,033A,03F2,04D7,0610,077F,090A,0A42,0C3B,0EC2,1137,13A7,1750,1BF9,20DF,2596,2C9D,3579,3E55,4768,54FF,6624,773B,883F,A1DA,C0FC,E094,FFFF +VOLTAB.R80=0000,0201,033C,04D7,0783,0CA6,133E,2396,2868,45D4,606A,76EA,97BC,B8A6,DC52,FFFF + +[COLORS] +color=default + +; palette format: +; name=ZZ,ZN,NN,NB,BB,ZB:R11,R12,R13;R21;R22;R23;R31;R32;R33 +; where ZZ,NN,BB - bright levels for black,normal,and bright colors +; ZN,NB,ZB - bright levels for zero+normal, normal+bright, zero+bright +; for GIGASCREEN and FLASH-COLORS +; Rnn - matrix for post-processing: +; real_Red = (Red*R11 + Green*R12 + Blue*R13) / 0x100 +; real_Green = (Red*R21 + Green*R22 + Blue*R23) / 0x100 +; real_Blue = (Red*R31 + Green*R32 + Blue*R33) / 0x100 +; +; note: emulator uses 8-bit precision for color components, +; 8-bit filters: use 6 bit precision (VGA palette limitation) +; overlay & hi-color: truncates results to 5 bit +; true color: no quality loss +; +; R, G, B - 0..0xFF +; R11..R33 - 0..0x100 +; palette settings does not affect chunky modes +; +; default=00,80,C0,E0,FF,C8:100,00,00;00,100,00;00,00,100 + +Mars=00,80,C0,E0,FF,C8:100,00,00;40,C0,00;00,40,C0 +Ocean=20,80,A0,C0,E0,A8:D0,00,30;00,D0,30;00,00,100 +Grey=00,80,C0,E0,FF,C8:49,92,24;49,92,24;49,92,24 +alone=00,60,A0,E0,FF,A0:100,00,00;00,100,00;00,00,100 +pulsar=00,76,CD,E9,FF,9F:100,00,00;00,100,00;00,00,100 +ATM=00,55,AA,E0,FF,80:100,00,00;00,100,00;00,00,100 + +; presets for atari-like mode +; format: : [ , : ]* +; color is a mix of 2 pure spectrum colors, e.g. 11=red+red=red, 47=green+white=light-green, etc... +atari.fl_shark_red_palette=06:EE666600 +atari.test=70:00132177,0F:001199FF,34:33119944 + + +[HDD] +Scheme=NEMO-DIVIDE ; interface type: NONE/ATM/NEMO/NEMO-A8/NEMO-DIVIDE/SMUC/PROFI/DIVIDE +SkipReal=1 ; disable work with physical drives. faster startup when + ; you have no connected spectrum drive(s) + +CDROM=;ASPI;SPTI ; SPTI (native WinNT) or ASPI (use 3rd-party library wnaspi32.dll) + +; for image file, specify filename with full path, else it'll not work + +; master device. specify image filename or to use real drive +Image0=wc.img +CHS0=1572/16/63 ; max size, accessible through CHS. not used for real drive +LBA0=0 ; max size, accessible through LBA. not used for real drive +HD0RO=0 ; read only flag +CD0=0 ; 1 if image is cdrom + +; slave device. empty filename used for disconnected device +Image1= +;<_NEC CD-RW NR-7800B> +; +CHS1=1937/16/63 +LBA1=1950720 +HD1RO=0 +CD1=1 ; 1 if image is cdrom + +[ROM] +; general sound firmware +GS=rom\bootgs.rom +; moonsound wavetable +MOONSOUND=rom\YRW801-M - Yamaha - 1993.rom + + +; 64/128/512/1024K ROM images (single file) + +PENTAGON=rom\pentagon.rom + +ATM1=rom\atm1.rom + +;ATM2=Xbios103.rom +ATM2=rom\atm2.rom + +ATM3=rom\zxevo.rom + +; standard PROFI 64K rom +PROFI=rom\profi.rom + +; standard scorpion 64K rom +SCORP=rom\scorpion.rom + +; for SMUC 512k ROM, use file.rom:0 and file.rom:1 to select active bank +PROFROM=rom\scorp_prof401.ROM:0 + +; standard GMX 512K rom +GMX=rom\gmx.rom + +; standard KAY 64K rom +KAY=rom\kay1024.rom + +; TS-config +TSL=rom\zxevo.rom + +; LSY256 +LSY=rom\lsy256.rom + +; ZXM-Phoenix +PHOENIX=rom\ZXM-Phoenix_bios.bin + +; you can create own 64K-ROMSET, naming ini section [ROM.my_own_set] +; for empty ROMSET, single-file large ROM used (accordingly to memory model) +ROMSET= + +[ROM.default] +title=Separate ROMs set +; service rom [optional] +sys=rom\glukpen.rom +; basic48 - high 16K of sinclair rom [required!] +sos=rom\2006.rom +; tr-dos [optional] +dos=rom\dos6_10e.rom +; basic128 - low 16K of sinclair rom [optional] +128=rom\qc_3_05.rom + +[ROM.pentagon] +title=Pentagon-128 + TR-DOS +128=rom\128_low.rom +sos=rom\48for128.rom +dos=rom\trdos504t.ROM +sys=rom\service.rom + +[ROM.GLUK] +title=Mr Gluk Reset Service +128=rom\128.rom +sos=rom\48.rom +dos=rom\dos6_10e.rom +sys=rom\glukpen.rom + +[ROM.scorpion] +title=Scorpion (64K,v2.92) +128=rom\scorpion.rom:0 +sos=rom\scorpion.rom:1 +sys=rom\scorpion.rom:2 +dos=rom\scorpion.rom:3 + +[ROM.profi] +title=PROFI (64k) +sys=rom\profi.rom:0 +dos=rom\profi.rom:1 +128=rom\profi.rom:2 +sos=rom\profi.rom:3 + +[ROM.KAY] +title=KAY (64k) +128=rom\kay1024s.rom:0 +sos=rom\kay1024s.rom:1 +sys=rom\kay1024s.rom:2 +dos=rom\kay1024s.rom:3 + +[ROM.48K] +title=ZX-Spectrum 48K +sos=rom\48.rom + +[ROM.128K] +title=ZX-Spectrum 128K +128=rom\128.rom:0 +sos=rom\128.rom:1 + +[ROM.ATM1] +title=ATM1 +sos=rom\atm1.rom:0 +dos=rom\atm1.rom:1 +128=rom\atm1.rom:2 +sys=rom\atm1.rom:3 + +[ROM.ATM2] +title=ATM2 +sos=rom\atm2.rom:0 +dos=rom\atm2.rom:1 +128=rom\atm2.rom:2 +sys=rom\atm2.rom:3 + +[ROM.ATM3] +title=ATM3 +sos=rom\zxevo.rom:0 +dos=rom\zxevo.rom:13 +128=rom\zxevo.rom:2 +sys=rom\zxevo.rom:3 + +[LEDS] +; enable leds +leds=1 +; enable statis line +status=1 +; ay amplitude peaks on keyboard leds +KBD_AY=0 + +; led coordinates in pixels. comment out, if you don't want some led +; negative coordinates are offset from bottom and right +; WARNING! if you choose coordinates outside visible screen area +; emulator will crash + +; AY volumes. color indicates noise, envelope or digital sound use +AY=1:1,1 +; show Z80 t-states between HALTs and PC framerate +Perf=1:1,-11 +PerfShowT=0 ; display used CPU t-states + +; GS/TRDOS/TAPE read/write +LOAD=1:-24,1 +; active input devices: keyboard, mouse, kempston +Input=1:20,1 +; local time +Time=1:-32,-10 +; on-screen watches (debugger) +OSW=0:-50,26 +; zx-spectrum memory in dynamic: read/write/execute +MemBand=0:60,3 +; bytes per pixel in memory band (allowed 64,128,256,512) +BandBpp=256 + +[ARC] +; these files are ignored +SkipFiles=*.nfo;*.txt;*.diz;*.inf;files.bbs;descript.ion + +; you may add your own archivers! + +; command line to extract all files without pathnames and without +; queries on overwrite, use %s to indicate archive filename; +; use archivers, that understand long filenames! +CAB=extrac32 /E /Y "%s" +ZIP=pkzip25 -extract=all -overwrite=all "%s" +ZIP=pkunzip -o -Jahrs %s +ZIP=pkunzip -o %s +ZIP=pkunzip %s +RAR=rar32 e -y "%s" +RAR=rar e -y "%s" + +[SYSTEM.KEYS] + +; pc keyboard: +; +; ESC F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 PRSCR SCLOCK PAUSE +; TIL 1 2 3 4 5 6 7 8 9 0 MINUS PLUS BACK INS HOME PGUP NUMLOCK GRDIV GRMUL GRSUB +; TAB Q W E R T Y U I O P LB RB DEL END PGDN N7 N8 N9 GRADD +; CAPS A S D F G H J K L COL QUOTE ENTER N4 N5 N6 +; LSHIFT Z X C V B N M COMMA POINT SLASH RSHIFT BACKSL UP N1 N2 N3 GRENTER +; LCONTROL LALT SPACE RALT MENU RCONTROL LEFT DOWN RIGHT N0 NP + +; LMB,RMB,MMB - mouse buttons +; MWU,MWD - mouse wheel up/down +; JLEFT,JRIGHT,JUP,JDOWN,JFIRE - joystick/gamepad +; JB0..JB31 - joystick/gamepad buttons + +; mouse & joystick "keys" can't be bound to commands, only to spectrum keys + +; p.s. +; CONTROL = LCONTROL or RCONTROL +; ALT = LALT or RALT +; SHIFT = LSHIFT or RSHIFT +; numlock must be on for N0-N9 and NP + +; system commands: +; command=key1 [key2 [key3 [key4]]] + +; keys active in emulation mode + +main.exit=ALT F4 ; why do this? +main.lockmouse=SHIFT ESC ; read kempston mouse in windowed mode +main.monitor=TIL ; for hackers only +main.selectfilter=CONTROL F4 ; select video filter +main.visuals=F4 ; visuals window +main.selectdriver=SHIFT F4 ; select video driver for some filters +main.flictoggle=CONTROL SHIFT F4 ; toggle flic/noflic +main.fullscreen=ALT ENTER ; toggle full-screen on/off +main.pause=PAUSE ; pause +main.selectfix=GRMUL ; select video parameter +main.selectsnd=ALT GRMUL ; select soundchip parameter or palette +main.incfix=SHIFT GRADD ; fine tuneup +main.decfix=SHIFT GRSUB ; fine tuneup +main.incfix10=GRADD ; tuneup +main.decfix10=GRSUB ; tuneup +main.leds=GRDIV ; leds on/off +main.status=ALT GRDIV ; status line on/off +main.maxspeed=NUMLOCK ; max speed on/off +main.pokedialog=F6 ; enter poke(s) +main.starttape=F7 ; start/stop tape +main.tapebrowser=SHIFT F7 ; tape browser +main.screenshot=ALT F8 ; screenshot +main.shotclipbrd=SHIFT ALT F8 ; screenshot to clipboard +main.savevideo=CONTROL F8 ; save video +main.reset=F12 ; reset to [MISC]Reset=DOS/BASIC/MENU/SYS +main.reset128=CONTROL SHIFT F12 ; reset to basic 128 +main.resetsys=SHIFT F12 ; reset to service ROM +main.reset48=ALT SHIFT F12 ; reset to basic 48 with locked 128k memory +main.resetbasic=ALT F12 ; reset to basic 48 with unlocked 128k memory +main.resetdos=CONTROL F12 ; reset to TR-DOS +main.resetcache=ALT CONTROL F12 ; reset to cache +main.nmi=F11 ;ALT SHIFT F11 ; NMI without ROM switching +main.nmidos=CONTROL F11 ; TR-DOS magic +main.nmicache=ALT F11 ; NMI in cache +main.help=F1 ; show keys +main.settings=ALT F1 ; emulation settings (GUI) +main.save=F2 ; save disk/snapshot +main.load=F3 ; load disk/tape/snapshot +main.savesound=F5 ; save WAV/VTX +main.qsave1=ALT F2 ; quick save to qsave1.sna +main.qsave2=CONTROL F2 ; quick save to qsave2.sna +main.qsave3=SHIFT F2 ; quick save to qsave3.sna +main.qload1=ALT F3 ; quick load from qsave1.sna +main.qload2=CONTROL F3 ; quick load from qsave2.sna +main.qload3=SHIFT F3 ; quick load from qsave3.sna +main.keystick=SCLOCK ; toggle keystick mode +main.autofire=ALT NUMLOCK ; toggle autofire mode +main.saveall=F9 ; quick save cmos and disks +main.saveram=ALT F9 ; quick save RAM dump +main.xtkbd=ALT CONTROL Z ; enable/disable ATM XT keyboard (emulation controls blocked!) +main.pastetext=ALT SHIFT INS ; copy text from clipboard to keyboard buffer +main.size1=ALT 1 ; set normal window size (in windowed mode) +main.size2=ALT 2 ; set double window size (in windowed mode) +main.sizem=ALT 3 ; maximize window (in windowed mode) +main.memsearch=ALT F6 ; search in memory for changed values (cheats, etc) +main.tsutoggle=ALT CONTROL TIL ; toggle TSU layers + +; keys in all monitor windows + +mon.cpu=CONTROL TIL ; switch to next cpu +mon.exit=ALT F4 ; +mon.emul=TIL ; back to emulation +mon.bpdialog=ALT C ; conditional breakpoints dialog +mon.gs=ALT G ; gs status dialog +mon.pokedialog=ALT P ; enter poke(s) F6 is already used +mon.tapebrowser=SHIFT F7 ; tape browser +mon.reset=F12 ; reset to [MISC]Reset=DOS/BASIC/MENU/SYS +mon.reset128=CONTROL SHIFT F12 ; reset to basic 128 +mon.resetsys=ALT F12 ; reset to service ROM +mon.reset48=ALT SHIFT F12 ; reset to basic 48 with locked 128k memory +mon.resetbasic=SHIFT F12 ; reset to basic 48 with unlocked 128k memory +mon.resetdos=CONTROL F12 ; reset to TR-DOS +mon.resetcache=ALT CONTROL F12 ; reset to cache +mon.nmi=ALT SHIFT F11 ; NMI without ROM switching +mon.nmidos=CONTROL F11 ; TR-DOS magic +mon.nmicache=ALT F11 ; NMI in cache +mon.help=F1 ; show keys +mon.settings=ALT F1 ; emulation settings (GUI) +mon.save=F2 ; save disk/snapshot +mon.load=F3 ; load disk/tape/snapshot +mon.savesound=F5 ; save WAV/VTX +mon.qsave1=ALT F2 ; quick save to qsave1.sna +mon.qsave2=CONTROL F2 ; quick save to qsave2.sna +mon.qsave3=SHIFT F2 ; quick save to qsave3.sna +mon.qload1=ALT F3 ; quick load from qsave1.sna +mon.qload2=CONTROL F3 ; quick load from qsave2.sna +mon.qload3=SHIFT F3 ; quick load from qsave3.sna +mon.setrange=F6 ; set range of breakpoints +mon.resetrange=SHIFT F6 ; reset range of breakpoints +mon.screen=F9 ; show screen, as it present in video memory +mon.altscreen=SHIFT F9 ; show inactive screen +mon.rayscreen=ALT F9 ; show painted screen area until crt-ray (with MC, border & 2-scr effects) +mon.step=F7 ; single step +mon.stepover=F8 ; step over loops and calls +mon.exitsub=F11 ; trace until (sp) +mon.loadblock=ALT R ; load memory block +mon.saveblock=ALT W ; save memory block +mon.fillblock=ALT F ; fill memory block with pattern +mon.setbank=ALT B ; write to #7FFD +mon.sethimem=ALT M ; write to model-specific extended memory port +mon.next=TAB ; next window +mon.prev=SHIFT TAB ; prev window +mon.dump=ALT D ; toggle dump +mon.rip=ALT T ; ripper's tool +mon.osw=ALT O ; setup on-screen watches +mon.scrshot=ALT S ; toggle scrdump/watches +mon.scrshot_alt=ALT SHIFT S ; toggle main/alt screen in screendump +mon.setwatch=CONTROL U ; set user-defined watch address +mon.switchay=ALT Y ; switch active AY (turbo-sound) +mon.labels=CONTROL J ; goto label +mon.memsearch=ALT F6 ; search in memory for changed values (cheats, etc) +mon.switchdump=CONTROL D ; CMOS/NVRAM editor + +; keys in registers window + +reg.left=LEFT ; select register +reg.right=RIGHT ; select register +reg.up=UP ; select register +reg.down=DOWN ; select register +reg.edit=ENTER ; edit current register +reg.a=CONTROL A ; edit A +reg.f=CONTROL F ; edit flags +reg.bc=CONTROL B ; edit BC +reg.de=CONTROL D ; edit DE +reg.hl=H ; edit HL +reg.pc=P ; edit PC +reg.sp=S ; edit SP +reg.ix=X ; edit IX +reg.iy=Y ; edit IY +reg.i=I ; edit i +reg.r=R ; edit R +reg.im=M ; edit im +reg.iff1=CONTROL 1 ; edit iff1 +reg.iff2=CONTROL 2 ; edit iff2 +reg.SF=ALT S ; toggle SF +reg.ZF=ALT Z ; toggle ZF +reg.F5=ALT 5 ; toggle F5 +reg.HF=ALT H ; toggle HF +reg.F3=ALT 3 ; toggle F3 +reg.PF=ALT P ; toggle PF/PV +reg.NF=ALT N ; toggle NF +reg.CF=ALT C ; toggle CF +reg.codejump=QUOTE ; goto address in code window under cursor +reg.datajump=COL ; goto address in data window under cursor + +; keys in cpu window + +cpu.findpc=HOME ; set cursor at PC +cpu.here=F4 ; trace to cursor +cpu.findtext=ALT F7 ; find string +cpu.findcode=CONTROL F7 ; find code with mask +cpu.goto=G ; goto address +cpu.bpx=SPACE ; breakpoint under cursor +cpu.asm=ENTER ; edit instruction +cpu.setpc=Z ; set PC to cursor +cpu.up=UP ; move cursor up +;cpu.up=MWU ; move cursor up +cpu.down=DOWN ; move cursor down +;cpu.down=MWD ; move cursor down +cpu.left=LEFT ; move cursor left +cpu.right=RIGHT ; move cursor right +cpu.pgdn=PGDN ; next page +cpu.pgup=PGUP ; prev page +cpu.save1=CONTROL 1 ; save cursor position to slot 1 +cpu.save2=CONTROL 2 +cpu.save3=CONTROL 3 +cpu.save4=CONTROL 4 +cpu.save5=CONTROL 5 +cpu.save6=CONTROL 6 +cpu.save7=CONTROL 7 +cpu.save8=CONTROL 8 +cpu.rest1=1 ; save cursor position to jumpstack, load from slot 1 +cpu.rest2=2 +cpu.rest3=3 +cpu.rest4=4 +cpu.rest5=5 +cpu.rest6=6 +cpu.rest7=7 +cpu.rest8=8 +cpu.back=BACK ; pop cursor position from jumpstack +cpu.context=QUOTE ; push cursor position and goto instruction operand +cpu.datajump=COL ; jump to instruction operand in data window +cpu.labels=CONTROL L ; show labels on/off +cpu.importl=CONTROL A ; menu for importing labels from XAS/ALASM + +; keys in memory dump window + +mem.left=LEFT ; cursor left +mem.right=RIGHT ; cursor right +mem.up=UP ; cursor up +mem.down=DOWN ; cursor down +mem.pgup=PGUP ; page up +mem.pgdn=PGDN ; page down +mem.switch=CONTROL TAB ; move to hex/ascii +mem.stline=HOME ; start of line +mem.endline=END ; end of line +mem.findtext=ALT F7 ; find text in memory / disk track +mem.findcode=CONTROL F7 ; find code with mask in memory / disk track +mem.goto=CONTROL G ; goto address +mem.mem=CONTROL M ; select memory editor +mem.diskphys=CONTROL V ; select disk editor (physical track) +mem.disklog=CONTROL O ; select disk editor (logical sectors) +mem.diskgo=CONTROL T ; select drive/track/sector in diskeditor +mem.pc=CONTROL P ; goto PC +mem.sp=CONTROL S ; goto SP +mem.bc=CONTROL B ; goto BC +mem.de=CONTROL D ; goto DE +mem.hl=CONTROL H ; goto HL +mem.ix=CONTROL X ; goto IX +mem.iy=CONTROL Y ; goto IY + + + +[ZX.KEYS.default] + +Name=unreal default + +; ZX keyboard layout: +; +; 1 2 3 4 5 6 7 8 9 0 +; Q W E R T Y U I O P +; A S D F G H J K L ENT +; CAP Z X C V B N M SYM SPC + +; KLEFT,KRIGHT,KUP,KDOWN,KFIRE - kempston joystick + +; format: +; PC_KEY=[ZX_KEY_SHIFT] ZX_KEY + +1=1 +2=2 +3=3 +4=4 +5=5 +6=6 +7=7 +8=8 +9=9 +0=0 +Q=Q +W=W +E=E +R=R +T=T +Y=Y +U=U +I=I +O=O +P=P +A=A +S=S +D=D +F=F +G=G +H=H +J=J +K=K +L=L +Z=Z +X=X +C=C +V=V +B=B +N=N +M=M +TIL=CAP 1 +CAPS=CAP 2 +PGUP=CAP 3 +PGDN=CAP 4 +LEFT=CAP 5 +DOWN=CAP 6 +UP=CAP 7 +RIGHT=CAP 8 +DEL=CAP 9 +BACK=CAP 0 +MINUS=SYM J +PLUS=SYM K +COMMA=SYM N +POINT=SYM M +ENTER=ENT +SPACE=SPC +LB=SYM 8 +RB=SYM 9 +COL=SYM Z +TAB=CAP SPC ; break +INS=SYM W +HOME=SYM Q +END=SYM E +QUOTE=SYM P +SLASH=SYM C +BACKSL=SYM CAP + +LSHIFT=CAP +RSHIFT=SYM + +N4=KLEFT +N6=KRIGHT +N8=KUP +N2=KDOWN +N5=KFIRE +N1=KLEFT KDOWN +N3=KRIGHT KDOWN +N7=KUP KLEFT +N9=KUP KRIGHT + +MWU=CAP 7 +MWD=CAP 6 + +JLEFT=KLEFT +JRIGHT=KRIGHT +JUP=KUP +JDOWN=KDOWN +JFIRE=KFIRE + +[ZX.KEYS.XAS] + +Name=XAS 7.xx (incomplete) + +1=1 +2=2 +3=3 +4=4 +5=5 +6=6 +7=7 +8=8 +9=9 +0=0 +Q=Q +W=W +E=E +R=R +T=T +Y=Y +U=U +I=I +O=O +P=P +A=A +S=S +D=D +F=F +G=G +H=H +J=J +K=K +L=L +Z=Z +X=X +C=C +V=V +B=B +N=N +M=M +SHIFT=CAP +CONTROL=SYM + +[ZX.KEYS.ACEDIT] + +; layout created by Alone Coder + +Name=ACEdit + +1=1 +2=2 +3=3 +4=4 +5=5 +6=6 +7=7 +8=8 +9=9 +0=0 +Q=Q +W=W +E=E +R=R +T=T +Y=Y +U=U +I=I +O=O +P=P +A=A +S=S +D=D +F=F +G=G +H=H +J=J +K=K +L=L +Z=Z +X=X +C=C +V=V +B=B +N=N +M=M +TIL=CAP 1 +CAPS=CAP 2 +PGUP=CAP 3 +PGDN=CAP 4 +LEFT=CAP 5 +DOWN=CAP 6 +UP=CAP 7 +RIGHT=CAP 8 +DEL=CAP 9 +BACK=CAP 0 +MINUS=SYM J +PLUS=SYM L +COMMA=SYM N +POINT=SYM M +ENTER=ENT +SPACE=SPC +LB=SYM 8 +RB=SYM 9 +COL=SYM O +TAB=SYM CAP +ESC=CAP SPC +INS=SYM W +HOME=SYM Q +END=SYM E +QUOTE=SYM P +SLASH=SYM C +BACKSL=SYM Z + +SHIFT=CAP +CONTROL=SYM + +N4=KLEFT +N6=KRIGHT +N8=KUP +N2=KDOWN +N5=KFIRE +N1=KLEFT KDOWN +N3=KRIGHT KDOWN +N7=KUP KLEFT +N9=KUP KRIGHT +GRSUB=SYM J +GRADD=SYM K +GRMUL=SYM B +GRDIV=SYM V + +[ZX.KEYS.Z80Stealth] + +Name=Z80Stealth default + +; ZX keyboard layout: +; +; 1 2 3 4 5 6 7 8 9 0 +; Q W E R T Y U I O P +; A S D F G H J K L ENT +; CAP Z X C V B N M SYM SPC + +; KLEFT,KRIGHT,KUP,KDOWN,KFIRE - kempston joystick + +; format: +; PC_KEY=[ZX_KEY_SHIFT] ZX_KEY + +1=1 +2=2 +3=3 +4=4 +5=5 +6=6 +7=7 +8=8 +9=9 +0=0 +Q=Q +W=W +E=E +R=R +T=T +Y=Y +U=U +I=I +O=O +P=P +A=A +S=S +D=D +F=F +G=G +H=H +J=J +K=K +L=L +Z=Z +X=X +C=C +V=V +B=B +N=N +M=M +TIL=CAP 1 +CAPS=CAP 2 +PGUP=CAP 3 +PGDN=CAP 4 +LEFT=CAP 5 +DOWN=CAP 6 +UP=CAP 7 +RIGHT=CAP 8 +DEL=CAP 9 +BACK=CAP 0 +MINUS=SYM J +PLUS=SYM K +COMMA=SYM N +POINT=SYM M +ENTER=ENT +SPACE=SPC +LB=SYM 8 +RB=SYM 9 +COL=SYM Z +TAB=CAP SPC ; break +INS=SYM W +HOME=SYM Q +END=SYM E +QUOTE=SYM P +SLASH=SYM C +BACKSL=SYM CAP + +RALT=SYM +LSHIFT=CAP +RSHIFT=CAP +RCONTROL=CAP SYM + +N4=KLEFT +N6=KRIGHT +N8=KUP +N2=KDOWN +N5=KFIRE +N1=KLEFT KDOWN +N3=KRIGHT KDOWN +N7=KUP KLEFT +N9=KUP KRIGHT + +MWU=CAP 7 +MWD=CAP 6 + +JLEFT=KLEFT +JRIGHT=KRIGHT +JUP=KUP +JDOWN=KDOWN +JFIRE=KFIRE + +[ZX.KEYS.quorum] +Name=Quorum + +; Quorum keyboard layout: +; +; F1 F2 F3 F4 F5 [G] NMI INV [< RES +; [E] 1 2 3 4 5 6 7 8 9 0 - + BS DEL NSLASH NSTAR NMINUS +; TAB Q W E R T Y U I O P [ ] \ N7 N8 N9 +; CAPS A S D F G H J K L ; ' ~ ENT N4 N5 N6 +; RUS LAT Z X C V B N M , . / UP N1 N2 N3 +; CAP FIRE SPC SYM LT DN RT N0 NPOINT NPLUS +; +; FIRE,LT,DN,RT,UP=0,5,6,8,7 +; NMI,RES=F11,F12(see main.*) +; INV,[< - NOT emulated +; KLEFT,KRIGHT,KUP,KDOWN,KFIRE - kempston joystick +; (JLEFT,JRIGHT,JUP,JDOWN,JFIRE reserved for joystick) +; format: +; PC_KEY=[ZX_KEY_SHIFT] ZX_KEY +1=1 +2=2 +3=3 +4=4 +5=5 +6=6 +7=7 +8=8 +9=9 +0=0 +Q=Q +W=W +E=E +R=R +T=T +Y=Y +U=U +I=I +O=O +P=P +A=A +S=S +D=D +F=F +G=G +H=H +J=J +K=K +L=L +Z=Z +X=X +C=C +V=V +B=B +N=N +M=M +ESC=EBOX +DEL=BS +TIL=TILDA +CAPS=CAPS +LEFT=5 +DOWN=6 +UP=7 +RIGHT=8 +BACK=DEL +MINUS=- +PLUS=+ +COMMA=, +POINT=. +ENTER=ENT +SPACE=SPC +LB=[ +RB=] +COL=COLON +TAB=TAB +;HOME=N7 +;END=N1 +;PGUP=N8 +;PGDN=N2 +N1=N1 +N2=N2 +N3=N3 +N4=N4 +N5=N5 +N6=N6 +N7=N7 +N8=N8 +N9=N9 +N0=N0 +NP=NPOINT +QUOTE=QUOTE +SLASH=/ +BACKSL=\ +F1=F1 +F2=F2 +F3=F3 +F4=F4 +F5=F5 +F6=GBOX +GRDIV=NSLASH +GRMUL=NSTAR +GRSUB=NMINUS +GRADD=NPLUS +PGUP=RUS +PGDN=LAT + +LSHIFT=CAP +RSHIFT=SYM + +;N4=KLEFT +;N6=KRIGHT +;N8=KUP +;N2=KDOWN +;N5=KFIRE +;N1=KLEFT KDOWN +;N3=KRIGHT KDOWN +;N7=KUP KLEFT +;N9=KUP KRIGHT diff --git a/tools/unreal/ZEUSTOT2.SCL b/tools/unreal/ZEUSTOT2.SCL new file mode 100644 index 0000000..dbf5468 Binary files /dev/null and b/tools/unreal/ZEUSTOT2.SCL differ diff --git a/tools/unreal/bass.dll b/tools/unreal/bass.dll new file mode 100644 index 0000000..825b68e Binary files /dev/null and b/tools/unreal/bass.dll differ diff --git a/tools/unreal/boot.$b b/tools/unreal/boot.$b new file mode 100644 index 0000000..70601d1 Binary files /dev/null and b/tools/unreal/boot.$b differ diff --git a/tools/unreal/bpx.ini b/tools/unreal/bpx.ini new file mode 100644 index 0000000..e69de29 diff --git a/tools/unreal/bt8xxemu.dll b/tools/unreal/bt8xxemu.dll new file mode 100644 index 0000000..56c6106 Binary files /dev/null and b/tools/unreal/bt8xxemu.dll differ diff --git a/tools/unreal/doc/help_eng.html b/tools/unreal/doc/help_eng.html new file mode 100644 index 0000000..807238c --- /dev/null +++ b/tools/unreal/doc/help_eng.html @@ -0,0 +1,536 @@ + + + + UnrealSpeccy: current key bindings + + + + + + +

Emulator keys

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{main.exit} exit emulator
{main.settings} configure emulator
{main.lockmouse} bind mouse in windowed mode to kempston mouse
{main.monitor} enter debugger
{main.selectfilter} select video filter for output
{main.selectdriver} select driver for filters, working through driver
{main.flictoggle} toggle flic/noflic
{main.fullscreen} toggle full-screen on/off (only for GDI, Overlay, or ddraw blitter video modes)
{main.pause} pause
{main.selectfix} select video parameter for tune-up
{main.selectsnd} select sound parameter for tune-up
{main.incfix} increase selected parameter
{main.decfix} decrease selected parameter
{main.incfix10} coarse increase selected parameter
{main.decfix10} coarse decrease selected parameter
{main.leds} toggle on-screen leds
{main.status} toggle status line
{main.maxspeed} toggle max. speed
{main.pokedialog} show poke dialog
{main.starttape} start tape
{main.tapebrowser} show tape browser
{main.screenshot} write screenshot
{main.shotclipbrd} copy screenshot to clipboard
{main.reset} reset to default mode
{main.reset128} reset to 128K
{main.resetsys} reset to service ROM
{main.reset48} reset to 48K
{main.resetbasic} reset to 48K basic with unlocked high memory
{main.resetdos} reset to TR-DOS
{main.resetcache} reset to cache
{main.nmi} generate NMI
{main.nmidos} select TR-DOS and generate NMI ("MAGIC" button)
{main.nmicache} select cache and generate NMI
{main.help} show this help
{main.save} save disk or memory image
{main.load} load disk or memory image
{main.savesound} start sound recording
{main.qsave1} quick-save to slot 1
{main.qsave2} quick-save to slot 2
{main.qsave3} quick-save to slot 3
{main.qload1} quick-load from slot 1
{main.qload2} quick-load from slot 2
{main.qload3} quick-load from slot 3
{main.keystick} toggle sticky-keys mode
{main.autofire} toggle autofire mode
{main.saveram} save RAM dump
{main.saveall} save CMOS and unsaved diskette images
{main.xtkbd} enable/disable ATM XT keyboard controller (emulation control keys will be disabled in this mode)
{main.pastetext} copy text from clipboard to keyboard buffer
{main.memsearch} search in memory for changed values (cheats, etc)
{main.size1} set normal window size in windowed mode
{main.size2} set double window size in windowed mode
{main.sizem} maximize window in windowed mode
{main.tsutoggle} toggle TSU layers
+ + + + +

Debugger Keys

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{mon.exit} exit to system
{mon.emul} back to emulation
{mon.settigns} configure emulator
{mon.bpdialog} breakpoints manager
{mon.gs} general sound status dialog
{mon.pokedialog} enter pokes
{mon.tapebrowser} tape browser
{mon.reset128} reset to 128K
{main.resetsys} reset to service ROM
{mon.reset48} reset to 48K
{mon.resetbasic} reset to 48K basic with unlocked high memory
{mon.resetdos} reset to TR-DOS
{mon.resetcache} reset to cache
{mon.nmi} generate NMI
{mon.nmidos} select TR-DOS and generate NMI ("MAGIC" button)
{mon.nmicache} select cache and generate NMI
{mon.help} show this help
{mon.save} save disk or memory image
{mon.load} load disk or memory image
{mon.savesound} start sound recording
{mon.qsave1} quick-save to slot 1
{mon.qsave2} quick-save to slot 2
{mon.qsave3} quick-save to slot 3
{mon.qload1} quick-load from slot 1
{mon.qload2} quick-load from slot 2
{mon.qload3} quick-load from slot 3
{mon.screen} show spectrum screen, as it present in video memory
{mon.altscreen} show 128K spectrum screen at bank#7
{mon.rayscreen} show painted screen area until crt-ray (with MC, border & 2-scr effects)
{mon.step} trace single Z80 M1 cycle
{mon.stepover} trace Z80 over loops, calls, etc...
{mon.exitsub} trace until word in (PC) (designed for 'exit routine')
{mon.loadblock} load memory block
{mon.saveblock} save/disasm memory block
{mon.fillblock} fill memory block with pattern
{mon.setbank} write to port #7FFD
{mon.sethimem} write to model-specific extended memory port
{mon.next} next window
{mon.prev} prev. window
{mon.dump} toggle mode of dump window
{mon.rip} activate ripper's tool
{mon.osw} configure on-screen watches
{mon.scrshot} toggle screenshot/screendump/watches
{mon.setwatch} set user-defined watch address
{mon.switchay} switch active AY (turbo-sound)
{mon.labels} goto label
{mon.memsearch} search in memory for changed values (cheats, etc)
+ +

Debugger Keys - registers window

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{reg.left} left
{reg.right} right
{reg.up} up
{reg.down} down
{reg.edit} edit current register
{reg.a} edit register 'A'
{reg.f} edit flags
{reg.bc} edit BC
{reg.de} edit DE
{reg.hl} edit HL
{reg.pc} edit PC
{reg.sp} edit SP
{reg.ix} edit IX
{reg.iy} edit IY
{reg.i} edit I
{reg.r} edit R
{reg.im} toggle interrupt mode
{reg.iff1} toggle iff1
{reg.iff2} toggle iff2
{reg.SF} toggle flag SF
{reg.ZF} toggle flag ZF
{reg.F5} toggle flag F5
{reg.HF} toggle flag HF
{reg.F3} toggle flag F3
{reg.PF} toggle flag PF
{reg.NF} toggle flag NF
{reg.CF} toggle flag CF
{reg.codejump} goto address in code window under cursor
{reg.datajump} goto address in data window under cursor
+ +

Debugger Keys - disasm window

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{cpu.findpc} set cursor at PC
{cpu.here} trace to cursor
{cpu.findtext} find string
{cpu.findcode} find code with mask
{cpu.goto} push cursor position and goto address
{cpu.bpx} breakpoint under cursor
{cpu.asm} edit instruction
{cpu.setpc} set PC to cursor
{cpu.up} up
{cpu.down} down
{cpu.left} left
{cpu.right} right
{cpu.pgdn} next page
{cpu.pgup} prev. page
{cpu.save1} save cursor position to slot 1
{cpu.save2} save cursor position to slot 2
{cpu.save3} save cursor position to slot 3
{cpu.save4} save cursor position to slot 4
{cpu.save5} save cursor position to slot 5
{cpu.save6} save cursor position to slot 6
{cpu.save7} save cursor position to slot 7
{cpu.save8} save cursor position to slot 8
{cpu.rest1} push cursor position to jumpstack and restore cursor position from slot 1
{cpu.rest2} push cursor position to jumpstack and restore cursor position from slot 2
{cpu.rest3} push cursor position to jumpstack and restore cursor position from slot 3
{cpu.rest4} push cursor position to jumpstack and restore cursor position from slot 4
{cpu.rest5} push cursor position to jumpstack and restore cursor position from slot 5
{cpu.rest6} push cursor position to jumpstack and restore cursor position from slot 6
{cpu.rest7} push cursor position to jumpstack and restore cursor position from slot 7
{cpu.rest8} push cursor position to jumpstack and restore cursor position from slot 8
{cpu.back} pop cursor position from jumpstack
{cpu.context} push cursor position and goto instruction operand
{cpu.datajump} jump to instruction operand in data window
{cpu.labels} show labels on/off
{cpu.importl} menu for importing labels from XAS/ALASM
+ +

Debugger Keys - memory/disk editor window

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{mem.left} left
{mem.right} right
{mem.up} up
{mem.down} down
{mem.pgup} page up
{mem.pgdn} page down
{mem.switch} move between hex/ascii fields
{mem.stline} goto start of line
{mem.endline} goto end of line
{mem.findtext} find text in memory / disk track
{mem.findcode} find code with mask in memory / disk track
{mem.goto} goto address
{mem.mem} select memory editor
{mem.diskphys} select disk editor (physical track)
{mem.disklog} select disk editor (logical sectors)
{mem.diskgo} select drive/track/sector in diskeditor
{mem.pc} goto PC
{mem.sp} goto SP
{mem.bc} goto BC
{mem.de} goto DE
{mem.hl} goto HL
{mem.ix} goto IX
{mem.iy} goto IY
+ + diff --git a/tools/unreal/doc/history.txt b/tools/unreal/doc/history.txt new file mode 100644 index 0000000..56efefd --- /dev/null +++ b/tools/unreal/doc/history.txt @@ -0,0 +1,1209 @@ +[!] - Исправлена серьезная ошибка +[+] - Добавлен новый функционал +[-] - Убран ненужный/устаревший функцинал +[*] - Мелкие исправления +[@] - Функционал изменен +[?] - Функционал добавлен но не проверен + + version 0.01b 3 sep 2001 + +* first version + + + + version 0.02b 6 sep 2001 + +* i was very surprised with a lot of e-mails, that came in two days after + first release; thank you all for support! + +! fixed bug with mouse cursor visible, fixed settings dialog + for leds and memory model [suggested by Maxim A. Vasilyev] + ++ disk rotation emulated [thanks to Mac Buster for Beta 128 timings] + + + version 0.03b 10 sep 2001 + +! fixed bug when TRDOS reads index even if no disk, that appared in 0.02b +! fixed kempston mouse [credits to Kuba Adamowicz] ++ added SwapButtons and MouseSpeed +! fixed small bug in tape traps ++ fully customizable keyboard ++ kempston joystick on keyboard ++ keymatrix effect ++ 1T screen update for best border effects and multicolors ++ faster screen output mode without border and multicolors for slow PCs ++ load from archives and archives inside archives ;=) +! fixed bug when boot not attached to SCL +* several speed optimizations ++ new video filter - chunky 2x2 ++ new video filter - smart scaling. sorry, not optimized yet + + + version 0.04b 17 sep 2001 + +! changed load dialog [suggested by Maxim Vasilyev] +* 'smart scaling' filter optimized +! fixed directory for qsave/qload ++ FDI read/write, but can't format and use write_protection flag +! fixed HALT emulation +! fixed fadeout (envelope #01) and envelope #0E on AY chip ++ all syncronization in INI: monitor refresh, int freq, VSync option, etc... + read how to make smooth screen repaint +! more accurate AY tones, especially high frequencies ++ new video filter - 'nice colors' + + + version 0.05b 24 sep 2001 + +! fixed one more bug in load dialog [reported by Maxim Vasilyev] ++ preset for AY Chip = none [suggested by Maxim Vasilyev] ++ added YM chip: sound is same with AY, but FFFD behavior is different +! small changes in beta 128 emulation ++ emulated write protection flag (FDI-from header, TRD,SCL-from R/O attribute) ++ multiple command lines for one archive format [suggested by Maxim Vasilyev] +! fixed (finally!) AY envelope ++ General Sound (MOD player not complete) [credits to Shiru Otaku for GSPAK.ZIP] ++ memory models: pentagon-1024, extension on ports #DFFD,#1FFD,#FDFD ++ display exception position and registers on crash (for bugreports) + + version 0.06b 26 sep 2001 + +! fixed AY envelope [ looks like a tradition ;-) ] - now really, i hope +! logarithmic volume tables for General Sound +! many improvements in MOD-player, but still not complete ++ reads zero-padded .TAP files [suggested by Georgy Lomsadze] ++ monitor shows contents of memory extension port +* changed fps led [suggested by Vitaliy Pigo] +! fixed bug when GS (almost) always plays wrong sample ++ digital filter for general sound ++ kempston joystick on mouse - not really playable, just for fun + + + version 0.07b 1 oct 2001 + +* yesterday i've received 100th e-mail concerning emulator :) + no doubt - ZX platform will never die ! +! works in windows NT4 [bug reported by Georgy Lomsadze] +! fixed terrible bug in GS sample player [thanks to Vitaliy Pigo] +* INI option: LEDS\PerfShowT, coz some people wants to see t-states, many don't +! mod-player: set tempo ignored (what the difference in speed and tempo?) + now music in 'green beret' works better [bugreported by Moroz] +! killed clicks in GS player [bugreported by Moroz] ++ conditional breakpoints (uses c-like expressions of any complexity); breaks + to range, port in/out, on instruction, on memory change superseded by this + powerful feature and removed as obsolete. RTFM before use +@ fixed blah-blah in documentation about 'disk spinning is not emulated' + it's emulated since version 0.02 +! monitor: enabled save and load block (just forgot to bind keys to this funcs) + strange, that nobody noticed that +! monitor: fixed bug in 'set bank' +! better TR-DOS, now Unreal Megademo (by KSA) works, if trdos traps disabled. + (Z80S, R80, RealSpectrum failed to emulate this demo) [bugreported by Drew] ++ runs without soundcard (sound\fq=0 in INI) [suggested by Alexey Kravchenko] ++ INI option for autosave TRD/FDI ++ monitor: general sound status/fxplayer dialog +- removed memory ATM-1024 ++ MMX simple video filter - it's first assembly routine in emul. + in future versions i'll replace most filters using MMX and + bilinear CPU expensive filters using 3DNow! ++ unbelievable video filter - chunky filtered (look to REFRESH or LIFEFORMS) + not fully optimized, but will be recoded with 3DNow! in future versions + [thanx to Mr.Dsteuz//Proxium for this excellent idea] + + + version 0.08b 8 oct 2001 + +! fixed MOD-saver [reported by Shiru Otaku] ++ INI: MISC\DIR [suggested by too many people to list them all] +! fixed portz selection (for RST7 crazy coding style :) [thanks to Paul Pavlov] +! killed screen flicks when tracing on F8 [reported by Max Vasilyev] ++ monitor: added stack window +* changed font in monitor: it's grievous, that people prefer PC font instead of + native Speccy charsets, but i've added a nice gfx as a compensation :) + [stolen from XL-DESIGN - sorry, guys] ++ able to debug borders/multicolors: screen updates when tracing with F8 ++ emulated pentagon with AY on 3.5 MHz [suggested by Shiru Otaku] ++ changed GS dialog +! a bit fixed INT signal, but not perfect - differs by 1T from real pentagon :( +* some video filters removed, some mmx-optimized ++ video: motion blur for jumpin scrolls[thanks to Ms.Dsteuz for fast algorithm] +! able to load uncompressed Z80 2.01 [thanks to Dmitry Sanarin for files] ++ monitor: watches ++ monitor: text dump mode [suggested by Max Vasilyev and Ironfist] ++ border only mode for debugging border effects [suggested by Mad Cat] ++ INI option: MISC\RESET +! monitor: fixed PgUp/PgDn [reported by Ironfist] +! fixed terrible bug in tape loader (with max speed) [thanks to Dmitry Sanarin] +! fixed speech on rasputin & robocop [reported by Dmitry Sanarin], but added + new bug: AY does not play when tone period = 0 +! separate tables for AY and YM +! AY: rewritten noise, fixed period, noise rand() from AY_Emul + + + version 0.09b 10 oct 2001 + +! fixed rare bug in AY +! changed BMP-saver [suggested by diver] +! changed palettes for all chunky filters, small changes in chunkFX filter + + + version 0.10b not released to public + +! fixed clicks in mod-player [bugreported by Moroz] +! fixed diskette saving led (broken in version 0.07) +* speed optimizations in video (using 486+ opcodes in conjuction with MMX) +* one filter rewritten in MMX +! changed SNA loader - now may load incorrect files [bug found by Ironfist] ++ hotkeys for selectin' AY/YM,ABC/ACB on the fly [suggested by Max Vasilyev] +! fixed bug in BMP screenshot for 'small' and 'scanlines' modes +! emulated GS bug: cmd #3E loads _*unsigned*_ samples [thanks to Moroz] +* tested monitor with 100Hz refresh - great! for setting rate in windows 95 + use PowerStrip or similar utility, for NT/2000 edit emul.ini +! undone INT changes, made in 0.08 + + + version 0.11b 18 oct 2001 + +! monitor: bit 6 in beta 128 port #FF displays right [thanks to Alex Makeev] +! monitor: repaint screen on task switching [bugreported by Alex Makeev] +* documented many bugs - read unreal.txt +! changed palette for noflic2 [suggested by diver] +! black background in 'small' and 'scanlines' filters [suggested by many people] +! fixed loading of too long tapes [bugreported by Andrew Moonlight] ++ video filter: using hardware blitter [idea by Alex Baskakov] ++ monitor: stepover(F8) trace rst/call with stack params at one step (rst#28) ++ sticky keys mode [completely stolen from ZX_Emul, read description there] + + + version 0.12b 22 oct 2001 + +* fantastic (>50%) speedup for CPU with 32 byte L1 cache line (K6-2) + probably poison for intel pentium-I, but i don't care + [thanks to AMD's "Processor Code Optimization Application Note"] ++ hi-color chunky filters, designed especially for LIFEFORMS [hi, Placebo!] + filtered 2x2 chunky allows read all texts in demo and does not loose colors +* in next version some 8-bit filters will be removed. mail me, if you need them +* small speed optimizations in Z80 core +! load dialog remembers last dir [suggested by Alexey Kravchenko] +! screenshot saving to emulator directory + + + version 0.16b 26 oct 2001 + +* drastically improved overall sound quality [thanks to Hacker KAY] +! 'save VTX' does not ignore chip, stereo layout, and 3.5mhz mode +* changed monitor save/load [suggested by Ironfist] +! always loop MOD in GS player [suggested by Moroz] ++ monitor: ripper's tool [idea by Ironfist] ++ Hobeta loader (read doc) [suggested by Mr.Dsteuz] ++ FLASH-COLOR [thanks to Mad Cat for description] ++ INI option to hide video filters, that do not work on your system +? gameport joystick as kempston (not tested - just copied code from DirectX SDK) + + + version 0.16b2 31 oct 2001 + +* slightly increased quality and speed for 2x2 hicolor chunky +! able to load 48K qsaves [bugreported by Kuba Adamowicz] +! fixed some bugs in keyboard layout [bugreported by Alexey Kravchenko] +! load dialog: fixed masks for hobeta and fdi [bugreported by Paul Pavlov] +! may work in 15-bit hicolor (not tested) [thanks to Vitaliy Pigo for techinfo] + + + version 0.17b 5 nov 2001 + +! fixed bug in 15-bit hicolor (not tested) +* MCR optimized for any (K5,K6,K7,P-I,P-II,P-III) cpu, not only for K6-2 ++ scorpion timings (available from INI) [s.b. Alexey Kravchenko] +! monitor: fixed bug in GS samples preview ++ hardware multicolor [s.b. Nuts and Mr.Dsteuz] ++ support for ZS Scorpion Service ROM 2.94 [s.b. many people] ++ customizable (from INI and GUI) ROM sets [s.b. many people] ++ memory model: ZS Scorpion 1024K +- removed many useless 8-bit filters. i've received only one request for + keeping them, so i think 16-bit modes much better + + + version 0.18b 26 nov 2001 + +! monitor: LD (nnnn),HL and LD HL,(nnnn) assembles now to short (not #ED) form +! fixed bug in short #7FFD addressation [thanks to Paul Pavlov] +! fixed port #FF (broken in 0.17?) [thanks to Alexey Kravchenko] +! finally found lost 3T in MCR. now exact pentagon border +! some changes in beta128 timing [thanks to Max Vasilyev], but still incomplete +! monitor: fixed flipping video pages in dialogs using GDI +! monitor: update MCR on every trace step +- monitor: removed logo [alas, found a copyright owner :)] ++ led: current time [s.b. Alone Coder] ++ INI: GSReset [s.b. Ilya Podrezov and Necromancer] +* small changes in INI:SYSTEM.KEYS ++ monitor: save cursor positions [s.b. Alone Coder] ++ monitor: context jumps [s.b. Alone Coder] +! shame on me - fixed bugs in Z80 (not all) [thanks to Alone Coder] ++ GS: LPT covox mode +* small changes in INI:LEDS ++ autofire [s.b. Alexey Kravchenko] ++ DirectSound for w95. delay=2frames, if DS driver uses hardware acceleration + (also select out_ds.dll in winamp;) [damn,i've got >10 requests for this shit] ++ user palettes on hotkeys [s.b. diver] ++ CMOS+RTC [s.b. Pawel Kislyak] ++ hotkey for quick saving disk images and cmos + + + version 0.18b2 29 nov 2001 + +! fixed bugs in palette for some filters (appared in 0.18) +* some hi-color speed optimizations (but still 32 bit instructions) ++ "new" video filter for chunky demos ++ INI: ShareCPU [s.b. Dexus] + + + version 0.19b 19 dec 2001 + +* completely rewritten video engine: requires video overlay hardware, but works + in windowed mode; plz review hardware requirements +* changed AY Chip=digital (added anti-vibratto, ambience effect, etc...) +- GS: removed MOD player (b'coz it suxx), now able to exploit BASS Sound System ++ 512x192 ++ added 5 fast, but low-resolution hicolor chunky modes +! fixed bugs in GS [found by diver] ++ unreal api [s.b. PoP HL^exult] +* +10% speedup in z80 emulation ++ monitor: on-screen watches ++ on-screen watches trace used RAM/ROM banks [s.b. Mad Cat] +? fixed bugs in gamepad/joystick (?) ++ TD0 read/write [thanks to Alex Baskakov for HalfElf's sources] ++ video filter: color tv (not optimized -> slow) + + + version 0.19b2 21 dec 2001 + +! TD0 reader updated for TD0 v21 + + + version 0.19b3 24 dec 2001 + +! video: supported pixel format RGB555 +! fixed bug in windowed mode [found by Ilya Hromenkov] ++ video filter ch4true - best quality + + + version 0.19b4 25 dec 2001 + +! video: supported MPEG1 YUY2 (for nVidia cards), some video code cleanup +* video filter 'color tv' optimized +! fixed EI:HALT [thanks to Alone Coder] +! 'fixed' bug in beta128 [found by Larchenko Eugene] + + + version 0.19b5 28 dec 2001 + +! fixed ds driver for win98. INI:DSoundBuffer is obsolete (auto-detection) + sounddrv=wave will be removed in next version +! fixed windowed mode bugs in win98 ++ monitor: disasm to file [s.b. Sergey Bulba] ++ monitor: INS/DEL in line editor +! monitor: fixed bug in read block from file + + + version 0.20 final 9 jan 2002 + +* first not-a-beta, though not all bugz fixed ++ open source +- soundchip tables are wrong again (opensource restrictions, see sound.cpp) ++ check against loading same disk image to different drives [s.b. Alone Coder] +! fixed ch2hw,ch4hw for nVidia cards +* more flexible video settings ++ monitor: select active window with mouse [s.b. Sergey Bulba] +! GS/MOD: fixed bug in position jump [found by Ironfist] +! dialogs do not trash main window while dragging over it [s.b. Alex Baskakov] ++ keyboard layouts can be selected without restarting emul [s.b. Flying] +* faq updated ++ INI: DSPrimary may be for win98 (win2k works well in both modes) + + + version 0.20 fix #1 29 mar 2002 + +* +5% z80 speedup when monitor not used +! RTC day_of_week [fb Alone Coder] ++ video renderer: standart win32 GDI +* new sound mixer: always max quality ++ clock-precise ay/ym emulation (in prev. versions i've used a simulation) +! fixed covox on port #DD ++ video: wide border [esp. for ZX-Time] ++ #EFF7 bit2 and bit3 ++ video: new screen mode [from ZX-Guide#2] ++ video: works in 320x200, 320x240, 400x300, 512x384, 800x600 modes ++ boot auto-appends to disk in any format, not only to SCL +* new engine for WD1793 emulator: +! complete emulation of all WD93 commands and status register +! right CRC in address and sector [thnx to AlCo for track dumps] +! complete timings (insult, powerup works as real with traps=0,fast=0) ++ FDDs LED ++ load/save unreal disk image format ++ built-in converter HOBETA -> TRD <-> TD0 <-> UDI <-> FDI <- SCL +! TD0 writer updates CRC +! able to load TD0 with description [fb AlCo] +* many cosmetic bugfixes/flicfixes/improvements + + + version 0.20 fix #2 1 apr 2002 + +! fixed FDD_MOTOR bug [fb SirX] +! fixed ay sound [fb SirX] +! fixed bugs in FDI loader [fb SirX] +! fixed FDD step bug + + + version 0.20 fix #2.5 10 apr 2002 + +! fixed ay envelopes #0E and #0A [fb SirX and Alex Makeev] + + + version 0.20 light edition 19 apr 2002 + +- shit removed +* some MCR optimizations ++ separate reading left and right shift,control,alt ++ direct access to video hardware, full speed on iP100 (without sound) + use 50/100/150hz modes - set EGATiming in INI for your display + + + version 0.21b 20 may 2002 + +* video output +20% faster +- removed filters: sgdi, motion blur, flashcolor. removed leds: FDD (too ugly) ++ resizable-window video mode for accelerated cards (recommended for GeForce1-4) ++ chunky modes through hardware bilinear filter (realtime in gf2/1600x1200x32) +* leds: при указании координат возможны отступы снизу или справа от границы экр +* all tape routines completely rewritten (maybe reading WAVs in next versions) +- tape traps removed. pls press NumLock or enjoy loading process +! load TZX with direct recording and loops [thnx to Dmitry Sanarin for files] +! better support for TZX info blocks: messages, hardware info, pokes, etc... ++ CSW loader (hacked from couple examples, if you have specification, mail me) +* monitor: disassembly window acts like STS 3.2 ++ General Sound low-level emulation [greets to PSB/Halloween & Adventurer #13] + (GS firmware sux (details in notes.rus). use BASS for 16b/8ch) +* faster emulation of DDCB/FDCB opcodes +! speed-up & bug-fix wd93 emulation [thanx to Alone Coder for tests on real zx] +! block read/write when FDD motor is not spinning +* support for new version of UDI format, read ++ executable file is not UPX'ed (+2% faster loading) + + + version 0.21b2 31 may 2002 + +! updated sound mixer [fb Siril, thanks to SirX for bugreport] +! save/load dialogs don't clear background in 8bit fullscreen mode +! correct CSW loader [thanks to Ramsoft for docs] +! TZX loader updated to format version 1.13 +* minor video optimizations ++ loader for .SP snapshots ++ monitor: diskeditor [sb Alone Coder] + + + version 0.21 light edition 17 jun 2002 + +* as usual, 'lite' release + + + version 0.21b3 25 jun 2002 + +! diskeditor fix [released 10 jun 2002] +* Z80 emulation +8% faster +* video output optimizations +! fixed bug in AY mixer, much faster AY/YM emulation ++ new unreal soundchip: YM-turbo (headphones recommended) ++ custom AY volume/stereo presets (+bonus - over 10 ripped volume tables) ++ ULA presets +* flash-color is back + + + version 0.21b3LE 25 jun 2002 + ++ monitor works better, but still incomplete functionality + + + version 0.21b4 + +! fixed: 0.21b3 could not load ULA/AY presets on win9x +! fixed: TRD writer saves last accessed track instead of trk 0 [fb AlCo] ++ F1 invokes help on bound keys (requires IE4.0 or later) +! fixed bilinear filter (crashed in 0.21b3) +! fixed small bug in flash-color, now may work with bilinear filter +! fixed 'nopaper' mode +* video sub-system rewritten. overlay in 'double' mode don't pervert colors ++ profi ROM and screen (to run CP/M, find profi.rom and cpm.udi) + profi service ROM can start only when BETA128 delays enabled ++ pentagon service ROMs (details in service.rus) ++ my service ROM included in archive (service.rom) ++ pentagon 256K, pentagon 128SR (service ROM on #7FFD) ++ #EFF7 switches 512x192 and HWMC, #DFFD - 512x240 + (without changing screen mode - always 50fps, even if switching each frame) +- unreal API removed, looks like nobody uses it ++ configurable scanlines brightness +* if some ROM not defined in ROMSET, it substitutes with #FF's ++ gui: config page EFF7 - set/reset/lock arbitrary bits of #EFF7 ++ filter anti-64 for reading e-zines: converts 4x8 fonts to 8x8/8x16 on the fly + to run on slow pc's, unset 'update border' and set 'low quality'. works only + with known fonts. i've used fonts from heresy, body and others,,, (sorry ppl) + + + version 0.21b5 + +! monitor: fixed stupid bug in 'watches' window, added Alt-F, Alt-S, Ctrl-U +! fixed HIMEM=PENTSR - #7FFD bit 4 should not lock #7FFD bit 7 +* recompiled with vc7, separate versions for pentium and p-II + in virtue of this amazing compiler unreal runs on pentium-166MMX! +! monitor: Alt-S,Alt-C,Alt-P didn't work in registers window +* all asm fragments removed from Z80 engine, now it's pure portable C++ +! TZX loader: fixed overflow on too long description strings [fb Ir0n Man] ++ video: AlCo-384 on #EFF7 bit 6 +* anti-64: more fonts + + + version 0.22b + +! fixed: emul played ay-envelope #0B instead of #01 [fb Mad Cat] +! Z80: fixed DD,CB and FD,CB opcodes [thanks to Kamill Karimov] +! fixed many small bugs in TZX loader +! fixed bugs in WD1793 and FDD emulation ++ text64: detect per-pixel scrolling and 6x4 fonts, font searcher +- text64: pre-defined fonts removed [(c) restriction] + + + version 0.23b 13 sep 2004 + +- win95/98/ME are no longer supported +- NT4.0 (directX 3.0) is no longer supported +- no special version for P5,K6 CPUs. code optimized for P6/K7 +! a bit better AY sound, still not perfect [acn#32]. btw, AlCo, border=wide is able to display 300 lines for 384x304 screen (pls read unreal.txt) ++ faster AY emulation +! AltLock=1 could freeze keyboard, fixed +! fixed crash on playing zero-length GS samples ++ mouse buttons and wheel can be bound to spectrum keyboard ++ ini:[ARC]SkipFiles - ignore archive descriptions ++ anti-text64 font setup: "find next" searches through left/right,linear/planes +* improved font searcher in anti-text64 font setup + + + version 0.24b 26 oct 2004 + +* increased filter order in sound engine (better AY sound) ++ ATM 7.10 128/512/1024 (requires BIOS to setup memory) thanx to Timonin Maxim ++ ATM 7.10 video modes (palette does not work) + + + version 0.24b2 5 nov 2004 + +! fixed bug in WD1793 engine (ZX-Power#3, ATM CP/M) +! flash attribute in AlCo384 screen mode +! unable to edit ascii memory dump in monitor +! fixed saving ATM screenshot from windowed mode +! fixed screen repaint in pause mode [fb Gambler] ++ ATM palette ++ ATM7.10 128/256/512/1024kb ROM +* minor ATM bugfixes/improvements ++ able to load config from command line (unreal.exe -i cfg.ini) ++ added atm2.ini, slowcomp.ini + + + version 0.24b3 bugfix release 11 nov 2004 + +! fixed ATM text-mode attribute [fb Gambler] +! fixed loading from archives - broken since 0.24b2 [fb Paul Pavlov] + + + version 0.25b 15 jan 2005 + ++ ATM screen mode #7 [thanks to Timonin Maxim for scrshots] +! fixed bugs in sound engine [fb Alex Fill] +* FDD led shows current track +* BestView, ZX-Power, etc now works with [BETA128] Fast=1 +* "disk modified" flag shown in BETA128 settings [sb AlCo] +! fixed small bug in Z80 [fb AlCo] +! fixed bug in service ROM emulation [fb AlCo] +! reset should not clear Z80 registers [fb AlCo] ++ BASIC-48 labels from file "sos.l" shown in monitor (Ctrl-L) + unfortunately, i have no BASIC-128 and TR-DOS labels +! fixed crash on Alt-Tab with dsprimary=1 [fb AlCo] +! fixed screen update when returning from full-screen in gdi/overlay mode ++ ATM/Nemo ATA HDD emulation [alpha]. works with images and real drives +@ ini: changed ROMSET=pentagon (some soft expect tr-dos in service ROM) +! fixed saving/loading snapshots (Snowball, Return to Eden) ++ XAS7 labels shown in monitor (Ctrl-A,Ctrl-L). for XAS 128k config ++ alasm labels shown in monitor (Ctrl-Shift-A,Ctrl-L) [thanx to Alco for docs] + + + version 0.26b 27 jan 2005 + +! HDD emulation: fixed reading in CHS mode, when it goes to next track/head +@ changed ini: [SOUND]SoundBuffer=2 (less sound delay for 1GHz+ CPU) ++ jump to address under cursor from registers window [sb AlCo] ++ cache size option: 16/32K +* new gui/ini design for selection of model, romset and memory size ++ ULA option: "unstable data bus" [sb AlCo] +! invoking monitor from full-screen mode has switched screen resolution ++ Scorpion PROF-ROM (useless without SMUC hardware) [greetz to jtn, Neu Animal] +* able to load XAS/ALASM labels from PENTAGON-256/512 high-memory +* added "cancel" button when replacing unsaved disk or exit [sb Vitamin, AlCo] +* selecting normal/double switches window size in GDI mode [sb elf/2] +* centered picture of 384x304 video mode when border=none,small [sb AlCo] ++ ATM XT keyboard [thanx to Kamil Karimov for 8051 disasm] +! saving snapshots/images with dots in filename [fb lvd] + + + version 0.27b 27 feb 2005 + +! asm/disasm of DD/FD+ED (out (c),xl, etc) [fb MadCat] +@ ini [SOUND]SoundBuffer=2 changed back to 6 (was unstable) +! fixed bug in GS emulation [fb Moroz] +* trace step removes "cpu halted" mode [sb lvd] +! disk image filename was not changed after "Save As" [sb AlCo] ++ memory R/W breakpoints. new gui for bp manager (Alt-C) +* switching to/from ATM XT mode without resetting controller [sb Gambler] ++ tactmeter [sb Spectre] +! noflic did not work in 8-bit and GDI modes with scanlines ++ GIGASCREEN on #EFF7 bit 4, without multicolors yet (use with noflic) ++ option for alternative (interlaced) noflic algoritm for filter=double ++ paste text from clipboard [sb Alco, elf/2] +* anti-text64: fonts from ACE,PC20 in default fontset ++ anti-text64: search font in external file ++ anti-text64 detects inverted characters [sb AlCo] +! closing on ALT-F4 sometimes didn't ask "save? yes,no,cancel" +* register #0B bit 2 of CMOS RTC controls binary/BCD mode +* new option [VIDEO]DetectModel (description in unreal.ini) ++ Kondrat'yev ISA modem [mapped to windows driver for real modem] + + + version 0.28b 28 mar 2005 + +! pc joystick/gamepad did not work since 0.27 [fb Terazini] ++ pc joystick/gamepad can be bound to any keys, not only kempston joystick + please create new keyboard layouts with JLEFT=6,JRIGHT=7,JUP=9,... etc... ++ AY mouse [thanx to [bETA]mEN for scheme] +* mouse coords updated (interpolated) every Z80 cycle, not every frame ++ loading labels from user.l for external assemblers [sb Kurles^HS^CPU] + label format is 'PP:AAAA nnn', PP - page, AAAA - offset, nnn - text +* flash-color options read from ini [sb Agent Cooper] +@ ini: [SOUND] Fq=48000 by default (supported by most AC'97's. for others, + consult soundcard manual or set default 44100) ++ Scorpion SMUC: PROFROM,IDE,CMOS,NVRAM [thanx to Neu Animal for scheme scan] +* press F12 to reset in default mode, as specified in ini: [MISC]Reset +* GS emulation code re-organized in object-oriented way ++ monitor: jump to label [sb Kurles^HS^CPU] +* edit MODS.H to compile size-optimized versions without unnecessary parts +@ fixed errata in documentation [thanx to AlCo] + + + version 0.29b 29 may 2005 + +* added ATA-1 compatibility (address registers after read/write) [fb Blade] +! fixed bug in sound engine, that cause crash after long running ++ AY 2nd chip: Turbo-AY (POS), Turbo-sound (NedoPC) and Quadro-AY schemes + monitor: Alt-Y switches active chip ++ frame resampler: converts spectrum 50hz-int into 60/75/80/... hz display + refresh, has built-in low-pass filters to handle 3color and noflic. + set [VIDEO]Refresh to maximum for you monitor in 640x480 (75,85 or 100 hz) +! fixed bug in .Z80 loader [fb Terazini] +! General Sound was accidentally disabled in 0.28b +! fixed INTRQ bug in ATM HDD scheme +! fixed GS volume tables in low level mode [fb psb] +! fixed bugs in GS Z80 [fb psb] +! fixed bug in on-screen watches [fb MadCat] +! fixed reset keys inside monitor + + + version 0.30b 1 aug 2005 + +! fixed bug in TV+noflic [fb AlCo] ++ video filters menu on F4 / SHIFT-F4 +! fixed general sound BASS mode: sound effects not played (bug since ver 0.28) +! OSD GS led continue show volume after sample stop for looped samples +! fixed bug in frame resampler - crash on full border mode [fb Alex/AT] +! fixed bug in frame resampler that caused additional flicker +* better contrast in frame resampler (less frames to mix => more contrast) ++ frame resampler works with all renders, for slow (less then 600mhz) + machines use 8-bit ddraw driver. it is b/w and does not use MMX + probably there are bugs with ddraw/ddrawh. ddrawt/blt/ovr work fine +@ for LCD monitors, pls use refresh=60 with "frame resampler": LCDs can't + switch pixels so fast and picture worse with higher rates +* added SMUC version ports (#5FBA & #5FBE) [sb deathsoft] +* border (port #FE) not cleared on reset [sb deathsoft] ++ KAY-256/1024: himem,ROMs (note that ROMs in the net have invalid pages order) +- scorpion port #1FFD decoded as in real scheme. pentagon soft may not work +* some changes in overlay render (may not work on old vcards, i don't care) +! removed unnecessary screen mode switches when entering monitor ++ modem: speccy able to control transfer mode and rate [code by Alex/AT] +! modem: fixed bug - could read trash byte from input buffer [fb Alex/AT] ++ IDE/ATAPI cdrom (needs physical drive or windows-level emulated device) + + + version 0.31b 24 aug 2005 + +! atapi: sense key not placed into error register on ATAPI error +! atapi: fixed bug in ATAPI commands, that don't return data block +! atapi: fixed bug with DRQ on end of ATAPI data block +! gs: "save mod" was disabled since 0.28b [fb TJ] +! fixed bug in window size after leaving fullscreen [fb Tj] ++ new funny led shows R/W/X dynamic in spectrum memory [sb Raider] ++ keyboard shortcuts in windowed mode: Alt-1, Alt-2, Alt-3 ++ 4x scale video filter [sb Shiru Otaku] (set minres=1024 to avoid 1280x960) ++ shows condition hit and jp/ret/call direction in trace window [sb Dexus] ++ monitor: Alt-F9 shows only ray-painted screen area ++ monitor: Alt-S cycles through watches / screen dump / ray-painted +- monitor: mon.render hotkey is deleted (as obsolete) +* monitor: detection internal loop exit when tracing loop on F8 [sb Dexus] ++ monitor: fill memory with pattern [sb Raider] +* FDD & FDC code comletely rewritten, bugs may appear +! when saving disk image, prefer original format as default, not TRD +! CacheVox works fine with [BETA128]Fast=0. no other emul can handle this yet +! better WD1793 sector write emulation (overwrite old gap and data AM) ++ monitor: more fields in beta128 panel (see docs) +@ visual ini-file editor available at http://realsoftmakers.nm.ru/RSM.files/download.htm + + + version 0.32b 4 sep 2005 + ++ monitor: jump destination marked with arrow [sb Dexus] ++ ini option "HighPriority" ++ non-bluring scale 2x,3x,4x, developed in AdvanceMAME project (MMX-rewritten) +@ scale3x is not MMX-vectorized and slower then scale4x +@ if your monitor displays only up to 1024x768, use border=none for scale4x +! better FDD emulation: BestView works with [beta128]fast=0 [fb Kubas] +! temporary hacks in scorpion trdos replaced accordingly to scheme +! better FDC emulation: adjusted timeouts in fast=1 mode +@ [beta128]IL is 2 by default in INI: speed-up #3D13 loaders in fast=0 mode +! no crash on 1280x1024 scrshots: dynamic buffer size + + + version 0.32b2 21 sep 2005 + +! fixed bug (only drive A: can be used) [fb Strunov] +! fixed bug in FDD delays [fb Kubas] +* some changes in multicolor renderer [sb AlCo] +* Pentagon-1024: #7FFD.5 is a 48k mode lock when #EFF7.2=1 [sb AlCo] +* color console output +* AdvMAME scale 2x,4x optimized for SSE2 + + + version 0.33b 7 dec 2005 + +* monitor: importing ALASM labels with searching in whole memory ++ monitor: Alt-M to edit model-specific high memory port [sb Spectre] ++ monitor: transfer data between FDD image and spectrum memory [sb AlCo] ++ Alt-F6 - memory (cheat) searcher [sb Kpa3] ++ SAM style video drive (disabled by default, edit mods.h and recompile) ++ Alone Coder's 4bpp video mode (on #EFF7 bit 5) ++ TRD/SCL preview in save/load dialogs [sb 3HAXAPb] ++ added "AUTOLOAD" ini section [sb AlCo] ++ load file with drag'n'drop from explorer window +* mouse capture on left-click to main window (Shift+ESC still works) +! restored fire function in "joystick on mouse" mode +! fixed small bug in HOBETA loader [fb AlCo] +* GLUK 6.1 is used as default system ROM + + + version 0.34b 22 jan 2006 + +! correct border color after snapshot loading [fb [bETA]mEN] +! fixed bug in ATM memswap on RG0 [fb AlCo] +! fixed rare crashes on reading port #FF +! fixed bugs (not all) in CD-ROM emulation [fb AlCo] ++ Z80 INT length emulation (Adventurer#13 intro works with right timings) +* ignore some unwanted mouse clicks and keyboard pushes [sb AlCo] ++ complete emulation of ATM-TURBO 4.50 [thanks to Doronetty and Max Timonin] + + + version 0.35b 22 jul 2006 + +! Z80: fixed flags after ADD IX/IY,SP instruction +! Z80: fixed undoc flags after LDI(R)/LDD(R) +! Z80: fixed undoc flags after CPI(R)/CPD(R) +- Z80: undoc flags for IN[R] / OUT[R] not ready yet +! Z80: MEMPTR implemented (as described in boo_boo & Kladov research) +! anti-text64 now works in ATM-TURBO models [fb AlCo] +! monitor: don't waste CPU time in spectrum screen view (F9) [fb AlCo] +* changes in HDD/CDROM code: physical layer moved to separate classes +! GS: fixed unused port#BB bits (some games rely on it) [fb moroz] ++ CD-ROM access via SPTI (native WinNT) or ASPI (3rd-party wnaspi32.dll) +* used my SNDRENDER library for sound & AY emulation (easier & reusable) +* implemented 16/32bpp renders for ATM-1,2 640x200 screen mode +* improved GS quality: high-level emulation mode playing samples using BASS +@ please upgrade BASS to version 2.3 (API changed) + + + version 0.35b2 9 aug 2006 + +! GS: fixed MOD seeking in BASS mode [fb molodcov_alex] +! HDD: fixed crash when master or slave device absent +! floppy image loader can read images with very short gaps (CABALGS5.TD0, etc) ++ mouse wheel on high bits of #FAFD port (see ini: Wheel=KEMPSTON) [sb AlCo] ++ KeybPCMode (see ini). when enabled, simulates pc keyboard [sb 3HAXAPb] + + + version 0.36b 28 feb 2007 (Alone Coder) + ++ поддержка YM2203 (TurboSound FM) интегрирована Dexus'ом с изменениями Alone Coder'а. Теперь исправленная эмуляция (TurboFM Revision C). YM2203 support (TurboSound FM) was integrated by Dexus with Alone Coder's changes. Fixed emulation now (TurboFM Revision C) +* исходники теперь компилируются под MSVC6 (было MSVC7). Sources are now compiled by MSVC6 (was MSVC7) +- в связи с этим выброшены фильтры AdvMAME, FrameResampler. Because of that the filters AdvMAME and FrameResampler are removed +! цвет на точку по EFF7 bit0, мультиколор перенесён на bit5. Colour-per-pixel mode is set by EFF7 bit0, and hardware multicolour mode is moved to bit5 +! исправлена загрузка 48k снапшотов, если до этого был установлен режим Pentagon 1024k: вместо 0-й ставилась 32-я банка памяти. Fixed loading of 48k snapshots, if Pentagon 1024k mode was before: 32nd bank was set instead of 0th +! сброс в 48k на Pentagon 1024k включал 32-ю банку. Reset to 48k was setting 32nd bank on Pentagon 1024k +- убита защита от выгрузки sna в режимах >128k, теперь можно отгружаться в Walker'е. SNA saving protection in >128k modes is now removed, so Walker may be saved +! максимальная длина дорожки увеличена до 7000 байт - теперь работает игра "Шибениця" (gallows.td0). Если какая-то программа перестала работать, СООБЩАЙТЕ! Maximum track length is increased to 7000 bytes, and Gallows game works now. PLEASE REPORT if some program ceases to work! ++ русская версия хелпа. Russian version of help ++ название последнего загруженного диска выводится в заголовке окна эмулятора. A name of last loaded disk is shown in a title of emulator window +! вываливалось при обращении к IDE-устройству, если одно из IDE-устройств не было настроено. Crashed while addressing IDE device if one of IDE devices was not set +! CD-ROM не работал под Windows 9x, теперь работает. Сделан обход глюка, который возникает вот здесь, по непонятной мне причине: CD-ROM did not work in Windows 9x, it works now. Walkaround of bug, that appears here, was made: + + + version 0.36.1 10 mar 2007 (Alone Coder) + +! буфер на 256 значений YM2203 убран - мешает цифровому звуку через YM2203. Buffer for 256 YM2203 samples is removed - it hampered the digital sound via YM2203 ++ ввод из клипборда теперь поддерживает и русские символы (кроме двух: +заглавная Ё понимается как Е, заглавный Ъ - как апостроф). Paste from clipboard understands Cyrillic letters +* убрано окошко, извещающее об изменении файла user.l (sb Elfh). Messagebox "unreal discovered changes in user labels" is removed +* дополнен хелп (обязательно посмотрите раздел "несовместимость"!). More info in help (please look "bugs and incompatibilities") +* дополнен комплект файлов. File set is filled up + + + version 0.36.2 28 mar 2007 (Alone Coder) + +! клавиши в max speed режиме опрашиваются каждый фрейм: +temp.inputblock = 0;//temp.vidblock; +а то не всегда реагировало на NumLock в max speed режиме. +Keys in max speed mode are scanned every frame, for better responsibility +of NumLock in max speed mode. +! звуковой буфер не чистился (sound_stop()) в меню Save disk +(main_save(), correct_exit()). +Sound buffer was not cleared in Save disk menu +! вываливался при показе меток длиной 10 (sb Elfh). +Emulator crashed when tried to display 10 char long labels +* 0 не показывается как метка. +0 is no more shown as a label +! что-то исправлено в интерфейсе с BASS.DLL (fb molodcov_alex): +if (!BASS_ChannelPlay(hmod, FALSE)) reportError("BASS_ChannelPlay() [music]"); +Something fixed in BASS.DLL interface +! порт #7ffd на Скорпионе дешифровался неправильно (теперь с учётом A12, как +#1ffd) - не работала листалка ACNews. +Port #7ffd in Scorpion mode was deciphered without A12 - +ACNews lister failed to work + + + version 0.36.3 28 apr 2007 (Alone Coder) + +! исправлена палитра. Palette is fixed +! Z80: в операциях записи в память сама запись (wm()) делается после увеличения +счётчика тактов (cpu->t+=) (если два байта, то 2 раза cpu->t+=). Важно для мультиколоров. +Z80: memory write procedure (wm()) in memory write operations is called after increasing +the tick counter (cpu->t+=) (in two-byte operations cpu->t+= is made twice). +Important for multicolors +! исправлена русская буква "м" в anti-text64 - показывалась как "m". +Anti-text64: fixed Russian letter "м" +! TurboSound FM: FM часть заглушена в 4 раза в соответствии с реальным устройством. +TurboSound FM: FM is 4 times more quiet as in real device +! поставлен шумопонижающий фильтр (нужно для beeper'а). Кто знает как - +помогите убрать щелчки на границах 20ms кадров! Если просто заполнять первые +2-4 отсчёта кадра предыдущим значением, то щелчки на бипере исчезают, +но появляются щелчки на AY... Noise reduction filter +added (for beeper music). Please help to remove that clicks in boundaries of 20ms frames! +If I simply fill the first 2 to 4 samples of a frame with preceding value, then +the beeper clicks are disappearing, but AY clicks appear... +! звуковой буфер не чистился в меню Save sound. Sound buffer was not cleared +in Save sound menu + + + version 0.36.4 14 jun 2007 (Alone Coder) + +! шумопонижающий фильтр можно выключить в меню и в ini. Lowpass filter may be triggered in menu and in .ini-file +! в eff7 (bit 4) вместо Gigascreen работает выключение турбо-режима (ставится 71680 тактов) - в соответствии с реальной машиной (кнопка W в Mr Gluk Reset Service). Звук при каждом переключении переинициализируется. Gigascreen mode (eff7 bit 4) changed to turbo mode off, as in real Pentagon 1024SL 2.x (key W in Mr Gluk Reset Service). Turbo off sets 71680 ticks per frame. The sound is re-initialized when this bit switched +! в eff7 (bit 3) вместо Read only cache работает включение 0-го банка ОЗУ в область 0000-3fff - в соответствии с реальной машиной. Read only cache (eff7 bit 3) changed to "RAM0 in 0000-3fff" mode, as in real Pentagon 1024SL 2.x +! немного исправлена громкость FM. FM volume slightly fixed +! PSB: половина правого канала GS слышна в левом и наоборот. Half of GS right channel is mixed to the left channel and vice versa +! PSB: исправление щелчков громкости в GS. Reduced volume clicks in GS + + + version 0.36.5 29 jul 2007 (Alone Coder) + +! исправлен глюк прошлой версии с вводом числа тактов в кадре. Fixed framesize bug of previous version +! диалог POKE ругался на что угодно. POKE did not work +! при сохранении trd под другим именем обновляется имя в титуле. When trd disk is saved under another name, that name is shown in emulator window title +! LVD: в z80asm исправлены операции ALU a,r. Fixed ALU a,r operations in assembler/disassembler +! Deathsoft: в modem неправильно ставилось число бит в байте. Fixed wrong number of bits in byte for modem +! PSB: новая версия ПЗУ General Sound. New version of General Sound ROM + + + version 0.36.6 17 oct 2007 (Alone Coder) + ++ окно эмулятора возникает в центре рабочего стола (из расчёта размера double). Emulator window appears in the middle of the screen (for "double" size of emulator screen) +! убрано выпадение при показе длинных меток в отладчике. Fixed crash while showing long labels in debugger ++ неполная дешифрация кэша (для MODPLAYi) (scor). Cache access is decoded by 1 address line (for MODPLAYi) +! определялся TFM, когда есть только TS. TFM was detected by Spectrum software when only TS was present +! звуковой буфер не чистился при вызове хелпа. Sound buffer was not cleared in help window ++ настройка способа работы колеса мыши в ini. Mouse wheel mode setup in ini +* в ini исправлены секции ROM и по умолчанию поставлен DSPrimary=0. ROM sections in ini fixed, and DSPrimary=0 by default + + + version 0.36.7 18 nov 2007 (Alone Coder) + +! Digital Studio не работало на TurboSound, т.к. по сбросу включался 0-й AY (KYV). +Digital Studio did not work with TurboSound enabled because 0th AY was on +- заштопал ещё две дыры в показе меток, теперь вроде не вываливается (Elfh). +Two more holes fixed in show labels in debugger, so it does not crash any more +- эмулятор не компилировался на более новых компиляторах (из-за локальных меток цикла +и нетипизированных const и static) (DeathSoft). Emulator sources were incompatible +with modern compilers (local loop labels and omission of explicit types for const and static labels) + + + version 0.37.0 8 feb 2008 (Alone Coder) + ++ General Sound 2MB (Dmitry Demyanenko) +- под XP не работало чтение таблицы партиций с CD (выдавало длину 65535), теперь насильно формируется длина 2048. CD partition table under XP returned wrong length of 65535 (fixed to 2048). ++ NMI в области ПЗУ игнорируются (иначе не работает теневой монитор). NMI is ignored when ROM area is executed (Scorpion shadow monitor wants this). ++ от турбо-режима зависит число тактов в команде (t=(t+1)>>1), теперь исключены выпадания при частом переключении режима. Turbo mode reduces number of t-stated in every command (t=(t+1)>>1), and shutdowns because of switching turbo frequently are now fixed. ++ программное переключение турбо-режима на ATM Turbo 7.10. Software turbo mode switching in ATM Turbo 7.10 +* соответственно, по умолчанию в кадре снова 71680 тактов, однако включен турбо-режим (выключение - клавишей W в Mr Gluk Reset Service). By default 71680 t-states are in frame again, with turbo on though (switch it off with W key in Mr Gluk Reset Service). ++ в Mr Gluk Service (6.4) исправлено выключение турбо-режима при выходе в бейсик и др. Mr Gluk Reset Service (6.4) fixed to correctly set turbo mode when in quits to BASIC etc. + + + version 0.37.1 14 sep 2008 (Alone Coder) + +-в нетурбо-режиме на пентагоне насильно ставилось 71680 тактов +pentagon had 71680 ticks per frame in noturbo mode unregardless to setup ++клавиатура компьютера Орель БК-08 (Sergey V. Korop) +Orel' BK-08 keyboard added +*немного пофикшенная версия Глюка (не работал PC128, доктор теперь по reset+G, потом цифра - номер диска, но нужно инициализировать TR-DOS) +Mr Gluk Reset Service slightly fixed (PC128 did not work, disk doctor moved to reset+G then drive number, but TR-DOS must be initialised) ++новая палитра от pulsar +new palette from pulsar + + + version 0.37.2 16 sep 2008 (deathsoft) + ++ В отладчике добавлен указатель исходной точки для команд меняющих PC (актуально, когда надо посмотреть откуда делался JP, RET и т.п.) ++ Исправлена поддержка nmi в режиме скорпиона, теперь в сервис монитор можно без проблем входить из basic 48. + + + version 0.37.3 13 feb 2009 (deathsoft) + +! Проблемы с захватом аудиобуфера для проигрывания 0 байт +(Буфер проигрывался быстрее чем в него рендерились данные, когда частота кадров > 50Гц) + +! Исправлены ошибки с обработкой событий в отладчике (меню и поля ввода) +Не обрабатывалась перерисовка + +В режиме скорпиона: +- Неправильно декодировались 7FFD и 1FFD, переделано на соответствие с +платой SC16 (зеленая). + +- Отклюено декодирование пентагоновского порта EFF7 (нет такого порта в скорпионе и не было никогда) +- Неправильно декодировался порт FE (по A0=0), сделано по схеме SC16 + + Исправлена грубая ошибка в эмуляции ВГ93: +! В ВГ93 код размера сектора содержит всего 2 бита (размеры 128-1024 байта), +остальные биты просто игнорируются. В эмуляторе же использовались все биты из +размера сектора в заголовке сектора. Это приводило к тому что образы в которых размер сектора +равен 5 (101) вообще не читались (а на самом деле сектоа должны читаться как сектора с кодом 01) +(Такая же проблема замечена и в zxmak и в elf/2 плугинах к FAR) + +! Длина дорожки выставлена в 6250 байт (максимально возможная теоретическая длина дорожки) +(о чем неоднократно писал Alexandr Medvedev, подробности в документации на формат udi) +(Alco - учи мат часть!!!) +! В загрузчике td0 сделана обработка флагов (пока на начальном уровне, +пропускаются сектора без заголовков и сектора без данных) +заработала игра gallows + ++ В процедуре форматирования дорожки gap1 сокращен с 50 байт до 40 +(это неправильно, нужно расчитывать исходя из реального количества секторов и их размера, + для стандартных форматов использовать 50, а для форматов типа 10 секторов по 512 байт + - расчитаное значение, иначе длинна дорожки превысит максимальную) + +* Неправильно обрабатывался параметр wParam у сообщения WM_SYSCOMMAND (не накладывалась маска 0xFFF0). +* Зарезервированые 4 младших бита wParam использовались для своих нужд (переделано на WM_USER+xxx) + +* Исправлена ошибка с обработкой цифрового звука (звук от спикера и магнитофона +обрабатывался неверно, сначала записывался звуковой буфер, а потом обновлялись +переменные которые используются для формирования звука (а нужно было наоборот), +из за этого в звуковом буфере постоянно оставался последний проигранный звук и при +остановке/продолжении звука были слышны щелчки) + +- Заблокирован режим турбо в пентагоне. +(Реализованный режим турбо - неприемлем, т.к. после сброса режим турбо всегда включен +и нет возможности его выключить (при каждом сбросе турбо опять включается)) + +! Исправлена ошибка с инициализацией указателя на данные для расчета crc для ВГ93 +(раньше этот указатель инициализировался только при команде байте F5, запись CRC). +Заработала программа dobrota. + +!Исправлена обработка звуковых буферов нулевой длины при проигрывании через WaveMapper. +Заработала программа fuckemul. + +! Исправлена обработка nmi для скорпиона, теперь сигнал nmi активен в течение 1 кадра. + ++ Переименованы настройки в ini файле (Добавлен суффикс Vol) +Beeper -> BeeperVol +MicOut -> MicOutVol +MicIn -> MicInVol +AY -> AYVol +CovoxFB -> CovoxFBVol +CovoxDD -> CovoxDDVol +SD -> SDVol +GS -> GSVol +BASS -> BASSVol + ++ Добавлены настройки для отключения soundrive и covox: +SD +CovoxFB +CovoxDD + +! Исправлена ошибка в эмуляции дисковода. Не везде делалась проверка на превышение +максимального номера дорожки. (В ADS30 заработал тест на максимальное количество дорожек). + ++ Сделана обработка средней кнопки мыши (раньше для нее все было предусмотренно, но самой обработки события, +по непонятной причине, не было). + +! Вернул на место фильтры AdvMAME и Frame Resampler (которые отключил Alone Coder). + ++ Добавлена опция RejectDC в ini файл для включения/выключения подавления постоянной +составляющей в звуке. + +* Исправлена ошибка при загрузке образа пзу, если путь к новому образу больше предыдущего (molodcov_alex) ++ Добавлена опция ConfirmExit в ini файл для включения/выключения подтверждения при выходе. + ++ Добавлены следующие патчи от molodcov_alex'а: +Поддержка IDE profi +Поддержка часов profi +Исправлено поведение порта 7FFDh +У экрана profi не щелкались страницы +Дешифрация расширенных портов profi (платы v5.xx) +Изменено расположение страниц ПЗУ под реальный profi +* Функция applyconfig() вызывалась два раза подряд (в init_all() и сразу же после нее), +в результате двоились сообщения ошибок при запуске эмуля + ++ Реализована поддержка NeoGS (не полностью), работает как в режиме совместимости со старым ГС, так и в расширеном режиме. +Работает MP3 плеер сделанный савелием. MP3 проигрываются через bass.dll (эмуляция кодека VS1001). +NeoGS работает ТОЛЬКО в режиме реального z80. + ++ Реализована поддержка SD карты (пока только read only), в режиме NeoGS и в режиме Z-контроллера (можно использовать сразу 2 SD карты). + Работает FatAll. ++ Отладчик переделан на работу с любым числом процессоров, переключение процессоров выполняется Ctrl + ~ (настраивается в ini файле). ++ Добавлен пресет ULA для ATM (скопирован с пентагоновского но отключено unstable bus in dos) + + + version 0.37.3.fix1 16 feb 2009 (deathsoft) + +* Исправлен статус выдаваемый hdd по reset (molodcov_alex) ++ Tape traps (molodcov_alex) +* При загрузке снапшотов убрано выставление конфигурации pentagon 128 (molodcov_alex) + +* Неверно обрабатывлись пути (всегда как относительные если второй символ не двоеточие), +не указать \\.\X: + +! Исправлен расчет тактов в отладчике (сделано попроцессорно, в предыдущей версии оба процессора меняли одну переменную +и брался неверный счетчик тактов, из за этого выдавался мусор). + +! В предыдущей версии добавил неверно доьавил патчи от molodcov_alex'а +(переключение profi экрана, и блокировку 7FFD по DFFD.4), теперь исправлено. + +! Восстановлен режим ввода символов в PC режиме (был выкинут из сорцов AlCo, в US0.35b2 присутствовал), +в ini файл добавлена опция KeybPCMode (как в US0.35b2) +-Ошибка с определением NEMO-A8 из ini файла (появилась в 0.37.3) + + + version 0.37.3.fix2 24 feb 2009 (deathsoft) + +! Восстановлены настройки AdvMAME (были отключены AlCo) ++ Добавлена ATA команда 0x91 (INITIALIZE DEVICE PARAMETERS) (molodcov_alex) + +* Исправлена ошибка с вылетом при минимизации окна, если render = blt +(отсутствовала проверка на минимизацию окна и на пустую клиентскую зону окна) + +* Переделан выбор модемов в GUI, теперь кроме модемов можно выбрать и COM порты доступные на PC. +(поддерживаются порты COM1...COM255) + +* Исправлена ошибка со звуковым буфером в direct sound (неверно вычислялось смещение в буфере) +* Исправлена ошибка с переполнением в фильтре DC (использовался unsigned short вместо short) +(Alone Coder, DDp) + + + version 0.37.3.fix3 11 may 2009 (deathsoft) + ++ Добавлена модель памяти sectrum +3 ++ Добавлена палитра profi (molodcov_alex) +@ Параметр в ini UseAtmPalette -> UsePalette +- Убрано timeBeginPeriod(1) ++ Добавлены команды ATA 0x40, 0x41 (verify sectors) (SinglWolf) +! Исправлена ошибка обработки identify device, из за которой для реальных hdd выдавалась информация (SinglWolf) +как для образов. + ++ В smuc добавлена поддержка бита 7 порта FFBA (переключение HDD регистров) (SinglWolf). +@ Для KAY ПЗУ вибирается теперь только через пресеты +(Стандарта не было, в kay-1024 старший разряд пзу мог переключатся от /DOS или от DOS +выбиралось джампером J5) + +! Исправлена обработка di/halt (раньше выполнене команд продолжалось после halt) +* Исправлены ошибки в эмуляции hdd(установка готовности в команде чтения сектора +и пересчет текущих секторов/головок/цилиндров) + +! Запись в память в отладчике сделана по процессорно (раньше запись всегда происходила на cpu0) +! Отображение watches и sp всегда показывало память cpu0 (savelij) +! Исправлена работа брекпонитов в многопроцессорном режим (savelij) ++ Сделана поддержка .isd формата образов дискет (образы дискет is-dos, аналог .trd совместимо с mktrd и winisd) +* Не работал запрос подтверждения при выходе эмулятора по ошибке ++ Сделано сохранение измененных образов дискет при выходе по ошибке + + + version 0.37.3.fix4 7 nov 2009 (deathsoft) + +* Совместимость с win98 в диалогах открытия/сохранения файлов +* Имена файлов в отладчике можно задавать русскими буквами +! Клавиатура обрабатывается через DirectInput +- Убрано мелькание в отладчике по step over (мелькает только на call/rst) +* Исправлена ошибка при чтении блоков длинной больше 255 из CSW формата (DDp) +* Исправлена ошибка в декодировании портов Z-Controller'а, которая приводила + к конфликтам с портом EFF7 +* Исправлена ошибка с отменой загрузки образов через диалог опций (beta128/load) +! Исправлена ошибка с переключением страниц памяти в режиме +3 (transman) ++ Поддержка скриншотов в png ++ Поддержка видео в виде серии png (сделано в виде эксперимента, тормозит) +! Исправлена ошибка форматирования дискет (при сохранении trd образа в нулевом треке был мусор) +* Исправлено именование банков ПЗУ в отладчике (теперь SVMn только для скорпиона) +! Исправлена ошибка в отладчике при многопроцессорной отладке (savelij) ++ Исправлена работа gs-z80 при частоте кадров не равной 50Гц +! Исправлена серьезная ошибка со сбросом ngs (при сбросе была рассинхронизация +cpu ngs и cpu спектрума) +! Исправлена ошибка с выбором банков памяти ngs при сбросе (savelij) ++ Добавлен ngs dma +! Исправлена ошибка с условными точками останова в ngs (AlCo) ++ Добавлен atm scroll (работает catdemo) (screw) ++ Сделан режим reset to trdos для atm1 и atm2 +* Исправлена формула пересчета палитры, теперь результирующие цвета имеют диапазон 0..255 ++ Реализован бит UF в RTC, заработало отображение часов в мониторе Scorpion'а (savelij) ++ Добавлено открытие DOS портов по D2 в 1FFD для Scorpion'а ++ Добавлен DDp scroll для pentagon в 16c режиме (исключен режим 384x304) (DDp) ++ При открытии hdd образа lba расчитывается автоматически, а chs устанавливается в 0/0/0 ++ Настройки в GUI поддержки колеса мыши (были в 0.35b2) ++ Сохранение восстановление текущего каталога в диалогах open/save ++ Добавлена поддержка работы с CD образами напрямую (опции CD0=1, CD1=1 в ini файле) +* Исправлена ошибка с формированием 9го сектора в trd (при загрузке scl и hobeta) (savelij) +* Исправлена ошибка с порчей номера текущего регистра AY при выходе из отладчика +* Испавлена ошибка с проверкой параметров ВГ93 (screw) ++ Добавлена возможноть монохромного режима высокого разрешения для profi (molodcov_alex) ++ Добавлена команда ata 0x50 (format track) +! Исправлена ошибка с автоматическим добавлением boot.b на дискеты с не tr-dos форматом ++ Исправлена ошибка ВГ93 (отсутствовало состояние BUSY в команде seek) из-за этого + profi-dos вис на опросе готовности (проверялся переход READY->BUSY->READY) + + + version 0.37.3.fix5 16 jun 2010 (deathsoft) + +* Исправлена ошибка с блокировкой памяти через EFF7 на pentagon 1024 (VELESOFT) ++ Поддержка z80 снапшотов для +3 (VELESOFT) ++ Вывод сообщения об ошибке при неправильном размере rom для ngs (jtn) ++ Добавлена поддержка записи на sdcard ++ Проверка параметров ata команды 0x91 (Initialize device parameters) (savelij) ++ Добавлен автодетект lba для образов hdd у которых в ini файле прописано lba=0 +* Исправлена ошибка ВГ93 (отсутствовало состояние BUSY в командах read/write) +* Исправлена ошибка ВГ93 (при отсутствии диска в командах read/write BUSY должно быть бесконечно долго) ++ Добавлены команды 9 и 10 для sd карт +* Исправлена ошибка с путями к образам hdd +! Исправлена ошибка с восстановлением surface'ов при отрисовке через blt ++ Сделана поддержка .pro формата образов дискет (образы дискет cp/m profi, аналог .trd совместимо с ZX Disk Studio) +! Исправлена ошибка с добавлением boot в trd образы + + + version 0.37.4 18 jan 2011 (deathsoft) + +* Убраны задержки позиционирования в tr-dos traps ++ Добавлена ide часть от divide ++ Добавлен режим divide на портах nemoide (работает inir/otir) ++ Добавлена поддержка quorum 128/1024 ++ Добавлен выбор джойстиков через ini файл ++ Добавлена поддержка 32 кнопок для джойстика +! Исправлена обработка прерываний и команды halt (раньше в отладчике halt выполнялся как nop не ожидая прихода прерывания) (psb) ++ Добавлена поддержка ATM3 (4Мб) ++ Добавлена индикация блокировки 48k в отладчике (только для 7ffd) ++ Добавлена натройка порта FF (включение/выключение) ++ Добавлены SSE2 версии фильтров normal и double ++ Добавлены фильтры advmame 2x и 3x для 32bpp ++ Добавлен фильтр triple (масштабирование 3x) ++ Добавлена поддержа saa1099 (включается через ini файл) +! Исправлены ошибки ВГ93 (заработал журнал zx expert из udi образа) (fk0, jtn, savelij) + + + version 0.37.5 23 feb 2011 (deathsoft) + +* Исправлена обработка чекбокса Port FF +* Исправлен сброс в trdos для ATM2/3 (был испорчен при добавлении памяти ATM3) +! Исправлены коды условий для команды Dx в ВГ93 (биты обрабатывались задом наперед) +! Исправлена обработка команды Dx в ВГ93 (после этой команды не обновлялся статус, было испорчено в прошлой версии) +* Исправлено редактирвоание поля intlen в gui (раньше пресет не менялся на custom) ++ Добавлен выбор размера памяти для ngs (через ini файл) +* Исправлена обработка команд чтения ВГ93 в режиме no delays (в RDS заработал пункт analyze track) +* Исправлена невозможность редактирования расширенного порта памяти в отладчике (было испорчено при в одной из предыдущих версий) (witchcraft) +* Исправлена поддержка NT4 ++ Добавлена фильтр 1x для режимов 16c (pentagon и ATM1/2/3) ++ Добавлена опция масштабирования окна (winscale, через ini файл) ++ Добавлен новый драйвер видеовывода через direct 3d 9 + (аппаратное масштабирование без размазывания картинки, при этом выбирать фильтр normal + и winscale 2 или 3) +* Не было проверки на нулевой указатель при неверной версии bass.dll (AlCo) +* Исправлена ошибка с деинициализацией vs1001 ++ Добавлена поддержка сохранения/загрузки списка брекпоинтов bpx.ini + формат: + r0=0x1000-0x1100 + r - r/w/x + 0 - номер процессора (0 - спектрум, 1 - ngs) + адрес начала диапазона, адрес конца диапазона + (можно указывать толкьо адрес начала, если брекпоинт на 1 байт) + + version 0.37.6 13 sep 2011 (deathsoft) ++ Увеличена точность вычисления fps +* При включенных брекпоинтах неверно инициализировался видеорежим ATM1/2/3 ++ Добавлен режим редактирования cmos/nvram в окне дампа (сочетание кнопок в ini - mon.switchdump) +! Исправлено переключение sinclair<->textmode для ATM2/3 +* Исправлена обработка брекпоинтов при выполнении первой инструкции обработчика прерывания (было испорчено в предыдущей версии) +* Исправлена загрузка scl +* Исправлена обработка меток из users.l с указанным номером банка +! Неправильно обрабатывались пути к реальным hdd в ini файле (Budder) ++ Добавлено размонтирование всех разделов на реальных hdd ++ Добавлена программная загрузка шрифта на ATM3 ++ Добавлено чтение портов ATM3 ++ Добавлено чтение палитры на ATM3 ++ Добавлена обработка NMI на ATM3 ++ Добавлена проверка выхода за пределы CHS параметров HDD при загрузке конфига ++ В условные брекпоинты добавлена команда DOS (признак активности дос портов) (можно использовать выражения вида !DOS && ((IN & 0FF) == 1F)) ++ Добавлен вывод активности DOS портов в отладчике ++ Добавлено чтение CTS в эмуляции модема +* Исправлена обработка kempston mouse (не работала в trdos) (savelij) +! Не проверялось переполнение каталога дикеты (trd) при добавлении файлов (Петр) ++ Добавлен выбор дисковода по умолчанию в который грузятся образы из командной строки эмулятора и при drag & drop (Петр) + (параметр DefaultDrive в ini) + + version 0.37.7 10 apr 2012 (thims) + ++ Добавлены фиксы lvd { + модель памяти пентевы - порт EFF7 и мегабайт через 7FFD; + прерывания через FF77 невозможно запретить; + Доступны видеомоды через EFF7: алко16ц и хардваре мультиколор; + впилен текстовый режим #7 (1страничный); + выпилен ATM2 256х192 текстовый режим; + запись фонтрома сделана как в железе - по любому адресу, а не только по 0-2047; + впилено RAM0 в 0000-3FFF через EFF7, не тестировал; } +- Убраны зависимости от ddraw.lib, а тажке libpng и иже с ней +- Убрана всяческая поддержка win9x +* Работа с графическими форматами переведена на gdiplus ++ Добавлен формат скриншотов GIF ++ Добавлен новый параметр конфигурации ScrShotDir: папка для скриншотов ++ Добавлена поддержка ps/2 клавиатуры пентевы ++ Добавлено чтение версии базовой конфигурации и бутлоадера пентевы ++ Добавлено управление статусом capslock пентевы ++ Добавлена поддержка ключей командной строки /b и /l (см. доки) ++ Makefile для MinGW, протестировано на версии 4.6.2 +* вроде еще каких-то пару фиксов по мелочи, где-то что-то падало при неверных параметрах и т.п. + + version 0.37.8 10 jul 2012 (ts-labs) + ++ Added TS-Configuration diff --git a/tools/unreal/doc/rom.txt b/tools/unreal/doc/rom.txt new file mode 100644 index 0000000..9a5aedf --- /dev/null +++ b/tools/unreal/doc/rom.txt @@ -0,0 +1,23 @@ +48.rom - оригинальная прошивка от ZX Spectrum 48k +2006.rom - 48k прошивка 2006 года (отличается исправлением багов оригинала) - не для 128 бейсика! +atm103rs.rom - прошивка ATM Turbo 1 +xBIOS132+sl.rom - прошивка ATM Turbo 2+ +bios_1_07_13 - BIOS 1.07.13 для ATM2+ +QC_3_05.rom - Quick Commander (вместо 128 бейсика) +scorp295.rom - старая скорпионовская прошивка 2.95 +scorp_prof401.rom - профПЗУ 4.01 для Scorpion (с поддержкой SMUC) +profi.rom - прошивка для Profi +pentagon.rom - стандартная прошивка для pentagon basic 48, basic 128 с пунктом tr-dos в меню +kay1024.rom - прошивка для KAY 1024, 2000, JV Kramis 0.3 +kay1024b.rom - прошивка для KAY 1024, 1998, JV Kramis 0.2 +trd503.rom - tr-dos 5.03 +trd504tm.rom - tr-dos 5.04tm +bootgs.rom - прошивка для ngs (на базе gs1.05a) +ts-bios.rom - BIOS for TS-Configuration (includes: TS BIOS Setup and start code, 128 basic ROM (32 kB), tr-dos 5.04T) + +Дополнительные rom (не входят в компект) +48for128.rom - 48k прошивка для 128 бейсика +128tr!.rom - 128k прошивка с TR-DOS'ом наверху +DOS6_10E.rom - TR-DOS с исправленными глюками и поддержкой рамдиска (см. Info Guide #7) +GLUKPEN.rom - Mr Gluk Reset Service 6.5R (фикс для US0.37.1 - дискдоктор по reset+G и номер диска) (см. Info Guide #7) +GLUKATM.rom - версия Глюка для ATM Turbo 2+ (на реале работать не может, нужна для разработки TR-DOS программ под ATM) diff --git a/tools/unreal/doc/todo.txt b/tools/unreal/doc/todo.txt new file mode 100644 index 0000000..34ffa74 --- /dev/null +++ b/tools/unreal/doc/todo.txt @@ -0,0 +1,30 @@ +Tests: +- check TheLink Turbo switching for Pentagon config + +Bugs: +- Wrong multicolor rendering: truncate tcpu to lower limit of attribute address +- Add WnR=1 to CRAM and SFILE DMA, otherwise ignore +- Incorrect exit from DEBUG in Bomber (paging?) +- Probably incorrect DOS on/off behaviour with regard to RAM at #0000 (MEMCONFIG) +- 3x and 4x renderers on Win7 crash Unreal + +Missing features: +- ld a,r and ld a,i tacts not recalculated for Turbo (op_ed.cpp.177 and op_ed.cpp.232) +- leds +- ATM3 wrong palette update in Personal Nightmare demo due to fine ATM3 port decode (PN uses coarse addressing) +- NoFlic (in some renderers) +- VDOS +- other clones full compatibility +- TheLink doesn't compile on TSconf (probably missing #EFF7 features) + +To Do: +- Save settings to .ini +- Carry out Debugger into separate window +- Add all models resources to Debugger +- Save .avi using VFW +- Add some workaround for TR-DOS timeouts on 7+ Turbo +- Separate reset() and res_init() procedures +- Add res_init() to SNA load when TS-conf active + +X-factors: +- Regular lags/glitches in when Process Explorer ON \ No newline at end of file diff --git a/tools/unreal/doc/unreal_e.txt b/tools/unreal/doc/unreal_e.txt new file mode 100644 index 0000000..641accc --- /dev/null +++ b/tools/unreal/doc/unreal_e.txt @@ -0,0 +1,676 @@ +---------------------------------------------------------------------- + UnrealSpeccy 0.37.7 by SMT and Others +---------------------------------------------------------------------- + + contents + + 1. features + 2. system requirements + 3. command line + 4. keys in emulator + 5. bugs and incompatibilities + 6. greets + 7. license + + +---------------------------------------------------------------------- + +features + + + cpu + Z80: all (un)documented commands and flags + + + memory + + Pentagon 128/512/1024 and 48K ROM + TS-Configuration + Scorpion 256/1024 RAM and 64K ROM + Scorpion PROF-ROM with SMUC support (128/256/512K) + KAY-256/1024 and 64K ROM + ZX Evolution + Profi 1024 RAM/ROM and memory mapper + ATM TURBO 1 v4.50 512/1024 RAM, 64-1024K ROM + ATM TURBO 2+ v7.10 128/512/1024 RAM, 64-1024K ROM + cache 16/32K + CMOS/RTC: russian chip (512Bu1, 64 bytes, 1st day is monday) or + pc-compatible (DALLAS, 256 bytes, 1st day of week is sunday) + SMUC RTC and NVRAM (24LC16) + + sound + + beeper, tape saving and loading tones + + AY-3-8910/YM2149: excellent sound emulation, including digital + and unusual effects, parallel ports on R14/R15 are not emulated + + Turbo-AY (Turbo-Sound): two AY(or YM) chips by PoS and NedoPC schemes + + TurboSound FM: two YM2203 chips (6 AY channels + 6 FM channels) + + also there is 'unreal' (not available in real Spectrum) mode: + playing AY music using digital player - emul plays three sound samples + with volumes and frequencies taken from corresponding AY registers + and 'pseudo-turbo' - two parallel YM chips with some phase shift + + two covoxes - Pentagon (port #FB) & Scorpion (port #DD) + + SounDrive + + saving sound to WAV or VTX (VTX - good format for AY sound, much + more compact than PSG, but you need lha.exe somewhere in %PATH%) + + General Sound - FX & MOD player through BASS sound system. + You can save music and listen it in your favourite mod-player + + General Sound low level emulation (including internal Z80 on 12MHz, + 512Kb onboard memory, internal ports and DACs) + + BASS offers higher quality and more sound channels than low-level + emulation. Low-level emulation is useful for programs altering the + firmware player and using second Z80 for their own needs. In general, + use BASS for games, and low-level emulation for advanced sound/music + editors (RIFF tracker) + + video + + several video drivers, from simple and fastest to complex bi-linear + filtering, that upsamples Spectrum display to 512x384 * 64 colors; + 'noflic' filter is specially for programs fast switching two screens + to get more colors; + several chunky filters for 21st century demos + + amazing video filter: Anti-Text64. Designed for reading e-zines. + It converts 4x8 fonts to 8x8 or 8x16 on the fly and saves your eyes + + multicolors are fixed for Pentagon & Scorpion, 1T screen update + for all border effects + + able to emulate scorpion timings, including scorpion border update + + nopaper mode for debugging border effects + + flash-color (using 128 ink colors with black paper when FLASH bit on) + + hi-res modes: Pentagon 512x192, Profi 512x240, ATM + (works with 'double' video filter) + + ATM-TURBO color video modes (works with 'double' video filter): + text mode 80x25, EGA mode 320x200, 640x200. + To use custom text-mode font, place your "SGEN.ROM" to unreal directory. + Don't use border=none with ATM video modes + + Pentagon 384x304 mode [see ZX-Guide#2, AlCoNews#32] + + Pentagon color-per-pixel mode [see Info Guide#8, #9] + + hardware multicolor + + attribute port #FF + + + tune-up video/sound with hotkeys + + change video settings, such as number of t-states/frame, t-states/line, + fix with grey+, grey-, grey* + + t-states/frame is a computer speed, so it's easy to adjust speed in + fast (or slow) games in very wide range for comfortable playing + + alt+grey*, grey-, grey+ selects soundchip (AY/YM) and + stereo mode (ABC/ACB), or palette + + + beta 128 disk interface + + implemented all FDC WD1793 commands, including read/format track + + traps for read/write sector - more then 256 times speedup + + + tape + + full TAP and TZX support + + CSW support + + tape browser + + auto-start tape when accessing to ROM load routine + + fast loading: UnrealSpeccy is able to recognize loader logic + and speed up loader by removing delay loops and edge detection loops. + (Of course, UnrealSpeccy leaves original t-states timing, + so max speed mode must be enabled for fast loading - press NumLock) + With this technique any 128K game may be loaded in 10-20 secs. + I've developed this feature independent from RAMSOFT, but looks like + they used the same idea in RS 0.90.38... + For really fast loading, also choose 'normal' video driver + + + IDE + + UnrealSpeccy is able to use KAY, SCORPION and ATM-2 IDE interfaces with + two devices on it (hard disks or CD-ROMs) + + for hard disks you may use image file or Spectrum drive, connected to + computer. CD-ROM requires physical or windows-level emulated device + + schemes supported: + Nemo - controller for ZX-BUS manufactured by Nemo and NedoPC + ATM - ATM TURBO 2+ v7.10 + Nemo(A8) - Nemo scheme modification: uses A8 instead of A0 to + select high/low byte of 16bit data (easy to + transfer sector with just two commands INIR:INIR) + SMUC - IDE controller for ZS Scorpion + + modem + + ISA modem, configured to use COM1 ports 03F8-03FF + translated to Spectrum's ports #F8EF-#FFEF. + Modem interrupt (translated to Spectrum NMI) is not emulated + Host computer should have installed windows driver for real modem. + At least ping/dns by Kamil Karimov tested and works fine + with empty 'modem initialization string' + + + input devices + + fully customizable keyboard + copy text from clipboard via emulation of Spectrum keyboard + AY-mouse (KORVET mouse, connected to AY-3-8910/12 port A) + kempston mouse with adjustable speed and swap-buttons option, + mouse wheel included (D7..D4 in buttons' port or UP/DOWN keys) + kempston joystick on keyboard + kempston joystick on mouse (joystick will follow mouse cursor) + mouse wheel and buttons can be bound to any zx keys or kempston + PC joystick/gamepad can be bound to any zx keys or kempston + sticky keys mode [stolen from ZX_Emul, read description there] + autofire (with adjustable key and period) + + files + + reading: + + SP, SNA and Z80 (all versions, 48k and 128k) + TAP, TZX, CSW + FDI, TRD, SCL, TD0, UDI + if disk image does not contain boot.B, it may be appended. + Disk images for every drive may be loaded automatically + at start of UnrealSpeccy + HOBETA format - appends file to existing disk; if no disk, then empty + TR-DOS disk will be created. Best use with command line: + unreal.exe prog.$B prog.$C <- will be placed on same disk + You can also load it from 'F3' dialog, but only by one file + + writing: + + SNA 48k and 128k + TRD, FDI, UDI + TD0 (saved unpacked, so expect saved file larger than loaded) + SCR, BMP (screenshots) + WAV, VTX + + + disk images converter: + + emulator can open disk image in any format (TRD/TD0/FDI/SCL/UDI) + and save it to any other format + + + leds + + 1. AY volume/status. Just for fun. White color - tone, yellow - noise, + green - envelope; also shows YM2203 and GS volumes + + 2. perfomance leds. One displays how much Z80 t-states is between two + interrupts. While watching some demo you can see how much time + takes one or another effect. Second led shows PC framerate - at + normal speed it must be 50. If it is less, then your CPU is too + slow, and sound may be jerky - try to select faster video driver, + disable some devices, increase skipframe or decrease t-states + per int. When running on max speed, framerate shows how fast you + computer is. For example, 800 fps is 16 times faster than + Spectrum, so average game will load in 20 secs. + + 3. input led. Shows which input devices are active - keyboard, mouse, + kempston joystick. For keyboard, displays queried keyrows. + One example: I've noticed, that in KOL0BOK2 intro active keyrows + are - and A-G. is an exit, but why program + scans - ? I've pressed , and I saw that now program + queries -

. After pressing A+P, program queries -. + When pressing A+P+X active keyrow areonly -. So, I've + found cheat without digging in program code! (Another way was to + set breakpoint on reading port #FE and analyze code nearby) + + 4. loading led. When saving/loading from disk, emulator shows + blue or red diskette. When formatting track, yellow disk is + displayed. When loading from tape, emulator displays + cassette picture and progress for current block (or 'PS' - tape + silence) + + 5. local time - for those ZX Spectrum fans, who spend all their + time with virtual Spectrum + + 6. on-screen watches - display used RAM and ROM banks in last frame, + count user-defined expressions + + 7. using leds on AT keyboard to display AY amplitude peaks + + + leds look strange when flash-color is enabled. It's not a bug + + + monitor + + software that in PC world is called 'debugger', in Spectrum world + is often called 'monitor' + + find text or code. + Using mask: code matches when ((code & mask) == (pattern & mask)) + + unlimited number of code breakpoints + + conditional breakpoints of any complexity, uses C-like expression + syntax (with this feature can be easily done: breakpoint on range, + breakpoint to port in and out, on instruction, etc..) + + memory access breakpoints (on reading/writing/executing of given memory + areas) + + built-in Z80 assembler/disassembler + + save-load memory blocks + + disk editor + + ripper's tool: for ripping routines with their data. + Marks which bytes were read/written, and saves whole 64kb memory, + replacing unreferenced bytes with user-defined byte (default is #CF) + + hotkeys to save/restore cursor position in disasm window + + hotkeys for location instruction operand in Z80 or dump window + + on-screen watches: show memory areas addressed by register pairs + + + GUI + + all settings, that present in INI file, may be changed in GUI + (but will not be saved, for permanent effect edit INI file) + + easy entering POKEs, if you don't want to use monitor or use + HEX addresses. Delimiters for numbers are space, comma, colon, + semicolon - so POKEs can be cut'n'pasted from any cheats collection + + loading from archives. Now supported: CAB,ZIP,RAR. + You can add your own archivers to INI file + +---------------------------------------------------------------------- + +system requirements + + Windows 95 or higher, or NT (not tested). + Windows 2000/XP/2003 are required for real HDD support. + + for running UnrealSpeccy you need: + + Pentium-166 CPU MMX for basic functionality + Celeron-266 CPU for Profi/Pentagon/ATM/Anti-Text64 hi-res modes + Celeron-400 CPU for GS low-level emulation + UnrealSpeccy eats only 12Mb of RAM, so + if you've started Windows, then memory is enough + 2D accelerared video card with hardware support of + video overlays and color keying for overlay modes + 3D accelerared video card for hardware chunky bilinear filter + 2 Mb of video memory in fullscreen + 4 Mb of video memory for windowed mode + 16-bit sound card [optional, but recommended] + + + if you have such hardware, but emulator runs slow, try the + following: + + 0. install latest directX and videocard drivers + 1. increase sound buffer size (especially for Win95) in INI file + 2. select video filter='small', video driver='ddraw' + 3. disable vsync + 4. set 'ShareCPU=0' in INI file + 5. do not use General Sound low-level emulation + 6. do not use YM2203 + 7. set 'skipframe=4' in INI file + 8. set 'Frame=30000' in INI file (if nothing else does not work) + 9. disable sound (without sound emulator runs much faster) + + this should help + +---------------------------------------------------------------------- + +command line parameters + +/i specify custom INI file, default: + emulator_executable_filename_without_extension.ini +/l specify labels file, default: user.l +/b specify breakpoints file, default: bpx.ini + + +---------------------------------------------------------------------- + +keys in emulator + +these are default keys, can be changed in INI file + + +Ctrl-F1 monitor +Shift-ESC only for windowed mode: give mouse to emulator or to MS Windows +Alt-F1 help on keys +F1 options/GUI +F2 write snapshot/disk image +Alt-F2 quicksave to qsave1.sna +Ctrl-F2 quicksave to qsave2.sna +Shift-F2 quicksave to qsave3.sna +F3 read snapshot/tape/disk image +Alt-F3 quickload from qsave1.sna +Ctrl-F3 quickload from qsave2.sna +Shift-F3 quickload from qsave3.sna +(SHIFT+)F4 select video driver (filter) +Alt-F4 exit emulator +F5 start/stop saving sound (WAV or VTX formats). + May be used from monitor: set breakpoint to + start/stop routines and save; + when saving to vtx, start and end silence are wiped +F6 enter POKE(s) +Alt-F6 search in memory for changed values (cheats, etc) +F7 start/stop tape +Shift-F7 tape browser +Alt-F8 screenshot +F9 quicksave all altered disk images and CMOS +Alt-Shift-F11 NMI +Alt-F11 NMI to cache +Ctrl-F11 NMI to TR-DOS ("magic") +F12 reset to default mode, as specified in INI file +Ctrl-Shift-F12 reset to basic-128 +Ctrl-F12 reset TR-DOS (128) +Shift-F12 reset to 48 basic, but port 7FFD unlocked +Alt-Shift-F12 reset to 48 basic, port 7FFD locked +Alt-F12 reset to system ROM +Ctrl-Alt-F12 reset to cache +pause/break pause +grey/ leds on/off +grey* select parameter to change: t-states/int (speed), fix, etc... +Alt+grey* select parameter to change: AY/palette +grey+,grey- change sound/video parameter (press with SHIFT for finetuning) +NumLock max speed on/off (no sound on max speed) +Alt-NumLock toggle autofire on/off +Alt-Ctrl-Z toggle ATM XT(AT)-keyboard controller on/off +Alt-Enter toggle full-screen mode +Alt-Shift-Ins copy text from clipboard to keyboard buffer +Alt-1 set window size to 1x scale in windowed mode +Alt-2 set window size to 2x scale, useful for hardware-stretched + modes like overlay and blitter +Alt-3 maximize window + + +keys in monitor + +(CTRL,SHIFT,ALT+)F1,F2,F3,F5,F11,F12 - see above +Ctrl-F1 exit, run program +F7 step +F8 trace, skip calls and loops +F9 show Spectrum screen +Shift-F9 show alternative 128k screen +Alt-F9 show screen until painted point with border, mc and 2-scr +effects +F11 run program until address (SP) +TAB next window +SHIFT-TAB previous window +Alt-D toggle text/hex dump +Alt-B write to port 7FFD (select bank / screen) +Alt-M write to model-specific extended memory port (like +#1FFD,#FDFD,#DFFD,...) +Alt-P enter POKE(s) +Alt-F6 search in memory for changed values (cheats, etc) +Alt-R load memory block from file +Alt-W save memory block to file +Alt-F fill memory block with hex pattern + (note: to copy block, please use save+load) +Alt-G General Sound status (double-click - play sample) +Alt-T ripper's tool +Alt-O configure on-screen watches +Alt-U disasm to file +Alt-S cycle screenshot (ray-painted) / screendump (screen memory) / +watches +Alt-F render current frame to video buffer without border and MC +effects +Ctrl-U set user-defined watch address +Ctrl-L view labels, visible for Z80 in current memory state +Ctrl-J select and go to label +Alt-C breakpoints manager +Alt-Y switch active sound chip in Turbo-Sound mode + + + manager can handle 3 types of breakpoints: + + 1. conditional breakpoints and on-screen watches + are using C expression syntax. + + operators (in priority order): + () ! ~ M(x) -> * % / + - >> << > < == = <= >= != & ^ | && || + operands: + a,b,c,d,e,h,l,bc,de,hl - common registers + a',b',c',d',e',h',l',bc',de',hl' - alternative registers + ix,iy,pc,sp,i,r - control registers + FD - memory port #FD (also known as #7FFD) + PG0, PG1, PG2, PG3 - TS-conf page registers for memory windows at #0000, #4000, #8000, #C000 + OUT - port used in 'OUT' command (or 0FFFFFFFF when no outs) + IN - port used in 'IN' command (or 0FFFFFFFF when no ins) + VAL - read/written value to port (only meaningful when OUT != +0FFFFFFFF or IN != 0FFFFFFFF) + RD - address used in memory read operation + WR - address used in memory write operation + MDT - data read/written to memory + numbers - hex, must start with digit: DFFD - wrong, 0DFFD - right + characters - must be enclosed in single quotes ('A','B',etc..) + All calculations use 32-bit unsigned interger arithmetic. + If you are not familiar with C, use brackets as much as possible, due to + non-intuitive operator's precedence, e.g. + out & 0FF == 0FE is treated as 'out & (0FF == 0FE)' <- always 0 + + M(x) is byte from memory address x (same as x->0) + '=' is an alias for '==' (for Pascal programmers) + + examples: + + (out+1) | (in+1) - in/out to any port + (in & 8001) == 0 - read keyboard row from to + !(out & 1) - any out to port #FE + (out & 0FF)==0BB && (val==0F3 || val==0F4) - reset General Sound + (out & 0FF)==0FD && (val&7)==3 - set 3rd memory page + (FD & 7) == 3 - the same + M(pc)==0CB && pc->1 >= 10 && pc->1 <= 17 + - set break on executing instruction "rl " + + I hope, these examples will be enough to get idea + + note: double-click on breakpoint to edit it + + 2. execution breakpoints (also set in CPU window by pressing SPACE). + In manager window it is easy to setup breakpoint on execution + range like 6000-FFFF + + 3. breakpoints on reading/writing memory. Similar to execution + breakpoints, but have flags "R" and "W" to issue interrupt on + reading/writing memory address (flags may be set together) + + +monitor: registers window + +Cursor select register +ENTER,0-F change register +Ctrl+A,F,B,D,H,S,P,X,Y,I,R,M,1,2 edit A,F,BC,DE,HL,SP,PC,IX,IY,I,R,IM,IFF1,IFF2 +Alt+S,Z,5,H,3,P,N,C toggle flag +' go to value under cursor in CPU window +; go to value under cursor in memory window + + +monitor: CPU window + +F4 trace to cursor +Alt-F7 find text +Ctrl-F7 find code with mask +HOME go to PC +Ctrl-G go to address +SPACE set/reset breakpoint under cursor +ENTER edit Z80 instruction, opcode bytes or cursor address +A-X assemble new Z80 instruction +Z set PC to cursor +Ctrl-1,.,Ctrl-8 save current position to slot 1-8 +1-8 save current position to stack, go to saved position in slot 1-8 +Backspace restore position from jumpstack +' push cursor position and go to instruction operand +; jump to instruction operand in data window +Ctrl-A reading labels from XAS or ALASM, using 3 methods: + 1. XAS7: assembler should be in bank 6 (or bank #46 for Pentagon-512) + 2. ALASM: version 4.42-5.0x should be in 128k-memory (pages 1-7) + 3. ALASM+STS: STS should be in bank#7 (or bank #47 for Pentagon-512) + + +monitor: memory window + +Alt-F7 find text in memory / disk track +Ctrl-F7 find code with mask in memory / disk track +Cursor,Home,End,PgUp,PgDn move +Ctrl-G go to address +Ctrl-M select memory editor +Ctrl-V select disk editor (physical track) +Ctrl-O select disk editor (logical sectors) +Ctrl-T select disk/track/sector in diskeditor +Ctrl-TAB move to hex/ascii +Ctrl+S,P,B,D,H,X,Y go to SP,PC,BC,DE,HL,IX,IY +other keys edit + +monitor: Beta 128 window + +here is a typical FDC indicator content +CD:1820 +STAT:21 +SECT:09 +T:01/01 +S:3C/00 + +first line is command and data register (cmd18,data20 - seek track 20) +2nd line - status register +3rd line - sector register +4th line - actual FDD head position and track register +5th line - Beta disk system register and DRQ/INTRQ port + +---------------------------------------------------------------------- + +bugs and incompatibilities + + + some programs, such as Manic Miner and Lode Runner, require + old 48K ROM (1982.rom), while some other programs require + ZX Spectrum 128 ROM (48for128.rom + 128tr!.rom) + + some TR-DOS programs (Unreal Megademo by KSA, Street Fighter) + work only if TR-DOS traps are disabled and all delays enabled + + Profi service ROM can work only when all TR-DOS delays are enabled + + Bumpy does not work in default configuration, it tries to read port + #FFDF for kempston joystick - so disable kempston mouse before + playing [bug found by Paul Pavlov] + + sounddrv=wave does not work with CTSB128PCI (Creative Sound Blaster), + there is a bug in drivers for this card. [bug found by Nuts] + + some ABS adventures (ALIEN.SCL, LENIN.SCL) do not work with TR-DOS 5.04TM, + use version 5.03 - it is most compatible [bug found by Paul Pavlov] + + Vixen 1,2,3 - incompatible with SounDrive [bug found by Paul Pavlov] + Fernandez - incompatible with SounDrive [bug found by Shadow Maker] + + port #FF sometimes is not responding when SkipFrame!=0 + (the same to SkipFrameMaxSpeed!=0 and max speed mode) + + Scorpion 'MAGIC' generates NMI even if PC < #4000, but shadow + monitor is not designed for this situation and may crash + + CacheVox by AlCo requires TR-DOS delays enabled and [BETA128] IL=1 + + If the emulator after returning of focus seems to have a sticky key, + press Scroll Lock, deactivate blinking key, then press Scroll Lock again. + + If emulator control keys work wrong after returning of focus, press Enter, + and keys must go normal. + + If emulator hangs with General Sound, try DSPrimary=0. + + If emulator crashes with General Sound, try GSType=Z80. + + If Kempston Joystick doesn't work, go to menu, press NumLock there, +then return. + + In case of bad sound, try DSPrimary=1 (may crash emulator eventually!) +or SoundDrv=wave in .ini. + + Screenshots are saved to the emulator directory. + Files to read and write via debugger are situated in a current directory +(which is seen by F3). + +Download sys.trd (for drive B:) from http://alonecoder.narod.ru/zx/SYS.rar +Download xBIOS for ATM Turbo2+ from http://atmturbo.nedopc.com/ +Get WNASPI32.DLL from Ahead Nero or from Ahead website + +BASS.DLL v2.3 was taken from http://www.un4seen.com/ + +---------------------------------------------------------------------- + +greetings from SMT: + + * dj.Z, IMP and MIC - I enjoyed your music a lot ! + * Nik-O and Fatal Snipe - for adherence to ay scene + * Hacker KAY - thanks for most accurate information about AY/YM + * Paul Pavlov - maintainer of largest TR-DOS archive + * Raul Gomez Sanchez (author of R80) + * Stalker - thanks for STS + * Placebo, Extreme, Digital Reality, CBS - for quality demos + * V_Soft (Roman Scherbakov) - for Vortex format + * Sergey Bulba - for AY_Emul + * Alone Coder - you keep Spectrum alive + * Ian Luck - thanks for BASS + * SirX - just a hello + * all ZX Spectrum fans - Speccy is da best! + + +special thanks to people, who were interested in +UnrealSpeccy's development and sent me info about hard/soft: + + * Maxim Vasilyev - sent me many bugreports + * Mac Buster - some info about beta 128 disk interface + * Shiru Otaku - sent me GSPAK.ZIP + * Alexey Kravchenko - Scorpion timing info + * Vitaliy Pigo - testing and benchmarking + * Alex Baskakov - helped me with TD0 format + * Alone Coder - thnx for many info about Pentagon hardware + * Dexus - for integrating YM2203 support + * Maksagor - ATM information + * and many others, who emailed me congratulations/suggestions/etc + +---------------------------------------------------------------------- + +license + + UnrealSpeccy is completely freeware. You may use this emulator + and its sources without any restrictions. You may use the + sources in your own emul or to release bugfixed/new versions. + in this case you do not have to release updated source code, + (unlike GNU Public License requires, for example). You may port + emulator to any other platform (but there are many + MSVC-specific language constructions, so it's doubtful that + you can compile it with another compiler) + + diff --git a/tools/unreal/doc/unreal_r.txt b/tools/unreal/doc/unreal_r.txt new file mode 100644 index 0000000..9613099 --- /dev/null +++ b/tools/unreal/doc/unreal_r.txt @@ -0,0 +1,710 @@ +---------------------------------------------------------------------- + UnrealSpeccy 0.37.7 by SMT and Others +---------------------------------------------------------------------- + + содержание + + 1. возможности + 2. требования к системе + 3. командная строка + 4. управляющие клавиши эмулятора + 5. несовместимость + 6. приветы и благодарности + 7. лицензия + + +---------------------------------------------------------------------- + +возможности + + + процессор + Z80: все (не)документированные команды и флаги + + + модели памяти + + Pentagon 128/512/1024 с 64K ПЗУ + TS-Configuration + Scorpion 256/1024 с 64K ПЗУ + Scorpion ПрофПЗУ с поддержкой SMUC (128/256/512K) + KAY-256/1024 с 64K ПЗУ + ZX Evolution + Profi 1024 ОЗУ/ПЗУ с memory mapper'ом + ATM TURBO 1 v4.50 512/1024 ОЗУ, 64-1024K ПЗУ + ATM TURBO 2+ v7.10 128/512/1024 ОЗУ, 64-1024K ПЗУ + кэш 16/32K + CMOS/часы(RTC): + советская микросхема (512ВИ1, 64 байта, 1-й день - понедельник) или + микросхема из IBM PC (DALLAS, 256 байт, 1-й день - воскресенье) + часы и энергонезависимое ОЗУ (NVRAM) (24LC16) контроллера SMUC + + звук + + бипер и звук магнитофона + + AY-3-8910/YM2149: точная эмуляция, включая цифровой звук + и нестандартные эффекты, но параллельные порты на R14/R15 не эмулируются + + Turbo-AY (Turbo-Sound): 2 микросхемы AY(или YM) по схеме PoS или NedoPC + + TurboSound FM: 2 микросхемы YM2203 (6 AY каналов + 6 FM каналов) + + режим 'use digital samples' (отсутствующий на реальном Спектруме): + AY-музыка через цифровой плейер - играются три сэмпла + с громкостями и частотами из соответствующих регистров AY + + режим 'pseudo-turbo': 2 запараллеленных микросхемы YM со сдвигом по фазе + + два ковокса - Pentagon (порт #FB) и Scorpion (порт #DD) + + SounDrive + + запись звука в WAV или VTX (VTX - хороший формат для AY звука, гораздо + плотнее, чем PSG, но для него требуется lha.exe где-либо в %PATH%) + + General Sound - плейер MOD и звуковых эффектов через библиотеку BASS. + Можно сохранить музыку, чтобы затем слушать её во внешнем плейере + + низкоуровневая эмуляция General Sound (внутренний Z80 на 12MHz, + 512Kb внутреннего ОЗУ, внутренние порты и ЦАПы) + + BASS даёт более высокое качество и больше каналов, чем низкоуровневая + эмуляция. Низкоуровневая эмуляция нужна для программ, подменяющих + фирменный плейер и использующих Z80 для своих целей. На практике + BASS рекомендуется для игр, а низкоуровневая эмуляция - для продвинутых + музыкальных/звуковых редакторов (RIFF tracker) + + графика + + множество фильтров, от быстрого simple до билинейной + фильтрации, масштабирующей спектрумовское изображение до 512x384 + с 64 цветами; + фильтр 'noflic' для программ, которые быстро переключают два экрана, + чтобы получить больше цветов; + несколько чанковых фильтров для демок XIX века + + замечательный фильтр 'Anti-Text64'. Предназначен для чтения прессы. + На лету заменяет шрифты 4x8 на более приятные для глаз шрифты 8x8 и 8x16 + + настройки Pentagon и Scorpion для мультиколорных эффектов, + причём бордер обновляется с точностью до такта + + для эмуляции скорпионовских бордерных эффектов имеется режим + обновления бордера раз в 4 такта + + режим nopaper для отладки бордерных эффектов + + flash-color (в каждом знакоместе при включенном бите FLASH можно выбрать + один из 128 цветов ink на чёрном фоне) + + режимы высокого разрешения: Pentagon 512x192, Profi 512x240, ATM + (работают только с фильтром 'double') + + цветные видеорежимы ATM-TURBO (только с фильтром 'double'): + текстовый 80x25, EGA 320x200, мультиколор 640x200. + Для смены шрифта текстового режима поместите SGEN.ROM в папку unreal. + Не включайте border=none в видеорежимах ATM + + режим 384x304 компьютера Pentagon [см. ZX-Guide#2, AlCoNews#32] + + режим "цвет на точку" компьютера Pentagon [см. Info Guide#8, #9] + + аппаратный мультиколор + + порт атрибутов #FF + + + настройка графики и звука горячими клавишами + + параметры графики (число тактов в кадре и в строке) выбираются и + настраиваются клавишами grey+, grey-, grey* на цифровой клавиатуре + + t-states/frame (число тактов в кадре) позволяет подобрать скорость + в слишком быстрых (или слишком медленных) играх + + комбинации alt+grey*, grey-, grey+ выбирают звуковой сопроцессор (AY/YM) + и стереорежим (ABC/ACB), или же палитру + + + дисковый интерфейс Beta 128 + + все команды контроллера ВГ93 (WD1793), включая чтение/запись дорожки + + перехват чтения/записи секторов - ускорение более чем в 256 раз + + + магнитофон + + полная поддержка TAP и TZX + + поддержка CSW + + выбор файла (tape browser) + + автозапуск ленты при обращении к стандартной процедуре чтения + + быстрая загрузка: UnrealSpeccy может распознать логику загрузчика + и ускорить его путём пропуска циклов задержек и ожидания фронтов. + Разумеется, UnrealSpeccy при этом не сбивает тайминги, + так что для быстрой загрузки требуется включить max speed (NumLock). + Таким образом, любую 128K игру можно загрузить за 10-20 секунд. + Эта хитрость разработана независимо от RAMSOFT, но похоже, что + RS 0.90.38 работает аналогичным образом. + Для действительно быстрой загрузки включите видеофильтр 'normal' + + + IDE + + UnrealSpeccy эмулирует IDE-интерфейсы компьютеров KAY, SCORPION и ATM-2 + с двумя устройствами типа жёсткий диск/CD-ROM + + вместо реальных жёстких дисков можно использовать файл-образы. + CD-ROM эмулируется через реальное устройство или через виртуальное, + работающее на уровне операционной системы + + эмулируемые схемы: + Nemo - контроллер для шины ZX-BUS, производившийся Nemo и NedoPC + ATM - ATM TURBO 2+ v7.10 + Nemo(A8) - изменение схемы Nemo: использует A8 вместо A0, чтобы + выбирать старший/младший байт 16-разрядного слова (можно + считать сектор двумя командами INIR:INIR) + SMUC - IDE-контроллер для компьютера ZS Scorpion + + модем + + Внутренний ISA-модем, настроенный на COM1 (порты 03F8-03FF, + транслируемые на спектрумовские порты #F8EF-#FFEF). + Прерывание от модема (NMI на Спектруме) не эмулируется. + Требуется установить драйвер модема в Windows. + Программа ping by Kamil Karimov проверена и работает + при пустой строке инициализации модема (modem initialization string) + + + устройства ввода + + полностью настраиваемая клавиатура + вставка текста из буфера обмена через эмуляцию спектрумовской клавиатуры + AY-mouse (мышь от КОРВЕТа, подключённая к порту A звукового сопроцессора) + kempston mouse с настраиваемой скоростью и опцией обмена кнопок, + с поддержкой колеса (D7..D4 в порту кнопок либо как клавиши UP/DOWN) + kempston joystick на клавиатуре + kempston joystick на мыши (джойстик следует за курсором мыши) + колесо и кнопки мыши можно привязать к любым клавишам + клавиатуры Спектрума или кемпстон-джойстика + джойстик для PC можно привязать к любым клавишам + клавиатуры Спектрума или кемпстон-джойстика + режим залипших клавиш (sticky keys) [взято из ZX_Emul, см. его описание] + autofire (настраиваемая клавиша, настраиваемый период) + + файлы + + читаются: + + SP, SNA и Z80 (все версии, 48k и 128k) + TAP, TZX, CSW + FDI, TRD, SCL, TD0, UDI + если образ диска не содержит boot.B, + можно его автоматически добавлять. + Можно использовать загрузку образов дисков для + каждого из дисководов при запуске эмулятора + формат HOBETA - файл добавляется на существующий диск; + если нет диска, создаётся пустой TR-DOS диск. + Удобно грузить из командной строки: + unreal.exe prog.$B prog.$C <- будут помещены на один диск + Можно грузить хобетные файлы через меню 'F3', + но только по одному файлу + + записываются: + + SNA 48k и 128k + TRD, FDI, UDI + TD0 (записывается без сжатия, поэтому записанный файл, скорее всего, + будет больше считанного) + SCR, BMP (скриншоты) + WAV, VTX + + + преобразование форматов: + + эмулятор может открыть образ диска в одном из форматов + (TRD/TD0/FDI/SCL/UDI) и записать его в другом формате + + + индикаторы + + 1. громкость/состояние AY. Белый цвет - тон, жёлтый - шум, + зелёный - огибающая; также отображаются громкости YM2203 и GS + + 2. индикаторы performance. Один показывает, сколько тактов Z80 + прошло между двумя прерываниями. При просмотре демонстраций можно + увидеть, сколько времени занимает тот или иной эффект. Второе число - + частота кадров PC - на нормальной скорости должно быть 50. + Если оно меньше, то ваш компьютер слишком медленный, и вы слышите + искажённый звук. Выберите более быстрый видеодрайвер и фильтр, + отключите какие-нибудь устройства, увеличьте skipframe или убавьте + t-states per int. При прогоне на max speed частота кадров показывает, + насколько ваш компьютер быстр. Например, 800 fps - это в 16 раз + быстрее, чем Spectrum, и средняя игра загрузится за 20 секунд. + + 3. индикатор input. Показывает, какие устройства ввода опрашиваются - + клавиатура, мышь, кемпстон-джойстик. Для клавиатуры показываются + опрашиваемые полуряды. Пример: я заметил, что в интро к KOL0BOK2 + активны полуряды - и A-G. - это выход, но почему + программа опрашивает -? Я нажал и увидел, что теперь + программа опрашивает -

. После нажатия A+P программа опрашивает + -. После нажатия A+P+X активным остаётся только полуряд + -. Получается, что я нашёл cheat без залезания в код! + (Можно было поставить breakpoint на чтение порта #FE и анализировать + код в этом районе) + + 4. индикатор loading. При записи/чтении диска эмулятор показывает + синюю/красную дискету. При форматировании дорожки дискета жёлтая. + При загрузке с магнитофона эмулятор показывает кассету и прогресс + считывания текущего блока (или 'PS' - пауза в записи) + + 5. local time - часы для тех, что проводит всё своё время с + виртуальным Спектрумом + + 6. on-screen watches - показывает банки ОЗУ и ПЗУ, использованные + в последнем кадре; вычисляет выражения, определённые пользователем + + 7. можно использовать лампочки на AT клавиатуре в качестве индикаторов + громкости AY каналов + + + индикаторы необычно окрашиваются в режиме flash-color. Это не глюк + + + монитор-отладчик + + программа, называемая в мире PC "дебаггером", в мире Spectrum часто + носит название "монитор" + + поиск текста или кода. + Использует маску: совпадение, когда ((code & mask) == (pattern & mask)) + + неограниченное число точек останова (breakpoints) + + остановы по условию (conditional breakpoints) любой сложности, + используют Си-подобный синтаксис (через них можно реализовать останов + по чтению или записи в порт определённого значения, по определённой + команде процессора и т.п.) + + остановы по обращению к памяти (по чтению/записи/исполнению указанных + областей памяти) + + встроенный ассемблер/дизассемблер Z80 + + сохранение-загрузка областей памяти + + редактор образа диска (диск-доктор) + + ripper's tool: для извлечения процедур с их данными. Показывает, какие + байты были считаны/записаны, и сохраняет всё адресное пространство + процессора, забивая неиспользованные адреса указанным байтом + (по умолчанию #CF) + + горячие клавиши сохранения/восстановления положения курсора в окне + дизассемблера + + горячие клавиши перехода по адресу, указанному в команде - в окно + дизассемблера или в окно дампа + + on-screen watches: показывают области памяти, адресованные + регистровыми парами + + + графический интерфейс (GUI) + + все настройки, имеющиеся в INI-файле, можно изменять в GUI + (но они не сохраняются, для постоянного изменения редактируйте INI-файл) + + легко вводимые POKE, если вы не хотите использовать монитор или + шестнадцатеричную систему счисления. Разделители чисел - пробел, + запятая, двоеточие, точка с запятой - так что можно копировать POKE + прямо из коллекций cheat'ов + + чтение из архивов. Поддержаны: CAB,ZIP,RAR. + Можете добавить свои архиваторы в INI файле + +---------------------------------------------------------------------- + +требования к системе + + Windows 95 и выше, либо NT (не проверялось). + Для работы с реальным HDD требуется Windows 2000/XP/2003. + + + для работы UnrealSpeccy требуется: + + Pentium-166 CPU MMX для минимального функционирования + Celeron-266 CPU для режимов высокого разрешения + Profi/Pentagon/ATM/Anti-Text64 + Celeron-400 CPU для низкоуровневой эмуляции GS + UnrealSpeccy занимает всего 12Mb ОЗУ, так что + если Windows запустился, то памяти хватает + видеокарта с 2D ускорителем, поддерживающая video overlays и + color keying for overlay modes + видеокарта с 3D ускорителем для hardware chunky bilinear filter + 2 Mb видеопамяти в полноэкранном режиме (fullscreen) + 4 Mb видеопамяти в оконном режиме (windowed) + 16-битная звуковая карта [не обязательна, но рекомендуется] + + + Если вы имеете соответствующее железо, но эмулятор тормозит, + то попробуйте следующее: + + 0. установите свежий directX и свежие дрова видеокарты + 1. увеличьте размер sound buffer (особенно для Win95) в INI-файле + 2. выберите video filter='small', video driver='ddraw' + 3. отключите vsync + 4. установите 'ShareCPU=0' в INI-файле + 5. отключите низкоуровневую эмуляцию General Sound + 6. отключите YM2203 + 7. установите 'skipframe=4' в INI-файле + 8. установите 'Frame=30000' в INI-файле + (если больше ничего не помогает) + 9. отключите звук (без звука эмулятор работает значительно быстрее) + + Это должно помочь + +---------------------------------------------------------------------- + +параметры командной строки + +можно запускать эмулятор, указав следующие параметры командной строки: + +/i <файл> указать собственный файл конфигурации, по умолчанию: + имя_исполняемого_файла_эмулятора_без_расширения.ini +/l <файл> указать файл меток, по умолчанию: user.l +/b <файл> указать файл ловушек, по умолчанию: bpx.ini + +кроме того после параметров можно указать список файлов, поддерживаемых +эмулятором форматов для загрузки. + +---------------------------------------------------------------------- + +управляющие клавиши эмулятора + +перечислены клавиши по умолчанию, их можно сменить в INI-файле + +Ctrl-F1 монитор-отладчик +Shift-ESC для оконного режима: отдать мышь эмулятору или системе +Alt-F1 помощь по клавишам +F1 настройки/GUI +F2 записать снапшот/образ диска +Alt-F2 записать снапшот qsave1.sna +Ctrl-F2 записать снапшот qsave2.sna +Shift-F2 записать снапшот qsave3.sna +F3 загрузить снапшот/образ ленты/образ диска +Alt-F3 загрузить снапшот qsave1.sna +Ctrl-F3 загрузить снапшот qsave2.sna +Shift-F3 загрузить снапшот qsave3.sna +(SHIFT+)F4 выбрать видеодрайвер (фильтр) +Alt-F4 выход из эмулятора +F5 начать/закончить сохранение звука (WAV или VTX). + Можно использовать из монитора: установите точки останова на + начальных/конечных процедурах и записывайте; + при записи vtx убираются паузы в начале и конце +F6 ввести POKE (одно или много) +Alt-F6 поиск изменённых значений в памяти (для cheat'ов и т.п.) +F7 запустить/остановить ленту +Shift-F7 выбор файла на ленте (tape browser) +Alt-F8 записать скриншот +F9 сохранить все изменённые образы диска и содержимое CMOS-часов +Alt-Shift-F11 NMI +Alt-F11 NMI в кэш +Ctrl-F11 NMI в TR-DOS ("magic") +F12 сброс в режиме по умолчанию, указанном в INI +Ctrl-Shift-F12 сброс в 128 бейсик +Ctrl-F12 сброс в TR-DOS (128) +Shift-F12 сброс в 48 бейсик с открытой верхней памятью +Alt-Shift-F12 сброс в 48 бейсик в режиме 48k +Alt-F12 сброс в системную страницу ПЗУ +Ctrl-Alt-F12 сброс в кэш +pause/break пауза +grey/ индикаторы on/off +grey* выбор параметра для изменения: t-states/int (скорость), fix... +Alt+grey* выбор параметра для изменения: AY/палитра +grey+,grey- настройка выбранного параметра (с SHIFT'ом более тонкая) +NumLock максимальная скорость (max speed) on/off (в max speed нет звука) +Alt-NumLock autofire on/off +Alt-Ctrl-Z on/off контроллер клавиатуры XT(AT) компьютера ATM +Alt-Enter полноэкранный режим on/off +Alt-Shift-Ins вставить текст из буфера обмена через эмуляцию клавиатуры Спектрума +Alt-1 установить размер окна 1x в оконном режиме +Alt-2 установить размер окна 2x, полезно для режимов, использующих + графический ускоритель, таких как overlay и blitter +Alt-3 максимизировать окно + + +клавиши в мониторе-отладчике + +(CTRL,SHIFT,ALT+)F1,F2,F3,F5,F11,F12 - см. выше +Ctrl-F1 выход из монитора в программу +F7 шаг (выполнить одну команду процессора) +F8 шаг без трассировки подпрограмм и циклов +F9 показать экран Спектрума +Shift-F9 показать неактивный экран 128k Спектрума +Alt-F9 показать экран до текущей отрисованной точки, с бордером, + мультиколорами и двухэкранными эффектами +F11 запустить программу с остановом в точке (SP) +TAB следующая панель +SHIFT-TAB предыдущая панель +Alt-D переключение текст/дамп +Alt-B изменить значение в порту 7FFD (выбор банка / экрана) +Alt-M изменить значение дополнительного порта памяти (#1FFD,#FDFD,#DFFD,...) +Alt-P ввести POKE (одно или много) +Alt-F6 поиск изменённых значений в памяти (для cheat'ов и т.п.) +Alt-R загрузить область памяти из файла +Alt-W записать область памяти в файл +Alt-F заполнить область памяти указанной последовательностью + (чтобы скопировать область памяти, запишите её, потом загрузите) +Alt-G состояние General Sound (двойной щелчок мышью - играть сэмпл) +Alt-T выдиралка процедур (ripper's tool) +Alt-O настройка on-screen watches +Alt-U дизассемблирование в файл +Alt-S переключение скриншот (отрисованный с мультиколорами и т.п.) / + дамп экрана (изображение экранной памяти) / watches +Alt-F отрендерить текущий кадр в видеобуфер без мультиколорных + эффектов и т.п. +Ctrl-U установить произвольный адрес для watch +Ctrl-L показ меток, видимых для Z80 в текущем состоянии диспетчера памяти +Ctrl-J переход по меткам +Alt-C менеджер точек останова (breakpoints) +Alt-Y переключить активный звуковой сопроцессор в режиме Turbo-Sound + + + менеджер точек останова оперирует тремя типами точек останова: + + 1. условные остановы (conditional breakpoints) и on-screen watches + используют Си-подобный синтаксис выражений. + + операции (перечислены в порядке убывания приоритета): + () ! ~ M(x) -> * % / + - >> << > < == = <= >= != & ^ | && || + операнды: + a,b,c,d,e,h,l,bc,de,hl - основной набор регистров + a',b',c',d',e',h',l',bc',de',hl' - альтернативный набор регистров + ix,iy,pc,sp,i,r - прочие регистры + FD - порт памяти #FD (также известен как #7FFD) + PG0, PG1, PG2, PG3 - регистры страниц для TS-conf для окон #0000, #4000, #8000, #C000 + OUT - порт, использованный в команде OUT (или 0FFFFFFFF, если OUT не было) + IN - порт, использованный в команде IN (или 0FFFFFFFF, если IN не было) + VAL - значение, считанное/записанное в порт (имеет смысл, только + если OUT или IN != 0FFFFFFFF) + RD - адрес памяти из которого производилось чтение + WR - адрес памяти в который производилась запись + MDT - значение, считанное/записанное в память + + числа - шестнадцатеричные, должны начинаться с цифры: DFFD - нельзя, 0DFFD - можно + коды символов - заключаются в апострофы ('A','B' и т.п.) + Все вычисления производятся в 32-битной беззнаковой целой арифметике. + Если вы плохо знакомы с Си, ставьте скобки, где возможно, + т.к. приоритет операций неинтуитивен, например: + out & 0FF == 0FE понимается как 'out & (0FF == 0FE)' <- всегда 0 + + M(x) - байт, считанный из адреса x (можно записать как x->0) + '=' - то же, что '==' (для паскалистов) + + Примеры: + + (out+1) | (in+1) - IN/OUT в любой порт + (in & 8001) == 0 - чтение клавиш от до + !(out & 1) - любой вывод в порт #FE + (out & 0FF)==0BB && (val==0F3 || val==0F4) - сброс General Sound + (out & 0FF)==0FD && (val&7)==3 - установка 3-й банки памяти + (FD & 7) == 3 - то же самое + M(pc)==0CB && pc->1 >= 10 && pc->1 <= 17 + - останавливается во время выполнения команды RL + + Надеюсь, этих примеров достаточно для понимания идеи + + Чтобы редактировать точку останова, щёлкните на ней мышью два раза + + 2. точки останова по адресам (также ставятся в окне дизассемблера кнопкой + "пробел"). В окне менеджера точек останова легко расставить точки + останова на большой диапазон памяти, например, 6000-FFFF + + 3. остановы по чтению/записи памяти. Похожи на остановы по адресам, + но имеют флаги "R" и "W", означающие останов по чтению/записи + указанных адресов памяти (можно установить оба флага) + + +монитор: окно регистров + +стрелки выбор регистра +ENTER,0-F изменение значения регистра +Ctrl+A,F,B,D,H,S,P,X,Y,I,R,M,1,2 + изменить A,F,BC,DE,HL,SP,PC,IX,IY,I,R,IM,IFF1,IFF2 +Alt+S,Z,5,H,3,P,N,C + переключить соответствующий флаг +' перейти в панель дизассемблера к адресу под курсором +; перейти в панель дампа к адресу под курсором + + +монитор: панель дизассемблера (CPU window) + +F4 выполнение программы до адреса под курсором +Alt-F7 поиск текста +Ctrl-F7 поиск кодов с учётом маски +HOME перейти на адрес PC +Ctrl-G перейти на указанный адрес +SPACE установить/снять точку останова под курсором +ENTER редактировать команду Z80, её код или текущий адрес +A-X ввести команду Z80 +Z установить PC на адрес под курсором +Ctrl-1,.,Ctrl-8 запомнить адрес курсора в спецячейку 1..8 +1-8 запомнить адрес курсора в спецстеке и перейти к адресу, + ранее запомненному в спецячейке 1..8 +Backspace вернуть курсор по запомненному в спецстеке адресу +' запомнить адрес курсора в спецстеке и перейти в панели + дизассемблера к адресу, указанному в команде под курсором +; перейти из панели дизассемблера в панель дампа + к адресу, указанному в команде под курсором +Ctrl-A чтение меток из XAS или ALASM, используя 3 метода: + 1. XAS7: ассемблер должен лежать в банке 6 (или #46 для Pentagon-512) + 2. ALASM: версия 4.42-5.0x должна лежать в 128k памяти (банки 1-7) + 3. ALASM+STS: STS должен лежать в банке 7 (или #47 для Pentagon-512) + + +монитор: окно дампа (memory window) + +Alt-F7 поиск текста в памяти / дорожке диска +Ctrl-F7 поиск кодов с учётом маски в памяти / дорожке диска +стрелки,Home,End,PgUp,PgDn движение курсора +Ctrl-G перейти на указанный адрес +Ctrl-M включить редактирование памяти +Ctrl-V включить редактирование диска (физической дорожки) +Ctrl-O включить редактирование диска (логических секторов) +Ctrl-T выбор диска/дорожки/сектора при редактировании диска +Ctrl-TAB переход между дампом и текстом +Ctrl+S,P,B,D,H,X,Y перейти по адресу SP,PC,BC,DE,HL,IX,IY +прочие клавиши редактирование + +монитор: панель дискового интерфейса Beta 128 + +здесь обычный набор индикаторов дискового контроллера +CD:1820 +STAT:21 +SECT:09 +T:01/01 +S:3C/00 + +первая строка - команда и регистр данных (1820 - переход к дорожке #20) +2-я - регистр состояния (status) +3-я - регистр сектора +4-я - реальное положение головки дисковода и содержимое регистра дорожки +5-я - системный регистр Beta disk и порт для чтения DRQ/INTRQ (порт #ff) + +---------------------------------------------------------------------- + +несовместимость + + + некоторые программы, такие как Manic Miner и Lode Runner, требуют + старое 48K ПЗУ (1982.rom), некоторые другие требуют ПЗУ от + ZX Spectrum 128 (48for128.rom + 128tr!.rom) + + некоторые TR-DOS программы (Unreal Megademo by KSA, Street Fighter) + работают только если перехват процедур TR-DOS (TR-DOS traps) выключен + и включены задержки (delays) + + сервисное ПЗУ Profi работает только при включенных задержках TR-DOS + + Bumpy не работает в конфигурации по умолчанию, т.к. пытается читать + кемпстон-джойстик через порт #FFDF - выключайте kempston mouse перед + запуском этой игры [bug found by Paul Pavlov] + + sounddrv=wave не работает на CTSB128PCI (Creative Sound Blaster) + из-за глюка в драйверах этой звуковой карты. [bug found by Nuts] + + некоторые адвентюры, созданные в ABS (ALIEN.SCL, LENIN.SCL) не работают на + TR-DOS 5.04TM, используйте версию 5.03 - самую совместимую [bug found by Paul Pavlov] + + Vixen 1,2,3 - несовместимы с SounDrive [bug found by Paul Pavlov] + Fernandez - несовместимо с SounDrive [bug found by Shadow Maker] + + port #FF иногда не отвечает при SkipFrame!=0 + (то же самое при SkipFrameMaxSpeed!=0 в режиме max speed) + + Кнопка 'MAGIC' на компьютере Scorpion генерирует NMI даже при PC < #4000, + но теневой монитор не рассчитан на это и может сглючить + + CacheVox by AlCo требует включения TR-DOS задержек и [BETA128] IL=1 + + Если в эмуляторе после возвращения фокуса залипает клавиша, следует нажать + Scroll Lock, деактивировать мерцающую клавишу, потом снова нажать + Scroll Lock. + + Если в эмуляторе после возвращения фокуса неадекватно работают управляющие + клавиши, следует нажать Enter, и работа клавиш исправится. + + Если эмулятор виснет при работе с General Sound, попробуйте DSPrimary=0. + + Если эмулятор вываливается при работе с General Sound, попробуйте GSType=Z80. + + Если Kempston Joystick не работает, войдите в меню, нажмите там NumLock, +затем вернитесь из меню. + + Если звук плохой, попробуйте в .ini DSPrimary=1 (эмулятор может при этом +неожиденно закрываться!) или SoundDrv=wave. + + Скриншоты кладутся в каталог эмулятора. + Чтение-запись файла в отладчике - в текущем каталоге (который видно по F3). + +Качайте sys.trd (для дисковода B:) с http://alonecoder.narod.ru/zx/SYS.rar +Качайте xBIOS для ATM Turbo2+ с http://atmturbo.nedopc.com/ +Возьмите WNASPI32.DLL из Ahead Nero или с веб-сайта Ahead + +BASS.DLL v2.3 взят с http://www.un4seen.com/ + +---------------------------------------------------------------------- + +приветы от SMT: + + * dj.Z, IMP и MIC - мне очень нравится ваша музыка ! + * Nik-O и Fatal Snipe - за верность AY сцене + * Hacker KAY - спасибо за самую точную информацию про AY/YM + * Paul Pavlov - собрал самый (сейчас уже не самый) большой TR-DOS архив + * Raul Gomez Sanchez (автор R80) + * Stalker - спасибо за STS + * Placebo, Extreme, Digital Reality, CBS - за качественные демки + * V_Soft (Roman Scherbakov) - за формат Vortex + * Sergey Bulba - за AY_Emul + * Alone Coder - you keep Spectrum alive + * Ian Luck - спасибо за BASS + * SirX - просто привет + * всем фанатам ZX Spectrum - Speccy is da best! + + +особая благодарность людям, заинтересованным в развитии UnrealSpeccy и +присылавшим информацию о железе и софте: + + * Maxim Vasilyev - присылал много багрепортов + * Mac Buster - информация про дисковый интерфейс Beta 128 + * Shiru Otaku - прислал GSPAK.ZIP + * Alexey Kravchenko - прислал скорпионовские тайминги + * Vitaliy Pigo - тестирование и замеры скорости + * Alex Baskakov - помог с форматом TD0 + * Alone Coder - прислал много информации о пентагоновском железе + * Dexus - интегрировал поддержку YM2203 + * Maksagor - информация про ATM + * многие другие, кто присылал письма с благодарностями/предложениями и т.п. + +---------------------------------------------------------------------- + +лицензия + + UnrealSpeccy распространяется совершенно свободно. Вы можете + использовать эмулятор и его исходники без всяких ограничений. + Вы можете использовать исходники UnrealSpeccy в своих эмуляторах. + Вы можете выпускать исправленные/новые версии UnrealSpeccy. + В этом случае вы не обязаны распространять исходный код (в отличие, + например, от требований GNU Public License). + Вы можете портировать эмулятор на любую другую платформу + (но здесь много специфических для MSVC конструкций языка, + поэтому сборка другим компилятором затруднительна) + + + diff --git a/tools/unreal/ft8xxemu.dll b/tools/unreal/ft8xxemu.dll new file mode 100644 index 0000000..58ec625 Binary files /dev/null and b/tools/unreal/ft8xxemu.dll differ diff --git a/tools/unreal/help_eng.html b/tools/unreal/help_eng.html new file mode 100644 index 0000000..7c06db8 --- /dev/null +++ b/tools/unreal/help_eng.html @@ -0,0 +1,537 @@ + + + + UnrealSpeccy: current key bindings + + + + + + +

Emulator keys

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{main.exit} exit emulator
{main.settings} configure emulator
{main.lockmouse} bind mouse in windowed mode to kempston mouse
{main.monitor} enter debugger
{main.selectfilter} select video filter for output
{main.selectdriver} select driver for filters, working through driver
{main.flictoggle} toggle flic/noflic
{main.fullscreen} toggle full-screen on/off (only for GDI, Overlay, or ddraw blitter video modes)
{main.pause} pause
{main.selectfix} select video parameter for tune-up
{main.selectsnd} select sound parameter for tune-up
{main.incfix} increase selected parameter
{main.decfix} decrease selected parameter
{main.incfix10} coarse increase selected parameter
{main.decfix10} coarse decrease selected parameter
{main.leds} toggle on-screen leds
{main.status} toggle status line
{main.maxspeed} toggle max. speed
{main.pokedialog} show poke dialog
{main.starttape} start tape
{main.tapebrowser} show tape browser
{main.screenshot} write screenshot
{main.reset} reset to default mode
{main.reset128} reset to 128K
{main.resetsys} reset to service ROM
{main.reset48} reset to 48K
{main.resetbasic} reset to 48K basic with unlocked high memory
{main.resetdos} reset to TR-DOS
{main.resetcache} reset to cache
{main.nmi} generate NMI
{main.nmidos} select TR-DOS and generate NMI ("MAGIC" button)
{main.nmicache} select cache and generate NMI
{main.help} show this help
{main.save} save disk or memory image
{main.load} load disk or memory image
{main.savesound} start sound recording
{main.qsave1} quick-save to slot 1
{main.qsave2} quick-save to slot 2
{main.qsave3} quick-save to slot 3
{main.qload1} quick-load from slot 1
{main.qload2} quick-load from slot 2
{main.qload3} quick-load from slot 3
{main.keystick} toggle sticky-keys mode
{main.autofire} toggle autofire mode
{main.saveram} save RAM dump
{main.saveall} save CMOS and unsaved diskette images
{main.xtkbd} enable/disable ATM XT keyboard controller (emulation control keys will be disabled in this mode)
{main.pastetext} copy text from clipboard to keyboard buffer
{main.memsearch} search in memory for changed values (cheats, etc)
{main.size1} set normal window size in windowed mode
{main.size2} set double window size in windowed mode
{main.sizem} maximize window in windowed mode
{main.tsutoggle} toggle TSU layers
+ + + + +

Debugger Keys

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{mon.exit} exit to system
{mon.emul} back to emulation
{mon.settigns} configure emulator
{mon.bpdialog} breakpoints manager
{mon.gs} general sound status dialog
{mon.pokedialog} enter pokes
{mon.tapebrowser} tape browser
{mon.reset128} reset to 128K
{main.resetsys} reset to service ROM
{mon.reset48} reset to 48K
{mon.resetbasic} reset to 48K basic with unlocked high memory
{mon.resetdos} reset to TR-DOS
{mon.resetcache} reset to cache
{mon.nmi} generate NMI
{mon.nmidos} select TR-DOS and generate NMI ("MAGIC" button)
{mon.nmicache} select cache and generate NMI
{mon.help} show this help
{mon.save} save disk or memory image
{mon.load} load disk or memory image
{mon.savesound} start sound recording
{mon.qsave1} quick-save to slot 1
{mon.qsave2} quick-save to slot 2
{mon.qsave3} quick-save to slot 3
{mon.qload1} quick-load from slot 1
{mon.qload2} quick-load from slot 2
{mon.qload3} quick-load from slot 3
{mon.screen} show spectrum screen, as it present in video memory
{mon.altscreen} show 128K spectrum screen at bank#7
{mon.rayscreen} show painted screen area until crt-ray (with MC, border & 2-scr effects)
{mon.step} trace single Z80 M1 cycle
{mon.stepover} trace Z80 over loops, calls, etc...
{mon.exitsub} trace until word in (PC) (designed for 'exit routine')
{mon.loadblock} load memory block
{mon.saveblock} save/disasm memory block
{mon.fillblock} fill memory block with pattern
{mon.setbank} write to port #7FFD
{mon.sethimem} write to model-specific extended memory port
{mon.next} next window
{mon.prev} prev. window
{mon.dump} toggle mode of dump window
{mon.rip} activate ripper's tool
{mon.osw} configure on-screen watches
{mon.scrshot} toggle watches/screendump
{mon.scrshot_alt} toggle main\alt screen in screendump window
{mon.setwatch} set user-defined watch address
{mon.switchay} switch active AY (turbo-sound)
{mon.labels} goto label
{mon.memsearch} search in memory for changed values (cheats, etc)
+ +

Debugger Keys - registers window

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{reg.left} left
{reg.right} right
{reg.up} up
{reg.down} down
{reg.edit} edit current register
{reg.a} edit register 'A'
{reg.f} edit flags
{reg.bc} edit BC
{reg.de} edit DE
{reg.hl} edit HL
{reg.pc} edit PC
{reg.sp} edit SP
{reg.ix} edit IX
{reg.iy} edit IY
{reg.i} edit I
{reg.r} edit R
{reg.im} toggle interrupt mode
{reg.iff1} toggle iff1
{reg.iff2} toggle iff2
{reg.SF} toggle flag SF
{reg.ZF} toggle flag ZF
{reg.F5} toggle flag F5
{reg.HF} toggle flag HF
{reg.F3} toggle flag F3
{reg.PF} toggle flag PF
{reg.NF} toggle flag NF
{reg.CF} toggle flag CF
{reg.codejump} goto address in code window under cursor
{reg.datajump} goto address in data window under cursor
+ +

Debugger Keys - disasm window

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{cpu.findpc} set cursor at PC
{cpu.here} trace to cursor
{cpu.findtext} find string
{cpu.findcode} find code with mask
{cpu.goto} push cursor position and goto address
{cpu.bpx} breakpoint under cursor
{cpu.asm} edit instruction
{cpu.setpc} set PC to cursor
{cpu.up} up
{cpu.down} down
{cpu.left} left
{cpu.right} right
{cpu.pgdn} next page
{cpu.pgup} prev. page
{cpu.save1} save cursor position to slot 1
{cpu.save2} save cursor position to slot 2
{cpu.save3} save cursor position to slot 3
{cpu.save4} save cursor position to slot 4
{cpu.save5} save cursor position to slot 5
{cpu.save6} save cursor position to slot 6
{cpu.save7} save cursor position to slot 7
{cpu.save8} save cursor position to slot 8
{cpu.rest1} push cursor position to jumpstack and restore cursor position from slot 1
{cpu.rest2} push cursor position to jumpstack and restore cursor position from slot 2
{cpu.rest3} push cursor position to jumpstack and restore cursor position from slot 3
{cpu.rest4} push cursor position to jumpstack and restore cursor position from slot 4
{cpu.rest5} push cursor position to jumpstack and restore cursor position from slot 5
{cpu.rest6} push cursor position to jumpstack and restore cursor position from slot 6
{cpu.rest7} push cursor position to jumpstack and restore cursor position from slot 7
{cpu.rest8} push cursor position to jumpstack and restore cursor position from slot 8
{cpu.back} pop cursor position from jumpstack
{cpu.context} push cursor position and goto instruction operand
{cpu.datajump} jump to instruction operand in data window
{cpu.labels} show labels on/off
{cpu.importl} menu for importing labels from XAS/ALASM
+ +

Debugger Keys - memory/disk editor window

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{mem.left} left
{mem.right} right
{mem.up} up
{mem.down} down
{mem.pgup} page up
{mem.pgdn} page down
{mem.switch} move between hex/ascii fields
{mem.stline} goto start of line
{mem.endline} goto end of line
{mem.findtext} find text in memory / disk track
{mem.findcode} find code with mask in memory / disk track
{mem.goto} goto address
{mem.mem} select memory editor
{mem.diskphys} select disk editor (physical track)
{mem.disklog} select disk editor (logical sectors)
{mem.diskgo} select drive/track/sector in diskeditor
{mem.pc} goto PC
{mem.sp} goto SP
{mem.bc} goto BC
{mem.de} goto DE
{mem.hl} goto HL
{mem.ix} goto IX
{mem.iy} goto IY
+ + diff --git a/tools/unreal/rom/gs104.rom b/tools/unreal/rom/gs104.rom new file mode 100644 index 0000000..d042846 Binary files /dev/null and b/tools/unreal/rom/gs104.rom differ diff --git a/tools/unreal/rom/gs105a.rom b/tools/unreal/rom/gs105a.rom new file mode 100644 index 0000000..6135f46 Binary files /dev/null and b/tools/unreal/rom/gs105a.rom differ diff --git a/tools/unreal/rom/zxevo.rom b/tools/unreal/rom/zxevo.rom new file mode 100644 index 0000000..673fd40 Binary files /dev/null and b/tools/unreal/rom/zxevo.rom differ diff --git a/tools/unreal/sos.l b/tools/unreal/sos.l new file mode 100644 index 0000000..595c3f8 --- /dev/null +++ b/tools/unreal/sos.l @@ -0,0 +1,1117 @@ +0000 START +0008 ERROR-1 +0010 PRINT-A-1 +0018 GET-CHAR +001C TEST-CHAR +0020 NEXT-CHAR +0028 FP-CALC +0030 BC-SPACES +0038 MASK-INT +0048 KEY-INT +0053 ERROR-2 +0055 ERROR-3 +0066 RESET +0070 NO-RESET +0074 CH-ADD+1 +0077 TEMP-PTR1 +0078 TEMP-PTR2 +007D SKIP-OVER +0090 SKIPS +0095 TKN-TABLE +0205 MAIN-KEYS +022C E-UNSHIFT +0246 EXT-SHIFT +0260 CTL-CODES +026A SYM-CODES +0284 E-DIGITS +028E KEY-SCAN +0296 KEY-LINE +029F KEY-3KEYS +02A1 KEY-BITS +02AB KEY-DONE +02BF KEYBOARD +02C6 K-ST-LOOP +02D1 K-CH-SET +02F1 K-NEW +0308 K-END +0310 K-REPEAT +031E K-TEST +032C K-MAIN +0333 K-DECODE +033E addit +0341 K-E-LET +034A K-LOOK-UP +034F K-KLC-LET +0364 K-TOKENS +0367 K-DIGIT +0382 K-8-&-9 +0389 K-GRA-DGT +039D K-KLC-DGT +03B2 K-@-CHAR +03B5 BEEPER +03D1 BE-IX+3 +03D2 BE-IX+2 +03D3 BE-IX+1 +03D4 BE-IX+0 +03D6 BE-H&L-LP +03F2 BE-AGAIN +03F6 BE-END +03F8 beep +0425 BE-i-OK +0427 BE-OCTAVE +046C REPORT-B +046E semi-tone +04AA zx81-name +04C2 SA-BYTES +04D0 SA-FLAG +04D8 SA-LEADER +04EA SA-SYNC-1 +04F2 SA-SYNC-2 +04FE SA-LOOP +0505 SA-LOOP-P +0507 SA-START +050E SA-PARITY +0511 SA-BIT-2 +0514 SA-BIT-1 +051A SA-SET +051C SA-OUT +0525 SA-8-BITS +053C SA-DELAY +053F SA/LD-RET +0552 REPORT-Da +0554 SA/LD-END +0556 LD-BYTES +056B LD-BREAK +056C LD-START +0574 LD-WAIT +0580 LD-LEADER +058F LD-SYNC +05A9 LD-LOOP +05B3 LD-FLAG +05BD LD-VERIFY +05C2 LD-NEXT +05C4 LD-DEC +05C8 LD-MARKER +05CA LD-8-BITS +05E3 LD-EDGE-2 +05E7 LD-EDGE-1 +05E9 LD-DELAY +05ED LD-SAMPLE +0605 SAVE-ETC +0621 SA-SPACE +0629 SA-BLANK +0642 REPORT-Fa +0644 SA-NULL +064B SA-NAME +0652 SA-DATA +0670 REPORT-2a +0672 SA-V-OLD +0685 SA-V-NEW +068F SA-V-TYPE +0692 SA-DATA-1 +06A0 SA-SCR$ +06C3 SA-CODE +06E1 SA-CODE-1 +06F0 SA-CODE-2 +06F5 SA-CODE-3 +06F9 SA-CODE-4 +0710 SA-TYPE-3 +0716 SA-LINE +0723 SA-LINE-1 +073A SA-TYPE-0 +075A SA-ALL +0767 LD-LOOK-H +078A LD-TYPE +07A6 LD-NAME +07AD LD-CH-PR +07CB VR-CONTROL +07E9 VR-CONT-1 +07F4 VR-CONT-2 +0800 VR-CONT-3 +0802 LD-BLOCK +0806 REPORT-R +0808 LD-CONTRL +0819 LD-CONT-1 +0825 LD-CONT-2 +082E LD-DATA +084C LD-DATA-1 +0873 LD-PROG +08AD LD-PROG-1 +08B6 ME-CONTRL +08D2 ME-NEW-LP +08D7 ME-OLD-LP +08DF ME-OLD-L1 +08EB ME-NEW-L2 +08F0 ME-VAR-LP +08F9 ME-OLD-VP +0901 ME-OLD-V1 +0909 ME-OLD-V2 +0912 ME-OLD-V3 +091E ME-OLD-V4 +0921 ME-VAR-L1 +0923 ME-VAR-L2 +092C ME-ENTER +093E ME-ENT-1 +0955 ME-ENT-2 +0958 ME-ENT-3 +0970 SA-CONTRL +0991 SA-1-SEC +09A1 tape-msgs +09F4 PRINT-OUT +0A11 ctlchrtab +0A23 PO-BACK-1 +0A38 PO-BACK-2 +0A3A PO-BACK-3 +0A3D PO-RIGHT +0A4F PO-ENTER +0A5F PO-COMMA +0A69 PO-QUEST +0A6D PO-TV-2 +0A75 PO-2-OPER +0A7A PO-1-OPER +0A7D PO-TV-1 +0A80 PO-CHANGE +0A87 PO-CONT +0AAC PO-AT-ERR +0ABF PO-AT-SET +0AC2 PO-TAB +0AC3 PO-FILL +0AD0 PO-SPACE +0AD9 PO-ABLE +0ADC PO-STORE +0AF0 PO-ST-E +0AFC PO-ST-PR +0B03 PO-FETCH +0B1D PO-F-PR +0B24 PO-ANY +0B38 PO-GR-1 +0B3E PO-GR-2 +0B4C PO-GR-3 +0B52 PO-T&UDG +0B5F PO-T +0B65 PO-CHAR +0B6A PO-CHAR-2 +0B76 PO-CHAR-3 +0B7F PR-ALL +0B93 PR-ALL-1 +0BA4 PR-ALL-2 +0BB6 PR-ALL-3 +0BB7 PR-ALL-4 +0BC1 PR-ALL-5 +0BD3 PR-ALL-6 +0BDB PO-ATTR +0BFA PO-ATTR-1 +0C08 PO-ATTR-2 +0C0A PO-MSG +0C10 PO-TOKENS +0C14 PO-TABLE +0C22 PO-EACH +0C35 PO-TR-SP +0C3B PO-SAVE +0C41 PO-SEARCH +0C44 PO-STEP +0C55 PO-SCR +0C86 REPORT-5 +0C88 PO-SCR-2 +0CD2 PO-SCR-3 +0CF0 PO-SCR-3A +0CF8 scrl-mssg +0D00 REPORT-D +0D02 PO-SCR-4 +0D1C PO-SCR-4A +0D2D PO-SCR-4B +0D4D TEMPS +0D5B TEMPS-1 +0D65 TEMPS-2 +0D6B CLS +0D6E CLS-LOWER +0D87 CLS-1 +0D89 CLS-2 +0D8E CLS-3 +0D94 CL-CHAN +0DA0 CL-CHAN-A +0DAF CL-ALL +0DD9 CL-SET +0DEE CL-SET-1 +0DF4 CL-SET-2 +0DFE CL-SC-ALL +0E00 CL-SCROLL +0E05 CL-SCR-1 +0E0D CL-SCR-2 +0E19 CL-SCR-3 +0E44 CL-LINE +0E4A CL-LINE-1 +0E4D CL-LINE-2 +0E80 CL-LINE-3 +0E88 CL-ATTR +0E9B CL-ADDR +0EAC COPY +0EB2 COPY-1 +0EC9 COPY-2 +0ECD COPY-BUFF +0ED3 COPY-3 +0EDA COPY-END +0EDF CLEAR-PRB +0EE7 PRB-BYTES +0EF4 COPY-LINE +0EFD COPY-L-1 +0F0A REPORT-Dc +0F0C COPY-L-2 +0F14 COPY-L-3 +0F18 COPY-L-4 +0F1E COPY-L-5 +0F2C EDITOR +0F30 ED-AGAIN +0F38 ED-LOOP +0F6C ED-CONTR +0F81 ADD-CHAR +0F8B ADD-CH-1 +0F92 ED-KEYS +0FA0 ed-keys-t +0FA9 ED-EDIT +0FF3 ED-DOWN +1001 ED-STOP +1007 ED-LEFT +100C ED-RIGHT +1011 ED-CUR +1015 ED-DELETE +101E ED-IGNORE +1024 ED-ENTER +1026 ED-END +1031 ED-EDGE +103E ED-EDGE-1 +1051 ED-EDGE-2 +1059 ED-UP +106E ED-LIST +1076 ED-SYMBOL +107C ED-GRAPH +107F ED-ERROR +1097 CLEAR-SP +10A8 KEY-INPUT +10DB KEY-M-CL +10E6 KEY-MODE +10F4 KEY-FLAG +10FA KEY-CONTR +1105 KEY-DATA +110D KEY-NEXT +1113 KEY-CHAN +111B KEY-DONE +111D ED-COPY +1150 ED-BLANK +115E ED-SPACES +1167 ED-FULL +117C ED-C-DONE +117E ED-C-END +1190 SET-HL +1195 SET-DE +11A7 REMOVE-FP +11B7 NEW +11CB START-NEW +11DA ram-check +11DC RAM-FILL +11E2 RAM-READ +11EF RAM-DONE +1219 RAM-SET +12A2 MAIN-EXEC +12A9 MAIN-1 +12AC MAIN-2 +12CF MAIN-3 +1303 MAIN-4 +1313 MAIN-G +133C MAIN-5 +1373 MAIN-6 +1376 MAIN-7 +1384 MAIN-8 +1386 MAIN-9 +1391 rpt-mesgs +1555 REPORT-G +155D MAIN-ADD +157D MAIN-ADD1 +15AB MAIN-ADD2 +15AF init-chan +15C4 REPORT-J +15C6 init-strm +15D4 WAIT-KEY +15DE WAIT-KEY1 +15E4 REPORT-8 +15E6 INPUT-AD +15EF OUT-CODE +15F2 PRINT-A-2 +15F7 CALL-SUB +1601 CHAN-OPEN +160E REPORT-Oa +1610 CHAN-OP-1 +1615 CHAN-FLAG +162C CALL-JUMP +162D chn-cd-lu +1634 CHAN-K +1642 CHAN-S +1646 CHAN-S-1 +164D CHAN-P +1652 ONE-SPACE +1655 MAKE-ROOM +1664 POINTERS +166B PTR-NEXT +167F PTR-DONE +168F LINE-ZERO +1691 LINE-NO-A +1695 LINE-NO +169E RESERVE +16B0 SET-MIN +16BF SET-WORK +16C5 SET-STK +16D4 REC-EDIT +16DB INDEXER-1 +16DC INDEXER +16E5 CLOSE +16FC CLOSE-1 +1701 CLOSE-2 +1716 cl-str-lu +171C CLOSE-STR +171E STR-DATA +1725 REPORT-Ob +1727 STR-DATA1 +1736 OPEN +1756 OPEN-1 +175D OPEN-2 +1765 REPORT-F +1767 OPEN-3 +177A op-str-lu +1781 OPEN-K +1785 OPEN-S +1789 OPEN-P +178B OPEN-END +1793 CAT-ETC +1795 AUTO-LIST +17CE AUTO-L-1 +17E1 AUTO-L-2 +17E4 AUTO-L-3 +17ED AUTO-L-4 +17F5 LLIST +17F9 LIST +17FB LIST-1 +1814 LIST-2 +181A LIST-3 +181F LIST-4 +1822 LIST-5 +1833 LIST-ALL +1835 LIST-ALL1 +1855 OUT-LINE +1865 OUT-LINE1 +187D OUT-LINE2 +1881 OUT-LINE3 +1894 OUT-LINE4 +18A1 OUT-LINE5 +18B4 OUT-LINE6 +18B6 NUMBER +18C1 OUT-FLASH +18E1 OUT-CURS +18F3 OUT-C-1 +1909 OUT-C-2 +190F LN-FETCH +191C LN-STORE +1925 OUT-SP-2 +192A OUT-SP-NO +192B OUT-SP-1 +1937 OUT-CHAR +195A OUT-CH-1 +1968 OUT-CH-2 +196C OUT-CH-3 +196E LINE-ADDR +1974 LINE-AD-1 +1980 CP-LINES +1988 not-used +198B EACH-STMT +1990 EACH-S-1 +1998 EACH-S-2 +199A EACH-S-3 +19A5 EACH-S-4 +19AD EACH-S-5 +19B1 EACH-S-6 +19B8 NEXT-ONE +19C7 NEXT-O-1 +19CE NEXT-O-2 +19D5 NEXT-O-3 +19D6 NEXT-O-4 +19DB NEXT-O-5 +19DD DIFFER +19E5 RECLAIM-1 +19E8 RECLAIM-2 +19FB E-LINE-NO +1A15 E-L-1 +1A1B OUT-NUM-1 +1A28 OUT-NUM-2 +1A30 OUT-NUM-3 +1A42 OUT-NUM-4 +1A48 offst-tbl +1A7A P-LET +1A7D P-GO-TO +1A81 P-IF +1A86 P-GO-SUB +1A8A P-STOP +1A8D P-RETURN +1A90 P-FOR +1A98 P-NEXT +1A9C P-PRINT +1A9F P-INPUT +1AA2 P-DIM +1AA5 P-REM +1AA8 P-NEW +1AAB P-RUN +1AAE P-LIST +1AB1 P-POKE +1AB5 P-RANDOM +1AB8 P-CONT +1ABB P-CLEAR +1ABE P-CLS +1AC1 P-PLOT +1AC5 P-PAUSE +1AC9 P-READ +1ACC P-DATA +1ACF P-RESTORE +1AD2 P-DRAW +1AD6 P-COPY +1AD9 P-LPRINT +1ADC P-LLIST +1ADF P-SAVE +1AE0 P-LOAD +1AE1 P-VERIFY +1AE2 P-MERGE +1AE3 P-BEEP +1AE7 P-CIRCLE +1AEB P-INK +1AEC P-PAPER +1AED P-FLASH +1AEE P-BRIGHT +1AEF P-INVERSE +1AF0 P-OVER +1AF1 P-OUT +1AF5 P-BORDER +1AF9 P-DEF-FN +1AFC P-OPEN +1B02 P-CLOSE +1B06 P-FORMAT +1B0A P-MOVE +1B10 P-ERASE +1B14 P-CAT +1B17 LINE-SCAN +1B28 STMT-LOOP +1B29 STMT-L-1 +1B52 SCAN-LOOP +1B55 GET-PARAM +1B6F SEPARATOR +1B76 STMT-RET +1B7B REPORT-L +1B7D STMT-R-1 +1B8A LINE-RUN +1B9E LINE-NEW +1BB0 REPORT-0 +1BB2 REM +1BB3 LINE-END +1BBF LINE-USE +1BD1 NEXT-LINE +1BEC REPORT-N +1BEE CHECK-END +1BF4 STMT-NEXT +1C01 class-tbl +1C0D CLASS-03 +1C10 CLASS-00 +1C11 CLASS-05 +1C1F CLASS-01 +1C22 VAR-A-1 +1C2E REPORT-2 +1C30 VAR-A-2 +1C46 VAR-A-3 +1C4E CLASS-02 +1C56 VAL-FET-1 +1C59 VAL-FET-2 +1C6C CLASS-04 +1C79 NEXT-2NUM +1C7A EXPT-2NUM +1C82 EXPT-1NUM +1C8A REPORT-C +1C8C EXPT-EXP +1C96 CLASS-07 +1CBE CLASS-09 +1CD6 CL-09-1 +1CDB CLASS-0B +1CDE FETCH-NUM +1CE6 USE-ZERO +1CEE REPORT-9 +1CF0 IF +1D00 IF-1 +1D03 FOR +1D10 F-USE-1 +1D16 F-REORDER +1D34 F-L-S +1D64 F-LOOP +1D7C F-FOUND +1D84 REPORT-I +1D86 LOOK-PROG +1D8B LOOK-P-1 +1DA3 LOOK-P-2 +1DAB NEXT +1DD8 REPORT-1 +1DDA NEXT-LOOP +1DE2 NEXT-1 +1DE9 NEXT-2 +1DEC READ-3 +1DED READ +1E08 REPORT-E +1E0A READ-1 +1E1E READ-2 +1E27 DATA +1E2C DATA-1 +1E37 DATA-2 +1E39 PASS-BY +1E42 RESTORE +1E45 REST-RUN +1E4F RANDOMIZE +1E5A RAND-1 +1E5F CONTINUE +1E67 GO-TO +1E73 GO-TO-2 +1E7A OUT +1E80 POKE +1E85 TWO-PARAM +1E8E TWO-P-1 +1E94 FIND-INT1 +1E99 FIND-INT2 +1E9C FIND-I-1 +1E9F REPORT-Bb +1EA1 RUN +1EAC CLEAR +1EAF CLEAR-RUN +1EB7 CLEAR-1 +1EDA REPORT-M +1EDC CLEAR-2 +1EED GO-SUB +1F05 TEST-ROOM +1F15 REPORT-4 +1F1A free-mem +1F23 RETURN +1F36 REPORT-7 +1F3A PAUSE +1F3D PAUSE-1 +1F49 PAUSE-2 +1F4F PAUSE-END +1F54 BREAK-KEY +1F60 DEF-FN +1F6A DEF-FN-1 +1F7D DEF-FN-2 +1F86 DEF-FN-3 +1F89 DEF-FN-4 +1F94 DEF-FN-5 +1FA6 DEF-FN-6 +1FBD DEF-FN-7 +1FC3 UNSTACK-Z +1FC9 LPRINT +1FCD PRINT +1FCF PRINT-1 +1FDF PRINT-2 +1FE5 PRINT-3 +1FF2 PRINT-4 +1FF5 PRINT-CR +1FFC PR-ITEM-1 +200E PR-ITEM-2 +201E PR-AT-TAB +2024 PR-ITEM-3 +203C PR-STRING +2045 PR-END-Z +2048 PR-ST-END +204E PR-POSN-1 +2061 PR-POSN-2 +2067 PR-POSN-3 +206E PR-POSN-4 +2070 STR-ALTER +2089 INPUT +2096 INPUT-1 +20AD INPUT-2 +20C1 IN-ITEM-1 +20D8 IN-ITEM-2 +20ED IN-ITEM-3 +20FA IN-PROMPT +211A IN-PR-1 +211C IN-PR-2 +2129 IN-PR-3 +213A IN-VAR-1 +2148 IN-VAR-2 +215E IN-VAR-3 +2161 IN-VAR-4 +2174 IN-VAR-5 +219B IN-VAR-6 +21AF IN-NEXT-1 +21B2 IN-NEXT-2 +21B9 IN-ASSIGN +21CE REPORT-Cb +21D0 IN-STOP +21D4 REPORT-H +21D6 IN-CHAN-K +21E1 CO-TEMP-1 +21E2 CO-TEMP-2 +21F2 CO-TEMP-3 +21FC CO-TEMP-4 +2211 CO-TEMP-5 +2228 CO-TEMP-6 +2234 CO-TEMP-7 +223E CO-TEMP-8 +2244 REPORT-K +2246 CO-TEMP-9 +2257 CO-TEMP-A +2258 CO-TEMP-B +226C CO-CHANGE +2273 CO-TEMP-C +227D CO-TEMP-D +2287 CO-TEMP-E +2294 BORDER +22A6 BORDER-1 +22AA PIXEL-ADD +22CB POINT-SUB +22D4 POINT-LP +22DC PLOT +22E5 PLOT-SUB +22F0 PLOT-LOOP +22FD PL-TST-IN +2303 PLOT-END +2307 STK-TO-BC +2314 STK-TO-A +2320 CIRCLE +233B C-R-GRE-1 +235A C-ARC-GE1 +2382 DRAW +238D DR-3-PRMS +23A3 DR-SIN-NZ +23C1 DR-PRMS +2420 DRW-STEPS +2425 ARC-LOOP +2439 ARC-START +245F ARC-END +2477 LINE-DRAW +247D CD-PRMS1 +2495 USE-252 +2497 DRAW-SAVE +24B7 DRAW-LINE +24C4 DL-X-GE-Y +24CB DL-LARGER +24CE D-L-LOOP +24D4 D-L-DIAG +24DB D-L-HR-VT +24DF D-L-STEP +24EC D-L-PLOT +24F7 D-L-RANGE +24F9 REPORT-Bc +24FB SCANNING +24FF S-LOOP-1 +250F S-QUOTE-S +2522 S-2-COORD +252D S-RPORT-C +2530 SYNTAX-Z +2535 S-SCRN$-S +254F S-SCRN-LP +255A S-SC-MTCH +255D S-SC-ROWS +2573 S-SCR-NXT +257D S-SCR-STO +2580 S-ATTR-S +2596 scan-func +25AF S-U-PLUS +25B3 S-QUOTE +25BE S-Q-AGAIN +25CB S-Q-COPY +25D9 S-Q-PRMS +25DB S-STRING +25E8 S-BRACKET +25F5 S-FN +25F8 S-RND +2625 S-RND-END +2627 S-PI +2630 S-PI-END +2634 S-INKEY$ +2660 S-IK$-STK +2665 S-INK$-EN +2668 S-SCREEN$ +2672 S-ATTR +267B S-POINT +2684 S-ALPHNUM +268D S-DECIMAL +26B5 S-STK-DEC +26B6 S-SD-SKIP +26C3 S-NUMERIC +26C9 S-LETTER +26DD S-CONT-1 +26DF S-NEGATE +2707 S-NO-TO-$ +270D S-PUSH-PO +2712 S-CONT-2 +2713 S-CONT-3 +2723 S-OPERTR +2734 S-LOOP +274C S-STK-LST +275B S-SYNTEST +2761 S-RPORT-C +2764 S-RUNTEST +2770 S-LOOPEND +2773 S-TIGHTER +2788 S-NOT-AND +2790 S-NEXT +2795 tbl-of-ops +27B0 tbl-priors +27BD S-FN-SBRN +27D0 SF-BRKT-1 +27D9 SF-ARGMTS +27E4 SF-BRKT-2 +27E6 SF-RPRT-C +27E9 SF-FLAG-6 +27F4 SF-SYN-EN +27F7 SF-RUN +2802 SF-ARGMT1 +2808 SF-FND-DF +2812 REPORT-P +2814 SF-CP-DEF +2825 SF-NOT-FD +2831 SF-VALUES +2843 SF-ARG-LP +2852 SF-ARG-VL +2885 SF-R-BR-2 +288B REPORT-Q +288D SF-VALUE +28AB FN-SKPOVR +28B2 LOOK-VARS +28D4 V-CHAR +28DE V-STR-VAR +28E3 V-TEST-FN +28EF V-RUN/SYN +28FD V-RUN +2900 V-EACH +2912 V-MATCHES +2913 V-SPACES +2929 V-GET-PTR +292A V-NEXT +2932 V-80-BYTE +2934 V-SYNTAX +293E V-FOUND-1 +293F V-FOUND-2 +2943 V-PASS +294B V-END +2951 STK-F-ARG +295A SFA-LOOP +296B SFA-CP-VR +2981 SFA-MATCH +2991 SFA-END +2996 STK-VAR +29A1 SV-SIMPLE$ +29AE SV-ARRAYS +29C0 SV-PTR +29C3 SV-COMMA +29D8 SV-CLOSE +29E0 SV-CH-ADD +29E7 SV-COUNT +29EA SV-LOOP +29FB SV-MULT +2A12 SV-RPT-C +2A20 REPORT-3 +2A22 SV-NUMBER +2A2C SV-ELEM$ +2A45 SV-SLICE +2A48 SV-DIM +2A49 SV-SLICE? +2A52 SLICING +2A7A SL-RPT-C +2A81 SL-SECOND +2A94 SL-DEFINE +2AA8 SL-OVER +2AAD SL-STORE +2AB1 STK-ST-0 +2AB2 STK-STO-$ +2AB6 STK-STORE +2ACC INT-EXP1 +2ACD INT-EXP2 +2AE8 I-CARRY +2AEB I-RESTORE +2AEE DE,(DE+1) +2AF4 GET-HL*DE +2AFF LET +2B0B L-EACH-CH +2B0C L-NO-SP +2B1F L-TEST-CH +2B29 L-SPACES +2B3E L-CHAR +2B4F L-SINGLE +2B59 L-NUMERIC +2B66 L-EXISTS +2B72 L-DELETE$ +2B9B L-LENGTH +2BA3 L-IN-W/S +2BA6 L-ENTER +2BAF L-ADD$ +2BC0 L-NEW$ +2BC6 L-STRING +2BEA L-FIRST +2BF1 STK-FETCH +2C02 DIM +2C05 D-RPORT-C +2C15 D-RUN +2C1F D-LETTER +2C2D D-SIZE +2C2E D-NO-LOOP +2C7C DIM-CLEAR +2C7F DIM-SIZES +2C88 ALPHANUM +2C8D ALPHA +2C9B DEC-TO-FP +2CA2 BIN-DIGIT +2CB3 BIN-END +2CB8 NOT-BIN +2CCB DECIMAL +2CCF DEC-RPT-C +2CD5 DEC-STO-1 +2CDA NXT-DGT-1 +2CEB E-FORMAT +2CF2 SIGN-FLAG +2CFE SIGN-DONE +2CFF ST-E-PART +2D18 E-FP-JUMP +2D1B NUMERIC +2D22 STK-DIGIT +2D28 STACK-A +2D2B STACK-BC +2D3B INT-TO-FP +2D40 NXT-DGT-2 +2D4F E-TO-FP +2D55 E-SAVE +2D60 E-LOOP +2D6D E-DIVSN +2D6E E-FETCH +2D71 E-TST-END +2D7B E-END +2D7F INT-FETCH +2D8C p-int-sto +2D8E INT-STORE +2DA2 FP-TO-BC +2DAD FP-DELETE +2DC1 LOG(2^A) +2DD5 FP-TO-A +2DE1 FP-A-END +2DE3 PRINT-FP +2DF2 PF-NEGTVE +2DF8 PF-POSTVE +2E01 PF-LOOP +2E1E PF-SAVE +2E24 PF-SMALL +2E56 PF-LARGE +2E6F PF-MEDIUM +2E7B PF-BITS +2E8A PF-BYTES +2EA1 PF-DIGITS +2EA9 PF-INSERT +2EB3 PF-TEST-2 +2EB8 PF-ALL-9 +2ECB PF-MORE +2ECF PF-FRACTN +2EDF PF-FRN-LP +2EEC PF-FR-DGT +2EEF PF-FR-EXX +2F0C PF-ROUND +2F18 PF-RND-LP +2F25 PF-R-BACK +2F2D PF-COUNT +2F46 PF-NOT-E +2F4A PF-E-SBRN +2F52 PF-OUT-LP +2F59 PF-OUT-DT +2F5E PF-DC-OUT +2F64 PF-DEC-0$ +2F6C PF-E-FRMT +2F83 PF-E-POS +2F85 PF-E-SIGN +2F8B CA-10*A+C +2F9B PREP-ADD +2FAF NEG-BYTE +2FBA FETCH-TWO +2FDD SHIFT-FP +2FE5 ONE-SHIFT +2FF9 ADDEND-0 +2FFB ZEROS-4/5 +3004 ADD-BACK +300D ALL-ADDED +300F subtract +3014 addition +303C ADDN-OFLW +303E FULL-ADDN +3055 SHIFT-LEN +307C TEST-NEG +309F ADD-REP-6 +30A3 END-COMPL +30A5 GO-NC-MLT +30A9 HL-HL*DE +30B1 HL-LOOP +30BC HL-AGAIN +30BE HL-END +30C0 PREP-M/D +30CA multiply +30EA MULT-RSLT +30EF MULT-OFLW +30F0 MULT-LONG +3114 MLT-LOOP +311B NO-ADD +3125 STRT-MLT +313B MAKE-EXPT +313D DIVN-EXPT +3146 OFLW1-CLR +3151 OFLW2-CLR +3155 TEST-NORM +3159 NEAR-ZERO +315D ZERO-RSLT +315E SKIP-ZERO +316C NORMALISE +316E SHIFT-ONE +3186 NORML-NOW +3195 OFLOW-CLR +31AD REPORT-6 +31AF division +31D2 DIV-LOOP +31DB div-34th +31E2 DIV-START +31F2 SUBN-ONLY +31F9 NO-RSTORE +31FA COUNT-ONE +3214 truncate +3221 T-GR-ZERO +3233 T-FIRST +323F T-SMALL +3252 T-NUMERIC +325E T-TEST +3261 T-SHIFT +3267 T-STORE +326C T-EXPNENT +326D X-LARGE +3272 NIL-BYTES +327E BYTE-ZERO +3283 BITS-ZERO +328A LESS-MASK +3290 IX-END +3293 RE-ST-TWO +3296 RESTK-SUB +3297 re-stack +32B1 RS-NRMLSE +32B2 RSTK-LOOP +32BD RS-STORE +32C5 (stk-zero) +32C8 (stk-one) +32CC (stk-half) +32CE (stk-pi/2) +32D3 (stk-ten) +32D7 tbl-addrs +335B CALCULATE +335E GEN-ENT-1 +3362 GEN-ENT-2 +3365 RE-ENTRY +336C SCAN-ENT +3380 FIRST-3D +338C DOUBLE-A +338E ENT-TABLE +33A1 delete +33A2 fp-calc-2 +33A9 TEST-5-SP +33B4 STACK-NUM +33C0 MOVE-FP +33C6 stk-data +33C8 STK-CONST +33DE FORM-EXP +33F1 STK-ZEROS +33F7 SKIP-CONS +33F8 SKIP-NEXT +3406 LOC-MEM +340F get-mem-xx +341B stk-const-xx +342D st-mem-xx +343C exchange +343E SWAP-BYTE +3449 series-xx +3453 G-LOOP +346A abs +346E negate +3474 NEG-TEST +3483 INT-CASE +3492 sgn +34A5 in +34AC peek +34B0 IN-PK-STK +34B3 usr-no +34BC usr-$ +34D3 USR-RANGE +34E4 USR-STACK +34E7 REPORT-A +34E9 TEST-ZERO +34F9 greater-0 +3501 not +3506 less-0 +3507 SIGN-TO-C +350B FP-0/1 +351B or +3524 no-&-no +352D str-&-no +353B no-l-eql +3543 EX-OR-NOT +354E NU-OR-STR +3559 STRINGS +3564 BYTE-COMP +356B SECND-LOW +3572 BOTH-NULL +3575 SEC-PLUS +3585 FRST-LESS +3588 STR-TEST +358C END-TESTS +359C strs-add +35B7 OTHER-STR +35BF STK-PNTRS +35C9 chrs +35DC REPORT-Bd +35DE val +360C V-RPORT-C +361F str$ +3645 read-in +365F R-I-STORE +3669 code +3671 STK-CODE +3674 len +367A dec-jr-nz +3686 JUMP +3687 JUMP-2 +368F jump-true +369B end-calc +36A0 n-mod-m +36AF int +36B7 X-NEG +36C2 EXIT +36C4 exp +3703 REPORT-6b +3705 N-NEGTV +370C RESULT-OK +370E RSLT-ZERO +3713 ln +371A REPORT-Ab +371C VALID +373D GRE.8 +3783 get-argt +37A1 ZPLUS +37A8 YNEG +37AA cos +37B5 sin +37B7 C-ENT +37DA tan +37E2 atn +37F8 SMALL +37FA CASES +3833 asn +3843 acs +384A sqr +3851 to-power +385D XISO +386A ONE +386C LAST +386E spare +3D00 char_set +3D13 TRDOS-FN +3D2F DOS-GATE +3D30 DOS-GATE