Redo main cycle

This commit is contained in:
Роман Бойков 2026-03-22 14:27:14 +03:00
parent c1616dfbd6
commit a74841b048
11 changed files with 434 additions and 139 deletions

View File

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"image/color" "image/color"
"okemu/config"
"okemu/okean240" "okemu/okean240"
"okemu/okean240/fdc" "okemu/okean240/fdc"
@ -17,7 +18,7 @@ import (
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
) )
func mainWindow(computer *okean240.ComputerType) (*fyne.Window, *canvas.Raster, *widget.Label) { func mainWindow(computer *okean240.ComputerType, config *config.OkEmuConfig) (*fyne.Window, *canvas.Raster, *widget.Label) {
emulatorApp := app.New() emulatorApp := app.New()
w := emulatorApp.NewWindow("Океан 240.2") w := emulatorApp.NewWindow("Океан 240.2")
w.Canvas().SetOnTypedKey( w.Canvas().SetOnTypedKey(
@ -52,7 +53,7 @@ func mainWindow(computer *okean240.ComputerType) (*fyne.Window, *canvas.Raster,
w.Resize(fyne.NewSize(600, 600)) w.Resize(fyne.NewSize(600, 600))
vBox := container.NewVBox( vBox := container.NewVBox(
newToolbar(computer, w), newToolbar(computer, w, emulatorApp, config),
centerRaster, centerRaster,
label, label,
) )
@ -62,45 +63,75 @@ func mainWindow(computer *okean240.ComputerType) (*fyne.Window, *canvas.Raster,
return &w, raster, label return &w, raster, label
} }
func newToolbar(c *okean240.ComputerType, w fyne.Window) *fyne.Container { func newToolbar(c *okean240.ComputerType, w fyne.Window, a fyne.App, config *config.OkEmuConfig) *fyne.Container {
hBox := container.NewHBox() hBox := container.NewHBox()
for d := 0; d < fdc.TotalDrives; d++ { for d := 0; d < fdc.TotalDrives; d++ {
hBox.Add(widget.NewLabel(string(rune(66+d)) + ":")) hBox.Add(widget.NewLabel(string(rune(66+d)) + ":"))
hBox.Add(widget.NewToolbar( hBox.Add(widget.NewToolbar(
widget.NewToolbarAction(theme.DocumentSaveIcon(), func() { widget.NewToolbarAction(theme.DocumentSaveIcon(), func() {
err := c.SaveFloppy(fdc.FloppyB) err := c.SaveFloppy(byte(d))
if err != nil { if err != nil {
dialog.ShowError(err, w) dialog.ShowError(err, w)
} }
}), }),
//widget.NewToolbarSpacer(), //widget.NewToolbarSpacer(),
widget.NewToolbarAction(theme.FolderOpenIcon(), func() { widget.NewToolbarAction(theme.FolderOpenIcon(), func() {
err := c.SaveFloppy(fdc.FloppyC) err := c.LoadFloppy(byte(d))
if err != nil { if err != nil {
dialog.ShowError(err, w) dialog.ShowError(err, w)
} }
}), }),
)) ))
}
hBox.Add(widget.NewSeparator()) hBox.Add(widget.NewSeparator())
hBox.Add(widget.NewButtonWithIcon("Ctrl+C", theme.LogoutIcon(), func() { }
hBox.Add(widget.NewButtonWithIcon("1", theme.DownloadIcon(), func() {
c.SetRamBytes(ramBytes1)
}))
hBox.Add(widget.NewSeparator())
hBox.Add(widget.NewButtonWithIcon("2", theme.DownloadIcon(), func() {
c.SetRamBytes(ramBytes2)
}))
hBox.Add(widget.NewSeparator())
hBox.Add(widget.NewButtonWithIcon("^C", theme.MediaStopIcon(), func() {
c.PutCtrlKey(0x03) c.PutCtrlKey(0x03)
})) }))
hBox.Add(widget.NewSeparator()) hBox.Add(widget.NewSeparator())
bNorm := widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() { cbFreq := widget.NewCheck("Fmax", func(checked bool) {
fullSpeed.Store(false) fullSpeed.Store(checked)
if checked {
c.SetCPUFrequency(25_000_000)
} else {
c.SetCPUFrequency(2_500_000) c.SetCPUFrequency(2_500_000)
//bNorm.Disable() }
})
//bNorm := widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() {
// fullSpeed.Store(false)
// c.SetCPUFrequency(2_500_000)
// //bNorm.Disable()
//
//})
//bFast := widget.NewButtonWithIcon("", theme.MediaFastForwardIcon(), func() {
// fullSpeed.Store(true)
// c.SetCPUFrequency(50_000_000)
// bNorm.Enable()
// //bFast.Disable()
//})
}) hBox.Add(cbFreq)
bFast := widget.NewButtonWithIcon("", theme.MediaFastForwardIcon(), func() { //hBox.Add(bFast)
fullSpeed.Store(true) hBox.Add(widget.NewSeparator())
c.SetCPUFrequency(50_000_000) hBox.Add(widget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
bNorm.Enable() cfg := config.Clone()
//bFast.Disable() d := dialog.NewCustomConfirm("OK-Emu settings", "Save", "Cancel", settingsDialog(cfg), func(b bool) {
}) if b {
hBox.Add(bNorm) cfg.Save()
hBox.Add(bFast) }
}, w)
d.Resize(fyne.NewSize(450, 360))
//w.SetContent(settings.NewSettings().LoadAppearanceScreen(w))
d.Show()
}))
hBox.Add(layout.NewSpacer()) hBox.Add(layout.NewSpacer())
hBox.Add(widget.NewButtonWithIcon("Reset", theme.MediaReplayIcon(), func() { hBox.Add(widget.NewButtonWithIcon("Reset", theme.MediaReplayIcon(), func() {
needReset = true needReset = true

View File

@ -10,21 +10,21 @@ import (
const defaultMonitorFile = "rom/MON_v5.bin" const defaultMonitorFile = "rom/MON_v5.bin"
const defaultCPMFile = "rom/CPM_v5.bin" const defaultCPMFile = "rom/CPM_v5.bin"
const DefaultDebufPort = 10000 const DefaultDebufPort = 10000
const confFile = "okemu.yml"
type OkEmuConfig struct { type OkEmuConfig struct {
LogFile string `yaml:"logFile"` LogFile string `yaml:"logFile"`
LogLevel string `yaml:"logLevel"` LogLevel string `yaml:"logLevel"`
MonitorFile string `yaml:"monitorFile"` MonitorFile string `yaml:"monitorFile"`
CPMFile string `yaml:"cpmFile"` CPMFile string `yaml:"cpmFile"`
FDC FDCConfig `yaml:"fdc"` FDC []FDCConfig `yaml:"fdc"`
Debugger DebuggerConfig `yaml:"debugger"` Debugger DebuggerConfig `yaml:"debugger"`
} }
type FDCConfig struct { type FDCConfig struct {
AutoLoadB bool `yaml:"autoLoadB"` AutoLoad bool `yaml:"autoLoad"`
AutoLoadC bool `yaml:"autoLoadC"` AutoSave bool `yaml:"autoSave"`
FloppyB string `yaml:"floppyB"` FloppyFile string `yaml:"floppyFile"`
FloppyC string `yaml:"floppyC"`
} }
type DebuggerConfig struct { type DebuggerConfig struct {
@ -54,7 +54,6 @@ func LoadConfig() {
// usage() // usage()
//} //}
// confFile := args[2] // confFile := args[2]
confFile := "okemu.yml"
conf := OkEmuConfig{} conf := OkEmuConfig{}
data, err := os.ReadFile(confFile) data, err := os.ReadFile(confFile)
@ -96,3 +95,37 @@ func setDefaultConf(conf *OkEmuConfig) {
conf.Debugger.Port = DefaultDebufPort conf.Debugger.Port = DefaultDebufPort
} }
} }
func (c *OkEmuConfig) Clone() *OkEmuConfig {
fds := make([]FDCConfig, 2)
for n, fd := range c.FDC {
fds[n].FloppyFile = fd.FloppyFile
fds[n].AutoLoad = fd.AutoLoad
fds[n].AutoSave = fd.AutoSave
}
return &OkEmuConfig{
LogFile: c.LogFile,
LogLevel: c.LogLevel,
MonitorFile: c.MonitorFile,
CPMFile: c.CPMFile,
FDC: fds,
Debugger: DebuggerConfig{
Enabled: c.Debugger.Enabled,
Host: c.Debugger.Host,
Port: c.Debugger.Port,
},
}
}
func (c *OkEmuConfig) Save() {
data, err := yaml.Marshal(c)
if err != nil {
log.Errorf("config error: %v", err)
}
err = os.WriteFile(confFile, data, 0600)
if err != nil {
log.Errorf("save config file: %v", err)
}
}

View File

@ -34,7 +34,7 @@ var orMatch = regexp.MustCompile(`\s+OR\s+`)
// var xorMatch = regexp.MustCompile(`\s+XOR\s+`) // var xorMatch = regexp.MustCompile(`\s+XOR\s+`)
var hexHMatch = regexp.MustCompile(`[[:xdigit:]]+H`) var hexHMatch = regexp.MustCompile(`[[:xdigit:]]+H`)
var eqMatch = regexp.MustCompile(`[^=><]=[^=]`) var eqMatch = regexp.MustCompile(`[^=><!]=[^=]`)
func patchExpression(expr string) string { func patchExpression(expr string) string {
ex := strings.ToUpper(expr) ex := strings.ToUpper(expr)
@ -58,6 +58,8 @@ func patchExpression(expr string) string {
break break
} }
} }
ex = strings.ReplaceAll(ex, "NOT", "!")
ex = strings.ReplaceAll(ex, "<>", "!=")
return ex return ex
} }

View File

@ -5,13 +5,14 @@ import (
"testing" "testing"
) )
const expr1 = "PC=00100h && SP>=256" const expr1 = "PC=00100h and (NOT(B > 10)) and a != 5"
const expr2 = "PC=00115h" const expr2 = "PC=00115h"
const expr3 = "SP>=1332" const expr3 = "SP>=1332"
var ctx = map[string]interface{}{ var ctx = map[string]interface{}{
"PC": 0x100, "PC": 0x100,
"A": 0x55, "A": 0x55,
"B": 5,
"SP": 0x200, "SP": 0x200,
} }

View File

@ -241,6 +241,8 @@ func HandleCommand(str string, writer *bufio.Writer) bool {
quit = true quit = true
case "snapshot-save": case "snapshot-save":
writeResponseMessage(writer, snapshotSave(params)) writeResponseMessage(writer, snapshotSave(params))
case "snapshot-load":
writeResponseMessage(writer, snapshotLoad(params))
case "set-breakpointaction": case "set-breakpointaction":
// now do nothing // now do nothing
writeResponseMessage(writer, "") writeResponseMessage(writer, "")
@ -665,3 +667,11 @@ func snapshotSave(params string) string {
} }
return "" return ""
} }
func snapshotLoad(params string) string {
e := computer.LoadSnapshot(strings.TrimSpace(params))
if e != nil {
return fmt.Sprintf("Error load snapshot: %s", e)
}
return ""
}

157
main.go
View File

@ -1,14 +1,16 @@
package main package main
import ( import (
"context"
_ "embed" _ "embed"
"fmt" "fmt"
"math"
"okemu/config" "okemu/config"
"okemu/debug" "okemu/debug"
"okemu/debug/listener" "okemu/debug/listener"
"okemu/logger" "okemu/logger"
"okemu/nanotime"
"okemu/okean240" "okemu/okean240"
"okemu/okean240/fdc"
"okemu/z80/dis" "okemu/z80/dis"
"sync/atomic" "sync/atomic"
"time" "time"
@ -16,27 +18,33 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
//log "github.com/sirupsen/logrus"
) )
var Version = "v1.0.0" var Version = "v1.0.0"
var BuildTime = "2026-03-01" var BuildTime = "2026-03-01"
//go:embed hex/m80.hex ////go:embed hex/m80.hex
var serialBytes []byte //var serialBytes []byte
//go:embed bin/main.com //go:embed bin/2048.com
var ramBytes1 []byte var ramBytes1 []byte
//go:embed bin/PLOT.BAS //go:embed bin/JACK.COM
var ramBytes2 []byte var ramBytes2 []byte
var needReset = false var needReset = false
var fullSpeed atomic.Bool var fullSpeed atomic.Bool
func main() { func main() {
fmt.Printf("Starting Ocean-240.2 emulator %s build at %s\n", Version, BuildTime) fmt.Printf("Starting Ocean-240.2 emulator %s build at %s\n", Version, BuildTime)
// Create a context that can be cancelled
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// base log init // base log init
logger.InitLogging() logger.InitLogging()
@ -51,91 +59,148 @@ func main() {
debugger := debug.NewDebugger() debugger := debug.NewDebugger()
computer := okean240.NewComputer(conf, debugger) computer := okean240.NewComputer(conf, debugger)
computer.SetSerialBytes(serialBytes) //computer.SetSerialBytes(serialBytes)
if conf.FDC.AutoLoadB { computer.AutoLoadFloppy()
err := computer.LoadFloppy(fdc.FloppyB)
if err != nil {
// show message
}
}
if conf.FDC.AutoLoadC {
err := computer.LoadFloppy(fdc.FloppyC)
if err != nil {
// show message
}
}
disasm := dis.NewDisassembler(computer) disasm := dis.NewDisassembler(computer)
w, raster, label := mainWindow(computer) w, raster, label := mainWindow(computer, conf)
go emulator(computer) go emulator(ctx, computer)
go screen(computer, raster, label) // go timerClock(ctx, computer)
go screen(ctx, computer, raster, label)
if conf.Debugger.Enabled { if conf.Debugger.Enabled {
go listener.SetupTcpHandler(conf, debugger, disasm, computer) go listener.SetupTcpHandler(conf, debugger, disasm, computer)
} }
(*w).ShowAndRun() (*w).ShowAndRun()
computer.AutoSaveFloppy()
} }
func screen(computer *okean240.ComputerType, raster *canvas.Raster, label *widget.Label) { func screen(ctx context.Context, computer *okean240.ComputerType, raster *canvas.Raster, label *widget.Label) {
ticker := time.NewTicker(20 * time.Millisecond) ticker := time.NewTicker(20 * time.Millisecond)
defer ticker.Stop()
frame := 0 frame := 0
var pre uint64 = 0 var pre uint64 = 0
var freq uint64 = 0 var preTim uint64 = 0
var freq float64 = 0
var freqTim float64 = 0
timeStart := time.Now()
for range ticker.C { for {
select {
case <-ticker.C:
frame++ frame++
// redraw screen here // redraw screen here
fyne.Do(func() { fyne.Do(func() {
// status for every 50 frames // status for every 50 frames
if frame%50 == 0 { if frame%50 == 0 {
freq = computer.Cycles() - pre timeElapsed := time.Since(timeStart)
period := float64(timeElapsed.Nanoseconds()) / 1_000_000.0
freq = math.Round(float64(computer.Cycles()-pre)/period) / 1000.0
freqTim = math.Round(float64(timerTicks.Load()-preTim)/period) / 1000.0
label.SetText(formatLabel(computer, freq, freqTim))
// adjust cpu clock
if freq > 2.55 && cpuClkPeriod.Load() < defaultCpuClkPeriod+40 {
cpuClkPeriod.Add(1)
} else if freq < 2.45 && cpuClkPeriod.Load() > defaultCpuClkPeriod-40 {
cpuClkPeriod.Add(-1)
}
// adjust timer clock
if freqTim > 1.53 && timerClkPeriod.Load() < defaultTimerClkPeriod+20 {
timerClkPeriod.Add(1)
} else if freqTim < 1.47 && timerClkPeriod.Load() > defaultTimerClkPeriod-20 {
timerClkPeriod.Add(-1)
}
//log.Debugf("Cpu clk period: %d, Timer clock period: %d, period: %1.3f", cpuClkPeriod.Load(), timerClkPeriod.Load(), period)
pre = computer.Cycles() pre = computer.Cycles()
label.SetText(formatLabel(computer, freq)) preTim = timerTicks.Load()
timeStart = time.Now()
} }
raster.Refresh() raster.Refresh()
}) })
case <-ctx.Done():
return
}
} }
} }
func formatLabel(computer *okean240.ComputerType, freq uint64) string { func formatLabel(computer *okean240.ComputerType, freq float64, freqTim float64) string {
return fmt.Sprintf("Screen size: %dx%d | F: %d | Debugger: %s", computer.ScreenWidth(), computer.ScreenHeight(), freq, computer.DebuggerState()) return fmt.Sprintf("Screen size: %dx%d | Fcpu: %1.2fMHz | Ftmr: %1.2fMHz | Debugger: %s", computer.ScreenWidth(), computer.ScreenHeight(), freq, freqTim, computer.DebuggerState())
} }
const ticksPerTact uint64 = 4 var timerTicks atomic.Uint64
func emulator(computer *okean240.ComputerType) { const defaultTimerClkPeriod = 564 // = 1_000_000_000 / 1_607_900 // period in nanos for 1.5MHz frequency
ticker := time.NewTicker(66 * time.Nanosecond) const defaultCpuClkPeriod = 221 // = 1_000_000_000 / 2_770_000 // period in nanos for 2.5MHz frequency
var ticks uint64 = 0
var nextClock = ticks + ticksPerTact var timerClkPeriod atomic.Int64 // = 1_000_000_000 / 1_607_900 // period in nanos for 1.5MHz frequency
//var ticksCPU = 3 var cpuClkPeriod atomic.Int64 // = 1_000_000_000 / 2_770_000 // period in nanos for 2.5MHz frequency
for range ticker.C {
ticks++ func emulator(ctx context.Context, computer *okean240.ComputerType) {
if ticks%10 == 0 { ticker := time.NewTicker(133 * time.Nanosecond)
// 1.5 MHz defer ticker.Stop()
cpuClkPeriod.Store(defaultCpuClkPeriod)
timerClkPeriod.Store(defaultTimerClkPeriod)
cpuTicks := uint64(0)
nextTick := uint64(0)
cpuTStart := nanotime.Now()
tmrTStart := cpuTStart
var bp uint16
var bpType byte
for {
select {
case <-ticker.C:
tmrElapsed := nanotime.Since(tmrTStart)
// TIMER CLK
if tmrElapsed.Nanoseconds() >= timerClkPeriod.Load() {
computer.TimerClk() computer.TimerClk()
timerTicks.Add(1)
tmrTStart = nanotime.Now()
} }
var bp uint16 = 0
var typ byte = 0 // CPU
cpuElapsed := nanotime.Since(cpuTStart)
if cpuElapsed.Nanoseconds() >= cpuClkPeriod.Load() {
cpuTicks++
bp = 0
bpType = 0
if fullSpeed.Load() { if fullSpeed.Load() {
_, bp, typ = computer.Do() // Max frequency
_, bp, bpType = computer.Do()
} else { } else {
if ticks >= nextClock { // 2.5MHz frequency
if cpuTicks >= nextTick {
var t uint32 var t uint32
t, bp, typ = computer.Do() t, bp, bpType = computer.Do()
nextClock = ticks + uint64(t)*ticksPerTact nextTick += uint64(t)
} }
} }
// Breakpoint hit // Breakpoint hit
if bp > 0 || typ != 0 { if bp > 0 || bpType != 0 {
listener.BreakpointHit(bp, typ) listener.BreakpointHit(bp, bpType)
} }
if needReset { if needReset {
computer.Reset() computer.Reset()
needReset = false needReset = false
} }
cpuTStart = nanotime.Now()
}
case <-ctx.Done():
return
} }
} }
}

18
nanotime/nanotime.go Normal file
View File

@ -0,0 +1,18 @@
package nanotime
import (
"time"
_ "unsafe" // required to use //go:linkname
)
//go:noescape
//go:linkname nanotime runtime.nanotime
func nanotime() int64
func Now() uint64 {
return uint64(nanotime())
}
func Since(t uint64) time.Duration {
return time.Duration(Now() - t)
}

View File

@ -14,6 +14,7 @@ import (
"okemu/okean240/usart" "okemu/okean240/usart"
"okemu/z80" "okemu/z80"
"os" "os"
"strconv"
//"okemu/z80" //"okemu/z80"
"okemu/z80/c99" "okemu/z80/c99"
@ -48,6 +49,7 @@ type ComputerType struct {
cpuFrequency uint32 cpuFrequency uint32
// //
debugger *debug.Debugger debugger *debug.Debugger
config *config.OkEmuConfig
} }
type Snapshot struct { type Snapshot struct {
@ -98,6 +100,7 @@ func (c *ComputerType) MemWrite(addr uint16, val byte) {
// NewComputer Builds new computer // NewComputer Builds new computer
func NewComputer(cfg *config.OkEmuConfig, deb *debug.Debugger) *ComputerType { func NewComputer(cfg *config.OkEmuConfig, deb *debug.Debugger) *ComputerType {
c := ComputerType{} c := ComputerType{}
c.config = cfg
c.memory = Memory{} c.memory = Memory{}
c.memory.Init(cfg.MonitorFile, cfg.CPMFile) c.memory.Init(cfg.MonitorFile, cfg.CPMFile)
@ -379,17 +382,6 @@ func (c *ComputerType) memoryAsHexStr() string {
} }
func (c *ComputerType) SaveSnapshot(fn string) error { func (c *ComputerType) SaveSnapshot(fn string) error {
// create snapshot file
file, err := os.Create(fn)
if err != nil {
return err
}
defer func() {
err := file.Close()
if err != nil {
log.Error(err)
}
}()
// take snapshot // take snapshot
s := Snapshot{ s := Snapshot{
CPU: c.cpu.GetState(), CPU: c.cpu.GetState(),
@ -401,9 +393,59 @@ func (c *ComputerType) SaveSnapshot(fn string) error {
return err return err
} }
// and save // and save
err = binary.Write(file, binary.LittleEndian, b) err = os.WriteFile(fn, b, 0644)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func (c *ComputerType) LoadSnapshot(fn string) error {
// read snapshot file
b, err := os.ReadFile(fn)
if err != nil {
return err
}
// unmarshal from JSON
var result Snapshot
err = json.Unmarshal(b, &result)
if err != nil {
return err
}
c.cpu.SetState(result.CPU)
return c.restoreMemoryFromHex(result.Memory)
}
func (c *ComputerType) restoreMemoryFromHex(memory string) error {
for addr := 0; addr <= 65535; addr++ {
b, e := strconv.ParseUint(memory[addr*2:addr*2+2], 16, 8)
if e != nil {
log.Error(e)
return e
}
c.memory.MemWrite(uint16(addr), byte(b))
}
return nil
}
func (c *ComputerType) AutoSaveFloppy() {
for drv := byte(0); drv < fdc.TotalDrives; drv++ {
if c.config.FDC[drv].AutoSave {
e := c.fdc.SaveFloppy(drv)
if e != nil {
log.Error(e)
}
}
}
}
func (c *ComputerType) AutoLoadFloppy() {
for drv := byte(0); drv < fdc.TotalDrives; drv++ {
if c.config.FDC[drv].AutoLoad {
e := c.fdc.LoadFloppy(drv)
if e != nil {
log.Error(e)
}
}
}
}

View File

@ -88,7 +88,7 @@ type FloppyDriveController struct {
//curSector *SectorType //curSector *SectorType
bytePtr uint16 bytePtr uint16
trackBuffer []byte trackBuffer []byte
floppyFile []string // floppyFile []string
config *config.OkEmuConfig config *config.OkEmuConfig
} }
@ -332,14 +332,14 @@ func (f *FloppyDriveController) Drq() byte {
func (f *FloppyDriveController) LoadFloppy(drive byte) error { func (f *FloppyDriveController) LoadFloppy(drive byte) error {
if drive < TotalDrives { if drive < TotalDrives {
return loadFloppy(&f.sectors[drive], f.floppyFile[drive]) return loadFloppy(&f.sectors[drive], f.config.FDC[drive].FloppyFile)
} }
return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range") return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
} }
func (f *FloppyDriveController) SaveFloppy(drive byte) error { func (f *FloppyDriveController) SaveFloppy(drive byte) error {
if drive < TotalDrives { if drive < TotalDrives {
return saveFloppy(&f.sectors[drive], f.floppyFile[drive]) return saveFloppy(&f.sectors[drive], f.config.FDC[drive].FloppyFile)
} }
return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range") return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
} }
@ -353,6 +353,7 @@ func NewFDC(conf *config.OkEmuConfig) *FloppyDriveController {
sec[d][i] = bytes.Repeat([]byte{0xe5}, SectorSize) sec[d][i] = bytes.Repeat([]byte{0xe5}, SectorSize)
} }
} }
return &FloppyDriveController{ return &FloppyDriveController{
sideNo: 0, sideNo: 0,
ddEn: 0, ddEn: 0,
@ -366,7 +367,8 @@ func NewFDC(conf *config.OkEmuConfig) *FloppyDriveController {
lastCmd: 0xff, lastCmd: 0xff,
sectors: sec, sectors: sec,
bytePtr: 0xffff, bytePtr: 0xffff,
floppyFile: []string{conf.FDC.FloppyB, conf.FDC.FloppyC}, //floppyConf: conf.FDC,
config: conf,
} }
} }

View File

@ -1,15 +1,15 @@
logFile: "okemu.log" logFile: okemu.log
logLevel: "info" logLevel: info
monitorFile: rom/MON_r8_9c6c6546.bin
monitorFile: "rom/MON_r8_9c6c6546.bin" cpmFile: rom/CPM_r8_bc0695e4.bin
cpmFile: "rom/CPM_r8_bc0695e4.bin"
fdc: fdc:
autoLoadB: true - autoLoad: true
floppyB: "floppy/floppyB.okd" autoSave: true
autoLoadC: true floppyFile: floppy/floppyB.okd
floppyC: "floppy/floppyC.okd" - autoLoad: true
autoSave: true
floppyFile: floppy/floppyC.okd
debugger: debugger:
enabled: true enabled: true
host: localhost
port: 10001 port: 10001

91
settings.go Normal file
View File

@ -0,0 +1,91 @@
package main
import (
"okemu/config"
"okemu/okean240/fdc"
"strconv"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/validation"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
log "github.com/sirupsen/logrus"
)
func settingsDialog(config *config.OkEmuConfig) fyne.CanvasObject {
debugger := widget.NewForm(
widget.NewFormItem("Remote debug", debugCheckBox(config)),
widget.NewFormItem("Host", hostEntry(config)),
widget.NewFormItem("Port", portEntry(config)))
cont := container.NewAppTabs(
container.NewTabItemWithIcon("Debug", theme.MediaPlayIcon(), widget.NewCard("", "", debugger)),
)
for drv := byte(0); drv < fdc.TotalDrives; drv++ {
cont.Append(
container.NewTabItemWithIcon("Drive "+string(rune(66+drv))+":", theme.DocumentSaveIcon(), widget.NewCard("Floppy 720k", "", diskForm(config, drv))),
)
}
return cont
}
func portEntry(cfg *config.OkEmuConfig) *widget.Entry {
dbgPort := widget.NewEntry()
dbgPort.SetText(strconv.Itoa(cfg.Debugger.Port))
dbgPort.Validator = validation.NewRegexp(`^[0-9]+$`, "port can only contain numbers")
dbgPort.OnSubmitted = func(s string) {
p, e := strconv.Atoi(s)
if e != nil {
log.Warn("Illegal port number: " + s)
} else {
cfg.Debugger.Port = p
}
}
return dbgPort
}
func hostEntry(cfg *config.OkEmuConfig) *widget.Entry {
entry := widget.NewEntry()
entry.SetText(cfg.Debugger.Host)
entry.Validator = validation.NewRegexp(`^[A-Za-z0-9_-]+$`, "hostname can only contain letters, numbers, '_', and '-'")
entry.OnSubmitted = func(s string) {
cfg.Debugger.Host = s
}
return entry
}
func debugCheckBox(cfg *config.OkEmuConfig) *widget.Check {
// Debug Enabled
check := widget.NewCheck("Enable", func(checked bool) {
cfg.Debugger.Enabled = checked
})
check.Checked = cfg.Debugger.Enabled
return check
}
func diskForm(cfg *config.OkEmuConfig, drv byte) *widget.Form {
dskAutoLoad := widget.NewCheck("AutoLoad", func(checked bool) {
cfg.FDC[drv].AutoLoad = checked
})
dskAutoLoad.Checked = cfg.FDC[drv].AutoLoad
dskAutoSave := widget.NewCheck("AutoSave", func(checked bool) {
cfg.FDC[drv].AutoSave = checked
})
dskAutoSave.Checked = cfg.FDC[drv].AutoSave
dskFileName := widget.NewEntry()
dskFileName.SetText(cfg.FDC[drv].FloppyFile)
dskFileName.OnSubmitted = func(s string) {
cfg.FDC[drv].FloppyFile = s
}
return widget.NewForm(
widget.NewFormItem("AutoLoad", dskAutoLoad),
widget.NewFormItem("AutoSave", dskAutoSave),
widget.NewFormItem("File", dskFileName),
)
}