mirror of
https://github.com/romychs/Ocean-240.2-Emulator.git
synced 2026-04-21 11:03:21 +03:00
Redo main cycle
This commit is contained in:
parent
c1616dfbd6
commit
a74841b048
69
appWindow.go
69
appWindow.go
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"okemu/config"
|
||||
"okemu/okean240"
|
||||
"okemu/okean240/fdc"
|
||||
|
||||
@ -17,7 +18,7 @@ import (
|
||||
"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()
|
||||
w := emulatorApp.NewWindow("Океан 240.2")
|
||||
w.Canvas().SetOnTypedKey(
|
||||
@ -52,7 +53,7 @@ func mainWindow(computer *okean240.ComputerType) (*fyne.Window, *canvas.Raster,
|
||||
w.Resize(fyne.NewSize(600, 600))
|
||||
|
||||
vBox := container.NewVBox(
|
||||
newToolbar(computer, w),
|
||||
newToolbar(computer, w, emulatorApp, config),
|
||||
centerRaster,
|
||||
label,
|
||||
)
|
||||
@ -62,45 +63,75 @@ func mainWindow(computer *okean240.ComputerType) (*fyne.Window, *canvas.Raster,
|
||||
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()
|
||||
for d := 0; d < fdc.TotalDrives; d++ {
|
||||
hBox.Add(widget.NewLabel(string(rune(66+d)) + ":"))
|
||||
hBox.Add(widget.NewToolbar(
|
||||
widget.NewToolbarAction(theme.DocumentSaveIcon(), func() {
|
||||
err := c.SaveFloppy(fdc.FloppyB)
|
||||
err := c.SaveFloppy(byte(d))
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
}
|
||||
}),
|
||||
//widget.NewToolbarSpacer(),
|
||||
widget.NewToolbarAction(theme.FolderOpenIcon(), func() {
|
||||
err := c.SaveFloppy(fdc.FloppyC)
|
||||
err := c.LoadFloppy(byte(d))
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
}
|
||||
}),
|
||||
))
|
||||
hBox.Add(widget.NewSeparator())
|
||||
}
|
||||
|
||||
hBox.Add(widget.NewButtonWithIcon("1", theme.DownloadIcon(), func() {
|
||||
c.SetRamBytes(ramBytes1)
|
||||
}))
|
||||
hBox.Add(widget.NewSeparator())
|
||||
hBox.Add(widget.NewButtonWithIcon("Ctrl+C", theme.LogoutIcon(), func() {
|
||||
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)
|
||||
}))
|
||||
hBox.Add(widget.NewSeparator())
|
||||
bNorm := widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() {
|
||||
fullSpeed.Store(false)
|
||||
c.SetCPUFrequency(2_500_000)
|
||||
//bNorm.Disable()
|
||||
cbFreq := widget.NewCheck("Fmax", func(checked bool) {
|
||||
fullSpeed.Store(checked)
|
||||
if checked {
|
||||
c.SetCPUFrequency(25_000_000)
|
||||
} else {
|
||||
c.SetCPUFrequency(2_500_000)
|
||||
}
|
||||
})
|
||||
//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()
|
||||
//})
|
||||
|
||||
})
|
||||
bFast := widget.NewButtonWithIcon("", theme.MediaFastForwardIcon(), func() {
|
||||
fullSpeed.Store(true)
|
||||
c.SetCPUFrequency(50_000_000)
|
||||
bNorm.Enable()
|
||||
//bFast.Disable()
|
||||
})
|
||||
hBox.Add(bNorm)
|
||||
hBox.Add(bFast)
|
||||
hBox.Add(cbFreq)
|
||||
//hBox.Add(bFast)
|
||||
hBox.Add(widget.NewSeparator())
|
||||
hBox.Add(widget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
|
||||
cfg := config.Clone()
|
||||
d := dialog.NewCustomConfirm("OK-Emu settings", "Save", "Cancel", settingsDialog(cfg), func(b bool) {
|
||||
if b {
|
||||
cfg.Save()
|
||||
}
|
||||
}, w)
|
||||
d.Resize(fyne.NewSize(450, 360))
|
||||
//w.SetContent(settings.NewSettings().LoadAppearanceScreen(w))
|
||||
d.Show()
|
||||
}))
|
||||
hBox.Add(layout.NewSpacer())
|
||||
hBox.Add(widget.NewButtonWithIcon("Reset", theme.MediaReplayIcon(), func() {
|
||||
needReset = true
|
||||
|
||||
@ -10,21 +10,21 @@ import (
|
||||
const defaultMonitorFile = "rom/MON_v5.bin"
|
||||
const defaultCPMFile = "rom/CPM_v5.bin"
|
||||
const DefaultDebufPort = 10000
|
||||
const confFile = "okemu.yml"
|
||||
|
||||
type OkEmuConfig struct {
|
||||
LogFile string `yaml:"logFile"`
|
||||
LogLevel string `yaml:"logLevel"`
|
||||
MonitorFile string `yaml:"monitorFile"`
|
||||
CPMFile string `yaml:"cpmFile"`
|
||||
FDC FDCConfig `yaml:"fdc"`
|
||||
FDC []FDCConfig `yaml:"fdc"`
|
||||
Debugger DebuggerConfig `yaml:"debugger"`
|
||||
}
|
||||
|
||||
type FDCConfig struct {
|
||||
AutoLoadB bool `yaml:"autoLoadB"`
|
||||
AutoLoadC bool `yaml:"autoLoadC"`
|
||||
FloppyB string `yaml:"floppyB"`
|
||||
FloppyC string `yaml:"floppyC"`
|
||||
AutoLoad bool `yaml:"autoLoad"`
|
||||
AutoSave bool `yaml:"autoSave"`
|
||||
FloppyFile string `yaml:"floppyFile"`
|
||||
}
|
||||
|
||||
type DebuggerConfig struct {
|
||||
@ -54,7 +54,6 @@ func LoadConfig() {
|
||||
// usage()
|
||||
//}
|
||||
// confFile := args[2]
|
||||
confFile := "okemu.yml"
|
||||
|
||||
conf := OkEmuConfig{}
|
||||
data, err := os.ReadFile(confFile)
|
||||
@ -96,3 +95,37 @@ func setDefaultConf(conf *OkEmuConfig) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ var orMatch = regexp.MustCompile(`\s+OR\s+`)
|
||||
|
||||
// var xorMatch = regexp.MustCompile(`\s+XOR\s+`)
|
||||
var hexHMatch = regexp.MustCompile(`[[:xdigit:]]+H`)
|
||||
var eqMatch = regexp.MustCompile(`[^=><]=[^=]`)
|
||||
var eqMatch = regexp.MustCompile(`[^=><!]=[^=]`)
|
||||
|
||||
func patchExpression(expr string) string {
|
||||
ex := strings.ToUpper(expr)
|
||||
@ -58,6 +58,8 @@ func patchExpression(expr string) string {
|
||||
break
|
||||
}
|
||||
}
|
||||
ex = strings.ReplaceAll(ex, "NOT", "!")
|
||||
ex = strings.ReplaceAll(ex, "<>", "!=")
|
||||
return ex
|
||||
}
|
||||
|
||||
|
||||
@ -5,13 +5,14 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const expr1 = "PC=00100h && SP>=256"
|
||||
const expr1 = "PC=00100h and (NOT(B > 10)) and a != 5"
|
||||
const expr2 = "PC=00115h"
|
||||
const expr3 = "SP>=1332"
|
||||
|
||||
var ctx = map[string]interface{}{
|
||||
"PC": 0x100,
|
||||
"A": 0x55,
|
||||
"B": 5,
|
||||
"SP": 0x200,
|
||||
}
|
||||
|
||||
|
||||
@ -241,6 +241,8 @@ func HandleCommand(str string, writer *bufio.Writer) bool {
|
||||
quit = true
|
||||
case "snapshot-save":
|
||||
writeResponseMessage(writer, snapshotSave(params))
|
||||
case "snapshot-load":
|
||||
writeResponseMessage(writer, snapshotLoad(params))
|
||||
case "set-breakpointaction":
|
||||
// now do nothing
|
||||
writeResponseMessage(writer, "")
|
||||
@ -665,3 +667,11 @@ func snapshotSave(params string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func snapshotLoad(params string) string {
|
||||
e := computer.LoadSnapshot(strings.TrimSpace(params))
|
||||
if e != nil {
|
||||
return fmt.Sprintf("Error load snapshot: %s", e)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
205
main.go
205
main.go
@ -1,14 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"math"
|
||||
"okemu/config"
|
||||
"okemu/debug"
|
||||
"okemu/debug/listener"
|
||||
"okemu/logger"
|
||||
"okemu/nanotime"
|
||||
"okemu/okean240"
|
||||
"okemu/okean240/fdc"
|
||||
"okemu/z80/dis"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -16,27 +18,33 @@ import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
//log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var Version = "v1.0.0"
|
||||
var BuildTime = "2026-03-01"
|
||||
|
||||
//go:embed hex/m80.hex
|
||||
var serialBytes []byte
|
||||
////go:embed hex/m80.hex
|
||||
//var serialBytes []byte
|
||||
|
||||
//go:embed bin/main.com
|
||||
//go:embed bin/2048.com
|
||||
var ramBytes1 []byte
|
||||
|
||||
//go:embed bin/PLOT.BAS
|
||||
//go:embed bin/JACK.COM
|
||||
var ramBytes2 []byte
|
||||
|
||||
var needReset = false
|
||||
|
||||
var fullSpeed atomic.Bool
|
||||
|
||||
func main() {
|
||||
|
||||
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
|
||||
logger.InitLogging()
|
||||
|
||||
@ -51,91 +59,148 @@ func main() {
|
||||
debugger := debug.NewDebugger()
|
||||
computer := okean240.NewComputer(conf, debugger)
|
||||
|
||||
computer.SetSerialBytes(serialBytes)
|
||||
//computer.SetSerialBytes(serialBytes)
|
||||
|
||||
if conf.FDC.AutoLoadB {
|
||||
err := computer.LoadFloppy(fdc.FloppyB)
|
||||
if err != nil {
|
||||
// show message
|
||||
}
|
||||
}
|
||||
if conf.FDC.AutoLoadC {
|
||||
err := computer.LoadFloppy(fdc.FloppyC)
|
||||
if err != nil {
|
||||
// show message
|
||||
}
|
||||
}
|
||||
computer.AutoLoadFloppy()
|
||||
|
||||
disasm := dis.NewDisassembler(computer)
|
||||
|
||||
w, raster, label := mainWindow(computer)
|
||||
w, raster, label := mainWindow(computer, conf)
|
||||
|
||||
go emulator(computer)
|
||||
go screen(computer, raster, label)
|
||||
go emulator(ctx, computer)
|
||||
// go timerClock(ctx, computer)
|
||||
go screen(ctx, computer, raster, label)
|
||||
|
||||
if conf.Debugger.Enabled {
|
||||
go listener.SetupTcpHandler(conf, debugger, disasm, computer)
|
||||
}
|
||||
|
||||
(*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)
|
||||
defer ticker.Stop()
|
||||
|
||||
frame := 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 {
|
||||
frame++
|
||||
// redraw screen here
|
||||
fyne.Do(func() {
|
||||
// status for every 50 frames
|
||||
if frame%50 == 0 {
|
||||
freq = computer.Cycles() - pre
|
||||
pre = computer.Cycles()
|
||||
label.SetText(formatLabel(computer, freq))
|
||||
}
|
||||
raster.Refresh()
|
||||
})
|
||||
}
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
frame++
|
||||
// redraw screen here
|
||||
fyne.Do(func() {
|
||||
// status for every 50 frames
|
||||
if frame%50 == 0 {
|
||||
timeElapsed := time.Since(timeStart)
|
||||
period := float64(timeElapsed.Nanoseconds()) / 1_000_000.0
|
||||
|
||||
func formatLabel(computer *okean240.ComputerType, freq uint64) string {
|
||||
return fmt.Sprintf("Screen size: %dx%d | F: %d | Debugger: %s", computer.ScreenWidth(), computer.ScreenHeight(), freq, computer.DebuggerState())
|
||||
}
|
||||
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))
|
||||
|
||||
const ticksPerTact uint64 = 4
|
||||
// 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)
|
||||
}
|
||||
|
||||
func emulator(computer *okean240.ComputerType) {
|
||||
ticker := time.NewTicker(66 * time.Nanosecond)
|
||||
var ticks uint64 = 0
|
||||
var nextClock = ticks + ticksPerTact
|
||||
//var ticksCPU = 3
|
||||
for range ticker.C {
|
||||
ticks++
|
||||
if ticks%10 == 0 {
|
||||
// 1.5 MHz
|
||||
computer.TimerClk()
|
||||
}
|
||||
var bp uint16 = 0
|
||||
var typ byte = 0
|
||||
if fullSpeed.Load() {
|
||||
_, bp, typ = computer.Do()
|
||||
} else {
|
||||
if ticks >= nextClock {
|
||||
var t uint32
|
||||
t, bp, typ = computer.Do()
|
||||
nextClock = ticks + uint64(t)*ticksPerTact
|
||||
}
|
||||
}
|
||||
// Breakpoint hit
|
||||
if bp > 0 || typ != 0 {
|
||||
listener.BreakpointHit(bp, typ)
|
||||
}
|
||||
if needReset {
|
||||
computer.Reset()
|
||||
needReset = false
|
||||
//log.Debugf("Cpu clk period: %d, Timer clock period: %d, period: %1.3f", cpuClkPeriod.Load(), timerClkPeriod.Load(), period)
|
||||
pre = computer.Cycles()
|
||||
preTim = timerTicks.Load()
|
||||
timeStart = time.Now()
|
||||
}
|
||||
raster.Refresh()
|
||||
})
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatLabel(computer *okean240.ComputerType, freq float64, freqTim float64) string {
|
||||
return fmt.Sprintf("Screen size: %dx%d | Fcpu: %1.2fMHz | Ftmr: %1.2fMHz | Debugger: %s", computer.ScreenWidth(), computer.ScreenHeight(), freq, freqTim, computer.DebuggerState())
|
||||
}
|
||||
|
||||
var timerTicks atomic.Uint64
|
||||
|
||||
const defaultTimerClkPeriod = 564 // = 1_000_000_000 / 1_607_900 // period in nanos for 1.5MHz frequency
|
||||
const defaultCpuClkPeriod = 221 // = 1_000_000_000 / 2_770_000 // period in nanos for 2.5MHz frequency
|
||||
|
||||
var timerClkPeriod atomic.Int64 // = 1_000_000_000 / 1_607_900 // period in nanos for 1.5MHz frequency
|
||||
var cpuClkPeriod atomic.Int64 // = 1_000_000_000 / 2_770_000 // period in nanos for 2.5MHz frequency
|
||||
|
||||
func emulator(ctx context.Context, computer *okean240.ComputerType) {
|
||||
ticker := time.NewTicker(133 * time.Nanosecond)
|
||||
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()
|
||||
timerTicks.Add(1)
|
||||
tmrTStart = nanotime.Now()
|
||||
}
|
||||
|
||||
// CPU
|
||||
cpuElapsed := nanotime.Since(cpuTStart)
|
||||
if cpuElapsed.Nanoseconds() >= cpuClkPeriod.Load() {
|
||||
cpuTicks++
|
||||
bp = 0
|
||||
bpType = 0
|
||||
|
||||
if fullSpeed.Load() {
|
||||
// Max frequency
|
||||
_, bp, bpType = computer.Do()
|
||||
} else {
|
||||
// 2.5MHz frequency
|
||||
if cpuTicks >= nextTick {
|
||||
var t uint32
|
||||
t, bp, bpType = computer.Do()
|
||||
nextTick += uint64(t)
|
||||
}
|
||||
}
|
||||
// Breakpoint hit
|
||||
if bp > 0 || bpType != 0 {
|
||||
listener.BreakpointHit(bp, bpType)
|
||||
}
|
||||
if needReset {
|
||||
computer.Reset()
|
||||
needReset = false
|
||||
}
|
||||
cpuTStart = nanotime.Now()
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
18
nanotime/nanotime.go
Normal file
18
nanotime/nanotime.go
Normal 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)
|
||||
}
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"okemu/okean240/usart"
|
||||
"okemu/z80"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
//"okemu/z80"
|
||||
"okemu/z80/c99"
|
||||
@ -48,6 +49,7 @@ type ComputerType struct {
|
||||
cpuFrequency uint32
|
||||
//
|
||||
debugger *debug.Debugger
|
||||
config *config.OkEmuConfig
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
@ -98,6 +100,7 @@ func (c *ComputerType) MemWrite(addr uint16, val byte) {
|
||||
// NewComputer Builds new computer
|
||||
func NewComputer(cfg *config.OkEmuConfig, deb *debug.Debugger) *ComputerType {
|
||||
c := ComputerType{}
|
||||
c.config = cfg
|
||||
c.memory = Memory{}
|
||||
c.memory.Init(cfg.MonitorFile, cfg.CPMFile)
|
||||
|
||||
@ -379,17 +382,6 @@ func (c *ComputerType) memoryAsHexStr() string {
|
||||
}
|
||||
|
||||
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
|
||||
s := Snapshot{
|
||||
CPU: c.cpu.GetState(),
|
||||
@ -401,9 +393,59 @@ func (c *ComputerType) SaveSnapshot(fn string) error {
|
||||
return err
|
||||
}
|
||||
// and save
|
||||
err = binary.Write(file, binary.LittleEndian, b)
|
||||
err = os.WriteFile(fn, b, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,8 +88,8 @@ type FloppyDriveController struct {
|
||||
//curSector *SectorType
|
||||
bytePtr uint16
|
||||
trackBuffer []byte
|
||||
floppyFile []string
|
||||
config *config.OkEmuConfig
|
||||
// floppyFile []string
|
||||
config *config.OkEmuConfig
|
||||
}
|
||||
|
||||
type FloppyDriveControllerInterface interface {
|
||||
@ -332,14 +332,14 @@ func (f *FloppyDriveController) Drq() byte {
|
||||
|
||||
func (f *FloppyDriveController) LoadFloppy(drive byte) error {
|
||||
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")
|
||||
}
|
||||
|
||||
func (f *FloppyDriveController) SaveFloppy(drive byte) error {
|
||||
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")
|
||||
}
|
||||
@ -353,20 +353,22 @@ func NewFDC(conf *config.OkEmuConfig) *FloppyDriveController {
|
||||
sec[d][i] = bytes.Repeat([]byte{0xe5}, SectorSize)
|
||||
}
|
||||
}
|
||||
|
||||
return &FloppyDriveController{
|
||||
sideNo: 0,
|
||||
ddEn: 0,
|
||||
init: 0,
|
||||
drive: 0,
|
||||
mot1: 0,
|
||||
mot0: 0,
|
||||
intRq: 0,
|
||||
motSt: 0,
|
||||
drq: 0,
|
||||
lastCmd: 0xff,
|
||||
sectors: sec,
|
||||
bytePtr: 0xffff,
|
||||
floppyFile: []string{conf.FDC.FloppyB, conf.FDC.FloppyC},
|
||||
sideNo: 0,
|
||||
ddEn: 0,
|
||||
init: 0,
|
||||
drive: 0,
|
||||
mot1: 0,
|
||||
mot0: 0,
|
||||
intRq: 0,
|
||||
motSt: 0,
|
||||
drq: 0,
|
||||
lastCmd: 0xff,
|
||||
sectors: sec,
|
||||
bytePtr: 0xffff,
|
||||
//floppyConf: conf.FDC,
|
||||
config: conf,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
26
okemu.yml
26
okemu.yml
@ -1,15 +1,15 @@
|
||||
logFile: "okemu.log"
|
||||
logLevel: "info"
|
||||
|
||||
monitorFile: "rom/MON_r8_9c6c6546.bin"
|
||||
cpmFile: "rom/CPM_r8_bc0695e4.bin"
|
||||
|
||||
logFile: okemu.log
|
||||
logLevel: info
|
||||
monitorFile: rom/MON_r8_9c6c6546.bin
|
||||
cpmFile: rom/CPM_r8_bc0695e4.bin
|
||||
fdc:
|
||||
autoLoadB: true
|
||||
floppyB: "floppy/floppyB.okd"
|
||||
autoLoadC: true
|
||||
floppyC: "floppy/floppyC.okd"
|
||||
|
||||
- autoLoad: true
|
||||
autoSave: true
|
||||
floppyFile: floppy/floppyB.okd
|
||||
- autoLoad: true
|
||||
autoSave: true
|
||||
floppyFile: floppy/floppyC.okd
|
||||
debugger:
|
||||
enabled: true
|
||||
port: 10001
|
||||
enabled: true
|
||||
host: localhost
|
||||
port: 10001
|
||||
|
||||
91
settings.go
Normal file
91
settings.go
Normal 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),
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user