diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..915d0e5 --- /dev/null +++ b/TODO.md @@ -0,0 +1,7 @@ +# TODO + +## Backlog + +- [ ] Remove `#ifdef INCLUDE_RCSID_C` / `#endif` guards from header files and `#define`/`#undef INCLUDE_RCSID_C` from `sim65816.c` and `sound.c` in `gsplus/src/`. The `const char rcsid_` lines were already removed; these are the leftover scaffolding. + +- [ ] Update Menubar titles for X/Win to match.. Mac build already updated to "GS+" \ No newline at end of file diff --git a/gsplus/lib/2mg.icns b/gsplus/lib/2mg.icns new file mode 100644 index 0000000..1e612e0 Binary files /dev/null and b/gsplus/lib/2mg.icns differ diff --git a/gsplus/lib/525.icns b/gsplus/lib/525.icns new file mode 100644 index 0000000..94e5a90 Binary files /dev/null and b/gsplus/lib/525.icns differ diff --git a/gsplus/lib/MainMenu.nib b/gsplus/lib/MainMenu.nib new file mode 100644 index 0000000..a18e5ff Binary files /dev/null and b/gsplus/lib/MainMenu.nib differ diff --git a/gsplus/lib/kegsicon.icns b/gsplus/lib/kegsicon.icns new file mode 100644 index 0000000..72ea172 Binary files /dev/null and b/gsplus/lib/kegsicon.icns differ diff --git a/gsplus/lib/kegsicon.png b/gsplus/lib/kegsicon.png new file mode 100644 index 0000000..f17e994 Binary files /dev/null and b/gsplus/lib/kegsicon.png differ diff --git a/gsplus/lib/make_mac_icon b/gsplus/lib/make_mac_icon new file mode 100755 index 0000000..de47fd7 --- /dev/null +++ b/gsplus/lib/make_mac_icon @@ -0,0 +1,57 @@ +#!/usr/bin/perl -w + +# Based on https://gist.github.com/ansarizafar/6fa64f44aa933794c4d6638eec32b9aa +# and https://github.com/retifrav/generate-iconset +# We need to create a directory of the icon in several scaled sizes, +# (the Mac command "sips" can do this), then run iconutil to form the +# .icns file. +# kegsicon.png created by Alex Lee + +my $icondir; +my $img_file = ""; +my $ext = ".png"; +my $scale; +my $sz; +my $pixels; +my $scale_str; + +if($#ARGV == 0) { + $img_file = shift; + if($img_file =~ /^.*\.(^\.*)$/) { + $ext = $1; + print "Set ext to $ext\n"; + } +} else { + die "Usage: $0 image_file.jpg/.png" +} + +$icondir = "./icon.iconset"; # Must have .iconset extension +if(-d $icondir) { + `rm -rf $icondir`; +} + +`mkdir $icondir`; +for($scale = 1; $scale <= 2; $scale++) { + for($sz = 16; $sz <= 512; $sz = $sz * 2) { + if($sz == 64) { + next; + } + $pixels = $sz * $scale; + $scale_str = ""; + if($scale == 2) { + $scale_str = '@2x'; + } + @cmd = ("sips", "-z", $pixels, $pixels, $img_file, + "--matchTo", + "/System/Library/ColorSync/Profiles/sRGB\\ Profile.icc", + "--out", $icondir . "/" . "icon_" . $sz . "x" . $sz . + $scale_str . $ext); + print "cmd: @cmd\n"; + `@cmd`; + } +} + +print "Calling: iconutil -o kegs.icns -c icns $icondir"; +`iconutil -o kegs.icns -c icns $icondir`; +`rm -rf $icondir`; + diff --git a/gsplus/src/AppDelegate.swift b/gsplus/src/AppDelegate.swift new file mode 100644 index 0000000..a61a590 --- /dev/null +++ b/gsplus/src/AppDelegate.swift @@ -0,0 +1,365 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2024 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +import Cocoa + +let Context_draw = false + // Default: use safe draw function. + // If true, use NSGraphicsContext.current.data to try to write + // directly to screen memory (in a different ARGB format) + +class Window_info { + var x_win : NSWindow? = nil + var mac_view : MainView? = nil + var kimage_ptr : UnsafeMutablePointer! = nil + var title : String = "" + var app_delegate : AppDelegate! = nil + var mac_a2_height : Int = 0 + +// init(_ new_is_main: Bool) { +// is_main = new_is_main +// } + + func set_kimage(_ kimage_ptr : UnsafeMutablePointer!, + title: String, delegate: AppDelegate!) { + self.kimage_ptr = kimage_ptr + self.title = title + self.app_delegate = delegate + } + + func create_window() { + let x_xpos = Int(video_get_x_xpos(kimage_ptr)) + let x_ypos = Int(video_get_x_ypos(kimage_ptr)) + let width = Int(video_get_x_width(kimage_ptr)) + let height = Int(video_get_x_height(kimage_ptr)) + let windowRect = NSRect(x: x_xpos, y: x_ypos, width: width, + height: height) + var window : NSWindow! + var view : MainView! + let style : NSWindow.StyleMask = [.titled, .closable, + .resizable] + + window = NSWindow(contentRect: windowRect, + styleMask: style, + backing: .buffered, defer: false) + + let viewRect = NSRect(x: 0, y: 0, width: windowRect.size.width, + height: windowRect.size.height) + print("About to init MainView"); + view = MainView(frame: viewRect) + print("About to set kimage_ptr"); + view.kimage_ptr = kimage_ptr; + print("About to call initialize"); + view.initialize() + view.closed = false + + window.delegate = app_delegate + window.contentView = view + window.makeKeyAndOrderFront(app_delegate) + window.acceptsMouseMovedEvents = true + window.title = title + window.showsToolbarButton = true + window.contentAspectRatio = NSSize(width: width, height: height) + + video_set_active(kimage_ptr, Int32(1)) + video_update_scale(kimage_ptr, Int32(width), Int32(height), + Int32(1)) + + x_win = window + mac_view = view + mac_a2_height = height; + window.makeKey() + } + + func update() { + // Decide if window should be opened/closed (if it's the + // debugger window), and call mac_update_display() to update + let new_height = Int(video_get_a2_height(kimage_ptr)) + let a2_active = video_get_active(kimage_ptr) + if let view = mac_view { + if(new_height != mac_a2_height) { + mac_resize_window() + } + if(a2_active == 0 && !view.closed) { + print("a2_active 0 on \(title), calling close") + x_win!.orderOut(x_win) + view.closed = true + } else if(a2_active != 0 && view.closed) { + print("Opening closed window \(title)") + view.closed = false + x_win!.orderFront(x_win) + x_win!.makeKey() // Move to front + } else if(a2_active != 0) { + view.mac_update_display() + } + if((a2_active != 0) && !view.closed) { + if(adb_get_copy_requested() != 0) { + view.do_copy_text(view); + } + } + } else { + if(a2_active != 0) { + print("Opening window \(title)") + create_window() + } + } + } + func mac_resize_window() { + let a2_height = Int(video_get_a2_height(kimage_ptr)) + let a2_width = Int(video_get_a2_width(kimage_ptr)) + let ratio = CGFloat(a2_height) / CGFloat(a2_width) + + let cur_width = x_win!.frame.size.width + let new_height = cur_width * ratio // CGFloat + var newframe = x_win!.frame // NSRect + mac_a2_height = a2_height + newframe.size.height = new_height + x_win!.contentAspectRatio = NSSize(width: a2_width, + height: a2_height) + x_win!.setFrame(newframe, display: true, animate: true) + mac_view!.initialize() + // Must call initialize for the case where the + // status lines were enabled, we need more lines + // print("Call video_update_scale from mac_resize_window\n"); + video_update_scale(kimage_ptr, Int32(x_win!.frame.width), + Int32(x_win!.frame.height), 0) + video_update_xpos_ypos(kimage_ptr, Int32(x_win!.frame.origin.x), + Int32(x_win!.frame.origin.y)) + //print("Did mac_resize window to \(a2_width), \(a2_height)" + + // " frame:\(x_win!.frame.width), " + + // "\(x_win!.frame.height)" + + // " ratio:\(ratio)\n") + } + + func update_window_size(width: Int, height: Int) { + // print("Call video_update_scale from update_window_size\n"); + video_update_scale(kimage_ptr, Int32(width), Int32(height), 0); + } +} + +@main +class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { + + static func main() { + let delegate = AppDelegate() + NSApplication.shared.delegate = delegate + _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) + } + + var mainwin_info = Window_info(); + var debugwin_info = Window_info(); + + func find_win_info(_ window: NSWindow) -> Window_info { + if(mainwin_info.x_win == window) { + return mainwin_info + } + return debugwin_info + } + @objc func do_about(_:AnyObject) { + print("About") + if let ver_str = Bundle.main.infoDictionary?[ + "CFBundleShortVersionString"] as? String { + NSApplication.shared.orderFrontStandardAboutPanel( + options: [.applicationVersion: ver_str, + .version: "", .applicationName: "GS+"]) + } + } + func applicationDidFinishLaunching(_ aNotification: Notification) { + // This is your first real entry point into the app + print("start!") + set_menu_for_kegs() + NSApp.activate(ignoringOtherApps: true) // Bring window to front + main_init() + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + func applicationShouldTerminateAfterLastWindowClosed( + _ theApplication: NSApplication) -> Bool { + // Application will close if main window is closed + return true + } + func windowDidBecomeKey(_ notification: Notification) { + if let w = notification.object as? NSWindow { + if(w == mainwin_info.x_win) { + adb_mainwin_focus(Int32(1)); + //print("Main window became KEY") + } + } + //print("DidbecomeKey") + // If window focus is changing, turn off key repeat + adb_kbd_repeat_off() + } + func windowDidResignKey(_ notification: Notification) { + //print("DidResignKey") + adb_kbd_repeat_off() + adb_mainwin_focus(Int32(0)) + CGDisplayShowCursor(CGMainDisplayID()) + } + func windowDidMove(_ notification: Notification) { + //print("DidMove") + if let w = notification.object as? NSWindow { + let win_info = find_win_info(w) + video_update_xpos_ypos(win_info.kimage_ptr, + Int32(w.frame.origin.x), + Int32(w.frame.origin.y)) + } + } + + func windowWillResize(_ window: NSWindow, to frameSize: NSSize) + -> NSSize { + // print("WILL RESIZE app \(frameSize)") + let width = Int(frameSize.width) + let height = Int(frameSize.height) + let win_info = find_win_info(window) + win_info.update_window_size(width: width, height: height) + return frameSize + } + func windowShouldClose(_ window: NSWindow) -> Bool { + print("windowShouldClose") + let win_info = find_win_info(window) + if(mainwin_info.x_win == window) { + // User has clicked the close box on the main emulator + // window. Just exit the app + NSApp.terminate(window) + return true // Let main window close + } else { + video_set_active(win_info.kimage_ptr, Int32(0)) + win_info.update() + return false + } + } + + func set_menu_for_kegs() { + let appname = "Kegs" + let menu = NSMenu(title: "MainMenu") + NSApp.mainMenu = menu + + print("Installing my menu now") + + // Add "Kegs" menu + let kegs = NSMenu(title: appname) + kegs.addItem(withTitle: "About \(appname)", + action: #selector(AppDelegate.do_about(_:)), + keyEquivalent: "") + kegs.addItem(NSMenuItem.separator()) + let quit_item = NSMenuItem(title: "Quit \(appname)", + action: #selector(NSApplication.terminate(_:)), + keyEquivalent: "q") + quit_item.keyEquivalentModifierMask = [.option, + .command ] + kegs.addItem(quit_item) + let kegs_item = NSMenuItem() + kegs_item.title = appname + kegs_item.submenu = kegs + menu.addItem(kegs_item) + + // Add "Edit" menu + let edit = NSMenu(title: "Edit") + edit.addItem(withTitle: "Copy Text Screen", + action: #selector(MainView.do_copy_text(_:)), + keyEquivalent: "") + edit.addItem(NSMenuItem.separator()) + edit.addItem(withTitle: "Paste", + action: #selector(MainView.do_paste(_:)), + keyEquivalent: "") + let edit_item = NSMenuItem() + edit_item.title = "Edit" + edit_item.submenu = edit + menu.addItem(edit_item) + + // Add "Config" menu + let config = NSMenu(title: "Config") + config.addItem(withTitle: "Configuration F4", + action: #selector(MainView.do_config(_:)), + keyEquivalent: "") + let config_item = NSMenuItem() + config_item.title = "Config" + config_item.submenu = config + menu.addItem(config_item) + + show_menu(menu, depth: 0) + } + + func show_menu(_ menu: NSMenu, depth: Int) { + if(depth >= -10) { + return // HACK: remove to see debug output! + } + print("menu at depth \(depth): \(menu.title)") + if(depth > 5) { // Prevent infinite recursion + return + } + for menuit in menu.items { + print("menuit: depth:\(depth) \(menuit.title)") + print(" keyeq:\(menuit.keyEquivalent)") + print(" modifiers:\(menuit.keyEquivalentModifierMask)") + print(" isSeparator:\(menuit.isSeparatorItem)") + if let sub = menuit.submenu { + show_menu(sub, depth: depth+1) + } + } + } + + var mainWindow : NSWindow! + var mainwin_view : MainView! + + func main_init() { + var kimage_ptr : UnsafeMutablePointer! + var rect = NSRect.zero + + let argc = CommandLine.argc + let argv = CommandLine.unsafeArgv + parse_argv(argc, argv, 3); + + rect.size.width = 2560 + rect.size.height = 1440 + if let screen = NSScreen.main { + rect = screen.frame + } + kegs_init(24, Int32(rect.size.width), Int32(rect.size.height), + Int32(1)) + if(Context_draw) { + video_set_blue_mask(UInt32(0x0000ff)) + video_set_green_mask(UInt32(0x00ff00)) + video_set_red_mask(UInt32(0xff0000)) + } else { + video_set_red_mask(UInt32(0x0000ff)) + video_set_green_mask(UInt32(0x00ff00)) + video_set_blue_mask(UInt32(0xff0000)) + } + video_set_palette() + kimage_ptr = video_get_kimage(Int32(0)) + mainwin_info.set_kimage(kimage_ptr, title: "GS+", + delegate: self) + kimage_ptr = video_get_kimage(Int32(1)) + debugwin_info.set_kimage(kimage_ptr, title: "Debugger", + delegate: self) + + mainwin_info.create_window() + NSApp.activate(ignoringOtherApps: true) + main_run_loop() + } + + func main_run_loop() { + DispatchQueue.main.asyncAfter(deadline: .now() + + .milliseconds(1)) { + self.main_run_loop() + } + let ret = run_16ms() + if(ret != 0) { + exit(ret); + } + mainwin_info.update() + debugwin_info.update() + } +} + diff --git a/gsplus/src/Info.plist b/gsplus/src/Info.plist new file mode 100644 index 0000000..bc67b2b --- /dev/null +++ b/gsplus/src/Info.plist @@ -0,0 +1,52 @@ + + + + + BuildMachineOSBuild + 18G103 + CFBundleDevelopmentRegion + en + CFBundleExecutable + KEGSMAC + CFBundleIconFile + kegs.icns + CFBundleIdentifier + com.provalid.Kegs + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Kegs + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.38 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 11A1027 + DTPlatformVersion + GM + DTSDKBuild + 19A547 + DTSDKName + macosx10.15 + DTXcode + 1110 + DTXcodeBuild + 11A1027 + LSMinimumSystemVersion + 10.13 + NSHumanReadableCopyright + Copyright © 2025 Kent Dickey. All rights reserved. + NSHighResolutionCapable + + NSPrincipalClass + NSApplication + + diff --git a/gsplus/src/Kegs-Bridging-Header.h b/gsplus/src/Kegs-Bridging-Header.h new file mode 100644 index 0000000..7633497 --- /dev/null +++ b/gsplus/src/Kegs-Bridging-Header.h @@ -0,0 +1,5 @@ +// $KmKId: Kegs-Bridging-Header.h,v 1.1 2019-10-14 22:33:09+00 kentd Exp $ +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "defc.h" diff --git a/gsplus/src/MainView.swift b/gsplus/src/MainView.swift new file mode 100644 index 0000000..e134dec --- /dev/null +++ b/gsplus/src/MainView.swift @@ -0,0 +1,412 @@ +// $KmKId: MainView.swift,v 1.42 2024-09-15 13:55:35+00 kentd Exp $ + +// Copyright 2019-2024 by Kent Dickey +// This code is covered by the GNU GPL v3 +// See the file COPYING.txt or https://www.gnu.org/licenses/ +// + +import Cocoa +import CoreGraphics +import AudioToolbox + +class MainView: NSView { + + var bitmapContext : CGContext! + var bitmapData : UnsafeMutableRawPointer! + var rawData : UnsafeMutablePointer! + var current_flags : UInt = 0 + var mouse_moved : Bool = false + var mac_warp_pointer : Int32 = 0 + var mac_hide_pointer : Int32 = 0 + var kimage_ptr : UnsafeMutablePointer! + var closed : Bool = false + var pixels_per_line = 640 + var max_height = 600 + + let is_cmd = UInt(NSEvent.ModifierFlags.command.rawValue) + let is_control = UInt(NSEvent.ModifierFlags.control.rawValue) + let is_shift = NSEvent.ModifierFlags.shift.rawValue + let is_capslock = NSEvent.ModifierFlags.capsLock.rawValue + let is_option = NSEvent.ModifierFlags.option.rawValue + + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + func windowDidResize(_ notification: Notification) { + print("DID RESIZE") + } + override func performKeyEquivalent(with event: NSEvent) -> Bool { + let keycode = event.keyCode + let is_repeat = event.isARepeat + let unicode_key = get_unicode_key_from_event(event) + // print(".performKeyEquiv keycode: \(keycode), is_repeat: " + + // "\(is_repeat)") + if(((current_flags & is_cmd) == 0) || is_repeat) { + // If CMD isn't being held down, just ignore this + return false + } + // Otherwise, manually do down, then up, for this key + adb_physical_key_update(kimage_ptr, Int32(keycode), + UInt32(unicode_key), 0); + adb_physical_key_update(kimage_ptr, Int32(keycode), + UInt32(unicode_key), 1); + return true + } + + override var acceptsFirstResponder: Bool { + return true + } + + func get_unicode_key_from_event(_ event: NSEvent) -> UInt32 { + var unicode_key = UInt32(0); + if let str = event.charactersIgnoringModifiers { + //print(" keydown unmod str: \(str), " + + // "code:\(event.keyCode)") + let arr_chars = Array(str.unicodeScalars) + //print(" arr_chars: \(arr_chars)") + if(arr_chars.count == 1) { + unicode_key = UInt32(arr_chars[0]); + //print("key1:\(unicode_key)") + } + } + return unicode_key + } + override func keyDown(with event: NSEvent) { + let keycode = event.keyCode + let is_repeat = event.isARepeat; + // print(".keyDown code: \(keycode), repeat: \(is_repeat)") + //if let str = event.characters { + // print(" keydown str: \(str), code:\(keycode)") + //} + let unicode_key = get_unicode_key_from_event(event) + if(is_repeat) { + // If we do CMD-E, then we never get a down for the E, + // but we will get repeat events for that E. Let's + // ignore those + return + } + adb_physical_key_update(kimage_ptr, Int32(keycode), + UInt32(unicode_key), 0); + } + + override func keyUp(with event: NSEvent) { + let keycode = event.keyCode + // let is_repeat = event.isARepeat; + // print(".keyUp code: \(keycode), repeat: \(is_repeat)") + let unicode_key = get_unicode_key_from_event(event) + adb_physical_key_update(kimage_ptr, Int32(keycode), + UInt32(unicode_key), 1); + } + + override func flagsChanged(with event: NSEvent) { + let flags = event.modifierFlags.rawValue & + (is_cmd | is_control | is_shift | is_capslock | + is_option) + var c025_val = UInt32(0); + if((flags & is_shift) != 0) { + c025_val |= 1; + } + if((flags & is_control) != 0) { + c025_val |= 2; + } + if((flags & is_capslock) != 0) { + c025_val |= 4; + } + if((flags & is_option) != 0) { + c025_val |= 0x40; + } + if((flags & is_cmd) != 0) { + c025_val |= 0x80; + } + adb_update_c025_mask(kimage_ptr, c025_val, UInt32(0xc7)); + current_flags = flags + //print("flagsChanged: \(flags) and keycode: \(event.keyCode)") + } + override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + // This is to let the first click when changing to this window + // through to the app, I probably don't want this. + return false + } + override func mouseMoved(with event: NSEvent) { + //let type = event.eventNumber + //print(" event type: \(type)") + mac_update_mouse(event: event, buttons_state:0, buttons_valid:0) + + } + override func mouseDragged(with event: NSEvent) { + mac_update_mouse(event: event, buttons_state: 0, + buttons_valid: 0) + } + override func otherMouseDown(with event: NSEvent) { + mac_update_mouse(event: event, buttons_state:2, buttons_valid:2) + } + override func otherMouseUp(with event: NSEvent) { + mac_update_mouse(event: event, buttons_state:0, buttons_valid:2) + } + + override func mouseEntered(with event: NSEvent) { + print("mouse entered") + } + override func rightMouseUp(with event: NSEvent) { + mac_update_mouse(event: event, buttons_state:0, buttons_valid:4) + } + override func rightMouseDown(with event: NSEvent) { + print("Right mouse down") + mac_update_mouse(event: event, buttons_state:4, buttons_valid:4) + } + override func mouseUp(with event: NSEvent) { + // print("Mouse up \(event.locationInWindow.x)," + + // "\(event.locationInWindow.y)") + mac_update_mouse(event: event, buttons_state:0, buttons_valid:1) + } + override func mouseDown(with event: NSEvent) { + //print("Mouse down \(event.locationInWindow.x)," + + // "\(event.locationInWindow.y)") + mac_update_mouse(event: event, buttons_state:1, buttons_valid:1) + } + + func mac_update_mouse(event: NSEvent, buttons_state: Int, + buttons_valid: Int) { + var warp = Int32(0) + var x_width = 0 + var y_height = 0 + var x = Int32(0) + var y = Int32(0) + var do_delta = Int(0) + let hide = adb_get_hide_warp_info(kimage_ptr, &warp) + if(warp != mac_warp_pointer) { + mouse_moved = true + } + mac_warp_pointer = warp + if(mac_hide_pointer != hide) { + mac_hide_pointer(hide: hide) + } + mac_hide_pointer = hide + let location = event.locationInWindow + if(!Context_draw) { + // We're letting the Mac scale the window for us, + // so video_scale_mouse*() doesn't know the scale + // factor, so pass it in + x_width = Int(bounds.size.width); + y_height = Int(bounds.size.height); + } + let raw_x = location.x + let raw_y = bounds.size.height - 1 - location.y + // raw_y is 0 at the top of the window now + x = video_scale_mouse_x(kimage_ptr, Int32(raw_x), + Int32(x_width)) + y = video_scale_mouse_y(kimage_ptr, Int32(raw_y), + Int32(y_height)) + do_delta = 0; + if(mac_warp_pointer != 0) { + do_delta |= 0x1000; // x,y are deltas + x = Int32(event.deltaX) + y = Int32(event.deltaY) + } + let ret = adb_update_mouse(kimage_ptr, x, y, + Int32(buttons_state), + Int32(buttons_valid | do_delta)) + if(ret != 0) { + mouse_moved = true + } + guard let win = window else { + return // No valid window + } + if(mouse_moved) { + var rect1 = NSRect.zero + + // If warp, warp cursor to middle of window. Moving + // the cursor requires absolute screen coordinates, + // where y=0 is the top of the screen. We must convert + // window coords (where y=0 is the bottom of the win). + mouse_moved = false + let warp_x = CGFloat(video_unscale_mouse_x(kimage_ptr, + Int32(A2_WINDOW_WIDTH/2), Int32(x_width))) + let warp_y = CGFloat(video_unscale_mouse_y(kimage_ptr, + Int32(A2_WINDOW_HEIGHT/2), Int32(y_height))) + let scr_height = CGDisplayPixelsHigh(CGMainDisplayID()); + rect1.origin.x = CGFloat(warp_x) + rect1.origin.y = bounds.size.height - 1 - + CGFloat(warp_y) + rect1.size.width = 1; + rect1.size.height = 0; + let screen_rect = win.convertToScreen(rect1) + let screen_rect_y = CGFloat(scr_height) - + screen_rect.origin.y + let cg_loc = CGPoint(x: screen_rect.origin.x, + y: CGFloat(screen_rect_y)) + //print("scr_rect:\(screen_rect)") + if(warp != 0) { + // Warp to middle of the window + CGDisplayMoveCursorToPoint(CGMainDisplayID(), + cg_loc); + } + } + } + + func mac_hide_pointer(hide: Int32) { + // print("Hide called: \(hide)") + if(hide != 0) { + CGDisplayHideCursor(CGMainDisplayID()) + } else { + CGDisplayShowCursor(CGMainDisplayID()) + } + } + func initialize() { + //let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) + print("Initialize view called") + // Get width,height from video.c to handle toggling status lines + let width = Int(video_get_a2_width(kimage_ptr)) + let height = Int(video_get_a2_height(kimage_ptr)) + //if let screen = NSScreen.main { + // let rect = screen.frame + // width = Int(rect.size.width) + // height = Int(rect.size.height) + //} + pixels_per_line = width + max_height = height + //print("pixels_per_line: \(pixels_per_line), " + + // "max_height: \(max_height)") + + let color_space = CGDisplayCopyColorSpace(CGMainDisplayID()) + //let colorSpace = CGColorSpaceCreateDeviceRGB() + bitmapContext = CGContext(data: nil, width: width, + height: height, bitsPerComponent: 8, + bytesPerRow: width * 4, + space: color_space, + bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) + + //CGImageAlphaInfo.noneSkipLast.rawValue + bitmapData = bitmapContext.data! + // Set the intial value of the data to black (0) + bitmapData.initializeMemory(as: UInt32.self, repeating: 0, + count: height * width) + rawData = bitmapData.bindMemory(to: UInt32.self, + capacity: height * width) + //print("Calling video_update_scale from MainViewinitialize()") + let x_width = Int(video_get_x_width(kimage_ptr)) + let x_height = Int(video_get_x_height(kimage_ptr)) + video_update_scale(kimage_ptr, Int32(x_width), Int32(x_height), + Int32(1)) + if(Context_draw) { + video_set_alpha_mask(UInt32(0xff000000)) + // Set video.c alpha mask, since 0 means transparent + } + } + + override func draw(_ dirtyRect: NSRect) { + var rect : Change_rect + //super.draw(dirtyRect) + // Documentation says super.draw not needed... + // Draw the current image buffer to the screen + let context = NSGraphicsContext.current!.cgContext + if(!Context_draw) { + // Safe, simple drawing + let image = bitmapContext.makeImage()! + context.draw(image, in: bounds) + // The above call does the actual copy of + // data to the screen, and can take a while + //print("Draw, bounds:\(bounds), dirtyr:\(dirtyRect)") + } else { + // Unsafe, more direct drawing by peeking into + // NSGraphicsContext.current.data + if let data = context.data { + rect = Change_rect(x:0, y:0, + width:Int32(context.width), + height:Int32(context.height)); + video_update_scale(kimage_ptr, + Int32(context.width), + Int32(context.height), Int32(0)) + video_out_data_scaled(data, kimage_ptr, + Int32(context.bytesPerRow/4), &rect); + video_out_done(kimage_ptr); + print("Did out_data_scaled, rect:\(rect)"); + } + } + } + + @objc func do_config(_ : AnyObject) { + // Create a "virtual" F4 press + //print("do_config") + // Create a keydown for the F4 key (keycode:0x76) + adb_physical_key_update(kimage_ptr, Int32(0x76), 0, 0); + + // and create a keyup for the F4 key (keycode:0x76) + adb_physical_key_update(kimage_ptr, Int32(0x76), 0, 1); + } + + @objc func do_copy_text(_ : AnyObject) { + // print("do_copy"); + //let text_buf_cstr = UnsafeMutablePointer.allocate( + // capacity: 2100); + if let cstr = cfg_text_screen_str() { + let str = String(cString: cstr); + NSPasteboard.general.clearContents(); + NSPasteboard.general.setString(str, + forType: NSPasteboard.PasteboardType.string); + } + } + @objc func do_paste(_ : AnyObject) { + // print("do_paste") + let general = NSPasteboard.general; + guard let str = general.string(forType: .string) else { + print("Cannot paste, nothing in clipboard"); + return + } + //print("str: \(str)") + for raw_c in str.utf8 { + let c = UInt32(raw_c) + let ret = adb_paste_add_buf(c) + if(ret != 0) { + print("Paste too large!") + return; + } + } + } + + func mac_update_display() { + var valid : Int32 + var rect : Change_rect + var dirty_rect = NSRect.zero + + if(Context_draw) { + // We just need to know if there are any changes, + // don't actually do the copies now + valid = video_out_query(kimage_ptr); + if(valid != 0) { + self.setNeedsDisplay(bounds) + print("Needs display"); + } + return; + } + // Otherwise, update rawData in our Bitmap now + rect = Change_rect(x:0, y:0, width:0, height:0) + for i in 0.. ../GSplus.app/Contents/PkgInfo + cp -f Info.plist ../GSplus.app/Contents/ + $(PROJROOT)/lib/make_mac_icon $(PROJROOT)/lib/kegsicon.png + cp -f kegs.icns ../GSplus.app/Contents/Resources/ + touch '../GSplus.app/Icon?' + #cp -f $(PROJROOT)/lib/2mg.icns ../GSplus.app/Contents/Resources/ + #cp -f $(PROJROOT)/lib/525.icns ../GSplus.app/Contents/Resources/ + + +# Linux for X builds: +gsplus-x: $(OBJECTS) $(OBJECTS1) compile_time.o + $(CC) $(CCOPTS) $(LDOPTS) $(OBJECTS) $(OBJECTS1) compile_time.o \ + $(LDFLAGS) -o $(NAME)$(SUFFIX) $(XLIBS) $(EXTRA_LIBS) \ + -lX11 -lXext + mv gsplus .. + +# Cygwin for X builds: +gsplus.exe: $(OBJECTS) $(OBJECTS1) compile_time.o + $(CC) $(CCOPTS) $(LDOPTS) $(OBJECTS) $(OBJECTS1) compile_time.o \ + $(LDFLAGS) -o $(NAME)$(SUFFIX) $(XLIBS) $(EXTRA_LIBS) \ + -lXext -lX11 -lm + mv gsplus.exe .. + +# Mingw32 (native windows) builds: (broken, doesn't work currently) +gspluswin.exe: $(OBJECTS) $(OBJECTS1) compile_time.o + $(CC) $(CCOPTS) $(LDOPTS) $(OBJECTS) $(OBJECTS1) compile_time.o \ + $(LDFLAGS) -o $(NAME)$(SUFFIX) $(EXTRA_LIBS) \ + -lwinmm -lgdi32 -ldsound -lcomctl32 -lws2_32 + mv $(NAME)$(SUFFIX) .. + + +.s.o: + $(AS) -c $(OPTS) -I. $*.s + +.c.o: + $(CC) $(CCOPTS) $(XOPTS) -c $(OPTS) -I. $*.c + +.m.o: + $(CC) $(CCOPTS) $(XOPTS) -c $(OPTS) -I. $*.m + +AppDelegate.o: AppDelegate.swift + sh ./comp_swift -c $(OPTS) -I. -primary-file $*.swift \ + MainView.swift -o $*.o + +MainView.o: MainView.swift + sh ./comp_swift -c $(OPTS) -I. -primary-file $*.swift \ + AppDelegate.swift -o $*.o + +win32.o: win32.rc + windres -o win32.o win32.rc + +.c.proto: + $(KMKROOT)/bin/kmkproto $(XOPTS) $*.c > $*.proto + echo >> $*.proto + +.m.proto: + $(KMKROOT)/bin/kmkproto $(XOPTS) $*.m > $*.proto + echo >> $*.proto + +$(PROTO_OUT): $(PROTO_FILE_LIST) ldvars proto_vars + $(KMKROOT)/bin/kmkproto_head $(PROTO_OUT) tmp_protos.h + cat /dev/null $(PROTO_FILE_LIST) >> tmp_protos.h + rm -f $(PROTO_OUT) + mv tmp_protos.h $(PROTO_OUT) + kmk_cp_if_diff $(PROTO_OUT) .. + rm -f *.proto + + +compile_time.o: $(OBJECTS) $(OBJECTS1) + +include dependency + diff --git a/gsplus/src/adb.c b/gsplus/src/adb.c new file mode 100644 index 0000000..d9bf395 --- /dev/null +++ b/gsplus/src/adb.c @@ -0,0 +1,2264 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2024 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +/* adb_mode bit 3 and bit 2 (faster repeats for arrows and space/del) not done*/ + +#include "defc.h" + +extern int Verbose; +extern word32 g_vbl_count; +extern int g_num_lines_prev_superhires640; +extern int g_num_lines_prev_superhires; +extern int g_rom_version; +extern int g_fast_disk_emul_en; +extern int g_limit_speed; +extern int g_irq_pending; +extern int g_swap_paddles; +extern int g_invert_paddles; +extern int g_joystick_type; +extern int g_config_control_panel; +extern int g_status_enable; +extern dword64 g_cur_dfcyc; + +extern byte *g_slow_memory_ptr; +extern byte *g_memory_ptr; +extern word32 g_mem_size_total; +extern Kimage g_mainwin_kimage; +extern Kimage g_debugwin_kimage; + +enum { + ADB_IDLE = 0, + ADB_IN_CMD, + ADB_SENDING_DATA, +}; + +#define ADB_C027_MOUSE_DATA 0x80 +#define ADB_C027_MOUSE_INT 0x40 +#define ADB_C027_DATA_VALID 0x20 +#define ADB_C027_DATA_INT 0x10 +#define ADB_C027_KBD_VALID 0x08 +#define ADB_C027_KBD_INT 0x04 +#define ADB_C027_MOUSE_COORD 0x02 +#define ADB_C027_CMD_FULL 0x01 + +#define ADB_C027_NEG_MASK ( ~ ( \ + ADB_C027_MOUSE_DATA | ADB_C027_DATA_VALID | \ + ADB_C027_KBD_VALID | ADB_C027_MOUSE_COORD | \ + ADB_C027_CMD_FULL)) + + +int halt_on_all_c027 = 0; + +word32 g_adb_repeat_delay = 45; +word32 g_adb_repeat_rate = 3; +word32 g_adb_repeat_info = 0x23; +word32 g_adb_char_set = 0x0; +word32 g_adb_layout_lang = 0x0; + +word32 g_adb_interrupt_byte = 0; +int g_adb_state = ADB_IDLE; + +word32 g_adb_cmd = (word32)-1; +int g_adb_cmd_len = 0; +int g_adb_cmd_so_far = 0; +word32 g_adb_cmd_data[16]; + +#define MAX_ADB_DATA_PEND 16 + +word32 g_adb_data[MAX_ADB_DATA_PEND]; +int g_adb_data_pending = 0; + +word32 g_c027_val = 0; +word32 g_c025_val = 0; + +byte adb_memory[256]; + +word32 g_adb_mode = 0; /* mode set via set_modes, clear_modes */ + +int g_warp_pointer = 0; +int g_hide_pointer = 0; +int g_unhide_pointer = 0; +int g_adb_copy_requested = 0; + +int g_mouse_a2_x = 0; +int g_mouse_a2_y = 0; +int g_mouse_a2_button = 0; +int g_mouse_fifo_pos = 0; +int g_mouse_raw_x = 0; +int g_mouse_raw_y = 0; + +#define ADB_MOUSE_FIFO 8 + +STRUCT(Mouse_fifo) { + dword64 dfcyc; + int x; + int y; + int buttons; +}; + +Mouse_fifo g_mouse_fifo[ADB_MOUSE_FIFO] = { { 0, 0, 0, 0 } }; + +int g_adb_mouse_valid_data = 0; +int g_adb_mouse_coord = 0; + +#define MAX_KBD_BUF 8 +#define MAX_KBD_PASTE_BUF 32768 + +int g_adb_mainwin_has_focus = 1; +#if defined(__linux__) || defined(_WIN32) +int g_adb_swap_command_option = 1; // Default to swap on Linux/Win +#else +int g_adb_swap_command_option = 0; +#endif +int g_key_down = 0; +int g_hard_key_down = 0; +int g_a2code_down = 0; +int g_kbd_read_no_update = 0; +int g_kbd_chars_buffered = 0; +int g_kbd_buf[MAX_KBD_BUF]; +int g_kbd_paste_rd_pos = 0; +int g_kbd_paste_wr_pos = 0; +byte g_kbd_paste_buf[MAX_KBD_PASTE_BUF]; +word32 g_kbd_paste_last_key = 0; +word32 g_adb_repeat_vbl = 0; + +int g_kbd_dev_addr = 2; /* ADB physical kbd addr */ +int g_mouse_dev_addr = 3; /* ADB physical mouse addr */ + +int g_kbd_ctl_addr = 2; /* ADB microcontroller's kbd addr */ +int g_mouse_ctl_addr = 3; /* ADB ucontroller's mouse addr*/ + /* above are ucontroller's VIEW of where mouse/kbd */ + /* are...if they are moved, mouse/keyboard funcs */ + /* should stop (c025, c000, c024, etc). */ + +word32 g_virtual_key_up[4]; /* bitmask of all possible 128 a2codes */ + /* indicates which keys are up=1 by bit */ +int g_rawa2_to_a2code[128]; + +int g_keypad_key_is_down[10] = { 0 };/* List from 0-9 of which keypad */ + /* keys are currently pressed */ + + +#define SHIFT_DOWN ( (g_c025_val & 0x01) ) +#define CTRL_DOWN ( (g_c025_val & 0x02) ) +#define CAPS_LOCK_DOWN ( (g_c025_val & 0x04) ) +#define OPTION_DOWN ( (g_c025_val & 0x40) ) +#define CMD_DOWN ( (g_c025_val & 0x80) ) + + +#define MAX_ADB_KBD_REG3 16 + +int g_kbd_reg0_pos = 0; +int g_kbd_reg0_data[MAX_ADB_KBD_REG3]; +int g_kbd_reg3_16bit = 0x602; /* also set in adb_reset()! */ + +int g_adb_init = 0; + +/* Format: a2code, ascii if no shift, ascii if shift, ascii if ctl */ +const int g_a2_key_to_ascii[][4] = { + { 0x00, 'a', 'A', 0x01 }, + { 0x01, 's', 'S', 0x13 }, + { 0x02, 'd', 'D', 0x04 }, + { 0x03, 'f', 'F', 0x06 }, + { 0x04, 'h', 'H', 0x08 }, + { 0x05, 'g', 'G', 0x07 }, + { 0x06, 'z', 'Z', 0x1a }, + { 0x07, 'x', 'X', 0x18 }, + + { 0x08, 'c', 'C', 0x03 }, + { 0x09, 'v', 'V', 0x16 }, + { 0x0a, -1, -1, -1 }, + { 0x0b, 'b', 'B', 0x02 }, + { 0x0c, 'q', 'Q', 0x11 }, + { 0x0d, 'w', 'W', 0x17 }, + { 0x0e, 'e', 'E', 0x05 }, + { 0x0f, 'r', 'R', 0x12 }, + + { 0x10, 'y', 'Y', 0x19 }, + { 0x11, 't', 'T', 0x14 }, + { 0x12, '1', '!', -1 }, + { 0x13, '2', '@', 0x00 }, + { 0x14, '3', '#', -1 }, + { 0x15, '4', '$', -1 }, + { 0x16, '6', '^', 0x1e }, + { 0x17, '5', '%', -1 }, + + { 0x18, '=', '+', -1 }, + { 0x19, '9', '(', -1 }, + { 0x1a, '7', '&', -1 }, + { 0x1b, '-', '_', 0x1f }, + { 0x1c, '8', '*', -1 }, + { 0x1d, '0', ')', -1 }, + { 0x1e, ']', '}', 0x1d }, + { 0x1f, 'o', 'O', 0x0f }, + + { 0x20, 'u', 'U', 0x15 }, + { 0x21, '[', '{', 0x1b }, + { 0x22, 'i', 'I', 0x09 }, + { 0x23, 'p', 'P', 0x10 }, + { 0x24, 0x0d, 0x0d, -1 }, /* return */ + { 0x25, 'l', 'L', 0x0c }, + { 0x26, 'j', 'J', 0x0a }, + { 0x27, 0x27, '"', -1 }, /* single quote */ + + { 0x28, 'k', 'K', 0x0b }, + { 0x29, ';', ':', -1 }, + { 0x2a, 0x5c, '|', 0x1c }, /* \, | */ + { 0x2b, ',', '<', -1 }, + { 0x2c, '/', '?', 0x7f }, + { 0x2d, 'n', 'N', 0x0e }, + { 0x2e, 'm', 'M', 0x0d }, + { 0x2f, '.', '>', -1 }, + + { 0x30, 0x09, 0x09, -1 }, /* tab */ + { 0x31, ' ', ' ', -1 }, + { 0x32, '`', '~', -1 }, + { 0x33, 0x7f, 0x7f, -1 }, /* Delete */ + { 0x34, -1, -1, -1 }, + { 0x35, 0x1b, 0x1b, -1 }, /* Esc */ + { 0x36, 0x0200, 0x0200, -1 }, /* control */ + { 0x37, 0x8000, 0x8000, -1 }, /* Command */ + + { 0x38, 0x0100, 0x0100, -1 }, /* shift */ + { 0x39, 0x0400, 0x0400, -1 }, /* caps lock */ + { 0x3a, 0x4000, 0x4000, -1 }, /* Option */ + { 0x3b, 0x08, 0x08, -1 }, /* left */ + { 0x3c, 0x15, 0x15, -1 }, /* right */ + { 0x3d, 0x0a, 0x0a, -1 }, /* down */ + { 0x3e, 0x0b, 0x0b, -1 }, /* up arrow */ + { 0x3f, -1, -1, -1 }, + + { 0x40, -1, -1, -1 }, + { 0x41, 0x102e, 0x102c, -1 }, /* keypad . */ + { 0x42, -1, -1, -1 }, + { 0x43, 0x102a, 0x102a, -1 }, /* keypad * */ + { 0x44, -1, -1, -1 }, + { 0x45, 0x102b, 0x102b, -1 }, /* keypad + */ + { 0x46, -1, -1, -1 }, + { 0x47, 0x1018, 0x1018, -1 }, /* keypad Clear */ + + { 0x48, -1, -1, -1 }, + { 0x49, -1, -1, -1 }, + { 0x4a, -1, -1, -1 }, + { 0x4b, 0x102f, 0x102f, -1 }, /* keypad / */ + { 0x4c, 0x100d, 0x100d, -1 }, /* keypad enter */ + { 0x4d, -1, -1, -1 }, + { 0x4e, 0x102d, 0x102d, -1 }, /* keypad - */ + { 0x4f, -1, -1, -1 }, + + { 0x50, -1, -1, -1 }, + { 0x51, 0x103d, 0x103d, -1 }, /* keypad = */ + { 0x52, 0x1030, 0x1030, -1 }, /* keypad 0 */ + { 0x53, 0x1031, 0x1031, -1 }, /* keypad 1 */ + { 0x54, 0x1032, 0x1032, -1 }, /* keypad 2 */ + { 0x55, 0x1033, 0x1033, -1 }, /* keypad 3 */ + { 0x56, 0x1034, 0x1034, -1 }, /* keypad 4 */ + { 0x57, 0x1035, 0x1035, -1 }, /* keypad 5 */ + + { 0x58, 0x1036, 0x1036, -1 }, /* keypad 6 */ + { 0x59, 0x1037, 0x1037, -1 }, /* keypad 7 */ + { 0x5a, 'a', 'A', 0x01 }, /* probably not necessary */ + { 0x5b, 0x1038, 0x1038, -1 }, /* keypad 8 */ + { 0x5c, 0x1039, 0x1039, -1 }, /* keypad 9 */ + { 0x5d, -1, -1, -1 }, + { 0x5e, -1, -1, -1 }, + { 0x5f, -1, -1, -1 }, + + { 0x60, 0x8005, 0x1060, -1 }, /* F5 */ + { 0x61, 0x8006, 0x1061, -1 }, /* F6 */ + { 0x62, 0x8007, 0x1062, -1 }, /* F7 */ + { 0x63, 0x8003, 0x1063, -1 }, /* F3 */ + { 0x64, 0x8008, 0x1064, -1 }, /* F8 */ + { 0x65, 0x8009, 0x1065, -1 }, /* F9 */ + { 0x66, -1, -1, -1 }, + { 0x67, 0x800b, 0x1067, -1 }, /* F11 */ + + { 0x68, -1, -1, -1 }, + { 0x69, 0x800d, 0x1069, -1 }, /* F13 */ + { 0x6a, -1, -1, -1 }, + { 0x6b, 0x800e, 0x106b, -1 }, /* F14 */ + { 0x6c, -1, -1, -1 }, + { 0x6d, 0x800a, 0x106d, -1 }, /* F10 */ + { 0x6e, 0x4000, 0x4000, -1 }, /* windows key alias to option */ + { 0x6f, 0x800c, 0x106f, -1 }, /* F12 */ + + { 0x70, -1, -1, -1 }, + { 0x71, 0x800f, 0x1071, -1 }, /* F15 */ + { 0x72, 0x1072, 0x1072, -1 }, /* Help, insert */ + { 0x73, 0x1073, 0x1073, -1 }, /* Home */ + { 0x74, 0x1074, 0x1074, -1 }, /* Page up */ + { 0x75, 0x1075, 0x1075, -1 }, /* keypad delete */ + { 0x76, 0x8004, 0x1076, -1 }, /* F4 */ + { 0x77, 0x1077, 0x1077, -1 }, /* keypad end */ + + { 0x78, 0x8002, 0x1078, -1 }, /* F2 */ + { 0x79, 0x1079, 0x1079, -1 }, /* keypad page down */ + { 0x7a, 0x8001, 0x107a, -1 }, /* F1 */ + { 0x7b, 0x08, 0x08, -1 }, /* left */ /* remapped to 0x3b */ + { 0x7c, 0x15, 0x15, -1 }, /* right */ /* remapped to 0x3c */ + { 0x7d, 0x0a, 0x0a, -1 }, /* down */ /* remapped to 0x3d */ + { 0x7e, 0x0b, 0x0b, -1 }, /* up arrow */ /* remapped to 0x3e */ + { 0x7f, -1, -1, -1 }, /* Reset */ +}; + +int +adb_get_hide_warp_info(Kimage *kimage_ptr, int *warpptr) +{ + if((kimage_ptr == &g_mainwin_kimage) && g_adb_mainwin_has_focus) { + *warpptr = g_warp_pointer; + return g_hide_pointer; + } + *warpptr = 0; + return 0; +} + +int +adb_get_copy_requested() +{ + int ret; + + ret = g_adb_copy_requested; + g_adb_copy_requested = 0; + return ret; +} + +void +adb_nonmain_check() +{ + // Debug window active. Undo F8 pointer warping + g_warp_pointer = 0; + g_hide_pointer = 0; +} + +void +adb_init() +{ + int keycode; + int i; + + if(g_adb_init) { + halt_printf("g_adb_init = %d!\n", g_adb_init); + } + g_adb_init = 1; + + for(i = 0; i < 128; i++) { + keycode = g_a2_key_to_ascii[i][0]; + if(keycode != i) { + printf("ADB keycode lost/skipped: i=%x: keycode=%x\n", + i, keycode); + my_exit(1); + } + g_rawa2_to_a2code[i] = -1; + } + + g_c025_val = 0; + + for(i = 0; i < 4; i++) { + g_virtual_key_up[i] = -1; + } + + for(i = 0; i < 10; i++) { + g_keypad_key_is_down[i] = 0; + } +} + +void +adb_reset() +{ + g_c027_val = 0; + + g_key_down = 0; + g_kbd_paste_rd_pos = 0; + g_kbd_paste_wr_pos = 0; + g_kbd_chars_buffered = 0; + + g_kbd_dev_addr = 2; + g_mouse_dev_addr = 3; + + g_kbd_ctl_addr = 2; + g_mouse_ctl_addr = 3; + + adb_clear_data_int(); + adb_clear_mouse_int(); + adb_clear_kbd_srq(); + + g_adb_data_pending = 0; + g_adb_interrupt_byte = 0; + g_adb_state = ADB_IDLE; + g_adb_mouse_coord = 0; + g_adb_mouse_valid_data = 0; + + g_kbd_reg0_pos = 0; + g_kbd_reg3_16bit = 0x602; +} + +#define LEN_ADB_LOG 16 +STRUCT(Adb_log) { + word32 addr; + int val; + int state; +}; + +Adb_log g_adb_log[LEN_ADB_LOG]; +int g_adb_log_pos = 0; + +void +adb_log(word32 addr, int val) +{ + int pos; + + pos = g_adb_log_pos; + g_adb_log[pos].addr = addr; + g_adb_log[pos].val = val; + g_adb_log[pos].state = g_adb_state; + pos++; + if(pos >= LEN_ADB_LOG) { + pos = 0; + } + g_adb_log_pos = pos; +} + +void +show_adb_log(void) +{ + int pos; + int i; + + pos = g_adb_log_pos; + printf("ADB log pos: %d\n", pos); + for(i = 0; i < LEN_ADB_LOG; i++) { + pos--; + if(pos < 0) { + pos = LEN_ADB_LOG - 1; + } + printf("%d:%d: addr:%04x = %02x, st:%d\n", i, pos, + g_adb_log[pos].addr, g_adb_log[pos].val, + g_adb_log[pos].state); + } + printf("kbd: dev: %x, ctl: %x; mouse: dev: %x, ctl: %x\n", + g_kbd_dev_addr, g_kbd_ctl_addr, + g_mouse_dev_addr, g_mouse_ctl_addr); + printf("g_adb_state: %d, g_adb_interrupt_byte: %02x\n", + g_adb_state, g_adb_interrupt_byte); +} + +void +adb_error(void) +{ + halt_printf("Adb Error\n"); + + show_adb_log(); +} + + + +void +adb_add_kbd_srq() +{ + if(g_kbd_reg3_16bit & 0x200) { + /* generate SRQ */ + g_adb_interrupt_byte |= 0x08; + add_irq(IRQ_PENDING_ADB_KBD_SRQ); + } else { + printf("Got keycode but no kbd SRQ!\n"); + } +} + +void +adb_clear_kbd_srq() +{ + remove_irq(IRQ_PENDING_ADB_KBD_SRQ); + + /* kbd SRQ's are the only ones to handle now, so just clean it out */ + g_adb_interrupt_byte &= (~(0x08)); +} + +void +adb_add_data_int() +{ + if(g_c027_val & ADB_C027_DATA_INT) { + add_irq(IRQ_PENDING_ADB_DATA); + } +} + +void +adb_add_mouse_int() +{ + if(g_c027_val & ADB_C027_MOUSE_INT) { + add_irq(IRQ_PENDING_ADB_MOUSE); + } +} + +void +adb_clear_data_int() +{ + remove_irq(IRQ_PENDING_ADB_DATA); +} + +void +adb_clear_mouse_int() +{ + remove_irq(IRQ_PENDING_ADB_MOUSE); +} + + +void +adb_send_bytes(int num_bytes, word32 val0, word32 val1, word32 val2) +{ + word32 val; + int shift_amount; + int i; + + if((num_bytes >= 12) || (num_bytes >= MAX_ADB_DATA_PEND)) { + halt_printf("adb_send_bytes: %d is too many!\n", num_bytes); + } + + g_adb_state = ADB_SENDING_DATA; + g_adb_data_pending = num_bytes; + adb_add_data_int(); + + for(i = 0; i < num_bytes; i++) { + if(i < 4) { + val = val0; + } else if(i < 8) { + val = val1; + } else { + val = val2; + } + + shift_amount = 8*(3 - i); + g_adb_data[i] = (val >> shift_amount) & 0xff; + adb_printf("adb_send_bytes[%d] = %02x\n", i, g_adb_data[i]); + } +} + + +void +adb_send_1byte(word32 val) +{ + + if(g_adb_data_pending != 0) { + halt_printf("g_adb_data_pending: %d\n", g_adb_data_pending); + } + + adb_send_bytes(1, val << 24, 0, 0); +} + +void +adb_response_packet(int num_bytes, word32 val) +{ + + if(g_adb_data_pending != 0) { + halt_printf("adb_response_packet, but pending: %d\n", + g_adb_data_pending); + } + + g_adb_state = ADB_IDLE; + g_adb_data_pending = num_bytes; + g_adb_data[0] = val & 0xff; + g_adb_data[1] = (val >> 8) & 0xff; + g_adb_data[2] = (val >> 16) & 0xff; + g_adb_data[3] = (val >> 24) & 0xff; + if(num_bytes) { + g_adb_interrupt_byte |= 0x80 + num_bytes - 1; + } else { + g_adb_interrupt_byte |= 0x80; + } + + adb_printf("adb_response packet: %d: %08x\n", + num_bytes, val); + + adb_add_data_int(); +} + +void +adb_kbd_reg0_data(int a2code, int is_up) +{ + if(g_kbd_reg0_pos >= MAX_ADB_KBD_REG3) { + /* too many keys, toss */ + halt_printf("Had to toss key: %02x, %d\n", a2code, is_up); + return; + } + + g_kbd_reg0_data[g_kbd_reg0_pos] = a2code + (is_up << 7); + + adb_printf("g_kbd_reg0_data[%d] = %02x\n", g_kbd_reg0_pos, + g_kbd_reg0_data[g_kbd_reg0_pos]); + + g_kbd_reg0_pos++; + + adb_add_kbd_srq(); +} + +void +adb_kbd_talk_reg0() +{ + word32 val0, val1, reg; + int num_bytes, num; + int i; + + num = 0; + val0 = g_kbd_reg0_data[0]; + val1 = g_kbd_reg0_data[1]; + + num_bytes = 0; + if(g_kbd_reg0_pos > 0) { + num_bytes = 2; + num = 1; + if((val0 & 0x7f) == 0x7f) { + /* reset */ + val1 = val0; + } else if(g_kbd_reg0_pos > 1) { + num = 2; + if((val1 & 0x7f) == 0x7f) { + /* If first byte some other key, don't */ + /* put RESET next! */ + num = 1; + val1 = 0xff; + } + } else { + val1 = 0xff; + } + } + + if(num) { + for(i = num; i < g_kbd_reg0_pos; i++) { + g_kbd_reg0_data[i-1] = g_kbd_reg0_data[i]; + } + g_kbd_reg0_pos -= num; + } + + reg = (val0 << 8) + val1; + + adb_printf("adb_kbd_talk0: %04x\n", reg); + + adb_response_packet(num_bytes, reg); + if(g_kbd_reg0_pos == 0) { + adb_clear_kbd_srq(); + } +} + +void +adb_set_config(word32 val0, word32 val1, word32 val2) +{ + int new_mouse; + int new_kbd; + int tmp1; + + new_mouse = val0 >> 4; + new_kbd = val0 & 0xf; + if(new_mouse != g_mouse_ctl_addr) { + printf("ADB config: mouse from %x to %x!\n", + g_mouse_ctl_addr, new_mouse); + adb_error(); + g_mouse_ctl_addr = new_mouse; + } + if(new_kbd != g_kbd_ctl_addr) { + printf("ADB config: kbd from %x to %x!\n", + g_kbd_ctl_addr, new_kbd); + adb_error(); + g_kbd_ctl_addr = new_kbd; + } + + if(val1) { + // Do nothing + } + + tmp1 = val2 >> 4; + if(tmp1 == 4) { + g_adb_repeat_delay = 0; + } else if(tmp1 < 4) { + g_adb_repeat_delay = (tmp1 + 1) * 15; + } else { + halt_printf("Bad ADB repeat delay: %02x\n", tmp1); + } + + tmp1 = val2 & 0xf; + if(g_rom_version >= 3) { + tmp1 = 9 - tmp1; + } + + switch(tmp1) { + case 0: + g_adb_repeat_rate = 1; + break; + case 1: + g_adb_repeat_rate = 2; + break; + case 2: + g_adb_repeat_rate = 3; + break; + case 3: + g_adb_repeat_rate = 3; + break; + case 4: + g_adb_repeat_rate = 4; + break; + case 5: + g_adb_repeat_rate = 5; + break; + case 6: + g_adb_repeat_rate = 7; + break; + case 7: + g_adb_repeat_rate = 15; + break; + case 8: + /* I don't know what this should be, ROM 03 uses it */ + g_adb_repeat_rate = 30; + break; + case 9: + /* I don't know what this should be, ROM 03 uses it */ + g_adb_repeat_rate = 60; + break; + default: + halt_printf("Bad repeat rate: %02x\n", tmp1); + } + +} + +void +adb_set_new_mode(word32 val) +{ + if(val & 0x03) { + printf("Disabling keyboard/mouse:%02x!\n", val); + } + + if(val & 0xa2) { + halt_printf("ADB set mode: %02x!\n", val); + adb_error(); + } + + g_adb_mode = val; +} + + +int +adb_read_c026() +{ + word32 ret; + int i; + + ret = 0; + switch(g_adb_state) { + case ADB_IDLE: + ret = g_adb_interrupt_byte; + g_adb_interrupt_byte = 0; + if(g_irq_pending & IRQ_PENDING_ADB_KBD_SRQ) { + g_adb_interrupt_byte |= 0x08; + } + if(g_adb_data_pending == 0) { + if(ret & 0x80) { + halt_printf("read_c026: ret:%02x, pend:%d\n", + ret, g_adb_data_pending); + } + adb_clear_data_int(); + } + if(g_adb_data_pending) { + if(g_adb_state != ADB_IN_CMD) { + g_adb_state = ADB_SENDING_DATA; + } + } + break; + case ADB_IN_CMD: + ret = 0; + break; + case ADB_SENDING_DATA: + ret = g_adb_data[0]; + for(i = 1; i < g_adb_data_pending; i++) { + g_adb_data[i-1] = g_adb_data[i]; + } + g_adb_data_pending--; + if(g_adb_data_pending <= 0) { + g_adb_data_pending = 0; + g_adb_state = ADB_IDLE; + adb_clear_data_int(); + } + break; + default: + halt_printf("Bad ADB state: %d!\n", g_adb_state); + adb_clear_data_int(); + break; + } + + adb_printf("Reading c026. Returning %02x, st: %02x, pend: %d\n", + ret, g_adb_state, g_adb_data_pending); + + adb_log(0xc026, ret); + return (ret & 0xff); +} + + +void +adb_write_c026(int val) +{ + word32 tmp; + int dev; + + adb_printf("Writing c026 with %02x\n", val); + adb_log(0x1c026, val); + + + switch(g_adb_state) { + case ADB_IDLE: + g_adb_cmd = val; + g_adb_cmd_so_far = 0; + g_adb_cmd_len = 0; + + dev = val & 0xf; + switch(val) { + case 0x01: /* Abort */ + adb_printf("Performing adb abort\n"); + /* adb_abort() */ + break; + case 0x03: /* Flush keyboard buffer */ + adb_printf("Flushing adb keyboard buffer\n"); + /* Do nothing */ + break; + case 0x04: /* Set modes */ + adb_printf("ADB set modes\n"); + g_adb_state = ADB_IN_CMD; + g_adb_cmd_len = 1; + break; + case 0x05: /* Clear modes */ + adb_printf("ADB clear modes\n"); + g_adb_state = ADB_IN_CMD; + g_adb_cmd_len = 1; + break; + case 0x06: /* Set config */ + adb_printf("ADB set config\n"); + g_adb_state = ADB_IN_CMD; + g_adb_cmd_len = 3; + break; + case 0x07: /* Sync */ + adb_printf("Performing sync cmd!\n"); + g_adb_state = ADB_IN_CMD; + if(g_rom_version == 1) { + g_adb_cmd_len = 4; + } else { + g_adb_cmd_len = 8; + } + break; + case 0x08: /* Write mem */ + adb_printf("Starting write_mem cmd\n"); + g_adb_state = ADB_IN_CMD; + g_adb_cmd_len = 2; + break; + case 0x09: /* Read mem */ + adb_printf("Performing read_mem cmd!\n"); + g_adb_state = ADB_IN_CMD; + g_adb_cmd_len = 2; + break; + case 0x0a: /* Read modes byte */ + printf("Performing read_modes cmd!\n"); + /* set_halt(1); */ + adb_send_1byte(g_adb_mode); + break; + case 0x0b: /* Read config bytes */ + printf("Performing read_configs cmd!\n"); + tmp = (g_mouse_ctl_addr << 20) + + (g_kbd_ctl_addr << 16) + + (g_adb_char_set << 12) + + (g_adb_layout_lang << 8) + + (g_adb_repeat_info << 0); + tmp = (0x82U << 24) + tmp; + adb_send_bytes(4, tmp, 0, 0); + break; + case 0x0d: /* Get Version */ + adb_printf("Performing get_version cmd!\n"); + val = 0; + if(g_rom_version == 1) { + /* ROM 01 = revision 5 */ + val = 5; + } else { + /* ROM 03 checks for rev >= 6 */ + val = 6; + } + adb_send_1byte(val); + break; + case 0x0e: /* Read avail char sets */ + adb_printf("Performing read avail char sets cmd!\n"); + adb_send_bytes(2, /* just 2 bytes */ + 0x08000000, /* number of ch sets=0x8 */ + 0, 0); + /* set_halt(1); */ + break; + case 0x0f: /* Read avail kbd layouts */ + adb_printf("Performing read avail kbd layouts cmd!\n"); + adb_send_bytes(0x2, /* number of kbd layouts=0xa */ + 0x0a000000, 0, 0); + /* set_halt(1); */ + break; + case 0x10: /* Reset */ + printf("ADB reset, cmd 0x10\n"); + do_reset(); + break; + case 0x11: /* Send ADB keycodes */ + adb_printf("Sending ADB keycodes\n"); + g_adb_state = ADB_IN_CMD; + g_adb_cmd_len = 1; + break; + case 0x12: /* ADB cmd 12: ROM 03 only! */ + if(g_rom_version >= 3) { + g_adb_state = ADB_IN_CMD; + g_adb_cmd_len = 2; + } else { + printf("ADB cmd 12, but not ROM 3!\n"); + adb_error(); + } + break; + case 0x13: /* ADB cmd 13: ROM 03 only! */ + if(g_rom_version >= 3) { + g_adb_state = ADB_IN_CMD; + g_adb_cmd_len = 2; + } else { + printf("ADB cmd 13, but not ROM 3!\n"); + adb_error(); + } + break; + case 0x73: /* Disable SRQ device 3: mouse */ + adb_printf("Disabling Mouse SRQ's (device 3)\n"); + /* HACK HACK...should deal with SRQs on mouse */ + break; + case 0xb0: case 0xb1: case 0xb2: case 0xb3: + case 0xb4: case 0xb5: case 0xb6: case 0xb7: + case 0xb8: case 0xb9: case 0xba: case 0xbb: + case 0xbc: case 0xbd: case 0xbe: case 0xbf: + /* Listen dev x reg 3 */ + adb_printf("Sending data to dev %x reg 3\n", dev); + g_adb_state = ADB_IN_CMD; + g_adb_cmd_len = 2; + break; + case 0xc0: case 0xc1: case 0xc2: case 0xc3: + case 0xc4: case 0xc5: case 0xc6: case 0xc7: + case 0xc8: case 0xc9: case 0xca: case 0xcb: + case 0xcc: case 0xcd: case 0xce: case 0xcf: + /* Talk dev x reg 0 */ + adb_printf("Performing talk dev %x reg 0\n", dev); + if(dev == g_kbd_dev_addr) { + adb_kbd_talk_reg0(); + } else { + printf("Unknown talk dev %x reg 0!\n", dev); + /* send no data, on SRQ, system polls devs */ + /* so we don't want to send anything */ + adb_error(); + } + break; + case 0xf0: case 0xf1: case 0xf2: case 0xf3: + case 0xf4: case 0xf5: case 0xf6: case 0xf7: + case 0xf8: case 0xf9: case 0xfa: case 0xfb: + case 0xfc: case 0xfd: case 0xfe: case 0xff: + /* Talk dev x reg 3 */ + adb_printf("Performing talk dev %x reg 3\n", dev); + if(dev == g_kbd_dev_addr) { + adb_response_packet(2, g_kbd_reg3_16bit); + } else { + printf("Performing talk dev %x reg 3!!\n", dev); + adb_error(); + } + break; + default: + halt_printf("ADB ucontroller cmd %02x unknown!\n", val); + /* The Gog's says ACS Demo 2 has a bug and writes to */ + /* c026 */ + break; + } + break; + case ADB_IN_CMD: + adb_printf("Setting byte %d of cmd %02x to %02x\n", + g_adb_cmd_so_far, g_adb_cmd, val); + + g_adb_cmd_data[g_adb_cmd_so_far] = val; + g_adb_cmd_so_far++; + if(g_adb_cmd_so_far >= g_adb_cmd_len) { + adb_printf("Finished cmd %02x\n", g_adb_cmd); + do_adb_cmd(); + } + + break; + default: + printf("adb_state: %02x is unknown! Setting it to ADB_IDLE\n", + g_adb_state); + g_adb_state = ADB_IDLE; + adb_error(); + halt_on_all_c027 = 1; + break; + } + return; +} + +void +do_adb_cmd() +{ + word32 val; + int dev, new_kbd, addr; + + dev = g_adb_cmd & 0xf; + + g_adb_state = ADB_IDLE; + + switch(g_adb_cmd) { + case 0x04: /* Set modes */ + adb_printf("Performing ADB set mode: OR'ing in %02x\n", + g_adb_cmd_data[0]); + + val = g_adb_cmd_data[0] | g_adb_mode; + adb_set_new_mode(val); + + break; + case 0x05: /* clear modes */ + adb_printf("Performing ADB clear mode: AND'ing in ~%02x\n", + g_adb_cmd_data[0]); + + val = g_adb_cmd_data[0]; + val = g_adb_mode & (~val); + adb_set_new_mode(val); + break; + case 0x06: /* Set config */ + adb_printf("Set ADB config to %02x %02x %02x\n", + g_adb_cmd_data[0], g_adb_cmd_data[1],g_adb_cmd_data[2]); + + adb_set_config(g_adb_cmd_data[0], g_adb_cmd_data[1], + g_adb_cmd_data[2]); + + break; + case 0x07: /* SYNC */ + adb_printf("Performing ADB SYNC\n"); + adb_printf("data: %02x %02x %02x %02x\n", + g_adb_cmd_data[0], g_adb_cmd_data[1], g_adb_cmd_data[2], + g_adb_cmd_data[3]); + + adb_set_new_mode(g_adb_cmd_data[0]); + adb_set_config(g_adb_cmd_data[1], g_adb_cmd_data[2], + g_adb_cmd_data[3]); + + if(g_rom_version >= 3) { + adb_printf(" and cmd12:%02x %02x cmd13:%02x %02x\n", + g_adb_cmd_data[4], g_adb_cmd_data[5], + g_adb_cmd_data[6], g_adb_cmd_data[7]); + } + break; + case 0x08: /* Write mem */ + addr = g_adb_cmd_data[0]; + val = g_adb_cmd_data[1]; + write_adb_ram(addr, val); + break; + case 0x09: /* Read mem */ + addr = (g_adb_cmd_data[1] << 8) + g_adb_cmd_data[0]; + adb_printf("Performing mem read to addr %04x\n", addr); + adb_send_1byte(read_adb_ram(addr)); + break; + case 0x11: /* Send ADB keycodes */ + val = g_adb_cmd_data[0]; + adb_printf("Performing send ADB keycodes: %02x\n", val); + adb_virtual_key_update(val & 0x7f, val >> 7); + break; + case 0x12: /* ADB cmd12 */ + adb_printf("Performing ADB cmd 12\n"); + adb_printf("data: %02x %02x\n", g_adb_cmd_data[0], + g_adb_cmd_data[1]); + break; + case 0x13: /* ADB cmd13 */ + adb_printf("Performing ADB cmd 13\n"); + adb_printf("data: %02x %02x\n", g_adb_cmd_data[0], + g_adb_cmd_data[1]); + break; + case 0xb0: case 0xb1: case 0xb2: case 0xb3: + case 0xb4: case 0xb5: case 0xb6: case 0xb7: + case 0xb8: case 0xb9: case 0xba: case 0xbb: + case 0xbc: case 0xbd: case 0xbe: case 0xbf: + /* Listen dev x reg 3 */ + if(dev == g_kbd_dev_addr) { + if(g_adb_cmd_data[1] == 0xfe) { + /* change keyboard addr? */ + new_kbd = g_adb_cmd_data[0] & 0xf; + if(new_kbd != dev) { + printf("Moving kbd to dev %x!\n", + new_kbd); + adb_error(); + } + g_kbd_dev_addr = new_kbd; + } else if(g_adb_cmd_data[1] != 1) { + /* see what new device handler id is */ + printf("KBD listen to dev %x reg 3: 1:%02x\n", + dev, g_adb_cmd_data[1]); + adb_error(); + } + if(g_adb_cmd_data[0] != (word32)g_kbd_dev_addr) { + /* see if app is trying to change addr */ + printf("KBD listen to dev %x reg 3: 0:%02x!\n", + dev, g_adb_cmd_data[0]); + adb_error(); + } + g_kbd_reg3_16bit = ((g_adb_cmd_data[0] & 0xf) << 12) + + (g_kbd_reg3_16bit & 0x0fff); + } else if(dev == g_mouse_dev_addr) { + if(g_adb_cmd_data[0] != (word32)dev) { + /* see if app is trying to change mouse addr */ + printf("MOUS listen to dev %x reg3: 0:%02x!\n", + dev, g_adb_cmd_data[0]); + adb_error(); + } + if(g_adb_cmd_data[1] != 1 && g_adb_cmd_data[1] != 2) { + /* see what new device handler id is */ + printf("MOUS listen to dev %x reg 3: 1:%02x\n", + dev, g_adb_cmd_data[1]); + adb_error(); + } + } else { + printf("Listen cmd to dev %x reg3????\n", dev); + printf("data0: %02x, data1: %02x ????\n", + g_adb_cmd_data[0], g_adb_cmd_data[1]); + adb_error(); + } + break; + default: + printf("Doing adb_cmd %02x: UNKNOWN!\n", g_adb_cmd); + break; + } +} + + +int +adb_read_c027() +{ + word32 ret; + + if(halt_on_all_c027) { + halt_printf("halting on all c027 reads!\n"); + } + + if(g_c027_val & (~ADB_C027_NEG_MASK)) { + halt_printf("read_c027: g_c027_val: %02x\n", g_c027_val); + } + + ret = (g_c027_val & ADB_C027_NEG_MASK); + + if(g_adb_mouse_valid_data) { + ret |= ADB_C027_MOUSE_DATA; + } + + if(g_adb_interrupt_byte != 0) { + ret |= ADB_C027_DATA_VALID; + } else if(g_adb_data_pending > 0) { + if((g_adb_state != ADB_IN_CMD)) { + ret |= ADB_C027_DATA_VALID; + } + } + + if(g_adb_mouse_coord) { + ret |= ADB_C027_MOUSE_COORD; + } + +#if 0 + adb_printf("Read c027: %02x, int_byte: %02x, d_pend: %d\n", + ret, g_adb_interrupt_byte, g_adb_data_pending); +#endif + +#if 0 + adb_log(0xc027, ret); +#endif + return ret; +} + +void +adb_write_c027(int val) +{ + word32 old_val; + word32 new_int; + word32 old_int; + + adb_printf("Writing c027 with %02x\n", val); + adb_log(0x1c027, val); + + + old_val = g_c027_val; + + g_c027_val = (val & ADB_C027_NEG_MASK); + new_int = g_c027_val & ADB_C027_MOUSE_INT; + old_int = old_val & ADB_C027_MOUSE_INT; + if(!new_int && old_int) { + adb_clear_mouse_int(); + } + + new_int = g_c027_val & ADB_C027_DATA_INT; + old_int = old_val & ADB_C027_DATA_INT; + if(!new_int && old_int) { + /* ints were on, now off */ + adb_clear_data_int(); + } + + if(g_c027_val & ADB_C027_KBD_INT) { + halt_printf("Can't support kbd interrupts!\n"); + } + + return; +} + +int +read_adb_ram(word32 addr) +{ + int val; + + adb_printf("Reading adb ram addr: %02x\n", addr); + + if(addr >= 0x100) { + if(addr >= 0x1000 && addr < 0x2000) { + /* ROM self-test checksum */ + if(addr == 0x1400) { + val = 0x72; + } else if(addr == 0x1401) { + val = 0xf7; + } else { + val = 0; + } + } else { + printf("adb ram addr out of range: %04x!\n", addr); + val = 0; + } + } else { + val = adb_memory[addr]; + if((addr == 0xb) && (g_rom_version == 1)) { + // read special key state byte for Out of This World + val = (g_c025_val >> 1) & 0x43; + val |= (g_c025_val << 2) & 0x4; + val |= (g_c025_val >> 2) & 0x10; + } + if((addr == 0xc) && (g_rom_version >= 3)) { + // read special key state byte for Out of This World + val = g_c025_val & 0xc7; + printf("val is %02x\n", val); + } + } + + adb_printf("adb_ram returning %02x\n", val); + return val; +} + +void +write_adb_ram(word32 addr, int val) +{ + + adb_printf("Writing adb_ram addr: %02x: %02x\n", addr, val); + + if(addr >= 0x100) { + printf("write adb_ram addr: %02x: %02x!\n", addr, val); + adb_error(); + } else { + adb_memory[addr] = val; + } +} + +int +adb_get_keypad_xy(int get_y) +{ + int x, y, key, num_keys; + int i, j; + + key = 1; + num_keys = 0; + x = 0; + y = 0; + for(i = 0; i < 3; i++) { + for(j = 0; j < 3; j++) { + if(g_keypad_key_is_down[key]) { + num_keys++; + x = x + (j - 1)*32768; + y = y + (1 - i)*32768; + } + key++; + } + } + if(num_keys == 0) { + num_keys = 1; + } + + adb_printf("get_xy=%d, num_keys: %d, x:%d, y:%d\n", get_y, + num_keys, x, y); + + if(get_y) { + return y / num_keys; + } else { + return x / num_keys; + } +} + +// g_mouse_raw_x/y: Current position (in A2 coordinates) of mouse on host screen +// g_mouse_fifo[0].x/y: Current position (in A2 coords) of where we "want" +// mouse on the A2 screen. +// g_mouse_a2_x/y: last x,y returned through $c024 to software. +// So, reading $c024 return g_mouse_fifo[].x - g_mouse_a2_x. +// And, in simple cases, host mouse movement just sets g_mouse_fifo[0].x=raw_x +int +adb_update_mouse(Kimage *kimage_ptr, int x, int y, int button_states, + int buttons_valid) +{ + dword64 dfcyc; + int button1_changed, mouse_moved, unhide, pos; + int i; + + if(kimage_ptr != &g_mainwin_kimage) { + adb_nonmain_check(); + } + dfcyc = g_cur_dfcyc; + + unhide = (g_adb_mainwin_has_focus == 0); + if((buttons_valid >= 0) && (buttons_valid & 0x1000)) { + // x, y are really deltas + buttons_valid &= 0xfff; + x = g_mouse_raw_x + x; + y = g_mouse_raw_y + y; + g_mouse_raw_x = x; + g_mouse_raw_y = y; + } else { + g_mouse_raw_x = x; + g_mouse_raw_y = y; + + // Clamp mouse to 0-639, 0-399 to make GSOS work nicely + if(x < 0) { + x = 0; + unhide = 1; + } + if(x >= 640) { + x = 639; + unhide = 1; + } + if(y < 0) { + y = 0; + unhide = 1; + } + if(y >= 400) { + y = 399; + unhide = 1; + } + } + + g_unhide_pointer = unhide && !g_warp_pointer; + + if(kimage_ptr != &g_mainwin_kimage) { + // In debugger window...just get out + return 0; + } + + if(!g_warp_pointer) { + if(g_hide_pointer && g_unhide_pointer) { + /* cursor has left a2 window, show it */ + g_hide_pointer = 0; + } + if((g_num_lines_prev_superhires == 200) && + (g_num_lines_prev_superhires640 == 0) && + ((g_slow_memory_ptr[0x19d00] & 0x80) == 0)) { + // In 320-mode superhires, cut mouse range in half + x = x >> 1; + } + y = y >> 1; + } + + mouse_compress_fifo(dfcyc); + +#if 0 + printf("Update Mouse called with buttons:%d x,y:%d,%d, fifo:%d,%d, " + " a2: %d,%d\n", buttons_valid, x, y, + g_mouse_fifo[0].x, g_mouse_fifo[0].y, + g_mouse_a2_x, g_mouse_a2_y); +#endif + + if((buttons_valid < 0) && g_warp_pointer) { + /* Warping the pointer causes it to jump here...this is not */ + /* real motion, just update info and get out */ + g_mouse_a2_x += (x - g_mouse_fifo[0].x); + g_mouse_a2_y += (y - g_mouse_fifo[0].y); + g_mouse_fifo[0].x = x; + g_mouse_fifo[0].y = y; + return 0; + } + +#if 0 + printf("...real move, new x: %d, %d, a2:%d,%d\n", g_mouse_fifo[0].x, + g_mouse_fifo[0].y, g_mouse_a2_x, g_mouse_a2_y); +#endif + + mouse_moved = (g_mouse_fifo[0].x != x) || (g_mouse_fifo[0].y != y); + + g_mouse_fifo[0].x = x; + g_mouse_fifo[0].y = y; + g_mouse_fifo[0].dfcyc = dfcyc; + + button1_changed = (buttons_valid & 1) && + ((button_states & 1) != (g_mouse_fifo[0].buttons & 1)); + + if((button_states & 4) && !(g_mouse_fifo[0].buttons & 4) && + (buttons_valid & 4)) { + /* right button pressed */ + adb_increment_speed(); + } + if((button_states & 2) && !(g_mouse_fifo[0].buttons & 2) && + (buttons_valid & 2)) { + /* middle button pressed */ + halt2_printf("Middle button pressed\n"); + } + + pos = g_mouse_fifo_pos; + if((pos < (ADB_MOUSE_FIFO - 2)) && button1_changed) { + /* copy delta to overflow, set overflow */ + /* overflow ensures the mouse button state is precise at */ + /* button up/down times. Using a mouse event list where */ + /* deltas accumulate until a button change would work, too */ + for(i = pos; i >= 0; i--) { + g_mouse_fifo[i + 1] = g_mouse_fifo[i]; /* copy struct*/ + } + g_mouse_fifo_pos = pos + 1; + } + + g_mouse_fifo[0].buttons = (button_states & buttons_valid) | + (g_mouse_fifo[0].buttons & ~buttons_valid); + + if(mouse_moved || button1_changed) { + if( (g_mouse_ctl_addr == g_mouse_dev_addr) && + ((g_adb_mode & 0x2) == 0)) { + g_adb_mouse_valid_data = 1; + adb_add_mouse_int(); + } + } + + return mouse_moved; +} + +int +mouse_read_c024(dword64 dfcyc) +{ + word32 ret, tool_start; + int em_active, target_x, target_y, delta_x, delta_y, a2_x, a2_y; + int mouse_button, clamped, pos; + + if(((g_adb_mode & 0x2) != 0) || (g_mouse_dev_addr != g_mouse_ctl_addr)){ + /* mouse is off, return 0, or mouse is not autopoll */ + g_adb_mouse_valid_data = 0; + adb_clear_mouse_int(); + return 0; + } + + mouse_compress_fifo(dfcyc); + + pos = g_mouse_fifo_pos; + target_x = g_mouse_fifo[pos].x; + target_y = g_mouse_fifo[pos].y; + mouse_button = (g_mouse_fifo[pos].buttons & 1); + delta_x = target_x - g_mouse_a2_x; + delta_y = target_y - g_mouse_a2_y; + + clamped = 0; + if(delta_x > 0x3f) { + delta_x = 0x3f; + clamped = 1; + } else if(delta_x < -0x3f) { + delta_x = -0x3f; + clamped = 1; + } + if(delta_y > 0x3f) { + delta_y = 0x3f; + clamped = 1; + } else if(delta_y < -0x3f) { + delta_y = -0x3f; + clamped = 1; + } + + if(pos > 0) { + /* peek into next entry's button info if we are not clamped */ + /* and we're returning the y-coord */ + if(!clamped && g_adb_mouse_coord) { + mouse_button = g_mouse_fifo[pos - 1].buttons & 1; + } + } + + if(g_adb_mouse_coord) { + /* y coord */ + delta_x = 0; /* clear unneeded x delta */ + } else { + delta_y = 0; /* clear unneeded y delta */ + } + + + adb_printf(" pre a2_x:%02x,%02x,%02x,%02x\n", + g_slow_memory_ptr[0x100e9], g_slow_memory_ptr[0x100ea], + g_slow_memory_ptr[0x100eb], g_slow_memory_ptr[0x100ec]); + adb_printf(" pre a2_x:%02x,%02x,%02x,%02x\n", + g_slow_memory_ptr[0x10190], g_slow_memory_ptr[0x10192], + g_slow_memory_ptr[0x10191], g_slow_memory_ptr[0x10193]); + + /* Update event manager internal state */ + tool_start = (g_slow_memory_ptr[0x103ca] << 16) + + (g_slow_memory_ptr[0x103c9] << 8) + + g_slow_memory_ptr[0x103c8]; + + em_active = 0; + if((tool_start >= 0x20000) && (tool_start < (g_mem_size_total - 28)) ) { + /* seems to be valid ptr to addr of mem space for tools */ + /* see if event manager appears to be active */ + em_active = g_memory_ptr[tool_start + 6*4] + + (g_memory_ptr[tool_start + 6*4 + 1] << 8); + if(g_warp_pointer) { + em_active = 0; + } + } + + //em_active = 0; // HACK! + a2_x = g_mouse_a2_x; + a2_y = g_mouse_a2_y; + + if(em_active) { + if((!g_hide_pointer) && (g_num_lines_prev_superhires == 200) && + !g_unhide_pointer) { + /* if super-hires and forcing tracking, then hide */ + g_hide_pointer = 1; + } + if(g_adb_mouse_coord == 0) { + /* update x coord values */ + g_slow_memory_ptr[0x47c] = a2_x & 0xff; + g_slow_memory_ptr[0x57c] = a2_x >> 8; + g_memory_ptr[0x47c] = a2_x & 0xff; + g_memory_ptr[0x57c] = a2_x >> 8; + + g_slow_memory_ptr[0x10190] = a2_x & 0xff; + g_slow_memory_ptr[0x10192] = a2_x >> 8; + } else { + g_slow_memory_ptr[0x4fc] = a2_y & 0xff; + g_slow_memory_ptr[0x5fc] = a2_y >> 8; + g_memory_ptr[0x4fc] = a2_y & 0xff; + g_memory_ptr[0x5fc] = a2_y >> 8; + + g_slow_memory_ptr[0x10191] = a2_y & 0xff; + g_slow_memory_ptr[0x10193] = a2_y >> 8; + } + } else { + if(g_hide_pointer && !g_warp_pointer) { + g_hide_pointer = 0; + } + } + + ret = ((!mouse_button) << 7) + ((delta_x | delta_y) & 0x7f); + if(g_adb_mouse_coord) { + g_mouse_a2_button = mouse_button; /* y coord has button*/ + } else { + ret |= 0x80; /* mouse button not down on x coord rd */ + } + + a2_x += delta_x; + a2_y += delta_y; + g_mouse_a2_x = a2_x; + g_mouse_a2_y = a2_y; + if(g_mouse_fifo_pos) { + if((target_x == a2_x) && (target_y == a2_y) && + (g_mouse_a2_button == mouse_button)) { + g_mouse_fifo_pos--; + } + } + + + adb_printf("Rd c024, mouse is_y:%d, %02x, vbl:%08x, dfcyc:%016llx, em:" + "%d\n", g_adb_mouse_coord, ret, g_vbl_count, dfcyc, em_active); + adb_printf("...mouse targ_x:%d,%d delta_x,y:%d,%d fifo:%d, a2:%d,%d\n", + target_x, target_y, delta_x, delta_y, g_mouse_fifo_pos, + a2_x, a2_y); + adb_printf(" post a2_x:%02x,%02x,%02x,%02x\n", + g_slow_memory_ptr[0x10190], g_slow_memory_ptr[0x10192], + g_slow_memory_ptr[0x10191], g_slow_memory_ptr[0x10193]); + + if((g_mouse_fifo_pos == 0) && (g_mouse_fifo[0].x == a2_x) && + (g_mouse_fifo[0].y == a2_y) && + ((g_mouse_fifo[0].buttons & 1) == g_mouse_a2_button)) { + g_adb_mouse_valid_data = 0; + adb_clear_mouse_int(); + } + + g_adb_mouse_coord = !g_adb_mouse_coord; + return ret; +} + +void +mouse_compress_fifo(dword64 dfcyc) +{ + dword64 ddelta; + int pos; + + /* The mouse fifo exists so that fast button changes don't get lost */ + /* if the emulator lags behind the mouse events */ + /* But the FIFO means really old mouse events are saved if */ + /* the emulated code isn't looking at the mouse registers */ + /* This routine compresses all mouse events > 0.5 seconds old */ + + ddelta = (500LL*1000) << 16; + for(pos = g_mouse_fifo_pos; pos >= 1; pos--) { + if((g_mouse_fifo[pos].dfcyc + ddelta) < dfcyc) { + /* Remove this entry */ + adb_printf("Old mouse FIFO pos %d removed\n", pos); + g_mouse_fifo_pos = pos - 1; + continue; + } + /* Else, stop searching the FIFO */ + break; + } +} + +void +adb_paste_update_state() +{ + int rd_pos, wr_pos; + + rd_pos = g_kbd_paste_rd_pos; + wr_pos = g_kbd_paste_wr_pos; + if(rd_pos >= wr_pos) { + g_kbd_paste_rd_pos = 0; + g_kbd_paste_wr_pos = 0; + return; + } + if(g_kbd_chars_buffered == 0) { + g_kbd_buf[0] = g_kbd_paste_buf[rd_pos]; + g_kbd_paste_rd_pos = rd_pos + 1; + g_kbd_chars_buffered = 1; + } +} + +int +adb_paste_add_buf(word32 key) +{ + word32 last_key; + int pos; + + // Applesoft reads $C000 to check for ctrl-C after each statement. + // So if we dropped all chars into g_kbd_buf[], we could end up + // losing chars due to multiple reads of $C000 without writes to $C010 + // causing g_kbd_read_no_update to toss a paste char. + // Instead, have a separate buffer, and when g_kbd_chars_buffered==0, + // copy one paste char to g_kbd_buf[0]. This also solves a problem + // where Applesoft is doing: 10 GOTO 10 and it needs to see a Ctrl-C + // to stop--but a paste buffer is in the way. + // But, now pressing keys while a paste is pending causes those keys + // to take priority during the paste. + last_key = g_kbd_paste_last_key; + g_kbd_paste_last_key = key; + if(key == 10) { // \n, newline on Unix + key = 13; // \r, return + if(last_key == 13) { + key = 0; // CR, then LF--eat the LF + } + } + if((key == 0) || (key >= 0x80)) { + return 0; // Just skip these keys + } + pos = g_kbd_paste_wr_pos; + if(pos >= MAX_KBD_PASTE_BUF) { + return 1; + } + g_kbd_paste_buf[pos] = key | 0x80; + g_kbd_paste_wr_pos = pos + 1; + + adb_paste_update_state(); + return 0; +} + +void +adb_key_event(int a2code, int is_up) +{ + word32 special, vbl_count; + int key, hard_key, pos, tmp_ascii, ascii; + + if(is_up) { + adb_printf("adb_key_event, key:%02x, is up, g_key_down: %02x\n", + a2code, g_key_down); + } + + if(a2code < 0 || a2code > 0x7f) { + halt_printf("add_key_event: a2code: %04x!\n", a2code); + return; + } + + if(!is_up && a2code == 0x35) { + /* ESC pressed, see if ctrl & cmd key down */ + if(CTRL_DOWN && CMD_DOWN) { + /* Desk mgr int */ + printf("Desk mgr int!\n"); + + g_adb_interrupt_byte |= 0x20; + adb_add_data_int(); + } + } + + /* convert key to ascii, if possible */ + hard_key = 0; + if(g_a2_key_to_ascii[a2code][1] & 0xef00) { + /* special key */ + } else { + /* we have ascii */ + hard_key = 1; + } + + pos = 1; + ascii = g_a2_key_to_ascii[a2code][1]; + if(CAPS_LOCK_DOWN && (ascii >= 'a') && (ascii <= 'z')) { + pos = 2; + if(SHIFT_DOWN && (g_adb_mode & 0x40)) { + /* xor shift mode--capslock and shift == lowercase */ + pos = 1; + } + } else if(SHIFT_DOWN) { + pos = 2; + } + + ascii = g_a2_key_to_ascii[a2code][pos]; + if(CTRL_DOWN) { + tmp_ascii = g_a2_key_to_ascii[a2code][3]; + if(tmp_ascii >= 0) { + ascii = tmp_ascii; + } + } + key = (ascii & 0x7f) + 0x80; + + special = (ascii >> 8) & 0xff; + if(ascii < 0) { + printf("ascii1: %d, a2code: %02x, pos: %d\n", ascii,a2code,pos); + ascii = 0; + special = 0; + } + + if(!is_up) { + if(hard_key) { + g_kbd_buf[g_kbd_chars_buffered] = key; + g_kbd_chars_buffered++; + if(g_kbd_chars_buffered >= MAX_KBD_BUF) { + g_kbd_chars_buffered = MAX_KBD_BUF - 1; + } + g_key_down = 1; + g_a2code_down = a2code; + + /* first key down, set up autorepeat */ + vbl_count = g_vbl_count; + g_adb_repeat_vbl = vbl_count + g_adb_repeat_delay; + if(g_adb_repeat_delay == 0) { + g_key_down = 0; + } + g_hard_key_down = 1; + } + + g_c025_val = g_c025_val | special; + adb_printf("new c025_or: %02x\n", g_c025_val); + } else { + if(hard_key && (a2code == g_a2code_down)) { + g_hard_key_down = 0; + /* Turn off repeat */ + g_key_down = 0; + } + + g_c025_val = g_c025_val & (~ special); + adb_printf("new c025_and: %02x\n", g_c025_val); + } + + if(g_key_down) { + g_c025_val = g_c025_val & (~0x20); + } else { + /* If no hard key down, set update mod latch */ + g_c025_val = g_c025_val | 0x20; + } + +} + +word32 +adb_read_c000() +{ + word32 vbl_count; + + if( ((g_kbd_buf[0] & 0x80) == 0) && (g_key_down == 0)) { + /* nothing happening, just get out */ + return g_kbd_buf[0]; + } + if(g_kbd_buf[0] & 0x80) { + /* got one */ + if((g_kbd_read_no_update++ > 5) && (g_kbd_chars_buffered > 1)) { + /* read 5 times, keys pending, let's move it along */ + printf("Read %02x %d times, tossing\n", g_kbd_buf[0], + g_kbd_read_no_update); + adb_access_c010(); + } + } else { + vbl_count = g_vbl_count; + if(g_key_down && (vbl_count >= g_adb_repeat_vbl)) { + /* repeat the g_key_down */ + g_c025_val |= 0x8; + adb_key_event(g_a2code_down, 0); + g_adb_repeat_vbl = vbl_count + g_adb_repeat_rate; + } + } + + return g_kbd_buf[0]; +} + +word32 +adb_access_c010() +{ + int tmp; + int i; + + g_kbd_read_no_update = 0; + + tmp = g_kbd_buf[0] & 0x7f; + g_kbd_buf[0] = tmp; + + tmp = tmp | (g_hard_key_down << 7); + if(g_kbd_chars_buffered) { + for(i = 1; i < g_kbd_chars_buffered; i++) { + g_kbd_buf[i - 1] = g_kbd_buf[i]; + } + g_kbd_chars_buffered--; + if(g_kbd_chars_buffered == 0) { + adb_paste_update_state(); + } + } + + g_c025_val = g_c025_val & (~ (0x08)); + + return tmp; +} + +word32 +adb_read_c025() +{ + return g_c025_val; +} + +int +adb_is_cmd_key_down() +{ + return CMD_DOWN; +} + +int +adb_is_option_key_down() +{ + return OPTION_DOWN; +} + +void +adb_increment_speed() +{ + const char *str; + + g_limit_speed++; + if(g_limit_speed > 3) { + g_limit_speed = 0; + } + + str = ""; + switch(g_limit_speed) { + case 0: + str = "...as fast as possible!"; + break; + case 1: + str = "...1.024MHz!"; + break; + case 2: + str = "...2.8MHz!"; + break; + case 3: + str = "...8.0MHz!"; + break; + } + printf("Toggling g_limit_speed to %d%s\n", g_limit_speed, str); +} + +void +adb_update_c025_mask(Kimage *kimage_ptr, word32 new_c025_val, word32 mask) +{ + // Called by *driver.c host drivers to handle focus changes and + // capslock state (so if capslock is on, we leave the window, release + // capslock, then reenter the window, we update things properly). + if(kimage_ptr == &g_mainwin_kimage) { + g_c025_val = (g_c025_val & (~mask)) | new_c025_val; + } else { + kimage_ptr->c025_val = (kimage_ptr->c025_val & (~mask)) | + new_c025_val; + } +} + +int +adb_ascii_to_a2code(int unicode_c, int a2code, int *shift_down_ptr) +{ + int i; + + switch(unicode_c) { + case 0x00a3: // British pound + unicode_c = '#'; + break; + case 0x00e0: // a with left accent + unicode_c = '@'; + break; + case 0x00b0: // degrees (French) + case 0x00c4: // A with umlaut (German, Swedish) + case 0x00a1: // ! upside down (Spanish) + case 0x00c6: // AE (Danish) + unicode_c = '['; + break; + case 0x00e7: // c with tail (French/Italian) + case 0x00d1: // N with ~ (Spanish) + case 0x00d6: // O with umlaut (German, Swedish) + case 0x00d8: // O with slash (Danish) + unicode_c = '\\'; + break; + case 0x00a7: // ss like thing (French) + case 0x00dc: // U with umlaut + case 0x00bf: // ? upside down (Spanish) + case 0x00c5: // A with circle (Danish) + //case 0x00e9: // e with right accent (Italian) + unicode_c = ']'; + break; + //case 0x0000: // u with left accent (Italian) + // unicode_c = '`'; + // break; + case 0x00e4: // a with umlaut (german) + case 0x00e9: // e with accent (french) + case 0x0000: // ae (Danish) + unicode_c = '{'; + break; + case 0x00f6: // o with umlaut (German/Swedish) + case 0x00f9: // u with left accent (French) + case 0x00f8: // o with slash (Danish) + case 0x00f1: // n with ~ (Spanish) + case 0x00f2: // o with ` (Italian) + unicode_c = '|'; + break; + case 0x00e8: // e with ` (French, Italian) + case 0x00fc: // u with umlaut (German) + case 0x00e5: // a with circle (Danish/Swedish) + unicode_c = '}'; + break; + case 0x00a8: // two high dots (French) + case 0x00ec: // i with ` (Italian) + case 0x00df: // german B thing (German) + unicode_c = '~'; + break; + } + + if(unicode_c > 0x7f) { + return a2code; // Use a2code instead + } + if((g_a2_key_to_ascii[a2code][1] & 0xf000) == 0x1000) { // Keypad + // Don't remap keypad keys, we need them for Keypad Joystick + if((unicode_c >= '0') && (unicode_c <= '9')) { + return a2code; + } + } + + for(i = 0; i < 128; i++) { + if(g_a2_key_to_ascii[i][1] == unicode_c) { // Not-shifted + *shift_down_ptr = 0; + return g_a2_key_to_ascii[i][0]; + } + if(g_a2_key_to_ascii[i][2] == unicode_c) { // Shifted + *shift_down_ptr = 1; + return g_a2_key_to_ascii[i][0]; + } + } + + return a2code; // Not found, use default a2code +} + +void +adb_physical_key_update(Kimage *kimage_ptr, int raw_a2code, word32 unicode_c, + int is_up) +{ + word32 restore_c025_val, restorek_c025_val; + int special, ascii_and_type, ascii, new_shift, a2code, other_a2code; + + /* this routine called by xdriver to pass raw codes--handle */ + /* ucontroller and ADB bus protocol issues here */ + /* if autopoll on, pass it on through to c025,c000 regs */ + /* else only put it in kbd reg 3, and pull SRQ if needed */ + + adb_printf("adb_phys_key_update: %02x, %d\n", raw_a2code, is_up); + + if((raw_a2code < 0) || (raw_a2code > 0x7f)) { + halt_printf("raw_a2code: %04x!\n", raw_a2code); + return; + } + a2code = raw_a2code; + restore_c025_val = 0; + restorek_c025_val = 0; + if(unicode_c > 0) { + // To enable international keyboards, ignore a2code, look up + // what U.S. keycode would be and return that + new_shift = g_c025_val & 1; + a2code = adb_ascii_to_a2code(unicode_c, a2code, &new_shift); + if(a2code && ((g_c025_val & 1) != new_shift)) { + restore_c025_val = g_c025_val | 0x100; + restorek_c025_val = kimage_ptr->c025_val; + g_c025_val = (g_c025_val & -2) | new_shift; + kimage_ptr->c025_val = (kimage_ptr->c025_val & -2) | + new_shift; + } + if(!is_up) { + g_rawa2_to_a2code[raw_a2code & 0x7f] = a2code; + } + } + + /* Remap 0x7b-0x7e to 0x3b-0x3e (arrow keys on new mac keyboards) */ + if((a2code >= 0x7b) && (a2code <= 0x7e)) { + a2code = a2code - 0x40; + } + if(g_adb_swap_command_option) { + if(a2code == 0x37) { // Command? + a2code = 0x3a; // -> Option + } else if(a2code == 0x3a) { // Option? + a2code = 0x37; // -> Command + } + } + + /* Now check for special keys (function keys, etc) */ + ascii_and_type = g_a2_key_to_ascii[a2code][1]; + special = 0; + if((ascii_and_type & 0xf000) == 0x8000) { + /* special function key */ + special = ascii_and_type & 0xff; + switch(special) { + case 0x01: /* F1 - remap to cmd */ + a2code = 0x37; + special = 0; + break; + case 0x02: /* F2 - remap to option */ + a2code = 0x3a; + special = 0; + break; + case 0x03: /* F3 - remap to escape for OS/2 */ + a2code = 0x35; + special = 0; + break; + case 0x0c: /* F12 - remap to reset */ + a2code = 0x7f; + special = 0; + break; + default: + break; + } + } + + /* Only process reset requests here */ + if((is_up == 0) && (a2code == 0x7f) && CTRL_DOWN) { + /* Reset pressed! */ + printf("Reset pressed since CTRL_DOWN: %d\n", CTRL_DOWN); + do_reset(); + return; + } + + if(special && !is_up) { + switch(special) { + case 0x04: /* F4 - Emulator config panel */ + cfg_toggle_config_panel(); + break; + case 0x05: /* F5 - Force Refresh */ + g_status_enable = !g_status_enable; + // video_update() will call video_update_status_enable() + break; + case 0x06: /* F6 - emulator speed */ + if(SHIFT_DOWN) { + halt2_printf("Shift-F6 pressed\n"); + } else { + adb_increment_speed(); + } + break; + case 0x07: /* F7 - toggle debugger window, SHIFT:fast disk */ + if(SHIFT_DOWN) { + g_fast_disk_emul_en = !g_fast_disk_emul_en; + iwm_update_fast_disk_emul(g_fast_disk_emul_en); + printf("g_fast_disk_emul_en is now %d\n", + g_fast_disk_emul_en); + } else { + video_set_active(&g_debugwin_kimage, + !g_debugwin_kimage.active); + printf("Toggled debugger window to:%d\n", + g_debugwin_kimage.active); + } + break; + case 0x08: /* F8 - warp pointer */ + g_warp_pointer = !g_warp_pointer; + g_hide_pointer = g_warp_pointer; + printf("New warp:%d, new hide:%d\n", g_warp_pointer, + g_hide_pointer); + break; + case 0x09: /* F9 - swap paddles */ + if(CTRL_DOWN) { + g_adb_copy_requested = 1; + } else if(SHIFT_DOWN) { + g_swap_paddles = !g_swap_paddles; + printf("Swap paddles is now: %d\n", + g_swap_paddles); + } else { + g_invert_paddles = !g_invert_paddles; + printf("Invert paddles is now: %d\n", + g_invert_paddles); + } + break; + case 0x0a: /* F10 - nothing */ + break; + case 0x0b: /* F11 - full screen */ + break; + } + + return; + } + + if(kimage_ptr == &g_debugwin_kimage) { + debugger_key_event(kimage_ptr, a2code, is_up); + if(restore_c025_val) { + g_c025_val = restore_c025_val & 0xff; // Restore shift + kimage_ptr->c025_val = restorek_c025_val; + } + return; + } + + /* Handle Keypad Joystick here partly...if keypad key pressed */ + /* while in Keypad Joystick mode, do not pass it on as a key press */ + if((ascii_and_type & 0xff00) == 0x1000) { + /* Keep track of keypad number keys being up or down even */ + /* if joystick mode isn't keypad. This avoid funny cases */ + /* if joystick mode is changed while a key is pressed */ + ascii = ascii_and_type & 0xff; + if(ascii > 0x30 && ascii <= 0x39) { + g_keypad_key_is_down[ascii - 0x30] = !is_up; + } + if(g_joystick_type == 0) { + /* If Joystick type is keypad, then do not let these */ + /* keypress pass on further, except for cmd/opt */ + if(ascii == 0x30) { + /* remap '0' to cmd */ + a2code = 0x37; + } else if(ascii == 0x2e || ascii == 0x2c) { + /* remap '.' and ',' to option */ + a2code = 0x3a; + } else { + /* Just ignore it in this mode */ + return; + } + } + } + + adb_maybe_virtual_key_update(a2code, is_up); + other_a2code = g_rawa2_to_a2code[raw_a2code & 0x7f]; + if((other_a2code >= 0) && is_up) { + adb_maybe_virtual_key_update(other_a2code, is_up); + g_rawa2_to_a2code[raw_a2code & 0x7f] = -1; + } + + if(restore_c025_val) { + g_c025_val = restore_c025_val & 0xff; // Restore shift + } +} + +void +adb_maybe_virtual_key_update(int a2code, int is_up) +{ + int autopoll; + + autopoll = 1; + if(g_adb_mode & 1) { + /* autopoll is explicitly off */ + autopoll = 0; + } + if(g_kbd_dev_addr != g_kbd_ctl_addr) { + /* autopoll is off because ucontroller doesn't know kbd moved */ + autopoll = 0; + } + if(g_config_control_panel) { + /* always do autopoll */ + autopoll = 1; + } + + if(is_up) { + if(!autopoll) { + /* no auto keys, generate SRQ! */ + adb_kbd_reg0_data(a2code, is_up); + } else { + adb_virtual_key_update(a2code, is_up); + } + } else { + if(!autopoll) { + /* no auto keys, generate SRQ! */ + adb_kbd_reg0_data(a2code, is_up); + } else { + /* was up, now down */ + adb_virtual_key_update(a2code, is_up); + } + } +} + +void +adb_virtual_key_update(int a2code, int is_up) +{ + word32 mask; + int bitpos; + int i; + + adb_printf("Virtual handle a2code: %02x, is_up: %d\n", a2code, is_up); + + if(a2code < 0 || a2code > 0x7f) { + halt_printf("a2code: %04x!\n", a2code); + return; + } + + i = (a2code >> 5) & 3; + bitpos = a2code & 0x1f; + mask = (1 << bitpos); + + if(is_up) { + if(g_virtual_key_up[i] & mask) { + /* already up, do nothing */ + } else { + g_virtual_key_up[i] |= mask; + adb_key_event(a2code, is_up); + } + } else { + if(g_virtual_key_up[i] & mask) { + g_virtual_key_up[i] &= (~mask); + adb_key_event(a2code, is_up); + } + } +} + +#if 0 +void +adb_all_keys_up() +{ + word32 mask; + int i, j; + + for(i = 0; i < 4; i++) { + for(j = 0; j < 32; j++) { + mask = 1 << j; + if((g_virtual_key_up[i] & mask) == 0) { + /* create key-up event */ + adb_physical_key_update(i*32 + j, 1); + } + } + } +} +#endif + +void +adb_kbd_repeat_off() +{ + g_key_down = 0; +} + +void +adb_mainwin_focus(int has_focus) +{ + g_adb_mainwin_has_focus = has_focus; + // printf("g_adb_mainwin_has_focus=%d\n", g_adb_mainwin_has_focus); + if(!has_focus) { + adb_nonmain_check(); + } +} diff --git a/gsplus/src/applesingle.c b/gsplus/src/applesingle.c new file mode 100644 index 0000000..5bc7139 --- /dev/null +++ b/gsplus/src/applesingle.c @@ -0,0 +1,362 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2021 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// From Wikipedia AppleSingle_and_AppleDouble_formats): +// https://web.archive.org/web/20180311140826/http://kaiser-edv.de/ +// documents/AppleSingle_AppleDouble.pdf +// All fields in an Applesingle file are in big-endian format +// ProDOS forked files are described in Technote tn-pdos-025. + +#include "defc.h" + +word32 +applesingle_get_be32(const byte *bptr) +{ + return (bptr[0] << 24) | (bptr[1] << 16) | (bptr[2] << 8) | bptr[3]; +} + +word32 +applesingle_get_be16(const byte *bptr) +{ + return (bptr[0] << 8) | bptr[1]; +} + +void +applesingle_set_be32(byte *bptr, word32 val) +{ + bptr[3] = val; + bptr[2] = val >> 8; + bptr[1] = val >> 16; + bptr[0] = val >> 24; +} + +void +applesingle_set_be16(byte *bptr, word32 val) +{ + bptr[1] = val; + bptr[0] = val >> 8; +} + +word32 +applesingle_map_from_prodos(Disk *dsk, Dynapro_file *fileptr, int do_file_data) +{ + byte *fptr, *bptr; + word32 data_size, resource_size, rounded_data_size, num_entries; + word32 rounded_resource_size, hdr_size, max_size, hdr_pos, data_pos; + word32 block, key_block, ret, good, has_finder_info, offset; + int level; + int i, j; + +#if 0 + printf("applesingle_map_from_prodos: %s do_file_data:%d\n", + fileptr->unix_path, do_file_data); +#endif + + // First, handle mini directory describing the forks + key_block = fileptr->key_block; + ret = dynapro_map_one_file_block(dsk, fileptr, key_block, 1U << 30, 0); + if(ret == 0) { + printf(" dynapro_map_one_file_block ret 0, applesingle done\n"); + return 0; + } + bptr = &(dsk->raw_data[key_block << 9]); + data_size = dynapro_get_word24(&bptr[5]); + resource_size = dynapro_get_word24(&bptr[0x100 + 5]); + has_finder_info = bptr[9] | bptr[27]; + + num_entries = 1; // ProDOS info always + if(has_finder_info) { + num_entries++; + } + rounded_data_size = data_size; + if(data_size) { + rounded_data_size = (data_size + 0x200) & -0x200; + num_entries++; + } + rounded_resource_size = resource_size; + if(resource_size) { + rounded_resource_size = (resource_size + 0x200) & -0x200; + num_entries++; + } + hdr_size = 256; + max_size = hdr_size + rounded_resource_size + rounded_data_size; + fileptr->buffer_ptr = 0; + fptr = 0; + if(do_file_data) { + fptr = calloc(1, max_size + 0x200); +#if 0 + printf(" fptr:%p, max_size:%08x, res:%08x, data:%08x\n", + fptr, max_size, rounded_resource_size, + rounded_data_size); +#endif + } + + // From now on, errors cannot return without free'ing fptr + good = 1; + if(resource_size) { + block = dynapro_get_word16(&bptr[0x100 + 1]); + level = bptr[0x100]; + if(fptr) { + fileptr->buffer_ptr = fptr + 256; + } + ret = dynapro_map_file_blocks(dsk, fileptr, block, level, 0, + resource_size); + if(ret == 0) { + good = 0; + } + } + + if(data_size) { + block = dynapro_get_word16(&bptr[1]); + level = bptr[0]; + if(fptr) { + fileptr->buffer_ptr = fptr + 256 + + rounded_resource_size; + } + ret = dynapro_map_file_blocks(dsk, fileptr, block, level, 0, + data_size); + if(ret == 0) { + good = 0; + } + } + + fileptr->buffer_ptr = 0; + + // Now prepare the header + if(fptr) { + applesingle_set_be32(&fptr[0], 0x00051600); // Magic + applesingle_set_be32(&fptr[4], 0x00020000); // Version + applesingle_set_be16(&fptr[24], num_entries); // Version + hdr_pos = 26; + data_pos = 192; + + // First do ProDOS entry + applesingle_set_be32(&fptr[hdr_pos + 0], 11); // ProDOS Info + applesingle_set_be32(&fptr[hdr_pos + 4], data_pos); + applesingle_set_be32(&fptr[hdr_pos + 8], 8); + + applesingle_set_be16(&fptr[data_pos + 0], 0xc3); + applesingle_set_be16(&fptr[data_pos + 2], fileptr->file_type); + applesingle_set_be32(&fptr[data_pos + 4], fileptr->aux_type); + hdr_pos += 12; + data_pos += 8; + + // Then do FinderInfo + if(has_finder_info) { + applesingle_set_be32(&fptr[hdr_pos + 0], 9); //Finder + applesingle_set_be32(&fptr[hdr_pos + 4], data_pos); + applesingle_set_be32(&fptr[hdr_pos + 8], 32); + + for(i = 0; i < 2; i++) { + offset = bptr[9 + 18*i]; + if(!offset) { + continue; // skip it + } + offset = ((offset - 1) & 1) * 8; + for(j = 0; j < 9; j++) { + fptr[data_pos + offset + j] = + bptr[10 + 18*i + j]; + } + } + hdr_pos += 12; + data_pos += 32; + } + + if(data_pos >= 256) { + printf("data_pos:%08x is too big\n", data_pos); + good = 0; + } + + // First, do data fork + if(data_size) { + applesingle_set_be32(&fptr[hdr_pos + 0], 1); // Data + applesingle_set_be32(&fptr[hdr_pos + 4], + 256 + rounded_resource_size); + applesingle_set_be32(&fptr[hdr_pos + 8], data_size); + hdr_pos += 12; + } + // Then do resource fork + if(resource_size) { + applesingle_set_be32(&fptr[hdr_pos + 0], 2); // Rsrc + applesingle_set_be32(&fptr[hdr_pos + 4], 256); + applesingle_set_be32(&fptr[hdr_pos + 8], resource_size); + hdr_pos += 12; + } + if(hdr_pos > 192) { + printf("hdr:%08x stomped on data\n", hdr_pos); + good = 0; + } + if(good) { + ret = dynapro_write_to_unix_file(fileptr->unix_path, + fptr, 256 + rounded_resource_size + data_size); + if(ret == 0) { + good = 0; + } + } + + free(fptr); + } + + // printf("applesingle_map_from_prodos done, good:%d\n", good); + return good; +} + +word32 +applesingle_from_unix(Disk *dsk, Dynapro_file *fileptr, byte *fptr, + dword64 dsize) +{ + byte *bptr, *tptr; + word32 key_block, blocks_used, entry_id, blocks_out, offset, length; + word32 magic, version, hdr_pos, did_fork; + int num_entries; + int i; + + // Return 0 if anything is wrong with the .applesingle file + // Otherwise, return (blocks_used << 16) | (key_block & 0xffff) + +#if 0 + printf("applesingle_from_unix %s, size:%08llx\n", fileptr->unix_path, + dsize); +#endif + + key_block = fileptr->key_block; + bptr = &(dsk->raw_data[key_block << 9]); + + if(dsize < 50) { + printf("Applesingle is too small\n"); + return 0; + } + magic = applesingle_get_be32(&fptr[0]); + version = applesingle_get_be32(&fptr[4]); + num_entries = applesingle_get_be16(&fptr[24]); + if((magic != 0x00051600) || (version < 0x00020000)) { + printf("Bad Applesingle magic number is: %08x, vers:%08x\n", + magic, version); + return 0; + } + hdr_pos = 26; + blocks_used = 1; + did_fork = 0; + // printf(" num_entries:%d\n", num_entries); + for(i = 0; i < num_entries; i++) { + if((hdr_pos + 24) > dsize) { + printf("Applesingle header exceeds file size i:%d of " + "%d, hdr_pos:%04x dsize:%08llx\n", i, + num_entries, hdr_pos, dsize); + return 0; + } + entry_id = applesingle_get_be32(&fptr[hdr_pos + 0]); + offset = applesingle_get_be32(&fptr[hdr_pos + 4]); + length = applesingle_get_be32(&fptr[hdr_pos + 8]); +#if 0 + printf(" header[%d] at +%04x: id:%d, offset:%08x, len:%08x\n", + i, hdr_pos, entry_id, offset, length); +#endif + if((offset + length) > dsize) { + printf("Applesingle entry_id:%d exceeds file size\n", + entry_id); + return 0; + } + switch(entry_id) { + case 1: // Data fork + case 2: // Resource fork + tptr = bptr; + if(entry_id == 2) { // Resource fork + tptr += 0x100; + } +#if 0 + printf(" for entry_id %d, offset:%08x, length:%08x, " + "fptr:%p\n", entry_id, offset, length, fptr); +#endif + if(did_fork & (1 << entry_id)) { + printf("fork %d repeated!\n", entry_id); + return 0; + } + did_fork |= (1 << entry_id); + blocks_out = applesingle_make_prodos_fork(dsk, + fptr + offset, tptr, length); + if(blocks_out == 0) { + return 0; + } + blocks_used += (blocks_out >> 16); + break; + case 9: // Finder Info + if(length < 32) { + printf("Invalid Finder info, len:%d\n", length); + } + bptr[8] = 0x12; + bptr[8 + 18] = 0x12; + bptr[9] = 1; + bptr[9 + 18] = 2; + for(i = 0; i < 16; i++) { + bptr[10 + i] = fptr[offset + i]; + bptr[10 + 18 + i] = fptr[offset + 16 + i]; + } + break; + case 11: // ProDOS File Info + fileptr->file_type = fptr[offset + 3]; + fileptr->aux_type = (fptr[offset + 6] << 8) | + fptr[offset + 7]; + break; + default: + break; // Ignore it + } + hdr_pos += 12; + } + + for(i = 1; i < 3; i++) { + if((did_fork >> i) & 1) { + continue; + } + // Create one block for this fork even though it's length==0 + // i==1: no data fork; i==2: no resource fork + printf(" Doing dummy fork, i:%d, fptr:%p\n", i, fptr); + blocks_out = applesingle_make_prodos_fork(dsk, fptr, + bptr + ((i & 2) * 0x80), 0); + if(blocks_out == 0) { + return blocks_out; + } + blocks_used += (blocks_out >> 16); + } + + fileptr->eof = 0x200; + return (blocks_used << 16) | key_block; +} + +word32 +applesingle_make_prodos_fork(Disk *dsk, byte *fptr, byte *tptr, word32 length) +{ + word32 block, blocks_out, storage_type; + +#if 0 + printf("applesingle_make_prodos_fork: fptr:%p, tptr:%p, length:%08x\n", + fptr, tptr, length); +#endif + + // Handle creating either a resource or data fork + block = dynapro_find_free_block(dsk); + if(block == 0) { + return 0; + } + blocks_out = dynapro_fork_from_unix(dsk, fptr, &storage_type, block, + length); + + // printf(" dynapro_fork_from_unix ret: %08x, storage:%02x\n", + // blocks_out, storage_type); + tptr[0] = storage_type >> 4; + tptr[1] = blocks_out & 0xff; // key_block lo + tptr[2] = (blocks_out >> 8) & 0xff; // key_block hi + tptr[3] = (blocks_out >> 16) & 0xff; // blocks_used lo + tptr[4] = (blocks_out >> 24) & 0xff; // blocks_used hi + tptr[5] = length & 0xff; // eof lo + tptr[6] = (length >> 8) & 0xff; // eof mid + tptr[7] = (length >> 16) & 0xff; // eof hi + return blocks_out; +} diff --git a/gsplus/src/clock.c b/gsplus/src/clock.c new file mode 100644 index 0000000..28701c4 --- /dev/null +++ b/gsplus/src/clock.c @@ -0,0 +1,396 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2022 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include "defc.h" +#include +#ifdef _WIN32 +# include +# include +#else +# include +#endif + +extern int Verbose; +extern word32 g_vbl_count; +extern int g_rom_version; +extern int g_config_kegs_update_needed; + +#define CLK_IDLE 1 +#define CLK_TIME 2 +#define CLK_INTERNAL 3 +#define CLK_BRAM1 4 +#define CLK_BRAM2 5 + +int g_clk_mode = CLK_IDLE; +int g_clk_read = 0; +int g_clk_reg1 = 0; + +extern word32 g_c033_data; +extern word32 g_c034_val; + +byte g_bram[2][256]; +byte *g_bram_ptr = &(g_bram[0][0]); + +word32 g_clk_cur_time = 0xa0000000; +int g_clk_next_vbl_update = 0; + +double +get_dtime() +{ + +#ifdef _WIN32 + FILETIME filetime; + dword64 dlow, dhigh; +#else + struct timeval tp1; + double dsec; + double dusec; +#endif + double dtime; + + /* Routine used to return actual system time as a double */ + /* No routine cares about the absolute value, only deltas--maybe */ + /* take advantage of that in future to increase usec accuracy */ + +#ifdef _WIN32 + //dtime = timeGetTime() / 1000.0; + GetSystemTimePreciseAsFileTime(&filetime); + dlow = filetime.dwLowDateTime; + dhigh = filetime.dwHighDateTime; + dlow = (dhigh << 32) | dlow; + dtime = (double)dlow; + dtime = dtime / (1000*1000*10.0); // FILETIME is in 100ns incs +#else + +# ifdef SOLARIS + gettimeofday(&tp1, (void *)0); +# else + gettimeofday(&tp1, (struct timezone *)0); +# endif + + dsec = (double)tp1.tv_sec; + dusec = (double)tp1.tv_usec; + + dtime = dsec + (dusec / (1000.0 * 1000.0)); +#endif + + return dtime; +} + +int +micro_sleep(double dtime) +{ +#ifndef _WIN32 + struct timeval Timer; + int ret; +#endif + + if(dtime <= 0.0) { + return 0; + } + if(dtime >= 1.0) { + halt_printf("micro_sleep called with %f!!\n", dtime); + return -1; + } + +#if 0 + printf("usleep: %f\n", dtime); +#endif + +#ifdef _WIN32 + Sleep((word32)(dtime * 1000)); +#else + Timer.tv_sec = 0; + Timer.tv_usec = (dtime * 1000000.0); + if( (ret = select(0, 0, 0, 0, &Timer)) < 0) { + fprintf(stderr, "micro_sleep (select) ret: %d, errno: %d\n", + ret, errno); + return -1; + } +#endif + return 0; +} + +void +clk_bram_zero() +{ + int i, j; + + /* zero out all bram */ + for(i = 0; i < 2; i++) { + for(j = 0; j < 256; j++) { + g_bram[i][j] = 0; + } + } + g_bram_ptr = &(g_bram[0][0]); +} + +void +clk_bram_set(int bram_num, int offset, int val) +{ + if((bram_num < 0) || (bram_num > 2)) { + printf("bram_num %d out of range\n", bram_num); + return; + } + if((offset < 0) || (offset > 0x100)) { + printf("bram offset %05x out of range\n", offset); + return; + } + g_bram[bram_num][offset] = val; +} + +void +clk_setup_bram_version() +{ + if(g_rom_version < 3) { + g_bram_ptr = (&g_bram[0][0]); // ROM 01 + } else { + g_bram_ptr = (&g_bram[1][0]); // ROM 03 + } +} + +void +clk_write_bram(FILE *fconf) +{ + int i, j, k; + + for(i = 0; i < 2; i++) { + fprintf(fconf, "\n"); + for(j = 0; j < 256; j += 16) { + fprintf(fconf, "bram%d[%02x] =", 2*i + 1, j); + for(k = 0; k < 16; k++) { + fprintf(fconf, " %02x", g_bram[i][j+k]); + } + fprintf(fconf, "\n"); + } + } +} + +void +update_cur_time() +{ + struct tm *tm_ptr; + time_t cur_time, secs, secs2; + + cur_time = time(0); + + /* Figure out the timezone (effectively) by diffing two times. */ + /* this is probably not right for a few hours around daylight savings*/ + /* time transition */ + secs2 = mktime(gmtime(&cur_time)); + tm_ptr = localtime(&cur_time); + secs = mktime(tm_ptr); + + secs2 = secs2 - secs; // this is the timezone offset +#ifdef MAC + /* Mac OS X's mktime function modifies the tm_ptr passed in for */ + /* the CDT timezone and breaks this algorithm. So on a Mac, we */ + /* will use the tm_ptr->gmtoff member to correct the time */ + secs = secs + tm_ptr->tm_gmtoff; +#else + secs = cur_time - secs2; + + if(tm_ptr->tm_isdst) { + /* adjust for daylight savings time */ + secs += 3600; + } +#endif + + /* add in secs to make date based on Apple Jan 1, 1904 instead of */ + /* Unix's Jan 1, 1970 */ + /* So add in 66 years and 17 leap year days (1904 is a leap year) */ + secs += ((66*365) + 17) * (24*3600); + + g_clk_cur_time = (word32)secs; + + clk_printf("Update g_clk_cur_time to %08x\n", g_clk_cur_time); + g_clk_next_vbl_update = g_vbl_count + 5; +} + +/* clock_update called by sim65816 every VBL */ +void +clock_update() +{ + /* Nothing to do */ +} + +void +clock_update_if_needed() +{ + int diff; + + diff = g_clk_next_vbl_update - g_vbl_count; + if(diff < 0 || diff > 60) { + /* Been a while, re-read the clock */ + update_cur_time(); + } +} + +void +clock_write_c034(word32 val) +{ + g_c034_val = val & 0x7f; + if((val & 0x80) != 0) { + if((val & 0x20) == 0) { + printf("c034 write not last = 1\n"); + /* set_halt(1); */ + } + do_clock_data(); + } +} + + +void +do_clock_data() +{ + word32 mask, read, op; + + clk_printf("In do_clock_data, g_clk_mode: %02x\n", g_clk_mode); + + read = g_c034_val & 0x40; + switch(g_clk_mode) { + case CLK_IDLE: + g_clk_read = (g_c033_data >> 7) & 1; + g_clk_reg1 = (g_c033_data >> 2) & 3; + op = (g_c033_data >> 4) & 7; + if(!read) { + /* write */ + switch(op) { + case 0x0: /* Read/write seconds register */ + g_clk_mode = CLK_TIME; + clock_update_if_needed(); + break; + case 0x3: /* internal registers */ + g_clk_mode = CLK_INTERNAL; + if(g_clk_reg1 & 0x2) { + /* extend BRAM read */ + g_clk_mode = CLK_BRAM2; + g_clk_reg1 = (g_c033_data & 7) << 5; + } + break; + case 0x2: /* read/write ram 0x10-0x13 */ + g_clk_mode = CLK_BRAM1; + g_clk_reg1 += 0x10; + break; + case 0x4: /* read/write ram 0x00-0x0f */ + case 0x5: case 0x6: case 0x7: + g_clk_mode = CLK_BRAM1; + g_clk_reg1 = (g_c033_data >> 2) & 0xf; + break; + default: + halt_printf("Bad c033_data in CLK_IDLE: %02x\n", + g_c033_data); + } + } else { + printf("clk read from IDLE mode!\n"); + /* set_halt(1); */ + g_clk_mode = CLK_IDLE; + } + break; + case CLK_BRAM2: + if(!read) { + /* get more bits of bram addr */ + if((g_c033_data & 0x83) == 0x00) { + /* more address bits */ + g_clk_reg1 |= ((g_c033_data >> 2) & 0x1f); + g_clk_mode = CLK_BRAM1; + } else { + halt_printf("CLK_BRAM2: c033_data: %02x!\n", + g_c033_data); + g_clk_mode = CLK_IDLE; + } + } else { + halt_printf("CLK_BRAM2: clock read!\n"); + g_clk_mode = CLK_IDLE; + } + break; + case CLK_BRAM1: + /* access battery ram addr g_clk_reg1 */ + if(read) { + if(g_clk_read) { + /* Yup, read */ + g_c033_data = g_bram_ptr[g_clk_reg1]; + clk_printf("Reading BRAM loc %02x: %02x\n", + g_clk_reg1, g_c033_data); + } else { + halt_printf("CLK_BRAM1: said wr, now read\n"); + } + } else { + if(g_clk_read) { + halt_printf("CLK_BRAM1: said rd, now write\n"); + } else { + /* Yup, write */ + clk_printf("Writing BRAM loc %02x with %02x\n", + g_clk_reg1, g_c033_data); + g_bram_ptr[g_clk_reg1] = g_c033_data; + g_config_kegs_update_needed = 1; + } + } + g_clk_mode = CLK_IDLE; + break; + case CLK_TIME: + if(read) { + if(g_clk_read == 0) { + halt_printf("Reading time, but in set mode!\n"); + } + g_c033_data = (g_clk_cur_time >> (g_clk_reg1 * 8)) & + 0xff; + clk_printf("Returning time byte %d: %02x\n", + g_clk_reg1, g_c033_data); + } else { + /* Write */ + if(g_clk_read) { + halt_printf("Write time, but in read mode!\n"); + } + clk_printf("Writing TIME loc %d with %02x\n", + g_clk_reg1, g_c033_data); + mask = 0xff << (8 * g_clk_reg1); + + g_clk_cur_time = (g_clk_cur_time & (~mask)) | + ((g_c033_data & 0xff) << (8 * g_clk_reg1)); + } + g_clk_mode = CLK_IDLE; + break; + case CLK_INTERNAL: + if(read) { + printf("Attempting to read internal reg %02x!\n", + g_clk_reg1); + } else { + switch(g_clk_reg1) { + case 0x0: /* test register */ + if(g_c033_data & 0xc0) { + printf("Writing test reg: %02x!\n", + g_c033_data); + /* set_halt(1); */ + } + break; + case 0x1: /* write protect reg */ + clk_printf("Writing clk wr_protect: %02x\n", + g_c033_data); + if(g_c033_data & 0x80) { + printf("Stop, wr clk wr_prot: %02x\n", + g_c033_data); + /* set_halt(1); */ + } + break; + default: + halt_printf("Writing int reg: %02x with %02x\n", + g_clk_reg1, g_c033_data); + } + } + g_clk_mode = CLK_IDLE; + break; + default: + halt_printf("clk mode: %d unknown!\n", g_clk_mode); + g_clk_mode = CLK_IDLE; + break; + } +} + diff --git a/gsplus/src/comp_swift b/gsplus/src/comp_swift new file mode 100644 index 0000000..151b41b --- /dev/null +++ b/gsplus/src/comp_swift @@ -0,0 +1,13 @@ +#!/bin/bash +# $KmKId: comp_swift,v 1.2 2020-12-11 22:58:32+00 kentd Exp $ + +echo "args are: " "$@" +/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift -frontend -c \ + -enable-objc-interop \ + -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \ + -swift-version 4 -Onone \ + -serialize-debugging-options \ + -import-objc-header Kegs-Bridging-Header.h \ + -module-name Kegs \ + "$@" + diff --git a/gsplus/src/compile_time.c b/gsplus/src/compile_time.c new file mode 100644 index 0000000..f576f92 --- /dev/null +++ b/gsplus/src/compile_time.c @@ -0,0 +1,2 @@ +char g_compile_time[] = "Compiled: " __DATE__ " " __TIME__ ; + diff --git a/gsplus/src/config.c b/gsplus/src/config.c new file mode 100644 index 0000000..57c9e74 --- /dev/null +++ b/gsplus/src/config.c @@ -0,0 +1,4613 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2025 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// g_cfg_slotdrive: 0: not doing file selection at all +// 1-0x7ff: doing file selection for given slot/drive +// 0xfff: doing file selection for ROM or charrom +#include "defc.h" +#include +#include "config.h" + +#ifdef _WIN32 +# include "win_dirent.h" +#else +# include +#endif + +extern int Verbose; +extern word32 g_vbl_count; + +extern int g_track_bytes_35[]; +extern int g_c031_disk35; + +extern int g_cur_a2_stat; +extern byte *g_slow_memory_ptr; +extern byte *g_rom_fc_ff_ptr; +extern byte *g_rom_cards_ptr; +extern double g_cur_dcycs; +extern int g_rom_version; + +extern word32 g_adb_repeat_vbl; +extern int g_adb_swap_command_option; + +extern int g_limit_speed; +extern int g_zip_speed_mhz; +extern int g_force_depth; +int g_serial_cfg[2] = { 0, 1 }; // Slot 1=0=Real serial (printer?) + // Slot 2=1=Virt Modem +int g_serial_mask[2] = { 0, 0 }; +char *g_serial_remote_ip[2] = { "", "" }; // cfg_init_menus will malloc() +int g_serial_remote_port[2] = { 9100, 9100 }; +char *g_serial_device[2] = { "/dev/tty.USB.0", "/dev/tty.USB.1" }; + // cfg_init_menus() will malloc() the above +int g_serial_win_device[2] = { 0, 0 }; // Disabled +int g_serial_modem_response_code = 10; // 10 - 2400 +int g_serial_modem_allow_incoming = 0; // 1 for BBS'es +int g_serial_modem_init_telnet = 1; // 1 for BBS'es + +extern word32 g_mem_size_base; +extern word32 g_mem_size_exp; +extern int g_video_line_update_interval; +extern int g_user_halt_bad; +extern int g_joystick_type; +extern int g_joystick_scale_factor_x; +extern int g_joystick_scale_factor_y; +extern int g_joystick_trim_amount_x; +extern int g_joystick_trim_amount_y; +extern int g_swap_paddles; +extern int g_invert_paddles; +extern int g_voc_enable; +extern int g_status_enable; +extern int g_mainwin_width; +extern int g_mainwin_height; +extern int g_mainwin_xpos; +extern int g_mainwin_ypos; + +extern int g_screen_index[]; +extern word32 g_full_refresh_needed; +extern word32 g_a2_screen_buffer_changed; + +extern int g_key_down; +extern const char g_kegs_version_str[]; + +int g_config_control_panel = 0; +char g_config_kegs_name[1024] = { 0 }; +char g_cfg_cwd_str[CFG_PATH_MAX] = { 0 }; + +int g_config_kegs_auto_update = 1; +int g_config_kegs_update_needed = 0; +int g_cfg_newdisk_select = 0; +int g_cfg_newdisk_blocks = 0; +int g_cfg_newdisk_blocks_default = 140*2; +int g_cfg_newdisk_type = 1; +int g_cfg_newdisk_type_default = 1; +word32 g_cfg_newdisk_slotdrive = 0; + +const char *g_config_kegs_name_list[] = { + "config.kegs", "kegs_conf", ".config.kegs", 0 +}; + +int g_highest_smartport_unit = -1; +int g_reparse_delay = 0; +int g_user_page2_shadow = 1; + +char g_cfg_printf_buf[CFG_PRINTF_BUFSIZE]; +char g_config_kegs_buf[CONF_BUF_LEN]; + +#define CFG_ERR_BUFSIZE 80 +#define CFG_ERR_MAX 5 + +int g_cfg_err_pos = 0; +char g_cfg_err_bufs[CFG_ERR_MAX][CFG_ERR_BUFSIZE]; + +int g_cfg_curs_x = 0; +int g_cfg_curs_y = 0; +int g_cfg_curs_inv = 0; +int g_cfg_curs_mousetext = 0; +int g_cfg_screen_changed = 0; +byte g_cfg_screen[24][80]; + +#if defined(MAC) || defined(_WIN32) +int g_cfg_ignorecase = 1; // Ignore case in filenames +#else +int g_cfg_ignorecase = 0; +#endif + +#define CFG_MAX_OPTS 34 +#define CFG_OPT_MAXSTR 100 + +int g_cfg_opts_vals[CFG_MAX_OPTS]; +char g_cfg_opts_str[CFG_PATH_MAX]; +char g_cfg_opt_buf[CFG_OPT_MAXSTR]; +char g_cfg_edit_buf[CFG_OPT_MAXSTR]; + +char *g_cfg_rom_path = "ROM"; // config_init_menus will malloc +char *g_cfg_charrom_path = "Undefined"; // config_init_menus will malloc +int g_cfg_charrom_pos = 0; +char *g_cfg_file_def_name = "Undefined"; +char **g_cfg_file_strptr = 0; +int g_cfg_file_min_size = 1024; +int g_cfg_file_max_size = 2047*1024*1024; +int g_cfg_edit_type = 0; +void *g_cfg_edit_ptr = 0; + +#define MAX_PARTITION_BLK_SIZE 65536 + +char *g_argv0_path = "."; + +const char *g_kegs_default_paths[] = { "", "./", "${HOME}/", + "${HOME}/Library/KEGS/", "${0}/../", "${0}/", + "${0}/Contents/Resources/", 0 }; + + +extern Cfg_menu g_cfg_main_menu[]; + +#define KNMP(a) &a, #a, 0 +#define KNM(a) &a, #a + +Cfg_menu g_cfg_disk_menu[] = { +{ "Disk Configuration", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, +{ "s5d1 = ", 0, 0, 0, CFGTYPE_DISK + 0x5000 }, +{ "s5d2 = ", 0, 0, 0, CFGTYPE_DISK + 0x5010 }, +{ "", 0, 0, 0, 0 }, +{ "s6d1 = ", 0, 0, 0, CFGTYPE_DISK + 0x6000 }, +{ "s6d2 = ", 0, 0, 0, CFGTYPE_DISK + 0x6010 }, +{ "", 0, 0, 0, 0 }, +{ "s7d1 = ", 0, 0, 0, CFGTYPE_DISK + 0x7000 }, +{ "s7d2 = ", 0, 0, 0, CFGTYPE_DISK + 0x7010 }, +{ "s7d3 = ", 0, 0, 0, CFGTYPE_DISK + 0x7020 }, +{ "s7d4 = ", 0, 0, 0, CFGTYPE_DISK + 0x7030 }, +{ "s7d5 = ", 0, 0, 0, CFGTYPE_DISK + 0x7040 }, +{ "s7d6 = ", 0, 0, 0, CFGTYPE_DISK + 0x7050 }, +{ "s7d7 = ", 0, 0, 0, CFGTYPE_DISK + 0x7060 }, +{ "s7d8 = ", 0, 0, 0, CFGTYPE_DISK + 0x7070 }, +{ "s7d9 = ", 0, 0, 0, CFGTYPE_DISK + 0x7080 }, +{ "s7d10= ", 0, 0, 0, CFGTYPE_DISK + 0x7090 }, +{ "s7d11= ", 0, 0, 0, CFGTYPE_DISK + 0x70a0 }, +{ "s7d12= ", 0, 0, 0, CFGTYPE_DISK + 0x70b0 }, +{ "", 0, 0, 0, 0 }, +{ "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, +{ 0, 0, 0, 0, 0 }, +}; + +Cfg_menu g_cfg_newslot6_menu[] = { +{ "New 5.25\" disk image Configuration", g_cfg_newslot6_menu, 0, 0, + CFGTYPE_MENU }, +{ "size,280,140KB", KNM(g_cfg_newdisk_blocks), + &g_cfg_newdisk_blocks_default, CFGTYPE_INT }, +{ "Type,1,ProDOS/DOS 3.3,2,WOZ image,3,Dynamic ProDOS directory", + KNM(g_cfg_newdisk_type), + &g_cfg_newdisk_type_default, CFGTYPE_INT }, +{ "", 0, 0, 0, 0 }, +{ "Create and name the image", (void *)cfg_name_new_image, 0, 0, CFGTYPE_FUNC }, +{ "", 0, 0, 0, 0 }, +{ "Cancel, go back to Disk Config", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, +{ 0, 0, 0, 0, 0 }, +}; + +Cfg_menu g_cfg_newslot5_menu[] = { +{ "New 3.5\" disk image Configuration", g_cfg_newslot5_menu, 0, 0,CFGTYPE_MENU}, +{ "size,1600,800KB", KNM(g_cfg_newdisk_blocks), + &g_cfg_newdisk_blocks_default, CFGTYPE_INT }, +{ "Type,1,ProDOS,2,WOZ image,3,Dynamic ProDOS directory", + KNM(g_cfg_newdisk_type), + &g_cfg_newdisk_type_default, CFGTYPE_INT }, +{ "", 0, 0, 0, 0 }, +{ "Create and name the image", (void *)cfg_name_new_image, 0, 0, CFGTYPE_FUNC }, +{ "", 0, 0, 0, 0 }, +{ "Cancel, go back to Disk Config", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, +{ 0, 0, 0, 0, 0 }, +}; + +Cfg_menu g_cfg_newslot7_menu[] = { +{ "New Smartport disk image Configuration", g_cfg_newslot7_menu, 0, 0, + CFGTYPE_MENU}, +{ "size,1600,800KB,3200,1600KB,16384,8MB,32768,16MB,65535,32MB", + KNM(g_cfg_newdisk_blocks), &g_cfg_newdisk_blocks_default, + CFGTYPE_INT }, +{ "Type,1,ProDOS,3,Dynamic ProDOS directory", KNM(g_cfg_newdisk_type), + &g_cfg_newdisk_type_default, CFGTYPE_INT }, +{ "", 0, 0, 0, 0 }, +{ "Create and name the image", (void *)cfg_name_new_image, 0, 0, CFGTYPE_FUNC }, +{ "", 0, 0, 0, 0 }, +{ "Cancel, go back to Disk Config", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, +{ 0, 0, 0, 0, 0 }, +}; + +Cfg_menu g_cfg_joystick_menu[] = { +{ "Joystick Configuration", g_cfg_joystick_menu, 0, 0, CFGTYPE_MENU }, +{ "Joystick Emulation,0,Keypad Joystick,1,Mouse Joystick,2,Native Joystick 1," + "3,Native Joystick 2", KNMP(g_joystick_type), CFGTYPE_INT }, +{ "Joystick Scale X,0x100,Standard,0x119,+10%,0x133,+20%," + "0x150,+30%,0xb0,-30%,0xcd,-20%,0xe7,-10%", + KNMP(g_joystick_scale_factor_x), CFGTYPE_INT }, +{ "Joystick Scale Y,0x100,Standard,0x119,+10%,0x133,+20%," + "0x150,+30%,0xb0,-30%,0xcd,-20%,0xe7,-10%", + KNMP(g_joystick_scale_factor_y), CFGTYPE_INT }, +{ "Joystick Trim X", KNMP(g_joystick_trim_amount_x), CFGTYPE_INT }, +{ "Joystick Trim Y", KNMP(g_joystick_trim_amount_y), CFGTYPE_INT }, +{ "Swap Joystick X and Y,0,Normal operation,1,Paddle 1 and Paddle 0 swapped", + KNMP(g_swap_paddles), CFGTYPE_INT }, +{ "Invert Joystick,0,Normal operation,1,Left becomes right and up becomes down", + KNMP(g_invert_paddles), CFGTYPE_INT }, +{ "", 0, 0, 0, 0 }, +{ "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, +{ 0, 0, 0, 0, 0 }, +}; + +Cfg_menu g_cfg_rom_menu[] = { +{ "ROM File Selection", g_cfg_rom_menu, 0, 0, CFGTYPE_MENU }, +{ "ROM File", KNMP(g_cfg_rom_path), CFGTYPE_FILE }, +{ "", 0, 0, 0, 0 }, +{ "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, +{ 0, 0, 0, 0, 0 }, +}; + +Cfg_menu g_cfg_charrom_menu[] = { +{ "Character ROM File Selection", g_cfg_charrom_menu, 0, 0, CFGTYPE_MENU }, +{ "Character ROM File", KNMP(g_cfg_charrom_path), CFGTYPE_FILE }, +{ "Character Set,0,0x00 US Enhanced,1,0x01 US Un-enhanced," + "2,0x02 Clinton Turner V1 Enhanced,3,0x03 ReActiveMicro Enhanced," + "4,0x04 Dan Paymar Enhanced,5,0x05 Blippo Black Enhanced," + "6,0x06 Byte Enhanced,7,0x07 Colossal Enhanced," + "8,0x08 Count Enhanced,9,0x09 Flow Enhanced," + "10,0x0a Gothic Enhanced,11,0x0b Outline Enhanced," + "12,0x0c Pigfont Enhanced,13,0x0d Pinocchio Enhanced," + "14,0x0e Slant Enhanced,15,0x0f Stop Enhanced," + "16,0x10 Euro Un-Enhanced,17,0x11 Euro Enhanced," + "18,0x12 Clinton Turner V2 Enhanced,19,0x13 Improved German Enhanced," + "20,0x14 Improved German Un-Enhanced,21,0x15 Franch Canadian Enhanced," + "22,0x16 French Canadian Un-Enhanced,23,0x17 Hebrew Enhanced," + "24,0x18 Hebrew Un-Enhanced,25,0x19 Apple II+ Enhanced," + "26,0x1a Apple II+ Un-Enhanced,27,0x1b Katakana Enhanced," + "28,0x1c Cyrillic Enhanced,29,0x1d Greek Enhanced," + "30,0x1e Esperanto Enhanced,31,0x1f Videx Enhanced", + KNMP(g_cfg_charrom_pos), CFGTYPE_INT }, +{ "", 0, 0, 0, 0 }, +{ "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, +{ 0, 0, 0, 0, 0 }, +}; + +Cfg_menu g_cfg_serial_menu[] = { +{ "Serial Port Configuration", g_cfg_serial_menu, 0, 0, CFGTYPE_MENU }, +{ "Slot 1 (port 0) settings", 0, 0, 0, 0 }, +{ " Main setting ,0,Use Real Device below,1,Use a virtual modem," + "2,Use Remote IP below,3,Use incoming port 6501", + KNMP(g_serial_cfg[0]), CFGTYPE_INT }, +{ " Status ", (void *)cfg_get_serial0_status, 0, 0, CFGTYPE_FUNC }, +{ " Real Device ", KNMP(g_serial_device[0]), CFGTYPE_FILE }, +{ " Windows Device,0,Disabled,1,COM1,2,COM2,3,COM3,4,COM4", + KNMP(g_serial_win_device[0]), CFGTYPE_INT }, +{ " Remote IP ", KNMP(g_serial_remote_ip[0]), CFGTYPE_STR }, +{ " Remote Port ", KNMP(g_serial_remote_port[0]), CFGTYPE_INT }, +{ " Serial Mask ,0,Send full 8-bit data,1,Mask off high bit", + KNMP(g_serial_mask[0]), CFGTYPE_INT }, +{ "", 0, 0, 0, 0 }, +{ "Slot 2 (port 1) settings", 0, 0, 0, 0, }, +{ " Main setting ,0,Use Real Device below,1,Use a virtual modem," + "2,Use Remote IP below,3,Use incoming port 6502", + KNMP(g_serial_cfg[1]), CFGTYPE_INT }, +{ " Status ", (void *)cfg_get_serial1_status, 0, 0, CFGTYPE_FUNC }, +{ " Real Device ", KNMP(g_serial_device[1]), CFGTYPE_FILE }, +{ " Windows Device,1,COM1,2,COM2,3,COM3,4,COM4", + KNMP(g_serial_win_device[1]), CFGTYPE_INT }, +{ " Remote IP ", KNMP(g_serial_remote_ip[1]), CFGTYPE_STR }, +{ " Remote Port ", KNMP(g_serial_remote_port[1]), CFGTYPE_INT }, +{ " Serial Mask ,0,Send full 8-bit data,1,Mask off high bit", + KNMP(g_serial_mask[1]), CFGTYPE_INT }, +{ "", 0, 0, 0, 0 }, +{ "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, +{ 0, 0, 0, 0, 0 }, +}; + +Cfg_menu g_cfg_modem_menu[] = { +{ "Virtual Modem Configuration", g_cfg_modem_menu, 0, 0, CFGTYPE_MENU }, +{ "Modem Speed Response Code ,5,5 - CONNECT 1200,10,10 - CONNECT 2400," + "12,12 - CONNECT 9600 (HAYES/Warp6)," + "13,13 - CONNECT 9600 (USR/HST)," + "14,14 - CONNECT 19200 (HAYES/Warp6)," + "28,28 - CONNECT 38400 (HAYES/Warp6)", + KNMP(g_serial_modem_response_code), CFGTYPE_INT }, +{ "Allow Modem incoming on 6501/6502 ,0,Outgoing only," + "1,Incoming and outgoing (BBS)", + KNMP(g_serial_modem_allow_incoming), CFGTYPE_INT }, +{ "Send Telnet Escape codes ,0,Disable Telnet,1,Send Telnet codes", + KNMP(g_serial_modem_init_telnet), CFGTYPE_INT }, +{ "", 0, 0, 0, 0 }, +{ "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, +{ 0, 0, 0, 0, 0 }, +}; + + + +Cfg_menu g_cfg_video_menu[] = { +{ "Force X-windows display depth", KNMP(g_force_depth), CFGTYPE_INT }, +{ "Enable VOC,0,Disabled,1,Enabled", KNMP(g_voc_enable), CFGTYPE_INT }, +{ "Default Main Window width", KNMP(g_mainwin_width), CFGTYPE_INT }, +{ "Default Main Window height", KNMP(g_mainwin_height), CFGTYPE_INT }, +{ "Main Window X position", KNMP(g_mainwin_xpos), CFGTYPE_INT }, +{ "Main Window Y position", KNMP(g_mainwin_ypos), CFGTYPE_INT }, +{ "3200 Color Enable,0,Auto (Full if fast enough),1,Full (Update every line)," + "8,Off (Update video every 8 lines)", + KNMP(g_video_line_update_interval), CFGTYPE_INT }, +{ "Dump text screen to file", (void *)cfg_text_screen_dump, 0, 0, CFGTYPE_FUNC}, +{ "", 0, 0, 0, 0 }, +{ "Back to Main Config", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, +{ 0, 0, 0, 0, 0 }, +}; + +Cfg_menu g_cfg_main_menu[] = { +{ "KEGS Configuration", g_cfg_main_menu, 0, 0, CFGTYPE_MENU }, +{ "Disk Configuration", g_cfg_disk_menu, 0, 0, CFGTYPE_MENU }, +{ "Joystick Configuration", g_cfg_joystick_menu, 0, 0, CFGTYPE_MENU }, +{ "ROM File Selection", g_cfg_rom_menu, 0, 0, CFGTYPE_MENU }, +{ "Character ROM Selection", g_cfg_charrom_menu, 0, 0, CFGTYPE_MENU }, +{ "Serial Port Configuration", g_cfg_serial_menu, 0, 0, CFGTYPE_MENU }, +{ "Virtual Modem Configuration", g_cfg_modem_menu, 0, 0, CFGTYPE_MENU }, +{ "Video Settings", g_cfg_video_menu, 0, 0, CFGTYPE_MENU }, +{ "Auto-update config.kegs,0,Manual,1,Immediately", + KNMP(g_config_kegs_auto_update), CFGTYPE_INT }, +{ "Speed,0,Unlimited,1,1.0MHz,2,2.8MHz,3,8.0MHz (Zip)", + KNMP(g_limit_speed), CFGTYPE_INT }, +{ "ZipGS Speed,8,8MHz,16,16MHz,32,32MHz,64,64MHz,128,128MHz", + KNMP(g_zip_speed_mhz), CFGTYPE_INT }, +{ "Expansion Mem Size,0,0MB,0x100000,1MB,0x200000,2MB,0x300000,3MB," + "0x400000,4MB,0x600000,6MB,0x800000,8MB,0xa00000,10MB,0xc00000,12MB," + "0xe00000,14MB", KNMP(g_mem_size_exp), CFGTYPE_INT }, +{ "Show Status lines,0,Disabled,1,Enabled", KNMP(g_status_enable), CFGTYPE_INT}, +{ "Code Red Halts,0,Do not stop on bad accesses,1,Enter debugger on bad " + "accesses", KNMP(g_user_halt_bad), CFGTYPE_INT }, +{ "Enable Text Page 2 Shadow,0,Disabled on ROM 01 (matches real hardware)," + "1,Enabled on ROM 01 and 03", + KNMP(g_user_page2_shadow), CFGTYPE_INT }, +{ "Swap Command/Option keys,0,Disabled,1,Swapped", + KNMP(g_adb_swap_command_option), CFGTYPE_INT }, +{ "", 0, 0, 0, 0 }, +{ "Save changes to config.kegs", (void *)config_write_config_kegs_file, 0, 0, + CFGTYPE_FUNC }, +{ "", 0, 0, 0, 0 }, +{ "Exit Config (or press F4)", (void *)cfg_exit, 0, 0, CFGTYPE_FUNC }, +{ 0, 0, 0, 0, 0 }, +}; + + +#define CFG_MAX_DEFVALS 128 +Cfg_defval g_cfg_defvals[CFG_MAX_DEFVALS]; +int g_cfg_defval_index = 0; + +word32 g_cfg_slotdrive = 0; +int g_cfg_select_partition = -1; +char g_cfg_tmp_path[CFG_PATH_MAX]; +char g_cfg_file_path[CFG_PATH_MAX]; +char g_cfg_file_cachedpath[CFG_PATH_MAX]; +char g_cfg_file_cachedreal[CFG_PATH_MAX]; +char g_cfg_file_curpath[CFG_PATH_MAX]; +char g_cfg_file_shortened[CFG_PATH_MAX]; +char g_cfg_file_match[CFG_PATH_MAX]; + +char g_cfg_part_path[CFG_PATH_MAX]; +int g_cfg_partition_is_zip = 0; + +Cfg_listhdr g_cfg_dirlist = { 0 }; +Cfg_listhdr g_cfg_partitionlist = { 0 }; + +int g_cfg_file_pathfield = 0; + +const char *g_kegs_rom_names[] = { "ROM", "ROM", "ROM.01", "ROM.03", + "APPLE2GS.ROM", "APPLE2GS.ROM2", "xgs.rom", "XGS.ROM", "Rom03gd", + "342-0077-b", // MAME ROM.01 + 0 }; + /* First entry is special--it will be overwritten by g_cfg_rom_path */ + +const char *g_kegs_c1rom_names[] = { 0 }; +const char *g_kegs_c2rom_names[] = { 0 }; +const char *g_kegs_c3rom_names[] = { 0 }; +const char *g_kegs_c4rom_names[] = { 0 }; +const char *g_kegs_c5rom_names[] = { 0 }; +const char *g_kegs_c6rom_names[] = { "c600.rom", "controller.rom", "disk.rom", + "DISK.ROM", "diskII.prom", 0 }; +const char *g_kegs_c7rom_names[] = { 0 }; + +const char **g_kegs_rom_card_list[8] = { + 0, g_kegs_c1rom_names, + g_kegs_c2rom_names, g_kegs_c3rom_names, + g_kegs_c4rom_names, g_kegs_c5rom_names, + g_kegs_c6rom_names, g_kegs_c7rom_names }; + +byte g_rom_c600_rom01_diffs[256] = { + 0x00, 0x00, 0x00, 0x00, 0xc6, 0x00, 0xe2, 0x00, + 0xd0, 0x50, 0x0f, 0x77, 0x5b, 0xb9, 0xc3, 0xb1, + 0xb1, 0xf8, 0xcb, 0x4e, 0xb8, 0x60, 0xc7, 0x2e, + 0xfc, 0xe0, 0xbf, 0x1f, 0x66, 0x37, 0x4a, 0x70, + 0x55, 0x2c, 0x3c, 0xfc, 0xc2, 0xa5, 0x08, 0x29, + 0xac, 0x21, 0xcc, 0x09, 0x55, 0x03, 0x17, 0x35, + 0x4e, 0xe2, 0x0c, 0xe9, 0x3f, 0x9d, 0xc2, 0x06, + 0x18, 0x88, 0x0d, 0x58, 0x57, 0x6d, 0x83, 0x8c, + 0x22, 0xd3, 0x4f, 0x0a, 0xe5, 0xb7, 0x9f, 0x7d, + 0x2c, 0x3e, 0xae, 0x7f, 0x24, 0x78, 0xfd, 0xd0, + 0xb5, 0xd6, 0xe5, 0x26, 0x85, 0x3d, 0x8d, 0xc9, + 0x79, 0x0c, 0x75, 0xec, 0x98, 0xcc, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x39, 0x00, 0x35, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, + 0x6c, 0x44, 0xce, 0x4c, 0x01, 0x08, 0x00, 0x00 +}; + +byte g_rom_c700[256] = { + //0xa2, 0x20, 0xa2, 0x00, 0xa2, 0x03, 0xc9, 0x3c, // For Apple //e + 0xa2, 0x20, 0xa2, 0x00, 0xa2, 0x03, 0xc9, 0x00, + //^^= LDX #$20; LDY #$00, LDX #$03 CMP #$3c + 0x80, 0x0c, 0x18, 0xb8, 0x70, 0x38, 0xb8, 0x42, + //^^= BRA $c716; CLC; CLV; BVS $c746 (SEC); CLV; WDM $c7,$00 + 0xc7, 0x00, 0x60, 0x00, 0x00, 0xea, 0xe2, 0x41, + //^^= ...; RTS..............; NOP; SEP #$41 + 0x70, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + //^^= BVS $c70f + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // So does WDM $c7,$00 with psr.v=1 for $c700; v=0,c=0 for $c70a, + // and v=0,c=1 for $c70d + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0xbf, 0x0a +}; + +Cfg_menu *g_menuptr = 0; +int g_menu_line = 1; +int g_menu_inc = 1; +int g_menu_max_line = 1; +int g_menu_redraw_needed = 1; + +#define MAX_CFG_ARGV_OVERRIDES 64 + +int g_cfg_argv_num_overrides = 0; +char *g_cfg_argv_overrides[MAX_CFG_ARGV_OVERRIDES]; + +int +config_add_argv_override(const char *str1, const char *str2) +{ + const char *equal_ptr; + char *str; + int ret, pos, len; + + // Handle things like "rom=rompath" and "rom", "rompath" + // Look through str1, see if there is '=', if so ignore str2 + equal_ptr = strchr(str1, '='); + ret = 1; + if(equal_ptr) { // str1 has '=' in it + ret = 0; // Don't eat up str2 argument + str = kegs_malloc_str(str1); + } else { + // We need to form a new string of str1, =, str2 + len = (int)(strlen(str1) + strlen(str2) + 2); + str = malloc(len); + cfg_strncpy(str, str1, len); + cfg_strlcat(str, "=", len); + cfg_strlcat(str, str2, len); + } + pos = g_cfg_argv_num_overrides++; + if(pos >= MAX_CFG_ARGV_OVERRIDES) { + g_cfg_argv_num_overrides = MAX_CFG_ARGV_OVERRIDES; + fatal_printf("MAX_CFG_ARGV_OVERRIDES overflow\n"); + my_exit(5); + return ret; + } + g_cfg_argv_overrides[pos] = str; + printf("Added config override %d, %s\n", pos, str); + + return ret; +} + +void +config_set_config_kegs_name(const char *str1) +{ + int maxlen; + + // Command line override "-cfg cfg_file" + g_config_kegs_name[0] = 0; + maxlen = (int)sizeof(g_config_kegs_name); + cfg_strncpy(&g_config_kegs_name[0], str1, maxlen); +} + +void +config_init_menus(Cfg_menu *menuptr) +{ + void *voidptr; + const char *name_str; + Cfg_defval *defptr; + char **str_ptr; + char *str; + int type, pos, val; + + if(menuptr[0].defptr != 0) { + return; + } + menuptr[0].defptr = (void *)1; + pos = 0; + while(pos < 100) { + type = menuptr->cfgtype; + voidptr = menuptr->ptr; + name_str = menuptr->name_str; + if(menuptr->str == 0) { + break; + } + if(name_str != 0) { + defptr = &(g_cfg_defvals[g_cfg_defval_index++]); + if(g_cfg_defval_index >= CFG_MAX_DEFVALS) { + fatal_printf("CFG_MAX_DEFVAL overflow\n"); + my_exit(5); + return; + } + defptr->menuptr = menuptr; + defptr->intval = 0; + defptr->strval = 0; + switch(type) { + case CFGTYPE_INT: + val = *((int *)voidptr); + defptr->intval = val; + menuptr->defptr = &(defptr->intval); + break; + case CFGTYPE_FILE: + case CFGTYPE_STR: + str_ptr = (char **)menuptr->ptr; + str = *str_ptr; + // We need to malloc this string since all + // string values must be dynamically alloced + defptr->strval = str; // this can have a copy + *str_ptr = kegs_malloc_str(str); + menuptr->defptr = &(defptr->strval); + break; + default: + fatal_printf("name_str is %p = %s, but type: " + "%d\n", name_str, name_str, type); + my_exit(5); + return; + } + } + if(type == CFGTYPE_MENU) { + config_init_menus((Cfg_menu *)voidptr); + } + pos++; + menuptr++; + } +} + +void +config_init() +{ + config_init_menus(g_cfg_main_menu); + + // Find the config.kegs file + if(g_config_kegs_name[0] == 0) { + cfg_find_config_kegs_file(); + } + + config_parse_config_kegs_file(); +} + +void +cfg_find_config_kegs_file() +{ + const char **path_ptr; + int maxlen, fd; + + g_config_kegs_name[0] = 0; + maxlen = sizeof(g_config_kegs_name); + fd = 0; + if(!config_setup_kegs_file(&g_config_kegs_name[0], maxlen, + &g_config_kegs_name_list[0])) { + // Try to create config.kegs + fd = -1; + path_ptr = &g_kegs_default_paths[0]; + while(*path_ptr) { + config_expand_path(&g_config_kegs_name[0], *path_ptr, + maxlen); + cfg_strlcat(&g_config_kegs_name[0], "config.kegs", + maxlen); + printf("Trying to create %s\n", &g_config_kegs_name[0]); + fd = open(&g_config_kegs_name[0], + O_CREAT | O_TRUNC | O_WRONLY, 0x1b6); + close(fd); + if(fd >= 0) { + break; + } + path_ptr++; + } + } + + if(fd < 0) { + fatal_printf("Could not create config.kegs!\n"); + my_exit(2); + } +} + +int +config_setup_kegs_file(char *outname, int maxlen, const char **name_ptr) +{ + struct stat stat_buf; + const char **path_ptr, **cur_name_ptr; + mode_t fmt; + int ret, len; + + outname[0] = 0; + + path_ptr = &g_kegs_default_paths[0]; // Array of strings + + while(*path_ptr) { + len = config_expand_path(outname, *path_ptr, maxlen); + if(len != (int)strlen(outname)) { + printf("config_expand_path ret %d, but strlen:%d!\n", + len, (int)strlen(outname)); + } + cur_name_ptr = name_ptr; + while(*cur_name_ptr && (len < maxlen)) { + outname[len] = 0; + cfg_strlcat(outname, *cur_name_ptr, maxlen); + // printf("Doing stat on %s\n", outname); + ret = cfg_stat(outname, &stat_buf, 0); + if(ret == 0) { + fmt = stat_buf.st_mode & S_IFMT; + if(fmt != S_IFDIR) { + /* got it! */ + return 1; + } + } + cur_name_ptr++; + } + path_ptr++; + } + + return 0; +} + +int +config_expand_path(char *out_ptr, const char *in_ptr, int maxlen) +{ + char name_buf[256]; + char *tmp_ptr; + int name_len, in_char, state, pos; + + out_ptr[0] = 0; + + pos = 0; + name_len = 0; + state = 0; + + /* See if in_ptr has ${} notation, replace with getenv or argv0 */ + while(pos < (maxlen - 1)) { + in_char = *in_ptr++; + out_ptr[pos++] = in_char; + out_ptr[pos] = 0; + if(in_char == 0) { + return pos - 1; + } + if(state == 0) { + /* No $ seen yet, look for it */ + if(in_char == '$') { + state = 1; + } + } else if(state == 1) { + /* See if next char is '{' (dummy }) */ + if(in_char == '{') { /* add dummy } */ + state = 2; + name_len = 0; + pos -= 2; + out_ptr[pos] = 0; + } else { + state = 0; + } + } else if(state == 2) { + /* fill name_buf ... dummy '{' */ + pos--; + out_ptr[pos] = 0; + if(in_char == '}') { + name_buf[name_len] = 0; + + /* got token, now look it up */ + tmp_ptr = ""; + if(!strncmp("0", name_buf, 128)) { + /* Replace ${0} with g_argv0_path */ + tmp_ptr = g_argv0_path; + } else { + tmp_ptr = getenv(name_buf); + if(tmp_ptr == 0) { + tmp_ptr = ""; + } + } + pos = cfg_strlcat(out_ptr, tmp_ptr, maxlen); + state = 0; + } else { + name_buf[name_len++] = in_char; + if(name_len >= 250) { + name_len--; + } + } + } + } + + return pos; +} + +char * +cfg_exit(int get_status) +{ + /* printf("In cfg exit\n"); */ + if(get_status) { + return 0; + } + cfg_toggle_config_panel(); + + return 0; +} + +void +cfg_err_vprintf(const char *pre_str, const char *fmt, va_list ap) +{ + char *bufptr; + int pos, len, bufsize; + + pos = g_cfg_err_pos; + if(pos >= CFG_ERR_MAX) { + pos = CFG_ERR_MAX - 1; + } + bufptr = &g_cfg_err_bufs[pos][0]; + len = 0; + bufsize = CFG_ERR_BUFSIZE; + if(pre_str && *pre_str) { + cfg_strncpy(bufptr, pre_str, CFG_ERR_BUFSIZE); + cfg_strlcat(bufptr, " error: ", CFG_ERR_BUFSIZE); + len = (int)strlen(bufptr); + bufsize = CFG_ERR_BUFSIZE - len; + } + if(bufsize > 0) { + vsnprintf(&bufptr[len], bufsize, fmt, ap); + } + + fputs(bufptr, stderr); + g_cfg_err_pos = pos + 1; +} + +void +cfg_err_printf(const char *pre_str, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + cfg_err_vprintf(pre_str, fmt, ap); + va_end(ap); +} + +void +cfg_toggle_config_panel() +{ + int panel; + + panel = !g_config_control_panel; + if(g_rom_version < 0) { + panel = 1; /* Stay in config mode */ + } + if(panel != g_config_control_panel) { + cfg_set_config_panel(panel); + } +} + +void +cfg_set_config_panel(int panel) +{ + int i; + + g_config_control_panel = panel; + if(panel) { + // Entering configuration panel + video_force_reparse(); + + cfg_printf("Entering config_control_panel\n"); + + for(i = 0; i < 20; i++) { + // Toss any queued-up keypresses + if(adb_read_c000() & 0x80) { + (void)adb_access_c010(); + } + } + // HACK: Force adb keyboard (and probably mouse) to "normal"... + + cfg_home(); + + g_menu_line = 1; + g_menu_inc = 1; + g_menu_redraw_needed = 1; + g_cfg_slotdrive = 0; + g_cfg_newdisk_select = 0; + g_cfg_select_partition = -1; + } else { + // Leave config panel, go back to A2 emulation + + video_force_reparse(); + } + g_full_refresh_needed = -1; + g_a2_screen_buffer_changed = -1; + g_adb_repeat_vbl = g_vbl_count + 60; +} + +char * +cfg_text_screen_dump(int get_status) +{ + FILE *ofile; + char *bufptr; + char *filename; + + if(get_status) { + return 0; + } + filename = "kegs.screen.dump"; + printf("Writing text screen to the file %s\n", filename); + ofile = fopen(filename, "w"); + if(ofile == 0) { + fatal_printf("Could not write to file %s, (%d)\n", filename, + errno); + return 0; + } + bufptr = cfg_text_screen_str(); + fputs(bufptr, ofile); + fclose(ofile); + return 0; +} + +char g_text_screen_buf[2100] = { 0 }; + +char * +cfg_text_screen_str() +{ + char *bufptr; + int pos, start_pos, c, offset; + int i, j; + + // bufptr must be at least (81*24)+2 characters + bufptr = &g_text_screen_buf[0]; + pos = 0; + for(i = 0; i < 24; i++) { + start_pos = pos; + for(j = 0; j < 40; j++) { + offset = g_screen_index[i] + j; + if(g_cur_a2_stat & ALL_STAT_VID80) { + c = g_slow_memory_ptr[0x10400 + offset] & 0x7f; + if(c < 0x20) { + c += 0x40; + } + bufptr[pos++] = c; + } + c = g_slow_memory_ptr[0x0400 + offset] & 0x7f; + if(c < 0x20) { + c += 0x40; + } + if(c == 0x7f) { + c = ' '; + } + bufptr[pos++] = c; + } + while((pos > start_pos) && (bufptr[pos-1] == ' ')) { + /* try to strip out trailing spaces */ + pos--; + } + bufptr[pos++] = '\n'; + bufptr[pos] = 0; + } + + return bufptr; +} + +char * +cfg_get_serial0_status(int get_status) +{ + return scc_get_serial_status(get_status, 0); +} + +char * +cfg_get_serial1_status(int get_status) +{ + return scc_get_serial_status(get_status, 1); +} + +char * +cfg_get_current_copy_selection() +{ + return &g_text_screen_buf[0]; +} + +void +config_vbl_update(int doit_3_persec) +{ + if(doit_3_persec) { + if(g_config_kegs_auto_update && g_config_kegs_update_needed) { + (void)config_write_config_kegs_file(0); + } + } + return; +} + +void +cfg_file_update_rom(const char *str) +{ + cfg_file_update_ptr(&g_cfg_rom_path, str, 1); +} + +void +cfg_file_update_ptr(char **strptr, const char *str, int need_update) +{ + char *newstr; + int remote_changed, serial_changed; + int i; + + // Update whatever g_cfg_file_strptr points to. If changing + // ROM path or Charrom, then do the proper updates + + newstr = kegs_malloc_str(str); + if(!strptr) { + return; + } + if(*strptr) { + free(*strptr); + } + *strptr = newstr; + if(strptr == &(g_cfg_rom_path)) { + printf("Updated ROM file\n"); + load_roms_init_memory(); + do_reset(); + } + if(strptr == &(g_cfg_charrom_path)) { + printf("Updated Char ROM file\n"); + cfg_load_charrom(); + } + for(i = 0; i < 2; i++) { + remote_changed = 0; + serial_changed = 0; + if(strptr == &g_serial_remote_ip[i]) { + remote_changed = 1; + } + if(strptr == &g_serial_device[i]) { + serial_changed = 1; + } + if(remote_changed || serial_changed) { + scc_config_changed(i, 0, remote_changed, + serial_changed); + } + } + if(need_update) { + g_config_kegs_update_needed = 1; + printf("Set g_config_kegs_update_needed = 1\n"); + } +} + +void +cfg_int_update(int *iptr, int new_val) +{ + int old_val, cfg_changed, remote_changed, serial_changed; + int i; + + // Called to handle an integer being changed in the F4 config menus + // where it's value may need special handling + + old_val = *iptr; + *iptr = new_val; + if(old_val == new_val) { + return; + } + if(iptr == &g_cfg_charrom_pos) { + cfg_load_charrom(); + } + + for(i = 0; i < 2; i++) { + remote_changed = 0; + serial_changed = 0; + cfg_changed = 0; + if(iptr == &g_serial_cfg[i]) { + cfg_changed = 1; + } + if(iptr == &g_serial_remote_port[i]) { + remote_changed = 1; + } + if(iptr == &g_serial_win_device[i]) { + serial_changed = 1; + } + if(cfg_changed || remote_changed || serial_changed) { + scc_config_changed(i, cfg_changed, remote_changed, + serial_changed); + } + } +} + +void +cfg_load_charrom() +{ + byte buffer[4096]; + dword64 dsize, dret; + word32 upos; + int fd; + + printf("Loading character ROM from: %s\n", g_cfg_charrom_path); + fd = open(g_cfg_charrom_path, O_RDONLY | O_BINARY); + if(fd < 0) { + printf("Cannot open %s\n", g_cfg_charrom_path); + return; + } + dsize = cfg_get_fd_size(fd); + upos = g_cfg_charrom_pos * 0x1000U; + if(dsize < (upos + 0x1000)) { + g_cfg_charrom_pos = 0; + return; + } + dret = cfg_read_from_fd(fd, &buffer[0], upos, 4096); + if(dret != 0) { + prepare_a2_romx_font(&buffer[0]); + } +} + +void +config_load_roms() +{ + struct stat stat_buf; + const char **names_ptr; + int more_than_8mb, changed_rom, len, fd, ret; + int i; + + g_rom_version = -1; + + /* set first entry of g_kegs_rom_names[] to g_cfg_rom_path so that */ + /* it becomes the first place searched. */ + g_kegs_rom_names[0] = g_cfg_rom_path; + ret = config_setup_kegs_file(&g_cfg_tmp_path[0], CFG_PATH_MAX, + &g_kegs_rom_names[0]); + if(ret == 0) { + // Just get out, let config interface select ROM + g_config_control_panel = 1; + printf("No ROM, set g_config_control_panel=1\n"); + return; + } + printf("Found ROM at path: %s\n", g_cfg_tmp_path); + fd = open(&g_cfg_tmp_path[0], O_RDONLY | O_BINARY); + if(fd < 0) { + fatal_printf("Open ROM file %s failed:%d, errno:%d\n", + &g_cfg_tmp_path[0], fd, errno); + g_config_control_panel = 1; + return; + } + + ret = fstat(fd, &stat_buf); + if(ret != 0) { + fatal_printf("fstat returned %d on fd %d, errno: %d\n", + ret, fd, errno); + g_config_control_panel = 1; + return; + } + + len = (int)stat_buf.st_size; + memset(&g_rom_fc_ff_ptr[0], 0, 4*65536); + /* Clear banks fc-ff to 0 */ + if(len == 32*1024) { // Apple //e + g_rom_version = 0; + g_mem_size_base = 128*1024; + ret = (int)read(fd, &g_rom_fc_ff_ptr[3*65536 + 32768], len); + } else if(len == 128*1024) { + g_rom_version = 1; + g_mem_size_base = 128*1024; + ret = (int)read(fd, &g_rom_fc_ff_ptr[2*65536], len); + } else if(len == 256*1024) { + g_rom_version = 3; + g_mem_size_base = 1024*1024; + ret = (int)read(fd, &g_rom_fc_ff_ptr[0], len); + } else { + fatal_printf("The ROM size should be 128K or 256K, this file " + "is %d bytes\n", len); + g_config_control_panel = 1; + return; + } + + printf("Read: %d bytes of ROM\n", ret); + if(ret != len) { + fatal_printf("errno: %d\n", errno); + g_config_control_panel = 1; + return; + } + close(fd); + + memset(&g_rom_cards_ptr[0], 0, 256*16); + + for(i = 0; i < 256; i++) { + // Place HD PROM in slot 7 + g_rom_cards_ptr[0x700 + i] = g_rom_c700[i]; + // g_rom_cards_ptr[0x500 + i] = g_rom_c700[i]; + } + /* initialize c600 rom to be diffs from the real ROM, to build-in */ + /* Apple II compatibility without distributing ROMs */ + if(g_rom_version == 0) { // Apple //e + for(i = 0; i < 256; i++) { + // Place Disk II PROM in slot 6 + g_rom_cards_ptr[0x600 + i] = g_rom_fc_ff_ptr[0x38600+i]; + } + } else { + for(i = 0; i < 256; i++) { + g_rom_cards_ptr[0x600 + i] = + g_rom_fc_ff_ptr[0x3c600 + i] ^ + g_rom_c600_rom01_diffs[i]; + } + } + if(g_rom_version >= 3) { + /* some patches */ + g_rom_cards_ptr[0x61b] ^= 0x40; + g_rom_cards_ptr[0x61c] ^= 0x33; + g_rom_cards_ptr[0x632] ^= 0xc0; + g_rom_cards_ptr[0x633] ^= 0x33; + } + + for(i = 1; i < 8; i++) { + names_ptr = g_kegs_rom_card_list[i]; + if(names_ptr == 0) { + continue; + } + if(*names_ptr == 0) { + continue; + } + + ret = config_setup_kegs_file(&g_cfg_tmp_path[0], CFG_PATH_MAX, + names_ptr); + + if(ret != 0) { + fd = open(&(g_cfg_tmp_path[0]), O_RDONLY | O_BINARY); + if(fd < 0) { + fatal_printf("Open card ROM file %s failed: %d " + "err:%d\n", &g_cfg_tmp_path[0], fd, + errno); + continue; + } + + len = 256; + ret = (int)read(fd, &g_rom_cards_ptr[i*0x100], len); + + if(ret != len) { + fatal_printf("While reading card ROM %s, file " + "is too short. (%d) Expected %d bytes, " + "read %d bytes\n", errno, len, ret); + continue; + } + close(fd); + } + } + + more_than_8mb = (g_mem_size_exp > 0x800000); + /* Only do the patch if users wants more than 8MB of expansion mem */ + + changed_rom = 0; + if(g_rom_version == 1) { + /* make some patches to ROM 01 */ +#if 0 + /* 1: Patch ROM selftest to not do speed test */ + printf("Patching out speed test failures from ROM 01\n"); + g_rom_fc_ff_ptr[0x3785a] = 0x18; + changed_rom = 1; +#endif + +#if 0 + /* 2: Patch ROM selftests not to do tests 2,4 */ + /* 0 = skip, 1 = do it, test 1 is bit 0 of LSByte */ + g_rom_fc_ff_ptr[0x371e9] = 0xf5; + g_rom_fc_ff_ptr[0x371ea] = 0xff; + changed_rom = 1; +#endif + + if(more_than_8mb) { + /* Geoff Weiss patch to use up to 14MB of RAM */ + g_rom_fc_ff_ptr[0x30302] = 0xdf; + g_rom_fc_ff_ptr[0x30314] = 0xdf; + g_rom_fc_ff_ptr[0x3031c] = 0x00; + changed_rom = 1; + } + + /* Patch ROM selftest to not do ROM cksum if any changes*/ + if(changed_rom) { + g_rom_fc_ff_ptr[0x37a06] = 0x18; + g_rom_fc_ff_ptr[0x37a07] = 0x18; + } + } else if(g_rom_version == 3) { + /* patch ROM 03 */ + printf("Patching ROM 03 smartport bug\n"); + /* 1: Patch Smartport code to fix a stupid bug */ + /* that causes it to write the IWM status reg into c036, */ + /* which is the system speed reg...it's "safe" since */ + /* IWM status reg bit 4 must be 0 (7MHz)..., otherwise */ + /* it might have turned on shadowing in all banks! */ + g_rom_fc_ff_ptr[0x357c9] = 0x00; + changed_rom = 1; + +#if 0 + /* patch ROM 03 to not to speed test */ + /* skip fast speed test */ + g_rom_fc_ff_ptr[0x36ad7] = 0x18; + g_rom_fc_ff_ptr[0x36ad8] = 0x18; + changed_rom = 1; +#endif + +#if 0 + /* skip slow speed test */ + g_rom_fc_ff_ptr[0x36ae7] = 0x18; + g_rom_fc_ff_ptr[0x36ae8] = 0x6b; + changed_rom = 1; +#endif + +#if 0 + /* 4: Patch ROM 03 selftests not to do tests 1-4 */ + g_rom_fc_ff_ptr[0x364a9] = 0xf0; + g_rom_fc_ff_ptr[0x364aa] = 0xff; + changed_rom = 1; +#endif + + /* ROM tests are in ff/6403-642x, where 6403 = addr of */ + /* test 1, etc. */ + + if(more_than_8mb) { + /* Geoff Weiss patch to use up to 14MB of RAM */ + g_rom_fc_ff_ptr[0x30b] = 0xdf; + g_rom_fc_ff_ptr[0x31d] = 0xdf; + g_rom_fc_ff_ptr[0x325] = 0x00; + changed_rom = 1; + } + + if(changed_rom) { + /* patch ROM 03 selftest to not do ROM cksum */ + g_rom_fc_ff_ptr[0x36cb0] = 0x18; + g_rom_fc_ff_ptr[0x36cb1] = 0x18; + } + + } +} + +void +config_parse_config_kegs_file() +{ + char *bufptr; + const char *str; + dword64 dsize; + int fd, pos, start, size, last_c, line, ret, c, maxlen; + int i; + + printf("Parsing config.kegs file: %s\n", g_config_kegs_name); + + clk_bram_zero(); + + g_highest_smartport_unit = -1; + + cfg_get_base_path(&g_cfg_cwd_str[0], g_config_kegs_name, 0); + if(g_cfg_cwd_str[0] != 0) { + ret = chdir(&g_cfg_cwd_str[0]); + if(ret != 0) { + printf("chdir to %s, errno:%d\n", g_cfg_cwd_str, errno); + } + // Do basename(g_config_kegs_name)--on it's own buffer + str = cfg_str_basename(g_config_kegs_name); + maxlen = sizeof(g_config_kegs_name); + cfg_strncpy(&g_config_kegs_name[0], str, maxlen); + } + + // In any case, copy the current directory path to g_cfg_cwd_str + (void)!getcwd(&g_cfg_cwd_str[0], CFG_PATH_MAX); + printf("CWD is now: %s\n", &g_cfg_cwd_str[0]); + + fd = open(g_config_kegs_name, O_RDONLY | O_BINARY); + dsize = 0; + if(fd >= 0) { + dsize = cfg_get_fd_size(fd); + } + if((fd < 0) || (dsize >= (1 << 30))) { + fatal_printf("cannot open config.kegs at %s, or it is too " + "large! Stopping!\n", g_config_kegs_name); + my_exit(3); + return; + } + size = (int)dsize; + bufptr = malloc(size + 2); + ret = (int)cfg_read_from_fd(fd, (byte *)bufptr, 0, size); + close(fd); + if(ret != size) { + free(bufptr); + fatal_printf("Could not read config.kegs at %s\n", + g_config_kegs_name); + my_exit(3); + return; + } + bufptr[size] = 0; // Ensure it's null terminated + + line = 0; + pos = 0; + last_c = 0; + while(pos < size) { + line++; + if((bufptr[pos] == '\n') && (last_c == '\r')) { + // CR,LF, just eat the LF + pos++; + } + start = pos; + while(pos < size) { + c = bufptr[pos]; + if((c == 0) || (c == '\n') || (c == '\r')) { + last_c = c; + bufptr[pos] = 0; + break; + } + pos++; + } + cfg_parse_one_line(&bufptr[start], line); + pos++; + } + + free(bufptr); + + // And now do command line argument overrides + for(i = 0; i < g_cfg_argv_num_overrides; i++) { + printf("Doing override %d, %s\n", i, g_cfg_argv_overrides[i]); + cfg_parse_one_line(g_cfg_argv_overrides[i], 1001 + i); + g_config_kegs_update_needed = 1; + } +} + +void +cfg_parse_one_line(char *buf, int line) +{ + Cfg_menu *menuptr; + Cfg_defval *defptr; + int *iptr; + const char *nameptr; + int pos, num_equals, type, val, c, len; + int i; + + // warning: modifies memory of bufptr (turns spaces to nulls) + if(line) { // Avoid unused parameter warning + } + + len = (int)strlen(buf); + if(len <= 1) { // Not a valid line, just get out + return; + } + + // printf("disk_conf[%d]: %s\n", line, buf); + if(buf[0] == '#') { + iwm_printf("Skipping comment\n"); + return; + } + + pos = 0; + + while((pos < len) && (buf[pos] == ' ' || buf[pos] == '\t') ) { + pos++; // Eat whitespace + } + if(pos >= len) { + return; // blank line + } + if((pos + 5) < len) { + c = buf[pos+1]; // Slot number + if((buf[pos] == 's') && (buf[pos+2] == 'd') && + (c >= '5' && c <= '7')) { + // looks like s5d1 through s7d15, parse that + cfg_parse_sxdx(buf, pos, len); + return; + } + } + +// parse buf from pos into option, "=" and then "rest" + + if(strncmp(&buf[pos], "bram", 4) == 0) { + cfg_parse_bram(buf, pos+4, len); + return; + } + + // find "name" as first contiguous string + //printf("...parse_option: line %d, %s (%s) len:%d\n", line, buf, + // &buf[pos], len); + + nameptr = &buf[pos]; + while(pos < len) { + c = buf[pos]; + if((c == 0) || (c == ' ') || (c == '\t') || (c == '\n') || + (c == '=')) { + break; + } + pos++; + } + buf[pos] = 0; + if(strcmp(nameptr, "rom") == 0) { + // Translate argument of "-rom" to "g_cfg_rom_path" + nameptr = "g_cfg_rom_path"; + } + pos++; + + // Eat up all whitespace and '=' + num_equals = 0; + while(pos < len) { + c = buf[pos]; + if((c == '=') && (num_equals == 0)) { + pos++; + num_equals++; + } else if(c == ' ' || c == '\t') { + pos++; + } else { + break; + } + } + + /* Look up nameptr to find type */ + type = -1; + defptr = 0; + menuptr = 0; + for(i = 0; i < g_cfg_defval_index; i++) { + defptr = &(g_cfg_defvals[i]); + menuptr = defptr->menuptr; + if(strcmp(menuptr->name_str, nameptr) == 0) { + type = menuptr->cfgtype; + break; + } + } + + switch(type) { + case CFGTYPE_INT: + /* use strtol */ + val = (int)strtol(&buf[pos], 0, 0); + iptr = (int *)menuptr->ptr; + cfg_int_update(iptr, val); + break; + case CFGTYPE_FILE: + case CFGTYPE_STR: + cfg_file_update_ptr(menuptr->ptr, &buf[pos], 0); + break; + default: + printf("Config file variable %s is unknown type: %d\n", + nameptr, type); + } +} + +void +cfg_parse_bram(char *buf, int pos, int len) +{ + word32 val; + int bram_num, offset; + + // Format: "bram1[00] = xx yy..." or "bram3[00] = xx yy ..." + // pos = position just after "bram" + if((len < (pos+5)) || (buf[pos+1] != '[') || (buf[pos+4] != ']')) { + fatal_printf("While reading config.kegs, found malformed bram " + "statement: %s\n", buf); + return; + } + bram_num = buf[pos] - '0'; + if((bram_num != 1) && (bram_num != 3)) { + fatal_printf("While reading config.kegs, found bad bram " + "num: %s\n", buf); + return; + } + + bram_num = bram_num >> 1; // turn 3->1 and 1->0 + + offset = (int)strtoul(&(buf[pos+2]), 0, 16); + pos += 5; + while(pos < len) { + if((buf[pos] == ' ') || (buf[pos] == '\t') || + (buf[pos] == 0x0a) || (buf[pos] == 0x0d) || + (buf[pos] == '=')) { + pos++; + continue; + } + val = (word32)strtoul(&buf[pos], 0, 16); // As hex + clk_bram_set(bram_num, offset, val); + offset++; + pos += 2; + } +} + +void +cfg_parse_sxdx(char *buf, int pos, int len) +{ + char *name_ptr, *partition_name; + word32 dynamic_blocks; + int part_num, ejected, slot, drive, c; + + slot = buf[pos+1] - '0'; + drive = buf[pos+3] - '0'; + + /* skip over slot, drive */ + pos += 4; + if((buf[pos] >= '0') && (buf[pos] <= '9')) { + // Second digit of drive is valid + drive = (drive * 10) + buf[pos] - '0'; + pos++; + } + + drive--; // make sxd1 mean index 0 + + while((pos < len) && ((buf[pos] == ' ') || (buf[pos] == '\t') || + (buf[pos] == '=')) ) { + pos++; + } + + ejected = 0; + if(buf[pos] == '#') { // disk is ejected, but read info anyway + ejected = 1; + pos++; + } + + partition_name = 0; + part_num = -1; + dynamic_blocks = 0; + if(buf[pos] == ':') { // yup, it's got a partition name! + pos++; + partition_name = &buf[pos]; + while((pos < len) && (buf[pos] != ':')) { + pos++; + } + buf[pos] = 0; /* null terminate partition name */ + pos++; + } + c = buf[pos]; + if((c == ';') || (c == '@')) { + pos++; + // ; - partition number; @ - Dynamic ProDOS dir size + part_num = 0; + while((pos < len) && (buf[pos] != ':')) { + part_num = (10*part_num) + buf[pos] - '0'; + pos++; + } + pos++; + if(c == '@') { // Dynamic ProDOS directory + dynamic_blocks = part_num * 2; + part_num = -1; + } + } + + /* Get filename */ + name_ptr = &(buf[pos]); + if((pos >= len) || (name_ptr[0] == 0)) { + return; + } + + insert_disk(slot, drive, name_ptr, ejected, partition_name, + part_num, dynamic_blocks); +} + +void +config_generate_config_kegs_name(char *outstr, int maxlen, Disk *dsk, + int with_extras) +{ + char *str; + + str = outstr; + + if(with_extras) { + if(dsk->fd < 0) { + snprintf(str, maxlen - (str - outstr), "#"); + str = &outstr[strlen(outstr)]; + } + if(dsk->dynapro_blocks) { + snprintf(str, maxlen - (str - outstr), "@%d:", + (dsk->dynapro_blocks + 1) / 2); + str = &outstr[strlen(outstr)]; + } else if(dsk->partition_name != 0) { + snprintf(str, maxlen - (str - outstr), ":%s:", + dsk->partition_name); + str = &outstr[strlen(outstr)]; + } else if(dsk->partition_num >= 0) { + snprintf(str, maxlen - (str - outstr), ";%d:", + dsk->partition_num); + str = &outstr[strlen(outstr)]; + } + } + snprintf(str, maxlen - (str - outstr), "%s", dsk->name_ptr); +} + +char * +config_write_config_kegs_file(int get_status) +{ + FILE *fconf; + Disk *dsk; + Cfg_defval *defptr; + Cfg_menu *menuptr; + char *curstr, *defstr; + int defval, curval, type, slot, drive; + int i; + + if(get_status) { + return 0; + } + printf("Writing config.kegs file to %s\n", g_config_kegs_name); + + fconf = fopen(g_config_kegs_name, "w+"); + if(fconf == 0) { + halt_printf("cannot open %s! Stopping!\n", g_config_kegs_name); + return 0; + } + + fprintf(fconf, "# KEGS configuration file version %s\n", + g_kegs_version_str); + + for(i = 0; i < MAX_C7_DISKS + 4; i++) { + slot = 7; + drive = i - 4; + if(i < 4) { + slot = (i >> 1) + 5; + drive = i & 1; + } + if(drive == 0) { + fprintf(fconf, "\n"); /* an extra blank line */ + } + + dsk = iwm_get_dsk_from_slot_drive(slot, drive); + if(dsk->name_ptr == 0 && (i > 4)) { + /* No disk, not even ejected--just skip */ + continue; + } + fprintf(fconf, "s%dd%d = ", slot, drive + 1); + if(dsk->name_ptr == 0) { + fprintf(fconf, "\n"); + continue; + } + config_generate_config_kegs_name(&g_cfg_tmp_path[0], + CFG_PATH_MAX, dsk, 1); + fprintf(fconf, "%s\n", &g_cfg_tmp_path[0]); + } + + fprintf(fconf, "\n"); + + /* See if any variables are different than their default */ + for(i = 0; i < g_cfg_defval_index; i++) { + defptr = &(g_cfg_defvals[i]); + menuptr = defptr->menuptr; + defval = defptr->intval; + type = menuptr->cfgtype; + if(type == CFGTYPE_INT) { + curval = *((int *)menuptr->ptr); + if(curval != defval) { + fprintf(fconf, "%s = %d\n", menuptr->name_str, + curval); + } + } + if((type == CFGTYPE_FILE) || (type == CFGTYPE_STR)) { + curstr = *((char **)menuptr->ptr); + defstr = *((char **)menuptr->defptr); + if(strcmp(curstr, defstr) != 0) { + fprintf(fconf, "%s = %s\n", menuptr->name_str, + curstr); + } + } + } + + fprintf(fconf, "\n"); + + /* write bram state */ + clk_write_bram(fconf); + + fclose(fconf); + + g_config_kegs_update_needed = 0; + return 0; +} + +void +insert_disk(int slot, int drive, const char *name, int ejected, + const char *partition_name, int part_num, word32 dynamic_blocks) +{ + byte buf_2img[512]; + Disk *dsk; + char *name_ptr, *part_ptr; + dword64 dsize, dunix_pos, exp_dsize, dtmp; + word32 len_bits, save_ftrack; + int image_type, part_len, ret, locked, len, is_po, name_len; + int i; + + g_config_kegs_update_needed = 1; + + if((slot < 5) || (slot > 7)) { + fatal_printf("Invalid slot for insertiing disk: %d\n", slot); + return; + } + if(drive < 0 || ((slot == 7) && (drive >= MAX_C7_DISKS)) || + ((slot < 7) && (drive > 1))) { + fatal_printf("Invalid drive for inserting disk: %d\n", drive); + return; + } + + dsk = iwm_get_dsk_from_slot_drive(slot, drive); + +#if 1 + printf("Inserting disk %s (%s or %d) in slot %d, drive: %d, " + "dyna_blocks:%d\n", name, partition_name, part_num, slot, drive, + dynamic_blocks); +#endif + + // DO NOT change dsk->just_ejected. If a disk was just ejected, then + // leave it alone. Otherwise, if we are a newly inserted disk, + // it should already be 0, so leave it alone + //dsk->just_ejected = 1; + + if(dsk->fd >= 0) { + iwm_eject_disk(dsk); + } + + /* Before opening, make sure no other mounted disk has this name */ + /* If so, unmount it */ + if(!ejected) { + for(i = 0; i < 2; i++) { + iwm_eject_named_disk(5, i, name, partition_name); + iwm_eject_named_disk(6, i, name, partition_name); + } + for(i = 0; i < MAX_C7_DISKS; i++) { + iwm_eject_named_disk(7, i, name, partition_name); + } + } + + /* free old name_ptr, partition_name */ + free(dsk->name_ptr); + free(dsk->partition_name); + dsk->name_ptr = 0; + dsk->partition_name = 0; + + name_ptr = kegs_malloc_str(name); + dsk->name_ptr = name_ptr; + name_len = (int)strlen(dsk->name_ptr); + + part_len = 0; + part_ptr = 0; + if(partition_name != 0) { + part_ptr = kegs_malloc_str(partition_name); + part_len = (int)strlen(part_ptr); + dsk->partition_name = part_ptr; + } + dsk->partition_num = part_num; + dsk->dynapro_blocks = dynamic_blocks; + + iwm_printf("Opening up disk image named: %s\n", name_ptr); + + if(ejected) { + /* just get out of here */ + dsk->fd = -1; + return; + } + + dsk->fd = -1; + dsk->raw_data = 0; + dsk->image_type = 0; + dsk->dimage_start = 0; + dsk->dimage_size = 0; + dsk->write_prot = 0; + dsk->write_through_to_unix = 1; + image_type = 0; + locked = 0; + + if(dynamic_blocks) { + ret = dynapro_mount(dsk, name_ptr, dynamic_blocks); + if(ret < 0) { + iwm_eject_disk(dsk); + return; + } + image_type = DSK_TYPE_DYNAPRO; + printf("After dynapro_mount, write_through:%d\n", + dsk->write_through_to_unix); + } + if((partition_name != 0) || (part_num >= 0)) { + ret = cfg_partition_find_by_name_or_num(dsk, partition_name, + part_num); + printf("partition %s (num %d) mounted, wr_prot: %d, ret:%d\n", + partition_name, part_num, dsk->write_prot, ret); + + if(ret < 0) { + iwm_eject_disk(dsk); + return; + } + locked = dsk->write_prot; + if(dsk->raw_data) { + // .zip file or something similar. Do name matching on + // partition name + name_len = part_len; + name_ptr = part_ptr; + locked = 1; + } + } + + if((name_len > 3) && !image_type && + !cfgcasecmp(".gz", &name_ptr[name_len - 3])) { + // it's gzip'ed, try to gunzip it to dsk->raw_data + undeflate_gzip(dsk, name_ptr); + + locked = 1; + dsk->dimage_start = 0; + dsk->dimage_size = dsk->raw_dsize; + name_len -= 3; // So .dsk, .po look for correct chars + } + + if((name_len > 4) && !image_type && + !cfgcasecmp(".bz2", &name_ptr[name_len - 4])) { + // it's bzip2'ed, try to bunzip2 it to dsk->raw_data + config_file_to_pipe(dsk, "bunzip2", name_ptr); + locked = 1; + + // Reduce name_len by 4 so that subsequent compares for .po + // look at the correct chars + name_len -= 4; + } + + if((name_len > 4) && !image_type && + !cfgcasecmp(&name_ptr[name_len - 4], ".sdk")) { + // it's a ShrinkIt archive with a disk image in it + unshk(dsk, name_ptr); + locked = 1; + image_type = DSK_TYPE_PRODOS; + printf("dsk->fd:%d dsk->raw_data:%p, raw_dsize:%lld\n", dsk->fd, + dsk->raw_data, dsk->raw_dsize); + dsk->dimage_start = 0; + dsk->dimage_size = dsk->raw_dsize; + } + + if((name_len > 4) && !image_type && dsk->disk_525 && + !cfgcasecmp(".nib", &name_ptr[name_len-4])) { + // Old, obsolete .nib 5.25" nibblized format. Support is + // read-only + image_type = DSK_TYPE_NIB; + locked = 1; + } + + if((dsk->fd < 0) && !locked && !dynamic_blocks) { + dsk->fd = open(name_ptr, O_RDWR | O_BINARY, 0x1b6); + } + + if((dsk->fd < 0) && !dynamic_blocks) { + printf("Trying to open %s read-only, errno: %d\n", name_ptr, + errno); + dsk->fd = open(name_ptr, O_RDONLY | O_BINARY, 0x1b6); + locked = 2; + } + + iwm_printf("open returned: %d\n", dsk->fd); + + if((dsk->fd < 0) && !dynamic_blocks) { + fatal_printf("Disk image %s does not exist!\n", name_ptr); + free(dsk->raw_data); + return; + } + + //printf("Checking if it's woz, name_ptr:%s. %d vs %d\n", name_ptr, + // name_len, (int)strlen(name_ptr)); + if((name_len > 4) && !image_type && + !cfgcasecmp(&name_ptr[name_len - 4], ".woz")) { + // it's a WOZ applesauce disk image + image_type = DSK_TYPE_WOZ; + printf("It is woz!\n"); + } + + if(locked) { + dsk->write_prot = locked; + } + + save_ftrack = dsk->cur_frac_track; /* save arm position */ + + /* See if it is in 2IMG format */ + if(dsk->raw_data) { + // Just do a copy from raw_data + for(i = 0; i < 512; i++) { + buf_2img[i] = dsk->raw_data[i]; + } + dsize = dsk->raw_dsize; + if(!dsk->dynapro_info_ptr) { + dsk->write_through_to_unix = 0; + } + } else { + ret = (int)read(dsk->fd, (char *)&buf_2img[0], 512); + dsize = cfg_get_fd_size(dsk->fd); + } + +#if 0 + /* Try to guess that there is a Mac Binary header of 128 bytes */ + /* See if image size & 0xfff = 0x080 which indicates extra 128 bytes */ + if(((dsize & 0xfff) == 0x080) && (dsize < (801*1024)) && !image_type) { + printf("Assuming Mac Binary header on %s\n", dsk->name_ptr); + dsk->dimage_start += 0x80; + dsize -= 0x80; + } +#endif + + dsk->dimage_size = dsize; + + if(!image_type && (buf_2img[0] == '2') && (buf_2img[1] == 'I') && + (buf_2img[2] == 'M') && (buf_2img[3] == 'G')) { + /* It's a 2IMG disk */ + printf("Image named %s is in 2IMG format\n", dsk->name_ptr); + image_type = DSK_TYPE_PRODOS; + + if(buf_2img[12] == 0) { + printf("2IMG is in DOS 3.3 sector order\n"); + image_type = DSK_TYPE_DOS33; + } + if(buf_2img[19] & 0x80) { + /* disk is locked */ + printf("2IMG is write protected\n"); + if(dsk->write_prot == 0) { + dsk->write_prot = 1; + } + } + if((buf_2img[17] & 1) && (dsk->image_type == DSK_TYPE_DOS33)) { + dsk->vol_num = buf_2img[16]; + printf("Setting DOS 3.3 vol num to %d\n", dsk->vol_num); + } + // Some 2IMG archives have the size byte reversed + dsize = (buf_2img[31] << 24) + (buf_2img[30] << 16) + + (buf_2img[29] << 8) + buf_2img[28]; + dunix_pos = (buf_2img[27] << 24) + (buf_2img[26] << 16) + + (buf_2img[25] << 8) + buf_2img[24]; + if(dsize == 0x800c00) { + // Byte reversed 0x0c8000 + dsize = 0x0c8000; + } + if(dsize == 0) { + /* Sweet-16 makes some images with size == 0 */ + /* Example: Prosel from */ + /* www.whatisthe2gs.apple2.org.za/the_ring/ */ + if(buf_2img[12] == 1) { + /* then get the size from 0x14 in blocks */ + dsize = (buf_2img[23] << 24) + + (buf_2img[22] << 16) + + (buf_2img[21] << 8) + buf_2img[20]; + dsize = dsize * 512; /* it was blocks */ + } + } + dsk->dimage_start = dunix_pos; + dsk->dimage_size = dsize; + } + exp_dsize = 800*1024; + dsk->fbit_mult = 256; + if(dsk->disk_525) { + exp_dsize = 140*1024; + dsk->fbit_mult = 128; + } + if(!image_type) { + /* See if it might be the Mac diskcopy format */ + dtmp = cfg_detect_dc42(&buf_2img[0], dsize, exp_dsize, slot); + if(dtmp != 0) { + // It's diskcopy 4.2 + printf("Image named %s is in Mac diskcopy format\n", + dsk->name_ptr); + dsk->dimage_start += 0x54; + dsk->dimage_size = dtmp; + image_type = DSK_TYPE_PRODOS; /* ProDOS */ + } + } + if(!image_type) { + /* Assume raw image */ + dsk->dimage_size = dsize; + image_type = DSK_TYPE_PRODOS; + is_po = (name_len > 3) && + !cfgcasecmp(".po", &name_ptr[name_len-3]); + if(dsk->disk_525) { + image_type = DSK_TYPE_DOS33; + if(is_po) { + image_type = DSK_TYPE_PRODOS; + } + } + } + + dsk->image_type = image_type; + dsk->disk_dirty = 0; + dsk->cur_fbit_pos = 0; + dsk->cur_track_bits = 0; + dsk->cur_trk_ptr = 0; + + if(image_type == DSK_TYPE_WOZ) { + // Special handling + ret = woz_open(dsk, 0); + if(!ret) { + iwm_eject_disk(dsk); + return; + } + } else if(dsk->smartport) { + g_highest_smartport_unit = MY_MAX(dsk->drive, + g_highest_smartport_unit); + + iwm_printf("adding smartport device[%d], img_sz:%08llx\n", + dsk->drive, dsk->dimage_size); + } else if(dsk->disk_525) { + dunix_pos = dsk->dimage_start; + dsize = dsk->dimage_size; + disk_set_num_tracks(dsk, 4*35); + len = 0x1000; + if(dsk->image_type == DSK_TYPE_NIB) { + // Weird .nib format, has no sync bits + len = (int)(dsk->dimage_size / 35); + for(i = 0; i < 35; i++) { + disk_unix_to_nib(dsk, 4*i, dunix_pos, len, + len * 8, 0); + dunix_pos += len; + } + } else { + for(i = 0; i < 35; i++) { + len_bits = iwm_get_default_track_bits(dsk, 4*i); + disk_unix_to_nib(dsk, 4*i, dunix_pos, len, + len_bits, 0); + dunix_pos += len; + } + } + if(dsize != (dword64)35*len) { + fatal_printf("Disk 5.25 error: size is %lld, not %d. " + "Will try to mount anyway\n", dsize, 35*len); + } + } else { + /* disk_35 */ + dunix_pos = dsk->dimage_start; + dsize = dsk->dimage_size; + if(dsize != 800*1024) { + printf("Disk 3.5 Drive %d (Image File: %s), Error: " + "size is %lld, not 800K. Will try to mount " + "anyway\n", drive+1, name, dsize); + } + disk_set_num_tracks(dsk, 2*80); + for(i = 0; i < 2*80; i++) { + len = g_track_bytes_35[i >> 5]; + len_bits = iwm_get_default_track_bits(dsk, i); + iwm_printf("Trk: %d.%d = unix: %08llx, %04x, %04x\n", + i>>1, i & 1, dunix_pos, len, len_bits); + disk_unix_to_nib(dsk, i, dunix_pos, len, len_bits, 0); + dunix_pos += len; + + iwm_printf(" trk_bits:%05x\n", dsk->trks[i].track_bits); + } + } + + iwm_move_to_ftrack(dsk, save_ftrack, 0, 0); +} + +dword64 +cfg_detect_dc42(byte *bptr, dword64 dsize, dword64 exp_dsize, int slot) +{ + dword64 ddata_size, dtag_size; + int i; + + // Detect Mac DiskCopy4.2 disk image (often .dmg or .dc or .dc42) + // bptr points to just the first 512 bytes of the file + + if((bptr[0x52] != 1) || (bptr[0x53] != 0)) { + return 0; // No "magic number 0x01, 0x00 for DiskCopy4.2 + } + if(bptr[0] > 0x3f) { + return 0; // not a valid image name length (1-0x3f) + } + ddata_size = 0; + dtag_size = 0; + for(i = 0; i < 4; i++) { + ddata_size = (ddata_size << 8) | bptr[0x40 + i]; + dtag_size = (dtag_size << 8) | bptr[0x44 + i]; + } + if((dtag_size != 0) && (dtag_size != ((ddata_size >> 9) * 12))) { + return 0; // Tags are either 0, or 12 bytes per block + } + if(slot == 7) { + if(ddata_size < 140*1024) { + return 0; // Allow any size >=140K slot 7 + } + } else if(ddata_size != exp_dsize) { + return 0; // Must be 140K or 800K + } + + if(ddata_size > (dsize - 0x54)) { + return 0; // data_size doesn't make sense + } + if((ddata_size & 0x1ff) || ((ddata_size >> 31) > 1)) { + return 0; // data_size not a multiple of 512 bytes + } + if((ddata_size + dtag_size + 0x54) != dsize) { + return 0; // File doesn't appear to be well-formed + } + + return ddata_size; +} + +dword64 +cfg_get_fd_size(int fd) +{ + struct stat stat_buf; + int ret; + + ret = fstat(fd, &stat_buf); + if(ret != 0) { + fprintf(stderr,"fstat returned %d on fd %d, errno: %d\n", + ret, fd, errno); + stat_buf.st_size = 0; + } + + return stat_buf.st_size; +} + +dword64 +cfg_read_from_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize) +{ + dword64 dret, doff; + word32 this_len; + + dret = kegs_lseek(fd, dpos, SEEK_SET); + if(dret != dpos) { + printf("lseek failed: %lld\n", dret); + return 0; + } + doff = 0; + while(1) { + if(doff >= dsize) { + break; + } + this_len = 1UL << 30; + if((dsize - doff) < this_len) { + this_len = (word32)(dsize - doff); + } + dret = read(fd, bufptr + doff, this_len); + if((dret + 1) == 0) { // dret==-1 + printf("read failed\n"); + return 0; + } + if(dret == 0) { + printf("Unexpected end of file, tried to read from " + "dpos:%lld dsize:%lld\n", dpos, dsize); + return 0; + } + doff += dret; + } + return doff; +} + +dword64 +cfg_write_to_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize) +{ + dword64 dret; + + dret = kegs_lseek(fd, dpos, SEEK_SET); + if(dret != dpos) { + printf("lseek failed: %lld\n", dret); + return 0; + } + return must_write(fd, bufptr, dsize); +} + +int +cfg_partition_maybe_add_dotdot() +{ + int part_len; + + part_len = (int)strlen(&(g_cfg_part_path[0])); + if(part_len > 0) { + // Add .. entry here + cfg_file_add_dirent(&g_cfg_partitionlist, "..", 1, 0, 0, 0, 0); + } + return part_len; +} + +int +cfg_partition_name_check(const byte *name_ptr, int name_len) +{ + int part_len; + int i; + + // Return 0 if name_ptr is not at the right path depth, 1 if OK + + part_len = (int)strlen(&g_cfg_part_path[0]); + for(i = 0; i < part_len; i++) { + if(i >= name_len) { + return 0; + } + if(name_ptr[i] != g_cfg_part_path[i]) { + return 0; + } + } + return 1; +} + +int +cfg_partition_read_block(int fd, void *buf, dword64 blk, int blk_size) +{ + if(!cfg_read_from_fd(fd, buf, blk * blk_size, blk_size)) { + // Read failed + return 0; + } + return blk_size; +} + +int +cfg_partition_find_by_name_or_num(Disk *dsk, const char *in_partnamestr, + int part_num) +{ + Cfg_dirent *direntptr; + const char *partnamestr; + int match, num_parts, ret, fd, len, c; + int i; + +#if 0 + printf("cfg_partition_find_by_name_or_num: %s, part_num:%d\n", + partnamestr, part_num); +#endif + + // We need to copy partnamestr up to the last / to g_cfg_part_path[], + // and use just the end as the partition name. + cfg_strncpy(&g_cfg_part_path[0], in_partnamestr, CFG_PATH_MAX); + len = (int)strlen(in_partnamestr); + partnamestr = in_partnamestr; + for(i = len - 1; i >= 0; i--) { + c = g_cfg_part_path[i]; + if(c == '/') { + partnamestr = &(in_partnamestr[i+1]); + break; + } + g_cfg_part_path[i] = 0; + } + + fd = open(dsk->name_ptr, O_RDONLY | O_BINARY, 0x1b6); + if(fd < 0) { + fatal_printf("Cannot open disk image: %s\n", dsk->name_ptr); + return -1; + } + + num_parts = cfg_partition_make_list(fd); + + if(num_parts <= 0) { + printf("num_parts: %d\n", num_parts); + close(fd); + return -1; + } + + for(i = 0; i < g_cfg_partitionlist.last; i++) { + direntptr = &(g_cfg_partitionlist.direntptr[i]); + match = 0; + if((strcmp(partnamestr, direntptr->name) == 0) && + (part_num < 0)) { + //printf("partition, match1, name:%s %s, part_num:%d\n", + // partnamestr, direntptr->name, part_num); + + match = 1; + } + if((partnamestr == 0) && (direntptr->part_num == part_num)) { + //printf("partition, match2, n:%s, part_num:%d == %d\n", + // direntptr->name, direntptr->part_num, part_num); + match = 1; + } + if(match) { + //printf("match with dimage_start:%08llx, dimage_size:" + // "%08llx\n", dsk->dimage_start, + // dsk->dimage_size); + + printf("match with dimage_start:%08llx, dimage_size:" + "%08llx\n", direntptr->dimage_start, + direntptr->dsize); + ret = i; + if(g_cfg_partition_is_zip) { + ret = undeflate_zipfile(dsk, fd, + direntptr->dimage_start, + direntptr->dsize, + direntptr->compr_dsize); + close(fd); + } else { + dsk->fd = fd; + dsk->dimage_start = direntptr->dimage_start; + dsk->dimage_size = direntptr->dsize; + } + + return ret; + } + } + + close(fd); + // printf("No matches, ret -1\n"); + return -1; +} + +int +cfg_partition_make_list_from_name(const char *namestr) +{ + int fd, ret; + + fd = open(namestr, O_RDONLY | O_BINARY, 0x1b6); + if(fd < 0) { + fatal_printf("Cannot open part image: %s\n", namestr); + return 0; + } + ret = cfg_partition_make_list(fd); + + close(fd); + return ret; +} + +int +cfg_partition_make_list(int fd) +{ + Driver_desc *driver_desc_ptr; + Part_map *part_map_ptr; + word32 *blk_bufptr; + dword64 dimage_start, dimage_size, dsize; + word32 start, len, data_off, data_len, sig, map_blk_cnt, cur_blk; + word32 map_blks, block_size; + int is_dir, ret; + + block_size = 512; + g_cfg_partition_is_zip = 0; + + cfg_free_alldirents(&g_cfg_partitionlist); + + blk_bufptr = (word32 *)malloc(MAX_PARTITION_BLK_SIZE); + + cfg_partition_read_block(fd, blk_bufptr, 0, block_size); + + driver_desc_ptr = (Driver_desc *)blk_bufptr; + sig = cfg_get_be_word16(&(driver_desc_ptr->sig)); + block_size = cfg_get_be_word16(&(driver_desc_ptr->blk_size)); + if(block_size == 0) { + block_size = 512; + } + if((sig != 0x4552) || (block_size < 0x200) || + (block_size > MAX_PARTITION_BLK_SIZE)) { + printf("Partition error: No driver descriptor map found\n"); + free(blk_bufptr); + ret = undeflate_zipfile_make_list(fd); + if(ret > 0) { + g_cfg_partition_is_zip = 1; + } + return ret; + } + + map_blks = 1; + cur_blk = 0; + dsize = cfg_get_fd_size(fd); + cfg_file_add_dirent(&g_cfg_partitionlist, "None - Whole image", + is_dir=0, dsize, 0, 0, -1); + + while(cur_blk < map_blks) { + cur_blk++; + cfg_partition_read_block(fd, blk_bufptr, cur_blk, block_size); + part_map_ptr = (Part_map *)blk_bufptr; + sig = cfg_get_be_word16(&(part_map_ptr->sig)); + map_blk_cnt = cfg_get_be_word32(&(part_map_ptr->map_blk_cnt)); + if(cur_blk <= 1) { + map_blks = MY_MIN(20, map_blk_cnt); + } + if(sig != 0x504d) { + printf("Partition entry %d bad signature:%04x\n", + cur_blk, sig); + free(blk_bufptr); + return g_cfg_partitionlist.last; + } + + /* found it, check for consistency */ + start = cfg_get_be_word32(&(part_map_ptr->phys_part_start)); + len = cfg_get_be_word32(&(part_map_ptr->part_blk_cnt)); + data_off = cfg_get_be_word32(&(part_map_ptr->data_start)); + data_len = cfg_get_be_word32(&(part_map_ptr->data_cnt)); + if(data_off + data_len > len) { + printf("Poorly formed entry\n"); + continue; + } + + if(data_len < 10 || start < 1) { + printf("Poorly formed entry %d, datalen:%d, " + "start:%08x\n", cur_blk, data_len, start); + continue; + } + + dimage_size = (dword64)data_len * block_size; + dimage_start = ((dword64)start + data_off) * block_size; + is_dir = 2*(dimage_size < 800*1024); +#if 0 + printf(" partition add entry %d = %s %d %08llx %08llx\n", + cur_blk, part_map_ptr->part_name, is_dir, + dimage_size, dimage_start); +#endif + + cfg_file_add_dirent(&g_cfg_partitionlist, + part_map_ptr->part_name, is_dir, dimage_size, + dimage_start, 0, cur_blk); + } + + free(blk_bufptr); + return g_cfg_partitionlist.last; +} + +int +cfg_maybe_insert_disk(int slot, int drive, const char *namestr) +{ + int num_parts; + + g_cfg_part_path[0] = 0; + num_parts = cfg_partition_make_list_from_name(namestr); + + if(num_parts > 0) { + printf("Choose a partition\n"); + g_cfg_select_partition = 1; + g_cfg_file_pathfield = 0; + } else { + insert_disk(slot, drive, namestr, 0, 0, -1, 0); + return 1; + } + return 0; +} + +void +cfg_insert_disk_dynapro(int slot, int drive, const char *name) +{ + int dynapro_blocks; + + dynapro_blocks = 280; + if(slot == 5) { + dynapro_blocks = 1600; + } else if(slot == 7) { + dynapro_blocks = 65535; + } + if(g_cfg_newdisk_select && (g_cfg_newdisk_type == 3) && + g_cfg_newdisk_blocks) { + dynapro_blocks = g_cfg_newdisk_blocks; + } + insert_disk(slot, drive, name, 0, 0, -1, dynapro_blocks); +} + +int +cfg_stat(char *path, struct stat *sb, int do_lstat) +{ + int ret, len, removed_slash; + + removed_slash = 0; + len = 0; + + /* Windows doesn't like to stat paths ending in a /, so remove it */ + len = (int)strlen(path); +#ifdef _WIN32 + if((len > 1) && (path[len - 1] == '/') ) { + path[len - 1] = 0; /* remove the slash */ + removed_slash = 1; + } +#endif + + if(do_lstat) { + ret = lstat(path, sb); + } else { + ret = stat(path, sb); + } + + /* put the slash back */ + if(removed_slash) { + path[len - 1] = '/'; + } + + return ret; +} + +word32 +cfg_get_le16(byte *bptr) +{ + return bptr[0] | (bptr[1] << 8); +} + +word32 +cfg_get_le32(byte *bptr) +{ + return bptr[0] | (bptr[1] << 8) | (bptr[2] << 16) | (bptr[3] << 24); +} + +dword64 +cfg_get_le64(byte *bptr) +{ + dword64 dval; + int i; + + dval = 0; + for(i = 7; i >= 0; i--) { + dval = (dval << 8) | bptr[i]; + } + return dval; +} + +word32 +cfg_get_be_word16(word16 *ptr) +{ + byte *bptr; + + bptr = (byte *)ptr; + return (bptr[0] << 8) | bptr[1]; +} + +word32 +cfg_get_be_word32(word32 *ptr) +{ + byte *bptr; + + bptr = (byte *)ptr; + return (bptr[0] << 24) | (bptr[1] << 16) | (bptr[2] << 8) | bptr[3]; +} + +void +cfg_set_le32(byte *bptr, word32 val) +{ + *bptr++ = val; + *bptr++ = val >> 8; + *bptr++ = val >> 16; + *bptr++ = val >> 24; +} + +void +config_file_to_pipe(Disk *dsk, const char *cmd_ptr, const char *name_ptr) +{ +#ifdef _WIN32 + printf("Cannot do pipe from cmd %s to %s\n", cmd_ptr, name_ptr); + return; +#else + int output_pipe[2]; + byte *bptr2; + char *bufptr; + pid_t pid; + int stat_loc, ret, bufsize, pos, fd; + int i; + + // Create a pipe to cmd_ptr, and send the contents of name_ptr to it + // Collect the output in a 32MB buffer. Once complete, allocate + // a buffer of the correct size, copy to it, and free the giant buffer + // Sample usage: "gunzip", "{filename}.gz" will run gunzip and collect + // uncompressed data + ret = pipe(&output_pipe[0]); + if(ret < 0) { + return; + } + printf("output_pipe[0]=%d, [1]=%d\n", output_pipe[0], output_pipe[1]); + bufsize = 32*1024*1024; + bufptr = malloc(bufsize); + if(bufptr == 0) { + return; + } + pos = 0; + pid = fork(); + if(pid == 0) { + close(output_pipe[0]); + ret = dup2(output_pipe[1], 1); + if(ret < 0) { + exit(1); + } + // The child. Open 0 as the file, and then do system + close(0); + fd = open(name_ptr, O_RDONLY | O_BINARY, 0x1b6); + if(fd != 0) { + exit(1); + } + // Now just run the command. Input is from name_ptr, output is + // to a pipe + (void)!system(cmd_ptr); + exit(0); + } else if(pid > 0) { + // Parent. Collect output from output_pipe[0], and write it + // to bufptr. + close(output_pipe[1]); + while(1) { + if(pos >= bufsize) { + break; + } + ret = read(output_pipe[0], bufptr + pos, bufsize - pos); + if(ret <= 0) { + break; + } + pos += ret; + } + close(output_pipe[0]); + waitpid(pid, &stat_loc, 0); + } else { + // Error case + close(output_pipe[1]); + close(output_pipe[0]); + } + + // See what we got + bptr2 = 0; + printf("Read %d bytes from %s\n", pos, name_ptr); + if(pos >= 140*1024) { + // Looks like it could be an image + bptr2 = malloc(pos); + for(i = 0; i < pos; i++) { + bptr2[i] = bufptr[i]; + } + dsk->raw_data = bptr2; + dsk->fd = 0; // Indicates raw_data is valid + dsk->raw_dsize = pos; + } + free(bufptr); +#endif +} + +void +cfg_htab_vtab(int x, int y) +{ + if(x > 79) { + x = 0; + } + if(y > 23) { + y = 0; + } + g_cfg_curs_x = x; + g_cfg_curs_y = y; + g_cfg_curs_inv = 0; + g_cfg_curs_mousetext = 0; +} + +void +cfg_home() +{ + int i; + + cfg_htab_vtab(0, 0); + for(i = 0; i < 24; i++) { + cfg_cleol(); + } +} + +void +cfg_cleol() +{ + g_cfg_curs_inv = 0; + g_cfg_curs_mousetext = 0; + cfg_putchar(' '); + while(g_cfg_curs_x != 0) { + cfg_putchar(' '); + } +} + +void +cfg_putchar(int c) +{ + int x, y; + + if(c == '\n') { + cfg_cleol(); + return; + } + if(c == '\b') { + g_cfg_curs_inv = !g_cfg_curs_inv; + return; + } + if(c == '\t') { + g_cfg_curs_mousetext = !g_cfg_curs_mousetext; + return; + } + y = g_cfg_curs_y; + x = g_cfg_curs_x; + + // Normal: 0xa0-0xff for space through lowercase + // Inverse: 0x00-0x3f for upper case and numbers, 0x60-0x7f for lcase + // Mousetext: 0x40-0x5f + if(g_cfg_curs_inv) { + if(c >= 0x40 && c < 0x60) { + c = c & 0x1f; + } + } else { + c = c | 0x80; + } + if(g_cfg_curs_mousetext) { + c = (c & 0x1f) | 0x40; + } + g_cfg_screen[y][x] = c; + x++; + if(x >= 80) { + x = 0; + y++; + if(y >= 24) { + y = 0; + } + } + g_cfg_curs_y = y; + g_cfg_curs_x = x; + g_cfg_screen_changed = 1; +} + +void +cfg_printf(const char *fmt, ...) +{ + va_list ap; + int c; + int i; + + va_start(ap, fmt); + (void)vsnprintf(g_cfg_printf_buf, CFG_PRINTF_BUFSIZE, fmt, ap); + va_end(ap); + + for(i = 0; i < CFG_PRINTF_BUFSIZE; i++) { + c = g_cfg_printf_buf[i]; + if(c == 0) { + return; + } + cfg_putchar(c); + } +} + +void +cfg_print_dnum(dword64 dnum, int max_len) +{ + char buf[64]; + char buf2[64]; + int len, cnt, c; + int i, j; + + /* Prints right-adjusted "num" in field "max_len" wide */ + snprintf(&buf[0], 64, "%lld", dnum); + len = (int)strlen(buf); + for(i = 0; i < 64; i++) { + buf2[i] = ' '; + } + j = max_len + 1; + buf2[j] = 0; + j--; + cnt = 0; + for(i = len - 1; (i >= 0) && (j >= 1); i--) { + c = buf[i]; + if(c >= '0' && c <= '9') { + if(cnt >= 3) { + buf2[j--] = ','; + cnt = 0; + } + cnt++; + } + buf2[j--] = c; + } + cfg_printf(&buf2[1]); +} + +int +cfg_get_disk_name(char *outstr, int maxlen, int type_ext, int with_extras) +{ + Disk *dsk; + int slot, drive; + + slot = type_ext >> 8; + drive = type_ext & 0xff; + dsk = iwm_get_dsk_from_slot_drive(slot, drive); + + outstr[0] = 0; + if(dsk->name_ptr == 0) { + return 0; + } + + config_generate_config_kegs_name(outstr, maxlen, dsk, with_extras); + return dsk->dynapro_blocks; +} + +int +cfg_get_disk_locked(int type_ext) +{ + Disk *dsk; + int slot, drive; + + slot = type_ext >> 8; + drive = type_ext & 0xff; + dsk = iwm_get_dsk_from_slot_drive(slot, drive); + if(dsk->fd < 0) { + return 0; + } + + if(dsk->write_prot) { + return 1; + } else if(!dsk->write_through_to_unix) { + return 2; + } + + return 0; +} + +void +cfg_parse_menu(Cfg_menu *menuptr, int menu_pos, int highlight_pos, int change) +{ + char valbuf[CFG_OPT_MAXSTR]; + char *(*fn_ptr)(int); + int *iptr; + char **str_ptr; + const char *menustr; + char *curstr, *defstr, *str, *outstr; + void *edit_ptr; + int val, num_opts, opt_num, bufpos, outpos, curval, defval, type; + int type_ext, opt_get_str, separator, len, c, locked; + int i; + + // For this menu_pos line, create output in g_cfg_opt_buf[] string + // Highlight it if menu_pos==highlight_pos + // Allow arrows to modify the currently selected item of a list using + // change: -1 moves to a previous item, +1 moves to the next + + g_cfg_opt_buf[0] = 0; + + num_opts = 0; + opt_get_str = 0; + separator = ','; + + menuptr += menu_pos; /* move forward to entry menu_pos */ + + menustr = menuptr->str; + type = menuptr->cfgtype; + type_ext = (type >> 4); + type = type & 0xf; + len = (int)strlen(menustr) + 1; + + bufpos = 0; + outstr = &(g_cfg_opt_buf[0]); + + outstr[bufpos++] = ' '; // 0 + outstr[bufpos++] = ' '; // 1 + outstr[bufpos++] = '\t'; // 2 + outstr[bufpos++] = '\t'; // 3 + outstr[bufpos++] = ' '; // 4 + outstr[bufpos++] = ' '; // 5 + + // Figure out if we should get a checkmark + curval = -1; + defval = -1; + curstr = 0; + if(type == CFGTYPE_INT) { + iptr = menuptr->ptr; + curval = *iptr; + iptr = menuptr->defptr; + if(!iptr) { + printf("BAD MENU, defptr is 0!\n"); + } else { + defval = *iptr; + } + if(curval == defval) { + g_cfg_opt_buf[3] = 'D'; /* checkmark */ + g_cfg_opt_buf[4] = '\t'; + } + } + + if((type == CFGTYPE_FILE) || (type == CFGTYPE_STR)) { + str_ptr = (char **)menuptr->ptr; + curstr = *str_ptr; + str_ptr = (char **)menuptr->defptr; + if(!str_ptr) { + printf("BAD MENU, defptr str is 0!\n"); + defstr = ""; + } else { + defstr = *str_ptr; + } + if(strcmp(curstr, defstr) == 0) { + g_cfg_opt_buf[3] = 'D'; /* checkmark */ + g_cfg_opt_buf[4] = '\t'; + } + } + + // If it's a menu, give it a special menu indicator + if(type == CFGTYPE_MENU) { + g_cfg_opt_buf[1] = '\t'; + g_cfg_opt_buf[2] = 'M'; /* return-like symbol */ + g_cfg_opt_buf[3] = '\t'; + g_cfg_opt_buf[4] = ' '; + } + + if(type == CFGTYPE_DISK) { + locked = cfg_get_disk_locked(type_ext); + if(locked) { + g_cfg_opt_buf[4] = '*'; + if(locked == 2) { // inverse + g_cfg_opt_buf[2] = '\b'; + g_cfg_opt_buf[3] = '*'; + g_cfg_opt_buf[4] = '\b'; + } + } + } + + if(menu_pos == highlight_pos) { + outstr[bufpos++] = '\b'; + } + + opt_get_str = 2; + i = -1; + outpos = bufpos; +#if 0 + printf("cfg menu_pos: %d str len: %d\n", menu_pos, len); +#endif + while(++i < len) { + c = menustr[i]; + if(c == separator) { // ','? + if(i == 0) { + continue; + } + c = 0; + } + outstr[outpos++] = c; + outstr[outpos] = 0; + if(outpos >= CFG_OPT_MAXSTR) { + fprintf(stderr, "CFG_OPT_MAXSTR exceeded\n"); + my_exit(1); + return; + } + if(c == 0) { + if(opt_get_str == 2) { + outstr = &(valbuf[0]); + bufpos = outpos - 1; + opt_get_str = 0; + } else if(opt_get_str) { +#if 0 + if(menu_pos == highlight_pos) { + printf("menu_pos %d opt %d = %s=%d\n", + menu_pos, num_opts, + g_cfg_opts_str[0], + g_cfg_opts_vals[num_opts]); + } +#endif + num_opts++; + outstr = &(valbuf[0]); + opt_get_str = 0; + if(num_opts >= CFG_MAX_OPTS) { + fprintf(stderr, "CFG_MAX_OPTS oflow\n"); + my_exit(1); + return; + } + } else { + val = (word32)strtoul(valbuf, 0, 0); + g_cfg_opts_vals[num_opts] = val; + outstr = &(g_cfg_opts_str[0]); + if(val != curval) { + outstr = valbuf; + } + opt_get_str = 1; + } + outpos = 0; + outstr[0] = 0; + } + } + + if(menu_pos == highlight_pos) { + g_cfg_opt_buf[bufpos++] = '\b'; + } + + g_cfg_opt_buf[bufpos] = 0; + + // Decide what to display on the "right" side + str = 0; + opt_num = -1; + if((type == CFGTYPE_INT) || (type == CFGTYPE_FILE) || + (type == CFGTYPE_STR)) { + g_cfg_opt_buf[bufpos++] = ' '; + g_cfg_opt_buf[bufpos++] = '='; + g_cfg_opt_buf[bufpos++] = ' '; + g_cfg_opt_buf[bufpos] = 0; + for(i = 0; i < num_opts; i++) { + if(curval == g_cfg_opts_vals[i]) { + opt_num = i; + break; + } + } + } + + if(change != 0) { + if(type == CFGTYPE_INT) { + if(num_opts > 0) { + opt_num += change; + if(opt_num >= num_opts) { + opt_num = 0; + } + if(opt_num < 0) { + opt_num = num_opts - 1; + } + curval = g_cfg_opts_vals[opt_num]; + } else { + curval += change; + /* HACK: min_val, max_val testing here */ + } + iptr = (int *)menuptr->ptr; + cfg_int_update(iptr, curval); + } + g_config_kegs_update_needed = 1; + } + +#if 0 + if(menu_pos == highlight_pos) { + printf("menu_pos %d opt_num %d\n", menu_pos, opt_num); + } +#endif + + edit_ptr = g_cfg_edit_ptr; + if((edit_ptr == menuptr->ptr) && edit_ptr) { + // Just show the current edit string + str = cfg_shorten_filename(&(g_cfg_edit_buf[0]), 68 - 1); + cfg_strlcat(str, "\b \b", CFG_PATH_MAX); + } else if(opt_num >= 0) { + str = &(g_cfg_opts_str[0]); + } else { + if(type == CFGTYPE_INT) { + str = &(g_cfg_opts_str[0]); + snprintf(str, CFG_OPT_MAXSTR, "%d", curval); + } else if (type == CFGTYPE_DISK) { + str = &(g_cfg_opts_str[0]); + (void)cfg_get_disk_name(str, CFG_PATH_MAX, type_ext, 1); + str = cfg_shorten_filename(str, 68); + } else if ((type == CFGTYPE_FILE) || (type == CFGTYPE_STR)) { + str = cfg_shorten_filename(curstr, 68); + } else if(type == CFGTYPE_FUNC) { + fn_ptr = (char *(*)(int))menuptr->ptr; + str = ""; + curstr = (*fn_ptr)(1); + if(curstr) { + g_cfg_opt_buf[bufpos++] = ' '; + g_cfg_opt_buf[bufpos++] = ':'; + g_cfg_opt_buf[bufpos++] = ' '; + g_cfg_opt_buf[bufpos] = 0; + str = cfg_shorten_filename(curstr, 68); + free(curstr); + } + } else { + str = ""; + } + } + +#if 0 + if(menu_pos == highlight_pos) { + printf("menu_pos %d buf_pos %d, str is %s, %02x, %02x, " + "%02x %02x\n", + menu_pos, bufpos, str, g_cfg_opt_buf[bufpos-1], + g_cfg_opt_buf[bufpos-2], + g_cfg_opt_buf[bufpos-3], + g_cfg_opt_buf[bufpos-4]); + } +#endif + + g_cfg_opt_buf[bufpos] = 0; + cfg_strncpy(&(g_cfg_opt_buf[bufpos]), str, CFG_OPT_MAXSTR - bufpos - 1); + g_cfg_opt_buf[CFG_OPT_MAXSTR-1] = 0; +} + +void +cfg_get_base_path(char *pathptr, const char *inptr, int go_up) +{ + const char *tmpptr; + char *slashptr; + char *outptr; + int add_dotdot, is_dotdot; + int len; + int c; + + // Take full filename, copy it to pathptr, and truncate at last slash + // inptr and pathptr can be the same. + // If go_up is set, then replace a blank dir with ".." + // but first, see if path is currently just ../ over and over + // if so, just tack .. onto the end and return + + //printf("cfg_get_base start with %s\n", inptr); + + g_cfg_file_match[0] = 0; + tmpptr = inptr; + is_dotdot = 1; + while(1) { + if(tmpptr[0] == 0) { + break; + } + if((tmpptr[0] == '.') && (tmpptr[1] == '.') && + (tmpptr[2] == '/')) { + tmpptr += 3; + } else { + is_dotdot = 0; + break; + } + } + slashptr = 0; + outptr = pathptr; + c = -1; + while(c != 0) { + c = *inptr++; + if(c == '/') { + if(*inptr != 0) { /* if not a trailing slash... */ + slashptr = outptr; + } + } + *outptr++ = c; + } + if(!go_up) { + /* if not go_up, copy chopped part to g_cfg_file_match*/ + /* copy from slashptr+1 to end */ + tmpptr = slashptr+1; + if(slashptr == 0) { + tmpptr = pathptr; + } + cfg_strncpy(&g_cfg_file_match[0], tmpptr, CFG_PATH_MAX); + /* remove trailing / from g_cfg_file_match */ + len = (int)strlen(&g_cfg_file_match[0]); + if((len > 1) && (len < (CFG_PATH_MAX - 1)) && + g_cfg_file_match[len - 1] == '/') { + g_cfg_file_match[len - 1] = 0; + } + //printf("set g_cfg_file_match to %s\n", &g_cfg_file_match[0]); + } + if(!is_dotdot && (slashptr != 0)) { + slashptr[0] = '/'; + slashptr[1] = 0; + outptr = slashptr + 2; + } + add_dotdot = 0; + if(pathptr[0] == 0 || is_dotdot) { + /* path was blank, or consisted of just ../ pattern */ + if(go_up) { + add_dotdot = 1; + } + } else if(slashptr == 0) { + /* no slashes found, but path was not blank--make it blank */ + if(pathptr[0] == '/') { + pathptr[1] = 0; + } else { + pathptr[0] = 0; + } + } + + if(add_dotdot) { + --outptr; + outptr[0] = '.'; + outptr[1] = '.'; + outptr[2] = '/'; + outptr[3] = 0; + } + + //printf("cfg_get_base end with %s, is_dotdot:%d, add_dotdot:%d\n", + // pathptr, is_dotdot, add_dotdot); +} + +char * +cfg_name_new_image(int get_status) +{ + // Called from menu to create a new disk image, this will pop up the + // file selection dialog. Once name is selected, + // cfg_create_new_image() is called. + if(get_status) { + return 0; + } + printf("cfg_name_new_image called!\n"); + g_cfg_slotdrive = g_cfg_newdisk_slotdrive; + g_cfg_newdisk_select = 1; + cfg_file_init(); + return 0; +} + +void +cfg_dup_existing_image(word32 slotdrive) +{ + // Set g_cfg_newdisk_* to copy the slotdrive image + g_cfg_slotdrive = slotdrive; + g_cfg_newdisk_select = 2; // Do DUP + cfg_file_init(); +} + +void +cfg_dup_image_selected() +{ + Disk *dsk; + Woz_info *wozinfo_ptr; + byte *bufptr; + char *str; + dword64 dsize, dret; + word32 slotdrive; + int fd; + + // printf("cfg_dup_image_selected\n"); + + slotdrive = g_cfg_slotdrive; + g_cfg_slotdrive = 0; + g_menuptr = &g_cfg_disk_menu[0]; + g_menu_redraw_needed = 1; + g_cfg_newdisk_select = 0; + g_cfg_newdisk_slotdrive = 0; + printf("slotdrive:%04x\n", slotdrive); + if(slotdrive < 0x500) { + return; // Invalid slot,drive: Do nothing + } + dsk = iwm_get_dsk_from_slot_drive((slotdrive >> 8) & 7, + slotdrive & 0xff); + if(dsk->fd < 0) { + return; // No disk + } + dsize = dsk->dimage_size; + + str = &g_cfg_file_path[0], + printf("Create dup image %s, dsize:%lld\n", str, dsize); + if((word32)dsize != dsize) { + return; + } + fd = open(str, O_RDWR | O_CREAT | O_TRUNC, 0x1b6); + if(fd < 0) { + printf("Open %s failed, errno:%d\n", str, errno); + return; + } + wozinfo_ptr = dsk->wozinfo_ptr; + if(wozinfo_ptr && !dsk->write_through_to_unix) { + // Just write out the WOZ image, and then fully enable this + // image + printf("Writing out .WOZ image to %s, size:%d\n", str, + wozinfo_ptr->woz_size); + if((dsk->raw_data == 0) && (dsk->fd >= 0)) { + close(dsk->fd); + } + dsk->fd = fd; + woz_rewrite_crc(dsk, wozinfo_ptr->woz_size); + // Above will recalc CRC and write out woz_size bytes + dsk->raw_data = 0; + dsk->write_through_to_unix = 1; + free(dsk->name_ptr); + dsk->name_ptr = kegs_malloc_str(str); + free(dsk->partition_name); + dsk->partition_name = 0; + dsk->image_type = DSK_TYPE_WOZ; + dsk->dimage_size = wozinfo_ptr->woz_size; + dsk->dimage_start = 0; + g_config_kegs_update_needed = 1; + woz_check_file(dsk); + } else if(dsk->raw_data) { + cfg_write_to_fd(fd, dsk->raw_data, 0, dsize); + close(fd); + } else { + bufptr = malloc((size_t)dsize); + if((bufptr != 0) && ((size_t)dsize == dsize)) { + dret = cfg_read_from_fd(dsk->fd, bufptr, 0, dsize); + if(dret == dsize) { + cfg_write_to_fd(fd, bufptr, 0, dsize); + } + } + free(bufptr); + close(fd); + } +} + +void +cfg_validate_image(word32 slotdrive) +{ + Disk *dsk; + + dsk = iwm_get_dsk_from_slot_drive((slotdrive >> 8) & 7, + slotdrive & 0xff); + dynapro_validate_any_image(dsk); +} + +void +cfg_toggle_lock_disk(word32 slotdrive) +{ + Disk *dsk; + + dsk = iwm_get_dsk_from_slot_drive((slotdrive >> 8) & 7, + slotdrive & 0xff); + iwm_toggle_lock(dsk); +} + +int +cfg_create_new_image_act(const char *str, int type, int size_blocks) +{ + byte buf[512]; + dword64 dret; + int fd, ret; + int i; + + // Called after file dialog selects a new image name, this creates it + + if(size_blocks == 65536) { + size_blocks--; // Shrink to 65535 total blocks + } + printf("Create new image type:%d, size:%dKB\n", type, size_blocks/2); + fd = open(str, O_RDWR | O_CREAT | O_TRUNC, 0x1b6); + if(fd < 0) { + printf("Open %s failed, errno:%d\n", str, errno); + return fd; + } + for(i = 0; i < 512; i++) { + buf[i] = 0; + } + + ret = 0; + if(type == 2) { // WOZ + (void)woz_new(fd, str, size_blocks/2); + } else { + for(i = 0; i < size_blocks; i++) { + dret = cfg_write_to_fd(fd, &(buf[0]), i * 512U, 512); + if(dret != 512) { + ret = -1; + break; + } + } + } + close(fd); + + return ret; // 0=success, -1 is a failure +} + +void +cfg_create_new_image() +{ + word32 dynamic_blocks; + int ret; + + // Type is in g_cfg_file_path. Create this file and prepare it + printf("Creating new image: %s\n", &g_cfg_file_path[0]); + + if(g_cfg_newdisk_select == 2) { + cfg_dup_image_selected(); + return; + } + ret = 0; + dynamic_blocks = 0; + if(g_cfg_newdisk_type == 3) { + dynamic_blocks = g_cfg_newdisk_blocks; + } else { + ret = cfg_create_new_image_act(&g_cfg_file_path[0], + g_cfg_newdisk_type, g_cfg_newdisk_blocks); + } + if(ret < 0) { + // Maybe open a dialog? Oh well...do nothing + } else { + insert_disk((g_cfg_slotdrive >> 8) & 0xf, + g_cfg_slotdrive & 0xff, &(g_cfg_file_path[0]), + 0, 0, -2, dynamic_blocks); + } + g_cfg_slotdrive = 0; + g_menuptr = &g_cfg_disk_menu[0]; + g_menu_redraw_needed = 1; + g_cfg_newdisk_select = 0; + g_cfg_newdisk_slotdrive = 0; +} + +void +cfg_file_init() +{ + int slot, drive, is_dynapro; + int i; + + is_dynapro = 0; + if((g_cfg_slotdrive & 0xfff) == 0xfff) { // File selection + // Just use g_cfg_file_def_name + cfg_strncpy(&g_cfg_tmp_path[0], g_cfg_file_def_name, + CFG_PATH_MAX); + } else { + is_dynapro = cfg_get_disk_name(&g_cfg_tmp_path[0], CFG_PATH_MAX, + g_cfg_slotdrive, 0); + + slot = (g_cfg_slotdrive >> 8) & 7; + drive = g_cfg_slotdrive & 1; + for(i = 0; i < 6; i++) { + if(g_cfg_tmp_path[0] != 0) { + break; + } + /* try to get a starting path from some mounted drive */ + drive = !drive; + if(i & 1) { + slot++; + if(slot >= 8) { + slot = 5; + } + } + is_dynapro = cfg_get_disk_name(&g_cfg_tmp_path[0], + CFG_PATH_MAX, (slot << 8) + drive, 0); + } + } + + cfg_get_base_path(&g_cfg_file_curpath[0], &g_cfg_tmp_path[0], 0); + if(is_dynapro) { + // Use the full path to the dir (don't strip off last part) + cfg_strncpy(&g_cfg_file_curpath[0], &g_cfg_tmp_path[0], + CFG_PATH_MAX); + } + g_cfg_dirlist.invalid = 1; +} + +void +cfg_free_alldirents(Cfg_listhdr *listhdrptr) +{ + int i; + + if(listhdrptr->max > 0) { + // Free the old directory listing + for(i = 0; i < listhdrptr->last; i++) { + free(listhdrptr->direntptr[i].name); + } + free(listhdrptr->direntptr); + } + + listhdrptr->direntptr = 0; + listhdrptr->last = 0; + listhdrptr->max = 0; + listhdrptr->invalid = 0; + + listhdrptr->topent = 0; + listhdrptr->curent = 0; +} + +void +cfg_file_add_dirent_unique(Cfg_listhdr *listhdrptr, const char *nameptr, + int is_dir, dword64 dsize, dword64 dimage_start, dword64 compr_dsize, + int part_num) +{ + Cfg_dirent *direntptr; + int num, namelen, this_len; + int i; + + // Loop through all entries, make sure name is unique + num = listhdrptr->last; + namelen = (int)strlen(nameptr); + for(i = 0; i < num; i++) { + direntptr = &(listhdrptr->direntptr[i]); + this_len = (int)strlen(direntptr->name); + if(cfg_str_match(direntptr->name, nameptr, namelen) == 0) { + // It's a match...check len + if(namelen == this_len) { + return; + } + } + } + cfg_file_add_dirent(listhdrptr, nameptr, is_dir, dsize, dimage_start, + compr_dsize, part_num); +} + +void +cfg_file_add_dirent(Cfg_listhdr *listhdrptr, const char *nameptr, int is_dir, + dword64 dsize, dword64 dimage_start, dword64 compr_dsize, int part_num) +{ + Cfg_dirent *direntptr; + char *ptr; + int inc_amt, namelen; + + namelen = (int)strlen(nameptr); + if(listhdrptr->last >= listhdrptr->max) { + // realloc + inc_amt = MY_MAX(64, listhdrptr->max); + inc_amt = MY_MIN(inc_amt, 1024); + listhdrptr->max += inc_amt; + listhdrptr->direntptr = realloc(listhdrptr->direntptr, + listhdrptr->max * sizeof(Cfg_dirent)); + } + ptr = malloc(namelen+1+is_dir); + cfg_strncpy(ptr, nameptr, namelen+1); + if(is_dir && (namelen >= 1) && (ptr[namelen - 1] != '/')) { + // Add a trailing '/' to directories, unless already there + cfg_strlcat(ptr, "/", namelen + 1 + is_dir); + } +#if 0 + printf("...file entry %d is %s\n", listhdrptr->last, ptr); +#endif + direntptr = &(listhdrptr->direntptr[listhdrptr->last]); + direntptr->name = ptr; + direntptr->is_dir = is_dir; + direntptr->dsize = dsize; + direntptr->dimage_start = dimage_start; + direntptr->compr_dsize = compr_dsize; + direntptr->part_num = part_num; + listhdrptr->last++; +} + +int +cfg_dirent_sortfn(const void *obj1, const void *obj2) +{ + const Cfg_dirent *direntptr1, *direntptr2; + int ret; + + /* Called by qsort to sort directory listings */ + direntptr1 = (const Cfg_dirent *)obj1; + direntptr2 = (const Cfg_dirent *)obj2; + ret = cfg_str_match(direntptr1->name, direntptr2->name, CFG_PATH_MAX); + return ret; +} + +int +cfg_str_match(const char *str1, const char *str2, int len) +{ + return cfg_str_match_maybecase(str1, str2, len, g_cfg_ignorecase); +} + +int +cfg_str_match_maybecase(const char *str1, const char *str2, int len, + int ignorecase) +{ + const byte *bptr1, *bptr2; + int c, c2; + int i; + + /* basically, work like strcmp or strcasecmp */ + + bptr1 = (const byte *)str1; + bptr2 = (const byte *)str2; + for(i = 0; i < len; i++) { + c = *bptr1++; + c2 = *bptr2++; + if(ignorecase) { + c = tolower(c); + c2 = tolower(c2); + } + if((c == 0) || (c2 == 0) || (c != c2)) { + return c - c2; + } + } + + return 0; +} + +int +cfgcasecmp(const char *str1, const char *str2) +{ + return cfg_str_match_maybecase(str1, str2, 32767, 1); +} + +int +cfg_strlcat(char *dstptr, const char *srcptr, int dstsize) +{ + char *ptr; + int destlen, srclen, ret, c; + + // Concat srcptr to the end of dstptr, ensuring a null within dstsize + // Return the total buffer size that would be needed, even if dstsize + // is too small. Compat with strlcat() + destlen = (int)strlen(dstptr); + srclen = (int)strlen(srcptr); + ret = destlen + srclen; + dstsize--; + if(destlen >= dstsize) { + return ret; // Do nothing, buf too small + } + ptr = dstptr + destlen; + while(destlen < dstsize) { + c = *srcptr++; + *ptr++ = c; + if(c == 0) { + return ret; + } + destlen++; + } + dstptr[dstsize] = 0; + return ret; +} + +char * +cfg_strncpy(char *dstptr, const char *srcptr, int dstsize) +{ + char *ptr; + int c; + + // Copy srcptr to dstptr, ensuring there is room for a null + // Compatible with strncpy()--except dstptr is ALWAYS null terminated + ptr = dstptr; + while(--dstsize > 0) { + c = *srcptr++; + *ptr++ = c; + if(c == 0) { + return dstptr; + } + } + *ptr = 0; + return dstptr; +} + +const char * +cfg_str_basename(const char *str) +{ + int len; + int i; + + // If str is /aa/bb/cc, this routine returns cc + len = (int)strlen(str); + while(len && (str[len - 1] == '/')) { + len--; // Ignore trailing '/' if there are any + } + for(i = len - 1; i > 0; i--) { + if(str[i] == '/') { + return str + i + 1; + } + } + + return str; +} + +char * +cfg_strncpy_dirname(char *dstptr, const char *srcptr, int dstsize) +{ + char *ptr; + int c; + + // If srcptr is /aa/bb/cc, this routine returns /aa/bb/ + // Copy srcptr to dstptr, ensuring there is room for a null + // Compatible with strncpy()--except dstptr is ALWAYS null terminated + ptr = dstptr; + while(--dstsize > 0) { + c = *srcptr++; + *ptr++ = c; + if(c == 0) { + // Remove any trailing /'s + ptr--; + while((ptr > dstptr) && (ptr[0] == '/')) { + ptr[0] = 0; + ptr--; + } + while(ptr > dstptr) { + if(ptr[0] == '/') { + ptr[1] = 0; + break; + } + ptr--; + } + return dstptr; + } + } + *ptr = 0; + return dstptr; +} + +void +cfg_file_readdir(const char *pathptr) +{ + struct dirent *direntptr; + struct stat stat_buf; + DIR *dirptr; + mode_t fmt; + char *str; + const char *tmppathptr; + int size, ret, is_dir, is_gz, len; + int i; + + if(!strncmp(pathptr, &g_cfg_file_cachedpath[0], CFG_PATH_MAX) && + (g_cfg_dirlist.last > 0) && (g_cfg_dirlist.invalid==0)){ + return; + } + // No match, must read new directory + + // Free all dirents that were cached previously + cfg_free_alldirents(&g_cfg_dirlist); + + cfg_strncpy(&g_cfg_file_cachedpath[0], pathptr, CFG_PATH_MAX); + cfg_strncpy(&g_cfg_file_cachedreal[0], pathptr, CFG_PATH_MAX); + + str = &g_cfg_file_cachedreal[0]; + + for(i = 0; i < 200; i++) { + len = (int)strlen(str); + if(len <= 0) { + break; + } else if(len < CFG_PATH_MAX-2) { + if(str[len-1] != '/') { + // append / to make various routines work + str[len] = '/'; + str[len+1] = 0; + } + } + ret = cfg_stat(str, &stat_buf, 0); + is_dir = 0; + if(ret == 0) { + fmt = stat_buf.st_mode & S_IFMT; + if(fmt == S_IFDIR) { + /* it's a directory */ + is_dir = 1; + } + } + if(is_dir) { + break; + } else { + // user is entering more path, use base for display + cfg_get_base_path(str, str, 0); + } + } + + tmppathptr = str; + if(str[0] == 0) { + tmppathptr = "."; + } + cfg_file_add_dirent(&g_cfg_dirlist, "..", 1, 0, 0, 0, -1); + + dirptr = opendir(tmppathptr); + if(dirptr == 0) { + printf("Could not open %s as a directory\n", tmppathptr); + return; + } + while(1) { + direntptr = readdir(dirptr); + if(direntptr == 0) { + break; + } + if(!strcmp(".", direntptr->d_name)) { + continue; + } + if(!strcmp("..", direntptr->d_name)) { + continue; + } + /* Else, see if it is a directory or a file */ + cfg_strncpy(&(g_cfg_tmp_path[0]), &(g_cfg_file_cachedreal[0]), + CFG_PATH_MAX); + cfg_strlcat(&(g_cfg_tmp_path[0]), direntptr->d_name, + CFG_PATH_MAX); + ret = cfg_stat(&g_cfg_tmp_path[0], &stat_buf, 0); + len = (int)strlen(g_cfg_tmp_path); + is_dir = 0; + is_gz = 0; + if((len > 3) && !cfgcasecmp(&g_cfg_tmp_path[len - 3], ".gz")) { + is_gz = 1; + } + if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".bz2")) { + is_gz = 1; + } + if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".zip")) { + is_gz = 1; + } + if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".woz")) { + is_gz = 1; + } + if((len > 4) && !cfgcasecmp(&g_cfg_tmp_path[len - 4], ".sdk")) { + is_gz = 1; + } + if(ret != 0) { + printf("stat %s ret %d, errno:%d\n", &g_cfg_tmp_path[0], + ret, errno); + stat_buf.st_size = 0; + continue; /* skip it */ + } else { + fmt = stat_buf.st_mode & S_IFMT; + size = (int)stat_buf.st_size; + if(fmt == S_IFDIR) { + /* it's a directory */ + is_dir = 1; + } else if((fmt == S_IFREG) && (is_gz == 0)) { + if((g_cfg_slotdrive & 0xfff) == 0xfff) { + /* see if there are size limits */ + if((size < g_cfg_file_min_size) || + (size > g_cfg_file_max_size)) { + continue; /* skip it */ + } + } else { + if(size < 140*1024) { + continue; /* skip it */ + } + } + } + } + cfg_file_add_dirent(&g_cfg_dirlist, direntptr->d_name, is_dir, + (dword64)stat_buf.st_size, 0, 0, -1); + } + + closedir(dirptr); + + /* then sort the results (Mac's HFS+ is sorted, but other FS won't be)*/ + qsort(&(g_cfg_dirlist.direntptr[0]), g_cfg_dirlist.last, + sizeof(Cfg_dirent), cfg_dirent_sortfn); + + g_cfg_dirlist.curent = g_cfg_dirlist.last - 1; + for(i = g_cfg_dirlist.last - 1; i >= 0; i--) { + ret = cfg_str_match(&(g_cfg_file_match[0]), + g_cfg_dirlist.direntptr[i].name, CFG_PATH_MAX); + if(ret <= 0) { + /* set cur ent to closest filename to the match name */ + g_cfg_dirlist.curent = i; + } + } +} + +char * +cfg_shorten_filename(const char *in_ptr, int maxlen) +{ + char *out_ptr; + int len, c; + int i; + + /* Warning: uses a static string, not reentrant! */ + + out_ptr = &(g_cfg_file_shortened[0]); + len = (int)strlen(in_ptr); + maxlen = MY_MIN(len, maxlen); + for(i = 0; i < maxlen; i++) { + c = in_ptr[i] & 0x7f; + if(c < 0x20) { + c = '*'; + } + out_ptr[i] = c; + } + out_ptr[maxlen] = 0; + if(len > maxlen) { + for(i = 0; i < (maxlen/2); i++) { + c = in_ptr[len-i-1] & 0x7f; + if(c < 0x20) { + c = '*'; + } + out_ptr[maxlen-i-1] = c; + } + out_ptr[(maxlen/2) - 1] = '.'; + out_ptr[maxlen/2] = '.'; + out_ptr[(maxlen/2) + 1] = '.'; + } + + return out_ptr; +} + +void +cfg_fix_topent(Cfg_listhdr *listhdrptr) +{ + int num_to_show; + + num_to_show = listhdrptr->num_to_show; + + /* Force curent and topent to make sense */ + if(listhdrptr->curent >= listhdrptr->last) { + listhdrptr->curent = listhdrptr->last - 1; + } + if(listhdrptr->curent < 0) { + listhdrptr->curent = 0; + } + if(abs(listhdrptr->curent - listhdrptr->topent) >= num_to_show) { + listhdrptr->topent = listhdrptr->curent - (num_to_show/2); + } + if(listhdrptr->topent > listhdrptr->curent) { + listhdrptr->topent = listhdrptr->curent - (num_to_show/2); + } + if(listhdrptr->topent < 0) { + listhdrptr->topent = 0; + } +} + +void +cfg_file_draw() +{ + Cfg_listhdr *listhdrptr; + Cfg_dirent *direntptr; + const char *tmp_str; + char *str, *fmt; + int num_to_show; + int yoffset; + int x, y; + int i; + + //printf("cfg_file_draw called\n"); + + cfg_file_readdir(&g_cfg_file_curpath[0]); + + for(y = 0; y < 21; y++) { + cfg_htab_vtab(0, y); + cfg_printf("\tZ\t"); + for(x = 1; x < 79; x++) { + cfg_htab_vtab(x, y); + cfg_putchar(' '); + } + cfg_htab_vtab(79, y); + cfg_printf("\t_\t"); + } + + cfg_htab_vtab(1, 0); + cfg_putchar('\b'); + for(x = 1; x < 79; x++) { + cfg_putchar(' '); + } + if((g_cfg_slotdrive & 0xfff) == 0xfff) { + cfg_htab_vtab(5, 0); + cfg_printf("\bSelect file to use as %-40s\b", + cfg_shorten_filename(g_cfg_file_def_name, 40)); + } else { + cfg_htab_vtab(30, 0); + tmp_str = "Select"; + if(g_cfg_newdisk_select == 2) { + tmp_str = "Create duplicate"; + } else if(g_cfg_newdisk_select) { + tmp_str = "Create new"; + } + cfg_printf("\b%s image for s%dd%d\b", tmp_str, + (g_cfg_slotdrive >> 8) & 0xf, + (g_cfg_slotdrive & 0xff) + 1); + } + + cfg_htab_vtab(2, 1); + cfg_printf("config.kegs path: %-56s", + cfg_shorten_filename(&g_config_kegs_name[0], 56)); + + cfg_htab_vtab(2, 2); + cfg_printf("Current KEGS directory: %-50s", + cfg_shorten_filename(&g_cfg_cwd_str[0], 50)); + + cfg_htab_vtab(2, 3); + + str = ""; + if(g_cfg_file_pathfield) { + str = "\b \b"; + } + cfg_printf("Path: %s%s", + cfg_shorten_filename(&g_cfg_file_curpath[0], 68), str); + + cfg_htab_vtab(0, 4); + cfg_printf(" \t"); + for(x = 1; x < 79; x++) { + cfg_putchar('\\'); + } + cfg_printf("\t "); + + /* Force curent and topent to make sense */ + listhdrptr = &g_cfg_dirlist; + num_to_show = CFG_NUM_SHOWENTS; + yoffset = 5; + if(g_cfg_select_partition > 0) { + listhdrptr = &g_cfg_partitionlist; + num_to_show -= 2; + cfg_htab_vtab(2, yoffset); + cfg_printf("Select partition of %-50s", + cfg_shorten_filename(&g_cfg_file_path[0], 50), ""); + cfg_htab_vtab(2, yoffset + 1); + cfg_printf("Current partition: %-50s", + cfg_shorten_filename(&g_cfg_part_path[0], 50), ""); + yoffset += 2; + } + + listhdrptr->num_to_show = num_to_show; + cfg_fix_topent(listhdrptr); + for(i = 0; i < num_to_show; i++) { + y = i + yoffset; + if(listhdrptr->last > (i + listhdrptr->topent)) { + direntptr = &(listhdrptr-> + direntptr[i + listhdrptr->topent]); + cfg_htab_vtab(2, y); + if(direntptr->is_dir) { + cfg_printf("\tXY\t "); + } else { + cfg_printf(" "); + } + if(direntptr->part_num >= 0) { + cfg_printf("%3d: ", direntptr->part_num); + } + str = cfg_shorten_filename(direntptr->name, 50); + fmt = "%-50s"; + if((i + listhdrptr->topent) == listhdrptr->curent) { + if(g_cfg_file_pathfield == 0) { + fmt = "\b%-50s\b"; + } else { + fmt = "%-49s\b \b"; + } + //printf("file highlight l %d top:%d cur:%d\n", + // i, listhdrptr->topent, + // listhdrptr->curent); + } + cfg_printf(fmt, str); + if(!direntptr->is_dir) { + cfg_print_dnum(direntptr->dsize, 18); + } + //printf(" :%s:%lld:\n", str, direntptr->dsize); + } + } + + cfg_htab_vtab(1, 5 + CFG_NUM_SHOWENTS); + cfg_putchar('\t'); + for(x = 1; x < 79; x++) { + cfg_putchar('L'); + } + cfg_putchar('\t'); + + //printf("cfg_file_draw done\n"); +} + +void +cfg_partition_select_all() +{ + word32 slot_drive; + int part_path_len, curent; + + slot_drive = g_cfg_slotdrive; + part_path_len = (int)strlen(&g_cfg_part_path[0]); + curent = g_cfg_partitionlist.curent; + while(1) { + g_cfg_slotdrive = slot_drive; + g_cfg_partitionlist.curent = curent; + cfg_partition_selected(); + if(g_cfg_slotdrive != 0) { + // Something went wrong, get out + return; + } + slot_drive++; + curent++; + g_cfg_part_path[part_path_len] = 0; + if(curent >= g_cfg_partitionlist.last) { + return; + } + if((slot_drive >> 8) == 7) { + if((slot_drive & 0xff) >= MAX_C7_DISKS) { + return; + } + if((slot_drive & 0xff) >= 12) { + return; + } + } else if((slot_drive & 0xff) >= 2) { + return; + } + } +} + +void +cfg_partition_selected() +{ + char *str; + const char *part_str; + char *part_str2; + int pos; + int part_num; + + pos = g_cfg_partitionlist.curent; + str = g_cfg_partitionlist.direntptr[pos].name; + if(g_cfg_partitionlist.direntptr[pos].is_dir) { + // Add this path to the partition path, and try again + if(!strcmp(str, "../")) { + /* go up one directory */ + cfg_get_base_path(&g_cfg_part_path[0], + &g_cfg_part_path[0], 1); + } else { + cfg_strlcat(&(g_cfg_part_path[0]), str, CFG_PATH_MAX); + } + cfg_partition_make_list_from_name(&g_cfg_file_path[0]); + return; + } + + part_num = -2; + part_str = 0; + if(str[0] == 0 || (str[0] >= '0' && str[0] <= '9')) { + part_num = g_cfg_partitionlist.direntptr[pos].part_num; + } else { + part_str = str; + } + part_str2 = 0; + if(part_str != 0) { + cfg_strlcat(&g_cfg_part_path[0], part_str, CFG_PATH_MAX); + part_str2 = kegs_malloc_str(&g_cfg_part_path[0]); + g_cfg_part_path[0] = 0; + } + printf("cfg_partition_selected, pos:%d, g_cfg_file_path[0]:%s, " + "part:%s\n", pos, g_cfg_file_path, part_str2); + + insert_disk((g_cfg_slotdrive >> 8) & 0xf, g_cfg_slotdrive & 0xff, + &(g_cfg_file_path[0]), 0, part_str2, part_num, 0); + free(part_str2); + g_cfg_slotdrive = 0; + g_cfg_newdisk_select = 0; + g_cfg_select_partition = -1; +} + +void +cfg_file_selected() +{ + struct stat stat_buf; + char *str; + int fmt, stat_errno, is_cmd_key_down; + int ret; + + is_cmd_key_down = adb_is_cmd_key_down() && + ((g_cfg_slotdrive & 0xfff) != 0xfff); + // Cmd-Return means create DynaPro image when using slot/drive + + if(g_cfg_select_partition > 0) { + cfg_partition_selected(); + return; + } + + if(!is_cmd_key_down && (g_cfg_file_pathfield == 0)) { + // in file section area of window + str = g_cfg_dirlist.direntptr[g_cfg_dirlist.curent].name; + if(!strcmp(str, "../")) { + /* go up one directory */ + cfg_get_base_path(&g_cfg_file_curpath[0], + &g_cfg_file_curpath[0], 1); + return; + } + + cfg_strncpy(&(g_cfg_file_path[0]), &(g_cfg_file_cachedreal[0]), + CFG_PATH_MAX); + cfg_strlcat(&(g_cfg_file_path[0]), str, CFG_PATH_MAX); + } else { + // just use cfg_file_curpath directly + cfg_strncpy(&g_cfg_file_path[0], &g_cfg_file_curpath[0], + CFG_PATH_MAX); + } + + ret = cfg_stat(&g_cfg_file_path[0], &stat_buf, 0); + stat_errno = errno; + fmt = stat_buf.st_mode & S_IFMT; + cfg_printf("Stat'ing %s, st_mode is: %08x\n", &g_cfg_file_path[0], + (int)stat_buf.st_mode); + + if((ret == 0) && (fmt == S_IFDIR) && is_cmd_key_down && + (g_cfg_newdisk_select != 2)) { + // Make a new DynaPro disk + cfg_insert_disk_dynapro((g_cfg_slotdrive >> 8) & 0xf, + g_cfg_slotdrive & 0xff, &g_cfg_file_path[0]); + g_cfg_slotdrive = 0; // End file selection + g_cfg_newdisk_select = 0; + g_menuptr = &g_cfg_disk_menu[0]; + } else if((g_cfg_newdisk_select == 1) && (g_cfg_newdisk_type == 3) && + g_cfg_file_pathfield && (fmt == S_IFDIR)) { + // Special handling for Dynamic ProDOS directories. User hit + // return in the Path field on a directory, use this directory + cfg_create_new_image(); + } if(ret != 0) { + if(g_cfg_newdisk_select && (g_cfg_newdisk_type != 3)) { + // This looks good, a new file name was entered + if(stat_errno == ENOENT) { + cfg_create_new_image(); + } else { + printf("Unknown errno:%d while checking %s\n", + stat_errno, &g_cfg_file_path[0]); + } + } else { + printf("stat %s returned %d, errno: %d\n", + &g_cfg_file_path[0], ret, stat_errno); + } + } else if(fmt == S_IFDIR) { + /* it's a directory */ + cfg_strncpy(&g_cfg_file_curpath[0], &g_cfg_file_path[0], + CFG_PATH_MAX); + } else if(g_cfg_newdisk_select) { + // Do not allow selecting files, just ignore it + } else if((g_cfg_slotdrive & 0xfff) < 0xfff) { + /* select it */ + ret = cfg_maybe_insert_disk((g_cfg_slotdrive >> 8) & 0xf, + g_cfg_slotdrive & 0xff, &g_cfg_file_path[0]); + if(ret > 0) { + g_cfg_slotdrive = 0; + g_cfg_newdisk_select = 0; + } + } else { + cfg_file_update_ptr(g_cfg_file_strptr, &g_cfg_file_path[0], 1); + g_cfg_slotdrive = 0; + g_cfg_newdisk_select = 0; + } +} + +void +cfg_file_handle_key(int key) +{ + Cfg_listhdr *listhdrptr; + int len, lowkey, got_match_key, is_cmd_key_down; + + // Modes: g_cfg_slotdrive: 1 to 0xfff: File selection dialog + // otherwise: normal menu being shown + // g_cfg_file_pathfield: File selection with cursor in Path: field + // otherwise: in scrolling file selection field + // g_cfg_select_partition: file selection for partition name + if(g_cfg_file_pathfield) { + if(key >= 0x20 && key < 0x7f) { + len = (int)strlen(&g_cfg_file_curpath[0]); + if(len < CFG_PATH_MAX-4) { + g_cfg_file_curpath[len] = key; + g_cfg_file_curpath[len+1] = 0; + } + return; + } + } + + listhdrptr = &g_cfg_dirlist; + is_cmd_key_down = 0; + if(g_cfg_select_partition > 0) { + listhdrptr = &g_cfg_partitionlist; + is_cmd_key_down = adb_is_cmd_key_down() && + ((g_cfg_slotdrive & 0xfff) != 0xfff); + } + lowkey = tolower(key); + got_match_key = 0; + if((g_cfg_file_pathfield == 0) && (lowkey >= 'a') && (lowkey <= 'z') && + !is_cmd_key_down) { + /* jump to file starting with this letter */ + g_cfg_file_match[0] = key; + g_cfg_file_match[1] = 0; + g_cfg_dirlist.invalid = 1; /* re-read directory */ + got_match_key = 1; + } + + switch(key) { + case 0x1b: // ESC + if(((g_cfg_slotdrive & 0xfff) < 0xfff) && + !g_cfg_newdisk_select) { + iwm_eject_disk_by_num((g_cfg_slotdrive >> 8) & 0xf, + g_cfg_slotdrive & 0xff); + } + g_cfg_slotdrive = 0; + g_cfg_select_partition = -1; + g_cfg_dirlist.invalid = 1; + g_cfg_newdisk_select = 0; + break; + case 0x0a: /* down arrow */ + if(g_cfg_file_pathfield == 0) { + listhdrptr->curent++; + cfg_fix_topent(listhdrptr); + } + break; + case 0x0b: /* up arrow */ + if(g_cfg_file_pathfield == 0) { + listhdrptr->curent--; + cfg_fix_topent(listhdrptr); + } + break; + case 0x0d: /* return */ + //printf("handling return press\n"); + cfg_file_selected(); + break; + case 0x61: /* 'a' */ + if(is_cmd_key_down && (g_cfg_select_partition > 0)) { + cfg_partition_select_all(); + } + break; + case 0x09: /* tab */ + g_cfg_file_pathfield = !g_cfg_file_pathfield; + if(g_cfg_select_partition > 0) { + // If selecting file inside zip or partition, don't + // allow editing of the Path info + g_cfg_file_pathfield = 0; + } + break; + case 0x08: /* left arrow */ + case 0x7f: /* delete key */ + if(g_cfg_file_pathfield) { + // printf("left arrow/delete\n"); + len = (int)strlen(&g_cfg_file_curpath[0]) - 1; + if(len >= 0) { + g_cfg_file_curpath[len] = 0; + } + } + break; + default: + if(!got_match_key) { + printf("key: %02x\n", key); + } + } +#if 0 + printf("curent: %d, topent: %d, last: %d\n", + g_cfg_dirlist.curent, g_cfg_dirlist.topent, g_cfg_dirlist.last); +#endif +} + +void +cfg_draw_menu() +{ + const char *str; + Cfg_menu *menuptr; + int print_eject_help, line, type, match_found, menu_line, max_line; + + g_menu_redraw_needed = 0; + + menuptr = g_menuptr; + if(menuptr == 0) { + menuptr = g_cfg_main_menu; + } + if(g_rom_version < 0) { + /* Must select ROM file */ + menuptr = g_cfg_rom_menu; + } + g_menuptr = menuptr; + + cfg_home(); + line = 1; + max_line = 1; + match_found = 0; + print_eject_help = 0; + menu_line = g_menu_line; + cfg_printf("%s\n\n", menuptr[0].str); + while(line < 24) { + str = menuptr[line].str; + type = menuptr[line].cfgtype; + if(str == 0) { + break; + } + if((type & 0xf) == CFGTYPE_DISK) { + print_eject_help = 1; + } +#if 0 + printf("Calling parse_menu line:%d, menu_line:%d, %p\n", line, + menu_line, menuptr); +#endif + cfg_parse_menu(menuptr, line, menu_line, 0); + if(line == g_menu_line) { + if(type != 0) { + match_found = 1; + } else if(g_menu_inc) { + menu_line++; + } else { + menu_line--; + } + } + if(line > max_line) { + max_line = line; + } + + cfg_printf("%s\n", g_cfg_opt_buf); + line++; + } + if((menu_line < 1) && !match_found) { + menu_line = 1; + } + if((menu_line >= max_line) && !match_found) { + menu_line = max_line; + } + + g_menu_line = menu_line; + g_menu_max_line = max_line; + if(!match_found) { + g_menu_redraw_needed = 1; + } + if(g_rom_version < 0) { + cfg_htab_vtab(0, 21); + cfg_printf("\bYOU MUST SELECT A VALID ROM FILE\b\n"); + } + + cfg_htab_vtab(0, 23); + cfg_printf("Move: \tJ\t \tK\t Change: \tH\t \tU\t \tM\t"); + if(print_eject_help) { + cfg_printf(" Eject: "); + if((g_cfg_slotdrive & 0xfff) > 0) { + cfg_printf("\bESC\b"); + } else { + cfg_printf("E"); + cfg_printf(" New image: N Dup image: D Verify: V"); + } + } + if((g_cfg_slotdrive & 0xfff) > 0) { + cfg_printf(" Edit Path: \bTAB\b"); + if(g_cfg_select_partition > 0) { + cfg_printf(" (\bCmd\b-\bA\b to mount all)"); + } else if((g_cfg_newdisk_type == 3) || !g_cfg_newdisk_select) { + // Dynamic ProDOS, select a directory + cfg_printf(" (\bCmd\b-\bEnter\b for DynaPro)"); + } + if(g_cfg_newdisk_select && (g_cfg_newdisk_type != 3)) { + cfg_printf(" (Enter new name on Path)"); + } + } +#if 0 + cfg_htab_vtab(0, 22); + cfg_printf("menu_line: %d line: %d, vbl:%d, adb:%d key_dn:%d\n", + menu_line, line, g_cfg_vbl_count, g_adb_repeat_vbl, + g_key_down); +#endif + + if((g_cfg_slotdrive & 0xfff) > 0) { + cfg_file_draw(); + } +} + +void +cfg_newdisk_pick_menu(word32 slotdrive) +{ + slotdrive = slotdrive & 0xfff; + g_cfg_newdisk_slotdrive = slotdrive; // 0x601: s6d2, 0x500: s5d1 + g_menu_line = 1; + //printf("N key, g_menuptr=%p\n", g_menuptr); + g_cfg_newdisk_type_default = 1; + g_cfg_newdisk_type = 1; + g_cfg_newdisk_blocks_default = 140*2; + g_cfg_newdisk_blocks = 280; + if((slotdrive >> 8) == 6) { + g_menuptr = g_cfg_newslot6_menu; + } else if((slotdrive >> 8) == 5) { + g_menuptr = g_cfg_newslot5_menu; + g_cfg_newdisk_blocks_default = 1600; + g_cfg_newdisk_blocks = 1600; + } else { + g_menuptr = g_cfg_newslot7_menu; + g_cfg_newdisk_blocks_default = 65535; + g_cfg_newdisk_blocks = 65535; + } +} + +int +cfg_control_panel_update() +{ + int ret; + int i; + + ret = cfg_control_panel_update1(); + if(g_cfg_screen_changed) { + for(i = 0; i < 24; i++) { + video_draw_a2_string(i, &g_cfg_screen[i][0]); + } + } + g_cfg_screen_changed = 0; + + return ret; +} + +void +cfg_edit_mode_key(int key) +{ + char *new_str; + int *iptr; + int len, ival; + + len = (int)strlen(&g_cfg_edit_buf[0]); + if(key == 0x0d) { // Return + // Try to accept the change + new_str = kegs_malloc_str(&g_cfg_edit_buf[0]); + if(g_cfg_edit_type == CFGTYPE_STR) { + cfg_file_update_ptr(g_cfg_edit_ptr, new_str, 1); + } else if(g_cfg_edit_type == CFGTYPE_INT) { + ival = strtol(&g_cfg_edit_buf[0], 0, 0); + iptr = (int *)g_cfg_edit_ptr; + cfg_int_update(iptr, ival); + } + g_cfg_edit_ptr = 0; + g_config_kegs_update_needed = 1; + } else if(key == 0x1b) { // ESC + g_cfg_edit_ptr = 0; // Abort out of edit mode, no changes + } else if((key == 0x08) || (key == 0x7f)) { // Left arrow or Delete + len--; + if(len >= 0) { + g_cfg_edit_buf[len] = 0; + } + } else if((key >= 0x20) && (key < 0x7f)) { + if(len < (CFG_OPT_MAXSTR - 3)) { + g_cfg_edit_buf[len] = key; + g_cfg_edit_buf[len+1] = 0; + } + } +} + +int +cfg_control_panel_update1() +{ + char *(*fn_ptr)(int); + void *ptr; + char **str_ptr; + int *iptr; + int type, key; + + while(g_config_control_panel) { + if(g_menu_redraw_needed) { + cfg_draw_menu(); + } + if(g_menu_redraw_needed) { + cfg_draw_menu(); + } + key = adb_read_c000(); + if(key & 0x80) { + key = key & 0x7f; + (void)adb_access_c010(); + } else { + return 0; // No keys + } + g_menu_redraw_needed = 1; + // If we get here, we got a key, figure out what to do with it + if(g_cfg_slotdrive & 0xfff) { + cfg_file_handle_key(key); + continue; + } + if(g_cfg_edit_ptr) { + cfg_edit_mode_key(key); + continue; + } + + // Normal menu system + switch(key) { + case 0x0a: /* down arrow */ + g_menu_line++; + g_menu_inc = 1; + break; + case 0x0b: /* up arrow */ + g_menu_line--; + g_menu_inc = 0; + if(g_menu_line < 1) { + g_menu_line = 1; + } + break; + case 0x15: /* right arrow */ + cfg_parse_menu(g_menuptr, g_menu_line, g_menu_line, 1); + break; + case 0x08: /* left arrow */ + cfg_parse_menu(g_menuptr, g_menu_line, g_menu_line, -1); + break; + case 0x0d: + type = g_menuptr[g_menu_line].cfgtype; + ptr = g_menuptr[g_menu_line].ptr; + switch(type & 0xf) { + case CFGTYPE_MENU: + g_menuptr = (Cfg_menu *)ptr; + g_menu_line = 1; + break; + case CFGTYPE_DISK: + g_cfg_slotdrive = (type >> 4) & 0xfff; + cfg_file_init(); + break; + case CFGTYPE_FUNC: + fn_ptr = (char * (*)(int))ptr; + (void)(*fn_ptr)(0); + break; + case CFGTYPE_FILE: + g_cfg_slotdrive = 0xfff; + g_cfg_file_def_name = *((char **)ptr); + g_cfg_file_strptr = (char **)ptr; + cfg_file_init(); + break; + case CFGTYPE_STR: + str_ptr = (char **)ptr; + if(str_ptr) { + g_cfg_edit_type = type & 0xf; + g_cfg_edit_ptr = str_ptr; + cfg_strncpy(&g_cfg_edit_buf[0], + *str_ptr, CFG_OPT_MAXSTR); + } + break; + case CFGTYPE_INT: + // If there are no ',' in the menu str, then + // allow user to enter a manual number + if(!strchr(g_menuptr[g_menu_line].str, ',')) { + g_cfg_edit_type = type & 0xf; + g_cfg_edit_ptr = ptr; + iptr = (int *)ptr; + snprintf(&g_cfg_edit_buf[0], + CFG_OPT_MAXSTR, "%d", *iptr); + } + } + break; + case 0x1b: + // Jump to last menu entry + g_menu_line = g_menu_max_line; + break; + case 'd': + case 'D': // Duplicate an image + type = g_menuptr[g_menu_line].cfgtype; + if((type & 0xf) == CFGTYPE_DISK) { + cfg_dup_existing_image(type >> 4); + } + break; + case 'e': + case 'E': + type = g_menuptr[g_menu_line].cfgtype; + if((type & 0xf) == CFGTYPE_DISK) { + iwm_eject_disk_by_num(type >> 12, + (type >> 4) & 0xff); + } + break; + case 'l': + case 'L': + type = g_menuptr[g_menu_line].cfgtype; + if((type & 0xf) == CFGTYPE_DISK) { + cfg_toggle_lock_disk(type >> 4); + } + break; + case 'n': + case 'N': + type = g_menuptr[g_menu_line].cfgtype; + if((type & 0xf) == CFGTYPE_DISK) { + cfg_newdisk_pick_menu(type >> 4); + } + break; + case 'v': + case 'V': + type = g_menuptr[g_menu_line].cfgtype; + if((type & 0xf) == CFGTYPE_DISK) { + cfg_validate_image(type >> 4); + } + break; + default: + printf("key: %02x\n", key); + } + } + + return 0; +} + diff --git a/gsplus/src/config.h b/gsplus/src/config.h new file mode 100644 index 0000000..1522f5b --- /dev/null +++ b/gsplus/src/config.h @@ -0,0 +1,36 @@ +#ifdef INCLUDE_RCSID_C +#endif + +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2019 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#define CONF_BUF_LEN 1024 +#define COPY_BUF_SIZE 4096 +#define CFG_PRINTF_BUFSIZE 2048 + +#define CFG_PATH_MAX 1024 + +#define CFG_NUM_SHOWENTS 16 + +#define CFGTYPE_MENU 1 +#define CFGTYPE_INT 2 +#define CFGTYPE_DISK 3 +#define CFGTYPE_FUNC 4 +#define CFGTYPE_FILE 5 +#define CFGTYPE_STR 6 +/* CFGTYPE limited to just 4 bits: 0-15 */ + +/* Cfg_menu, Cfg_dirent and Cfg_listhdr are defined in defc.h */ + +STRUCT(Cfg_defval) { + Cfg_menu *menuptr; + int intval; + char *strval; +}; diff --git a/gsplus/src/cp_gsplus_libs b/gsplus/src/cp_gsplus_libs new file mode 100644 index 0000000..10d9b9a --- /dev/null +++ b/gsplus/src/cp_gsplus_libs @@ -0,0 +1,39 @@ +#!/usr/bin/perl -w +# $KmKId: cp_kegs_libs,v 1.2 2021-02-09 00:35:48+00 kentd Exp $ + +use strict; +use English; + +if($#ARGV < 2) { + die "Usage: executable srclib_dir destlib_dir"; +} + +# Runs objdump on the executable, finds all unresolved dependencies, and +# then copies each of those libraries from srclib_dir to destlib_dir +# destlib_dir should be APPLICATION/Contents/Frameworks/ + +my $exe = shift; +my $srcdir = shift; +my $destdir = shift; +if(! -f $exe || ! -d $srcdir || ! -d $destdir) { + die "$exe is not a file, or $srcdir or $destdir are not a dir"; +} + +my $do_swiftos = 0; +open(RPATHS, "objdump -macho -dylibs-used $exe|") or die "Open failed: $!"; +my $line; +my $lib; +foreach $line () { + chomp($line); + if($line =~ m:\@rpath/([^ ]*) :) { + $lib = $1; + print "lib: $lib\n"; + `cp $srcdir/$lib $destdir/`; + } + $do_swiftos = 1; +} + +# And copy libswiftos.dylib if we copied any files +if($do_swiftos) { + `cp $srcdir/libswiftos.dylib $destdir/`; +} diff --git a/gsplus/src/debugger.c b/gsplus/src/debugger.c new file mode 100644 index 0000000..793d2ca --- /dev/null +++ b/gsplus/src/debugger.c @@ -0,0 +1,2172 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + + +#include +#include +#include "defc.h" + +#include "disas.h" + +#define LINE_SIZE 160 /* Input buffer size */ +#define PRINTF_BUF_SIZE 239 +#define DEBUG_ENTRY_MAX_CHARS 80 + +STRUCT(Debug_entry) { + byte str_buf[DEBUG_ENTRY_MAX_CHARS]; +}; + +char g_debug_printf_buf[PRINTF_BUF_SIZE]; +char g_debug_stage_buf[PRINTF_BUF_SIZE]; +int g_debug_stage_pos = 0; + +Debug_entry *g_debug_lines_ptr = 0; +int g_debug_lines_total = 0; +int g_debug_lines_pos = 0; +int g_debug_lines_alloc = 0; +int g_debug_lines_max = 1024*1024; +int g_debug_lines_view = -1; +int g_debug_lines_viewable_lines = 20; +int g_debugwin_changed = 1; +int g_debug_to_stdout = 1; + +extern byte *g_memory_ptr; +extern byte *g_slow_memory_ptr; +extern int g_halt_sim; +extern word32 g_c068_statereg; +extern word32 stop_run_at; +extern int Verbose; +extern int Halt_on; +extern int g_a2_key_to_ascii[][4]; +extern Kimage g_debugwin_kimage; + +extern int g_config_control_panel; +extern word32 g_mem_size_total; + +extern char *g_sound_file_str; +extern word32 g_sound_file_bytes; + +int g_num_breakpoints = 0; +Break_point g_break_pts[MAX_BREAK_POINTS]; + +extern int g_irq_pending; + +extern dword64 g_last_vbl_dcyc; +extern int g_ret1; +extern Engine_reg engine; +extern dword64 g_dcycles_end; + +int g_stepping = 0; + +word32 g_list_kpc; +int g_hex_line_len = 0x10; +word32 g_a1 = 0; +word32 g_a2 = 0; +word32 g_a3 = 0; +word32 g_a4 = 0; +word32 g_a1bank = 0; +word32 g_a2bank = 0; +word32 g_a3bank = 0; +word32 g_a4bank = 0; + +#define MAX_CMD_BUFFER 229 + +#define PC_LOG_LEN (2*1024*1024) + +Pc_log g_pc_log_array[PC_LOG_LEN + 2]; +Data_log g_data_log_array[PC_LOG_LEN + 2]; + +word32 g_log_pc_enable = 0; +Pc_log *g_log_pc_ptr = &(g_pc_log_array[0]); +Pc_log *g_log_pc_start_ptr = &(g_pc_log_array[0]); +Pc_log *g_log_pc_end_ptr = &(g_pc_log_array[PC_LOG_LEN]); + +Data_log *g_log_data_ptr = &(g_data_log_array[0]); +Data_log *g_log_data_start_ptr = &(g_data_log_array[0]); +Data_log *g_log_data_end_ptr = &(g_data_log_array[PC_LOG_LEN]); + +char g_cmd_buffer[MAX_CMD_BUFFER + 2] = { 0 }; +int g_cmd_buffer_len = 2; + +#define MAX_DISAS_BUF 150 +char g_disas_buffer[MAX_DISAS_BUF]; + +void +debugger_init() +{ + debugger_help(); + g_list_kpc = engine.kpc; +#if 0 + if(g_num_breakpoints == 0) { + set_bp(0xff5a0e, 0xff5a0e, 4); + set_bp(0x00c50a, 0x00c50a, 4); + set_bp(0x00c50d, 0x00c50d, 4); + } +#endif +} + +int g_dbg_new_halt = 0; + +void +check_breakpoints(word32 addr, dword64 dfcyc, word32 maybe_stack, + word32 type) +{ + Break_point *bp_ptr; + int count; + int i; + + count = g_num_breakpoints; + for(i = 0; i < count; i++) { + bp_ptr = &(g_break_pts[i]); + if((type & bp_ptr->acc_type) == 0) { + continue; + } + if((addr >= (bp_ptr->start_addr & 0xffffff)) && + (addr <= (bp_ptr->end_addr & 0xffffff))) { + debug_hit_bp(addr, dfcyc, maybe_stack, type, i); + } + } + + if((type == 4) && ((addr == 0xe10000) || (addr == 0xe10004))) { + FINISH(RET_TOOLTRACE, 0); + } +} + +void +debug_hit_bp(word32 addr, dword64 dfcyc, word32 maybe_stack, word32 type, + int pos) +{ + word32 trk_side, side, trk, cmd, unit, buf, blk, param_cnt, list_ptr; + word32 status_code, cmd_list, stack, rts; + + if((addr == 0xff5a0e) && (type == 4)) { + trk_side = get_memory_c(0xe10f32); + side = (trk_side >> 5) & 1; + trk = get_memory_c(0xe10f34) + ((trk_side & 0x1f) << 6); + buf = get_memory_c(0x42) | (get_memory_c(0x43) << 8) | + (get_memory_c(0x44) << 16); + printf("ff5a0e: 3.5 read of track %03x side:%d sector:%03x to " + "%06x at %016llx\n", trk, side, get_memory_c(0xe10f33), + buf, dfcyc); + return; + } + if((addr == 0x00c50a) && (type == 4)) { + cmd = get_memory_c(0x42); + unit = get_memory_c(0x43); + buf = get_memory_c(0x44) | (get_memory_c(0x45) << 8); + blk = get_memory_c(0x46) | (get_memory_c(0x47) << 8); + printf("00c50a: cmd %02x u:%02x buf:%04x blk:%04x at %016llx\n", + cmd, unit, buf, blk, dfcyc); + return; + } + if((addr == 0x00c50d) && (type == 4)) { + stack = maybe_stack & 0xffff; + rts = get_memory_c(stack + 1) | (get_memory_c(stack + 2) << 8); + cmd = get_memory_c(rts + 1); + cmd_list = get_memory_c(rts + 2) | (get_memory_c(rts+3) << 8); + param_cnt = get_memory_c(cmd_list); + unit = get_memory_c(cmd_list + 1); + list_ptr = get_memory_c(cmd_list + 2) | + (get_memory_c(cmd_list + 3) << 8); + status_code = get_memory_c(cmd_list + 4); + printf("00c50d: stack:%04x rts:%04x cmd:%02x cmd_list:%04x " + "param_cnt:%02x unit:%02x listptr:%04x " + "status:%02x at %016llx\n", stack, rts, cmd, cmd_list, + param_cnt, unit, list_ptr, status_code, dfcyc); + printf(" list_ptr: %04x: %02x %02x %02x %02x %02x %02x %02x\n", + list_ptr, get_memory_c(list_ptr), + get_memory_c(list_ptr + 1), get_memory_c(list_ptr + 2), + get_memory_c(list_ptr + 3), get_memory_c(list_ptr + 4), + get_memory_c(list_ptr + 5), get_memory_c(list_ptr + 6)); + return; + } + + dbg_log_info(dfcyc, addr, pos, 0x6270); + halt2_printf("Hit breakpoint at %06x\n", addr); +} + +int +debugger_run_16ms() +{ + // Called when g_halt_sim is set + if(g_dbg_new_halt) { + g_list_kpc = engine.kpc; + show_regs(); + } + g_dbg_new_halt = 0; + adb_nonmain_check(); + // printf("debugger_run_16ms: g_halt_sim:%d\n", g_halt_sim); + return 0; +} + +void +dbg_log_info(dword64 dfcyc, word32 info1, word32 info2, word32 type) +{ + if(dfcyc == 0) { + return; // Ignore some IWM t:00e7 events and others + } + g_log_data_ptr->dfcyc = dfcyc; + g_log_data_ptr->stat = 0; + g_log_data_ptr->addr = info1; + g_log_data_ptr->val = info2; + g_log_data_ptr->size = type; // type must be > 4 + g_log_data_ptr++; + if(g_log_data_ptr >= g_log_data_end_ptr) { + g_log_data_ptr = g_log_data_start_ptr; + } +} + +void +debugger_update_list_kpc() +{ + g_dbg_new_halt = 1; +} + +void +debugger_key_event(Kimage *kimage_ptr, int a2code, int is_up) +{ + word32 c025_val, special; + int key, pos, changed; + + pos = 1; + + c025_val = kimage_ptr->c025_val; + + if(c025_val & 1) { // Shift is down + pos = 2; + } else if(c025_val & 4) { // Capslock is down + key = g_a2_key_to_ascii[a2code][1]; + if((key >= 'a') && (key <= 'z')) { + pos = 2; // CAPS LOCK on + } + } + if(c025_val & 2) { // Ctrl is down + pos = 3; + } + key = g_a2_key_to_ascii[a2code][pos]; + if(key < 0) { + return; + } + special = (key >> 8) & 0xff; // c025 changes + if(is_up) { + c025_val = c025_val & (~special); + } else { + c025_val = c025_val | special; + } + kimage_ptr->c025_val = c025_val; + if(is_up) { + return; // Nothing else to do + } + if(key >= 0x80) { + // printf("key: %04x\n", key); + if(key == 0x8007) { // F7 - close debugger + video_set_active(kimage_ptr, !kimage_ptr->active); + printf("Toggled debugger window to:%d\n", + kimage_ptr->active); + } + if((key & 0xff) == 0x74) { // Page up keycode + debugger_page_updown(1); + } + if((key & 0xff) == 0x79) { // Page down keycode + debugger_page_updown(-1); + } + return; + } + pos = g_cmd_buffer_len; + changed = 0; + if((key >= 0x20) && (key < 0x7f)) { + // printable character, add it + if(pos < MAX_CMD_BUFFER) { + // printf("cmd[%d]=%c\n", pos, key); + g_cmd_buffer[pos++] = key; + changed = 1; + } + } else if((key == 0x08) || (key == 0x7f)) { + // Left arrow or backspace + if(pos > 2) { + pos--; + changed = 1; + } + } else if((key == 0x0d) || (key == 0x0a)) { + //dbg_printf("Did return, pos:%d, str:%s\n", pos, g_cmd_buffer); + do_debug_cmd(&g_cmd_buffer[2]); + pos = 2; + changed = 1; + } else { + // printf("ctrl key:%04x\n", key); + } + g_cmd_buffer[pos] = 0; + g_cmd_buffer_len = pos; + g_debug_lines_view = -1; + g_debugwin_changed |= changed; + // printf("g_cmd_buffer: %s\n", g_cmd_buffer); +} + +void +debugger_page_updown(int isup) +{ + int view, max; + + view = g_debug_lines_view; + if(view < 0) { + view = 0; + } + view = view + (isup*g_debug_lines_viewable_lines); + if(view < 0) { + view = -1; + } + max = g_debug_lines_pos; + if(g_debug_lines_alloc >= g_debug_lines_max) { + max = g_debug_lines_alloc - 4; + } + view = MY_MIN(view, max - g_debug_lines_viewable_lines); + + // printf("new view:%d, was:%d\n", view, g_debug_lines_view); + if(view != g_debug_lines_view) { + g_debug_lines_view = view; + g_debugwin_changed++; + } +} + +void +debugger_redraw_screen(Kimage *kimage_ptr) +{ + int line, vid_line, back, border_top, save_pos, num, lines_done; + int save_view, save_to_stdout; + int i; + + if((g_debugwin_changed == 0) || (kimage_ptr->active == 0)) { + return; // Nothing to do + } + + save_pos = g_debug_lines_pos; + save_view = g_debug_lines_view; + // printf("DEBUGGER drawing SCREEN!\n"); + g_cmd_buffer[0] = '>'; + g_cmd_buffer[1] = ' '; + g_cmd_buffer[g_cmd_buffer_len] = 0xa0; // Cursor: inverse space + g_cmd_buffer[g_cmd_buffer_len+1] = 0; + save_to_stdout = g_debug_to_stdout; + g_debug_to_stdout = 0; + dbg_printf("%s\n", &g_cmd_buffer[0]); + g_cmd_buffer[g_cmd_buffer_len] = 0; + dbg_printf("g_halt_sim:%02x\n", g_halt_sim); + border_top = 8; + g_debug_to_stdout = save_to_stdout; + + vid_line = (((kimage_ptr->a2_height - 2*border_top) / 16) * 8) - 1; + num = g_debug_lines_pos - save_pos; + if(num < 0) { + num = num + g_debug_lines_alloc; + } + if(num > 4) { + // printf("num is > 4!\n"); + num = 4; + } + for(i = 0; i < num; i++) { + line = debug_get_view_line(i); + debug_draw_debug_line(kimage_ptr, line, vid_line); + vid_line -= 8; + } + g_debug_lines_pos = save_pos; + g_debug_lines_view = save_view; + back = save_view; + if(back < 0) { // -1 means always show most recent + back = 0; + } + lines_done = 0; + while(vid_line >= border_top) { + line = debug_get_view_line(back); + debug_draw_debug_line(kimage_ptr, line, vid_line); + back++; + vid_line -= 8; + lines_done++; +#if 0 + printf(" did a line, line is now: %d after str:%s\n", line, + str); +#endif + } + g_debug_lines_viewable_lines = lines_done; + g_debugwin_changed = 0; + kimage_ptr->x_refresh_needed = 1; + // printf("x_refresh_needed = 1, viewable_lines:%d\n", lines_done); +} + +void +debug_draw_debug_line(Kimage *kimage_ptr, int line, int vid_line) +{ + word32 line_bytes; + int i; + + // printf("draw debug line:%d at vid_line:%d\n", line, vid_line); + for(i = 7; i >= 0; i--) { + line_bytes = (vid_line << 16) | (40 << 8) | 0; + redraw_changed_string(&(g_debug_lines_ptr[line].str_buf[0]), + line_bytes, -1L, kimage_ptr->wptr + 8, 0, 0x00ffffff, + kimage_ptr->a2_width_full, 1); + vid_line--; + } +} + +Dbg_longcmd g_debug_bp_clear[] = { + { "all", debug_bp_clear_all, 0, + "clear all breakpoints" }, + { 0, 0, 0, 0 } +}; +Dbg_longcmd g_debug_bp[] = { + { "set", debug_bp_set, 0, + "Set breakpoint: ADDR or ADDR0-ADDR1" }, + { "clear", debug_bp_clear, &g_debug_bp_clear[0], + "Clear breakpoint: ADDR OR ADDR0-ADDR1"}, + { 0, 0, 0, 0 } +}; + +Dbg_longcmd g_debug_logpc[] = { + { "on", debug_logpc_on, 0, "Turn on logging of pc and data" }, + { "off", debug_logpc_off,0, "Turn off logging of pc and data" }, + { "save", debug_logpc_save,0, "logpc save FILE: save to file" }, + { 0, 0, 0, 0 } +}; + +Dbg_longcmd g_debug_iwm[] = { + { "check", debug_iwm_check, 0, "Denibblize current track" }, + { 0, 0, 0, 0 } +}; + +// Main table of commands +Dbg_longcmd g_debug_longcmds[] = { + { "help", debug_help, 0, "Help" }, + { "bp", debug_bp, &g_debug_bp[0], + "bp ADDR: sets breakpoint on addr" }, + { "logpc", debug_logpc, &g_debug_logpc[0], "Log PC" }, + { "iwm", debug_iwm, &g_debug_iwm[0], "IWM" }, + { "soundfile", debug_soundfile, 0, "Save sound to a WAV file" }, + { 0, 0, 0, 0 } +}; + +void +debugger_help() +{ + dbg_printf("KEGS Debugger help (courtesy Fredric Devernay\n"); + dbg_printf("General command syntax: [bank]/[address][command]\n"); + dbg_printf("e.g. 'e1/0010B' to set a breakpoint at the interrupt jump " + "pt\n"); + dbg_printf("Enter all addresses using lower-case\n"); + dbg_printf("As with the IIgs monitor, you can omit the bank number " + "after\n"); + dbg_printf("having set it: 'e1/0010B' followed by '14B' will set\n"); + dbg_printf("breakpoints at e1/0010 and e1/0014\n"); + dbg_printf("\n"); + dbg_printf("g Go\n"); + dbg_printf("[bank]/[addr]g Go from [bank]/[address]\n"); + dbg_printf("s Step one instruction\n"); + dbg_printf("[bank]/[addr]s Step one instr at [bank]/[addr]\n"); + dbg_printf("[bank]/[addr]B Set breakpoint at [bank]/[addr]\n"); + dbg_printf("B Show all breakpoints\n"); + dbg_printf("[bank]/[addr]D Delete breakpoint at [bank]/" + "[addr]\n"); + dbg_printf("[bank]/[addr1].[addr2] View memory\n"); + dbg_printf("[bank]/[addr]L Disassemble memory\n"); + + dbg_printf("Z Dump SCC state\n"); + dbg_printf("I Dump IWM state\n"); + dbg_printf("[drive].[track]I Dump IWM state\n"); + dbg_printf("E Dump Ensoniq state\n"); + dbg_printf("[osc]E Dump oscillator [osc] state\n"); + dbg_printf("R Dump dtime array and events\n"); + dbg_printf("T Show toolbox log\n"); + dbg_printf("[bank]/[addr]T Dump tools using ptr [bank]/" + "[addr]\n"); + dbg_printf(" as 'tool_set_info'\n"); + dbg_printf("[mode]V XOR verbose with 1=DISK, 2=IRQ,\n"); + dbg_printf(" 4=CLK,8=SHADOW,10=IWM,20=DOC,\n"); + dbg_printf(" 40=ABD,80=SCC, 100=TEST, 200=" + "VIDEO\n"); + dbg_printf("[mode]H XOR halt_on with 1=SCAN_INT,\n"); + dbg_printf(" 2=IRQ, 4=SHADOW_REG, 8=" + "C70D_WRITES\n"); + dbg_printf("r Reset\n"); + dbg_printf("[0/1]=m Changes m bit for l listings\n"); + dbg_printf("[0/1]=x Changes x bit for l listings\n"); + dbg_printf("S show_bankptr_bank0 & smartport " + "errs\n"); + dbg_printf("P show_pmhz\n"); + dbg_printf("A show_a2_line_stuff show_adb_log\n"); + dbg_printf("Ctrl-e Dump registers\n"); + dbg_printf("[bank]/[addr1].[addr2]us[file] Save mem area to [file]\n"); + dbg_printf("[bank]/[addr1].[addr2]ul[file] Load mem area from " + "[file]\n"); + dbg_printf("v Show video information\n"); + dbg_printf("q Exit Debugger (and KEGS)\n"); +} + +void +dbg_help_show_strs(int help_depth, const char *str, const char *help_str) +{ + const char *blank_str, *pre_str, *post_str; + int column, len, blank_len, pre_len, post_len; + + // Indent by 3*help_depth chars, then output str, then hit + // column 14, then output help_str. This can be done in just 2-3 + // lines, but I made it longer and clearer to avoid any "overflow" + // cases + if(help_str == 0) { + return; + } + blank_str = " " " " " "; + blank_len = (int)strlen(blank_str); // should be >=17 + column = 17; + len = (int)strlen(str); + if(help_depth < 0) { + help_depth = 0; + } + pre_str = blank_str; + pre_len = 3 * help_depth; + if(pre_len < blank_len) { + pre_str = blank_str + blank_len - pre_len; + } + post_str = ""; + post_len = column - pre_len - len; + if((post_len >= 1) && (post_len < blank_len)) { + post_str = blank_str + blank_len - post_len; + } + dbg_printf("%s%s%s: %s\n", pre_str, str, post_str, help_str); +} + +const char * +debug_find_cmd_in_table(const char *line_ptr, Dbg_longcmd *longptr, + int help_depth) +{ + Dbg_fn *fnptr; + Dbg_longcmd *subptr; + const char *str, *newstr; + int len, c; + int i; + + // See if the command is from the longcmd list + while(*line_ptr == ' ') { + line_ptr++; // eat spaces + } + // Output " str :" where : is at column 14 always + // printf("dfcit: %s, help_depth:%d\n", line_ptr, help_depth); + for(i = 0; i < 1000; i++) { + // Provide a limit to avoid hang if table not terminated right + str = longptr[i].str; + fnptr = longptr[i].fnptr; + if(!str) { // End of table + break; // No match found + } + if(help_depth < 0) { + // Print the help string for all entries in this table + dbg_help_show_strs(-1 - help_depth, str, + longptr[i].help_str); + continue; + } + len = (int)strlen(str); + if(strncmp(line_ptr, str, len) != 0) { + continue; // Not a match + } + // Ensure next char is either a space, or 0 + // Let's us avoid commands which are prefixes, or + // which are old Apple II monitor hex+commands + c = line_ptr[len]; + if((c != 0) && (c != ' ')) { + continue; // Not valid + } + if(help_depth) { + dbg_help_show_strs(help_depth, str, + longptr[i].help_str); + } + subptr = longptr[i].subptr; + // Try a subcmd first + newstr = line_ptr + len; + if(subptr != 0) { + if(help_depth) { + help_depth++; + } + newstr = debug_find_cmd_in_table(newstr, subptr, + help_depth); + // If a subcmd was found, newstr is now 0 + } + if((newstr == 0) || help_depth) { + return 0; + } + if((newstr != 0) && (fnptr != 0)) { + (*fnptr)(line_ptr + len); + return 0; // Success + } + } + if(help_depth >= 1) { + // No subcommands found, print out all entries in this table + debug_find_cmd_in_table(line_ptr, longptr, -1 - help_depth); + return 0; + } + return line_ptr; +} + +void +do_debug_cmd(const char *in_str) +{ + const char *line_ptr; + const char *newstr; + int slot_drive, track, ret_val, mode, old_mode, got_num; + int save_to_stdout; + + mode = 0; + old_mode = 0; + + save_to_stdout = g_debug_to_stdout; + g_debug_to_stdout = 1; + dbg_printf("*%s\n", in_str); + line_ptr = in_str; + + // See if the command is from the longcmd list + newstr = debug_find_cmd_in_table(in_str, &(g_debug_longcmds[0]), 0); + if(newstr == 0) { + g_debug_to_stdout = save_to_stdout; + return; // Command found get out + } + + // If we get here, parse an Apple II monitor like command: + // {address}{cmd} repeat. + while(1) { + ret_val = 0; + g_a2 = 0; + got_num = 0; + while(1) { + if((mode == 0) && (got_num != 0)) { + g_a3 = g_a2; + g_a3bank = g_a2bank; + g_a1 = g_a2; + g_a1bank = g_a2bank; + } + ret_val = *line_ptr++ & 0x7f; + if((ret_val >= '0') && (ret_val <= '9')) { + g_a2 = (g_a2 << 4) + ret_val - '0'; + got_num = 1; + continue; + } + if((ret_val >= 'a') && (ret_val <= 'f')) { + g_a2 = (g_a2 << 4) + ret_val - 'a' + 10; + got_num = 1; + continue; + } + if(ret_val == '/') { + g_a2bank = g_a2; + g_a2 = 0; + continue; + } + break; + } + old_mode = mode; + mode = 0; + switch(ret_val) { + case 'h': + debugger_help(); + break; + case 'R': + show_dtime_array(); + show_all_events(); + break; + case 'I': + slot_drive = -1; + track = -1; + if(got_num) { + if(old_mode == '.') { + slot_drive = g_a1; + } + track = g_a2; + } + iwm_show_track(slot_drive, track, 0); + iwm_show_stats(slot_drive); + break; + case 'E': + doc_show_ensoniq_state(); + break; + case 'T': + if(got_num) { + show_toolset_tables(g_a2bank, g_a2); + } else { + show_toolbox_log(); + } + break; + case 'v': + if(got_num) { + dis_do_compare(); + } else { + video_show_debug_info(); + } + break; + case 'V': + dbg_printf("g_irq_pending: %05x\n", g_irq_pending); + dbg_printf("Setting Verbose ^= %04x\n", g_a1); + Verbose ^= g_a1; + dbg_printf("Verbose is now: %04x\n", Verbose); + break; + case 'H': + dbg_printf("Setting Halt_on ^= %04x\n", g_a1); + Halt_on ^= g_a1; + dbg_printf("Halt_on is now: %04x\n", Halt_on); + break; + case 'r': + do_reset(); + g_list_kpc = engine.kpc; + break; + case 'm': + if(old_mode == '=') { + if(!g_a1) { + engine.psr &= ~0x20; + } else { + engine.psr |= 0x20; + } + if(engine.psr & 0x100) { + engine.psr |= 0x30; + } + } else { + dis_do_memmove(); + } + break; + case 'p': + dis_do_pattern_search(); + break; + case 'x': + if(old_mode == '=') { + if(!g_a1) { + engine.psr &= ~0x10; + } else { + engine.psr |= 0x10; + } + if(engine.psr & 0x100) { + engine.psr |= 0x30; + } + } + break; + case 'z': + if(old_mode == '=') { + stop_run_at = g_a1; + dbg_printf("Calling add_event for t:%08x\n", + g_a1); + add_event_stop(((dword64)g_a1) << 16); + dbg_printf("set stop_run_at = %x\n", g_a1); + } + break; + case 'l': case 'L': + if(got_num) { + g_list_kpc = (g_a2bank << 16) + (g_a2 & 0xffff); + } + do_debug_list(); + break; + case 'Z': + show_scc_state(); + break; + case 'S': + show_bankptrs_bank0rdwr(); + smartport_error(); + break; + case 'M': + show_pmhz(); + mockingboard_show(got_num, g_a1); + break; + case 'A': + show_a2_line_stuff(); + show_adb_log(); + break; + case 's': + g_stepping = 1; + if(got_num) { + engine.kpc = (g_a2bank << 16) + (g_a2 & 0xffff); + } + mode = 's'; + g_list_kpc = engine.kpc; + break; + case 'B': + if(got_num) { + dbg_printf("got_num:%d, a2bank:%x, g_a2:%x\n", + got_num, g_a2bank, g_a2); + set_bp((g_a2bank << 16) + g_a2, + (g_a2bank << 16) + g_a2, 4); + } else { + show_bp(); + } + break; + case 'D': + if(got_num) { + dbg_printf("got_num: %d, a2bank: %x, a2: %x\n", + got_num, g_a2bank, g_a2); + delete_bp((g_a2bank << 16) + g_a2, + (g_a2bank << 16) + g_a2); + } + break; + case 'g': + case 'G': + dbg_printf("Going..\n"); + g_stepping = 0; + if(got_num) { + engine.kpc = (g_a2bank << 16) + (g_a2 & 0xffff); + } + do_go(); + g_list_kpc = engine.kpc; + break; + case 'u': + dbg_printf("Unix commands\n"); + line_ptr = do_debug_unix(line_ptr, old_mode); + break; + case ':': case '.': + case '+': case '-': + case '=': case ',': + mode = ret_val; + dbg_printf("Setting mode = %x\n", mode); + break; + case ' ': case '\t': + if(!got_num) { + mode = old_mode; + break; + } + mode = do_blank(mode, old_mode); + break; + case '<': + g_a4 = g_a2; + g_a4bank = g_a2bank; + break; + case 0x05: /* ctrl-e */ + case 'Q': + case 'q': + show_regs(); + break; + case 0: // The final null char + if(old_mode == 's') { + mode = do_blank(mode, old_mode); + g_debug_to_stdout = save_to_stdout; + return; + } + if(line_ptr == &in_str[1]) { + g_a2 = g_a1 | (g_hex_line_len - 1); + show_hex_mem(g_a1bank, g_a1, g_a2, -1); + g_a1 = g_a2 + 1; + } else { + if((got_num == 1) || (mode == 's')) { + mode = do_blank(mode, old_mode); + } + } + g_debug_to_stdout = save_to_stdout; + return; // Get out, all done + break; + default: + dbg_printf("\nUnrecognized command: %s\n", in_str); + g_debug_to_stdout = save_to_stdout; + return; + } + } +} + +word32 +dis_get_memory_ptr(word32 addr) +{ + word32 tmp1, tmp2, tmp3; + + tmp1 = get_memory_c(addr); + tmp2 = get_memory_c(addr + 1); + tmp3 = get_memory_c(addr + 2); + + return (tmp3 << 16) + (tmp2 << 8) + tmp1; +} + +void +show_one_toolset(FILE *toolfile, int toolnum, word32 addr) +{ + word32 rout_addr; + int num_routs; + int i; + + num_routs = dis_get_memory_ptr(addr); + fprintf(toolfile, "Tool 0x%02x, table: 0x%06x, num_routs:%03x\n", + toolnum, addr, num_routs); + if((addr < 0x10000) || (num_routs > 0x100)) { + fprintf(toolfile, "addr in page 0, or num_routs too large\n"); + return; + } + + for(i = 1; i < num_routs; i++) { + rout_addr = dis_get_memory_ptr(addr + 4*i); + fprintf(toolfile, "%06x = %02x%02x\n", rout_addr, i, toolnum); + } +} + +void +show_toolset_tables(word32 a2bank, word32 addr) +{ + FILE *toolfile; + word32 tool_addr; + int num_tools; + int i; + + addr = (a2bank << 16) + (addr & 0xffff); + + toolfile = fopen("tool_set_info", "w"); + if(toolfile == 0) { + fprintf(stderr, "fopen of tool_set_info failed: %d\n", errno); + exit(2); + } + + num_tools = dis_get_memory_ptr(addr); + fprintf(toolfile, "There are 0x%02x tools using ptr at %06x\n", + num_tools, addr); + + if(num_tools > 40) { + fprintf(toolfile, "Too many tools, aborting\n"); + num_tools = 0; + } + for(i = 1; i < num_tools; i++) { + tool_addr = dis_get_memory_ptr(addr + 4*i); + show_one_toolset(toolfile, i, tool_addr); + } + + fclose(toolfile); +} + +word32 +debug_getnum(const char **str_ptr) +{ + const char *str; + word32 val; + int c, got_num; + + str = *str_ptr; + while(*str == ' ') { + str++; + } + got_num = 0; + val = 0; + while(1) { + c = tolower(*str); + //printf("got c:%02x %c val was %08x got_num:%d\n", c, c, val, + // got_num); + if((c >= '0') && (c <= '9')) { + val = (val << 4) + (c - '0'); + got_num = 1; + } else if((c >= 'a') && (c <= 'f')) { + val = (val << 4) + 10 + (c - 'a'); + got_num = 1; + } else { + break; + } + str++; + } + *str_ptr = str; + if(got_num) { + return val; + } + return (word32)-1L; +} + +char * +debug_get_filename(const char **str_ptr) +{ + const char *str, *start_str; + char *new_str; + int c, len; + + // Go to first whitespace (or end of str), then kegs_malloc_str() + // the string and copy to it + str = *str_ptr; + start_str = 0; + //printf("get_filename, str now :%s:\n", str); + while(1) { + c = *str++; + if(c == 0) { + break; + } + if((c == ' ') || (c == '\t') || (c == '\n')) { + //printf("c:%02x at str :%s: , start_str:%p\n", c, str, + // start_str); + if(start_str) { + break; + } + continue; + } + // Else it's a valid char, set start_str if needed + if(!start_str) { + start_str = str - 1; + //printf("Got c:%02x, start_str :%s:\n", c, start_str); + } + } + new_str = 0; + if(start_str) { + len = (int)(str - start_str); + if(len > 1) { + new_str = malloc(len); + memcpy(new_str, start_str, len); + new_str[len - 1] = 0; + } + } + *str_ptr = str; + return new_str; +} + +void +debug_help(const char *str) +{ + dbg_printf("Help:\n"); + (void)debug_find_cmd_in_table(str, &(g_debug_longcmds[0]), 1); +} + +void +debug_bp(const char *str) +{ + // bp without a following set/clear command. Set a breakpoint if + // an address range follows, otherwise just print current breakpoints + debug_bp_setclr(str, 0); +} + +void +debug_bp_set(const char *str) +{ + debug_bp_setclr(str, 1); +} + +void +debug_bp_clear(const char *str) +{ + debug_bp_setclr(str, 2); +} + +void +debug_bp_clear_all(const char *str) +{ + if(str) { + // Use str to avoid warning + } + if(g_num_breakpoints) { + g_num_breakpoints = 0; + setup_pageinfo(); + dbg_printf("Deleted all breakpoints\n"); + } +} + +void +debug_bp_setclr(const char *str, int is_set_clear) +{ + word32 addr, end_addr, acc_type; + + printf("In debug_bp: %s\n", str); + + addr = debug_getnum(&str); + // printf("getnum ret:%08x\n", addr); + if(addr == (word32)-1L) { // No argument + show_bp(); + return; + } + end_addr = addr; + if(*str == '-') { // Range + str++; + end_addr = debug_getnum(&str); + // printf("end_addr is %08x\n", end_addr); + if(end_addr == (word32)-1L) { + end_addr = addr; + } + } + acc_type = 4; + acc_type = debug_getnum(&str); + if(acc_type == (word32)-1L) { + acc_type = 4; // Code breakpoint + } + if(is_set_clear == 2) { // clear + delete_bp(addr, end_addr); + } else { // set, or nothing + set_bp(addr, end_addr, acc_type); + } +} + +void +debug_soundfile(const char *cmd_str) +{ + char *str; + + // See if there's an argument + str = debug_get_filename(&cmd_str); // str=0 if no argument + sound_file_start(str); // str==0 means close file +} + +void +debug_logpc(const char *str) +{ + if(str) { + // Dummy use of argument + } + dbg_printf("logpc enable:%d, cur offset:%08lx\n", g_log_pc_enable, + (long)(g_log_pc_ptr - g_log_pc_start_ptr)); +} + +void +debug_logpc_on(const char *str) +{ + if(str) { + // Dummy use of argument + } + g_log_pc_enable = 1; + g_dcycles_end = 0; + dbg_printf("Enabled logging of PC and data accesses\n"); +} + +void +debug_logpc_off(const char *str) +{ + if(str) { + // Dummy use of argument + } + g_log_pc_enable = 0; + g_dcycles_end = 0; + dbg_printf("Disabled logging of PC and data accesses\n"); +} + +void +debug_logpc_out_data(FILE *pcfile, Data_log *log_data_ptr, dword64 start_dcyc) +{ + char *str, *shadow_str; + dword64 lstat, offset64, offset64slow, addr64; + word32 wstat, addr, size, val; + + addr = log_data_ptr->addr; + lstat = (dword64)(log_data_ptr->stat); + wstat = lstat & 0xff; + addr64 = lstat - wstat + (addr & 0xff); + offset64 = addr64 - (dword64)&(g_memory_ptr[0]); + str = "IO"; + shadow_str = ""; + if((wstat & BANK_SHADOW) || (wstat & BANK_SHADOW2)) { + shadow_str = "SHADOWED"; + } + size = log_data_ptr->size; + if(size > 32) { + fprintf(pcfile, "INFO %08x %08x %04x t:%04x %lld.%02lld\n", + log_data_ptr->addr, log_data_ptr->val, size >> 16, + size & 0xffff, (log_data_ptr->dfcyc - start_dcyc)>>16, + ((log_data_ptr->dfcyc & 0xffff) * 100) >> 16); + } else { + offset64slow = addr64 - (dword64)&(g_slow_memory_ptr[0]); + if(offset64 < g_mem_size_total) { + str = "mem"; + } else if(offset64slow < 0x20000) { + str = "slow_mem"; + offset64 = offset64slow; + } else { + str = "IO"; + offset64 = offset64 & 0xff; + } + val = log_data_ptr->val; + fprintf(pcfile, "DATA set %06x = ", addr); + if(size == 8) { + fprintf(pcfile, "%02x (8) ", val & 0xff); + } else if(size == 16) { + fprintf(pcfile, "%04x (16) ", val & 0xffff); + } else { + fprintf(pcfile, "%06x (%d) ", val, size); + } + fprintf(pcfile, "%lld.%02lld, %s[%06llx] %s\n", + (log_data_ptr->dfcyc - start_dcyc) >> 16, + ((log_data_ptr->dfcyc & 0xffff) * 100) >> 16, + str, offset64 & 0xffffffULL, shadow_str); + } +} + +Data_log * +debug_show_data_info(FILE *pcfile, Data_log *log_data_ptr, dword64 base_dcyc, + dword64 dfcyc, dword64 start_dcyc, int *data_wrap_ptr, + int *count_ptr) +{ + + while((*data_wrap_ptr < 2) && (log_data_ptr->dfcyc <= dfcyc) && + (log_data_ptr->dfcyc >= start_dcyc)) { + if(*count_ptr >= PC_LOG_LEN) { + break; + } + debug_logpc_out_data(pcfile, log_data_ptr, base_dcyc); + if(log_data_ptr->dfcyc == 0) { + break; + } + log_data_ptr++; + (*count_ptr)++; + if(log_data_ptr >= g_log_data_end_ptr) { + log_data_ptr = g_log_data_start_ptr; + (*data_wrap_ptr)++; + } + } + return log_data_ptr; +} + +void +debug_logpc_save(const char *cmd_str) +{ + FILE *pcfile; + Pc_log *log_pc_ptr; + Data_log *log_data_ptr; + char *str; + dword64 dfcyc, start_dcyc, base_dcyc, max_dcyc; + word32 instr, psr, acc, xreg, yreg, stack, direct, dbank, kpc, num; + int data_wrap, accsize, xsize, abs_time, data_count; + int i; + + // See if there's an argument + num = debug_getnum(&cmd_str); + abs_time = 1; + if(num != (word32)-1L) { + dbg_printf("Doing relative time\n"); + abs_time = 0; + } + + pcfile = fopen("logpc_out", "w"); + if(pcfile == 0) { + fprintf(stderr,"fopen failed...errno: %d\n", errno); + exit(2); + } + + log_pc_ptr = g_log_pc_ptr; + log_data_ptr = g_log_data_ptr; +#if 0 + printf("debug_logpc_save called, log_pc_ptr:%p, %p,%p log_data_ptr:%p, " + "%p,%p\n", log_pc_ptr, g_log_pc_start_ptr, g_log_pc_end_ptr, + log_data_ptr, g_log_data_start_ptr, g_log_data_end_ptr); +#endif +#if 0 + fprintf(pcfile, "current pc_log_ptr: %p, start: %p, end: %p\n", + log_pc_ptr, g_log_pc_start_ptr, g_log_pc_end_ptr); +#endif + + // See if we haven't filled buffer yet + if(log_pc_ptr->dfcyc == 0) { + log_pc_ptr = g_log_pc_start_ptr; + } + if(log_data_ptr->dfcyc == 0) { + log_data_ptr = g_log_data_start_ptr; + data_wrap = 1; + } + + start_dcyc = log_pc_ptr->dfcyc; + // Round to an exact usec + start_dcyc = (start_dcyc >> 16) << 16; + base_dcyc = start_dcyc; + if(abs_time) { + base_dcyc = 0; // Show absolute time + } + dfcyc = start_dcyc; + + data_wrap = 0; + data_count = 0; + /* find first data entry */ + while((data_wrap < 2) && (log_data_ptr->dfcyc < dfcyc)) { + log_data_ptr++; + if(log_data_ptr >= g_log_data_end_ptr) { + log_data_ptr = g_log_data_start_ptr; + data_wrap++; + } + } + fprintf(pcfile, "start_dcyc: %016llx, first entry:%016llx\n", + start_dcyc, log_pc_ptr->dfcyc); + + dfcyc = start_dcyc; + max_dcyc = dfcyc; + for(i = 0; i < PC_LOG_LEN; i++) { + dfcyc = log_pc_ptr->dfcyc; + log_data_ptr = debug_show_data_info(pcfile, log_data_ptr, + base_dcyc, dfcyc, start_dcyc, + &data_wrap, &data_count); + dbank = (log_pc_ptr->dbank_kpc >> 24) & 0xff; + kpc = log_pc_ptr->dbank_kpc & 0xffffff; + instr = log_pc_ptr->instr; + psr = (log_pc_ptr->psr_acc >> 16) & 0xffff; + acc = log_pc_ptr->psr_acc & 0xffff; + xreg = (log_pc_ptr->xreg_yreg >> 16) & 0xffff; + yreg = log_pc_ptr->xreg_yreg & 0xffff; + stack = (log_pc_ptr->stack_direct >> 16) & 0xffff; + direct = log_pc_ptr->stack_direct & 0xffff; + + accsize = 2; + xsize = 2; + if(psr & 0x20) { + accsize = 1; + } + if(psr & 0x10) { + xsize = 1; + } + + str = do_dis(kpc, accsize, xsize, 1, instr, 0); + fprintf(pcfile, "%06x] A:%04x X:%04x Y:%04x P:%03x " + "S:%04x D:%04x B:%02x %lld.%02lld %s\n", i, + acc, xreg, yreg, psr, stack, direct, dbank, + (dfcyc - base_dcyc) >> 16, + ((dfcyc & 0xffff) * 100) >> 16, str); + + if((dfcyc == 0) && (i != 0)) { + break; + } + max_dcyc = dfcyc; + log_pc_ptr++; + if(log_pc_ptr >= g_log_pc_end_ptr) { + log_pc_ptr = g_log_pc_start_ptr; + } + } + + // Print any more DATA or INFO after last PC entry + log_data_ptr = debug_show_data_info(pcfile, log_data_ptr, + base_dcyc, max_dcyc + 10 * 65536, start_dcyc, + &data_wrap, &data_count); + + fclose(pcfile); +} + +void +set_bp(word32 addr, word32 end_addr, word32 acc_type) +{ + int count; + + dbg_printf("About to set BP at %06x - %06x, type:%02x\n", addr, + end_addr, acc_type); + count = g_num_breakpoints; + if(count >= MAX_BREAK_POINTS) { + dbg_printf("Too many (0x%02x) breakpoints set!\n", count); + return; + } + + g_break_pts[count].start_addr = addr; + g_break_pts[count].end_addr = end_addr; + g_break_pts[count].acc_type = acc_type; + g_num_breakpoints = count + 1; + fixup_brks(); +} + +void +show_bp() +{ + char acc_str[4]; + word32 addr, end_addr, acc_type; + int i; + + dbg_printf("Showing breakpoints set\n"); + for(i = 0; i < g_num_breakpoints; i++) { + addr = g_break_pts[i].start_addr; + end_addr = g_break_pts[i].end_addr; + acc_type = g_break_pts[i].acc_type; + acc_str[0] = ' '; + acc_str[1] = ' '; + acc_str[2] = ' '; + acc_str[3] = 0; + if(acc_type & 4) { + acc_str[2] = 'X'; + } + if(acc_type & 2) { + acc_str[1] = 'W'; + } + if(acc_type & 1) { + acc_str[0] = 'R'; + } + if(end_addr != addr) { + dbg_printf("bp:%02x: %06x-%06x, t:%02x %s\n", i, addr, + end_addr, acc_type, acc_str); + } else { + dbg_printf("bp:%02x: %06x, t:%02x %s\n", i, addr, + acc_type, acc_str); + } + } +} + +void +delete_bp(word32 addr, word32 end_addr) +{ + int count, hit; + int i, j; + + dbg_printf("About to delete BP at %06x\n", addr); + count = g_num_breakpoints; + + hit = -1; + for(i = count - 1; i >= 0; i--) { + if((g_break_pts[i].start_addr > end_addr) || + (g_break_pts[i].end_addr < addr)) { + continue; // Not this entry + } + hit = i; + dbg_printf("Deleting brkpoint #0x%02x\n", hit); + for(j = i+1; j < count; j++) { + g_break_pts[j-1] = g_break_pts[j]; + } + count--; + } + g_num_breakpoints = count; + if(hit < 0) { + dbg_printf("Breakpoint not found!\n"); + } else { + setup_pageinfo(); + } + + show_bp(); +} + +void +debug_iwm(const char *str) +{ + if(str) { + // Dummy use of argument + } + iwm_show_track(-1, -1, 0); +} + +void +debug_iwm_check(const char *str) +{ + if(str) { + // Dummy use of argument + } + iwm_check_nibblization(0); +} + +int +do_blank(int mode, int old_mode) +{ + int tmp; + + switch(old_mode) { + case 's': + tmp = g_a2; + if(tmp == 0) { + tmp = 1; + } +#if 0 + for(i = 0; i < tmp; i++) { + g_stepping = 1; + do_step(); + if(g_halt_sim != 0) { + break; + } + } +#endif + g_list_kpc = engine.kpc; + /* video_update_through_line(262); */ + break; + case ':': + set_memory_c(((g_a3bank << 16) + g_a3), g_a2, 0); + g_a3++; + mode = old_mode; + break; + case '.': + case 0: + xam_mem(-1); + break; + case ',': + xam_mem(16); + break; + case '+': + dbg_printf("%x\n", g_a1 + g_a2); + break; + case '-': + dbg_printf("%x\n", g_a1 - g_a2); + break; + default: + dbg_printf("Unknown mode at space: %d\n", old_mode); + break; + } + return mode; +} + +void +do_go() +{ + /* also called by do_step */ + + g_config_control_panel = 0; + clear_halt(); +} + +void +do_step() +{ + int size_mem_imm, size_x_imm; + + return; // This is not correct + + do_go(); + + size_mem_imm = 2; + if(engine.psr & 0x20) { + size_mem_imm = 1; + } + size_x_imm = 2; + if(engine.psr & 0x10) { + size_x_imm = 1; + } + dbg_printf("%s\n", + do_dis(engine.kpc, size_mem_imm, size_x_imm, 0, 0, 0)); +} + +void +xam_mem(int count) +{ + show_hex_mem(g_a1bank, g_a1, g_a2, count); + g_a1 = g_a2 + 1; +} + +void +show_hex_mem(word32 startbank, word32 start, word32 end, int count) +{ + char ascii[MAXNUM_HEX_PER_LINE]; + word32 i; + int val, offset; + + if(count < 0) { + count = 16 - (start & 0xf); + } + + offset = 0; + ascii[0] = 0; + dbg_printf("Showing hex mem: bank: %x, start: %x, end: %x\n", + startbank, start, end); + for(i = start; i <= end; i++) { + if( (i==start) || (count == 16) ) { + dbg_printf("%04x:",i); + } + dbg_printf(" %02x", get_memory_c((startbank <<16) + i)); + val = get_memory_c((startbank << 16) + i) & 0x7f; + if((val < 32) || (val >= 0x7f)) { + val = '.'; + } + ascii[offset++] = val; + ascii[offset] = 0; + count--; + if(count <= 0) { + dbg_printf(" %s\n", ascii); + offset = 0; + ascii[0] = 0; + count = 16; + } + } + if(offset > 0) { + dbg_printf(" %s\n", ascii); + } +} + +void +do_debug_list() +{ + char *str; + int size, size_mem_imm, size_x_imm; + int i; + + dbg_printf("%d=m %d=x %d=LCBANK\n", (engine.psr >> 5)&1, + (engine.psr >> 4) & 1, (g_c068_statereg & 0x4) >> 2); + + size_mem_imm = 2; + if(engine.psr & 0x20) { + size_mem_imm = 1; + } + size_x_imm = 2; + if(engine.psr & 0x10) { + size_x_imm = 1; + } + for(i = 0; i < 20; i++) { + str = do_dis(g_list_kpc, size_mem_imm, size_x_imm, 0, 0, &size); + g_list_kpc += size; + dbg_printf("%s\n", str); + } +} + +void +dis_do_memmove() +{ + word32 val; + + dbg_printf("Memory move from %02x/%04x.%04x to %02x/%04x\n", g_a1bank, + g_a1, g_a2, g_a4bank, g_a4); + while(g_a1 <= (g_a2 & 0xffff)) { + val = get_memory_c((g_a1bank << 16) + g_a1); + set_memory_c((g_a4bank << 16) + g_a4, val, 0); + g_a1++; + g_a4++; + } + g_a1 = g_a1 & 0xffff; + g_a4 = g_a4 & 0xffff; +} + +void +dis_do_pattern_search() +{ +#if 0 + word32 match_val, val; + int match_shift, count; + + dbg_printf("Memory pattern search for %04x in %02x/%04x to %02x/%04x\n", + g_a4, g_a1bank, g_a1, g_a2bank, g_a2); + match_shift = 0; + count = 0; + match_val = g_a4; + while(1) { + if(g_a1bank > g_a2bank) { + break; + } + if(g_a1 > g_a2) { + break; + } + val = get_memory_c((g_a1bank << 16) + g_a1); + if(val == ((match_val >> match_shift) & 0xff)) { + match_shift += 8; + if(match_shift >= 16) { + dbg_printf("Found %04x at %02x/%04x\n", + match_val, g_a1bank, g_a1); + count++; + } + } else { + match_shift = 0; + } + g_a1++; + if(g_a1 >= 0x10000) { + g_a1 = 0; + g_a1bank++; + } + } +#endif +} + +void +dis_do_compare() +{ + word32 val1, val2; + + dbg_printf("Memory Compare from %02x/%04x.%04x with %02x/%04x\n", + g_a1bank, g_a1, g_a2, g_a4bank, g_a4); + while(g_a1 <= (g_a2 & 0xffff)) { + val1 = get_memory_c((g_a1bank << 16) + g_a1); + val2 = get_memory_c((g_a4bank << 16) + g_a4); + if(val1 != val2) { + dbg_printf("%02x/%04x: %02x vs %02x\n", g_a1bank, g_a1, + val1, val2); + } + g_a1++; + g_a4++; + } + g_a1 = g_a1 & 0xffff; + g_a4 = g_a4 & 0xffff; +} + +const char * +do_debug_unix(const char *str, int old_mode) +{ + char localbuf[LINE_SIZE+2]; + byte *bptr; + word32 offset, len, a1_val; + long ret; + int fd, load; + int i; + + load = 0; + switch(*str++) { + case 'l': case 'L': + dbg_printf("Loading.."); + load = 1; + break; + case 's': case 'S': + dbg_printf("Saving..."); + break; + default: + dbg_printf("Unknown unix command: %c\n", *(str - 1)); + if(str[-1] == 0) { + return str - 1; + } + return str; + } + while((*str == ' ') || (*str == '\t')) { + str++; + } + i = 0; + while(i < LINE_SIZE) { + localbuf[i++] = *str++; + if((*str==' ') || (*str == '\t') || (*str == '\n') || + (*str == 0)) { + break; + } + } + localbuf[i] = 0; + + dbg_printf("About to open: %s,len: %d\n", localbuf, + (int)strlen(localbuf)); + if(load) { + fd = open(localbuf, O_RDONLY | O_BINARY); + } else { + fd = open(localbuf, O_WRONLY | O_CREAT | O_BINARY, 0x1b6); + } + if(fd < 0) { + dbg_printf("Open %s failed: %d. errno:%d\n", localbuf, fd, + errno); + return str; + } + if(load) { + offset = g_a1 & 0xffff; + len = 0x20000 - offset; + } else { + if(old_mode == '.') { + len = g_a2 - g_a1 + 1; + } else { + len = 0x100; + } + } + a1_val = (g_a1bank << 16) | g_a1; + bptr = &g_memory_ptr[a1_val]; + if((g_a1bank >= 0xe0) && (g_a1bank < 0xe2)) { + bptr = &g_slow_memory_ptr[a1_val & 0x1ffff]; + } + if(load) { + ret = read(fd, bptr, len); + } else { + ret = write(fd, bptr, len); + } + dbg_printf("Read/write: addr %06x for %04x bytes, ret: %lx bytes\n", + a1_val, len, ret); + if(ret < 0) { + dbg_printf("errno: %d\n", errno); + } + g_a1 = g_a1 + (int)ret; + return str; +} + +void +do_debug_load() +{ + dbg_printf("Sorry, can't load now\n"); +} + +char * +do_dis(word32 kpc, int accsize, int xsize, int op_provided, word32 instr, + int *size_ptr) +{ + char buffer[MAX_DISAS_BUF]; + char buffer2[MAX_DISAS_BUF]; + const char *str; + word32 val, oldkpc, dtype; + int args, type, opcode, signed_val; + int i; + + oldkpc = kpc; + if(op_provided) { + opcode = (instr >> 24) & 0xff; + } else { + opcode = (int)get_memory_c(kpc) & 0xff; + } + + kpc++; + + dtype = disas_types[opcode]; + str = disas_opcodes[opcode]; + type = dtype & 0xff; + args = dtype >> 8; + + if(args > 3) { + if(args == 4) { + args = accsize; + } else if(args == 5) { + args = xsize; + } + } + + val = -1; + switch(args) { + case 0: + val = 0; + break; + case 1: + if(op_provided) { + val = instr & 0xff; + } else { + val = get_memory_c(kpc); + } + break; + case 2: + if(op_provided) { + val = instr & 0xffff; + } else { + val = get_memory16_c(kpc); + } + break; + case 3: + if(op_provided) { + val = instr & 0xffffff; + } else { + val = get_memory24_c(kpc); + } + break; + default: + fprintf(stderr, "args out of rang: %d, opcode: %08x\n", + args, opcode); + break; + } + kpc += args; + + if(!op_provided) { + instr = (opcode << 24) | (val & 0xffffff); + } + + switch(type) { + case ABS: + if(args != 2) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x", str, val); + break; + case ABSX: + if(args != 2) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x,X", str, val); + break; + case ABSY: + if(args != 2) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x,Y", str, val); + break; + case ABSLONG: + if(args != 3) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%06x", str, val); + break; + case ABSIND: + if(args != 2) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%04x)", str, val); + break; + case ABSXIND: + if(args != 2) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%04x,X)", str, val); + break; + case IMPLY: + case ACCUM: + if(args != 0) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s", str); + break; + case IMMED: + if(args == 1) { + snprintf(&buffer[0], MAX_DISAS_BUF, "%s #$%02x", str, + val); + } else if(args == 2) { + snprintf(&buffer[0], MAX_DISAS_BUF, "%s #$%04x", str, + val); + } else { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + break; + case JUST8: + case DLOC: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x", str, val); + break; + case DLOCX: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,X", str, val); + break; + case DLOCY: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,Y", str, val); + break; + case LONG: + if(args != 3) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%06x", str, val); + break; + case LONGX: + if(args != 3) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%06x,X", str, val); + break; + case DLOCIND: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x)", str, val); + break; + case DLOCINDY: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x),Y", str, val); + break; + case DLOCXIND: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x,X)", str, val); + break; + case DLOCBRAK: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s [$%02x]", str, val); + break; + case DLOCBRAKY: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s [$%02x],y", str, val); + break; + case DISP8: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + signed_val = (signed char)val; + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x", str, + (kpc + signed_val) & 0xffff); + break; + case DISP8S: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,S", str, + val & 0xff); + break; + case DISP8SINDY: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s ($%02x,S),Y", str, + val & 0xff); + break; + case DISP16: + if(args != 2) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%04x", str, + (word32)(kpc+(signed)(word16)(val)) & 0xffff); + break; + case MVPMVN: + if(args != 2) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s $%02x,$%02x", str, + val & 0xff, val >> 8); + break; + case SEPVAL: + case REPVAL: + if(args != 1) { + dbg_printf("arg # mismatch for opcode %x\n", opcode); + } + snprintf(&buffer[0], MAX_DISAS_BUF, "%s #$%02x", str, val); + break; + default: + dbg_printf("argument type: %d unexpected\n", type); + break; + } + + g_disas_buffer[0] = 0; + snprintf(&g_disas_buffer[0], MAX_DISAS_BUF, "%02x/%04x: %02x ", + oldkpc >> 16, oldkpc & 0xffff, opcode); + for(i = 1; i <= args; i++) { + snprintf(&buffer2[0], MAX_DISAS_BUF, "%02x ", instr & 0xff); + cfg_strlcat(&g_disas_buffer[0], &buffer2[0], MAX_DISAS_BUF); + instr = instr >> 8; + } + for(; i < 4; i++) { + cfg_strlcat(&g_disas_buffer[0], " ", MAX_DISAS_BUF); + } + cfg_strlcat(&g_disas_buffer[0], " ", MAX_DISAS_BUF); + cfg_strlcat(&g_disas_buffer[0], &buffer[0], MAX_DISAS_BUF); + + if(size_ptr) { + *size_ptr = args + 1; + } + return (&g_disas_buffer[0]); +} + +int +debug_get_view_line(int back) +{ + int pos; + + // where back==0 means return pos - 1. + pos = g_debug_lines_pos - 1; + pos = pos - back; + if(pos < 0) { + if(g_debug_lines_alloc >= g_debug_lines_max) { + pos += g_debug_lines_alloc; + } else { + return 0; // HACK: return -1 + } + } + return pos; +} + +int +debug_add_output_line(char *in_str) +{ + Debug_entry *line_ptr; + byte *out_bptr; + int pos, alloc, view, used_len, c; + int i; + + // printf("debug_add_output_line %s len:%d\n", in_str, len); + pos = g_debug_lines_pos; + line_ptr = g_debug_lines_ptr; + alloc = g_debug_lines_alloc; + if(pos >= alloc) { + if(alloc < g_debug_lines_max) { + alloc = MY_MAX(2048, alloc*3); + alloc = MY_MAX(alloc, pos*3); + alloc = MY_MIN(alloc, g_debug_lines_max); + line_ptr = realloc(line_ptr, + alloc * sizeof(Debug_entry)); + printf("realloc. now %p, alloc:%d\n", line_ptr, alloc); + g_debug_lines_ptr = line_ptr; + g_debug_lines_alloc = alloc; + printf("Alloced debug lines to %d\n", alloc); + } else { + pos = 0; + } + } + // Convert to A2 format chars: set high bit of each byte, 80 chars + // per line + out_bptr = &(line_ptr[pos].str_buf[0]); + used_len = 0; + for(i = 0; i < DEBUG_ENTRY_MAX_CHARS; i++) { + c = ' '; + if(*in_str) { + c = *in_str++; + used_len++; + } + c = c ^ 0x80; // Set highbit if not already set + out_bptr[i] = c; + } + pos++; + g_debug_lines_pos = pos; + g_debug_lines_total++; // For updating the window + g_debugwin_changed++; + view = g_debug_lines_view; + if(view >= 0) { + view++; // view is back from pos, so to stay the same, + // it must be incremented when pos incs + if((view - 50) >= g_debug_lines_max) { + // We were viewing the oldest page, and by wrapping + // around we're about to wipe out this old data + // Jump to most recent data + view = -1; + } + g_debug_lines_view = view; + } + + return used_len; +} + +void +debug_add_output_string(char *in_str, int len) +{ + int ret, tries; + + tries = 0; + ret = 0; + if(g_debug_to_stdout) { + puts(in_str); // Send output to stdout, too + } + while((len > 0) || (tries == 0)) { + // printf("DEBUG: adding str: %s, len:%d, ret:%d\n", in_str, + // len, ret); + ret = debug_add_output_line(in_str); + len -= ret; + in_str += ret; + tries++; + } +} + +void +debug_add_output_chars(char *str) +{ + int pos, c, tab_spaces; + + pos = g_debug_stage_pos; + tab_spaces = 0; + while(1) { + if(tab_spaces > 0) { + c = ' '; + tab_spaces--; + } else { + c = *str++; + if(c == '\t') { + tab_spaces = 7 - (pos & 7); + c = ' '; + } + } + pos = MY_MIN(pos, (PRINTF_BUF_SIZE - 1)); + if((c == '\n') || (pos >= (PRINTF_BUF_SIZE - 1))) { + g_debug_stage_buf[pos] = 0; + debug_add_output_string(&g_debug_stage_buf[0], pos); + pos = 0; + g_debug_stage_pos = 0; + continue; + } + if(c == 0) { + g_debug_stage_pos = pos; + return; + } + g_debug_stage_buf[pos++] = c; + } +} + +int +dbg_printf(const char *fmt, ...) +{ + va_list args; + int ret; + + va_start(args, fmt); + ret = dbg_vprintf(fmt, args); + va_end(args); + return ret; +} + +int +dbg_vprintf(const char *fmt, va_list args) +{ + int ret; + + ret = vsnprintf(&g_debug_printf_buf[0], PRINTF_BUF_SIZE, fmt, args); + debug_add_output_chars(&g_debug_printf_buf[0]); + return ret; +} + +void +halt_printf(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + dbg_vprintf(fmt, args); + va_end(args); + + set_halt(1); +} + +void +halt2_printf(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + dbg_vprintf(fmt, args); + va_end(args); + + set_halt(2); +} + diff --git a/gsplus/src/defc.h b/gsplus/src/defc.h new file mode 100644 index 0000000..a85882c --- /dev/null +++ b/gsplus/src/defc.h @@ -0,0 +1,364 @@ +#ifdef INCLUDE_RCSID_C +#endif + +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2024 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include "defcomm.h" + +#define STRUCT(a) typedef struct a ## _st a; struct a ## _st + +typedef unsigned char byte; +typedef unsigned short word16; +typedef unsigned int word32; +#if _MSC_VER +typedef unsigned __int64 dword64; +#else +typedef unsigned long long dword64; +#endif + +/* 28MHz crystal, plus every 65th 1MHz cycle is stretched 140ns */ +#define CYCS_28_MHZ (28636360) +#define DCYCS_28_MHZ (1.0*CYCS_28_MHZ) +#define CYCS_3_5_MHZ (CYCS_28_MHZ/8) +#define DCYCS_1_MHZ ((DCYCS_28_MHZ/28.0)*(65.0*7/(65.0*7+1.0))) + +// DCYCS_1_MHZ is 1020484.32016 + +#define CYCLES_IN_16MS_RAW (262 * 65) +/* Use precisely 17030 instead of forcing 60 Hz since this is the number of */ +/* 1MHz cycles per screen */ + +#define DCYCS_IN_16MS ((double)(CYCLES_IN_16MS_RAW)) +#define DRECIP_DCYCS_IN_16MS (1.0 / (DCYCS_IN_16MS)) +#define VBL_RATE (DCYCS_1_MHZ / DCYCS_IN_16MS) +// VBL rate is about 59.9227 frames/sec + +#define MAXNUM_HEX_PER_LINE 32 +#define MAX_SCALE_SIZE 5100 + +#ifdef __NeXT__ +# include +#endif + +#ifndef _WIN32 +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HPUX +# include /* for GET_ITIMER */ +#endif + +#ifdef SOLARIS +# include +#endif + +#ifdef _WIN32 +# include +# include +# pragma warning(disable : 4996) /* open() is deprecated...sigh */ +int ftruncate(int fd, word32 length); +int lstat(const char *path, struct stat *bufptr); +#endif + +#ifndef O_BINARY +/* work around some Windows junk */ +# define O_BINARY 0 +#endif + +#define MAX_CHANGE_RECTS 20 + +#ifdef __GNUC__ +int dbg_printf(const char *fmt, ...) __attribute__ (( + __format__(printf, 1, 2))); +#endif + +STRUCT(Pc_log) { + dword64 dfcyc; + word32 dbank_kpc; + word32 instr; + word32 psr_acc; + word32 xreg_yreg; + word32 stack_direct; + word32 pad; +}; + +STRUCT(Data_log) { + dword64 dfcyc; + byte *stat; + word32 addr; + word32 val; + word32 size; +}; + +STRUCT(Event) { + dword64 dfcyc; + int type; + Event *next; +}; + +STRUCT(Fplus) { + dword64 dplus_1; + dword64 dplus_x_minus_1; +}; + +STRUCT(Engine_reg) { + dword64 dfcyc; + word32 kpc; + word32 acc; + + word32 xreg; + word32 yreg; + + word32 stack; + word32 dbank; + + word32 direct; + word32 psr; + Fplus *fplus_ptr; +}; + +STRUCT(Break_point) { + word32 start_addr; + word32 end_addr; + word32 acc_type; +}; + +STRUCT(Change_rect) { + int x; + int y; + int width; + int height; +}; + + +STRUCT(Kimage) { + word32 *wptr; + int a2_width_full; + int a2_height_full; + int a2_width; + int a2_height; + int x_width; + int x_height; + int x_refresh_needed; + int x_max_width; + int x_max_height; + int x_xpos; + int x_ypos; + int active; + word32 vbl_of_last_resize; + word32 c025_val; + word32 scale_width_to_a2; + word32 scale_width_a2_to_x; + word32 scale_height_to_a2; + word32 scale_height_a2_to_x; + int num_change_rects; + Change_rect change_rect[MAX_CHANGE_RECTS]; + word32 scale_width[MAX_SCALE_SIZE + 1]; + word32 scale_height[MAX_SCALE_SIZE + 1]; +}; + +typedef byte *Pg_info; +STRUCT(Page_info) { + Pg_info rd_wr; +}; + +STRUCT(Cfg_menu) { + const char *str; + void *ptr; + const char *name_str; + void *defptr; + int cfgtype; +}; + +STRUCT(Cfg_dirent) { + char *name; + int is_dir; + int part_num; + dword64 dsize; + dword64 dimage_start; + dword64 compr_dsize; +}; + +STRUCT(Cfg_listhdr) { + Cfg_dirent *direntptr; + int max; + int last; + int invalid; + + int curent; + int topent; + + int num_to_show; +}; + +typedef void (Dbg_fn)(const char *str); + +STRUCT(Dbg_longcmd) { + const char *str; + Dbg_fn *fnptr; + Dbg_longcmd *subptr; + const char *help_str; +}; + +STRUCT(Emustate_intlist) { + const char *str; + word32 *iptr; +}; + +STRUCT(Emustate_dword64list) { + const char *str; + dword64 *dptr; +}; + +STRUCT(Emustate_word32list) { + const char *str; + word32 *wptr; +}; + +STRUCT(Lzw_state) { + word32 table[4096 + 2]; + word32 entry; + int bits; +}; + +#ifdef __LP64__ +# define PTR2WORD(a) ((word32)(unsigned long long)(a)) +#else +# define PTR2WORD(a) ((word32)(unsigned long long)(a)) +#endif + + +#define ALTZP (g_c068_statereg & 0x80) +/* #define PAGE2 (g_c068_statereg & 0x40) */ +#define RAMRD (g_c068_statereg & 0x20) +#define RAMWRT (g_c068_statereg & 0x10) +#define RDROM (g_c068_statereg & 0x08) +#define LCBANK2 (g_c068_statereg & 0x04) +#define ROMB (g_c068_statereg & 0x02) +// #define INTCX (g_c068_statereg & 0x01) + +#define C041_EN_25SEC_INTS 0x10 +#define C041_EN_VBL_INTS 0x08 +#define C041_EN_SWITCH_INTS 0x04 +#define C041_EN_MOVE_INTS 0x02 +#define C041_EN_MOUSE 0x01 + +/* WARNING: SCC1 and SCC0 interrupts must be in this order for scc.c */ +/* This order matches the SCC hardware, and SCC1_ZEROCNT must be 0x0001 */ +#define IRQ_PENDING_SCC1_ZEROCNT 0x00001 +#define IRQ_PENDING_SCC1_TX 0x00002 +#define IRQ_PENDING_SCC1_RX 0x00004 +#define IRQ_PENDING_SCC0_ZEROCNT 0x00008 +#define IRQ_PENDING_SCC0_TX 0x00010 +#define IRQ_PENDING_SCC0_RX 0x00020 +#define IRQ_PENDING_C023_SCAN 0x00100 +#define IRQ_PENDING_C023_1SEC 0x00200 +#define IRQ_PENDING_C046_25SEC 0x00400 +#define IRQ_PENDING_C046_VBL 0x00800 +#define IRQ_PENDING_ADB_KBD_SRQ 0x01000 +#define IRQ_PENDING_ADB_DATA 0x02000 +#define IRQ_PENDING_ADB_MOUSE 0x04000 +#define IRQ_PENDING_DOC 0x08000 +#define IRQ_PENDING_MOCKINGBOARDA 0x10000 +#define IRQ_PENDING_MOCKINGBOARDB 0x20000 /* must be BOARDA*2 */ + + +#define EXTRU(val, pos, len) \ + ( ( (len) >= (pos) + 1) ? ((val) >> (31-(pos))) : \ + (((val) >> (31-(pos)) ) & ( (1<<(len) ) - 1) ) ) + +#define DEP1(val, pos, old_val) \ + (((old_val) & ~(1 << (31 - (pos))) ) | \ + ( ((val) & 1) << (31 - (pos))) ) + +#define set_halt(val) \ + if(val) { set_halt_act(val); } + +#define clear_halt() \ + clr_halt_act() + +#define GET_PAGE_INFO_RD(page) \ + (page_info_rd_wr[page].rd_wr) + +#define GET_PAGE_INFO_WR(page) \ + (page_info_rd_wr[0x10000 + PAGE_INFO_PAD_SIZE + (page)].rd_wr) + +#define SET_PAGE_INFO_RD(page,val) \ + ;page_info_rd_wr[page].rd_wr = (Pg_info)val; + +#define SET_PAGE_INFO_WR(page,val) \ + ;page_info_rd_wr[0x10000 + PAGE_INFO_PAD_SIZE + (page)].rd_wr = \ + (Pg_info)val; + +#define VERBOSE_DISK 0x001 +#define VERBOSE_IRQ 0x002 +#define VERBOSE_CLK 0x004 +#define VERBOSE_SHADOW 0x008 +#define VERBOSE_IWM 0x010 +#define VERBOSE_DOC 0x020 +#define VERBOSE_ADB 0x040 +#define VERBOSE_SCC 0x080 +#define VERBOSE_TEST 0x100 +#define VERBOSE_VIDEO 0x200 +#define VERBOSE_MAC 0x400 +#define VERBOSE_DYNA 0x800 + +#ifdef NO_VERB +# define DO_VERBOSE 0 +#else +# define DO_VERBOSE 1 +#endif + +#define disk_printf if(DO_VERBOSE && (Verbose & VERBOSE_DISK)) printf +#define irq_printf if(DO_VERBOSE && (Verbose & VERBOSE_IRQ)) printf +#define clk_printf if(DO_VERBOSE && (Verbose & VERBOSE_CLK)) printf +#define shadow_printf if(DO_VERBOSE && (Verbose & VERBOSE_SHADOW)) printf +#define iwm_printf if(DO_VERBOSE && (Verbose & VERBOSE_IWM)) printf +#define doc_printf if(DO_VERBOSE && (Verbose & VERBOSE_DOC)) printf +#define adb_printf if(DO_VERBOSE && (Verbose & VERBOSE_ADB)) printf +#define scc_printf if(DO_VERBOSE && (Verbose & VERBOSE_SCC)) printf +#define test_printf if(DO_VERBOSE && (Verbose & VERBOSE_TEST)) printf +#define vid_printf if(DO_VERBOSE && (Verbose & VERBOSE_VIDEO)) printf +#define mac_printf if(DO_VERBOSE && (Verbose & VERBOSE_MAC)) printf +#define dyna_printf if(DO_VERBOSE && (Verbose & VERBOSE_DYNA)) printf + + +#define HALT_ON_SCAN_INT 0x001 +#define HALT_ON_IRQ 0x002 +#define HALT_ON_SHADOW_REG 0x004 +#define HALT_ON_C70D_WRITES 0x008 + +#define HALT_ON(a, msg) \ + if(Halt_on & a) { \ + halt_printf(msg); \ + } + + +#define MY_MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define MY_MAX(a,b) (((a) > (b)) ? (a) : (b)) + +#define GET_ITIMER(dest) dest = get_itimer(); + +#define FINISH(arg1, arg2) g_ret1 = arg1 | ((arg2) << 8); g_dcycles_end=0; + +#include "iwm.h" +#include "protos.h" diff --git a/gsplus/src/defcomm.h b/gsplus/src/defcomm.h new file mode 100644 index 0000000..d14707c --- /dev/null +++ b/gsplus/src/defcomm.h @@ -0,0 +1,129 @@ +#ifdef INCLUDE_RCSID_C +const char rcsdif_defcomm_h[] = "@(#)$KmKId: defcomm.h,v 1.109 2023-11-12 15:29:41+00 kentd Exp $"; +#endif + +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#define SHIFT_PER_CHANGE 3 +#define CHANGE_SHIFT (5 + SHIFT_PER_CHANGE) + +#define SLOW_MEM_CH_SIZE (0x20000 >> CHANGE_SHIFT) + +#define MAXNUM_HEX_PER_LINE 32 + +/* Different Joystick defines */ +#define JOYSTICK_MOUSE 1 +#define JOYSTICK_LINUX 2 +#define JOYSTICK_KEYPAD 3 +#define JOYSTICK_WIN32_1 4 +#define JOYSTICK_WIN32_2 5 + +#define MAX_BREAK_POINTS 0x20 + +#define MAX_BP_INDEX 0x100 +#define MAX_BP_PER_INDEX 3 /* 4 word32s total = 16 bytes */ +#define SIZE_BREAKPT_ENTRY_BITS 4 /* 16 bytes = 4 bits */ + +/* Warning--next defines used by asm! */ +#define PAGE_INFO_PAD_SIZE 0x800 +#define PAGE_INFO_WR_OFFSET 0x10000+PAGE_INFO_PAD_SIZE + +#define BANK_IO_BIT 31 +#define BANK_SHADOW_BIT 30 +#define BANK_SHADOW2_BIT 29 +#define BANK_IO2_BIT 28 +#define BANK_BREAK_BIT 27 +#define BANK_BREAK (1 << (31 - BANK_BREAK_BIT)) +#define BANK_IO2_TMP (1 << (31 - BANK_IO2_BIT)) +#define BANK_IO_TMP (1 << (31 - BANK_IO_BIT)) +#define BANK_SHADOW (1 << (31 - BANK_SHADOW_BIT)) +#define BANK_SHADOW2 (1 << (31 - BANK_SHADOW2_BIT)) +#define SET_BANK_IO \ + (&g_dummy_memory1_ptr[BANK_IO_TMP | BANK_IO2_TMP]) + +#define BANK_BAD_MEM (&g_dummy_memory1_ptr[0xff]) + + +#define RET_BREAK 0x1 +#define RET_COP 0x2 +#define RET_WDM 0x3 +#define RET_WAI 0x4 +#define RET_STP 0x5 +#define RET_PSR 0x6 +#define RET_IRQ 0x7 +#define RET_TOOLTRACE 0x8 + + +#define BIT_ALL_STAT_TEXT 0 +#define BIT_ALL_STAT_VID80 1 +#define BIT_ALL_STAT_ST80 2 +#define BIT_ALL_STAT_COLOR_C021 3 +#define BIT_ALL_STAT_MIX_T_GR 4 +#define BIT_ALL_STAT_DIS_COLOR_DHIRES 5 /* special val, c029 */ +#define BIT_ALL_STAT_PAGE2 6 /* special val, statereg */ +#define BIT_ALL_STAT_SUPER_HIRES 7 /* special, c029 */ +#define BIT_ALL_STAT_HIRES 8 +#define BIT_ALL_STAT_ANNUNC3 9 +#define BIT_ALL_STAT_ALTCHARSET 10 +#define BIT_ALL_STAT_FLASH_STATE 11 +#define BIT_ALL_STAT_BG_COLOR 12 /* 4 bits */ +#define BIT_ALL_STAT_TEXT_COLOR 16 /* 4 bits */ + /* Text must be just above */ + /* bg to match c022 reg */ +#define BIT_ALL_STAT_VOC_INTERLACE 20 +#define BIT_ALL_STAT_VOC_MAIN 21 +#define BIT_ALL_STAT_BORDER 22 + +#define ALL_STAT_SUPER_HIRES (1 << (BIT_ALL_STAT_SUPER_HIRES)) +#define ALL_STAT_TEXT (1 << (BIT_ALL_STAT_TEXT)) +#define ALL_STAT_VID80 (1 << (BIT_ALL_STAT_VID80)) +#define ALL_STAT_PAGE2 (1 << (BIT_ALL_STAT_PAGE2)) +#define ALL_STAT_ST80 (1 << (BIT_ALL_STAT_ST80)) +#define ALL_STAT_COLOR_C021 (1 << (BIT_ALL_STAT_COLOR_C021)) +#define ALL_STAT_DIS_COLOR_DHIRES (1 << (BIT_ALL_STAT_DIS_COLOR_DHIRES)) +#define ALL_STAT_MIX_T_GR (1 << (BIT_ALL_STAT_MIX_T_GR)) +#define ALL_STAT_HIRES (1 << (BIT_ALL_STAT_HIRES)) +#define ALL_STAT_ANNUNC3 (1 << (BIT_ALL_STAT_ANNUNC3)) +#define ALL_STAT_TEXT_COLOR (0xf << (BIT_ALL_STAT_TEXT_COLOR)) +#define ALL_STAT_BG_COLOR (0xf << (BIT_ALL_STAT_BG_COLOR)) +#define ALL_STAT_ALTCHARSET (1 << (BIT_ALL_STAT_ALTCHARSET)) +#define ALL_STAT_FLASH_STATE (1 << (BIT_ALL_STAT_FLASH_STATE)) +#define ALL_STAT_VOC_INTERLACE (1 << (BIT_ALL_STAT_VOC_INTERLACE)) +#define ALL_STAT_VOC_MAIN (1 << (BIT_ALL_STAT_VOC_MAIN)) +#define ALL_STAT_BORDER (1 << (BIT_ALL_STAT_BORDER)) + +#define BORDER_WIDTH 32 + +#define EFF_BORDER_WIDTH (BORDER_WIDTH + (640-560)) + +/* BASE_MARGIN_BOTTOM+MARGIN_TOP must equal 62. There are 262 scan lines */ +/* at 60Hz (15.7KHz line rate) and so we just make 62 border lines */ +#define BASE_MARGIN_TOP 32 +#define BASE_MARGIN_BOTTOM 30 +#define BASE_MARGIN_LEFT BORDER_WIDTH +#define BASE_MARGIN_RIGHT BORDER_WIDTH + +#define A2_WINDOW_WIDTH 640 +#define A2_WINDOW_HEIGHT 400 + +#define X_A2_WINDOW_WIDTH (A2_WINDOW_WIDTH + BASE_MARGIN_LEFT + \ + BASE_MARGIN_RIGHT) +#define X_A2_WINDOW_HEIGHT (A2_WINDOW_HEIGHT + BASE_MARGIN_TOP + \ + BASE_MARGIN_BOTTOM) + +#define MAX_STATUS_LINES 4 +#define STATUS_LINE_LENGTH 88 + +#define BASE_WINDOW_WIDTH (X_A2_WINDOW_WIDTH) + + +#define A2_BORDER_COLOR_NUM 0xfe + diff --git a/gsplus/src/defs_instr.h b/gsplus/src/defs_instr.h new file mode 100644 index 0000000..07e1bf6 --- /dev/null +++ b/gsplus/src/defs_instr.h @@ -0,0 +1,759 @@ +// $KmKId: defs_instr.h,v 1.70 2023-11-05 16:22:26+00 kentd Exp $ + +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2021 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#undef GET_DLOC_X_IND_RD + +#ifdef ACC8 +# define GET_DLOC_X_IND_RD() \ + GET_1BYTE_ARG; \ + GET_DLOC_X_IND_WR(); \ + GET_MEMORY8(arg, arg); +#else +# define GET_DLOC_X_IND_RD() \ + GET_1BYTE_ARG; \ + GET_DLOC_X_IND_WR(); \ + GET_MEMORY16(arg, arg, 0); +#endif + +#undef GET_DISP8_S_RD + +#ifdef ACC8 +# define GET_DISP8_S_RD() \ + GET_1BYTE_ARG; \ + GET_DISP8_S_WR(); \ + GET_MEMORY8(arg, arg); +#else +# define GET_DISP8_S_RD() \ + GET_1BYTE_ARG; \ + GET_DISP8_S_WR(); \ + GET_MEMORY16(arg, arg, 0); +#endif + +#undef GET_DLOC_RD + +#ifdef ACC8 +# define GET_DLOC_RD() \ + GET_1BYTE_ARG; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + INC_KPC_2; \ + GET_MEMORY8((direct + arg) & 0xffff, arg); +#else +# define GET_DLOC_RD() \ + GET_1BYTE_ARG; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + INC_KPC_2; \ + GET_MEMORY16((direct + arg) & 0xffff, arg, 1); +#endif + +#undef GET_DLOC_RD_RMW +#undef GET_MEM_RMW + +#define GET_MEM_RMW() \ + if(!IS_ACC16) { \ + if(psr & 0x100) { \ + /* emulation re-writes the address */ \ + SET_MEMORY8(addr_latch, arg); \ + } else { \ + /* otherwise, just read addr again */ \ + GET_MEMORY8(addr_latch, dummy1); \ + } \ + } else { \ + /* 16-bit re-reads addr+1 again */ \ + dummy1 = addr_latch + 1; \ + GET_MEMORY8(dummy1, dummy1); \ + addr_latch--; \ + } + +#define GET_DLOC_RD_RMW() \ + GET_DLOC_RD(); \ + GET_MEM_RMW(); + + +#undef GET_DLOC_L_IND_RD + +#ifdef ACC8 +# define GET_DLOC_L_IND_RD() \ + GET_1BYTE_ARG; \ + GET_DLOC_L_IND_WR(); \ + GET_MEMORY8(arg, arg); +#else +# define GET_DLOC_L_IND_RD() \ + GET_1BYTE_ARG; \ + GET_DLOC_L_IND_WR(); \ + GET_MEMORY16(arg, arg, 0); +#endif + +#undef GET_IMM_MEM + +#ifdef ACC8 +# define GET_IMM_MEM() \ + GET_1BYTE_ARG; \ + INC_KPC_2; +#else +# define GET_IMM_MEM() \ + GET_2BYTE_ARG; \ + CYCLES_PLUS_1; \ + INC_KPC_3; +#endif + +#undef GET_ABS_RD + +#ifdef ACC8 +# define GET_ABS_RD() \ + GET_2BYTE_ARG; \ + CYCLES_PLUS_1; \ + GET_MEMORY8((dbank << 16) + arg, arg); \ + INC_KPC_3; +#else +# define GET_ABS_RD() \ + GET_2BYTE_ARG; \ + CYCLES_PLUS_1; \ + GET_MEMORY16((dbank << 16) + arg, arg, 0); \ + INC_KPC_3; +#endif + +#undef GET_ABS_RD_RMW + +#define GET_ABS_RD_RMW() \ + GET_ABS_RD(); \ + GET_MEM_RMW(); + +#undef GET_LONG_RD + +#ifdef ACC8 +# define GET_LONG_RD() \ + GET_3BYTE_ARG; \ + CYCLES_PLUS_2; \ + GET_MEMORY8(arg, arg); \ + INC_KPC_4; +#else +# define GET_LONG_RD() \ + GET_3BYTE_ARG; \ + CYCLES_PLUS_2; \ + GET_MEMORY16(arg, arg, 0); \ + INC_KPC_4; +#endif + +#undef GET_DLOC_IND_Y_RD +#undef GET_DLOC_IND_Y_ADDR + +#ifdef ACC8 +# define GET_DLOC_IND_Y_RD() \ + GET_DLOC_IND_Y_ADDR(0) \ + GET_MEMORY8(arg, arg); +#else +# define GET_DLOC_IND_Y_RD() \ + GET_DLOC_IND_Y_ADDR(0) \ + GET_MEMORY16(arg, arg, 0); +#endif + +#define GET_DLOC_IND_Y_ADDR(is_write) \ + GET_1BYTE_ARG; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, tmp1, 0); \ + tmp1 += (dbank << 16); \ + arg = (tmp1 + yreg) & 0xffffff; \ + tmp2 = (tmp1 & 0xffff00) | (arg & 0xff); \ + if((psr & 0x10) && ((arg != tmp2) | is_write)) { \ + GET_MEMORY8(tmp2, tmp1); \ + } else if(((psr & 0x10) == 0) | (arg != tmp2) | is_write) { \ + CYCLES_PLUS_1; \ + } \ + INC_KPC_2; + +#undef GET_DLOC_IND_RD + +#ifdef ACC8 +# define GET_DLOC_IND_RD() \ + GET_1BYTE_ARG; \ + INC_KPC_2; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, arg, 0); \ + GET_MEMORY8((dbank << 16) + arg, arg); +#else +# define GET_DLOC_IND_RD() \ + GET_1BYTE_ARG; \ + INC_KPC_2; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, arg, 0); \ + GET_MEMORY16((dbank << 16) + arg, arg, 0); +#endif + +#undef GET_DLOC_X_RD + +#ifdef ACC8 +# define GET_DLOC_X_RD() \ + GET_1BYTE_ARG; \ + CYCLES_PLUS_1; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + INC_KPC_2; \ + arg = (arg + xreg + direct) & 0xffff; \ + if(psr & 0x100) { \ + if((direct & 0xff) == 0) { \ + arg = (direct & 0xff00) | (arg & 0xff); \ + } \ + } \ + GET_MEMORY8(arg, arg); +#else +# define GET_DLOC_X_RD() \ + GET_1BYTE_ARG; \ + CYCLES_PLUS_1; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + INC_KPC_2; \ + arg = (arg + xreg + direct) & 0xffff; \ + if(!IS_ACC16 && (psr & 0x100)) { \ + if((direct & 0xff) == 0) { \ + arg = (direct & 0xff00) | (arg & 0xff); \ + } \ + } \ + GET_MEMORY16(arg, arg, 1); +#endif + +#undef GET_DLOC_X_RD_RMW + +#define GET_DLOC_X_RD_RMW() \ + GET_DLOC_X_RD(); \ + GET_MEM_RMW(); + + +#undef GET_DISP8_S_IND_Y_RD + +#ifdef ACC8 +# define GET_DISP8_S_IND_Y_RD() \ + GET_1BYTE_ARG; \ + GET_DISP8_S_IND_Y_WR(); \ + GET_MEMORY8(arg, arg); +#else +# define GET_DISP8_S_IND_Y_RD() \ + GET_1BYTE_ARG; \ + GET_DISP8_S_IND_Y_WR(); \ + GET_MEMORY16(arg, arg, 0); +#endif + +#undef GET_DLOC_L_IND_Y_RD + +#ifdef ACC8 +# define GET_DLOC_L_IND_Y_RD() \ + GET_1BYTE_ARG; \ + GET_DLOC_L_IND_Y_WR(); \ + GET_MEMORY8(arg, arg); +#else +# define GET_DLOC_L_IND_Y_RD() \ + GET_1BYTE_ARG; \ + GET_DLOC_L_IND_Y_WR(); \ + GET_MEMORY16(arg, arg, 0); +#endif + +#undef GET_ABS_INDEX_ADDR + +#define GET_ABS_INDEX_ADDR(index_reg, is_write) \ + GET_2BYTE_ARG; \ + CYCLES_PLUS_1; \ + INC_KPC_3; \ + tmp1 = (dbank << 16) + arg; \ + arg = tmp1 + index_reg; \ + tmp1 = (tmp1 & 0xffff00) + (arg & 0xff); \ + if((psr & 0x10) && ((tmp1 != arg) | is_write)) { \ + GET_MEMORY8(tmp1, tmp2); \ + } else if(((psr & 0x10) == 0) | (tmp1 != arg) | is_write) { \ + CYCLES_PLUS_1; \ + } + +#undef GET_ABS_Y_RD + +#ifdef ACC8 +# define GET_ABS_Y_RD() \ + GET_ABS_INDEX_ADDR(yreg, 0); \ + GET_MEMORY8(arg, arg); +#else +# define GET_ABS_Y_RD() \ + GET_ABS_INDEX_ADDR(yreg, 0); \ + GET_MEMORY16(arg, arg, 0); +#endif + +#undef GET_ABS_X_RD +#undef GET_ABS_X_RD_RMW + +#ifdef ACC8 +# define GET_ABS_X_RD() \ + GET_ABS_INDEX_ADDR(xreg, 0); \ + GET_MEMORY8(arg, arg); +#define GET_ABS_X_RD_RMW() \ + GET_ABS_INDEX_ADDR(xreg, 1); \ + GET_MEMORY8(arg, arg); \ + GET_MEM_RMW(); +#else +# define GET_ABS_X_RD() \ + GET_ABS_INDEX_ADDR(xreg, 0); \ + GET_MEMORY16(arg, arg, 0); +#define GET_ABS_X_RD_RMW() \ + GET_ABS_INDEX_ADDR(xreg, 1); \ + GET_MEMORY16(arg, arg, 0); \ + GET_MEM_RMW(); +#endif + +#undef GET_LONG_X_RD + +#ifdef ACC8 +# define GET_LONG_X_RD() \ + GET_3BYTE_ARG; \ + arg = (arg + xreg) & 0xffffff; \ + INC_KPC_4; \ + CYCLES_PLUS_2; \ + GET_MEMORY8(arg, arg); +#else +# define GET_LONG_X_RD() \ + GET_3BYTE_ARG; \ + arg = (arg + xreg) & 0xffffff; \ + INC_KPC_4; \ + CYCLES_PLUS_2; \ + GET_MEMORY16(arg, arg, 0); +#endif + +#define SET_NEG_ZERO16(val) \ + zero = val; \ + neg7 = (val) >> 8; + +#define SET_NEG_ZERO8(val) \ + zero = val; \ + neg7 = val; + +#define SET_CARRY8(val) \ + psr = (psr & ~1) + (((val) >> 8) & 1); + +#define SET_CARRY16(val) \ + psr = (psr & ~1) + (((val) >> 16) & 1); + +#define SET_INDEX_REG(src, dest) \ + if(psr & 0x10) { \ + dest = (src) & 0xff; \ + SET_NEG_ZERO8(dest); \ + } else { \ + dest = (src) & 0xffff; \ + SET_NEG_ZERO16(dest); \ + } + +#define LD_INDEX_INST(index_reg, in_bank) \ + if(psr & 0x10) { \ + GET_MEMORY8(arg, arg); \ + } else { \ + GET_MEMORY16(arg, arg, in_bank);\ + } \ + SET_INDEX_REG(arg, index_reg); + +#define LDX_INST(in_bank) LD_INDEX_INST(xreg, in_bank) +#define LDY_INST(in_bank) LD_INDEX_INST(yreg, in_bank) + +#undef ORA_INST + +#ifdef ACC8 +# define ORA_INST() \ + tmp1 = (acc | arg) & 0xff; \ + acc = (acc & 0xff00) + tmp1; \ + SET_NEG_ZERO8(tmp1); +#else +# define ORA_INST() \ + acc = (acc | arg); \ + SET_NEG_ZERO16(acc); +#endif + +#undef AND_INST + +#ifdef ACC8 +# define AND_INST() \ + tmp1 = (acc & arg) & 0xff; \ + acc = (acc & 0xff00) + tmp1; \ + SET_NEG_ZERO8(tmp1); +#else +# define AND_INST() \ + acc = (acc & arg); \ + SET_NEG_ZERO16(acc); +#endif + +#undef EOR_INST + +#ifdef ACC8 +# define EOR_INST() \ + tmp1 = (acc ^ arg) & 0xff; \ + acc = (acc & 0xff00) + tmp1; \ + SET_NEG_ZERO8(tmp1); +#else +# define EOR_INST() \ + acc = (acc ^ arg); \ + SET_NEG_ZERO16(acc); +#endif + +#undef LDA_INST + +#ifdef ACC8 +# define LDA_INST() \ + acc = (acc & 0xff00) + (arg & 0xff); \ + SET_NEG_ZERO8(arg & 0xff); +#else +# define LDA_INST() \ + acc = (arg & 0xffff); \ + SET_NEG_ZERO16(acc); +#endif + +#undef ADC_INST + +#ifdef ACC8 +# define ADC_INST() \ + tmp1 = do_adc_sbc8(acc & 0xff, arg & 0xff, psr, 0); \ + acc = (acc & 0xff00) + (tmp1 & 0xff); \ + psr = (tmp1 >> 16); \ + zero = !(psr & 0x2); \ + neg7 = psr; +#else +# define ADC_INST() \ + tmp1 = do_adc_sbc16(acc, arg & 0xffff, psr, 0); \ + acc = (tmp1 & 0xffff); \ + psr = (tmp1 >> 16); \ + zero = !(psr & 0x2); \ + neg7 = psr; +#endif + +#undef SBC_INST + +#ifdef ACC8 +# define SBC_INST() \ + tmp1 = do_adc_sbc8(acc & 0xff, arg & 0xff, psr, 1); \ + acc = (acc & 0xff00) + (tmp1 & 0xff); \ + psr = (tmp1 >> 16); \ + zero = !(psr & 0x2); \ + neg7 = psr; +#else +# define SBC_INST() \ + tmp1 = do_adc_sbc16(acc, arg & 0xffff, psr, 1); \ + acc = (tmp1 & 0xffff); \ + psr = (tmp1 >> 16); \ + zero = !(psr & 0x2); \ + neg7 = psr; +#endif + + +#undef CMP_INST + +#ifdef ACC8 +# define CMP_INST() \ + arg = (acc & 0xff) + (0x100 - arg); \ + SET_CARRY8(arg); \ + arg = arg & 0xff; \ + SET_NEG_ZERO8(arg & 0xff); +#else +# define CMP_INST() \ + arg = (acc & 0xffff) + (0x10000 - arg); \ + SET_CARRY16(arg); \ + arg = arg & 0xffff; \ + SET_NEG_ZERO16(arg & 0xffff); +#endif + +#undef BIT_INST + +#ifdef ACC8 +# define BIT_INST() \ + neg7 = arg; \ + zero = (acc & arg & 0xff); \ + psr = (psr & (~0x40)) | (arg & 0x40); +#else +# define BIT_INST() \ + neg7 = arg >> 8; \ + zero = (acc & arg & 0xffff); \ + psr = (psr & (~0x40)) | ((arg >> 8) & 0x40); +#endif + +#undef STA_INST + +#ifdef ACC8 +# define STA_INST(in_bank) \ + SET_MEMORY8(arg, acc); +#else +# define STA_INST(in_bank) \ + SET_MEMORY16(arg, acc, in_bank); +#endif + +#undef TSB_INST + +#ifdef ACC8 +# define TSB_INST(in_bank) \ + arg = arg & 0xff; \ + tmp1 = arg | acc; \ + zero = arg & acc; \ + SET_MEMORY8(addr_latch, tmp1); +#else +# define TSB_INST(in_bank) \ + tmp1 = arg | acc; \ + zero = arg & acc; \ + SET_MEMORY16(addr_latch, tmp1, in_bank); +#endif + +#undef ASL_INST + +#ifdef ACC8 +# define ASL_INST(in_bank) \ + psr = (psr & 0x1fe) + ((arg >> 7) & 1); \ + tmp1 = (arg << 1) & 0xff; \ + SET_NEG_ZERO8(tmp1); \ + SET_MEMORY8(addr_latch, tmp1); +#else +# define ASL_INST(in_bank) \ + psr = (psr & 0x1fe) + ((arg >> 15) & 1);\ + tmp1 = (arg << 1) & 0xffff; \ + SET_NEG_ZERO16(tmp1); \ + SET_MEMORY16(addr_latch, tmp1, in_bank); +#endif + +#undef ROL_INST + +#ifdef ACC8 +# define ROL_INST(in_bank) \ + arg = arg & 0xff; \ + arg = (arg << 1) | (psr & 1); \ + SET_MEMORY8(addr_latch, arg); \ + SET_NEG_ZERO8(arg & 0xff); \ + SET_CARRY8(arg); +#else +# define ROL_INST(in_bank) \ + arg = (arg << 1) | (psr & 1); \ + SET_MEMORY16(addr_latch, arg, in_bank); \ + SET_NEG_ZERO16(arg & 0xffff); \ + SET_CARRY16(arg); +#endif + +#undef LSR_INST + +#ifdef ACC8 +# define LSR_INST(in_bank) \ + SET_CARRY8(arg << 8); \ + arg = (arg >> 1) & 0x7f; \ + SET_NEG_ZERO8(arg); \ + SET_MEMORY8(addr_latch, arg); +#else +# define LSR_INST(in_bank) \ + SET_CARRY16(arg << 16); \ + arg = (arg >> 1) & 0x7fff; \ + SET_NEG_ZERO16(arg); \ + SET_MEMORY16(addr_latch, arg, in_bank); +#endif + +#undef ROR_INST + +#ifdef ACC8 +# define ROR_INST(in_bank) \ + tmp1 = psr & 1; \ + SET_CARRY8(arg << 8); \ + arg = ((arg >> 1) & 0x7f) | (tmp1 << 7); \ + SET_MEMORY8(addr_latch, arg); \ + SET_NEG_ZERO8(arg); +#else +# define ROR_INST(in_bank) \ + tmp1 = psr & 1; \ + SET_CARRY16(arg << 16); \ + arg = ((arg >> 1) & 0x7fff) | (tmp1 << 15); \ + SET_MEMORY16(addr_latch, arg, in_bank); \ + SET_NEG_ZERO16(arg); +#endif + +#undef TRB_INST + +#ifdef ACC8 +# define TRB_INST(in_bank) \ + arg = arg & 0xff; \ + tmp1 = arg & ~acc; \ + zero = arg & acc; \ + SET_MEMORY8(addr_latch, tmp1); +#else +# define TRB_INST(in_bank) \ + tmp1 = arg & ~acc; \ + zero = arg & acc; \ + SET_MEMORY16(addr_latch, tmp1, in_bank); +#endif + +#undef DEC_INST + +#ifdef ACC8 +# define DEC_INST(in_bank) \ + arg = (arg - 1) & 0xff; \ + SET_MEMORY8(addr_latch, arg); \ + SET_NEG_ZERO8(arg); +#else +# define DEC_INST(in_bank) \ + arg = (arg - 1) & 0xffff; \ + SET_MEMORY16(addr_latch, arg, in_bank); \ + SET_NEG_ZERO16(arg); +#endif + +#undef INC_INST + +#ifdef ACC8 +# define INC_INST(in_bank) \ + arg = (arg + 1) & 0xff; \ + SET_MEMORY8(addr_latch, arg); \ + SET_NEG_ZERO8(arg); +#else +# define INC_INST(in_bank) \ + arg = (arg + 1) & 0xffff; \ + SET_MEMORY16(addr_latch, arg, in_bank); \ + SET_NEG_ZERO16(arg); +#endif + +#undef STZ_INST + +#ifdef ACC8 +# define STZ_INST(in_bank) \ + SET_MEMORY8(arg, 0); +#else +# define STZ_INST(in_bank) \ + SET_MEMORY16(arg, 0, in_bank); +#endif + +#undef BRANCH_DISP8 + +#define BRANCH_DISP8(cond) \ + GET_1BYTE_ARG; \ + tmp2 = kpc & 0xff0000; \ + kpc += 2; \ + tmp1 = kpc; \ + if(cond) { \ + kpc = kpc + arg - ((arg & 0x80) << 1); \ + CYCLES_PLUS_1; \ + if((tmp1 ^ kpc) & psr & 0x100) { \ + CYCLES_PLUS_1; \ + } \ + } \ + kpc = tmp2 + (kpc & 0xffff); + +#undef STY_INST +#undef STX_INST + +#define STY_INST(in_bank) \ + if(psr & 0x10) { \ + SET_MEMORY8(arg, yreg); \ + } else { \ + SET_MEMORY16(arg, yreg, in_bank);\ + } +#define STX_INST(in_bank) \ + if(psr & 0x10) { \ + SET_MEMORY8(arg, xreg); \ + } else { \ + SET_MEMORY16(arg, xreg, in_bank);\ + } + +#define C_LDX_ABS_Y() \ + GET_ABS_INDEX_ADDR(yreg, 0); \ + LDX_INST(0); + +#define C_LDY_ABS_X() \ + GET_ABS_INDEX_ADDR(xreg, 0); \ + LDY_INST(0); + +#define C_LDX_ABS() \ + GET_ABS_ADDR(); \ + LDX_INST(0); + +#define C_LDY_ABS() \ + GET_ABS_ADDR(); \ + LDY_INST(0); + +#define C_LDX_DLOC() \ + GET_DLOC_ADDR(); \ + LDX_INST(1); + +#define C_LDY_DLOC() \ + GET_DLOC_ADDR(); \ + LDY_INST(1); + +#define C_LDY_DLOC_X() \ + GET_DLOC_X_ADDR(); \ + LDY_INST(1); + +#define C_LDX_DLOC_Y() \ + GET_DLOC_Y_ADDR(); \ + LDX_INST(1); + +#define CP_INDEX_VAL(index_reg) \ + arg = 0x100 - arg + index_reg; \ + if((psr & 0x10) == 0) { \ + arg += 0xff00; \ + SET_NEG_ZERO16(arg & 0xffff); \ + SET_CARRY16(arg); \ + } else { \ + SET_NEG_ZERO8(arg & 0xff);\ + SET_CARRY8(arg); \ + } + +#define CP_INDEX_LOAD(index_reg, in_bank) \ + if((psr & 0x10) != 0) { \ + GET_MEMORY8(arg, arg); \ + } else { \ + GET_MEMORY16(arg, arg, in_bank);\ + } \ + CP_INDEX_VAL(index_reg) + +#define CPX_INST(in_bank) \ + CP_INDEX_LOAD(xreg, in_bank); + +#define CPY_INST(in_bank) \ + CP_INDEX_LOAD(yreg, in_bank); + +#define C_CPX_IMM() \ + INC_KPC_2; \ + if((psr & 0x10) == 0) { \ + GET_2BYTE_ARG; \ + CYCLES_PLUS_1; \ + INC_KPC_1; \ + } else { \ + GET_1BYTE_ARG; \ + } \ + CP_INDEX_VAL(xreg); + +#define C_CPY_IMM() \ + INC_KPC_2; \ + if((psr & 0x10) == 0) { \ + GET_2BYTE_ARG; \ + CYCLES_PLUS_1; \ + INC_KPC_1; \ + } else { \ + GET_1BYTE_ARG; \ + } \ + CP_INDEX_VAL(yreg); + +#define C_CPX_DLOC() \ + GET_DLOC_ADDR(); \ + CPX_INST(1); + +#define C_CPY_DLOC() \ + GET_DLOC_ADDR(); \ + CPY_INST(1); + +#define C_CPX_ABS() \ + GET_ABS_ADDR(); \ + CPX_INST(0); + +#define C_CPY_ABS() \ + GET_ABS_ADDR(); \ + CPY_INST(0); + diff --git a/gsplus/src/dependency b/gsplus/src/dependency new file mode 100644 index 0000000..f0c21f6 --- /dev/null +++ b/gsplus/src/dependency @@ -0,0 +1,31 @@ +adb.o: adb.c defc.h defcomm.h iwm.h protos.h protos_base.h +engine_c.o: engine_c.c defc.h defcomm.h iwm.h protos.h protos_base.h size_c.h op_routs.h engine.h defs_instr.h instable.h +clock.o: clock.c defc.h defcomm.h iwm.h protos.h protos_base.h +compile_time.o: compile_time.c +config.o: config.c defc.h defcomm.h iwm.h protos.h protos_base.h config.h win_dirent.h +debugger.o: debugger.c defc.h defcomm.h iwm.h protos.h protos_base.h disas.h +scc.o: scc.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h +scc_socket_driver.o: scc_socket_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h +scc_windriver.o: scc_windriver.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h +scc_unixdriver.o: scc_unixdriver.c defc.h defcomm.h iwm.h protos.h protos_base.h scc.h +iwm.o: iwm.c defc.h defcomm.h iwm.h protos.h protos_base.h +joystick_driver.o: joystick_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h +moremem.o: moremem.c defc.h defcomm.h iwm.h protos.h protos_base.h +paddles.o: paddles.c defc.h defcomm.h iwm.h protos.h protos_base.h +mockingboard.o: mockingboard.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h +sim65816.o: sim65816.c defc.h defcomm.h iwm.h protos.h protos_base.h +smartport.o: smartport.c defc.h defcomm.h iwm.h protos.h protos_base.h +doc.o: doc.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h +sound.o: sound.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h +sound_driver.o: sound_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h +woz.o: woz.c defc.h defcomm.h iwm.h protos.h protos_base.h +unshk.o: unshk.c defc.h defcomm.h iwm.h protos.h protos_base.h +undeflate.o: undeflate.c defc.h defcomm.h iwm.h protos.h protos_base.h +dynapro.o: dynapro.c defc.h defcomm.h iwm.h protos.h protos_base.h win_dirent.h +dyna_type.o: dyna_type.c defc.h defcomm.h iwm.h protos.h protos_base.h +dyna_filt.o: dyna_filt.c defc.h defcomm.h iwm.h protos.h protos_base.h +dyna_validate.o: dyna_validate.c defc.h defcomm.h iwm.h protos.h protos_base.h +applesingle.o: applesingle.c defc.h defcomm.h iwm.h protos.h protos_base.h +video.o: video.c defc.h defcomm.h iwm.h protos.h protos_base.h kegsfont.h +voc.o: voc.c defc.h defcomm.h iwm.h protos.h protos_base.h +macsnd_driver.o: macsnd_driver.c defc.h defcomm.h iwm.h protos.h protos_base.h sound.h diff --git a/gsplus/src/disas.h b/gsplus/src/disas.h new file mode 100644 index 0000000..b7aaf4a --- /dev/null +++ b/gsplus/src/disas.h @@ -0,0 +1,208 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2020 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +enum { + ABS = 1, + ABSX, + ABSY, + ABSLONG, + ABSIND, + ABSXIND, + IMPLY, + ACCUM, + IMMED, + JUST8, + DLOC, + DLOCX, + DLOCY, + LONG, + LONGX, + DLOCIND, + DLOCINDY, + DLOCXIND, + DLOCBRAK, + DLOCBRAKY, + DISP8, + DISP8S, + DISP8SINDY, + DISP16, + MVPMVN, + REPVAL, + SEPVAL +}; + + +const char * const disas_opcodes[256] = { + "BRK", "ORA", "COP", "ORA", "TSB", "ORA", "ASL", "ORA", /* 00-07 */ + "PHP", "ORA", "ASL", "PHD", "TSB", "ORA", "ASL", "ORA", /* 08-0f */ + "BPL", "ORA", "ORA", "ORA", "TRB", "ORA", "ASL", "ORA", /* 10-17 */ + "CLC", "ORA", "INC", "TCS", "TRB", "ORA", "ASL", "ORA", /* 18-1f */ + "JSR", "AND", "JSL", "AND", "BIT", "AND", "ROL", "AND", /* 20-27 */ + "PLP", "AND", "ROL", "PLD", "BIT", "AND", "ROL", "AND", /* 28-2f */ + "BMI", "AND", "AND", "AND", "BIT", "AND", "ROL", "AND", /* 30-37 */ + "SEC", "AND", "DEC", "TSC", "BIT", "AND", "ROL", "AND", /* 38-3f */ + "RTI", "EOR", "WDM", "EOR", "MVP", "EOR", "LSR", "EOR", /* 40-47 */ + "PHA", "EOR", "LSR", "PHK", "JMP", "EOR", "LSR", "EOR", /* 48-4f */ + "BVC", "EOR", "EOR", "EOR", "MVN", "EOR", "LSR", "EOR", /* 50-57 */ + "CLI", "EOR", "PHY", "TCD", "JMP", "EOR", "LSR", "EOR", /* 58-5f */ + "RTS", "ADC", "PER", "ADC", "STZ", "ADC", "ROR", "ADC", /* 60-67 */ + "PLA", "ADC", "ROR", "RTL", "JMP", "ADC", "ROR", "ADC", /* 68-6f */ + "BVS", "ADC", "ADC", "ADC", "STZ", "ADC", "ROR", "ADC", /* 70-77 */ + "SEI", "ADC", "PLY", "TDC", "JMP", "ADC", "ROR", "ADC", /* 78-7f */ + "BRA", "STA", "BRL", "STA", "STY", "STA", "STX", "STA", /* 80-87 */ + "DEY", "BIT", "TXA", "PHB", "STY", "STA", "STX", "STA", /* 88-8f */ + "BCC", "STA", "STA", "STA", "STY", "STA", "STX", "STA", /* 90-97 */ + "TYA", "STA", "TXS", "TXY", "STZ", "STA", "STZ", "STA", /* 98-9f */ + "LDY", "LDA", "LDX", "LDA", "LDY", "LDA", "LDX", "LDA", /* a0-a7 */ + "TAY", "LDA", "TAX", "PLB", "LDY", "LDA", "LDX", "LDA", /* a8-af */ + "BCS", "LDA", "LDA", "LDA", "LDY", "LDA", "LDX", "LDA", /* b0-b7 */ + "CLV", "LDA", "TSX", "TYX", "LDY", "LDA", "LDX", "LDA", /* b8-bf */ + "CPY", "CMP", "REP", "CMP", "CPY", "CMP", "DEC", "CMP", /* c0-c7 */ + "INY", "CMP", "DEX", "WAI", "CPY", "CMP", "DEC", "CMP", /* c8-cf */ + "BNE", "CMP", "CMP", "CMP", "PEI", "CMP", "DEC", "CMP", /* d0-d7 */ + "CLD", "CMP", "PHX", "STP", "JML", "CMP", "DEC", "CMP", /* d8-df */ + "CPX", "SBC", "SEP", "SBC", "CPX", "SBC", "INC", "SBC", /* e0-e7 */ + "INX", "SBC", "NOP", "XBA", "CPX", "SBC", "INC", "SBC", /* e8-ef */ + "BEQ", "SBC", "SBC", "SBC", "PEA", "SBC", "INC", "SBC", /* f0-f7 */ + "SED", "SBC", "PLX", "XCE", "JSR", "SBC", "INC", "SBC", /* f8-ff */ +}; + + +const word32 disas_types[256] = { + JUST8+0x100, DLOCXIND+0x100, /* 00-01 */ + JUST8+0x100, DISP8S+0x100, /* 02-03 */ + DLOC+0x100, DLOC+0x100, /* 04-05 */ + DLOC+0x100, DLOCBRAK+0x100, /* 06-07 */ + IMPLY+0x000, IMMED+0x400, /* 08-9 */ + ACCUM+0x000, IMPLY+0x000, /* 0a-b */ + ABS+0x200, ABS+0x200, /* c-d */ + ABS+0x200, LONG+0x300, /* e-f */ + DISP8+0x100, DLOCINDY+0x100, /* 10-11 */ + DLOCIND+0x100, DISP8SINDY+0x100, /* 12-13 */ + DLOC+0x100, DLOCX+0x100, /* 14-15 */ + DLOCX+0x100, DLOCBRAKY+0x100, /* 16-17 */ + IMPLY+0x000, ABSY+0x200, /* 18-19 */ + ACCUM+0x000, IMPLY+0x000, /* 1a-1b */ + ABS+0x200, ABSX+0x200, /* 1c-1d */ + ABSX+0x200, LONGX+0x300, /* 1e-1f */ + ABS+0x200, DLOCXIND+0x100, /* 20-21 */ + ABSLONG+0x300, DISP8S+0x100, /* 22-23 */ + DLOC+0x100, DLOC+0x100, /* 24-25 */ + DLOC+0x100, DLOCBRAK+0x100, /* 26-27 */ + IMPLY+0x000, IMMED+0x400, /* 28-29 */ + ACCUM+0x000, IMPLY+0x000, /* 2a-2b */ + ABS+0x200, ABS+0x200, /* 2c-2d */ + ABS+0x200, LONG+0x300, /* 2e-2f */ + DISP8+0x100, DLOCINDY+0x100, /* 30-31 */ + DLOCIND+0x100, DISP8SINDY+0x100, /* 32-33 */ + DLOCX+0x100, DLOCX+0x100, /* 34-35 */ + DLOCX+0x100, DLOCBRAKY+0x100, /* 36-37 */ + IMPLY+0x000, ABSY+0x200, /* 38-39 */ + ACCUM+0x000, IMPLY+0x000, /* 3a-3b */ + ABSX+0x200, ABSX+0x200, /* 3c-3d */ + ABSX+0x200, LONGX+0x300, /* 3e-3f */ + IMPLY+0x000, DLOCXIND+0x100, /* 40-41 */ + JUST8+0x100, DISP8S+0x100, /* 42-43 */ + MVPMVN+0x200, DLOC+0x100, /* 44-45 */ + DLOC+0x100, DLOCBRAK+0x100, /* 46-47 */ + IMPLY+0x000, IMMED+0x400, /* 48-49 */ + ACCUM+0x000, IMPLY+0x000, /* 4a-4b */ + ABS+0x200, ABS+0x200, /* 4c-4d */ + ABS+0x200, LONG+0x300, /* 4e-4f */ + DISP8+0x100, DLOCINDY+0x100, /* 50-51 */ + DLOCIND+0x100, DISP8SINDY+0x100, /* 52-53 */ + MVPMVN+0x200, DLOCX+0x100, /* 54-55 */ + DLOCX+0x100, DLOCBRAKY+0x100, /* 56-57 */ + IMPLY+0x000, ABSY+0x200, /* 58-59 */ + IMPLY+0x000, IMPLY+0x000, /* 5a-5b */ + LONG+0x300, ABSX+0x200, /* 5c-5d */ + ABSX+0x200, LONGX+0x300, /* 5e-5f */ + IMPLY+0x000, DLOCXIND+0x100, /* 60-61 */ + DISP16+0x200, DISP8S+0x100, /* 62-63 */ + DLOC+0x100, DLOC+0x100, /* 64-65 */ + DLOC+0x100, DLOCBRAK+0x100, /* 66-67 */ + IMPLY+0x000, IMMED+0x400, /* 68-69 */ + ACCUM+0x000, IMPLY+0x000, /* 6a-6b */ + ABSIND+0x200, ABS+0x200, /* 6c-6d */ + ABS+0x200, LONG+0x300, /* 6e-6f */ + DISP8+0x100, DLOCINDY+0x100, /* 70-71 */ + DLOCIND+0x100, DISP8SINDY+0x100, /* 72-73 */ + DLOCX+0x100, DLOCX+0x100, /* 74-75 */ + DLOCX+0x100, DLOCBRAKY+0x100, /* 76-77 */ + IMPLY+0x000, ABSY+0x200, /* 78-79 */ + IMPLY+0x000, IMPLY+0x000, /* 7a-7b */ + ABSXIND+0x200, ABSX+0x200, /* 7c-7d */ + ABSX+0x200, LONGX+0x300, /* 7e-7f */ + DISP8+0x100, DLOCXIND+0x100, /* 80-81 */ + DISP16+0x200, DISP8S+0x100, /* 82-83 */ + DLOC+0x100, DLOC+0x100, /* 84-85 */ + DLOC+0x100, DLOCBRAK+0x100, /* 86-87 */ + IMPLY+0x000, IMMED+0x400, /* 88-89 */ + IMPLY+0x000, IMPLY+0x000, /* 8a-8b */ + ABS+0x200, ABS+0x200, /* 8c-8d */ + ABS+0x200, LONG+0x300, /* 8e-8f */ + DISP8+0x100, DLOCINDY+0x100, /* 90-91 */ + DLOCIND+0x100, DISP8SINDY+0x100, /* 92-93 */ + DLOCX+0x100, DLOCX+0x100, /* 94-95 */ + DLOCY+0x100, DLOCBRAKY+0x100, /* 96-97 */ + IMPLY+0x000, ABSY+0x200, /* 98-99 */ + IMPLY+0x000, IMPLY+0x000, /* 9a-9b */ + ABS+0x200, ABSX+0x200, /* 9c-9d */ + ABSX+0x200, LONGX+0x300, /* 9e-9f */ + IMMED+0x500, DLOCXIND+0x100, /* a0-a1 */ + IMMED+0x500, DISP8S+0x100, /* a2-a3 */ + DLOC+0x100, DLOC+0x100, /* a4-a5 */ + DLOC+0x100, DLOCBRAK+0x100, /* a6-a7 */ + IMPLY+0x000, IMMED+0x400, /* a8-a9 */ + IMPLY+0x000, IMPLY+0x000, /* aa-ab */ + ABS+0x200, ABS+0x200, /* ac-ad */ + ABS+0x200, LONG+0x300, /* ae-af */ + DISP8+0x100, DLOCINDY+0x100, /* b0-b1 */ + DLOCIND+0x100, DISP8SINDY+0x100, /* b2-b3 */ + DLOCX+0x100, DLOCX+0x100, /* b4-b5 */ + DLOCY+0x100, DLOCBRAKY+0x100, /* b6-b7 */ + IMPLY+0x000, ABSY+0x200, /* b8-b9 */ + IMPLY+0x000, IMPLY+0x000, /* ba-bb */ + ABSX+0x200, ABSX+0x200, /* bc-bd */ + ABSY+0x200, LONGX+0x300, /* be-bf */ + IMMED+0x500, DLOCXIND+0x100, /* c0-c1 */ + REPVAL+0x100, DISP8S+0x100, /* c2-c3 */ + DLOC+0x100, DLOC+0x100, /* c4-c5 */ + DLOC+0x100, DLOCBRAK+0x100, /* c6-c7 */ + IMPLY+0x000, IMMED+0x400, /* c8-c9 */ + IMPLY+0x000, IMPLY+0x000, /* ca-cb */ + ABS+0x200, ABS+0x200, /* cc-cd */ + ABS+0x200, LONG+0x300, /* ce-cf */ + DISP8+0x100, DLOCINDY+0x100, /* d0-d1 */ + DLOCIND+0x100, DISP8SINDY+0x100, /* d2-d3 */ + DLOC+0x100, DLOCX+0x100, /* d4-d5 */ + DLOCX+0x100, DLOCBRAKY+0x100, /* d6-d7 */ + IMPLY+0x000, ABSY+0x200, /* d8-d9 */ + IMPLY+0x000, IMPLY+0x000, /* da-db */ + ABSIND+0x200, ABSX+0x200, /* dc-dd */ + ABSX+0x200, LONGX+0x300, /* de-df */ + IMMED+0x500, DLOCXIND+0x100, /* e0-e1 */ + SEPVAL+0x100, DISP8S+0x100, /* e2-e3 */ + DLOC+0x100, DLOC+0x100, /* e4-e5 */ + DLOC+0x100, DLOCBRAK+0x100, /* e6-e7 */ + IMPLY+0x000, IMMED+0x400, /* e8-e9 */ + IMPLY+0x000, IMPLY+0x000, /* ea-eb */ + ABS+0x200, ABS+0x200, /* ec-ed */ + ABS+0x200, LONG+0x300, /* ee-ef */ + DISP8+0x100, DLOCINDY+0x100, /* f0-f1 */ + DLOCIND+0x100, DISP8SINDY+0x100, /* f2-f3 */ + IMMED+0x200, DLOCX+0x100, /* f4-f5 */ + DLOCX+0x100, DLOCBRAKY+0x100, /* f6-f7 */ + IMPLY+0x000, ABSY+0x200, /* f8-f9 */ + IMPLY+0x000, IMPLY+0x000, /* fa-fb */ + ABSXIND+0x200, ABSX+0x200, /* fc-fd */ + ABSX+0x200, LONGX+0x300, /* fe-ff */ +}; + diff --git a/gsplus/src/doc.c b/gsplus/src/doc.c new file mode 100644 index 0000000..8375bdb --- /dev/null +++ b/gsplus/src/doc.c @@ -0,0 +1,1090 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// Ensoniq 5503 DOC routines +// The Ensoniq DOC is controlled through the Sound GLU at $C03C-$C03F, +// where $C03E-$C03F are a 16-bit pointer to "what" to access, $C03D is +// the data register, and $C03C is a control register with volume. +// The DOC operates at 894.886KHZ (7M clock divided by 8). It visits each +// oscillator (up to 32) once per clock, plus 2 extra clocks for DOC RAM +// refresh. +// KEGS cheats and pretends the DOC runs at 48KHz, and visits all oscillators +// each "sample". KEGS adjusts the internal accumulators so the right +// frequency is achieved. This allows the sample calculations to be +// greatly simplified, and achieves higher fidelity when all 32 osc are +// enabled (which is generally how most Apple IIgs code works). + +#include "defc.h" +#include "sound.h" + +#define DOC_LOG(a,b,c,d) + +extern int Verbose; +extern int g_use_shmem; +extern word32 g_vbl_count; +extern int g_preferred_rate; + +extern word32 g_c03ef_doc_ptr; + +extern dword64 g_last_vbl_dfcyc; + +byte doc_ram[0x10000 + 16]; + +word32 g_doc_sound_ctl = 0; +word32 g_doc_saved_val = 0; +int g_doc_num_osc_en = 1; +double g_drecip_osc_en_plus_2 = 1.0 / (double)(1 + 2); + +int g_doc_vol = 8; +int g_doc_saved_ctl = 0; +int g_num_osc_interrupting = 0; +Doc_reg g_doc_regs[32]; + +word32 doc_reg_e0 = 0xff; + +extern double g_drecip_audio_rate; +extern double g_dsamps_per_dfcyc; +extern double g_fcyc_per_samp; + +/* local function prototypes */ +void doc_write_ctl_reg(dword64 dfcyc, int osc, int val); + +#define DOC_SCAN_RATE (DCYCS_28_MHZ/32.0) + +#define UPDATE_G_DCYCS_PER_DOC_UPDATE(osc_en) \ + g_drecip_osc_en_plus_2 = 1.0 / (double)(osc_en + 2); + +#define SND_PTR_SHIFT 14 +#define SND_PTR_SHIFT_DBL ((double)(1 << SND_PTR_SHIFT)) + +void +doc_init() +{ + Doc_reg *rptr; + int i; + + for(i = 0; i < 32; i++) { + rptr = &(g_doc_regs[i]); + rptr->dsamp_ev = 0.0; + rptr->dsamp_ev2 = 0.0; + rptr->complete_dsamp = 0.0; + rptr->samps_left = 0; + rptr->cur_acc = 0; + rptr->cur_inc = 0; + rptr->cur_start = 0; + rptr->cur_end = 0; + rptr->cur_mask = 0; + rptr->size_bytes = 0; + rptr->event = 0; + rptr->running = 0; + rptr->has_irq_pending = 0; + rptr->freq = 0; + rptr->vol = 0; + rptr->waveptr = 0; + rptr->ctl = 1; + rptr->wavesize = 0; + rptr->last_samp_val = 0; + } +} +void +doc_reset(dword64 dfcyc) +{ + int i; + + for(i = 0; i < 32; i++) { + doc_write_ctl_reg(dfcyc, i, g_doc_regs[i].ctl | 1); + doc_reg_e0 = 0xff; + if(g_doc_regs[i].has_irq_pending) { + halt_printf("reset: has_irq[%02x] = %d\n", i, + g_doc_regs[i].has_irq_pending); + } + g_doc_regs[i].has_irq_pending = 0; + } + if(g_num_osc_interrupting) { + halt_printf("reset: num_osc_int:%d\n", g_num_osc_interrupting); + } + g_num_osc_interrupting = 0; + + g_doc_num_osc_en = 1; + UPDATE_G_DCYCS_PER_DOC_UPDATE(1); +} + +int +doc_play(dword64 dfcyc, double last_dsamp, double dsamp_now, int num_samps, + int snd_buf_init, int *outptr_start) +{ + Doc_reg *rptr; + int *outptr; + double complete_dsamp, cur_dsamp; + word32 cur_acc, cur_pos, cur_mask, cur_inc, cur_end; + int val, val2, imul, off, ctl, num_osc_en; + int samps_left, samps_to_do, samp_offset, pos, osc, done; + int i, j; + + num_osc_en = g_doc_num_osc_en; + + dbg_log_info(dfcyc, num_samps, 0, 0xd0c0); + + done = 0; + // Do Ensoniq oscillators + while(!done) { + done = 1; + for(j = 0; j < num_osc_en; j++) { + osc = j; + rptr = &(g_doc_regs[osc]); + complete_dsamp = rptr->complete_dsamp; + samps_left = rptr->samps_left; + cur_acc = rptr->cur_acc; + cur_mask = rptr->cur_mask; + cur_inc = rptr->cur_inc; + cur_end = rptr->cur_end; + if(!rptr->running || cur_inc == 0 || + (complete_dsamp >= dsamp_now)) { + continue; + } + + done = 0; + ctl = rptr->ctl; + + samp_offset = 0; + if(complete_dsamp > last_dsamp) { + samp_offset = (int)(complete_dsamp- last_dsamp); + if(samp_offset > num_samps) { + rptr->complete_dsamp = dsamp_now; + continue; + } + } + outptr = outptr_start + 2 * samp_offset; + if(ctl & 0x10) { + /* other channel */ + outptr += 1; + } + + imul = (rptr->vol * g_doc_vol); + off = imul * 128; + + samps_to_do = MY_MIN(samps_left, + num_samps - samp_offset); + if(imul == 0 || samps_to_do == 0) { + /* produce no sound */ + samps_left = samps_left - samps_to_do; + cur_acc += cur_inc * samps_to_do; + rptr->samps_left = samps_left; + rptr->cur_acc = cur_acc; + cur_dsamp = last_dsamp + + (double)(samps_to_do + samp_offset); + DOC_LOG("nosnd", osc, cur_dsamp, samps_to_do); + rptr->complete_dsamp = dsamp_now; + cur_pos = rptr->cur_start+(cur_acc & cur_mask); + if(samps_left <= 0) { + doc_sound_end(osc, 1, cur_dsamp, + dsamp_now); + val = 0; + j--; + } else { + val = doc_ram[cur_pos >> SND_PTR_SHIFT]; + } + rptr->last_samp_val = val; + continue; + } + if(snd_buf_init == 0) { + memset(outptr_start, 0, + 2*sizeof(outptr_start[0])*num_samps); + snd_buf_init++; + } + val = 0; + rptr->complete_dsamp = dsamp_now; + cur_pos = rptr->cur_start + (cur_acc & cur_mask); + pos = 0; + for(i = 0; i < samps_to_do; i++) { + pos = cur_pos >> SND_PTR_SHIFT; + cur_pos += cur_inc; + cur_acc += cur_inc; + val = doc_ram[pos]; + val2 = (val * imul - off) >> 4; + if((val == 0) || (cur_pos >= cur_end)) { + cur_dsamp = last_dsamp + + (double)(samp_offset + i + 1); + rptr->cur_acc = cur_acc; + rptr->samps_left = 0; + DOC_LOG("end or 0", osc, cur_dsamp, + ((pos & 0xffffU) << 16) | + ((i &0xff) << 8) | val); + doc_sound_end(osc, val, cur_dsamp, + dsamp_now); + val = 0; + break; + } + val2 = outptr[0] + val2; + samps_left--; + *outptr = val2; + outptr += 2; + } + rptr->last_samp_val = val; + + if(val != 0) { + rptr->cur_acc = cur_acc; + rptr->samps_left = samps_left; + rptr->complete_dsamp = dsamp_now; + } + DOC_LOG("splayed", osc, dsamp_now, + (samps_to_do << 16) + (pos & 0xffff)); + } + } + + return snd_buf_init; +} +void +doc_handle_event(int osc, dword64 dfcyc) +{ + /* handle osc stopping and maybe interrupting */ + + //DOC_LOG("doc_ev", osc, dsamps, 0); + + g_doc_regs[osc].event = 0; + + sound_play(dfcyc); +} + +void +doc_sound_end(int osc, int can_repeat, double eff_dsamps, double dsamps) +{ + Doc_reg *rptr, *orptr; + int mode, omode; + int other_osc; + int one_shot_stop; + int ctl; + + /* handle osc stopping and maybe interrupting */ + + if(osc < 0 || osc > 31) { + printf("doc_handle_event: osc: %d!\n", osc); + return; + } + + rptr = &(g_doc_regs[osc]); + ctl = rptr->ctl; + + if(rptr->event) { + remove_event_doc(osc); + } + rptr->event = 0; + rptr->cur_acc = 0; /* reset internal accumulator*/ + + /* check to make sure osc is running */ + if(ctl & 0x01) { + /* Oscillator already stopped. */ + halt_printf("Osc %d interrupt, but it was already stop!\n",osc); +#ifdef HPUX + U_STACK_TRACE(); +#endif + return; + } + + if(ctl & 0x08) { + if(rptr->has_irq_pending == 0) { + doc_add_sound_irq(osc); + } + } + + if(!rptr->running) { + halt_printf("Doc event for osc %d, but ! running\n", osc); + } + + rptr->running = 0; + + mode = (ctl >> 1) & 3; + other_osc = osc ^ 1; + orptr = &(g_doc_regs[other_osc]); + omode = (orptr->ctl >> 1) & 3; + + /* If either this osc or it's partner is in swap mode, treat the */ + /* pair as being in swap mode. This Ensoniq feature pointed out */ + /* by Ian Schmidt */ + if(mode == 0 && can_repeat) { + /* free-running mode with no 0 byte! */ + /* start doing it again */ + + doc_start_sound(osc, eff_dsamps, dsamps); + + return; + } else if((mode == 3) || (omode == 3)) { + /* swap mode (even if we're one_shot and partner is swap)! */ + /* unless we're one-shot and we hit a 0-byte--then */ + /* Olivier Goguel says just stop */ + rptr->ctl |= 1; + one_shot_stop = (mode == 1) && (!can_repeat); + if(!one_shot_stop && !orptr->running && + (orptr->ctl & 0x1)) { + orptr->ctl = orptr->ctl & (~1); + doc_start_sound(other_osc, eff_dsamps, dsamps); + } + return; + } else { + /* stop the oscillator */ + rptr->ctl |= 1; + } + + return; +} + +void +doc_add_sound_irq(int osc) +{ + int num_osc_interrupting; + + if(g_doc_regs[osc].has_irq_pending) { + halt_printf("Adding sound_irq for %02x, but irq_p: %d\n", osc, + g_doc_regs[osc].has_irq_pending); + } + + num_osc_interrupting = g_num_osc_interrupting + 1; + g_doc_regs[osc].has_irq_pending = num_osc_interrupting; + g_num_osc_interrupting = num_osc_interrupting; + + add_irq(IRQ_PENDING_DOC); + if(num_osc_interrupting == 1) { + doc_reg_e0 = 0x00 + (osc << 1); + } + + DOC_LOG("add_irq", osc, g_cur_dfcyc * g_dsamps_per_dfcyc, 0); +} + +void +doc_remove_sound_irq(int osc, int must) +{ + Doc_reg *rptr; + int num_osc_interrupt; + int has_irq_pending; + int first; + int i; + + doc_printf("remove irq for osc: %d, has_irq: %d\n", + osc, g_doc_regs[osc].has_irq_pending); + + num_osc_interrupt = g_doc_regs[osc].has_irq_pending; + first = 0; + if(num_osc_interrupt) { + g_num_osc_interrupting--; + g_doc_regs[osc].has_irq_pending = 0; + DOC_LOG("rem_irq", osc, g_cur_dfcyc * g_dsamps_per_dfcyc, 0); + if(g_num_osc_interrupting == 0) { + remove_irq(IRQ_PENDING_DOC); + } + + first = 0x40 | (doc_reg_e0 >> 1); + /* if none found, then def = no ints */ + for(i = 0; i < g_doc_num_osc_en; i++) { + rptr = &(g_doc_regs[i]); + has_irq_pending = rptr->has_irq_pending; + if(has_irq_pending > num_osc_interrupt) { + has_irq_pending--; + rptr->has_irq_pending = has_irq_pending; + } + if(has_irq_pending == 1) { + first = i; + } + } + if(num_osc_interrupt == 1) { + doc_reg_e0 = (first << 1); + } else { +#if 0 + halt_printf("remove_sound_irq[%02x]=%d, first:%d\n", + osc, num_osc_interrupt, first); +#endif + } + } else { +#if 0 + /* make sure no int pending */ + if(doc_reg_e0 != 0xff) { + halt_printf("remove_sound_irq[%02x]=0, but e0: %02x\n", + osc, doc_reg_e0); + } +#endif + if(must) { + halt_printf("REMOVE_sound_irq[%02x]=0, but e0: %02x\n", + osc, doc_reg_e0); + } + } + + if(doc_reg_e0 & 0x80) { + for(i = 0; i < 0x20; i++) { + has_irq_pending = g_doc_regs[i].has_irq_pending; + if(has_irq_pending) { + halt_printf("remove_sound_irq[%02x], but " + "[%02x]=%d!\n", osc,i,has_irq_pending); + printf("num_osc_int: %d, first: %02x\n", + num_osc_interrupt, first); + } + } + } +} + +void +doc_start_sound2(int osc, dword64 dfcyc) +{ + double dsamps; + + dsamps = dfcyc * g_dsamps_per_dfcyc; + doc_start_sound(osc, dsamps, dsamps); +} + +void +doc_start_sound(int osc, double eff_dsamps, double dsamps) +{ + Doc_reg *rptr; + int ctl; + int mode; + word32 sz; + word32 size; + word32 wave_size; + + if(osc < 0 || osc > 31) { + halt_printf("start_sound: osc: %02x!\n", osc); + } + + rptr = &(g_doc_regs[osc]); + + if(osc >= g_doc_num_osc_en) { + rptr->ctl |= 1; + return; + } + + ctl = rptr->ctl; + mode = (ctl >> 1) & 3; + wave_size = rptr->wavesize; + sz = ((wave_size >> 3) & 7) + 8; + size = 1 << sz; + + if(size < 0x100) { + halt_printf("size: %08x is too small, sz: %08x!\n", size, sz); + } + + if(rptr->running) { + halt_printf("start_sound osc: %d, already running!\n", osc); + } + + rptr->running = 1; + + rptr->complete_dsamp = eff_dsamps; + + doc_printf("Starting osc %02x, dsamp: %f\n", osc, dsamps); + doc_printf("size: %04x\n", size); + + if((mode == 2) && ((osc & 1) == 0)) { + printf("Sync mode osc %d starting!\n", osc); + /* set_halt(1); */ + + /* see if we should start our odd partner */ + if((rptr[1].ctl & 7) == 5) { + /* odd partner stopped in sync mode--start him */ + rptr[1].ctl &= (~1); + doc_start_sound(osc + 1, eff_dsamps, dsamps); + } else { + printf("Osc %d starting sync, but osc %d ctl: %02x\n", + osc, osc+1, rptr[1].ctl); + } + } + + doc_wave_end_estimate(osc, eff_dsamps, dsamps); + + DOC_LOG("st playing", osc, eff_dsamps, size); +#if 0 + if(rptr->cur_acc != 0) { + halt_printf("Start osc %02x, acc: %08x\n", osc, rptr->cur_acc); + } +#endif +} + +void +doc_wave_end_estimate2(int osc, dword64 dfcyc) +{ + double dsamps; + + dsamps = dfcyc * g_dsamps_per_dfcyc; + doc_wave_end_estimate(osc, dsamps, dsamps); +} + +void +doc_wave_end_estimate(int osc, double eff_dsamps, double dsamps) +{ + Doc_reg *rptr; + byte *ptr1; + dword64 event_dfcyc; + double event_dsamp, dfcycs_per_samp, dsamps_per_byte, num_dsamps; + double dcur_inc; + word32 tmp1, cur_inc, save_val; + int save_size, pos, size, estimate; + + dfcycs_per_samp = g_fcyc_per_samp; + + rptr = &(g_doc_regs[osc]); + + cur_inc = rptr->cur_inc; + dcur_inc = (double)cur_inc; + dsamps_per_byte = 0.0; + if(cur_inc) { + dsamps_per_byte = SND_PTR_SHIFT_DBL / (double)dcur_inc; + } + + /* see if there's a zero byte */ + tmp1 = rptr->cur_start + (rptr->cur_acc & rptr->cur_mask); + pos = tmp1 >> SND_PTR_SHIFT; + size = ((rptr->cur_end) >> SND_PTR_SHIFT) - pos; + + ptr1 = &doc_ram[pos]; + + estimate = 0; + if(rptr->ctl & 0x08 || g_doc_regs[osc ^ 1].ctl & 0x08) { + estimate = 1; + } + +#if 0 + estimate = 1; +#endif + if(estimate) { + save_size = size; + save_val = ptr1[size]; + ptr1[size] = 0; + size = (int)strlen((char *)ptr1); + ptr1[save_size] = save_val; + } + + /* calc samples to play */ + num_dsamps = (dsamps_per_byte * (double)size) + 1.0; + + rptr->samps_left = (int)num_dsamps; + + if(rptr->event) { + remove_event_doc(osc); + } + rptr->event = 0; + + event_dsamp = eff_dsamps + num_dsamps; + if(estimate) { + rptr->event = 1; + rptr->dsamp_ev = event_dsamp; + rptr->dsamp_ev2 = dsamps; + event_dfcyc = (dword64)(event_dsamp * dfcycs_per_samp) + + 65536LL; + add_event_doc(event_dfcyc, osc); + } +} + +void +doc_remove_sound_event(int osc) +{ + if(g_doc_regs[osc].event) { + g_doc_regs[osc].event = 0; + remove_event_doc(osc); + } +} + + +void +doc_write_ctl_reg(dword64 dfcyc, int osc, int val) +{ + Doc_reg *rptr; + word32 old_halt, new_halt; + int old_val; + + if(osc < 0 || osc >= 0x20) { + halt_printf("doc_write_ctl_reg: osc: %02x, val: %02x\n", + osc, val); + return; + } + + rptr = &(g_doc_regs[osc]); + old_val = rptr->ctl; + g_doc_saved_ctl = old_val; + + if(old_val == val) { + return; + } + + //DOC_LOG("ctl_reg", osc, dsamps, (old_val << 16) + val); + + old_halt = (old_val & 1); + new_halt = (val & 1); + + /* bits are: 28: old int bit */ + /* 29: old halt bit */ + /* 30: new int bit */ + /* 31: new halt bit */ + +#if 0 + if(osc == 0x10) { + printf("osc %d new_ctl: %02x, old: %02x\n", osc, val, old_val); + } +#endif + + /* no matter what, remove any pending IRQs on this osc */ + doc_remove_sound_irq(osc, 0); + +#if 0 + if(old_halt) { + printf("doc_write_ctl to osc %d, val: %02x, old: %02x\n", + osc, val, old_val); + } +#endif + + if(new_halt != 0) { + /* make sure sound is stopped */ + doc_remove_sound_event(osc); + if(old_halt == 0) { + /* it was playing, finish it up */ +#if 0 + halt_printf("Aborted osc %d at eff_dsamps: %f, ctl: " + "%02x, oldctl: %02x\n", osc, eff_dsamps, + val, old_val); +#endif + sound_play(dfcyc); + } + if(((old_val >> 1) & 3) > 0) { + /* don't clear acc if free-running */ + g_doc_regs[osc].cur_acc = 0; + } + + g_doc_regs[osc].ctl = val; + g_doc_regs[osc].running = 0; + } else { + /* new halt == 0 = make sure sound is running */ + if(old_halt != 0) { + /* start sound */ + //DOC_LOG("ctl_sound_play", osc, eff_dsamps, val); + sound_play(dfcyc); + g_doc_regs[osc].ctl = val; + + doc_start_sound2(osc, dfcyc); + } else { + /* was running, and something changed */ + doc_printf("osc %d old ctl:%02x new:%02x!\n", + osc, old_val, val); + sound_play(dfcyc); + g_doc_regs[osc].ctl = val; + if((old_val ^ val) & val & 0x8) { + /* now has ints on */ + doc_wave_end_estimate2(osc, dfcyc); + } + } + } +} + +void +doc_recalc_sound_parms(dword64 dfcyc, int osc) +{ + Doc_reg *rptr; + double dfreq, dtmp1, dacc, dacc_recip; + word32 res, sz, size, wave_size, cur_start, shifted_size; + + rptr = &(g_doc_regs[osc]); + + wave_size = rptr->wavesize; + + dfreq = (double)rptr->freq; + + sz = ((wave_size >> 3) & 7) + 8; + size = 1 << sz; + rptr->size_bytes = size; + res = wave_size & 7; + + shifted_size = size << SND_PTR_SHIFT; + cur_start = (rptr->waveptr << (8 + SND_PTR_SHIFT)) & (0-shifted_size); + + dtmp1 = dfreq * (DOC_SCAN_RATE * g_drecip_audio_rate); + dacc = (double)(1 << (20 - (17 - sz + res))); + dacc_recip = (SND_PTR_SHIFT_DBL) / ((double)(1 << 20)); + dtmp1 = dtmp1 * g_drecip_osc_en_plus_2 * dacc * dacc_recip; + + rptr->cur_inc = (int)(dtmp1); + rptr->cur_start = cur_start; + rptr->cur_end = cur_start + shifted_size; + rptr->cur_mask = (shifted_size - 1); + + dbg_log_info(dfcyc, (rptr->waveptr << 16) + wave_size, osc, 0xd0cf); +} + +int +doc_read_c03c() +{ + return g_doc_sound_ctl; +} + +int +doc_read_c03d(dword64 dfcyc) +{ + Doc_reg *rptr; + int osc, type, ret; + + ret = g_doc_saved_val; + + if(g_doc_sound_ctl & 0x40) { + /* Read RAM */ + g_doc_saved_val = doc_ram[g_c03ef_doc_ptr]; + } else { + /* Read DOC */ + g_doc_saved_val = 0; + + osc = g_c03ef_doc_ptr & 0x1f; + type = (g_c03ef_doc_ptr >> 5) & 0x7; + rptr = &(g_doc_regs[osc]); + + switch(type) { + case 0x0: /* freq lo */ + g_doc_saved_val = rptr->freq & 0xff; + break; + case 0x1: /* freq hi */ + g_doc_saved_val = rptr->freq >> 8; + break; + case 0x2: /* vol */ + g_doc_saved_val = rptr->vol; + break; + case 0x3: /* data register */ + sound_play(dfcyc); // Fix for Paperboy GS + g_doc_saved_val = rptr->last_samp_val; + break; + case 0x4: /* wave ptr register */ + g_doc_saved_val = rptr->waveptr; + break; + case 0x5: /* control register */ + g_doc_saved_val = rptr->ctl; + break; + case 0x6: /* control register */ + g_doc_saved_val = rptr->wavesize; + break; + case 0x7: /* 0xe0-0xff */ + switch(osc) { + case 0x00: /* 0xe0 */ + g_doc_saved_val = doc_reg_e0; + doc_printf("Reading doc 0xe0, ret: %02x\n", + g_doc_saved_val); + + /* Clear IRQ on read of e0, if any irq pend */ + if((doc_reg_e0 & 0x80) == 0) { + doc_remove_sound_irq(doc_reg_e0 >> 1, + 1); + } + break; + case 0x01: /* 0xe1 */ + g_doc_saved_val = (g_doc_num_osc_en - 1) << 1; + break; + case 0x02: /* 0xe2 */ + g_doc_saved_val = 0x80; +#if 0 + halt_printf("Reading doc 0xe2, ret: %02x\n", + g_doc_saved_val); +#endif + break; + default: + g_doc_saved_val = 0; + halt_printf("Reading bad doc_reg[%04x]: %02x\n", + g_c03ef_doc_ptr, g_doc_saved_val); + } + break; + default: + g_doc_saved_val = 0; + halt_printf("Reading bad doc_reg[%04x]: %02x\n", + g_c03ef_doc_ptr, g_doc_saved_val); + } + } + + doc_printf("read c03d, doc_ptr: %04x, ret: %02x, saved: %02x\n", + g_c03ef_doc_ptr, ret, g_doc_saved_val); + + //DOC_LOG("read c03d", -1, dsamps, (g_c03ef_doc_ptr << 16) + + // (g_doc_saved_val << 8) + ret); + + if(g_doc_sound_ctl & 0x20) { + g_c03ef_doc_ptr = (g_c03ef_doc_ptr + 1) & 0xffff; + } + + + return ret; +} + +void +doc_write_c03c(dword64 dfcyc, word32 val) +{ + int vol; + + vol = val & 0xf; + dbg_log_info(dfcyc, val, g_doc_vol, 0xc03c); + if(g_doc_vol != vol) { + sound_play(dfcyc); + + g_doc_vol = vol; + doc_printf("Setting doc vol to 0x%x at %016llx\n", vol, dfcyc); + } + + g_doc_sound_ctl = val; +} + +void +doc_write_c03d(dword64 dfcyc, word32 val) +{ + Doc_reg *rptr; + int osc, type, ctl, tmp; + int i; + + val = val & 0xff; + + doc_printf("write c03d, doc_ptr: %04x, val: %02x\n", + g_c03ef_doc_ptr, val); + + dbg_log_info(dfcyc, g_c03ef_doc_ptr, val, 0xc03d); + + if(g_doc_sound_ctl & 0x40) { + /* RAM */ + doc_ram[g_c03ef_doc_ptr] = val; + } else { + /* DOC */ + osc = g_c03ef_doc_ptr & 0x1f; + type = (g_c03ef_doc_ptr >> 5) & 0x7; + rptr = &(g_doc_regs[osc]); + ctl = rptr->ctl; +#if 0 + if((ctl & 1) == 0) { + if(type < 2 || type == 4 || type == 6) { + halt_printf("Osc %d is running, old ctl: %02x, " + "but write reg %02x=%02x\n", + osc, ctl, g_c03ef_doc_ptr & 0xff, val); + } + } +#endif + + switch(type) { + case 0x0: /* freq lo */ + if((rptr->freq & 0xff) == (word32)val) { + break; + } + if((ctl & 1) == 0) { + /* play through current status */ + //DOC_LOG("flo_sound_play", osc, dsamps, val); + sound_play(dfcyc); + } + rptr->freq = (rptr->freq & 0xff00) + val; + doc_recalc_sound_parms(dfcyc, osc); + break; + case 0x1: /* freq hi */ + if((rptr->freq >> 8) == (word32)val) { + break; + } + if((ctl & 1) == 0) { + /* play through current status */ + //DOC_LOG("fhi_sound_play", osc, dsamps, val); + sound_play(dfcyc); + } + rptr->freq = (rptr->freq & 0xff) + (val << 8); + doc_recalc_sound_parms(dfcyc, osc); + break; + case 0x2: /* vol */ + if(rptr->vol == (word32)val) { + break; + } + if((ctl & 1) == 0) { + /* play through current status */ + //DOC_LOG("vol_sound_play", osc, dsamps, val); + sound_play(dfcyc); + } + rptr->vol = val; + break; + case 0x3: /* data register */ +#if 0 + printf("Writing %02x into doc_data_reg[%02x]!\n", + val, osc); +#endif + break; + case 0x4: /* wave ptr register */ + if(rptr->waveptr == (word32)val) { + break; + } + if((ctl & 1) == 0) { + /* play through current status */ + //DOC_LOG("wptr_sound_play", osc, dsamps, val); + sound_play(dfcyc); + } + rptr->waveptr = val; + doc_recalc_sound_parms(dfcyc, osc); + break; + case 0x5: /* control register */ +#if 0 + printf("doc_write ctl osc %d, val: %02x\n", osc, val); +#endif + if(rptr->ctl == (word32)val) { + break; + } + doc_write_ctl_reg(dfcyc, osc, val); + break; + case 0x6: /* wavesize register */ + if(rptr->wavesize == (word32)val) { + break; + } + if((ctl & 1) == 0) { + /* play through current status */ + //DOC_LOG("wsz_sound_play", osc, dsamps, val); + sound_play(dfcyc); + } + rptr->wavesize = val; + doc_recalc_sound_parms(dfcyc, osc); + break; + case 0x7: /* 0xe0-0xff */ + switch(osc) { + case 0x00: /* 0xe0 */ + doc_printf("writing doc 0xe0 with %02x, " + "was:%02x\n", val, doc_reg_e0); +#if 0 + if(val != doc_reg_e0) { + halt_printf("writing doc 0xe0 with " + "%02x, was:%02x\n", val, + doc_reg_e0); + } +#endif + break; + case 0x01: /* 0xe1 */ + doc_printf("Writing doc 0xe1 with %02x\n", val); + tmp = val & 0x3e; + tmp = (tmp >> 1) + 1; + if(tmp < 1) { + tmp = 1; + } + if(tmp > 32) { + halt_printf("doc 0xe1: %02x!\n", val); + tmp = 32; + } + g_doc_num_osc_en = tmp; + UPDATE_G_DCYCS_PER_DOC_UPDATE(tmp); + + /* Stop any oscs that were running but now */ + /* are disabled */ + for(i = g_doc_num_osc_en; i < 0x20; i++) { + doc_write_ctl_reg(dfcyc, i, + g_doc_regs[i].ctl | 1); + } + + break; + default: + /* this should be illegal, but Turkey Shoot */ + /* and apparently TaskForce, OOTW, etc */ + /* writes to e2-ff, for no apparent reason */ + doc_printf("Writing doc 0x%x with %02x\n", + g_c03ef_doc_ptr, val); + break; + } + break; + default: + halt_printf("Writing %02x into bad doc_reg[%04x]\n", + val, g_c03ef_doc_ptr); + } + } + + if(g_doc_sound_ctl & 0x20) { + g_c03ef_doc_ptr = (g_c03ef_doc_ptr + 1) & 0xffff; + } + + g_doc_saved_val = val; +} + +void +doc_show_ensoniq_state() +{ + Doc_reg *rptr; + int i; + + printf("Ensoniq state\n"); + printf("c03c doc_sound_ctl: %02x, doc_saved_val: %02x\n", + g_doc_sound_ctl, g_doc_saved_val); + printf("doc_ptr: %04x, num_osc_en: %02x, e0: %02x\n", + g_c03ef_doc_ptr, g_doc_num_osc_en, doc_reg_e0); + + for(i = 0; i < 32; i += 8) { + printf("irqp: %02x: %04x %04x %04x %04x %04x %04x %04x %04x\n", + i, + g_doc_regs[i].has_irq_pending, + g_doc_regs[i + 1].has_irq_pending, + g_doc_regs[i + 2].has_irq_pending, + g_doc_regs[i + 3].has_irq_pending, + g_doc_regs[i + 4].has_irq_pending, + g_doc_regs[i + 5].has_irq_pending, + g_doc_regs[i + 6].has_irq_pending, + g_doc_regs[i + 7].has_irq_pending); + } + + for(i = 0; i < 32; i += 8) { + printf("freq: %02x: %04x %04x %04x %04x %04x %04x %04x %04x\n", + i, + g_doc_regs[i].freq, g_doc_regs[i + 1].freq, + g_doc_regs[i + 2].freq, g_doc_regs[i + 3].freq, + g_doc_regs[i + 4].freq, g_doc_regs[i + 5].freq, + g_doc_regs[i + 6].freq, g_doc_regs[i + 7].freq); + } + + for(i = 0; i < 32; i += 8) { + printf("vol: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", + i, + g_doc_regs[i].vol, g_doc_regs[i + 1].vol, + g_doc_regs[i + 2].vol, g_doc_regs[i + 3].vol, + g_doc_regs[i + 4].vol, g_doc_regs[i + 5].vol, + g_doc_regs[i + 6].vol, g_doc_regs[i + 6].vol); + } + + for(i = 0; i < 32; i += 8) { + printf("wptr: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", + i, + g_doc_regs[i].waveptr, g_doc_regs[i + 1].waveptr, + g_doc_regs[i + 2].waveptr, g_doc_regs[i + 3].waveptr, + g_doc_regs[i + 4].waveptr, g_doc_regs[i + 5].waveptr, + g_doc_regs[i + 6].waveptr, g_doc_regs[i + 7].waveptr); + } + + for(i = 0; i < 32; i += 8) { + printf("ctl: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", + i, + g_doc_regs[i].ctl, g_doc_regs[i + 1].ctl, + g_doc_regs[i + 2].ctl, g_doc_regs[i + 3].ctl, + g_doc_regs[i + 4].ctl, g_doc_regs[i + 5].ctl, + g_doc_regs[i + 6].ctl, g_doc_regs[i + 7].ctl); + } + + for(i = 0; i < 32; i += 8) { + printf("wsize: %02x: %02x %02x %02x %02x %02x %02x %02x %02x\n", + i, + g_doc_regs[i].wavesize, g_doc_regs[i + 1].wavesize, + g_doc_regs[i + 2].wavesize, g_doc_regs[i + 3].wavesize, + g_doc_regs[i + 4].wavesize, g_doc_regs[i + 5].wavesize, + g_doc_regs[i + 6].wavesize, g_doc_regs[i + 7].wavesize); + } + + for(i = 0; i < 32; i++) { + rptr = &(g_doc_regs[i]); + printf("%2d: ctl:%02x wp:%02x ws:%02x freq:%04x vol:%02x " + "ev:%d run:%d irq:%d sz:%04x\n", i, + rptr->ctl, rptr->waveptr, rptr->wavesize, rptr->freq, + rptr->vol, rptr->event, rptr->running, + rptr->has_irq_pending, rptr->size_bytes); + printf(" acc:%08x inc:%08x st:%08x end:%08x m:%08x\n", + rptr->cur_acc, rptr->cur_inc, rptr->cur_start, + rptr->cur_end, rptr->cur_mask); + printf(" compl_ds:%f samps_left:%d ev:%f ev2:%f\n", + rptr->complete_dsamp, rptr->samps_left, + rptr->dsamp_ev, rptr->dsamp_ev2); + } + +#if 0 + for(osc = 0; osc < 32; osc++) { + fmax = 0.0; + printf("osc %d has %d samps\n", osc, g_fsamp_num[osc]); + for(i = 0; i < g_fsamp_num[osc]; i++) { + printf("%4d: %f\n", i, g_fsamps[osc][i]); + fmax = MY_MAX(fmax, g_fsamps[osc][i]); + } + printf("osc %d, fmax: %f\n", osc, fmax); + } +#endif +} diff --git a/gsplus/src/dyna_filt.c b/gsplus/src/dyna_filt.c new file mode 100644 index 0000000..1044392 --- /dev/null +++ b/gsplus/src/dyna_filt.c @@ -0,0 +1,16 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2021 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include "defc.h" + +// Provide filters for Dynapro to use for copying files from the host to +// a ProDOS volume, and for writing changes to the ProDOS volume back to +// host files. + diff --git a/gsplus/src/dyna_type.c b/gsplus/src/dyna_type.c new file mode 100644 index 0000000..3328d5b --- /dev/null +++ b/gsplus/src/dyna_type.c @@ -0,0 +1,329 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2021-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include "defc.h" + +// Provide routines for Dynapro to use for detecting the file type on the +// host system. Host files can be "basic1.bas", "basic2,tbas,a$801" + +STRUCT(Dynatype_extensions) { + char str[16]; + word16 file_type; + word16 aux_type; +}; + +Dynatype_extensions g_dynatype_extensions[] = { +{ "applesingle", 0xfff, 0xffff }, +{ "txt", 0x04, 0 }, +{ "c", 0x04, 0 }, // ,ttxt +{ "s", 0x04, 0 }, // ,ttxt +{ "h", 0x04, 0 }, // ,ttxt +{ "bin", 0x06, 0x2000 }, // ,tbin +{ "bas", 0xfc, 0x0801 }, // ,tbas +{ "system", 0xff, 0x2000 }, // ,tsys +//{ "shr", 0xc0, 0x0002 }, // ,t$c0 +{ "shk", 0xe0, 0x8002 }, // ,t$e0 +{ "sdk", 0xe0, 0x8002 }, // ,t$e0 +{ "", 0, 0 } +}; + +STRUCT(Dynatype_types) { + char str[16]; + word16 file_type; + word16 aux_type; +}; + +Dynatype_types g_dynatype_types[] = { +{ "non", 0x00, 0 }, +{ "bad", 0x01, 0 }, +{ "txt", 0x04, 0 }, +{ "bin", 0x06, 0x2000 }, +{ "pnt", 0xc0, 0x0002 }, +{ "fnd", 0xc9, 0 }, +{ "icn", 0xca, 0 }, +{ "cmd", 0xf0, 0 }, +{ "bas", 0xfc, 0x0801 }, +{ "sys", 0xff, 0x2000 }, +{ "", 0, 0 } +}; + +word32 +dynatype_scan_extensions(const char *str) +{ + int len; + int i; + + len = (int)(sizeof(g_dynatype_extensions) / + sizeof(g_dynatype_extensions[0])); + for(i = 0; i < len; i++) { + if(cfgcasecmp(str, g_dynatype_extensions[i].str) == 0) { + return (g_dynatype_extensions[i].file_type << 16) | + g_dynatype_extensions[i].aux_type | + 0x1000000; + } + } + + return 0; +} + +word32 +dynatype_find_prodos_type(const char *str) +{ + word32 file_type; + int len; + int i; + + len = (int)(sizeof(g_dynatype_types) / sizeof(g_dynatype_types[0])); + for(i = 0; i < len; i++) { + if(cfgcasecmp(str, g_dynatype_types[i].str) == 0) { + file_type = g_dynatype_types[i].file_type; + return (file_type << 16) | g_dynatype_types[i].aux_type; + } + } + + return 0; +} + +const char * +dynatype_find_file_type(word32 file_type) +{ + int len; + int i; + + len = (int)(sizeof(g_dynatype_types) / sizeof(g_dynatype_types[0])); + for(i = 0; i < len; i++) { + if(g_dynatype_types[i].file_type == file_type) { + return g_dynatype_types[i].str; + } + } + + return 0; +} + +word32 +dynatype_detect_file_type(Dynapro_file *fileptr, const char *path_ptr, + word32 storage_type) +{ + char ext_buf[32]; + const char *str; + char *endstr; + word32 file_type, aux_type, type_or_aux; + int len, this_len, c, pos; + + // Look for ,tbas and ,a$2000 to get filetype and aux_type info + + str = cfg_str_basename(path_ptr); + len = (int)strlen(str); + + // Look for .ext and ,tbas, etc. + pos = 0; + ext_buf[0] = 0; + file_type = 0x06; // Default to BIN + aux_type = 0; + while(pos < len) { + c = str[pos++]; + if(c == '.') { + this_len = dynatype_get_extension(&str[pos], + &ext_buf[0], 30); + pos += this_len; + continue; + } else if(c == ',') { + this_len = dynatype_comma_arg(&str[pos], &type_or_aux); + if(type_or_aux & 0x1000000) { + file_type = type_or_aux; + } else if(type_or_aux & 0x2000000) { + aux_type = type_or_aux; + } else { + printf("Unknown , extension, %s ignored\n", + &str[pos]); + } + pos += this_len; + continue; + } else if(c == '#') { + // Cadius style encoding: #ff2000 is type=$ff, aux=$2000 + type_or_aux = strtol(&str[pos], &endstr, 16); + file_type = (type_or_aux & 0xffffff) | 0x1000000; + aux_type = 0; + pos += (int)(endstr - str); + continue; + } + } + + // Handle extensions and type. First do extension mapping + if(ext_buf[0]) { + type_or_aux = dynatype_scan_extensions(&ext_buf[0]); + if((type_or_aux) >= 0x0f000000UL) { + // AppleSingle + storage_type = 0x50; // Forked file + } + if(file_type < 0x1000000) { + file_type = type_or_aux; + } + if(aux_type < 0x1000000) { + aux_type = type_or_aux; + } + } +#if 0 + printf("After parsing ext, file_type:%08x, aux_type:%08x\n", + file_type, aux_type); +#endif + + fileptr->file_type = (file_type >> 16) & 0xff; + if(aux_type == 0) { + aux_type = file_type & 0xffff; + } + fileptr->aux_type = aux_type & 0xffff; + + return storage_type; +} + +int +dynatype_get_extension(const char *str, char *out_ptr, int buf_len) +{ + int c, len; + + // Will write up to buf_len chars to out_ptr + if(buf_len < 1) { + return 0; + } + buf_len--; + len = 0; + while(1) { + c = *str++; + *out_ptr = c; + if((c == 0) || (c == '.') || (c == ',') || (c == '#') || + (len >= buf_len)) { + *out_ptr = 0; + return len; + } + out_ptr++; + len++; + } +} + +int +dynatype_comma_arg(const char *str, word32 *type_or_aux_ptr) +{ + char type_buf[8]; + char *endstr; + word32 val, type_or_aux; + int c, len, base, this_len; + int i; + + // Read next char + *type_or_aux_ptr = 0; + + c = *str++; + if(c == 0) { + return 0; + } + len = 1; + c = tolower(c); + type_or_aux = c; + // See if next char is $ for hex + c = *str; + base = 0; + if(c == '$') { + base = 16; + str++; + len++; + } + val = strtol(str, &endstr, base); + this_len = (int)(endstr - str); + if((val == 0) && (this_len < 2) && (base == 0) && + (type_or_aux == 't')) { + // Not a valid number + for(i = 0; i < 3; i++) { + c = *str++; + if(c == 0) { + return len; + } + type_buf[i] = c; + len++; + } + type_buf[3] = 0; + val = dynatype_find_prodos_type(&type_buf[0]); + *type_or_aux_ptr = 0x1000000 | val; + } else { + len += this_len; + } + if(type_or_aux == 't') { + if(val < 0x100) { + *type_or_aux_ptr = 0x1000000 | ((val << 16) & 0xffffff); + } + } else if(type_or_aux == 'a') { + *type_or_aux_ptr = 0x2000000 | (val & 0xffff); + } + + return len; +} + +void +dynatype_fix_unix_name(Dynapro_file *fileptr, char *outbuf_ptr, int path_max) +{ + char buf[16]; + Dynapro_file tmpfile; + const char *str; + word32 aux_type; + +#if 0 + printf("Looking at %s ftype:%02x aux:%04x\n", outbuf_ptr, + fileptr->file_type, fileptr->aux_type); +#endif + + if(fileptr->prodos_name[0] >= 0xd0) { + return; // Directory, or Dir/Volume Header + } + if((fileptr->prodos_name[0] & 0xf0) == 0x50) { + // Forked file, add .applesingle + cfg_strlcat(outbuf_ptr, ".applesingle", path_max); + return; + } + + memset(&tmpfile, 0, sizeof(Dynapro_file)); + + // See what this file defaults to as to type/aux + (void)dynatype_detect_file_type(&tmpfile, outbuf_ptr, 0x10); + + // Otherwise, add ,ttype and ,a$aux as needed + if(tmpfile.file_type != fileptr->file_type) { + str = dynatype_find_file_type(fileptr->file_type); + if(str) { + aux_type = dynatype_find_prodos_type(str); + } else { + str = &buf[0]; + buf[15] = 0; + snprintf(&buf[0], 15, "$%02x", fileptr->file_type); + } + cfg_strlcat(outbuf_ptr, ",t", path_max); + cfg_strlcat(outbuf_ptr, str, path_max); + } + + (void)dynatype_detect_file_type(&tmpfile, outbuf_ptr, 0x10); + aux_type = fileptr->aux_type; + + if(aux_type != tmpfile.aux_type) { + buf[15] = 0; + snprintf(&buf[0], 15, ",a$%04x", aux_type & 0xffff); + cfg_strlcat(outbuf_ptr, &buf[0], path_max); + } + + // printf("dynatype_new_unix_name: %s\n", outbuf_ptr); + + // Check that it succeeded + (void)dynatype_detect_file_type(&tmpfile, outbuf_ptr, 0x10); + if((tmpfile.file_type != fileptr->file_type) || + (tmpfile.aux_type != fileptr->aux_type)) { + halt_printf("File %s want ftype:%02x aux:%04x, got:%02x %04x\n", + outbuf_ptr, fileptr->file_type, fileptr->aux_type, + tmpfile.file_type, tmpfile.aux_type); + exit(1); + } +} diff --git a/gsplus/src/dyna_validate.c b/gsplus/src/dyna_validate.c new file mode 100644 index 0000000..fcf5091 --- /dev/null +++ b/gsplus/src/dyna_validate.c @@ -0,0 +1,459 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2021-2022 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// Main information is from Beneath Apple ProDOS which has disk layout +// descriptions. Forked files are described in Technote tn-pdos-025. + +#include "defc.h" + +word32 +dynapro_validate_header(Disk *dsk, Dynapro_file *fileptr, word32 dir_byte, + word32 parent_dir_byte) +{ + word32 storage_type, exp_type, val, parent_block, exp_val; + + storage_type = fileptr->prodos_name[0] & 0xf0; + exp_type = 0xe0; + if(dir_byte == 0x0404) { + exp_type = 0xf0; // Volume header + } + if(storage_type != exp_type) { + printf("Volume/Dir header is %02x at %07x\n", + storage_type, dir_byte); + return 0; + } + + if(fileptr->aux_type != 0x0d27) { + printf("entry_length, entries_per_block:%04x at %07x\n", + fileptr->aux_type, dir_byte); + return 0; + } + + if(exp_type == 0xf0) { // Volume header + val = fileptr->lastmod_time >> 16; + if(val != 6) { + printf("bit_map_ptr:%04x, should be 6\n", val); + return 0; + } + val = fileptr->header_pointer; + if(val != (dsk->dimage_size >> 9)) { + printf("Num blocks at %07x is wrong: %04x\n", dir_byte, + val); + return 0; + } + } else { // Directory header + val = fileptr->lastmod_time >> 16; // parent_pointer + parent_block = parent_dir_byte >> 9; + if(val != parent_block) { + printf("Dir at %07x parent:%04x should be %04x\n", + dir_byte, val, parent_block); + return 0; + } + val = fileptr->header_pointer; + exp_val = ((parent_dir_byte & 0x1ff) - 4) / 0x27; + exp_val = (exp_val + 1) | 0x2700; + if(val != exp_val) { + printf("Parent entry at %07x is:%04x, should be:%04x\n", + dir_byte, val, exp_val); + return 0; + } + } + + return 1; +} + +void +dynapro_validate_init_freeblks(byte *freeblks_ptr, word32 num_blocks) +{ + word32 num_map_blocks, mask; + int pos; + word32 ui; + + for(ui = 0; ui < (num_blocks + 7)/8; ui++) { + freeblks_ptr[ui] = 0xff; + } + freeblks_ptr[0] &= 0x3f; + if(num_blocks & 7) { + freeblks_ptr[num_blocks / 8] = 0xff00 >> (num_blocks & 7); + } + + num_map_blocks = (num_blocks + 4095) >> 12; // 4096 bits per block + for(ui = 0; ui < num_map_blocks; ui++) { + // Mark blocks used in the bitmap as in use + pos = (ui + 6) >> 3; + mask = 0x80 >> ((ui + 6) & 7); + freeblks_ptr[pos] &= (~mask); + } +} + +word32 +dynapro_validate_freeblk(Disk *dsk, byte *freeblks_ptr, word32 block) +{ + word32 mask, ret; + int pos; + + // Return != 0 if block is free (which is success), returns == 0 + // if it is in use (which is an error). Marks block as in use + pos = block >> 3; + if(block >= (dsk->dimage_size >> 9)) { + return 0x100; // Out of range + } + mask = 0x80 >> (block & 7); + ret = freeblks_ptr[pos] & mask; + freeblks_ptr[pos] &= (~mask); + + if(!ret) { + printf("Block %04x was already in use\n", block); + } + return ret; +} + +word32 +dynapro_validate_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, + word32 eof, int level_first) +{ + byte *bptr; + word32 num_blocks, tmp, ret, exp_blocks, extra_blocks; + int level, first; + int i; + + level = level_first & 0xf; + first = level_first & 0x10; + + if(!dynapro_validate_freeblk(dsk, freeblks_ptr, block_num)) { + return 0; + } + if(level_first == 0x15) { + return dynapro_validate_forked_file(dsk, freeblks_ptr, + block_num, eof); + } + if((level < 1) || (level >= 4)) { + printf("level %d out of range, %08x\n", level, level_first); + return 0; + } + if(level == 1) { + return 1; + } + num_blocks = 1; + bptr = &(dsk->raw_data[block_num * 0x200]); + for(i = 0; i < 256; i++) { + tmp = bptr[i] + (bptr[256 + i] << 8); + if(tmp == 0) { + if(first) { + printf("First block is spare, illegal!\n"); + return 0; + } + continue; + } + ret = dynapro_validate_file(dsk, freeblks_ptr, tmp, eof, + first | (level - 1)); + if(ret == 0) { + return 0; + } + num_blocks += ret; + first = 0; + } + + if(level_first & 0x10) { + // Try to estimate exp_blocks based on eof + exp_blocks = (eof + 0x1ff) >> 9; + if(exp_blocks == 0) { + exp_blocks = 1; + } else if(exp_blocks > 1) { + // Add in sapling blocks + extra_blocks = ((exp_blocks + 255) >> 8); + if(exp_blocks > 256) { + extra_blocks++; + } + exp_blocks += extra_blocks; + } + if(num_blocks > exp_blocks) { + printf("blocks_used:%04x, eof:%07x, exp:%04x\n", + num_blocks, eof, exp_blocks); + return 0; + } + } + return num_blocks; +} + +word32 +dynapro_validate_forked_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, + word32 eof) +{ + byte *bptr; + word32 num_blocks, tmp, ret, size, type, exp_blocks; + int level; + int i; + + bptr = &(dsk->raw_data[block_num * 0x200]); + + if(eof != 0x200) { + printf("In forked file block %04x, eof in dir:%08x, exp 0200\n", + block_num, eof); + return 0; + } + // Check that most of the block is 0 + for(i = 44; i < 512; i++) { + if((i >= 0x100) && (i < 0x108)) { + continue; + } + if(bptr[i] != 0) { + printf("In forked file block:%04x, byte %03x is %02x\n", + block_num, i, bptr[i]); + return 0; + } + } + + // Check for basic Finder Info format + for(i = 0; i < 2; i++) { + size = bptr[8 + 18*i]; + type = bptr[9 + 18*i]; + if(((size != 0) && (size != 18)) || (type > 2)) { + printf("Finder Info size %04x+%03x=%02x, type:%02x\n", + block_num, 8 + 18*i, size, type); + return 0; + } + } + + num_blocks = 1; + for(i = 0; i < 2; i++) { + tmp = bptr[1 + 0x100*i] + (bptr[2 + 0x100*i] << 8); + if(tmp == 0) { + printf("First fork %d block is spare, illegal!\n", i); + return 0; + } + eof = bptr[5 + 0x100*i] + (bptr[6 + 0x100*i] << 8) + + (bptr[7 + 0x100*i] << 16); + level = bptr[0 + 0x100*i]; + ret = dynapro_validate_file(dsk, freeblks_ptr, tmp, eof, + 0x10 | level); + if(ret == 0) { + printf("Fork %d failed, eof:%08x, block:%04x " + "fork:%04x, level:%d\n", i, eof, block_num, + tmp, level); + return 0; + } + exp_blocks = bptr[3 + 0x100*i] + (bptr[4 + 0x100*i] << 8); + if(ret != exp_blocks) { + printf("Fork %d at %04x, blocks:%04x, exp:%04x\n", + i, block_num, ret, exp_blocks); + } + num_blocks += ret; + } + + return num_blocks; +} + +word32 +dynapro_validate_dir(Disk *dsk, byte *freeblks_ptr, word32 dir_byte, + word32 parent_dir_byte, word32 exp_blocks_used) +{ + char buf32[32]; + Dynapro_file localfile; + byte *bptr; + word32 start_dir_block, last_block, max_block, tmp_byte, sub_blocks; + word32 ret, act_entries, exp_entries, blocks_used, prev, next; + int cnt, is_header; + + // Read directory, make sure each entry is consistent + // Return 0 if there is damage, != 0 if OK. + bptr = dsk->raw_data; + start_dir_block = dir_byte >> 9; + last_block = 0; + max_block = (word32)(dsk->dimage_size >> 9); + cnt = 0; + is_header = 1; + exp_entries = 0xdeadbeef; + act_entries = 0; + blocks_used = 0; + while(dir_byte) { + if((dir_byte & 0x1ff) == 4) { + // First entry in this block, check prev/next + tmp_byte = dir_byte & -0x200; // Block align + prev = dynapro_get_word16(&bptr[tmp_byte + 0]); + next = dynapro_get_word16(&bptr[tmp_byte + 2]); + if((prev != last_block) || (next >= max_block)) { + printf("dir at %07x is damaged in links\n", + dir_byte); + return 0; + } + last_block = dir_byte >> 9; + ret = dynapro_validate_freeblk(dsk, freeblks_ptr, + dir_byte >> 9); + if(!ret) { + return 0; + } + blocks_used++; + } + if(cnt++ >= 65536) { + printf("Loop detected, dir_byte:%07x\n", dir_byte); + return 0; + } + ret = dynapro_fill_fileptr_from_prodos(dsk, &localfile, + &buf32[0], dir_byte); + if(ret == 0) { + return 0; + } + if(ret != 1) { + act_entries = act_entries + 1 - is_header; + } + if(is_header) { + if(ret == 1) { + printf("Volume/Dir header is erased\n"); + return 0; + } + ret = dynapro_validate_header(dsk, &localfile, dir_byte, + parent_dir_byte); + if(ret == 0) { + return 0; + } + exp_entries = localfile.lastmod_time & 0xffff; + } else if(ret != 1) { + if(localfile.header_pointer != start_dir_block) { + printf("At %07x, header_ptr:%04x != %04x\n", + dir_byte, localfile.header_pointer, + start_dir_block); + return 0; + } + if(localfile.prodos_name[0] >= 0xd0) { + sub_blocks = localfile.blocks_used; + if(localfile.eof != (sub_blocks * 0x200UL)) { + printf("At %07x, eof:%08x != %08x\n", + dir_byte, localfile.eof, + sub_blocks * 0x200U); + return 0; + } + ret = dynapro_validate_dir(dsk, freeblks_ptr, + (localfile.key_block * 0x200) + 4, + dir_byte, sub_blocks); + if(ret == 0) { + return 0; + } + } else { + ret = dynapro_validate_file(dsk, freeblks_ptr, + localfile.key_block, localfile.eof, + 0x10 | (localfile.prodos_name[0] >> 4)); + if(ret == 0) { + printf("At %07x, bad file\n", dir_byte); + return 0; + } + if(localfile.blocks_used != ret) { + printf("At %07x, blocks_used prodos " + "%04x != %04x calc\n", dir_byte, + localfile.blocks_used, ret); + return 0; + } + } + } + + is_header = 0; + dir_byte = dir_byte + 0x27; + tmp_byte = (dir_byte & 0x1ff) + 0x27; + if(tmp_byte < 0x200) { + continue; + } + + tmp_byte = (dir_byte - 0x27) & (0 - 0x200UL); + dir_byte = dynapro_get_word16(&bptr[tmp_byte + 2]) * 0x200UL; + if(dir_byte == 0) { + if(act_entries != exp_entries) { + printf("act_entries:%04x != exp:%04x, " + "dir_block:%04x\n", act_entries, + exp_entries, start_dir_block); + return 0; + } + if(blocks_used != exp_blocks_used) { + printf("At dir %07x, blocks_used:%04x!=%04x " + "exp\n", tmp_byte, blocks_used, + exp_blocks_used); + return 0; + } + return 1; + } + dir_byte += 4; + if(dir_byte >= (max_block * 0x200L)) { + printf(" invalid link pointer %07x\n", dir_byte); + return 0; + } + } + + return 0; +} + +int +dynapro_validate_disk(Disk *dsk) +{ + byte freeblks[65536/8]; // 8KB + byte *bptr; + word32 num_blocks, ret; + word32 ui; + + num_blocks = (word32)(dsk->dimage_size >> 9); + printf("******************************\n"); + printf("Validate disk: %s, blocks:%05x\n", dsk->name_ptr, num_blocks); + dynapro_validate_init_freeblks(&freeblks[0], num_blocks); + + // Validate starting at directory in block 2 + ret = dynapro_validate_dir(dsk, &freeblks[0], 0x0404, 0, 4); + if(!ret) { + printf("Disk does not validate!\n"); + exit(1); + return ret; + } + + // Check freeblks + bptr = &(dsk->raw_data[6*0x200]); + for(ui = 0; ui < (num_blocks + 7)/8; ui++) { + if(freeblks[ui] != bptr[ui]) { + printf("Expected free mask for blocks %04x-%04x:%02x, " + "but it is %02x\n", ui*8, ui*8 + 7, + freeblks[ui], bptr[ui]); + exit(1); + return 0; + } + } + return 1; +} + +void +dynapro_validate_any_image(Disk *dsk) +{ + byte *bufptr; + dword64 dsize; + int ret; + + // If dsk->raw_data already set, just use it. Otherwise, we need to + // temporarily read in entire image, set it, do validate, and then + // free it + + if(dsk->fd < 0) { + return; // No disk + } + if(dsk->wozinfo_ptr) { + return; + } + dsize = dsk->dimage_size; + bufptr = 0; + if((dsize >> 31) != 0) { + printf("Disk is too large, not valid\n"); + ret = 0; + } else if(dsk->raw_data == 0) { + bufptr = malloc((size_t)dsize); + dsk->raw_data = bufptr; + cfg_read_from_fd(dsk->fd, bufptr, 0, dsize); + ret = dynapro_validate_disk(dsk); + dsk->raw_data = 0; + free(bufptr); + } else { + ret = dynapro_validate_disk(dsk); + } + printf("validate_disk returned is_good: %d (0 is bad)\n", ret); +} + diff --git a/gsplus/src/dynapro.c b/gsplus/src/dynapro.c new file mode 100644 index 0000000..d2e9b87 --- /dev/null +++ b/gsplus/src/dynapro.c @@ -0,0 +1,2302 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2021-2022 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// Main information is from Beneath Apple ProDOS which has disk layout +// descriptions. Upper/lowercase is from Technote tn-gsos-008, and +// forked files are storage_type $5 from Technote tn-pdos-025. + +#include "defc.h" +#ifdef _WIN32 +# include "win_dirent.h" +#else +# include +#endif +#include + +extern int Verbose; + +#define DYNAPRO_PATH_MAX 2048 +char g_dynapro_path_buf[DYNAPRO_PATH_MAX]; + +extern word32 g_vbl_count, g_iwm_dynapro_last_vbl_count; + +byte g_prodos_block0[512] = { + // From Beagle Bros Pro-Byter disk + // This is a ProDOS boot block, able to load PRODOS and jump to it + 0x01, 0x38, 0xb0, 0x03, 0x4c, 0x32, 0xa1, 0x86, // 0x000 + 0x43, 0xc9, 0x03, 0x08, 0x8a, 0x29, 0x70, 0x4a, + 0x4a, 0x4a, 0x4a, 0x09, 0xc0, 0x85, 0x49, 0xa0, // 0x010 + 0xff, 0x84, 0x48, 0x28, 0xc8, 0xb1, 0x48, 0xd0, + 0x3a, 0xb0, 0x0e, 0xa9, 0x03, 0x8d, 0x00, 0x08, // 0x020 + 0xe6, 0x3d, 0xa5, 0x49, 0x48, 0xa9, 0x5b, 0x48, + 0x60, 0x85, 0x40, 0x85, 0x48, 0xa0, 0x63, 0xb1, // 0x030 + 0x48, 0x99, 0x94, 0x09, 0xc8, 0xc0, 0xeb, 0xd0, + 0xf6, 0xa2, 0x06, 0xbc, 0x1d, 0x09, 0xbd, 0x24, // 0x040 + 0x09, 0x99, 0xf2, 0x09, 0xbd, 0x2b, 0x09, 0x9d, + 0x7f, 0x0a, 0xca, 0x10, 0xee, 0xa9, 0x09, 0x85, // 0x050 + 0x49, 0xa9, 0x86, 0xa0, 0x00, 0xc9, 0xf9, 0xb0, + 0x2f, 0x85, 0x48, 0x84, 0x60, 0x84, 0x4a, 0x84, // 0x060 + 0x4c, 0x84, 0x4e, 0x84, 0x47, 0xc8, 0x84, 0x42, + 0xc8, 0x84, 0x46, 0xa9, 0x0c, 0x85, 0x61, 0x85, // 0x070 + 0x4b, 0x20, 0x12, 0x09, 0xb0, 0x68, 0xe6, 0x61, + 0xe6, 0x61, 0xe6, 0x46, 0xa5, 0x46, 0xc9, 0x06, // 0x080 + 0x90, 0xef, 0xad, 0x00, 0x0c, 0x0d, 0x01, 0x0c, + 0xd0, 0x6d, 0xa9, 0x04, 0xd0, 0x02, 0xa5, 0x4a, // 0x090 + 0x18, 0x6d, 0x23, 0x0c, 0xa8, 0x90, 0x0d, 0xe6, + 0x4b, 0xa5, 0x4b, 0x4a, 0xb0, 0x06, 0xc9, 0x0a, // 0x0a0 + 0xf0, 0x55, 0xa0, 0x04, 0x84, 0x4a, 0xad, 0x02, + 0x09, 0x29, 0x0f, 0xa8, 0xb1, 0x4a, 0xd9, 0x02, // 0x0b0 + 0x09, 0xd0, 0xdb, 0x88, 0x10, 0xf6, 0x29, 0xf0, + 0xc9, 0x20, 0xd0, 0x3b, 0xa0, 0x10, 0xb1, 0x4a, // 0x0c0 + 0xc9, 0xff, 0xd0, 0x33, 0xc8, 0xb1, 0x4a, 0x85, + 0x46, 0xc8, 0xb1, 0x4a, 0x85, 0x47, 0xa9, 0x00, // 0x0d0 + 0x85, 0x4a, 0xa0, 0x1e, 0x84, 0x4b, 0x84, 0x61, + 0xc8, 0x84, 0x4d, 0x20, 0x12, 0x09, 0xb0, 0x17, // 0x0e0 + 0xe6, 0x61, 0xe6, 0x61, 0xa4, 0x4e, 0xe6, 0x4e, + 0xb1, 0x4a, 0x85, 0x46, 0xb1, 0x4c, 0x85, 0x47, // 0x0f0 + 0x11, 0x4a, 0xd0, 0xe7, 0x4c, 0x00, 0x20, 0x4c, + + 0x3f, 0x09, 0x26, 0x50, 0x52, 0x4f, 0x44, 0x4f, // 0x100 + 0x53, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0xa5, 0x60, 0x85, 0x44, 0xa5, 0x61, // 0x110 + 0x85, 0x45, 0x6c, 0x48, 0x00, 0x08, 0x1e, 0x24, + 0x3f, 0x45, 0x47, 0x76, 0xf4, 0xd7, 0xd1, 0xb6, // 0x120 + 0x4b, 0xb4, 0xac, 0xa6, 0x2b, 0x18, 0x60, 0x4c, + 0xbc, 0x09, 0xa9, 0x9f, 0x48, 0xa9, 0xff, 0x48, // 0x130 + 0xa9, 0x01, 0xa2, 0x00, 0x4c, 0x79, 0xf4, 0x20, + 0x58, 0xfc, 0xa0, 0x1c, 0xb9, 0x50, 0x09, 0x99, // 0x140 + 0xae, 0x05, 0x88, 0x10, 0xf7, 0x4c, 0x4d, 0x09, + 0xaa, 0xaa, 0xaa, 0xa0, 0xd5, 0xce, 0xc1, 0xc2, // 0x150 + 0xcc, 0xc5, 0xa0, 0xd4, 0xcf, 0xa0, 0xcc, 0xcf, + 0xc1, 0xc4, 0xa0, 0xd0, 0xd2, 0xcf, 0xc4, 0xcf, // 0x160 + 0xd3, 0xa0, 0xaa, 0xaa, 0xaa, 0xa5, 0x53, 0x29, + 0x03, 0x2a, 0x05, 0x2b, 0xaa, 0xbd, 0x80, 0xc0, // 0x170 + 0xa9, 0x2c, 0xa2, 0x11, 0xca, 0xd0, 0xfd, 0xe9, + 0x01, 0xd0, 0xf7, 0xa6, 0x2b, 0x60, 0xa5, 0x46, // 0x180 + 0x29, 0x07, 0xc9, 0x04, 0x29, 0x03, 0x08, 0x0a, + 0x28, 0x2a, 0x85, 0x3d, 0xa5, 0x47, 0x4a, 0xa5, // 0x190 + 0x46, 0x6a, 0x4a, 0x4a, 0x85, 0x41, 0x0a, 0x85, + 0x51, 0xa5, 0x45, 0x85, 0x27, 0xa6, 0x2b, 0xbd, // 0x1a0 + 0x89, 0xc0, 0x20, 0xbc, 0x09, 0xe6, 0x27, 0xe6, + 0x3d, 0xe6, 0x3d, 0xb0, 0x03, 0x20, 0xbc, 0x09, // 0x1b0 + 0xbc, 0x88, 0xc0, 0x60, 0xa5, 0x40, 0x0a, 0x85, + 0x53, 0xa9, 0x00, 0x85, 0x54, 0xa5, 0x53, 0x85, // 0x1c0 + 0x50, 0x38, 0xe5, 0x51, 0xf0, 0x14, 0xb0, 0x04, + 0xe6, 0x53, 0x90, 0x02, 0xc6, 0x53, 0x38, 0x20, // 0x1d0 + 0x6d, 0x09, 0xa5, 0x50, 0x18, 0x20, 0x6f, 0x09, + 0xd0, 0xe3, 0xa0, 0x7f, 0x84, 0x52, 0x08, 0x28, // 0x1e0 + 0x38, 0xc6, 0x52, 0xf0, 0xce, 0x18, 0x08, 0x88, + 0xf0, 0xf5, 0xbd, 0x8c, 0xc0, 0x10, 0xfb, 0x00, // 0x1f0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +word32 +dynapro_get_word32(byte *bptr) +{ + return (bptr[3] << 24) | (bptr[2] << 16) | (bptr[1] << 8) | bptr[0]; +} + +word32 +dynapro_get_word24(byte *bptr) +{ + return (bptr[2] << 16) | (bptr[1] << 8) | bptr[0]; +} + +word32 +dynapro_get_word16(byte *bptr) +{ + return (bptr[1] << 8) | bptr[0]; +} + +void +dynapro_set_word24(byte *bptr, word32 val) +{ + // Write 3 bytes in little-endian form + *bptr++ = val; + *bptr++ = (val >> 8); + *bptr++ = (val >> 16); +} + +void +dynapro_set_word32(byte *bptr, word32 val) +{ + // Write 4 bytes in little-endian form + *bptr++ = val; + *bptr++ = (val >> 8); + *bptr++ = (val >> 16); + *bptr++ = (val >> 24); +} + +void +dynapro_set_word16(byte *bptr, word32 val) +{ + // Write 2 bytes in little-endian form + *bptr++ = val; + *bptr++ = (val >> 8); +} + +void +dynapro_error(Disk *dsk, const char *fmt, ...) +{ + Dynapro_info *info_ptr; + va_list args; + + va_start(args, fmt); + cfg_err_vprintf("Dynapro", fmt, args); + va_end(args); + info_ptr = dsk->dynapro_info_ptr; + if(info_ptr) { + cfg_err_printf("", "Path: %s\n", info_ptr->root_path); + } +} + +Dynapro_file * +dynapro_alloc_file() +{ + Dynapro_file *fileptr; + + fileptr = calloc(1, sizeof(Dynapro_file)); + return fileptr; +} + +void +dynapro_free_file(Dynapro_file *fileptr, int check_map) +{ + if(!fileptr) { + return; + } + if(fileptr->subdir_ptr) { + dynapro_free_recursive_file(fileptr->subdir_ptr, check_map); + } + fileptr->subdir_ptr = 0; + free(fileptr->unix_path); + fileptr->unix_path = 0; + free(fileptr->buffer_ptr); + fileptr->buffer_ptr = 0; + fileptr->next_ptr = 0; + // printf("FREE %p\n", fileptr); + if(check_map && (fileptr->map_first_block != 0)) { + printf(" ERROR: map_first_block is %08x\n", + fileptr->map_first_block); + exit(1); + } + free(fileptr); +} + +void +dynapro_free_recursive_file(Dynapro_file *fileptr, int check_map) +{ + Dynapro_file *nextptr; + + if(!fileptr) { + return; + } + // printf("free_recursive %s\n", fileptr->unix_path); + while(fileptr) { + nextptr = fileptr->next_ptr; + dynapro_free_file(fileptr, check_map); + fileptr = nextptr; + }; +} + +void +dynapro_free_dynapro_info(Disk *dsk) +{ + Dynapro_info *info_ptr; + + info_ptr = dsk->dynapro_info_ptr; + if(info_ptr) { + free(info_ptr->root_path); + + dynapro_free_recursive_file(info_ptr->volume_ptr, 0); + info_ptr->volume_ptr = 0; + } + free(info_ptr); + dsk->dynapro_info_ptr = 0; +} + +word32 +dynapro_find_free_block_internal(Disk *dsk) +{ + byte *bptr; + word32 num_blocks, bitmap_size_bytes, val, mask; + word32 ui; + int j; + + num_blocks = (word32)(dsk->raw_dsize >> 9); + bitmap_size_bytes = (num_blocks + 7) >> 3; + bptr = &(dsk->raw_data[6 * 512]); // Block 6 + for(ui = 0; ui < bitmap_size_bytes; ui++) { + val = bptr[ui]; + if(val == 0) { + continue; + } + mask = 0x80; + for(j = 0; j < 8; j++) { + if(val & mask) { + bptr[ui] = val & (~mask); + return 8*ui + j; + } + mask = mask >> 1; + } + return 0; + } + return 0; +} + +word32 +dynapro_find_free_block(Disk *dsk) +{ + byte *bptr; + word32 block; + int i; + + // Find first free block, and zero it out + + block = dynapro_find_free_block_internal(dsk); + if(block == 0) { + return 0; + } + bptr = &(dsk->raw_data[block * 512]); + for(i = 0; i < 512; i++) { + bptr[i] = 0; + } + + return block; +} + +byte * +dynapro_malloc_file(char *path_ptr, dword64 *dsize_ptr, int extra_size) +{ + byte *bptr; + dword64 dsize, dpos; + int fd; + int i; + + *dsize_ptr = 0; + fd = open(path_ptr, O_RDONLY | O_BINARY, 0x1b6); + if(fd < 0) { + return 0; + } + dsize = cfg_get_fd_size(fd); + + if((size_t)(dsize + extra_size) != (dsize + extra_size)) { + return 0; + } + bptr = malloc((size_t)(dsize + extra_size)); + if(bptr == 0) { + return bptr; + } + // printf("dynapro_malloc_file %p, size:%08lld\n", bptr, dsize); + for(i = 0; i < extra_size; i++) { + bptr[dsize + i] = 0; + } + dpos = cfg_read_from_fd(fd, bptr, 0, dsize); + close(fd); + if(dpos != dsize) { + free(bptr); + return 0; + } + *dsize_ptr = dsize; + return bptr; +} + +void +dynapro_join_path_and_file(char *outstr, const char *unix_path, const char *str, + int path_max) +{ + int len; + + // Create "unix_path" + "/" + "str" in outstr (which has size path_max) + cfg_strncpy(outstr, unix_path, path_max); + len = (int)strlen(outstr); + if((len > 0) && (outstr[len - 1] != '/')) { + cfg_strlcat(outstr, "/", path_max); + } + cfg_strlcat(outstr, str, path_max); +} + + +word32 +dynapro_fill_fileptr_from_prodos(Disk *dsk, Dynapro_file *fileptr, + char *buf32_ptr, word32 dir_byte) +{ + byte *bptr; + word32 upper_lower; + int len, c; + int i; + + buf32_ptr[0] = 0; + if((dir_byte < 0x400) || (dir_byte >= dsk->dimage_size)) { + return 0; // Directory is damaged + } + if(!fileptr) { + return 0; + } + bptr = &(dsk->raw_data[dir_byte]); + memset(fileptr, 0, sizeof(Dynapro_file)); + + fileptr->dir_byte = dir_byte; + fileptr->file_type = bptr[0x10]; + fileptr->key_block = dynapro_get_word16(&bptr[0x11]); + fileptr->blocks_used = dynapro_get_word16(&bptr[0x13]); + fileptr->eof = dynapro_get_word24(&bptr[0x15]); + //printf("Filling from entry %07x, eof:%06x\n", dir_byte, fileptr->eof); + fileptr->creation_time = dynapro_get_word32(&bptr[0x18]); + fileptr->upper_lower = dynapro_get_word16(&bptr[0x1c]); + fileptr->aux_type = dynapro_get_word16(&bptr[0x1f]); + fileptr->lastmod_time = dynapro_get_word32(&bptr[0x21]); + fileptr->header_pointer = dynapro_get_word16(&bptr[0x25]); + if(dir_byte == 0x404) { // Volume header + fileptr->upper_lower = dynapro_get_word32(&bptr[0x1a]); + fileptr->creation_time &= 0xffff; + } + + len = (bptr[0] & 0xf) + 1; + upper_lower = fileptr->upper_lower; + if((upper_lower & 0x8000) == 0) { // Not valid + upper_lower = 0; + } + for(i = 0; i < 16; i++) { + c = bptr[i]; + if(i > len) { + c = 0; + } + fileptr->prodos_name[i] = c; + if(i > 0) { + if(upper_lower & 0x4000) { + if((c >= 'A') && (c <= 'Z')) { + c = c - 'A' + 'a'; // Make lower + } + } + upper_lower = upper_lower << 1; + buf32_ptr[i - 1] = c; + buf32_ptr[i] = 0; + } + } + if(((bptr[0] & 0xf0) == 0) || ((bptr[0] & 0xf) == 0)) { + fileptr->prodos_name[0] = 0; + return 1; // Invalid entry + } + if(fileptr->prodos_name[0] >= 0xe0) { // Dir/Volume header + fileptr->key_block = dir_byte >> 9; + if((dir_byte & 0x1ff) != 4) { + printf("Header at dir_byte:%07x != 4\n", dir_byte); + return 0; // Not in first pos + } + if(bptr[-4] || bptr[-3]) { + printf("prev_link %02x,%02x should be 0\n", + bptr[-4], bptr[-3]); + return 0; // Not first dir block + } + if(fileptr->prodos_name[0] >= 0xf0) { + if(dir_byte != 0x0404) { + printf("Volume head dir_byte:%07x\n", dir_byte); + return 0; + } + } else if(dir_byte == 0x0404) { + printf("Directory head dir_byte 0x0404\n"); + return 0; // 0xe0 in block 2->bad + } + } else { + // Normal entry. Make sure it's not the first entry in a dir + if((bptr[-4] == 0) && (bptr[-3] == 0) && + ((dir_byte & 0x1ff) == 4)) { + printf("dir_byte:%07x, normal, prev:0\n", dir_byte); + return 0; // This is a dir/volume header! + } + } +#if 0 + printf("Fill resulted in buf32:%s, upper_lower:%04x\n", buf32_ptr, + fileptr->upper_lower); +#endif + + return 2; // OK +} + +word32 +dynapro_diff_fileptrs(Dynapro_file *oldfileptr, Dynapro_file *newfileptr) +{ + word32 ret, new_storage, old_storage; + int i; + + // Return 0 if the directory is damaged + // Return 1 if the entry is invalid (and not case 3!) + // Return 3 if the entry was valid and is now deleted + // Return 4 if no changes are needed + // Return 5 if oldfileptr needs to be rewritten + // Return 7 if oldfileptr needs to be erased and replaced with newfile + + old_storage = oldfileptr->prodos_name[0]; + new_storage = newfileptr->prodos_name[0]; + if(new_storage == 0) { // Erased + if(old_storage >= 0xe0) { // Vol/Dir header + return 0; + } + if(oldfileptr->dir_byte == newfileptr->dir_byte) { + return 3; // Entry just deleted + } + return 1; // Just an invalid entry + } + if(oldfileptr->dir_byte != newfileptr->dir_byte) { + return 0; + } + ret = 4; // No changes needed + + // Handle file expanding from seedling to tree + if((new_storage >= 0x10) && (new_storage < 0x40) && + (old_storage >= 0x10) && (old_storage < 0x40)) { + // Copy upper 4 bits from new_storage to old_storage + old_storage = (old_storage & 0x0f) | (new_storage & 0xf0); + if(oldfileptr->prodos_name[0] != old_storage) { + // Storage type changed, rewrite the file + oldfileptr->prodos_name[0] = old_storage; + ret |= 5; + } + } + for(i = 0; i < 16; i++) { + if(oldfileptr->prodos_name[i] != newfileptr->prodos_name[i]) { + ret |= 7; // Name changed + } + oldfileptr->prodos_name[i] = newfileptr->prodos_name[i]; + } + + if(oldfileptr->file_type != newfileptr->file_type) { + ret |= 7; // Filetype changed + oldfileptr->file_type = newfileptr->file_type; + } + if(newfileptr->prodos_name[0] < 0xe0) { + // Not a directory or volume header + if(oldfileptr->key_block != newfileptr->key_block) { + ret |= 5; // Key block has changed + oldfileptr->key_block = newfileptr->key_block; + } + if(oldfileptr->blocks_used != newfileptr->blocks_used) { + // ret stays 1, we don't care about this field + oldfileptr->blocks_used = newfileptr->blocks_used; + } + if(oldfileptr->eof != newfileptr->eof) { + ret |= 5; // eof has changed + oldfileptr->eof = newfileptr->eof; + } + } else { + // Directory or volume header + // Ignore key_block (used internally by dynapro.c, but not in + // the ProDOS disk image), blocks_used, eof. + // We ignore file_count at +0x21,0x22. But bitmap_ptr matters + // and if it moves, we are damaged + if((oldfileptr->lastmod_time >> 16) != + (newfileptr->lastmod_time >> 16)) { + return 0; // Bitmap_ptr moved, we are damaged + } + } + if(oldfileptr->upper_lower != newfileptr->upper_lower) { + ret |= 7; // lowercase flags have changed + oldfileptr->upper_lower = newfileptr->upper_lower; + } + if(oldfileptr->aux_type != newfileptr->aux_type) { + ret |= 5; // aux_type has changed + oldfileptr->aux_type = newfileptr->aux_type; + } + if(oldfileptr->header_pointer != newfileptr->header_pointer) { + return 0; // We are damaged + } + if(newfileptr->prodos_name[0] >= 0xe0) { + if(ret > 5) { + ret = 5; // No renaming volume or dir headers + } + } + return ret; +} + +word32 +dynapro_do_one_dir_entry(Disk *dsk, Dynapro_file *fileptr, + Dynapro_file *localfile_ptr, char *buf32_ptr, word32 dir_byte) +{ + word32 ret, diffs; + + ret = dynapro_fill_fileptr_from_prodos(dsk, localfile_ptr, + buf32_ptr, dir_byte); + if((ret == 0) || ((ret == 1) && !fileptr)) { + return ret; // Damaged or not valid + } + if(!fileptr) { + return 2; // must allocate new + } + + // Now, head_ptr must be non-null + diffs = dynapro_diff_fileptrs(fileptr, localfile_ptr); + return diffs; +} + +void +dynapro_fix_damaged_entry(Disk *dsk, Dynapro_file *fileptr) +{ + if(fileptr->prodos_name[0] >= 0xe0) { + // This is a volume/directory header. Re-parse entire dir + dynapro_handle_write_dir(dsk, fileptr->parent_ptr, fileptr, + (fileptr->key_block * 0x200UL) + 4); + } else if(fileptr->prodos_name[0] >= 0xd0) { + // This is a directory entry. + dynapro_handle_write_dir(dsk, fileptr, fileptr->subdir_ptr, + (fileptr->key_block * 0x200UL) + 4); + } else { + dynapro_handle_write_file(dsk, fileptr); + } +} + +void +dynapro_try_fix_damage(Disk *dsk, Dynapro_file *fileptr) +{ + // Walk entire tree (recursing to dynapro_try_fix_damage) + if(!fileptr) { + return; + } + while(fileptr) { + if(fileptr->damaged) { + dyna_printf("try_fix_damage %p %s\n", fileptr, + fileptr->unix_path); + dynapro_fix_damaged_entry(dsk, fileptr); + } + dynapro_try_fix_damage(dsk, fileptr->subdir_ptr); + fileptr = fileptr->next_ptr; + } +} + +void +dynapro_try_fix_damaged_disk(Disk *dsk) +{ + Dynapro_info *info_ptr; + + info_ptr = dsk->dynapro_info_ptr; + if(!info_ptr) { // This is impossible + return; + } + if(info_ptr->damaged == 0) { + return; + } + + dyna_printf("************************************\n"); + dyna_printf("try_fix_damaged_dsk called, damaged:%d\n", + info_ptr->damaged); + dyna_printf(" vbl_count:%d, g_iwm_dynapro_last_vbl_count:%d\n", + g_vbl_count, g_iwm_dynapro_last_vbl_count); + + info_ptr->damaged = 0; + dynapro_try_fix_damage(dsk, info_ptr->volume_ptr); + + dyna_printf("try_fix_damaged_dsk, damaged:%d\n", info_ptr->damaged); +} + + +void +dynapro_new_unix_path(Dynapro_file *fileptr, const char *path_str, + const char *name_str) +{ + if(fileptr->unix_path) { + free(fileptr->unix_path); + } + dynapro_join_path_and_file(&g_dynapro_path_buf[0], path_str, name_str, + DYNAPRO_PATH_MAX); + dynatype_fix_unix_name(fileptr, &g_dynapro_path_buf[0], + DYNAPRO_PATH_MAX); + fileptr->unix_path = kegs_malloc_str(&g_dynapro_path_buf[0]); +} + +Dynapro_file * +dynapro_process_write_dir(Disk *dsk, Dynapro_file *parent_ptr, + Dynapro_file **head_ptr_ptr, word32 dir_byte) +{ + char buf32[32]; + Dynapro_file localfile; + Dynapro_file *fileptr, *prev_ptr, *head_ptr; + byte *bptr; + const char *str; + word32 tmp_byte, prev, next, ret, last_block, parent_dir_byte; + int cnt, error, iret, is_header; + + head_ptr = *head_ptr_ptr; // head_ptr_ptr must be valid + // but head_ptr can be 0 + // We can be called with parent_ptr=0, head_ptr != 0: this is for the + // volume header. Otherwise, parent_ptr should be valid. + // If head_ptr==0, it means we need to allocate directory header and + // all other dir entries + // head_ptr is a pointer to a directory or volume header. + // For all entries, see if anything changed. We need to also + // possibly update head_ptr->parent_ptr + // Return 0 if the directory is damaged. If directory only contains + // damaged files, try to fix them, and always return success + + // First, unmap the directory blocks (this is done even if nothing + // changed, we'll map them back at the end). + if(head_ptr) { + dynapro_unmap_file(dsk, head_ptr); + } + + parent_dir_byte = 0; + if(parent_ptr) { + str = parent_ptr->unix_path; + parent_dir_byte = parent_ptr->dir_byte; + if(head_ptr == 0) { + // Do mkdir to make sure it exists +#ifdef _WIN32 + iret = _mkdir(str); +#else + iret = mkdir(str, 0x1ff); +#endif + error = errno; + dyna_printf("Did mkdir %s, iret:%d\n", str, iret); + if(iret < 0) { + if((error == EEXIST) || (error == EISDIR)) { + error = 0; // These are OK errors + } + if(error) { + printf("mkdir(%s) failed, error=%d\n", + str, error); + } + } + } + } else { + str = head_ptr->unix_path; // volume header + } +#if 0 + printf("process_write_dir str:%s %p parent:%p\n", str, head_ptr, + parent_ptr); +#endif + + // The directory blocks have already been unmapped + + // Then, walk the directory, noting if anything changed. If new + // files appear in the directory, add then to the chain. We may need + // to erase existing entries which no longer exist (or their directory + // entry was changed to a different file) + bptr = dsk->raw_data; + prev_ptr = 0; + fileptr = head_ptr; + last_block = 0; + cnt = 0; + is_header = 1; + while(dir_byte) { + //printf("process_write_dir, dir_byte:%07x, prev_ptr:%p\n", + // dir_byte, prev_ptr); + if((dir_byte & 0x1ff) == 4) { + // First entry in this block: check prev/next + tmp_byte = dir_byte & -0x200; // Block align + prev = dynapro_get_word16(&bptr[tmp_byte + 0]); + next = dynapro_get_word16(&bptr[tmp_byte + 2]); + if((prev != last_block) || + (next >= (dsk->raw_dsize >> 9))) { + // This is a damaged directory + printf("dir %s is damaged in the link fields\n", + str); + return 0; + } + last_block = dir_byte >> 9; + } + if(cnt++ >= 65536) { + printf("dir %s has a loop in block pointers\n", + head_ptr->unix_path); + return 0; + } + + ret = dynapro_do_one_dir_entry(dsk, fileptr, &localfile, + &buf32[0], dir_byte); +#if 0 + printf(" do_one_dir_entry ret:%08x fileptr:%p, &localfile:%p\n", + ret, fileptr, &localfile); +#endif + if((ret == 7) && !is_header) { // Entry dramatically changed + // Erase this file + dynapro_mark_damaged(dsk, fileptr); + } + if(ret == 0) { + return 0; + } else if((ret == 1) || (ret == 3)) { + if((ret == 3) && fileptr) { + // This entry was valid and is now deleted. + // Erase it right now and fix links + dyna_printf("fileptr %p deleted\n", fileptr); + prev_ptr->next_ptr = fileptr->next_ptr; + dynapro_erase_free_entry(dsk, fileptr); + } + if(head_ptr == 0) { + printf("return, head_ptr==0, deleted file at " + "%07x\n", dir_byte); + return 0; // Directory damaged + } + fileptr = prev_ptr; + } + if(ret == 2) { + // prev_ptr->next_ptr is 0, this is a new entry we + // need to put on the list + if(fileptr) { + halt_printf("file %s was ignored!\n", + fileptr->unix_path); + exit(1); + } + fileptr = dynapro_alloc_file(); + if(!fileptr) { + return 0; + } + *fileptr = localfile; // STRUCT copy! + dyna_printf("Allocated new fileptr:%p\n", fileptr); + fileptr->parent_ptr = parent_ptr; + if(head_ptr) { + fileptr->parent_ptr = head_ptr; + } + } + if((ret == 2) || (ret == 7)) { + // New entry, or dramatically changed, update path + if(!head_ptr) { + if(!parent_ptr) { + printf("parent_ptr is 0!\n"); + return 0; + } + parent_ptr->subdir_ptr = fileptr; + printf("2/7 set %p %s subdir=%p\n", parent_ptr, + parent_ptr->unix_path, fileptr); + fileptr->unix_path = kegs_malloc_str(str); + head_ptr = fileptr; + *head_ptr_ptr = head_ptr; + } else { + dyna_printf("Forming new path: %s buf32:%s\n", + str, buf32); + dynapro_new_unix_path(fileptr, str, buf32); + } + // If we are a directory entry (fileptr->subdir_ptr!=0) + // then now fileptr->unix_path != subdirptr->unix_path + // The subdir will be erased in + // dynapro_handle_changed_entry() + if(prev_ptr) { + prev_ptr->next_ptr = fileptr; + } + } + if((ret == 5) || (ret == 2) || (ret == 7)) { + // Changed, or new entry + if(is_header) { + ret = dynapro_validate_header(dsk, fileptr, + dir_byte, parent_dir_byte); + if(ret == 0) { + return 0; + } + } else { + dynapro_handle_changed_entry(dsk, fileptr); + } + } + prev_ptr = fileptr; + fileptr = prev_ptr->next_ptr; + dir_byte = dir_byte + 0x27; + tmp_byte = (dir_byte & 0x1ff) + 0x27; + is_header = 0; + if(tmp_byte < 0x200) { + continue; + } + tmp_byte = (dir_byte - 0x27) & (0 - 0x200UL); + dir_byte = dynapro_get_word16(&bptr[tmp_byte + 2]) * 0x200UL; + dyna_printf(" dir link at %07x = %04x\n", tmp_byte + 2, + dir_byte); + if(dir_byte == 0) { + if(fileptr) { + printf("At dir end, fileptr: %p\n", fileptr); + prev_ptr->next_ptr = 0; + dynapro_erase_free_dir(dsk, fileptr); + return 0; + } + ret = dynapro_map_dir_blocks(dsk, head_ptr); + // printf("process_write_dir %s done, remap ret:%d\n", + // head_ptr->unix_path, ret); + if(ret == 0) { + return 0; + } + return head_ptr; // Success + } + dir_byte += 4; + if(dir_byte >= dsk->dimage_size) { + // printf(" invalid link pointer, dir_byte:%08x\n", + // dir_byte); + return 0; // Bad link, get out + } + } + dyna_printf("At end of process_write_dir, returning 0\n"); + return 0; +} + +void +dynapro_handle_write_dir(Disk *dsk, Dynapro_file *parent_ptr, + Dynapro_file *head_ptr, word32 dir_byte) +{ + Dynapro_file *fileptr; + + //save_headptr = head_ptr; + dyna_printf("handle_write_dir parent_ptr:%p, head_ptr:%p, dir_b:%07x\n", + parent_ptr, head_ptr, dir_byte); + if(parent_ptr && head_ptr) { + dyna_printf(" parent:%s head:%s\n", parent_ptr->unix_path, + head_ptr->unix_path); + if(parent_ptr->subdir_ptr != head_ptr) { + printf("parent subdir:%p does not match %p\n", + parent_ptr->subdir_ptr, head_ptr); + exit(1); + } + } + if(parent_ptr == 0) { + if(head_ptr != dsk->dynapro_info_ptr->volume_ptr) { + printf("handle_write_dir %p %p %07x\n", parent_ptr, + head_ptr, dir_byte); + exit(1); + } + } + fileptr = dynapro_process_write_dir(dsk, parent_ptr, &head_ptr, + dir_byte); + if(fileptr == 0) { + // Directory is damaged. Free and erase it + dynapro_erase_free_dir(dsk, head_ptr); + head_ptr = 0; + } + //printf("handle_write_dir, process returned %p (was %p), parent:%p, " + // "save:%p\n", fileptr, head_ptr, parent_ptr, save_headptr); + if(parent_ptr) { + parent_ptr->subdir_ptr = head_ptr; + if(!fileptr) { + parent_ptr->damaged = 1; + dsk->dynapro_info_ptr->damaged = 1; + } + dyna_printf("hwd set parent %p %s subdir=%p\n", parent_ptr, + parent_ptr->unix_path, head_ptr); + } +} + +word32 +dynapro_process_write_file(Disk *dsk, Dynapro_file *fileptr) +{ + word32 ret; + + //printf("dynapro_process_write_file %p %s\n", fileptr, + // fileptr->unix_path); + + if(fileptr->subdir_ptr) { + printf("dynapro_handle_write_file, has subdir: %s\n", + fileptr->unix_path); + halt_printf("dynapro_handle_write_file, has subdir: %s\n", + fileptr->unix_path); + return 0; + } + + // First, unmap the file (the sapling/tree blocks may have changed). + dynapro_unmap_file(dsk, fileptr); + + // Then remap the blocks. This will copy the new data to buffer_ptr + + dynapro_debug_map(dsk, "handle_write_file, right before re-map"); + + ret = dynapro_map_file(dsk, fileptr, 1); + + // printf("dynapro_handle_write_file ending, ret:%d\n", ret); + + return ret; +} + +void +dynapro_handle_write_file(Disk *dsk, Dynapro_file *fileptr) +{ + word32 ret; + + ret = dynapro_process_write_file(dsk, fileptr); + if(ret == 0) { + dynapro_mark_damaged(dsk, fileptr); + } +} + +void +dynapro_handle_changed_entry(Disk *dsk, Dynapro_file *fileptr) +{ + // printf("handle_changed_entry with fileptr:%p\n", fileptr); + fileptr->damaged = 0; + if(fileptr->prodos_name[0] >= 0xe0) { + // Directory header, not valid to be called here + fileptr->damaged = 1; + dsk->dynapro_info_ptr->damaged = 1; + } else if(fileptr->prodos_name[0] >= 0xd0) { + // Directory entry + if(fileptr->subdir_ptr) { + dynapro_erase_free_dir(dsk, fileptr->subdir_ptr); + fileptr->subdir_ptr = 0; + } + dynapro_handle_write_dir(dsk, fileptr, 0, + (fileptr->key_block * 0x200UL) + 4); + } else { + dynapro_handle_write_file(dsk, fileptr); +#if 0 + printf("handle_changed_entry called handle_write_file for %p, " + "%s\n", fileptr, fileptr->unix_path); +#endif + } +} + +word32 +dynapro_write_to_unix_file(const char *unix_path, byte *data_ptr, word32 size) +{ + dword64 dret; + int fd; + + fd = open(unix_path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0x1b6); + if(fd < 0) { + printf("Open %s for writing failed\n", unix_path); + exit(1); + return 0; + } + dret = cfg_write_to_fd(fd, data_ptr, 0, size); + close(fd); + dyna_printf("dynapro_write_to_unix: %s size:%d, dret:%lld\n", + unix_path, size, dret); + + + if(size == 0) { + return 1; + } + return (word32)dret; +} + +void +dynapro_unmap_file(Disk *dsk, Dynapro_file *fileptr) +{ + Dynapro_file *this_fileptr; + Dynapro_map *map_ptr; + //const char *str; + word32 map_block, next_map_block, max_blocks; + int i; + + //printf("File %p: %s is unmapped\n", fileptr, fileptr->unix_path); + dynapro_debug_map(dsk, "start unmap file"); + + // Unmap all blocks to this file/dir + map_ptr = dsk->dynapro_info_ptr->block_map_ptr; + max_blocks = (word32)(dsk->dimage_size >> 9); + map_block = fileptr->map_first_block; + //printf(" map_block:%04x, fileptr:%p %s\n", map_block, fileptr, + // fileptr->unix_path); + fileptr->map_first_block = 0; + //printf(" unmap starting, map_block:%08x, max_blocks:%07x\n", + // map_block, max_blocks); + for(i = 0; i < 65536; i++) { + if((map_block == 0) || (map_block >= max_blocks)) { + break; + } + next_map_block = map_ptr[map_block].next_map_block; + this_fileptr = map_ptr[map_block].file_ptr; + if(this_fileptr != fileptr) { + //str = "??"; + if(this_fileptr) { + //str = this_fileptr->unix_path; + this_fileptr->damaged = 1; + } +#if 0 + printf("Found map[%04x]=%s while walking %s\n", + map_block, str, fileptr->unix_path); +#endif + } + map_ptr[map_block].file_ptr = 0; + map_ptr[map_block].next_map_block = 0; + map_ptr[map_block].modified = 0; + //printf(" just unmapped block %05x\n", map_block); + map_block = next_map_block; + } + + // printf(" unmap ending\n"); +} + +void +dynapro_unlink_file(Dynapro_file *fileptr) +{ + const char *str; + int ret, err; + + // Try to unlink unix_path + dyna_printf("Unlink %s (%p)\n", fileptr->unix_path, fileptr); + if(fileptr->unix_path == 0) { + printf("unix_path of %p is null!\n", fileptr); + exit(1); + } + if(fileptr->subdir_ptr != 0) { + printf("unlink_file %s, but subdirptr is valid!\n", + fileptr->unix_path); + exit(1); + } + + ret = unlink(fileptr->unix_path); + if(ret != 0) { + // Maybe it's a directory, rmdir + ret = rmdir(fileptr->unix_path); + } + if(ret != 0) { + // Cannot erase, try to rename + cfg_strncpy_dirname(&g_dynapro_path_buf[0], fileptr->unix_path, + DYNAPRO_PATH_MAX); + cfg_strlcat(&g_dynapro_path_buf[0], ".kegsrm_", + DYNAPRO_PATH_MAX); + str = cfg_str_basename(fileptr->unix_path); + cfg_strlcat(&g_dynapro_path_buf[0], str, DYNAPRO_PATH_MAX); + printf("Could not erase %s, renaming to: %s\n", + fileptr->unix_path, &g_dynapro_path_buf[0]); + ret = rename(fileptr->unix_path, &g_dynapro_path_buf[0]); + err = errno; + if(ret != 0) { + printf("Rename of %s failed, err:%d\n", + fileptr->unix_path, err); + } + } +} + +void +dynapro_erase_free_entry(Disk *dsk, Dynapro_file *fileptr) +{ + if(!fileptr) { + return; + } + dynapro_mark_damaged(dsk, fileptr); + + fileptr->next_ptr = 0; + if(fileptr != dsk->dynapro_info_ptr->volume_ptr) { + // Free everything--except the volume header + dyna_printf("erase_free_entry erasing %p since it != %p\n", + fileptr, dsk->dynapro_info_ptr->volume_ptr); + dynapro_free_file(fileptr, 1); + } +} + +void +dynapro_erase_free_dir(Disk *dsk, Dynapro_file *fileptr) +{ + Dynapro_file *nextptr, *parent_ptr, *save_fileptr; + + dyna_printf("dynapro_erase_free_dir of %p\n", fileptr); + if(fileptr == 0) { + return; + } + dyna_printf(" dynapro_erase_free_dir of %s\n", fileptr->unix_path); + dsk->dynapro_info_ptr->damaged = 1; + parent_ptr = fileptr->parent_ptr; + if(parent_ptr) { + if(parent_ptr->subdir_ptr) { + parent_ptr->damaged = 1; + } + } + save_fileptr = fileptr; + nextptr = fileptr->next_ptr; + fileptr->next_ptr = 0; + fileptr = nextptr; + while(fileptr) { + nextptr = fileptr->next_ptr; + dynapro_erase_free_entry(dsk, fileptr); + fileptr = nextptr; + } + dynapro_erase_free_entry(dsk, save_fileptr); +} + +void +dynapro_mark_damaged(Disk *dsk, Dynapro_file *fileptr) +{ + if(fileptr == 0) { + return; + } + dyna_printf("dynapro_mark_damaged: %s damaged\n", fileptr->unix_path); + fileptr->damaged = 1; + dsk->dynapro_info_ptr->damaged = 1; + dynapro_unmap_file(dsk, fileptr); + if(fileptr->subdir_ptr) { + dynapro_erase_free_dir(dsk, fileptr->subdir_ptr); + fileptr->subdir_ptr = 0; + } + + if((fileptr->prodos_name[0] >= 0xe0) && fileptr->parent_ptr) { + // We are a directory header, mark the directory entry of our + // parent as damaged (but don't actually damage it) + fileptr->parent_ptr->damaged = 1; + } else if(fileptr != dsk->dynapro_info_ptr->volume_ptr) { + dynapro_unlink_file(fileptr); + } +} + +int +dynapro_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size) +{ + Dynapro_info *info_ptr; + Dynapro_map *map_ptr; + Dynapro_file *fileptr; + byte *bptr; + word32 ui, block; + int num; + int i; + + // Return 1 if write was done. Return < 0 if an error occurs + + dyna_printf("\n"); + dyna_printf("------------------------------------------------\n"); + dyna_printf("dynapro_write to %08llx, size:%08x\n", doffset, size); + dynapro_debug_update(dsk); + + bptr = dsk->raw_data; + if((doffset + size) > dsk->dimage_size) { + printf("Write past end of disk, ignored\n"); + return -1; + } + for(ui = 0; ui < size; ui++) { +#if 0 + if((bptr[doffset + ui] != bufptr[ui]) && (diffs < 500)) { + printf("%07llx:%02x (was %02x)\n", doffset+ui, + bufptr[ui], bptr[doffset + ui]); + diffs++; + } +#endif + bptr[doffset + ui] = bufptr[ui]; + } + + info_ptr = dsk->dynapro_info_ptr; + if(info_ptr == 0) { + printf("dynapro_info_ptr==0\n"); + return -1; + } + + num = (size + 511) >> 9; + block = (word32)(doffset >> 9); + dyna_printf("Marking blocks %05x-%05x modified\n", block, + block + num - 1); + for(i = 0; i < num; i++) { + map_ptr = &(info_ptr->block_map_ptr[block + i]); + map_ptr->modified = 1; + } + for(i = 0; i < num; i++) { + map_ptr = &(info_ptr->block_map_ptr[block + i]); + if(!map_ptr->modified) { + continue; // Already cleared + } + fileptr = map_ptr->file_ptr; + if(fileptr == 0) { + continue; + } + if(fileptr->prodos_name[0] >= 0xe0) { + dynapro_handle_write_dir(dsk, fileptr->parent_ptr, + fileptr, fileptr->dir_byte); + } else { + dynapro_handle_write_file(dsk, fileptr); + } + } + + return 1; +} + + +void +dynapro_debug_update(Disk *dsk) +{ + dyna_printf("Writing out DYNAPRO_IMAGE, %p\n", dsk); +#if 0 + // This causes the file DYNAPRO_IMAGE to be written out as the raw + // image after any write to the Dynapro volume. This is for manual + // debugging. + dynapro_write_to_unix_file("DYNAPRO_IMAGE", dsk->raw_data, + (word32)dsk->dimage_size); +#endif +} + +void +dynapro_debug_map(Disk *dsk, const char *str) +{ + Dynapro_map *map_ptr; + Dynapro_file *lastfileptr, *fileptr; + const char *newstr; + int num_blocks; + int i; + + return; // HACK! + + num_blocks = (word32)((dsk->dimage_size + 511) >> 9); + map_ptr = dsk->dynapro_info_ptr->block_map_ptr; + lastfileptr = 0; + printf(" Showing map for %s, %05x blocks, %s\n", + dsk->dynapro_info_ptr->root_path, num_blocks, str); + for(i = 0; i < num_blocks; i++) { + fileptr = map_ptr[i].file_ptr; + if(fileptr != lastfileptr) { + newstr = ""; + if(fileptr) { + newstr = fileptr->unix_path; + } + printf(" %04x (%07x): %p %s\n", i, i << 9, fileptr, + newstr); + } + lastfileptr = fileptr; + } + printf("Recursive file map:\n"); + dynapro_debug_recursive_file_map(dsk->dynapro_info_ptr->volume_ptr, 1); +} + +void +dynapro_debug_recursive_file_map(Dynapro_file *fileptr, int start) +{ + if(!fileptr) { + return; + } + while(fileptr) { + printf(" file %p %s map_first_block:%05x, storage:%02x key:" + "%04x\n", fileptr, fileptr->unix_path, + fileptr->map_first_block, fileptr->prodos_name[0], + fileptr->key_block); + printf(" n:%p, sub:%p, eof:%06x, parent:%p dam:%d\n", + fileptr->next_ptr, fileptr->subdir_ptr, + fileptr->eof, fileptr->parent_ptr, fileptr->damaged); + if(fileptr->unix_path == 0) { + printf("Filename is invalid, exiting\n"); + exit(1); + } + if(!fileptr->parent_ptr && !start) { + printf("parent_ptr is 0, exiting\n"); + exit(1); + } + dynapro_debug_recursive_file_map(fileptr->subdir_ptr, 0); + start = 0; + fileptr = fileptr->next_ptr; + } +} + +word32 +dynapro_unix_to_prodos_time(const time_t *time_ptr) +{ + struct tm *tm_ptr; + word32 ymd, hours_mins, date_time; + int year; + + tm_ptr = localtime(time_ptr); + hours_mins = (tm_ptr->tm_hour << 8) | tm_ptr->tm_min; + year = tm_ptr->tm_year; // years since 1900 + if(year < 80) { + year = 80; + } else if(year >= 100) { + year -= 100; + if(year >= 80) { + year = 79; + } + } + ymd = (year << 9) | ((tm_ptr->tm_mon + 1) << 5) | (tm_ptr->tm_mday); + date_time = (ymd & 0xffff) | (hours_mins << 16); + // printf("Unix time:%s results in:%08x\n", asctime(tm_ptr), date_time); + + return date_time; +} + +int +dynapro_create_prodos_name(Dynapro_file *newfileptr, Dynapro_file *matchptr, + word32 storage_type) +{ + Dynapro_file *thisptr; + char *str; + word32 upper_lower; + int len, outpos, inpos, max_inpos, c, done, dot_pos, inc_pos; + int i; + +#if 0 + printf("dynapro_create_prodos_name to %s, match:%p, st:%03x\n", + newfileptr->unix_path, matchptr, storage_type); +#endif + + for(i = 0; i < 17; i++) { + newfileptr->prodos_name[i] = 0; + } + str = newfileptr->unix_path; + if(!str) { + return 0; + } + inpos = (int)strlen(str); + max_inpos = inpos + 1; + while(inpos >= 1) { + inpos--; + if(str[inpos] == '/') { + inpos++; + break; + } + } + if(storage_type == 0x50) { + // printf("max_inpos:%d, inpos:%d\n", max_inpos, inpos); + } + if((storage_type == 0x50) && ((max_inpos - inpos) > 12)) { + // Remove .applesingle extension + max_inpos -= 13; + } + //printf(" inpos:%d max_inpos:%d str:%s\n", inpos, max_inpos, + // &(str[inpos])); + outpos = 0; + while(outpos < (DYNAPRO_PATH_MAX - 1)) { + c = 0; + if(inpos < max_inpos) { + c = str[inpos++]; + } + g_dynapro_path_buf[outpos] = c; + if(c == 0) { + break; + } + outpos++; + if((c >= 'A') && (c <= 'Z')) { + continue; // This is legal + } + if((c >= 'a') && (c <= 'z')) { + continue; // Also legal + } + if((outpos > 1) && (c >= '0') && (c <= '9')) { + continue; // Also legal + } + if((outpos > 1) && (c == '.')) { + continue; // Also legal + } + // If this is the first character, make it "A" and continue + if(outpos == 1) { + g_dynapro_path_buf[0] = 'A'; + continue; + } + if((c == ',') || (c == '#')) { // ,ttxt,a$2000, ignore + outpos--; + break; // All done + } + + // This is not legal. Make it a '.' + if((c >= 0x20) && (c <= 0x7e)) { + g_dynapro_path_buf[outpos - 1] = '@'; // do '.' later + } else { + outpos--; // Ignore it + } + } + + g_dynapro_path_buf[outpos] = 0; + // printf(" initial path_buf:%s, %d\n", &g_dynapro_path_buf[0], outpos); + while((outpos >= 0) && (g_dynapro_path_buf[outpos-1] == '@')) { + // Remove trailing '@' since they are not useful + outpos--; + g_dynapro_path_buf[outpos] = 0; + } + for(i = 1; i < outpos; i++) { + // Convert '@' to '.' to make name legal + if(g_dynapro_path_buf[i] == '@') { + g_dynapro_path_buf[i] = '.'; + } + } + if(outpos == 0) { + // Not a valid file, just skip it + return 0; + } + // Now, it's valid. Squeeze it to 15 character but saving extension + len = (int)strlen(&g_dynapro_path_buf[0]); + if(len > 15) { + // Copy last 8 characters to be in positions 7..14 + for(i = 7; i < 16; i++) { + g_dynapro_path_buf[i] = g_dynapro_path_buf[len-15 + i]; + } + } + + len = (int)strlen(&g_dynapro_path_buf[0]); + if((len > 15) || (len == 0)) { + printf("Bad filename handling: %s\n", &g_dynapro_path_buf[0]); + return 0; + } + + // See if it conflicts with matchptr + thisptr = matchptr; + for(i = 0; i < 10000; i++) { + if(!thisptr || (thisptr == newfileptr)) { + thisptr = 0; + break; + } + //printf("Comparing %s to %s\n", &g_dynapro_path_buf[0], + // (char *)&(thisptr->prodos_name[1])); + len = (int)strlen(&g_dynapro_path_buf[0]); + if((len == (thisptr->prodos_name[0] & 0xf)) && + (cfgcasecmp(&g_dynapro_path_buf[0], + (char *)&(thisptr->prodos_name[1])) == 0)) { + dyna_printf(" that was a match\n"); + dot_pos = 0; + inc_pos = len - 1; + for(i = len - 2; i >= 1; i--) { + if(g_dynapro_path_buf[i] == '.') { + dot_pos = i; + } + } + if(len < 15) { + // Append "1" to the end + len++; + inc_pos = len - 1; + g_dynapro_path_buf[len] = 0; + g_dynapro_path_buf[len - 1] = '1'; + if(dot_pos > 1) { + for(i = len - 1; i >= dot_pos; i--) { + g_dynapro_path_buf[i] = + g_dynapro_path_buf[i-1]; + } + inc_pos = dot_pos; + } + g_dynapro_path_buf[inc_pos] = '1'; + } else if(dot_pos > 3) { + inc_pos = dot_pos - 1; + } + done = 0; + for(i = inc_pos; i >= 1; i--) { + c = g_dynapro_path_buf[i]; + c++; + if(c == ('9' + 1)) { + c = '0'; + } else if(c == ('z' + 1)) { + c = 'a'; + } else if(c == ('Z' + 1)) { + c = 'A'; + } else { + done = 1; + } + g_dynapro_path_buf[i] = c; + if(done) { + break; + } + } + thisptr = matchptr; + } else { + thisptr = thisptr->next_ptr; + } + } + if(thisptr) { + // File could not be made unique + printf("Could not make a unique ProDOS filename: %s\n", + newfileptr->unix_path); + return 0; + } + + upper_lower = 0; + for(i = 0; i < len; i++) { + c = g_dynapro_path_buf[i]; + if((c >= 'a') && (c <= 'z')) { + c = c - 'a' + 'A'; + upper_lower |= 0x8000 | (0x4000 >> i); + } + newfileptr->prodos_name[1 + i] = c; + } + newfileptr->prodos_name[0] = len | storage_type; + newfileptr->upper_lower = upper_lower; + + return len; +} + +Dynapro_file * +dynapro_new_unix_file(const char *path, Dynapro_file *parent_ptr, + Dynapro_file *match_ptr, word32 storage_type) +{ + Dynapro_file *fileptr; + int len; + int i; + +#if 0 + printf("dynapro_new_unix_file for %s, parent:%p, m:%p, st:%03x\n", + path, parent_ptr, match_ptr, storage_type); +#endif + fileptr = dynapro_alloc_file(); + if(!fileptr) { + return 0; + } + + fileptr->next_ptr = 0; + fileptr->parent_ptr = parent_ptr; + fileptr->subdir_ptr = 0; + fileptr->buffer_ptr = 0; + fileptr->unix_path = kegs_malloc_str(path); + for(i = 0; i < 17; i++) { + fileptr->prodos_name[i] = 0; + } + fileptr->dir_byte = 0; + fileptr->eof = 0; + fileptr->blocks_used = 0; + fileptr->creation_time = 0; + fileptr->lastmod_time = 0; + fileptr->upper_lower = 0; + fileptr->key_block = 0; + fileptr->aux_type = 0; + fileptr->header_pointer = 0; + fileptr->map_first_block = 0; + fileptr->file_type = 0x0f; // Default to "DIR" + fileptr->modified_flag = 0; + fileptr->damaged = 0; + + len = (int)strlen(fileptr->unix_path); + for(i = len - 1; i >= 0; i--) { + if(fileptr->unix_path[i] == '/') { + fileptr->unix_path[i] = 0; // Strip trailing / + } else { + break; + } + } + + if(storage_type < 0xd0) { + storage_type = dynatype_detect_file_type(fileptr, + fileptr->unix_path, storage_type); + } + + len = dynapro_create_prodos_name(fileptr, match_ptr, storage_type); + if(len == 0) { + printf("Could not create prodos name for: %s\n", path); + free(fileptr); + return 0; + } + +#if 0 + printf("dynapro_create_new_unix_file: %s prodos:%s, st:%02x, ft:%02x, " + "aux:%04x\n", fileptr->unix_path, &(fileptr->prodos_name[1]), + fileptr->prodos_name[0], fileptr->file_type, fileptr->aux_type); +#endif + return fileptr; +} + +int +dynapro_create_dir(Disk *dsk, char *unix_path, Dynapro_file *parent_ptr, + word32 dir_byte) +{ + struct stat stat_buf; + struct dirent *direntptr; + DIR *opendirptr; + Dynapro_file *fileptr, *head_ptr, *prev_ptr; + mode_t fmt; + word32 storage_type, val; + int ret; + + // Create a directory entry at dir_byte first +#if 0 + printf("\n"); + printf("dynapro_add_files to %s, %p dir_byte:%08x\n", unix_path, + parent_ptr, dir_byte); +#endif + + storage_type = 0xe0; // Directory header + if(dir_byte < 0x600) { // Block 2: volume header + storage_type = 0xf0; + } + head_ptr = dynapro_new_unix_file(unix_path, parent_ptr, 0, + storage_type); + if(parent_ptr) { + parent_ptr->subdir_ptr = head_ptr; +#if 0 + printf("set parent %s subdir_ptr=%p\n", parent_ptr->unix_path, + head_ptr); +#endif + } + if(dsk->dynapro_info_ptr->volume_ptr == 0) { + dsk->dynapro_info_ptr->volume_ptr = head_ptr; + } + if(head_ptr == 0) { + printf("new_file returned 0, skipping %s\n", unix_path); + return dir_byte; + } + head_ptr->key_block = dir_byte >> 9; + head_ptr->aux_type = 0x0d27; // 0x27,0x0d + head_ptr->file_type = 0x75; // Directory header type + if(storage_type >= 0xf0) { + head_ptr->file_type = 0x00; + } + ret = cfg_stat(unix_path, &stat_buf, 0); + if(ret != 0) { + printf("stat %s ret %d, errno:%d\n", unix_path, ret, errno); + return 0; + } + head_ptr->creation_time = dynapro_unix_to_prodos_time( + &stat_buf.st_ctime); + + dir_byte = dynapro_add_file_entry(dsk, head_ptr, 0, dir_byte, 0); + + opendirptr = opendir(unix_path); + if(opendirptr == 0) { + printf("Could not open %s as a dir\n", unix_path); + return 0; + } + prev_ptr = head_ptr; + while(1) { + direntptr = readdir(opendirptr); + if(direntptr == 0) { + break; + } + if(direntptr->d_name[0] == '.') { + continue; // Ignore all '.' files + } + dynapro_join_path_and_file(&(g_dynapro_path_buf[0]), unix_path, + direntptr->d_name, DYNAPRO_PATH_MAX); + ret = cfg_stat(&(g_dynapro_path_buf[0]), &stat_buf, 0); + if(ret != 0) { + printf("stat %s ret %d, errno:%d\n", + &g_dynapro_path_buf[0], ret, errno); + continue; // skip it + } + + fmt = stat_buf.st_mode & S_IFMT; + storage_type = 0; + if(fmt == S_IFDIR) { + // Ignore symlinks to directories (since they may point + // outside the base directory, and so dynamically + // removing files could be a security issue). + ret = cfg_stat(&g_dynapro_path_buf[0], &stat_buf, 1); + if(ret != 0) { + printf("lstat %s ret %d, errno:%d\n", + &g_dynapro_path_buf[0], ret, errno); + continue; + } + storage_type = 0xd0; // Directory + } else if(fmt != S_IFREG) { + continue; // Skip this + } +#if 0 + printf("GOT file: %s, is_dir:%d (%s), parent:%p\n", + &(g_dynapro_path_buf[0]), is_dir, direntptr->d_name, + parent_ptr); +#endif + fileptr = dynapro_new_unix_file(&(g_dynapro_path_buf[0]), + head_ptr, head_ptr->next_ptr, storage_type); + if(fileptr == 0) { + closedir(opendirptr); + return 0; + } + prev_ptr->next_ptr = fileptr; + fileptr->key_block = dynapro_find_free_block(dsk); + fileptr->creation_time = dynapro_unix_to_prodos_time( + &stat_buf.st_ctime); + fileptr->lastmod_time = dynapro_unix_to_prodos_time( + &stat_buf.st_mtime); + if(fileptr->key_block == 0) { + printf("Allocating directory block failed\n"); + closedir(opendirptr); + return 0; + } + fileptr->blocks_used = 1; + fileptr->eof = 1*0x200; + dir_byte = dynapro_add_file_entry(dsk, fileptr, head_ptr, + dir_byte, 0x27); + if(dir_byte == 0) { + closedir(opendirptr); + return 0; + } + if(fmt == S_IFDIR) { + val = dynapro_create_dir(dsk, fileptr->unix_path, + fileptr, (fileptr->key_block << 9) + 4); + if(val == 0) { + closedir(opendirptr); + return 0; + } + } else { + val = dynapro_file_from_unix(dsk, fileptr); + if(val == 0) { + closedir(opendirptr); + return 0; + } + } + prev_ptr = fileptr; + } + + closedir(opendirptr); + return dir_byte; +} + +word32 +dynapro_add_file_entry(Disk *dsk, Dynapro_file *fileptr, Dynapro_file *head_ptr, + word32 dir_byte, word32 inc) +{ + Dynapro_file *parent_ptr; + byte *bptr, *pkeyptr; + word32 storage_type, val, ent, new_dir_blk, new_dir_byte; + word32 header_pointer; + int i; + +#if 0 + printf("dynapro_add_file_entry: %p %p %s head:%p dir_byte:%08x " + "inc:%03x\n", dsk, fileptr, fileptr->unix_path, head_ptr, + dir_byte, inc); +#endif + bptr = dsk->raw_data; + if(((dir_byte & 0x1ff) + inc + inc) >= 0x200) { + // This entry will not fit in this directory block. + // Try to step to next block, otherwise allocate a new one + new_dir_byte = dir_byte & -0x200L; + new_dir_blk = dynapro_get_word16(&bptr[new_dir_byte + 2]); + dyna_printf(" Entry does not fit, new_dir_blk:%04x\n", + new_dir_blk); + if(new_dir_blk != 0) { + // Follow to the next block + dir_byte = (new_dir_blk * 0x200) + 4; + } else if(dir_byte < (6 * 0x200)) { + // Otherwise, allocate a new block (not for volume dir) + // This is a volume header, always 4 blocks, don't + // allocate any more, this is now full + printf("Too many file in volume directory\n"); + return 0; // Out of space + } else { + new_dir_blk = dynapro_find_free_block(dsk); + if(new_dir_blk == 0) { + return 0; + } + new_dir_byte = new_dir_blk * 512; + dynapro_set_word16(&bptr[new_dir_byte], dir_byte >> 9); + dynapro_set_word16(&bptr[new_dir_byte + 2], 0); + dir_byte = (dir_byte >> 9) << 9; + dynapro_set_word16(&bptr[dir_byte + 2], new_dir_blk); + dir_byte = new_dir_byte + 4; + if(!head_ptr) { + dyna_printf("No head:%s\n", fileptr->unix_path); + } + parent_ptr = head_ptr->parent_ptr; + if(!parent_ptr) { + printf("No parent: %s\n", fileptr->unix_path); + return 0; + } + parent_ptr->blocks_used++; + parent_ptr->eof += 0x200; + new_dir_byte = parent_ptr->dir_byte; + if(new_dir_byte == 0) { + printf("Invalid dir_byte for %s\n", + parent_ptr->unix_path); + return 0; + } + dynapro_set_word16(&bptr[new_dir_byte + 0x13], + parent_ptr->blocks_used); + dynapro_set_word24(&bptr[new_dir_byte + 0x15], + parent_ptr->eof); + } + } else { + dir_byte += inc; + } + bptr = &(dsk->raw_data[dir_byte]); + + fileptr->dir_byte = dir_byte; + for(i = 0; i < 0x27; i++) { + bptr[i] = 0; + } + for(i = 0; i < 16; i++) { + bptr[i] = fileptr->prodos_name[i]; // [0] = len,storage_t + } + bptr[0x10] = fileptr->file_type; + dynapro_set_word16(&bptr[0x11], fileptr->key_block); + dynapro_set_word16(&bptr[0x13], fileptr->blocks_used); + dynapro_set_word24(&bptr[0x15], fileptr->eof); + dynapro_set_word32(&bptr[0x18], fileptr->creation_time); + // creation date&time + bptr[0x1c] = fileptr->upper_lower & 0xff; // Version + bptr[0x1d] = fileptr->upper_lower >> 8; // Min_Version + bptr[0x1e] = 0xe3; // Access + dynapro_set_word16(&bptr[0x1f], fileptr->aux_type); + storage_type = bptr[0]; + if(storage_type >= 0xf0) { // Volume header + dynapro_set_word16(&bptr[0x11], 0); + fileptr->lastmod_time = 0x00060000; + // low 16 bits: file_count, upper 16 bits: bitmap_block + fileptr->header_pointer = (word32)(dsk->raw_dsize >> 9); + // Total blocks + dynapro_set_word16(&bptr[0x1c], 0x0005); + dynapro_set_word16(&bptr[0x16], fileptr->upper_lower); + } else if(storage_type >= 0xe0) { // Directory header + dynapro_set_word16(&bptr[0x11], 0); + dynapro_set_word16(&bptr[0x1c], 0x0005); + parent_ptr = fileptr->parent_ptr; // subdir entry + if(parent_ptr == 0) { + printf("parent_ptr of %s is 0\n", fileptr->unix_path); + return 0; + } + val = parent_ptr->dir_byte >> 9; + fileptr->lastmod_time = (val << 16); // Parent block + val = parent_ptr->dir_byte & 0x1ff; + ent = (val - 4) / 0x27; + fileptr->header_pointer = 0x2700 | (ent + 1); + } else { + // Directory entry, or normal file + if(head_ptr == 0) { + printf("head_ptr of %s is 0\n", fileptr->unix_path); + return 0; + } + header_pointer = head_ptr->key_block; + fileptr->header_pointer = header_pointer; + dynapro_set_word16(&bptr[0x25], header_pointer); + pkeyptr = &(dsk->raw_data[header_pointer << 9]); + val = head_ptr->lastmod_time + 1; + head_ptr->lastmod_time = val; + dynapro_set_word16(&pkeyptr[4 + 0x21], val); // File count + } + dynapro_set_word32(&bptr[0x21], fileptr->lastmod_time); + // Last Modified date&time (or header info) + dynapro_set_word16(&bptr[0x25], fileptr->header_pointer); +#if 0 + printf("Set dir_byte %07x=%04x\n", dir_byte + 0x25, + fileptr->header_pointer); +#endif + + return dir_byte; +} + +// When creating sparse files, always ensure first block is not sparse. GS/OS +// does not treat the first block as sparse, it will actually read block 0 +// This handles normal files, and one fork of a forked file +word32 +dynapro_fork_from_unix(Disk *dsk, byte *fptr, word32 *storage_type_ptr, + word32 key_block, dword64 dsize) +{ + byte *bptr; + word32 sap_block, tree_block, sap_byte, tree_byte, sparse, block_num; + word32 num_blocks, blocks_used, block_off, storage_type; + int num_bytes; + int i; + + bptr = &(dsk->raw_data[0]); + *storage_type_ptr = 0; + sap_block = 0; + tree_block = 0; + num_blocks = (word32)((dsize + 511) >> 9); + if(num_blocks == 0) { // 0-length file + num_blocks = 1; + } else if(num_blocks > 0x8000) { // >= 16MB (32K*512) + printf("File is too large, failing\n"); + return 0; + } + block_off = 0; + blocks_used = 1; + while(block_off < num_blocks) { + sparse = (block_off > 0); // sparse=0 for first block + for(i = 0; i < 0x200; i++) { + if(fptr[(block_off << 9) + i] != 0) { + sparse = 0; + break; + } + } + if(sparse) { + block_off++; + continue; + } + + if((tree_block == 0) && (num_blocks > 256)) { + tree_block = dynapro_find_free_block(dsk); + if(tree_block == 0) { + return 0; + } + blocks_used++; + } + tree_byte = (tree_block << 9) + ((block_off >> 8) & 0xff); + if(tree_block) { + sap_block = bptr[tree_byte + 0] | + (bptr[tree_byte + 256] << 8); + } + if((sap_block == 0) && (num_blocks > 1)) { + sap_block = dynapro_find_free_block(dsk); + if(sap_block == 0) { + return 0; + } + blocks_used++; + if(tree_block) { + bptr[tree_byte + 0] = sap_block; + bptr[tree_byte + 256] = sap_block >> 8; + } + } + if(block_off == 0) { + block_num = key_block; + } else { + block_num = dynapro_find_free_block(dsk); + if(block_num == 0) { + return 0; + } + blocks_used++; + } + sap_byte = (sap_block << 9) | (block_off & 0xff); + if(sap_block) { + bptr[sap_byte + 0] = block_num; + bptr[sap_byte + 256] = block_num >> 8; + } + + num_bytes = 0x200; + if(block_off == (dsize >> 9)) { // Last block + num_bytes = dsize & 0x1ff; + } + for(i = 0; i < num_bytes; i++) { + bptr[(block_num << 9) + i] = fptr[(block_off << 9) + i]; + } + + block_off++; + } + + storage_type = 0x10; + if(tree_block) { + storage_type = 0x30; + key_block = tree_block; + } else if(sap_block) { + storage_type = 0x20; + key_block = sap_block; + } + *storage_type_ptr = storage_type; + return (blocks_used << 16) | key_block; +} + +word32 +dynapro_file_from_unix(Disk *dsk, Dynapro_file *fileptr) +{ + byte *bptr, *fptr; + dword64 dsize; + word32 storage_type, blocks_out, dir_byte; + + fptr = dynapro_malloc_file(fileptr->unix_path, &dsize, 0x200); + fileptr->eof = (word32)dsize; +#if 0 + printf("file_from_unix %s, size:%08llx, file_type:%02x, dir_byte:" + "%07x, storage:%02x\n", fileptr->unix_path, dsize, + fileptr->file_type, fileptr->dir_byte, fileptr->prodos_name[0]); +#endif + storage_type = 0; + if((fileptr->prodos_name[0] & 0xf0) == 0x50) { + // .applesingle file with data and/or resource forks + blocks_out = applesingle_from_unix(dsk, fileptr, fptr, dsize); + } else { + // Normal file + fileptr->prodos_name[0] = (fileptr->prodos_name[0] & 0xf); + + blocks_out = dynapro_fork_from_unix(dsk, fptr, &storage_type, + fileptr->key_block, dsize); + } + free(fptr); + fileptr->prodos_name[0] |= storage_type; + fileptr->key_block = blocks_out & 0xffff; + fileptr->blocks_used = (blocks_out >> 16) & 0xffff; + + // Update dir_byte information for this file + dir_byte = fileptr->dir_byte; + if(dir_byte == 0) { + dyna_printf("dir_byte is 0 for %s\n", fileptr->unix_path); + } + bptr = &(dsk->raw_data[dir_byte]); + bptr[0] = fileptr->prodos_name[0]; + bptr[0x10] = fileptr->file_type; + dynapro_set_word16(&bptr[0x11], fileptr->key_block); + dynapro_set_word16(&bptr[0x13], fileptr->blocks_used); + dynapro_set_word24(&bptr[0x15], fileptr->eof); + dynapro_set_word16(&bptr[0x1f], fileptr->aux_type); +#if 0 + printf("Set %s dir_byte:%07x+0x10=%02x (file_type)\n", + fileptr->unix_path, dir_byte, fileptr->file_type); +#endif + + return blocks_out; +} + +word32 +dynapro_prep_image(Disk *dsk, const char *dir_path, word32 num_blocks) +{ + Dynapro_info *infoptr; + byte *bptr; + word32 bitmap_size_bytes, bitmap_size_blocks; + int pos; + word32 ui; + int i; + + dsk->raw_data = calloc(num_blocks, 512); + if(dsk->raw_data == 0) { + dynapro_error(dsk, "Could not allocate %d bytes\n", + num_blocks * 512); + return 0; + } + dsk->dimage_size = num_blocks * 512LL; + dsk->dimage_start = 0; + dsk->raw_dsize = num_blocks * 512LL; + + bptr = &(dsk->raw_data[0]); + for(i = 0; i < 512; i++) { + bptr[i] = g_prodos_block0[i]; + } + + // Directory is from blocks 2 through 5. Set up prev and next ptrs + bptr = &(dsk->raw_data[2 * 0x200]); + for(i = 0; i < 3; i++) { // Blocks 2,3,4 (or 3,4,5) + dynapro_set_word16(&bptr[(i + 1)*0x200], i + 2); // Prev_blk + dynapro_set_word16(&bptr[i*0x200 + 2], i + 3); // Next_blk + } + + // Calculate bitmap to go in blocks 6... + bitmap_size_bytes = (num_blocks + 7) >> 3; + bitmap_size_blocks = (bitmap_size_bytes + 512 - 1) >> 9; + bptr = &(dsk->raw_data[6 * 512]); // Block 6 + bptr[0] = 0; + for(ui = (6 + bitmap_size_blocks); ui < num_blocks; ui++) { + pos = (ui >> 3); + bptr[pos] |= (0x80U >> (ui & 7)); + } + + infoptr = calloc(sizeof(Dynapro_info), 1); + if(!infoptr) { + return 0; + } + infoptr->root_path = kegs_malloc_str(dir_path); + infoptr->volume_ptr = 0; + infoptr->block_map_ptr = calloc(num_blocks * sizeof(Dynapro_map), 1); + infoptr->damaged = 0; + if((infoptr->root_path == 0) || (infoptr->block_map_ptr == 0)) { + dynapro_error(dsk, "Could not allocate memory!\n"); + return 0; + } + dsk->dynapro_info_ptr = infoptr; + return 1; +} + +word32 +dynapro_map_one_file_block(Disk *dsk, Dynapro_file *fileptr, word32 block_num, + word32 file_offset, word32 eof) +{ + Dynapro_info *info_ptr; + Dynapro_map *map_ptr; + byte *buffer_ptr; + word32 size, size_to_end; + + info_ptr = dsk->dynapro_info_ptr; + if(!info_ptr || (block_num >= (dsk->dimage_size >> 9))) { + printf(" mapping file %s, block %04x is invalid\n", + fileptr->unix_path, block_num); + return 0; + } + if(info_ptr->block_map_ptr == 0) { + return 0; + } + if(block_num == 0) { + return 1; + } + map_ptr = &(info_ptr->block_map_ptr[block_num]); + if((map_ptr->file_ptr != 0) || (map_ptr->next_map_block != 0)) { + dyna_printf("Mapping %s to block %04x, already has file_ptr:" + "%p, next_map:%04x, mod:%d\n", fileptr->unix_path, + block_num, map_ptr->file_ptr, map_ptr->next_map_block, + map_ptr->modified); + if(map_ptr->file_ptr) { + dyna_printf(" Existing file: %s\n", + map_ptr->file_ptr->unix_path); + } + return 0; + } + //printf(" map file %s block %05x off:%08x\n", fileptr->unix_path, + // block_num, file_offset); + + map_ptr->next_map_block = fileptr->map_first_block; + fileptr->map_first_block = block_num; + map_ptr->modified = 0; + map_ptr->file_ptr = fileptr; + + if(file_offset >= eof) { + return 1; // This block was an "overhead" block + } + + buffer_ptr = fileptr->buffer_ptr; + if(buffer_ptr) { + // Copy this block in at file_offset + size = 0x200; + size_to_end = eof - file_offset; + if(size_to_end < size) { + size = size_to_end; + } +#if 0 + printf("mofb: Write to %p + %07x from block %04x, size:%04x\n", + buffer_ptr, file_offset, block_num, size); +#endif + memcpy(buffer_ptr + file_offset, + &(dsk->raw_data[block_num * 0x200]), size); + } + return 1; +} + +word32 +dynapro_map_file_blocks(Disk *dsk, Dynapro_file *fileptr, word32 block_num, + int level, word32 file_offset, word32 eof) +{ + byte *bptr; + word32 entry_inc, tmp, ret; + int i; + +#if 0 + printf("dynapro_map_file_blocks %s block_num %05x level:%d off:%08x\n", + fileptr->unix_path, block_num, level, file_offset); +#endif + if(level == 0) { + return 0; // Bad value, should not happen + } + if(level == 1) { + return dynapro_map_one_file_block(dsk, fileptr, block_num, + file_offset, eof); + } + ret = dynapro_map_one_file_block(dsk, fileptr, block_num, 1U << 30, 0); + if(ret == 0) { + return ret; + } + entry_inc = 512; + if(level == 3) { // Tree + entry_inc = 256*512; + } + bptr = &(dsk->raw_data[block_num * 0x200]); + for(i = 0; i < 256; i++) { + tmp = bptr[i] + (bptr[256 + i] << 8); + if(tmp == 0) { + continue; + } + ret = dynapro_map_file_blocks(dsk, fileptr, tmp, level - 1, + file_offset + i*entry_inc, eof); + if(ret == 0) { + return ret; + } + } + dynapro_debug_map(dsk, "post map_file_blocks"); + + return 1; +} + +word32 +dynapro_map_file(Disk *dsk, Dynapro_file *fileptr, int do_file_data) +{ + word32 block_num, ret; + int level; + + level = (fileptr->prodos_name[0] >> 4) & 0xf; + block_num = fileptr->key_block; + if(level == 5) { // Forked file + return applesingle_map_from_prodos(dsk, fileptr, + do_file_data); + } else if((level < 0) || (level >= 4)) { + printf("Storage_type: %02x for %s is bad\n", level, + fileptr->unix_path); + return 0; + } + + fileptr->buffer_ptr = 0; + if(do_file_data) { + // Create a place for data. We will free before returning + fileptr->buffer_ptr = calloc(1, fileptr->eof + 0x200); + if(fileptr->buffer_ptr == 0) { + printf("malloc failed!\n"); + return 0; + } + } + // Must not return now before free'ing fileptr->buffer_ptr! + + ret = dynapro_map_file_blocks(dsk, fileptr, block_num, level, 0, + fileptr->eof); + + // printf(" dynapro_map_file, map_file_blocks ret:%04x\n", ret); + if((ret != 0) && (do_file_data)) { + // Then, write buffer_ptr to the unix file + ret = dynapro_write_to_unix_file(fileptr->unix_path, + fileptr->buffer_ptr, fileptr->eof); + // printf(" map_file, write_to_unix_file ret:%04x\n", ret); + } + + // And free the buffer_ptr + free(fileptr->buffer_ptr); + fileptr->buffer_ptr = 0; + + return ret; +} + +word32 +dynapro_map_dir_blocks(Disk *dsk, Dynapro_file *fileptr) +{ + byte *bptr; + word32 block_num, ret; + int cnt; + + // Loop over all directory blocks marking the map + block_num = fileptr->key_block; + if(block_num == 0) { + printf("dynapro_map_dir_blocks, block_num is 0\n"); + return 0; + } + bptr = &(dsk->raw_data[0]); + cnt = 0; + fileptr->map_first_block = 0; + while(block_num != 0) { + ret = dynapro_map_one_file_block(dsk, fileptr, block_num, + 1U << 30, 0); + if(ret == 0) { + printf("dynapro_map_dir_on_block, ret 0, block:%04x\n", + block_num); + return 0; + } + block_num = dynapro_get_word16(&bptr[(block_num * 0x200) + 2]); + cnt++; + if(cnt > 1000) { + printf("Directory had loop in it, error\n"); + return 0; + } + } + + dynapro_debug_map(dsk, "post map_dir_blocks"); + return 1; +} + +word32 +dynapro_build_map(Disk *dsk, Dynapro_file *fileptr) +{ + word32 ret; + + if(fileptr == 0) { + return 0; + } + + // printf("### dynapro_build_map for dir:%s\n", fileptr->unix_path); + + // fileptr points to a directory header (volume or subdir). Walk + // all siblings and build a map + ret = 1; + while(fileptr && ret) { + if(fileptr->prodos_name[0] >= 0xe0) { + // Directory/Volume header + ret = dynapro_map_dir_blocks(dsk, fileptr); + } else if(fileptr->subdir_ptr) { + // Recurse to handle subdirectory + ret = dynapro_build_map(dsk, fileptr->subdir_ptr); + } else { + ret = dynapro_map_file(dsk, fileptr, 0); + } + fileptr = fileptr->next_ptr; + } + dynapro_debug_map(dsk, "post build_map"); + return ret; +} + +int +dynapro_mount(Disk *dsk, char *dir_path, word32 num_blocks) +{ + word32 ret; + +#if 0 + printf("dynapro_mount: %p, %s %08x\n", dsk, dir_path, num_blocks); +#endif + if(num_blocks >= 65536) { + num_blocks = 65535; + } + ret = dynapro_prep_image(dsk, dir_path, num_blocks); + if(ret == 0) { + return -1; + } + + ret = dynapro_create_dir(dsk, dir_path, 0, 0x404); // Block 2, +4 + // printf("dynapro_mount will end with ret:%05x\n", ret); + if(ret != 0) { + ret = dynapro_build_map(dsk, dsk->dynapro_info_ptr->volume_ptr); + } + // dynapro_debug_update(dsk); + if(ret == 0) { + dynapro_error(dsk, "Folder too large. dynapro_build_map " + "ret:0\n"); + } else { + ret = dynapro_validate_disk(dsk); + } + if(ret == 0) { + dynapro_error(dsk, "dynapro_validate_disk ret:0\n"); + dynapro_free_dynapro_info(dsk); + return -1; + } +#ifndef _WIN32 + setvbuf(stdout, 0, _IOLBF, 0); +#endif + dsk->fd = 0; + return 0; +} + diff --git a/gsplus/src/engine.h b/gsplus/src/engine.h new file mode 100644 index 0000000..58c5145 --- /dev/null +++ b/gsplus/src/engine.h @@ -0,0 +1,84 @@ +// "@(#)$KmKId: engine.h,v 1.9 2023-09-11 12:55:16+00 kentd Exp $" + +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +int +ENGINE_TYPE (Engine_reg *engine_ptr) +{ + register byte *ptr; + byte *arg_ptr; + Pc_log *tmp_pc_ptr; + Fplus *fplus_ptr; + byte *stat; + dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; + register word32 kpc, acc, xreg, yreg, direct, psr, zero, neg7, addr; + word32 wstat, arg, stack, dbank, opcode, addr_latch, tmp1, tmp2; + word32 getmem_tmp, save_addr, pull_tmp, tmp_bytes, dummy1; + + tmp_pc_ptr = 0; + dummy1 = 0; + if(tmp_pc_ptr || dummy1) { // "use" tmp_pc_ptr to avoid warning + } + + kpc = engine_ptr->kpc; + acc = engine_ptr->acc; + xreg = engine_ptr->xreg; + yreg = engine_ptr->yreg; + stack = engine_ptr->stack; + dbank = engine_ptr->dbank; + direct = engine_ptr->direct; + psr = engine_ptr->psr; + fplus_ptr = engine_ptr->fplus_ptr; + zero = !(psr & 2); + neg7 = psr; + + dplus_1 = fplus_ptr->dplus_1; + dplus_x_m1 = fplus_ptr->dplus_x_minus_1; + dfcyc = engine_ptr->dfcyc; + + g_ret1 = 0; + + while(dfcyc <= g_dcycles_end) { + + FETCH_OPCODE; + + LOG_PC_MACRO(); + + switch(opcode) { + default: + halt_printf("acc8 unk op: %02x\n", opcode); + arg = 9 +#include "defs_instr.h" + * 2; + break; +#include "instable.h" + break; + } + LOG_PC_MACRO2(); + } + + engine_ptr->kpc = kpc; + engine_ptr->acc = acc; + engine_ptr->xreg = xreg; + engine_ptr->yreg = yreg; + engine_ptr->stack = stack; + engine_ptr->dbank = dbank; + engine_ptr->direct = direct; + engine_ptr->dfcyc = dfcyc; + + psr = psr & (~0x82); + psr |= (neg7 & 0x80); + psr |= ((!zero) << 1); + + engine_ptr->psr = psr; + + return g_ret1; +} diff --git a/gsplus/src/engine_c.c b/gsplus/src/engine_c.c new file mode 100644 index 0000000..04df05e --- /dev/null +++ b/gsplus/src/engine_c.c @@ -0,0 +1,958 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2025 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include "defc.h" + +// PSR[8:0] is E_NVMX_DIZC + +extern int g_limit_speed; +extern int g_halt_sim; +extern int g_engine_recalc_event; +extern int g_code_red; +extern int g_ignore_halts; +extern int g_user_halt_bad; +extern dword64 g_dcycles_end; +extern dword64 g_last_vbl_dfcyc; +extern dword64 g_cur_dfcyc; +extern int g_wait_pending; +extern int g_irq_pending; +extern int g_num_brk; +extern int g_num_cop; +extern int g_emul_6502_ind_page_cross_bug; +extern byte *g_slow_memory_ptr; +extern byte *g_memory_ptr; +extern byte *g_rom_fc_ff_ptr; +extern byte *g_rom_cards_ptr; +extern byte *g_dummy_memory1_ptr; + +extern int g_num_breakpoints; +extern Break_point g_break_pts[]; + +extern Kimage g_debugwin_kimage; + +extern word32 g_log_pc_enable; +extern Pc_log *g_log_pc_ptr; +extern Pc_log *g_log_pc_start_ptr; +extern Pc_log *g_log_pc_end_ptr; + +extern Data_log *g_log_data_ptr; +extern Data_log *g_log_data_start_ptr; +extern Data_log *g_log_data_end_ptr; + +int g_ret1 = 0; + +int size_tab[] = { +#include "size_c.h" +}; + +int bogus[] = { + 0, +#include "op_routs.h" +}; + +#define INC_KPC_1 kpc = (kpc & 0xff0000) + ((kpc + 1) & 0xffff); +#define INC_KPC_2 kpc = (kpc & 0xff0000) + ((kpc + 2) & 0xffff); +#define INC_KPC_3 kpc = (kpc & 0xff0000) + ((kpc + 3) & 0xffff); +#define INC_KPC_4 kpc = (kpc & 0xff0000) + ((kpc + 4) & 0xffff); + +#define CYCLES_PLUS_1 dfcyc += dplus_1; +#define CYCLES_PLUS_2 dfcyc += dplus_1 * 2; +#define CYCLES_PLUS_3 dfcyc += dplus_1 * 3; +#define CYCLES_PLUS_4 dfcyc += dplus_1 * 4; +#define CYCLES_PLUS_5 dfcyc += dplus_1 * 5; +#define CYCLES_MINUS_1 dfcyc -= dplus_1; +#define CYCLES_MINUS_2 dfcyc -= dplus_1 * 2; + +#define FCYCLES_ROUND dfcyc = dfcyc + dplus_x_m1; \ + dfcyc = (dfcyc >> 16) << 16; + + +#define GET_1BYTE_ARG arg = arg_ptr[1]; +#define GET_2BYTE_ARG arg = arg_ptr[1] + (arg_ptr[2] << 8); +#define GET_3BYTE_ARG arg = arg_ptr[1] + (arg_ptr[2] << 8) + (arg_ptr[3]<<16); + +#define LOG_DATA_MACRO_ACT(in_addr, in_val, in_size, in_stat) \ + g_log_data_ptr->dfcyc = dfcyc; \ + g_log_data_ptr->stat = in_stat; \ + g_log_data_ptr->addr = in_addr; \ + g_log_data_ptr->val = in_val; \ + g_log_data_ptr->size = in_size; \ + g_log_data_ptr++; \ + if(g_log_data_ptr >= g_log_data_end_ptr) { \ + g_log_data_ptr = g_log_data_start_ptr; \ + } + +/* HACK HACK HACK */ +#define UPDATE_PSR(dummy, old_psr) \ + if(psr & 0x100) { \ + psr |= 0x30; \ + stack = 0x100 + (stack & 0xff); \ + } \ + if((~old_psr & psr) & 0x10) { \ + xreg = xreg & 0xff; \ + yreg = yreg & 0xff; \ + } \ + if(((psr & 4) == 0) && g_irq_pending) { \ + FINISH(RET_IRQ, 0); \ + } \ + if((old_psr ^ psr) & 0x20) { \ + FINISH(RET_PSR, 0); \ + } + +extern Page_info page_info_rd_wr[]; +extern word32 g_slow_mem_changed[]; + +#define GET_MEMORY8(addr,dest) \ + addr_latch = (addr); \ + CYCLES_PLUS_1; \ + stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ + wstat = PTR2WORD(stat) & 0xff; \ + ptr = stat - wstat + ((addr) & 0xff); \ + if(wstat & (1 << (31 - BANK_IO_BIT))) { \ + dcycles_tmp1 = dfcyc; \ + dest = get_memory8_io_stub((addr), stat, \ + &dcycles_tmp1, dplus_x_m1); \ + dfcyc = dcycles_tmp1; \ + } else { \ + dest = *ptr; \ + } + +#define GET_MEMORY(addr,dest) GET_MEMORY8(addr, dest) + +#define GET_MEMORY16(addr, dest, in_bank) \ + save_addr = addr; \ + stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ + wstat = PTR2WORD(stat) & 0xff; \ + ptr = stat - wstat + ((addr) & 0xff); \ + if((wstat & (1 << (31 - BANK_IO_BIT))) || (((addr) & 0xff) == 0xff)) { \ + dcycles_tmp1 = dfcyc; \ + dest = get_memory16_pieces_stub((addr), stat, \ + &dcycles_tmp1, fplus_ptr, in_bank); \ + dfcyc = dcycles_tmp1; \ + } else { \ + CYCLES_PLUS_2; \ + dest = ptr[0] + (ptr[1] << 8); \ + } \ + addr_latch = save_addr; + +#define GET_MEMORY24(addr, dest, in_bank) \ + save_addr = addr; \ + stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ + wstat = PTR2WORD(stat) & 0xff; \ + ptr = stat - wstat + ((addr) & 0xff); \ + if((wstat & (1 << (31 - BANK_IO_BIT))) || (((addr) & 0xfe) == 0xfe)) { \ + dcycles_tmp1 = dfcyc; \ + dest = get_memory24_pieces_stub((addr), stat, \ + &dcycles_tmp1, fplus_ptr, in_bank); \ + dfcyc = dcycles_tmp1; \ + } else { \ + CYCLES_PLUS_3; \ + dest = ptr[0] + (ptr[1] << 8) + (ptr[2] << 16); \ + } \ + addr_latch = save_addr; + +#define GET_MEMORY_DIRECT_PAGE16(addr, dest, dloc_x_wrap) \ + save_addr = addr; \ + if(psr & 0x100) { \ + if((direct & 0xff) == 0) { \ + save_addr = (save_addr & 0xff) + direct; \ + } \ + } \ + if((psr & 0x100) && (((addr) & 0xff) == 0xff)) { \ + GET_MEMORY8(save_addr, getmem_tmp); \ + if(dloc_x_wrap) { \ + save_addr = (save_addr & 0xff00) | \ + ((save_addr + 1) & 0xff); \ + } else { \ + save_addr = (save_addr + 1) & 0xffff; \ + } \ + if((direct & 0xff) == 0) { \ + save_addr = (save_addr & 0xff) + direct; \ + } \ + GET_MEMORY8(save_addr, dest); \ + dest = (dest << 8) + getmem_tmp; \ + } else { \ + GET_MEMORY16(save_addr, dest, 1); \ + } + + +#define PUSH8(arg) \ + SET_MEMORY8(stack, arg); \ + stack = (stack - 1) & 0xffff; \ + if(psr & 0x100) { \ + stack = 0x100 | (stack & 0xff); \ + } + +#define PUSH16(arg) \ + if((stack & 0xfe) == 0) { \ + /* stack will cross page! */ \ + PUSH8((arg) >> 8); \ + PUSH8(arg); \ + } else { \ + stack = (stack - 2) & 0xffff; \ + SET_MEMORY16(stack + 1, arg, 1); \ + } + +#define PUSH16_UNSAFE(arg) \ + save_addr = (stack - 1) & 0xffff; \ + stack = (stack - 2) & 0xffff; \ + SET_MEMORY16(save_addr, arg, 1); \ + if(psr & 0x100) { \ + stack = 0x100 | (stack & 0xff); \ + } + +#define PUSH24_UNSAFE(arg) \ + save_addr = (stack - 2) & 0xffff; \ + stack = (stack - 3) & 0xffff; \ + SET_MEMORY24(save_addr, arg, 1); \ + if(psr & 0x100) { \ + stack = 0x100 | (stack & 0xff); \ + } + +#define PULL8(dest) \ + stack++; \ + if(psr & 0x100) { \ + stack = 0x100 | (stack & 0xff); \ + } \ + stack = stack & 0xffff; \ + GET_MEMORY8(stack, dest); + +#define PULL8_UNSAFE(dest) \ + stack = (stack + 1) & 0xffff; \ + GET_MEMORY8(stack, dest); \ + if(psr & 0x100) { \ + stack = 0x100 | (stack & 0xff); \ + } + +#define PULL16(dest) \ + if((stack & 0xfe) == 0xfe) { /* page cross */ \ + PULL8(dest); \ + PULL8(pull_tmp); \ + dest = (pull_tmp << 8) + dest; \ + } else { \ + GET_MEMORY16(stack + 1, dest, 1); \ + stack = (stack + 2) & 0xffff; \ + if(psr & 0x100) { \ + stack = 0x100 | (stack & 0xff); \ + } \ + } + +#define PULL16_UNSAFE(dest) \ + stack = (stack + 1) & 0xffff; \ + GET_MEMORY16(stack, dest, 1); \ + stack = (stack + 1) & 0xffff; \ + if(psr & 0x100) { \ + stack = 0x100 | (stack & 0xff); \ + } + +#define PULL24(dest) \ + if((stack & 0xfc) == 0xfc) { /* page cross */ \ + PULL8(dest); \ + PULL8(pull_tmp); \ + pull_tmp = (pull_tmp << 8) + dest; \ + PULL8(dest); \ + dest = (dest << 16) + pull_tmp; \ + } else { \ + GET_MEMORY24(stack + 1, dest, 1); \ + stack = (stack + 3) & 0xffff; \ + if(psr & 0x100) { \ + stack = 0x100 | (stack & 0xff); \ + } \ + } + +#define PULL24_UNSAFE(dest) \ + stack = (stack + 1) & 0xffff; \ + GET_MEMORY24(stack, dest, 1); \ + stack = (stack + 2) & 0xffff; \ + if(psr & 0x100) { \ + stack = 0x100 | (stack & 0xff); \ + } + +#define SET_MEMORY8(addr, val) \ + stat = GET_PAGE_INFO_WR(((addr) >> 8) & 0xffff); \ + LOG_DATA_MACRO(addr, val, 8, stat); \ + CYCLES_PLUS_1; \ + wstat = PTR2WORD(stat) & 0xff; \ + ptr = stat - wstat + ((addr) & 0xff); \ + if(wstat) { \ + dcycles_tmp1 = dfcyc; \ + set_memory8_io_stub((addr), val, stat, &dcycles_tmp1, \ + dplus_x_m1); \ + dfcyc = dcycles_tmp1; \ + } else { \ + *ptr = val; \ + } + + +#define SET_MEMORY16(addr, val, in_bank) \ + stat = GET_PAGE_INFO_WR(((addr) >> 8) & 0xffff); \ + LOG_DATA_MACRO(addr, val, 16, stat); \ + wstat = PTR2WORD(stat) & 0xff; \ + ptr = stat - wstat + ((addr) & 0xff); \ + if((wstat) || (((addr) & 0xff) == 0xff)) { \ + dcycles_tmp1 = dfcyc; \ + set_memory16_pieces_stub((addr), (val), \ + &dcycles_tmp1, dplus_1, dplus_x_m1, in_bank); \ + dfcyc = dcycles_tmp1; \ + } else { \ + CYCLES_PLUS_2; \ + ptr[0] = (val); \ + ptr[1] = (val) >> 8; \ + } + +#define SET_MEMORY24(addr, val, in_bank) \ + stat = GET_PAGE_INFO_WR(((addr) >> 8) & 0xffff); \ + LOG_DATA_MACRO(addr, val, 24, stat); \ + wstat = PTR2WORD(stat) & 0xff; \ + ptr = stat - wstat + ((addr) & 0xff); \ + if((wstat) || (((addr) & 0xfe) == 0xfe)) { \ + dcycles_tmp1 = dfcyc; \ + set_memory24_pieces_stub((addr), (val), \ + &dcycles_tmp1, fplus_ptr, in_bank); \ + dfcyc = dcycles_tmp1; \ + } else { \ + CYCLES_PLUS_3; \ + ptr[0] = (val); \ + ptr[1] = (val) >> 8; \ + ptr[2] = (val) >> 16; \ + } + + +word32 +get_memory8_io_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, + dword64 dplus_x_m1) +{ + dword64 dfcyc; + word32 wstat; + byte *ptr; + + wstat = PTR2WORD(stat) & 0xff; + dfcyc = *dcycs_ptr; + if(wstat & BANK_BREAK) { + check_breakpoints(addr, dfcyc, 0, 1); + } + if(wstat & BANK_IO2_TMP) { + FCYCLES_ROUND; + *dcycs_ptr = dfcyc; + return get_memory_io((addr), dcycs_ptr); + } else { + ptr = stat - wstat + (addr & 0xff); + return *ptr; + } +} + +word32 +get_memory16_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, + Fplus *fplus_ptr, int in_bank) +{ + byte *ptr; + dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; + word32 addrp1, wstat, ret, tmp1, addr_latch; + + addr_latch = 0; + if(addr_latch != 0) { // "Use" addr_latch to avoid warning + } + dfcyc = *dcycs_ptr; + dplus_1 = fplus_ptr->dplus_1; + dplus_x_m1 = fplus_ptr->dplus_x_minus_1; + GET_MEMORY8(addr, tmp1); + addrp1 = addr + 1; + if(in_bank) { + addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); + } + GET_MEMORY8(addrp1, ret); + *dcycs_ptr = dfcyc; + return (ret << 8) + (tmp1); +} + +word32 +get_memory24_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, + Fplus *fplus_ptr, int in_bank) +{ + byte *ptr; + dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; + word32 addrp1, addrp2, wstat, addr_latch, ret, tmp1, tmp2; + + addr_latch = 0; + if(addr_latch != 0) { // "Use" addr_latch to avoid warning + } + dfcyc = *dcycs_ptr; + dplus_1 = fplus_ptr->dplus_1; + dplus_x_m1 = fplus_ptr->dplus_x_minus_1; + GET_MEMORY8(addr, tmp1); + addrp1 = addr + 1; + if(in_bank) { + addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); + } + GET_MEMORY8(addrp1, tmp2); + addrp2 = addr + 2; + if(in_bank) { + addrp2 = (addr & 0xff0000) + (addrp2 & 0xffff); + } + GET_MEMORY8(addrp2, ret); + *dcycs_ptr = dfcyc; + return (ret << 16) + (tmp2 << 8) + tmp1; +} + +void +set_memory8_io_stub(word32 addr, word32 val, byte *stat, dword64 *dcycs_ptr, + dword64 dplus_x_m1) +{ + byte *ptr; + dword64 dfcyc; + word32 setmem_tmp1, tmp1, tmp2, wstat; + + wstat = PTR2WORD(stat) & 0xff; + dfcyc = *dcycs_ptr; + if(wstat & (1 << (31 - BANK_BREAK_BIT))) { + check_breakpoints(addr, dfcyc, 0, 2); + } + ptr = stat - wstat + ((addr) & 0xff); + if(wstat & (1 << (31 - BANK_IO2_BIT))) { + FCYCLES_ROUND; + *dcycs_ptr = dfcyc; + set_memory_io((addr), val, dcycs_ptr); + } else if(wstat & (1 << (31 - BANK_SHADOW_BIT))) { + if(g_limit_speed) { + FCYCLES_ROUND; + *dcycs_ptr = dfcyc; + } + tmp1 = (addr & 0xffff); + setmem_tmp1 = g_slow_memory_ptr[tmp1]; + *ptr = val; + g_slow_memory_ptr[tmp1] = val; + if(setmem_tmp1 != ((val) & 0xff)) { + g_slow_mem_changed[tmp1 >> CHANGE_SHIFT] |= + (1U << ((tmp1 >> SHIFT_PER_CHANGE) & 31)); + } + } else if(wstat & (1 << (31 - BANK_SHADOW2_BIT))) { + if(g_limit_speed) { + FCYCLES_ROUND; + *dcycs_ptr = dfcyc; + } + tmp2 = (addr & 0xffff); + tmp1 = 0x10000 + tmp2; + setmem_tmp1 = g_slow_memory_ptr[tmp1]; + *ptr = val; + g_slow_memory_ptr[tmp1] = val; + if(setmem_tmp1 != ((val) & 0xff)) { + g_slow_mem_changed[tmp1 >> CHANGE_SHIFT] |= + (1U << ((tmp1 >> SHIFT_PER_CHANGE) & 31)); + if((tmp1 & 0xff00) == 0x9d00) { + scb_changed(dfcyc, tmp1, val, setmem_tmp1); + } + } + } else { + /* breakpoint only */ + *ptr = val; + } +} + +#define LOG_PC_MACRO() +#define LOG_PC_MACRO2() +#define LOG_DATA_MACRO(addr, val, size, in_stat) + +void +set_memory16_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, + dword64 dplus_1, dword64 dplus_x_m1, int in_bank) +{ + byte *ptr; + byte *stat; + dword64 dfcyc, dcycles_tmp1; + word32 addrp1, wstat; + + dfcyc = *dcycs_ptr; + SET_MEMORY8(addr, val); + addrp1 = addr + 1; + if(in_bank) { + addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); + } + SET_MEMORY8(addrp1, val >> 8); + + *dcycs_ptr = dfcyc; +} + +void +set_memory24_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, + Fplus *fplus_ptr, int in_bank) +{ + byte *ptr; + byte *stat; + dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; + word32 addrp1, addrp2; + word32 wstat; + + dfcyc = *dcycs_ptr; + dplus_1 = fplus_ptr->dplus_1; + dplus_x_m1 = fplus_ptr->dplus_x_minus_1; + SET_MEMORY8(addr, val); + addrp1 = addr + 1; + if(in_bank) { + addrp1 = (addr & 0xff0000) + (addrp1 & 0xffff); + } + SET_MEMORY8(addrp1, val >> 8); + addrp2 = addr + 2; + if(in_bank) { + addrp2 = (addr & 0xff0000) + (addrp2 & 0xffff); + } + SET_MEMORY8(addrp2, val >> 16); + + *dcycs_ptr = dfcyc; +} + +word32 +get_memory_c(word32 addr) +{ + byte *stat, *ptr; + dword64 dfcyc, dplus_1, dcycles_tmp1, dplus_x_m1; + word32 addr_latch, wstat, ret; + + dfcyc = 0; + dplus_1 = 0; + dplus_x_m1 = 0; + addr_latch = 0; + if(addr_latch != 0) { // "Use" addr_latch to avoid warning + } + GET_MEMORY8(addr, ret); + return ret; +} + +word32 +get_memory16_c(word32 addr) +{ + return get_memory_c(addr) + + (get_memory_c(addr+1) << 8); +} + +word32 +get_memory24_c(word32 addr) +{ + return get_memory_c(addr) + + (get_memory_c(addr+1) << 8) + + (get_memory_c(addr+2) << 16); +} + +void +set_memory_c(word32 addr, word32 val, int do_log) +{ + byte *stat, *ptr; + dword64 dfcyc, dcycles_tmp1, dplus_1, dplus_x_m1; + word32 wstat; + + dfcyc = g_cur_dfcyc; + dplus_1 = 0; + dplus_x_m1 = 0; + SET_MEMORY8(addr, val); + if(g_log_pc_enable && do_log) { + LOG_DATA_MACRO_ACT(addr, val, 8, stat) + } +} + +void +set_memory16_c(word32 addr, word32 val, int do_log) +{ + byte *stat, *ptr; + dword64 dfcyc, dcycles_tmp1, dplus_1, dplus_x_m1; + word32 wstat; + + dfcyc = g_cur_dfcyc; + dplus_1 = 0; + dplus_x_m1 = 0; + SET_MEMORY16(addr, val, 0); + if(g_log_pc_enable && do_log) { + LOG_DATA_MACRO_ACT(addr, val, 16, stat) + } +} + +void +set_memory24_c(word32 addr, word32 val) +{ + set_memory_c(addr, val, 1); + set_memory_c(addr + 1, val >> 8, 1); + set_memory_c(addr + 2, val >> 16, 1); +} + +word32 +do_adc_sbc8(word32 in1, word32 in2, word32 psr, int sub) +{ + word32 sum, carry, overflow; + word32 zero; + int decimal; + + overflow = 0; + decimal = psr & 8; + if(sub) { + in2 = (in2 ^ 0xff); + } + if(!decimal) { + sum = (in1 & 0xff) + in2 + (psr & 1); + overflow = ((sum ^ in2) >> 1) & 0x40; + } else { + /* decimal */ + sum = (in1 & 0xf) + (in2 & 0xf) + (psr & 1); + if(sub) { + if(sum < 0x10) { + sum = (sum - 0x6) & 0xf; + } + } else { + if(sum >= 0xa) { + sum = (sum - 0xa) | 0x10; + } + } + + sum = (in1 & 0xf0) + (in2 & 0xf0) + sum; + overflow = ((sum >> 2) ^ (sum >> 1)) & 0x40; + if(sub) { + if(sum < 0x100) { + sum = (sum + 0xa0) & 0xff; + } + } else { + if(sum >= 0xa0) { + sum += 0x60; + } + } + } + + zero = ((sum & 0xff) == 0); + carry = (sum >= 0x100); + if((in1 ^ in2) & 0x80) { + overflow = 0; + } + + psr = psr & (~0xc3); + psr = psr + (sum & 0x80) + overflow + (zero << 1) + carry; + + return (psr << 16) + (sum & 0xff); +} + +word32 +do_adc_sbc16(word32 in1, word32 in2, word32 psr, int sub) +{ + word32 sum, carry, overflow; + word32 tmp1, tmp2; + word32 zero; + int decimal; + + overflow = 0; + decimal = psr & 8; + if(!decimal) { + if(sub) { + in2 = (in2 ^ 0xffff); + } + sum = in1 + in2 + (psr & 1); + overflow = ((sum ^ in2) >> 9) & 0x40; + } else { + /* decimal */ + if(sub) { + tmp1 = do_adc_sbc8(in1 & 0xff, in2 & 0xff, psr, sub); + psr = (tmp1 >> 16); + tmp2 = do_adc_sbc8((in1 >> 8) & 0xff, + (in2 >> 8) & 0xff, psr, sub); + in2 = (in2 ^ 0xfffff); + } else { + tmp1 = do_adc_sbc8(in1 & 0xff, in2 & 0xff, psr, sub); + psr = (tmp1 >> 16); + tmp2 = do_adc_sbc8((in1 >> 8) & 0xff, + (in2 >> 8) &0xff, psr, sub); + } + sum = ((tmp2 & 0xff) << 8) + (tmp1 & 0xff) + + (((tmp2 >> 16) & 1) << 16); + overflow = (tmp2 >> 16) & 0x40; + } + + zero = ((sum & 0xffff) == 0); + carry = (sum >= 0x10000); + if((in1 ^ in2) & 0x8000) { + overflow = 0; + } + + psr = psr & (~0xc3); + psr = psr + ((sum & 0x8000) >> 8) + overflow + (zero << 1) + carry; + + return (psr << 16) + (sum & 0xffff); +} + +void +fixed_memory_ptrs_init() +{ + /* set g_slow_memory_ptr, g_rom_fc_ff_ptr, g_dummy_memory1_ptr, */ + /* and rom_cards_ptr */ + + g_slow_memory_ptr = memalloc_align(128*1024, 0, 0); + g_dummy_memory1_ptr = memalloc_align(256, 1024, 0); + g_rom_fc_ff_ptr = memalloc_align(256*1024, 512, 0); + g_rom_cards_ptr = memalloc_align(16*256, 256, 0); + +#if 0 + printf("g_memory_ptr: %08x, dummy_mem: %08x, slow_mem_ptr: %08x\n", + (word32)g_memory_ptr, (word32)g_dummy_memory1_ptr, + (word32)g_slow_memory_ptr); + printf("g_rom_fc_ff_ptr: %08x, g_rom_cards_ptr: %08x\n", + (word32)g_rom_fc_ff_ptr, (word32)g_rom_cards_ptr); + printf("page_info_rd = %08x, page_info_wr end = %08x\n", + (word32)&(page_info_rd_wr[0]), + (word32)&(page_info_rd_wr[PAGE_INFO_PAD_SIZE+0x1ffff].rd_wr)); +#endif +} + +word32 +get_itimer() +{ +#if defined(__i386) && defined(__GNUC__) + /* Here's my bad ia32 asm code to do rdtsc */ + /* Linux source uses: */ + /* asm volatile("rdtsc" : "=a"(ret) : : "edx"); */ + /* asm volatile("rdtsc" : "=%eax"(ret) : : "%edx"); */ + + /* GCC bug report 2001-03/msg00786.html used: */ + /*register dword64 dtmp; */ + /*asm volatile ("rdtsc" : "=A" (dtmp)); */ + /*return (word32)dtmp; */ + + register word32 ret; + + asm volatile ("rdtsc;movl %%eax,%0" : "=r"(ret) : : "%eax","%edx"); + + return ret; +#else +# if defined(__POWERPC__) && defined(__GNUC__) + register word32 ret; + + asm volatile ("mftb %0" : "=r"(ret)); + return ret; +# else +# if defined(__x86_64__) + register word32 ret, hi; + //ret = __rdtsc(); + asm volatile ("rdtsc" : "=a" (ret), "=d" (hi)); + return ret; +# else +# if defined(__aarch64__) /* 64-bit ARM architecture */ + register dword64 ret; + asm volatile("mrs %0,CNTVCT_EL0" : "=r"(ret)); + return ret; +# else + return 0; +# endif +# endif +# endif +#endif +} + +void +engine_recalc_events() +{ + g_dcycles_end = 0; // End inner loop + g_engine_recalc_event++; +} + +void +set_halt_act(int val) +{ + if((val == 1) && g_ignore_halts && !g_user_halt_bad) { + g_code_red++; + } else { + if(g_halt_sim == 0) { + debugger_update_list_kpc(); + } + g_halt_sim |= val; + if(g_halt_sim) { + video_set_active(&g_debugwin_kimage, 1); + } + g_dcycles_end = 0; + } +} + +void +clr_halt_act() +{ + g_halt_sim = 0; +} + +word32 +get_remaining_operands(word32 addr, word32 opcode, word32 psr, + dword64 *dcyc_ptr, Fplus *fplus_ptr) +{ + byte *stat, *ptr; + dword64 dfcyc, dcycles_tmp1, dplus_1, dplus_x_m1; + word32 addr_latch, wstat, save_addr, arg, addrp1; + int size; + + dfcyc = *dcyc_ptr; + dplus_1 = fplus_ptr->dplus_1; + dplus_x_m1 = fplus_ptr->dplus_x_minus_1; + addr_latch = 0; + if(addr_latch != 0) { // "Use" addr_latch to avoid warning + } + + size = size_tab[opcode]; + + addrp1 = (addr & 0xff0000) + ((addr + 1) & 0xffff); + switch(size) { + case 0: + // Always read pc+1 for single-byte opcodes + GET_MEMORY8(addrp1, arg); + arg = 0; /* no args */ + break; + case 1: + GET_MEMORY8(addrp1, arg); + break; /* 1 arg, already done */ + case 2: + GET_MEMORY16(addrp1, arg, 1); + dfcyc -= dplus_1; + break; + case 3: + GET_MEMORY24(addrp1, arg, 1); + dfcyc = dfcyc - dplus_1 - dplus_1; + break; + case 4: + if(psr & 0x20) { + GET_MEMORY8(addrp1, arg); + } else { + GET_MEMORY16(addrp1, arg, 1); + dfcyc -= dplus_1; + } + break; + case 5: + if(psr & 0x10) { + GET_MEMORY8(addrp1, arg); + } else { + GET_MEMORY16(addrp1, arg, 1); + dfcyc -= dplus_1; + } + break; + default: + printf("Unknown size: %d\n", size); + arg = 0; + exit(-2); + } + *dcyc_ptr = dfcyc; + + return arg; +} + +#define FETCH_OPCODE \ + addr = kpc; \ + CYCLES_PLUS_2; \ + stat = GET_PAGE_INFO_RD(((addr) >> 8) & 0xffff); \ + wstat = PTR2WORD(stat) & 0xff; \ + ptr = stat - wstat + ((addr) & 0xff); \ + arg_ptr = ptr; \ + opcode = *ptr; \ + if((wstat & (1 << (31-BANK_IO_BIT))) || ((addr & 0xff) > 0xfc)) {\ + CYCLES_MINUS_1; \ + if(wstat & BANK_BREAK) { \ + check_breakpoints(addr, dfcyc, stack, 4); \ + } \ + if(wstat & (1 << (31 - BANK_IO2_BIT))) { \ + FCYCLES_ROUND; \ + dcycles_tmp1 = dfcyc; \ + opcode = get_memory_io((addr), &dcycles_tmp1); \ + dfcyc = dcycles_tmp1; \ + } else { \ + opcode = *ptr; \ + } \ + dcycles_tmp1 = dfcyc; \ + arg = get_remaining_operands(addr, opcode, psr, \ + &dcycles_tmp1, fplus_ptr); \ + dfcyc = dcycles_tmp1; \ + arg_ptr = (byte *)&tmp_bytes; \ + arg_ptr[1] = arg; \ + arg_ptr[2] = arg >> 8; \ + arg_ptr[3] = arg >> 16; \ + } + + +#define ACC8 +#define IS_ACC16 0 +#define ENGINE_TYPE enter_engine_acc8 +#include "engine.h" +// The above creates enter_engine_acc8 + +#undef ACC8 +#undef IS_ACC16 +#undef ENGINE_TYPE +#define IS_ACC16 1 +#define ENGINE_TYPE enter_engine_acc16 +#include "engine.h" +// The above creates enter_engine_acc16 + +#undef LOG_PC_MACRO +#undef LOG_PC_MACRO2 +#undef LOG_DATA_MACRO + +#define LOG_PC_MACRO() \ + tmp_pc_ptr = g_log_pc_ptr; \ + tmp_pc_ptr->dbank_kpc = (dbank << 24) + kpc; \ + tmp_pc_ptr->instr = (opcode << 24) + arg_ptr[1] + \ + (arg_ptr[2] << 8) + (arg_ptr[3] << 16); \ + tmp_pc_ptr->dfcyc = dfcyc - dplus_1 * 2; + +#define LOG_PC_MACRO2() \ + tmp_pc_ptr->psr_acc = ((psr & ~(0x82)) << 16) | acc | \ + ((neg7 & 0x80) << 16) | ((!zero) << 17); \ + tmp_pc_ptr->xreg_yreg = (xreg << 16) + yreg; \ + tmp_pc_ptr->stack_direct = (stack << 16) + direct; \ + tmp_pc_ptr++; \ + if(tmp_pc_ptr >= g_log_pc_end_ptr) { \ + tmp_pc_ptr = g_log_pc_start_ptr; \ + } \ + g_log_pc_ptr = tmp_pc_ptr; + +#define LOG_DATA_MACRO(in_addr, in_val, in_size, in_stat) \ + LOG_DATA_MACRO_ACT(in_addr, in_val, in_size, in_stat) + +#undef ACC8 +#undef IS_ACC16 +#undef ENGINE_TYPE +#define ACC8 +#define IS_ACC16 0 +#define ENGINE_TYPE enter_engine_acc8_log +#include "engine.h" +// The above creates enter_engine_acc8_log + +#undef ACC8 +#undef IS_ACC16 +#undef ENGINE_TYPE +#define IS_ACC16 1 +#define ENGINE_TYPE enter_engine_acc16_log +#include "engine.h" +// The above creates enter_engine_acc16_log + + +int +enter_engine(Engine_reg *engine_ptr) +{ + dword64 dcycles_end_save; + int ret; + + dcycles_end_save = g_dcycles_end; + while(1) { + if(g_log_pc_enable) { + if(engine_ptr->psr & 0x20) { // 8-bit accumulator + ret = enter_engine_acc8_log(engine_ptr); + } else { + ret = enter_engine_acc16_log(engine_ptr); + } + } else { + if(engine_ptr->psr & 0x20) { // 8-bit accumulator + ret = enter_engine_acc8(engine_ptr); + } else { + ret = enter_engine_acc16(engine_ptr); + } + } + if((ret == RET_PSR) && !g_halt_sim) { + g_dcycles_end = dcycles_end_save; + continue; + } + return ret; + } +} + diff --git a/gsplus/src/instable.h b/gsplus/src/instable.h new file mode 100644 index 0000000..4795631 --- /dev/null +++ b/gsplus/src/instable.h @@ -0,0 +1,1555 @@ +// "@(#)$KmKId: instable.h,v 1.121 2023-11-12 15:31:14+00 kentd Exp $" + +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2021 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +case 0x00: /* brk */ + GET_1BYTE_ARG; + g_num_brk++; + INC_KPC_2; + psr = (psr & (~0x82)) | (neg7 & 0x80) | ((!zero) << 1); + if(psr & 0x100) { + PUSH16(kpc & 0xffff); + PUSH8(psr & 0xff); + tmp1 = 0xfffffe; + dbank = 0; + } else { + PUSH8(kpc >> 16); + PUSH16(kpc); + PUSH8(psr & 0xff); + tmp1 = 0xffffe6; + halt_printf("Halting for native break!\n"); + } + tmp1 = moremem_fix_vector_pull(tmp1); + GET_MEMORY16(tmp1, kpc, 0); + kpc = kpc & 0xffff; + psr |= 0x4; + psr &= ~(0x8); + break; + +case 0x01: /* ORA (Dloc,X) */ + GET_DLOC_X_IND_RD(); + ORA_INST(); + break; + +case 0x02: /* COP */ + g_num_cop++; + INC_KPC_2; + psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); + if(psr & 0x100) { + halt_printf("Halting for emul COP at %04x\n", kpc); + PUSH16(kpc & 0xffff); + PUSH8(psr & 0xff); + tmp1 = 0xfffff4; + dbank = 0; + } else { + PUSH8(kpc >> 16); + PUSH16(kpc & 0xffff); + PUSH8(psr & 0xff); + tmp1 = 0xffffe4; + } + tmp1 = moremem_fix_vector_pull(tmp1); + GET_MEMORY16(tmp1, kpc, 0); + kpc = kpc & 0xffff; + psr |= 4; + psr &= ~(0x8); + break; + +case 0x03: /* ORA Disp8,S */ + GET_DISP8_S_RD(); + ORA_INST(); + break; + +case 0x04: /* TSB Dloc */ + GET_DLOC_RD_RMW(); + TSB_INST(1); + break; + +case 0x05: /* ORA Dloc */ + GET_DLOC_RD(); + ORA_INST(); + break; + +case 0x06: /* ASL Dloc */ + GET_DLOC_RD_RMW(); + ASL_INST(1); + break; + +case 0x07: /* ORA [Dloc] */ + GET_DLOC_L_IND_RD(); + ORA_INST(); + break; + +case 0x08: /* PHP */ + INC_KPC_1; + psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); + PUSH8(psr); + break; + +case 0x09: /* ORA #imm */ + GET_IMM_MEM(); + ORA_INST(); + break; + +case 0x0a: /* ASL a */ + INC_KPC_1; + tmp1 = acc + acc; +#ifdef ACC8 + SET_CARRY8(tmp1); + acc = (acc & 0xff00) + (tmp1 & 0xff); + SET_NEG_ZERO8(acc & 0xff); +#else + SET_CARRY16(tmp1); + acc = tmp1 & 0xffff; + SET_NEG_ZERO16(acc); +#endif + break; + +case 0x0b: /* PHD */ + INC_KPC_1; + PUSH16_UNSAFE(direct); + break; + +case 0x0c: /* TSB abs */ + GET_ABS_RD_RMW(); + TSB_INST(0); + break; + +case 0x0d: /* ORA abs */ + GET_ABS_RD(); + ORA_INST(); + break; + +case 0x0e: /* ASL abs */ + GET_ABS_RD_RMW(); + ASL_INST(0); + break; + +case 0x0f: /* ORA long */ + GET_LONG_RD(); + ORA_INST(); + break; + +case 0x10: /* BPL disp8 */ + BRANCH_DISP8((neg7 & 0x80) == 0); + break; + +case 0x11: /* ORA (Dloc),y */ + GET_DLOC_IND_Y_RD(); + ORA_INST(); + break; + +case 0x12: /* ORA (Dloc) */ + GET_DLOC_IND_RD(); + ORA_INST(); + break; + +case 0x13: /* ORA (Disp8,s),y */ + GET_DISP8_S_IND_Y_RD(); + ORA_INST(); + break; + +case 0x14: /* TRB Dloc */ + GET_DLOC_RD_RMW(); + TRB_INST(1); + break; + +case 0x15: /* ORA Dloc,x */ + GET_DLOC_X_RD(); + ORA_INST(); + break; + +case 0x16: /* ASL Dloc,X */ + GET_DLOC_X_RD_RMW(); + ASL_INST(1); + break; + +case 0x17: /* ORA [Dloc],Y */ + GET_DLOC_L_IND_Y_RD(); + ORA_INST(); + break; + +case 0x18: /* CLC */ + psr = psr & (~1); + INC_KPC_1; + break; + +case 0x19: /* ORA abs,y */ + GET_ABS_Y_RD(); + ORA_INST(); + break; + +case 0x1a: /* INC a */ + INC_KPC_1; +#ifdef ACC8 + acc = (acc & 0xff00) | ((acc + 1) & 0xff); + SET_NEG_ZERO8(acc & 0xff); +#else + acc = (acc + 1) & 0xffff; + SET_NEG_ZERO16(acc); +#endif + break; + +case 0x1b: /* TCS */ + stack = acc; + INC_KPC_1; + if(psr & 0x100) { + stack = (stack & 0xff) + 0x100; + } + break; + +case 0x1c: /* TRB Abs */ + GET_ABS_RD_RMW(); + TRB_INST(0); + break; + +case 0x1d: /* ORA Abs,X */ + GET_ABS_X_RD(); + ORA_INST(); + break; + +case 0x1e: /* ASL Abs,X */ + GET_ABS_X_RD_RMW(); + ASL_INST(0); + break; + +case 0x1f: /* ORA Long,X */ + GET_LONG_X_RD(); + ORA_INST(); + break; + +case 0x20: /* JSR abs */ + GET_2BYTE_ARG; + INC_KPC_2; + PUSH16(kpc); + kpc = (kpc & 0xff0000) + arg; + CYCLES_PLUS_2; + break; + +case 0x21: /* AND (Dloc,X) */ + GET_DLOC_X_IND_RD(); + AND_INST(); + break; + +case 0x22: /* JSL Long */ + GET_3BYTE_ARG; + tmp1 = arg; + CYCLES_PLUS_3; + INC_KPC_3; + PUSH24_UNSAFE(kpc); + kpc = tmp1 & 0xffffff; + break; + +case 0x23: /* AND Disp8,S */ + GET_DISP8_S_RD(); + AND_INST(); + break; + +case 0x24: /* BIT Dloc */ + GET_DLOC_RD(); + BIT_INST(); + break; + +case 0x25: /* AND Dloc */ + GET_DLOC_RD(); + AND_INST(); + break; + +case 0x26: /* ROL Dloc */ + GET_DLOC_RD_RMW(); + ROL_INST(1); + break; + +case 0x27: /* AND [Dloc] */ + GET_DLOC_L_IND_RD(); + AND_INST(); + break; + +case 0x28: /* PLP */ + PULL8(tmp1); + tmp2 = psr; + CYCLES_PLUS_1; + INC_KPC_1; + psr = (psr & ~0xff) | (tmp1 & 0xff); + zero = !(psr & 2); + neg7 = psr; + UPDATE_PSR(psr, tmp2); + break; + +case 0x29: /* AND #imm */ + GET_IMM_MEM(); + AND_INST(); + break; + +case 0x2a: /* ROL a */ + INC_KPC_1; +#ifdef ACC8 + tmp1 = ((acc & 0xff) << 1) + (psr & 1); + SET_CARRY8(tmp1); + acc = (acc & 0xff00) + (tmp1 & 0xff); + SET_NEG_ZERO8(tmp1 & 0xff); +#else + tmp1 = (acc << 1) + (psr & 1); + SET_CARRY16(tmp1); + acc = (tmp1 & 0xffff); + SET_NEG_ZERO16(acc); +#endif + break; + +case 0x2b: /* PLD */ + INC_KPC_1; + PULL16_UNSAFE(direct); + CYCLES_PLUS_1; + SET_NEG_ZERO16(direct); + break; + +case 0x2c: /* BIT abs */ + GET_ABS_RD(); + BIT_INST(); + break; + +case 0x2d: /* AND abs */ + GET_ABS_RD(); + AND_INST(); + break; + +case 0x2e: /* ROL abs */ + GET_ABS_RD_RMW(); + ROL_INST(0); + break; + +case 0x2f: /* AND long */ + GET_LONG_RD(); + AND_INST(); + break; + +case 0x30: /* BMI disp8 */ + BRANCH_DISP8(neg7 & 0x80); + break; + +case 0x31: /* AND (Dloc),y */ + GET_DLOC_IND_Y_RD(); + AND_INST(); + break; + +case 0x32: /* AND (Dloc) */ + GET_DLOC_IND_RD(); + AND_INST(); + break; + +case 0x33: /* AND (Disp8,s),y */ + GET_DISP8_S_IND_Y_RD(); + AND_INST(); + break; + +case 0x34: /* BIT Dloc,x */ + GET_DLOC_X_RD(); + BIT_INST(); + break; + +case 0x35: /* AND Dloc,x */ + GET_DLOC_X_RD(); + AND_INST(); + break; + +case 0x36: /* ROL Dloc,X */ + GET_DLOC_X_RD_RMW(); + ROL_INST(1); + break; + +case 0x37: /* AND [Dloc],Y */ + GET_DLOC_L_IND_Y_RD(); + AND_INST(); + break; + +case 0x38: /* SEC */ + psr = psr | 1; + INC_KPC_1; + break; + +case 0x39: /* AND abs,y */ + GET_ABS_Y_RD(); + AND_INST(); + break; + +case 0x3a: /* DEC a */ + INC_KPC_1; +#ifdef ACC8 + acc = (acc & 0xff00) | ((acc - 1) & 0xff); + SET_NEG_ZERO8(acc & 0xff); +#else + acc = (acc - 1) & 0xffff; + SET_NEG_ZERO16(acc); +#endif + break; + +case 0x3b: /* TSC */ +/* set N,Z according to 16 bit acc */ + INC_KPC_1; + acc = stack; + SET_NEG_ZERO16(acc); + break; + +case 0x3c: /* BIT Abs,x */ + GET_ABS_X_RD(); + BIT_INST(); + break; + +case 0x3d: /* AND Abs,X */ + GET_ABS_X_RD(); + AND_INST(); + break; + +case 0x3e: /* ROL Abs,X */ + GET_ABS_X_RD_RMW(); + ROL_INST(0); + break; + +case 0x3f: /* AND Long,X */ + GET_LONG_X_RD(); + AND_INST(); + break; + +case 0x40: /* RTI */ + CYCLES_PLUS_1 + if(psr & 0x100) { + PULL24(tmp1); + kpc = (kpc & 0xff0000) + ((tmp1 >> 8) & 0xffff); + tmp2 = psr; + psr = (psr & ~0xff) + (tmp1 & 0xff); + neg7 = psr; + zero = !(psr & 2); + UPDATE_PSR(psr, tmp2); + } else { + PULL8(tmp1); + tmp2 = psr; + psr = (tmp1 & 0xff); + neg7 = psr; + zero = !(psr & 2); + PULL24(kpc); + UPDATE_PSR(psr, tmp2); + } + break; + +case 0x41: /* EOR (Dloc,X) */ + GET_DLOC_X_IND_RD(); + EOR_INST(); + break; + +case 0x42: /* WDM */ + GET_2BYTE_ARG; + INC_KPC_2; + if(arg < 0x100) { // Next byte is 00 + INC_KPC_1; // Skip over the BRK + } + FINISH(RET_WDM, arg); + break; + +case 0x43: /* EOR Disp8,S */ + GET_DISP8_S_RD(); + EOR_INST(); + break; + +case 0x44: /* MVP */ + GET_2BYTE_ARG; + /* arg & 0xff = dest bank, arg & 0xff00 = src bank */ + if(psr & 0x100) { + halt_printf("MVP in emulation!\n"); + break; + } + dbank = arg & 0xff; + tmp1 = (arg >> 8) & 0xff; + CYCLES_PLUS_1; + GET_MEMORY8((tmp1 << 16) + xreg, arg); + SET_MEMORY8((dbank << 16) + yreg, arg); + CYCLES_PLUS_2; + xreg = (xreg - 1) & 0xffff; + yreg = (yreg - 1) & 0xffff; + if(psr & 0x10) { // 8-bit index registers + xreg = xreg & 0xff; + yreg = yreg & 0xff; + } + acc = (acc - 1) & 0xffff; + if(acc == 0xffff) { + INC_KPC_3; + } + break; + +case 0x45: /* EOR Dloc */ + GET_DLOC_RD(); + EOR_INST(); + break; + +case 0x46: /* LSR Dloc */ + GET_DLOC_RD_RMW(); + LSR_INST(1); + break; + +case 0x47: /* EOR [Dloc] */ + GET_DLOC_L_IND_RD(); + EOR_INST(); + break; + +case 0x48: /* PHA */ + INC_KPC_1; +#ifdef ACC8 + PUSH8(acc); +#else + PUSH16(acc); +#endif + break; + +case 0x49: /* EOR #imm */ + GET_IMM_MEM(); + EOR_INST(); + break; + +case 0x4a: /* LSR a */ + INC_KPC_1; +#ifdef ACC8 + tmp1 = ((acc & 0xff) >> 1); + SET_CARRY8(acc << 8); + acc = (acc & 0xff00) + (tmp1 & 0xff); + SET_NEG_ZERO8(tmp1 & 0xff); +#else + tmp1 = (acc >> 1); + SET_CARRY8((acc << 8)); + acc = (tmp1 & 0xffff); + SET_NEG_ZERO16(acc); +#endif + break; + +case 0x4b: /* PHK */ + PUSH8(kpc >> 16); + INC_KPC_1; + break; + +case 0x4c: /* JMP abs */ + GET_2BYTE_ARG; + CYCLES_PLUS_1; + kpc = (kpc & 0xff0000) + arg; + break; + +case 0x4d: /* EOR abs */ + GET_ABS_RD(); + EOR_INST(); + break; + +case 0x4e: /* LSR abs */ + GET_ABS_RD_RMW(); + LSR_INST(0); + break; + +case 0x4f: /* EOR long */ + GET_LONG_RD(); + EOR_INST(); + break; + +case 0x50: /* BVC disp8 */ + BRANCH_DISP8((psr & 0x40) == 0); + break; + +case 0x51: /* EOR (Dloc),y */ + GET_DLOC_IND_Y_RD(); + EOR_INST(); + break; + +case 0x52: /* EOR (Dloc) */ + GET_DLOC_IND_RD(); + EOR_INST(); + break; + +case 0x53: /* EOR (Disp8,s),y */ + GET_DISP8_S_IND_Y_RD(); + EOR_INST(); + break; + +case 0x54: /* MVN */ + GET_2BYTE_ARG; + /* arg & 0xff = dest bank, arg & 0xff00 = src bank */ + if(psr & 0x100) { + halt_printf("MVN in emulation!\n"); + break; + } + dbank = arg & 0xff; + tmp1 = (arg >> 8) & 0xff; + CYCLES_PLUS_1; + GET_MEMORY8((tmp1 << 16) + xreg, arg); + SET_MEMORY8((dbank << 16) + yreg, arg); + CYCLES_PLUS_2; + xreg = (xreg + 1) & 0xffff; + yreg = (yreg + 1) & 0xffff; + if(psr & 0x10) { // 8-bit index registers + xreg = xreg & 0xff; + yreg = yreg & 0xff; + } + acc = (acc - 1) & 0xffff; + if(acc == 0xffff) { + INC_KPC_3; + } + break; + +case 0x55: /* EOR Dloc,x */ + GET_DLOC_X_RD(); + EOR_INST(); + break; + +case 0x56: /* LSR Dloc,X */ + GET_DLOC_X_RD_RMW(); + LSR_INST(1); + break; + +case 0x57: /* EOR [Dloc],Y */ + GET_DLOC_L_IND_Y_RD(); + EOR_INST(); + break; + +case 0x58: /* CLI */ + psr = psr & (~4); + INC_KPC_1; + if(((psr & 0x4) == 0) && g_irq_pending) { + FINISH(RET_IRQ, 0); + } + break; + +case 0x59: /* EOR abs,y */ + GET_ABS_Y_RD(); + EOR_INST(); + break; + +case 0x5a: /* PHY */ + INC_KPC_1; + if(psr & 0x10) { + PUSH8(yreg); + } else { + PUSH16(yreg); + } + break; + +case 0x5b: /* TCD */ + INC_KPC_1; + direct = acc; + SET_NEG_ZERO16(acc); + break; + +case 0x5c: /* JMP Long */ + GET_3BYTE_ARG; + CYCLES_PLUS_2; + kpc = arg; + break; + +case 0x5d: /* EOR Abs,X */ + GET_ABS_X_RD(); + EOR_INST(); + break; + +case 0x5e: /* LSR Abs,X */ + GET_ABS_X_RD_RMW(); + LSR_INST(0); + break; + +case 0x5f: /* EOR Long,X */ + GET_LONG_X_RD(); + EOR_INST(); + break; + +case 0x60: /* RTS */ + CYCLES_PLUS_2 + PULL16(tmp1); + kpc = (kpc & 0xff0000) + ((tmp1 + 1) & 0xffff); + break; + +case 0x61: /* ADC (Dloc,X) */ + GET_DLOC_X_IND_RD(); + ADC_INST(); + break; + +case 0x62: /* PER */ + GET_2BYTE_ARG; + CYCLES_PLUS_2; + INC_KPC_3; + PUSH16_UNSAFE(kpc + arg); + break; + +case 0x63: /* ADC Disp8,S */ + GET_DISP8_S_RD(); + ADC_INST(); + break; + +case 0x64: /* STZ Dloc */ + GET_DLOC_ADDR(); + STZ_INST(1); + break; + +case 0x65: /* ADC Dloc */ + GET_DLOC_RD(); + ADC_INST(); + break; + +case 0x66: /* ROR Dloc */ + GET_DLOC_RD_RMW(); + ROR_INST(1); + break; + +case 0x67: /* ADC [Dloc] */ + GET_DLOC_L_IND_RD(); + ADC_INST(); + break; + +case 0x68: /* PLA */ + INC_KPC_1; + CYCLES_PLUS_1; +#ifdef ACC8 + PULL8(tmp1); + acc = (acc & 0xff00) + tmp1; + SET_NEG_ZERO8(tmp1); +#else + PULL16(tmp1); + acc = tmp1; + SET_NEG_ZERO16(tmp1); +#endif + break; + +case 0x69: /* ADC #imm */ + GET_IMM_MEM(); + ADC_INST(); + break; + +case 0x6a: /* ROR a */ + INC_KPC_1; +#ifdef ACC8 + tmp1 = ((acc & 0xff) >> 1) + ((psr & 1) << 7); + SET_CARRY8((acc << 8)); + acc = (acc & 0xff00) + (tmp1 & 0xff); + SET_NEG_ZERO8(tmp1 & 0xff); +#else + tmp1 = (acc >> 1) + ((psr & 1) << 15); + SET_CARRY16((acc << 16)); + acc = (tmp1 & 0xffff); + SET_NEG_ZERO16(acc); +#endif + break; + +case 0x6b: /* RTL */ + CYCLES_PLUS_1; + PULL24_UNSAFE(tmp1); + kpc = (tmp1 & 0xff0000) + ((tmp1 + 1) & 0xffff); + break; + +case 0x6c: /* JMP (abs) */ + GET_2BYTE_ARG; + CYCLES_PLUS_1; + GET_MEMORY16(arg, tmp1, 1); + if((psr & 0x100) && g_emul_6502_ind_page_cross_bug && + ((arg & 0xff) == 0xff)) { + GET_MEMORY8(arg - 0xff, tmp2); + tmp1 = (tmp1 & 0xff) + (tmp2 << 8); + halt_printf("Halting for emul ind jmp\n"); + } + kpc = (kpc & 0xff0000) + tmp1; + break; + +case 0x6d: /* ADC abs */ + GET_ABS_RD(); + ADC_INST(); + break; + +case 0x6e: /* ROR abs */ + GET_ABS_RD_RMW(); + ROR_INST(0); + break; + +case 0x6f: /* ADC long */ + GET_LONG_RD(); + ADC_INST(); + break; + +case 0x70: /* BVS disp8 */ + BRANCH_DISP8((psr & 0x40)); + break; + +case 0x71: /* ADC (Dloc),y */ + GET_DLOC_IND_Y_RD(); + ADC_INST(); + break; + +case 0x72: /* ADC (Dloc) */ + GET_DLOC_IND_RD(); + ADC_INST(); + break; + +case 0x73: /* ADC (Disp8,s),y */ + GET_DISP8_S_IND_Y_RD(); + ADC_INST(); + break; + +case 0x74: /* STZ Dloc,x */ + GET_1BYTE_ARG; + GET_DLOC_X_WR(); + STZ_INST(1); + break; + +case 0x75: /* ADC Dloc,x */ + GET_DLOC_X_RD(); + ADC_INST(); + break; + +case 0x76: /* ROR Dloc,X */ + GET_DLOC_X_RD_RMW(); + ROR_INST(1); + break; + +case 0x77: /* ADC [Dloc],Y */ + GET_DLOC_L_IND_Y_RD(); + ADC_INST(); + break; + +case 0x78: /* SEI */ + psr = psr | 4; + INC_KPC_1; + break; + +case 0x79: /* ADC abs,y */ + GET_ABS_Y_RD(); + ADC_INST(); + break; + +case 0x7a: /* PLY */ + INC_KPC_1; + CYCLES_PLUS_1 + if(psr & 0x10) { + PULL8(yreg); + SET_NEG_ZERO8(yreg); + } else { + PULL16(yreg); + SET_NEG_ZERO16(yreg); + } + break; + +case 0x7b: /* TDC */ + INC_KPC_1; + acc = direct; + SET_NEG_ZERO16(direct); + break; + +case 0x7c: /* JMP (Abs,x) */ +/* always access kbank, xreg cannot wrap into next bank */ + GET_2BYTE_ARG; + arg = (kpc & 0xff0000) + ((xreg + arg) & 0xffff); + CYCLES_PLUS_2; + GET_MEMORY16(arg, tmp1, 1); + kpc = (kpc & 0xff0000) + tmp1; + break; + +case 0x7d: /* ADC Abs,X */ + GET_ABS_X_RD(); + ADC_INST(); + break; + +case 0x7e: /* ROR Abs,X */ + GET_ABS_X_RD_RMW(); + ROR_INST(0); + break; + +case 0x7f: /* ADC Long,X */ + GET_LONG_X_RD(); + ADC_INST(); + break; + +case 0x80: /* BRA */ + BRANCH_DISP8(1); + break; + +case 0x81: /* STA (Dloc,X) */ + GET_DLOC_X_IND_ADDR(); + STA_INST(0); + break; + +case 0x82: /* BRL disp16 */ + GET_2BYTE_ARG; + CYCLES_PLUS_1; + kpc = (kpc & 0xff0000) + ((kpc + 3 + arg) & 0xffff); + break; + +case 0x83: /* STA Disp8,S */ + GET_DISP8_S_ADDR(); + STA_INST(1); + break; + +case 0x84: /* STY Dloc */ + GET_DLOC_ADDR(); + STY_INST(1); + break; + +case 0x85: /* STA Dloc */ + GET_DLOC_ADDR(); + STA_INST(1); + break; + +case 0x86: /* STX Dloc */ + GET_DLOC_ADDR(); + STX_INST(1); + break; + +case 0x87: /* STA [Dloc] */ + GET_DLOC_L_IND_ADDR(); + STA_INST(0); + break; + +case 0x88: /* DEY */ + INC_KPC_1; + SET_INDEX_REG(yreg - 1, yreg); + break; + +case 0x89: /* BIT #imm */ + GET_IMM_MEM(); +#ifdef ACC8 + zero = (acc & arg) & 0xff; +#else + zero = (acc & arg) & 0xffff; +#endif + break; + +case 0x8a: /* TXA */ + INC_KPC_1; + arg = xreg; + LDA_INST(); + break; + +case 0x8b: /* PHB */ + INC_KPC_1; + PUSH8(dbank); + break; + +case 0x8c: /* STY abs */ + GET_ABS_ADDR(); + STY_INST(0); + break; + +case 0x8d: /* STA abs */ + GET_ABS_ADDR(); + STA_INST(0); + break; + +case 0x8e: /* STX abs */ + GET_ABS_ADDR(); + STX_INST(0); + break; + +case 0x8f: /* STA long */ + GET_LONG_ADDR(); + STA_INST(0); + break; + +case 0x90: /* BCC disp8 */ + BRANCH_DISP8((psr & 0x01) == 0); + break; + +case 0x91: /* STA (Dloc),y */ + GET_DLOC_IND_Y_ADDR(1); + STA_INST(0); + break; + +case 0x92: /* STA (Dloc) */ + GET_DLOC_IND_ADDR(); + STA_INST(0); + break; + +case 0x93: /* STA (Disp8,s),y */ + GET_DISP8_S_IND_Y_ADDR(); + STA_INST(0); + break; + +case 0x94: /* STY Dloc,x */ + GET_DLOC_X_ADDR(); + STY_INST(1); + break; + +case 0x95: /* STA Dloc,x */ + GET_DLOC_X_ADDR(); + STA_INST(1); + break; + +case 0x96: /* STX Dloc,Y */ + GET_DLOC_Y_ADDR(); + STX_INST(1); + break; + +case 0x97: /* STA [Dloc],Y */ + GET_DLOC_L_IND_Y_ADDR(); + STA_INST(0); + break; + +case 0x98: /* TYA */ + INC_KPC_1; + arg = yreg; + LDA_INST(); + break; + +case 0x99: /* STA abs,y */ + GET_ABS_INDEX_ADDR(yreg, 1) + STA_INST(0); + break; + +case 0x9a: /* TXS */ + stack = xreg; + if(psr & 0x100) { + stack = 0x100 | (stack & 0xff); + } + INC_KPC_1; + break; + +case 0x9b: /* TXY */ + SET_INDEX_REG(xreg, yreg); + INC_KPC_1; + break; + +case 0x9c: /* STZ Abs */ + GET_ABS_ADDR(); + STZ_INST(0); + break; + +case 0x9d: /* STA Abs,X */ + GET_ABS_INDEX_ADDR(xreg, 1); + STA_INST(0); + break; + +case 0x9e: /* STZ Abs,X */ + GET_ABS_INDEX_ADDR(xreg, 1); + STZ_INST(0); + break; + +case 0x9f: /* STA Long,X */ + GET_LONG_X_ADDR_FOR_WR(); + STA_INST(0); + break; + +case 0xa0: /* LDY #imm */ + INC_KPC_2; + if((psr & 0x10) == 0) { + GET_2BYTE_ARG; + CYCLES_PLUS_1 + INC_KPC_1; + } else { + GET_1BYTE_ARG; + } + SET_INDEX_REG(arg, yreg); + break; + +case 0xa1: /* LDA (Dloc,X) */ + GET_DLOC_X_IND_RD(); + LDA_INST(); + break; + +case 0xa2: /* LDX #imm */ + INC_KPC_2; + if((psr & 0x10) == 0) { + GET_2BYTE_ARG; + CYCLES_PLUS_1 + INC_KPC_1; + } else { + GET_1BYTE_ARG; + } + SET_INDEX_REG(arg, xreg); + break; + +case 0xa3: /* LDA Disp8,S */ + GET_DISP8_S_RD(); + LDA_INST(); + break; + +case 0xa4: /* LDY Dloc */ + C_LDY_DLOC(); + break; + +case 0xa5: /* LDA Dloc */ + GET_DLOC_RD(); + LDA_INST(); + break; + +case 0xa6: /* LDX Dloc */ + C_LDX_DLOC(); + break; + +case 0xa7: /* LDA [Dloc] */ + GET_DLOC_L_IND_RD(); + LDA_INST(); + break; + +case 0xa8: /* TAY */ + INC_KPC_1; + SET_INDEX_REG(acc, yreg); + break; + +case 0xa9: /* LDA #imm */ + GET_IMM_MEM(); + LDA_INST(); + break; + +case 0xaa: /* TAX */ + INC_KPC_1; + SET_INDEX_REG(acc, xreg); + break; + +case 0xab: /* PLB */ + INC_KPC_1; + CYCLES_PLUS_1 + PULL8_UNSAFE(dbank); + SET_NEG_ZERO8(dbank); + break; + +case 0xac: /* LDY abs */ + C_LDY_ABS(); + break; + +case 0xad: /* LDA abs */ + GET_ABS_RD(); + LDA_INST(); + break; + +case 0xae: /* LDX abs */ + C_LDX_ABS(); + break; + +case 0xaf: /* LDA long */ + GET_LONG_RD(); + LDA_INST(); + break; + +case 0xb0: /* BCS disp8 */ + BRANCH_DISP8((psr & 0x01)); + break; + +case 0xb1: /* LDA (Dloc),y */ + GET_DLOC_IND_Y_RD(); + LDA_INST(); + break; + +case 0xb2: /* LDA (Dloc) */ + GET_DLOC_IND_RD(); + LDA_INST(); + break; + +case 0xb3: /* LDA (Disp8,s),y */ + GET_DISP8_S_IND_Y_RD(); + LDA_INST(); + break; + +case 0xb4: /* LDY Dloc,x */ + C_LDY_DLOC_X(); + break; + +case 0xb5: /* LDA Dloc,x */ + GET_DLOC_X_RD(); + LDA_INST(); + break; + +case 0xb6: /* LDX Dloc,y */ + C_LDX_DLOC_Y(); + break; + +case 0xb7: /* LDA [Dloc],Y */ + GET_DLOC_L_IND_Y_RD(); + LDA_INST(); + break; + +case 0xb8: /* CLV */ + psr = psr & ~0x40; + INC_KPC_1; + break; + +case 0xb9: /* LDA abs,y */ + GET_ABS_Y_RD(); + LDA_INST(); + break; + +case 0xba: /* TSX */ + INC_KPC_1; + SET_INDEX_REG(stack, xreg); + break; + +case 0xbb: /* TYX */ + INC_KPC_1; + SET_INDEX_REG(yreg, xreg); + break; + +case 0xbc: /* LDY Abs,X */ + C_LDY_ABS_X(); + break; + +case 0xbd: /* LDA Abs,X */ + GET_ABS_X_RD(); + LDA_INST(); + break; + +case 0xbe: /* LDX Abs,y */ + C_LDX_ABS_Y(); + break; + +case 0xbf: /* LDA Long,X */ + GET_LONG_X_RD(); + LDA_INST(); + break; + +case 0xc0: /* CPY #imm */ + C_CPY_IMM(); + break; + +case 0xc1: /* CMP (Dloc,X) */ + GET_DLOC_X_IND_RD(); + CMP_INST(); + break; + +case 0xc2: /* REP #imm */ + GET_1BYTE_ARG; + tmp2 = psr; + CYCLES_PLUS_1; + INC_KPC_2; + psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); + psr = psr & ~(arg & 0xff); + zero = !(psr & 2); + neg7 = psr; + UPDATE_PSR(psr, tmp2); + break; + +case 0xc3: /* CMP Disp8,S */ + GET_DISP8_S_RD(); + CMP_INST(); + break; + +case 0xc4: /* CPY Dloc */ + C_CPY_DLOC(); + break; + +case 0xc5: /* CMP Dloc */ + GET_DLOC_RD(); + CMP_INST(); + break; + +case 0xc6: /* DEC Dloc */ + GET_DLOC_RD_RMW(); + DEC_INST(1); + break; + +case 0xc7: /* CMP [Dloc] */ + GET_DLOC_L_IND_RD(); + CMP_INST(); + break; + +case 0xc8: /* INY */ + INC_KPC_1; + SET_INDEX_REG(yreg + 1, yreg); + break; + +case 0xc9: /* CMP #imm */ + GET_IMM_MEM(); + CMP_INST(); + break; + +case 0xca: /* DEX */ + INC_KPC_1; + SET_INDEX_REG(xreg - 1, xreg); + break; + +case 0xcb: /* WAI */ + if(g_irq_pending) { + g_wait_pending = 0; + INC_KPC_1; + } else { + g_wait_pending = 1; + } + break; + +case 0xcc: /* CPY abs */ + C_CPY_ABS(); + break; + +case 0xcd: /* CMP abs */ + GET_ABS_RD(); + CMP_INST(); + break; + +case 0xce: /* DEC abs */ + GET_ABS_RD_RMW(); + DEC_INST(0); + break; + +case 0xcf: /* CMP long */ + GET_LONG_RD(); + CMP_INST(); + break; + +case 0xd0: /* BNE disp8 */ + BRANCH_DISP8(zero != 0); + break; + +case 0xd1: /* CMP (Dloc),y */ + GET_DLOC_IND_Y_RD(); + CMP_INST(); + break; + +case 0xd2: /* CMP (Dloc) */ + GET_DLOC_IND_RD(); + CMP_INST(); + break; + +case 0xd3: /* CMP (Disp8,s),y */ + GET_DISP8_S_IND_Y_RD(); + CMP_INST(); + break; + +case 0xd4: /* PEI Dloc */ + GET_DLOC_ADDR() + GET_MEMORY16(arg, arg, 1); + CYCLES_PLUS_1; + PUSH16_UNSAFE(arg); + break; + +case 0xd5: /* CMP Dloc,x */ + GET_DLOC_X_RD(); + CMP_INST(); + break; + +case 0xd6: /* DEC Dloc,x */ + GET_DLOC_X_RD_RMW(); + DEC_INST(1); + break; + +case 0xd7: /* CMP [Dloc],Y */ + GET_DLOC_L_IND_Y_RD(); + CMP_INST(); + break; + +case 0xd8: /* CLD */ + psr = psr & (~0x8); + INC_KPC_1; + break; + +case 0xd9: /* CMP abs,y */ + GET_ABS_Y_RD(); + CMP_INST(); + break; + +case 0xda: /* PHX */ + INC_KPC_1; + if(psr & 0x10) { + PUSH8(xreg); + } else { + PUSH16(xreg); + } + break; + +case 0xdb: /* STP */ + FINISH(RET_STP, 0); + break; + +case 0xdc: /* JML (Abs) */ + GET_2BYTE_ARG; + CYCLES_PLUS_1; + GET_MEMORY24(arg, kpc, 1); + break; + +case 0xdd: /* CMP Abs,X */ + GET_ABS_X_RD(); + CMP_INST(); + break; + +case 0xde: /* DEC Abs,X */ + GET_ABS_X_RD_RMW(); + DEC_INST(0); + break; + +case 0xdf: /* CMP Long,X */ + GET_LONG_X_RD(); + CMP_INST(); + break; + +case 0xe0: /* CPX #imm */ + C_CPX_IMM(); + break; + +case 0xe1: /* SBC (Dloc,X) */ + GET_DLOC_X_IND_RD(); + SBC_INST(); + break; + +case 0xe2: /* SEP #imm */ + GET_1BYTE_ARG; + tmp2 = psr; + CYCLES_PLUS_1; + INC_KPC_2; + psr = (psr & ~0x82) | (neg7 & 0x80) | ((!zero) << 1); + psr = psr | (arg & 0xff); + zero = !(psr & 2); + neg7 = psr; + UPDATE_PSR(psr, tmp2); + break; + +case 0xe3: /* SBC Disp8,S */ + GET_DISP8_S_RD(); + SBC_INST(); + break; + +case 0xe4: /* CPX Dloc */ + C_CPX_DLOC(); + break; + +case 0xe5: /* SBC Dloc */ + GET_DLOC_RD(); + SBC_INST(); + break; + +case 0xe6: /* INC Dloc */ + GET_DLOC_RD_RMW(); + INC_INST(1); + break; + +case 0xe7: /* SBC [Dloc] */ + GET_DLOC_L_IND_RD(); + SBC_INST(); + break; + +case 0xe8: /* INX */ + INC_KPC_1; + SET_INDEX_REG(xreg + 1, xreg); + break; + +case 0xe9: /* SBC #imm */ + GET_IMM_MEM(); + SBC_INST(); + break; + +case 0xea: /* NOP */ + INC_KPC_1; + break; + +case 0xeb: /* XBA */ + tmp1 = acc & 0xff; + CYCLES_PLUS_1 + acc = (tmp1 << 8) + (acc >> 8); + INC_KPC_1; + SET_NEG_ZERO8(acc & 0xff); + break; + +case 0xec: /* CPX abs */ + C_CPX_ABS(); + break; + +case 0xed: /* SBC abs */ + GET_ABS_RD(); + SBC_INST(); + break; + +case 0xee: /* INC abs */ + GET_ABS_RD_RMW(); + INC_INST(0); + break; + +case 0xef: /* SBC long */ + GET_LONG_RD(); + SBC_INST(); + break; + +case 0xf0: /* BEQ disp8 */ + BRANCH_DISP8(zero == 0); + break; + +case 0xf1: /* SBC (Dloc),y */ + GET_DLOC_IND_Y_RD(); + SBC_INST(); + break; + +case 0xf2: /* SBC (Dloc) */ + GET_DLOC_IND_RD(); + SBC_INST(); + break; + +case 0xf3: /* SBC (Disp8,s),y */ + GET_DISP8_S_IND_Y_RD(); + SBC_INST(); + break; + +case 0xf4: /* PEA Abs */ + GET_2BYTE_ARG; + CYCLES_PLUS_1; + INC_KPC_3; + PUSH16_UNSAFE(arg); + break; + +case 0xf5: /* SBC Dloc,x */ + GET_DLOC_X_RD(); + SBC_INST(); + break; + +case 0xf6: /* INC Dloc,x */ + GET_DLOC_X_RD_RMW(); + INC_INST(1); + break; + +case 0xf7: /* SBC [Dloc],Y */ + GET_DLOC_L_IND_Y_RD(); + SBC_INST(); + break; + +case 0xf8: /* SED */ + INC_KPC_1; + psr |= 0x8; + break; + +case 0xf9: /* SBC abs,y */ + GET_ABS_Y_RD(); + SBC_INST(); + break; + +case 0xfa: /* PLX */ + INC_KPC_1; + CYCLES_PLUS_1; + if(psr & 0x10) { + PULL8(xreg); + SET_NEG_ZERO8(xreg); + } else { + PULL16(xreg); + SET_NEG_ZERO16(xreg); + } + break; + +case 0xfb: /* XCE */ + tmp2 = psr; + INC_KPC_1; + psr = (tmp2 & 0xfe) | ((tmp2 & 1) << 8) | ((tmp2 >> 8) & 1); + UPDATE_PSR(psr, tmp2); + break; + +case 0xfc: /* JSR (Abs,X) */ + GET_2BYTE_ARG; + INC_KPC_2; + tmp1 = kpc; + arg = (kpc & 0xff0000) + ((arg + xreg) & 0xffff); + GET_MEMORY16(arg, tmp2, 1); + kpc = (kpc & 0xff0000) + tmp2; + CYCLES_PLUS_2 + PUSH16_UNSAFE(tmp1); + break; + +case 0xfd: /* SBC Abs,X */ + GET_ABS_X_RD(); + SBC_INST(); + break; + +case 0xfe: /* INC Abs,X */ + GET_ABS_X_RD_RMW(); + INC_INST(0); + break; + +case 0xff: /* SBC Long,X */ + GET_LONG_X_RD(); + SBC_INST(); + break; + diff --git a/gsplus/src/iwm.c b/gsplus/src/iwm.c new file mode 100644 index 0000000..cab465e --- /dev/null +++ b/gsplus/src/iwm.c @@ -0,0 +1,3752 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// Information from Beneath Apple DOS, Apple IIgs HW reference, Apple IIgs +// Firmware reference, and Inside Macintosh Vol 3 (for status35 info). +// Neil Parker wrote about the Apple 3.5 drive using IWM at +// http://nparker.llx.com/a2/disk and it is very useful. +// http://nparker.llx.com/a2/sethook lists hooks for IWM routines, from +// $e1/0c00 - 0fab. +// ff/5fa4 is STAT35. ff/5fae is CONT35 +// When testing DOS3.3, set bp at $b942, which is bad sector detected +// IWM mode register: 7:reserved; 6:5: 0; 4: 1=8MHz,0=7MHz (should always be 0); +// 3: bit-cells are 2usec, 2: 1sec timer off; 1: async handshake (writes) +// 0: latch mode enabled for reads (holds data for 8 bit times) +// dsk->fd >= 0 indicates a valid disk. dsk->raw_data != 0 indicates this is +// a compressed disk mounted read-only, and ignore dsk->fd (which will always +// be 0, to indicate a valid disk). +// fbit_pos encodes head position on track in increments of 1/64 usecs for 5.25" +// { byte_offset, bit[2:0], sub_bit[8:0] }, so fbit_pos >> 12 gives byte offset +// fbit_mult turns dfcyc into fbit_pos, where (512/fbit_mult) = the bit cell +// 5.25" bit cell of 4usec has fbit_mult=128; cell of 3.75usec has mult=136. +// For 3.5", fbit_mult=256 indicates a 2usec bit cell. +// https://support.apple.com/kb/TA39910?locale=en_US&viewlocale=en_US gives +// the RPMs of 800KB disks + +#include "defc.h" + +int g_halt_arm_move = 0; + +extern int Verbose; +extern word32 g_vbl_count; +extern word32 g_c036_val_speed; +extern int g_config_kegs_update_needed; +extern Engine_reg engine; + +const byte phys_to_dos_sec[] = { + 0x00, 0x07, 0x0e, 0x06, 0x0d, 0x05, 0x0c, 0x04, + 0x0b, 0x03, 0x0a, 0x02, 0x09, 0x01, 0x08, 0x0f +}; + +const byte phys_to_prodos_sec[] = { + 0x00, 0x08, 0x01, 0x09, 0x02, 0x0a, 0x03, 0x0b, + 0x04, 0x0c, 0x05, 0x0d, 0x06, 0x0e, 0x07, 0x0f +}; + + +const byte to_disk_byte[] = { + 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, + 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, +/* 0x10 */ + 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, +/* 0x20 */ + 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, + 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, +/* 0x30 */ + 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; + +int g_track_bytes_35[] = { + 0x200*12, + 0x200*11, + 0x200*10, + 0x200*9, + 0x200*8 +}; + +int g_track_bits_35[] = { + // Do 1.001 * (1020484 * (60/rpm)) / 2 usec = bits per track + 77779, // Trks 0-15: 394 rpm + 71433, // Trks 16-31: 429 rpm + 64926, // Trks 32-47: 472 rpm + 58371, // Trks 48-63: 525 rpm + 51940 // Trks 64-79: 590 rpm +}; + +int g_fast_disk_emul_en = 1; +int g_fast_disk_emul = 0; +int g_slow_525_emul_wr = 0; +dword64 g_dfcyc_end_emul_wr = 0; +int g_fast_disk_unnib = 0; +int g_iwm_fake_fast = 0; + +word32 g_from_disk_byte[256]; +int g_from_disk_byte_valid = 0; + +Iwm g_iwm; + +int g_iwm_motor_on = 0; +// g_iwm_motor_on is set when drive turned on, 0 when $c0e8 is touched. +// Use this to throttle speed to 1MHz. +// g_iwm.motor_on=1 means drive is spinning. g_iwm.motor_off=1 means we're +// in the 1-second countdown of g_iwm.motor_off_vbl_count. + +int g_check_nibblization = 1; + +void +iwm_init_drive(Disk *dsk, int smartport, int drive, int disk_525) +{ + int num_tracks; + int i; + + dsk->dfcyc_last_read = 0; + dsk->raw_data = 0; + dsk->wozinfo_ptr = 0; + dsk->dynapro_info_ptr = 0; + dsk->name_ptr = 0; + dsk->partition_name = 0; + dsk->partition_num = -1; + dsk->fd = -1; + dsk->dynapro_blocks = 0; + dsk->raw_dsize = 0; + dsk->dimage_start = 0; + dsk->dimage_size = 0; + dsk->smartport = smartport; + dsk->disk_525 = disk_525; + dsk->drive = drive; + dsk->cur_frac_track = 0; + dsk->image_type = 0; + dsk->vol_num = 254; + dsk->write_prot = 1; + dsk->write_through_to_unix = 0; + dsk->disk_dirty = 0; + dsk->just_ejected = 0; + dsk->last_phases = 0; + dsk->cur_fbit_pos = 0; + dsk->fbit_mult = 128; // 128 for 5.25", 256 for 3.5" + dsk->cur_track_bits = 0; + dsk->raw_bptr_malloc = 0; + dsk->cur_trk_ptr = 0; + dsk->num_tracks = 0; + dsk->trks = 0; + if(!smartport) { + // 3.5" and 5.25" drives. This is only called at init time, + // so one-time malloc can be done now + num_tracks = 2*80; // 3.5" needs 160 + dsk->trks = (Trk *)malloc(num_tracks * sizeof(Trk)); + + for(i = 0; i < num_tracks; i++) { + dsk->trks[i].raw_bptr = 0; + dsk->trks[i].sync_ptr = 0; + dsk->trks[i].dunix_pos = 0; + dsk->trks[i].unix_len = 0; + dsk->trks[i].dirty = 0; + dsk->trks[i].track_bits = 0; + } + // num_tracks != 0 indicates a valid disk, do not set it here + } +} + +void +iwm_init() +{ + word32 val; + int i; + + memset(&g_iwm, 0, sizeof(g_iwm)); + + for(i = 0; i < 2; i++) { + iwm_init_drive(&(g_iwm.drive525[i]), 0, i, 1); + iwm_init_drive(&(g_iwm.drive35[i]), 0, i, 0); + } + + for(i = 0; i < MAX_C7_DISKS; i++) { + iwm_init_drive(&(g_iwm.smartport[i]), 1, i, 0); + } + + if(g_from_disk_byte_valid == 0) { + for(i = 0; i < 256; i++) { + g_from_disk_byte[i] = 0x100 + i; + } + for(i = 0; i < 64; i++) { + val = to_disk_byte[i]; + g_from_disk_byte[val] = i; + } + g_from_disk_byte_valid = 1; + } else { + halt_printf("iwm_init called twice!\n"); + } +} + +void +iwm_reset() +{ + int i; + + g_iwm.state = 0; + g_iwm.motor_off_vbl_count = 0; + g_iwm.forced_sync_bit = 32*12345; + g_iwm.last_rd_bit = 32*12345; + g_iwm.write_val = 0; + for(i = 0; i < 5; i++) { + g_iwm.wr_last_bit[i] = 0; + g_iwm.wr_qtr_track[i] = 0; + g_iwm.wr_num_bits[i] = 0; + g_iwm.wr_prior_num_bits[i] = 0; + g_iwm.wr_delta[i] = 0; + } + g_iwm.num_active_writes = 0; + + g_iwm_motor_on = 0; +} + +void +disk_set_num_tracks(Disk *dsk, int num_tracks) +{ + if(num_tracks <= 2*80) { + dsk->num_tracks = num_tracks; + } else { + halt_printf("num_tracks out of range: %d\n", num_tracks); + } +} + +word32 +iwm_get_default_track_bits(Disk *dsk, word32 qtr_trk) +{ + word32 track_bits, extra; + + extra = (qtr_trk + (qtr_trk >> 5)) & 0xf; + // up to 15 extra bits + if(dsk->disk_525) { + track_bits = NIB_LEN_525 * 8; // 0x18f2 = 51088 + } else { + track_bits = g_track_bits_35[qtr_trk >> 5]; + } + return track_bits + extra; +} + +void +draw_iwm_status(int line, char *buf) +{ + char *flag[2][2]; + int apple35_sel, drive_select; + + flag[0][0] = " "; + flag[0][1] = " "; + flag[1][0] = " "; + flag[1][1] = " "; + + apple35_sel = (g_iwm.state >> IWM_BIT_C031_APPLE35SEL) & 1; + drive_select = (g_iwm.state >> IWM_BIT_DRIVE_SEL) & 1; + if(g_iwm.state & IWM_STATE_MOTOR_ON) { + flag[apple35_sel][drive_select] = "*"; + } + + sprintf(buf, "s6d1:%2d%s s6d2:%2d%s s5d1:%2d/%d%s " + "s5d2:%2d/%d%s fast_disk_emul:%d,%d c036:%02x", + g_iwm.drive525[0].cur_frac_track >> 18, flag[0][0], + g_iwm.drive525[1].cur_frac_track >> 18, flag[0][1], + g_iwm.drive35[0].cur_frac_track >> 17, + (g_iwm.drive35[0].cur_frac_track >> 16) & 1, flag[1][0], + g_iwm.drive35[1].cur_frac_track >> 17, + (g_iwm.drive35[1].cur_frac_track >> 16) & 1, flag[1][1], + g_fast_disk_emul, g_slow_525_emul_wr, g_c036_val_speed); + + video_update_status_line(line, buf); +} + +void +iwm_flush_cur_disk() +{ + Disk *dsk; + + dsk = iwm_get_dsk(g_iwm.state); + iwm_flush_disk_to_unix(dsk); +} + +void +iwm_flush_disk_to_unix(Disk *dsk) +{ + byte buffer[0x4000]; + int ret, did_write; + int i; + + if((dsk->disk_dirty == 0) || (dsk->write_through_to_unix == 0)) { + return; + } + if((dsk->raw_data != 0) && !dsk->dynapro_info_ptr) { + return; + } + + printf("Writing disk %s to Unix\n", dsk->name_ptr); + + for(i = 0; i < 160; i++) { + ret = iwm_track_to_unix(dsk, i, &(buffer[0])); + + if((ret != 1) && (ret != 0)) { + printf("iwm_flush_disk_to_unix ret: %d, cannot write " + "image to unix, qtrk:%04x\n", ret, i); + halt_printf("Converting to WOZ format!\n"); + woz_reparse_woz(dsk); + return; // Don't clear dsk->disk_dirty + } + if(ret == 1) { + did_write = 1; + } + } + if(dsk->wozinfo_ptr && did_write) { + woz_check_file(dsk); + } + + dsk->disk_dirty = 0; +} + +/* Check for dirty disk 3 times a second */ + +word32 g_iwm_dynapro_last_vbl_count = 0; + +void +iwm_vbl_update() +{ + word32 state; + int i; + + state = g_iwm.state; + if((state & IWM_STATE_MOTOR_ON) && (state & IWM_STATE_MOTOR_OFF)) { + if(g_iwm.motor_off_vbl_count <= g_vbl_count) { + printf("Disk timer expired, drive off: %08x\n", + g_vbl_count); + iwm_flush_cur_disk(); + g_iwm.state = state & + (~(IWM_STATE_MOTOR_ON | IWM_STATE_MOTOR_OFF)); + g_iwm.drive525[0].dfcyc_last_phases = 0; + g_iwm.drive525[1].dfcyc_last_phases = 0; + } + } + if((g_vbl_count - g_iwm_dynapro_last_vbl_count) >= 60) { + for(i = 0; i < 2; i++) { + dynapro_try_fix_damaged_disk(&(g_iwm.drive525[i])); + dynapro_try_fix_damaged_disk(&(g_iwm.drive35[i])); + } + for(i = 0; i < MAX_C7_DISKS; i++) { + dynapro_try_fix_damaged_disk(&(g_iwm.smartport[i])); + } + g_iwm_dynapro_last_vbl_count = g_vbl_count; + } +} + +void +iwm_update_fast_disk_emul(int fast_disk_emul_en) +{ + if(g_iwm_motor_on == 0) { + g_fast_disk_emul = fast_disk_emul_en; + } +} + +void +iwm_show_stats(int slot_drive) +{ + Trk *trkptr; + Disk *dsk; + int slot, drive; + int i; + + dbg_printf("IWM state: %07x (slot_drive:%d)\n", g_iwm.state, + slot_drive); + dbg_printf("g_iwm.drive525[0].fd: %d, [1].fd: %d\n", + g_iwm.drive525[0].fd, g_iwm.drive525[1].fd); + dbg_printf("g_iwm.drive525[0].last_phases: %d, [1].last_phases: %d\n", + g_iwm.drive525[0].last_phases, g_iwm.drive525[1].last_phases); + dbg_printf("g_iwm.write_val:%02x, wr_num_bits[0]:%d, last_rd_bit:" + "%06x, forced_sync_bit:%06x\n", g_iwm.write_val, + g_iwm.wr_num_bits[0], g_iwm.last_rd_bit, + g_iwm.forced_sync_bit); + if(slot_drive < 0) { + return; + } + drive = slot_drive & 1; + slot = 5 + ((slot_drive >> 1) & 1); + dsk = iwm_get_dsk_from_slot_drive(slot, drive); + if(dsk->trks == 0) { + return; + } + for(i = 0; i < 160; i++) { + trkptr = &(dsk->trks[i]); + printf("Qtrk:%02x: bits:%05x dunix_pos:%08llx unix_len:%06x, " + "d:%d %p,%p\n", i, trkptr->track_bits, + trkptr->dunix_pos, trkptr->unix_len, trkptr->dirty, + trkptr->raw_bptr, trkptr->sync_ptr); + } +} + +Disk * +iwm_get_dsk(word32 state) +{ + Disk *dsk; + int drive; + + drive = (state >> IWM_BIT_DRIVE_SEL) & 1; + if(state & IWM_STATE_C031_APPLE35SEL) { + dsk = &(g_iwm.drive35[drive]); + } else { + dsk = &(g_iwm.drive525[drive]); + } + + return dsk; +} + +Disk * +iwm_touch_switches(int loc, dword64 dfcyc) +{ + Disk *dsk; + dword64 dadd, dcycs_passed; + word32 track_bits, fbit_pos, forced_sync_bit, mask, mask_on, state; + int phase, on, motor_on; + + if(g_iwm.state & IWM_STATE_RESET) { + iwm_printf("IWM under reset: %06x\n", g_iwm.state); + } + + dsk = iwm_get_dsk(g_iwm.state); + + track_bits = dsk->cur_track_bits; + fbit_pos = track_bits * 4096; // Set to an illegal value + motor_on = g_iwm.state & IWM_STATE_MOTOR_ON; + if(motor_on) { + dcycs_passed = (dfcyc - dsk->dfcyc_last_read) >> 16; + dsk->dfcyc_last_read = dfcyc; + + // track_bits can be 0, indicating no valid data + dadd = dcycs_passed * dsk->fbit_mult; + if(track_bits) { + if(dadd >= (track_bits * 512)) { + dadd = dadd % (track_bits * 512); + } + } + fbit_pos = dsk->cur_fbit_pos + (word32)dadd; + if(fbit_pos >= (track_bits * 512)) { + fbit_pos -= (track_bits * 512); + } + if(g_slow_525_emul_wr || (g_iwm.state & IWM_STATE_Q7) || + !g_fast_disk_emul || !track_bits) { + dsk->cur_fbit_pos = fbit_pos; + } else { + fbit_pos = track_bits << 12; // out of range value + } +#if 0 + printf("dsk->cur_fbit:%08x, fbit_pos:%08x\n", + dsk->cur_fbit_pos, fbit_pos); +#endif + } + + dbg_log_info(dfcyc, dsk->cur_fbit_pos, + ((loc & 0xff) << 24) | g_iwm.state, + ((dsk->cur_frac_track + 0x8000) & 0x1ff0000) | 0xe0); + + if(loc < 0) { + // Not a real access, just a way to update fbit_pos + return dsk; + } + + if(dsk->dfcyc_last_phases) { + iwm525_update_head(dsk, dfcyc); + } + + on = loc & 1; + phase = loc >> 1; + state = g_iwm.state; + if(loc < 8) { + /* phase adjustments. See if motor is on */ + + mask = (1 << (phase & 3)) << IWM_BIT_PHASES; + mask_on = (on << (phase & 3)) << IWM_BIT_PHASES; + state = (state & (~mask)) | mask_on; + g_iwm.state = state; + iwm_printf("Iwm phase %d=%d, all phases: %06x (%016llx)\n", + phase, on, state, dfcyc); + + if(state & IWM_STATE_MOTOR_ON) { + if(state & IWM_STATE_C031_APPLE35SEL) { + if((phase == 3) && on) { + iwm_do_action35(dfcyc); + } + } else { + // Move apple525 head + iwm525_update_phases(dsk, dfcyc); + } + } + state = g_iwm.state; + /* See if enable or reset is asserted */ + if(((state >> IWM_BIT_PHASES) & 5) == 5) { // Ph 0, 2 set + state |= IWM_STATE_RESET; + iwm_printf("IWM reset active\n"); + } else { + state &= (~IWM_STATE_RESET); + } + if(((state >> IWM_BIT_PHASES) & 0xa) == 0xa) { // Ph 1, 3 set + state |= IWM_STATE_ENABLE2; + iwm_printf("IWM ENABLE2 active\n"); + } else { + state &= (~IWM_STATE_ENABLE2); + } + g_iwm.state = state; + } else { + /* loc >= 8 */ + switch(loc) { + case 0x8: + iwm_printf("Turning IWM motor off!\n"); + if(g_iwm.state & 0x04) { // iwm MODE + /* Turn off immediately */ + state &= (~(IWM_STATE_MOTOR_ON | + IWM_STATE_MOTOR_OFF)); + } else { + /* 1 second delay */ + if((state & IWM_STATE_MOTOR_ON) && + !(state & IWM_STATE_MOTOR_OFF)){ + state |= IWM_STATE_MOTOR_OFF; + g_iwm.motor_off_vbl_count = g_vbl_count + + 60; + } + } + g_iwm.state = state; + +#if 0 + printf("IWM motor off, g_iwm_motor_on:%d, slow:%d\n", + g_iwm_motor_on, g_slow_525_emul_wr); +#endif + if(g_iwm_motor_on || g_slow_525_emul_wr) { + /* recalc current speed */ + g_fast_disk_emul = g_fast_disk_emul_en; + engine_recalc_events(); + iwm_flush_cur_disk(); + } + + g_iwm_motor_on = 0; + g_slow_525_emul_wr = 0; + break; + case 0x9: + iwm_printf("Turning IWM motor on!\n"); + state |= IWM_STATE_MOTOR_ON; + state &= (~IWM_STATE_MOTOR_OFF); + state &= (~IWM_STATE_LAST_SEL35); + state |= ((state >> 6) & 1) << IWM_BIT_LAST_SEL35; + g_iwm.state = state; + dsk->dfcyc_last_read = dfcyc; + + if(g_iwm_motor_on == 0) { + /* recalc current speed */ + if(dsk->wozinfo_ptr) { + g_fast_disk_emul = 0; + } + engine_recalc_events(); + } + g_iwm_motor_on = 1; + + break; + case 0xa: + case 0xb: + if(((state >> IWM_BIT_DRIVE_SEL) & 1) != on) { + iwm_flush_cur_disk(); + } + state &= (~IWM_STATE_DRIVE_SEL); + state |= (on << IWM_BIT_DRIVE_SEL); + g_iwm.state = state; + dsk = iwm_get_dsk(state); + if(dsk->wozinfo_ptr && g_iwm_motor_on) { + g_fast_disk_emul = 0; + } else { + g_fast_disk_emul = g_fast_disk_emul_en; + } + break; + case 0xc: + case 0xd: + mask = IWM_STATE_Q6 | IWM_STATE_Q7 | + IWM_STATE_MOTOR_ON | IWM_STATE_ENABLE2; + mask_on = IWM_STATE_Q6 | IWM_STATE_MOTOR_ON; + if(((state & mask) == mask_on) && !on) { + // q6 on, q7 off, !on, motor_on, !enable2 + // Switched from $c08d to $c08c, resync now + // If latch mode, back up one more bit + // (for The School Speller - Tools.woz) + forced_sync_bit = iwm_calc_bit_sum( + fbit_pos >> 9, 0 - (state & 1), + track_bits); + g_iwm.forced_sync_bit = forced_sync_bit; + g_iwm.last_rd_bit = forced_sync_bit; + dbg_log_info(dfcyc, forced_sync_bit*2, 0, + 0x500ec); + } + state &= (~IWM_STATE_Q6); + state |= (on << IWM_BIT_Q6); + g_iwm.state = state; + break; + case 0xe: + case 0xf: + mask = IWM_STATE_Q7 | IWM_STATE_MOTOR_ON | + IWM_STATE_ENABLE2; + mask_on = IWM_STATE_Q7 | IWM_STATE_MOTOR_ON; + if(((state & mask) == mask_on) && !on) { + // q7, !on, motor_on, !enable2 +#if 0 + printf("state:%04x and new q7:%d, write_end\n", + state, on); +#endif + dbg_log_info(dfcyc, state, mask, 0xed); + iwm_write_end(dsk, 1, dfcyc); + // printf("write end complete, the track:\n"); + // iwm_check_nibblization(dfcyc); + } + state &= (~IWM_STATE_Q7); + state |= (on << IWM_BIT_Q7); + g_iwm.state = state; + break; + default: + printf("iwm_touch_switches: loc: %02x unknown!\n", loc); + exit(2); + } + } + + if(!(state & IWM_STATE_Q7)) { + g_iwm.num_active_writes = 0; + if(g_slow_525_emul_wr) { + g_slow_525_emul_wr = 0; + engine_recalc_events(); + } + } + + return dsk; +} + +void +iwm_move_to_ftrack(Disk *dsk, word32 new_frac_track, int delta, dword64 dfcyc) +{ + Trk *trkptr; + word32 track_bits, cur_frac_track, wr_last_bit, write_val; + int disk_525, drive, new_track, cur_track, max_track; + int num_active_writes; + + if(dsk->smartport) { + return; + } + cur_frac_track = dsk->cur_frac_track; + if(delta != 0) { + // 3.5" move, clear out lower fractional track bits + new_frac_track = new_frac_track & (-0x10000); + if((delta < 0) && (new_frac_track < (word32)(-delta))) { + // Moving down past track 0...stop, but preserve side + new_frac_track = cur_frac_track & 0x10000; + delta = 0; + } + } + new_frac_track = new_frac_track + delta; + + disk_525 = dsk->disk_525; +#if 0 + printf("iwm_move_to_track: %07x, num_tracks:%03x (cur:%07x) %016llx\n", + new_frac_track, dsk->num_tracks, dsk->cur_frac_track, dfcyc); +#endif + + max_track = 36*4 - 1; // Go to track 35.75 on 5.25" + if(dsk->num_tracks >= (max_track + 1)) { + max_track = dsk->num_tracks - 1; + } + if(max_track >= 159) { + max_track = 159; // Limit to 159 always + } + if(new_frac_track > (word32)(max_track << 16)) { + new_frac_track = max_track << 16; + } + new_track = (new_frac_track + 0x8000) >> 16; + + cur_track = (cur_frac_track + 0x8000) >> 16; + num_active_writes = g_iwm.num_active_writes; + wr_last_bit = g_iwm.wr_last_bit[0]; + write_val = g_iwm.write_val; + if(num_active_writes) { + iwm_write_end(dsk, 0, dfcyc); + printf("moving arm to new_track:%d, write active:%d, bit:%08x, " + "write_val:%02x\n", new_track, num_active_writes, + wr_last_bit, write_val); + } + if((cur_track != new_track) || (dsk->cur_trk_ptr == 0)) { + drive = dsk->drive + 1; + if(1) { + // Don't do this printf + } else if(disk_525) { + printf("s6d%d Track: %d.%02d\n", drive, + new_track >> 2, 25* (new_track & 3)); + } else { + printf("s5d%d Track: %d Side: %d\n", drive, + new_track >> 1, new_track & 1); + } + + trkptr = &(dsk->trks[new_track]); + track_bits = trkptr->track_bits; + if((track_bits == 0) && disk_525) { + // Use an adjacent .25 track if it is valid + if((new_track > 0) && (trkptr[-1].track_bits != 0)) { + new_track--; + // printf("Moved dn to qtrk:%04x\n", new_track); + } else if((new_track < max_track) && + (trkptr[1].track_bits != 0)) { + new_track++; + // printf("Moved up to qtrk:%04x\n", new_track); + } + } + iwm_flush_disk_to_unix(dsk); + iwm_move_to_qtr_track(dsk, new_track); + if(dfcyc != 0) { + dbg_log_info(dfcyc, cur_frac_track, new_frac_track, + (g_iwm.state << 16) | 0x00f000e1); +#if 0 + printf("Just moved from track %08x to %08x %016llx\n", + cur_frac_track, new_frac_track, dfcyc); +#endif + } + } + dsk->cur_frac_track = new_frac_track; + if(num_active_writes) { + iwm_start_write(dsk, wr_last_bit, write_val, 1); + } +} + +void +iwm_move_to_qtr_track(Disk *dsk, word32 qtr_track) +{ + Trk *trkptr; + word32 track_bits, fbit_pos; + + trkptr = &(dsk->trks[qtr_track]); + track_bits = trkptr->track_bits; + + dsk->cur_trk_ptr = trkptr; + dsk->cur_track_bits = track_bits; + fbit_pos = dsk->cur_fbit_pos; + if(track_bits) { + // Moving to a valid track. Ensure fbit_pos in range + fbit_pos = fbit_pos % (track_bits * 512); + } + dsk->cur_fbit_pos = fbit_pos; +} + +dword64 g_iwm_last_phase_dfcyc = 0; + +void +iwm525_update_phases(Disk *dsk, dword64 dfcyc) +{ + word32 ign_mask; + int last_phases, new_phases, eff_last_phases, eff_new_phases; + int my_phase; + + // Decide if dsk->last_phases needs to change. + last_phases = dsk->last_phases; + new_phases = (g_iwm.state >> IWM_BIT_PHASES) & 0xf; + if(last_phases != new_phases) { + iwm_printf("Phases changing %02x -> %02x, ftrack:%08x at %lld, " + "diff:%.2fmsec\n", last_phases, new_phases, + dsk->cur_frac_track, dfcyc >> 16, + ((dfcyc - g_iwm_last_phase_dfcyc) >> 16) / 1000.0); + g_iwm_last_phase_dfcyc = dfcyc; + } + my_phase = (dsk->cur_frac_track >> 17) & 3; + ign_mask = 1 << ((2 + my_phase) & 3); + eff_last_phases = last_phases & (~ign_mask); + eff_new_phases = new_phases & (~ign_mask); + if(eff_last_phases == eff_new_phases) { + dsk->last_phases = new_phases; + return; // Nothing to do + } + + // Update last_phases + iwm525_update_head(dsk, dfcyc); + + dsk->last_phases = new_phases; + dsk->dfcyc_last_phases = dfcyc; + dbg_log_info(dfcyc, new_phases, dsk->cur_frac_track, 0x100e1); +} + +void +iwm525_update_head(Disk *dsk, dword64 dfcyc) +{ + double dinc; + dword64 diff_dusec, dfcyc_last_phases; + word32 cur_frac_track, frac_track, sum, num, phases, diff; + int new_qtrk, cur_qtrk, my_phase, prev_phase, grind, inc; + int i; + + cur_frac_track = dsk->cur_frac_track; + my_phase = (dsk->cur_frac_track >> 17) & 3; + + // If my_phase is 1, then phase 0 being on should decrement the trk + // number, and phase 2 being on should increment the trk number + phases = dsk->last_phases & 0xf; // one bit for each phase + phases = (phases << 4) | phases; + prev_phase = (my_phase + 4 - 1) & 3; + phases = (phases >> prev_phase) & 0xf; + + // Now, phases[0] means decrement trk, phases[1] is where we are, + // phases[2] means increment trk, and phases[3] MIGHT mean increment + sum = 0; + num = 0; + grind = 0; + for(i = 0; i < 4; i++) { + if(((phases >> i) & 1) == 0) { + continue; + } + frac_track = cur_frac_track & -0x20000; + switch(i) { + case 0: // Previous phase is on, decrement trk + if(frac_track >= 0x20000) { + frac_track -= 0x20000; + } else { + frac_track = 0; + } + sum += frac_track; + num++; + if(cur_frac_track == 0) { + grind++; + } + break; + case 1: // "our" phase, pull to aligned track + sum += frac_track; + num++; + break; + case 2: // Next phase, increment track + sum += frac_track + 0x20000; + num++; + break; + case 3: // Next next phase: might pull + // Phase is nominally 2 away...but if cur_frac_track + // is just a little less than a new halftrack, + // (0xxx1ffff), then it may be within a half track + // and we'll let it pull us + frac_track += 0x20000; + if((frac_track - cur_frac_track) < 0x30000) { + sum += frac_track; + num++; + } + } + } + + frac_track = cur_frac_track; + dfcyc_last_phases = dsk->dfcyc_last_phases; + dsk->dfcyc_last_phases = 0; + if(num) { + frac_track = sum / num; // New desired track + // Now see if enough time has elapsed that we got to frac_track + diff_dusec = (dfcyc - dfcyc_last_phases) >> 16; + dinc = 0x20000 / 2800.0; // 2.8msec to move one phase + inc = (int)dinc; + if(cur_frac_track >= frac_track) { + diff = cur_frac_track - frac_track; + inc = -inc; + } else { + diff = frac_track - cur_frac_track; + } + if(g_fast_disk_emul) { + diff = 0; // Always moved enough + } + // Add inc*diff_dusec to cur_frac_track, unless we already reach + if(diff > (dinc * diff_dusec)) { + // Enough time has NOT elapsed, so calc where head is + // Update frac_track to be cur_frac_track + inc * dusec + frac_track = cur_frac_track + + (word32)(inc * diff_dusec); + dsk->dfcyc_last_phases = dfcyc; + } + } + + new_qtrk = (frac_track + 0x8000) >> 16; + cur_qtrk = (cur_frac_track + 0x8000) >> 16; + if(new_qtrk != cur_qtrk) { + if(g_halt_arm_move) { + halt_printf("Halt on arm move\n"); + g_halt_arm_move = 0; + } + if(grind) { + printf("GRIND GRIND GRIND\n"); + } + + dbg_log_info(dfcyc, frac_track, dsk->cur_frac_track, + (phases << 24) | 0x00e1); + iwm_move_to_ftrack(dsk, frac_track, 0, dfcyc); + + if(new_qtrk > 2) { +#if 0 + printf("Moving to qtr track: %04x (trk:%d.%02d), %d, " + "%02x, %08x dfcyc:%lld\n", new_qtrk, + new_qtrk >> 2, 25*(new_qtrk & 3), my_phase, + phases, g_iwm.state, dfcyc >> 16); +#endif + } + } else { + // On the same qtr_track, but update the fraction + dsk->cur_frac_track = frac_track; + } + +#if 0 + /* sanity check stepping algorithm */ + if((qtr_track & 7) == 0) { + /* check for just access phase 0 */ + if(last_phase != 0) { + halt_printf("last_phase: %d!\n", last_phase); + } + } +#endif +} + +int +iwm_read_status35(dword64 dfcyc) +{ + Disk *dsk; + word32 state; + int drive, stat35, tmp; + + // This is usually done by STAT35 at ff/5fa4 + // This code is called on the rising edge of Q6 asserted + state = g_iwm.state; + drive = (state >> IWM_BIT_DRIVE_SEL) & 1; + dsk = &(g_iwm.drive35[drive]); + + if(state & IWM_STATE_MOTOR_ON) { + /* Read status: ph[1], ph[0], disk35, ph[2] */ + stat35 = (((state >> IWM_BIT_PHASES) & 3) << 2) | + ((state >> 6) & 2) | + (((state >> IWM_BIT_PHASES) >> 2) & 1); + + iwm_printf("Iwm status read state: %02x\n", state); + dbg_log_info(dfcyc, state, stat35, 0xe7); + + switch(stat35) { + case 0x00: /* step direction */ + return (state >> IWM_BIT_STEP_DIRECTION35) & 1; + break; + case 0x01: /* lower head activate */ + /* also return instantaneous data from head */ + iwm_move_to_ftrack(dsk, + (dsk->cur_frac_track & (-0x20000)), 0, dfcyc); + return (dsk->cur_fbit_pos >> 15) & 1; + break; + case 0x02: /* disk in place */ + /* 1 = no disk, 0 = disk */ + iwm_printf("read disk in place, num_tracks: %d\n", + dsk->num_tracks); + tmp = (dsk->num_tracks <= 0); + dbg_log_info(dfcyc, 0, dsk->num_tracks, 0x100e7); + return tmp; + break; + case 0x03: /* upper head activate */ + /* also return instantaneous data from head */ + iwm_move_to_ftrack(dsk, dsk->cur_frac_track | 0x10000, + 0, dfcyc); + return (dsk->cur_fbit_pos >> 15) & 1; + break; + case 0x04: /* disk is stepping? */ + /* 1 = not stepping, 0 = stepping */ + return 1; + break; + case 0x05: /* Unknown function of ROM 03? */ + /* 1 = or $20 into 0xe1/f24+drive, 0 = don't */ + return 1; + break; + case 0x06: /* disk is locked */ + /* 0 = locked, 1 = unlocked */ + return (!dsk->write_prot); + break; + case 0x08: /* motor on */ + /* 0 = on, 1 = off */ + return !(state & IWM_STATE_MOTOR_ON35); + break; + case 0x09: /* number of sides */ + /* 1 = 2 sides, 0 = 1 side */ + return 1; + break; + case 0x0a: /* at track 0 */ + /* 1 = not at track 0, 0 = there */ + tmp = (dsk->cur_frac_track != 0); + iwm_printf("Read at track0_35: %d\n", tmp); + return tmp; + break; + case 0x0b: /* disk ready??? */ + /* 0 = ready, 1 = not ready? */ + tmp = !(state & IWM_STATE_MOTOR_ON35); + iwm_printf("Read disk ready, ret: %d\n", tmp); + return tmp; + break; + case 0x0c: /* disk switched?? */ + /* 0 = not switched, 1 = switched? */ + tmp = (dsk->just_ejected != 0); + iwm_printf("Read disk switched: %d\n", tmp); + return tmp; + break; + case 0x0d: /* false read when ejecting disk */ + return 1; + case 0x0e: /* tachometer */ + halt_printf("Reading tachometer!\n"); + return (dsk->cur_fbit_pos >> 11) & 1; + break; + case 0x0f: /* drive installed? */ + /* 0 = drive exists, 1 = no drive */ + if(drive) { + /* pretend no drive 1 */ + return 1; + } + return 0; + break; + default: + halt_printf("Read 3.5 status, stat35: %02x\n", stat35); + return 1; + } + } else { + iwm_printf("Read 3.5 status with drive off!\n"); + return 1; + } +} + +void +iwm_do_action35(dword64 dfcyc) +{ + Disk *dsk; + word32 state; + int drive, stat35; + + // Actions done by CONT35 routine at ff/5fae + // This code is called on the rising edge of phase[3] asserted + state = g_iwm.state; + drive = (state >> IWM_BIT_DRIVE_SEL) & 1; + dsk = &(g_iwm.drive35[drive]); + + if(state & IWM_STATE_MOTOR_ON) { + /* Perform action */ + /* stat35: ph[1], ph[0], disk35_ctrl, ph[2] */ + stat35 = (((state >> IWM_BIT_PHASES) & 3) << 2) | + ((state >> 6) & 2) | + (((state >> IWM_BIT_PHASES) >> 2) & 1); + dbg_log_info(dfcyc, state, stat35, 0xf00e7); + + switch(stat35) { + case 0x00: /* Set step direction inward (higher tracks) */ + /* towards higher tracks, clear STEP_DIRECTION35 */ + state &= (~IWM_STATE_STEP_DIRECTION35); + iwm_printf("Iwm set step dir35 = 0\n"); + break; + case 0x01: /* Set step direction outward (lower tracks) */ + /* towards lower tracks */ + state |= IWM_STATE_STEP_DIRECTION35; + dbg_log_info(dfcyc, state, stat35, 0x300e7); + iwm_printf("Iwm set step dir35 = 1\n"); + break; + case 0x03: /* reset disk-switched flag? */ + iwm_printf("Iwm reset disk switch\n"); + dsk->just_ejected = 0; + break; + case 0x04: /* step disk */ + if(state & IWM_STATE_STEP_DIRECTION35) { + iwm_move_to_ftrack(dsk, dsk->cur_frac_track, + -0x20000, dfcyc); + } else { + iwm_move_to_ftrack(dsk, dsk->cur_frac_track, + 0x20000, dfcyc); + } + break; + case 0x08: /* turn motor on */ + iwm_printf("Iwm set motor_on35 = 1\n"); + state |= IWM_STATE_MOTOR_ON35; + break; + case 0x09: /* turn motor off */ + iwm_printf("Iwm set motor_on35 = 0\n"); + state &= (~IWM_STATE_MOTOR_ON35); + break; + case 0x0d: /* eject disk */ + printf("Action 0x0d, will eject disk\n"); + iwm_eject_disk(dsk); + break; + case 0x02: + case 0x07: + case 0x0b: /* hacks to allow AE 1.6MB driver to not crash me */ + break; + default: + halt_printf("Do 3.5 action, state: %02x\n", state); + return; + } + } else { + halt_printf("Set 3.5 status with drive off!\n"); + return; + } + g_iwm.state = state; + dbg_log_info(dfcyc, state, stat35, 0x400e7); +} + +int +read_iwm(int loc, dword64 dfcyc) +{ + Disk *dsk; + word32 status, bit_diff, bit_pos, state; + int on, q7_q6, val; + + loc = loc & 0xf; + on = loc & 1; + + dsk = iwm_touch_switches(loc, dfcyc); + + state = g_iwm.state; + q7_q6 = (state >> IWM_BIT_Q6) & 3; + + if(on) { + /* odd address, return 0 */ + return 0; + } else { + /* even address */ + switch(q7_q6) { + case 0x00: /* q7 = 0, q6 = 0 */ + if(state & IWM_STATE_ENABLE2) { + return iwm_read_enable2(dfcyc); + } else { + if(state & IWM_STATE_MOTOR_ON) { + return iwm_read_data(dsk, dfcyc); + } else { + iwm_printf("read iwm st 0, m off!\n"); +/* HACK!!!! */ + return 0xff; + //return (((int)dfcyc) & 0x7f) + 0x80; + } + } + break; + case 0x01: /* q7 = 0, q6 = 1 */ + /* read IWM status reg */ + if(state & IWM_STATE_ENABLE2) { + iwm_printf("Read status under enable2: 1\n"); + status = 1; + } else { + if(state & IWM_STATE_C031_APPLE35SEL) { + status = iwm_read_status35(dfcyc); + } else { + status = dsk->write_prot; + } + } + + val = ((status & 1) << 7) | (state & 0x3f); + // bit 5 is motor_on, bits[4:0] are iwm_mode + iwm_printf("Read status: %02x\n", val); + + return val; + break; + case 0x02: /* q7 = 1, q6 = 0 */ + /* read handshake register */ + if(state & IWM_STATE_ENABLE2) { + return iwm_read_enable2_handshake(dfcyc); + } else { + status = 0xc0; + bit_pos = dsk->cur_fbit_pos >> 9; + bit_diff = iwm_calc_bit_diff(bit_pos, + g_iwm.wr_last_bit[0], + dsk->cur_track_bits); + if(bit_diff > 8) { + iwm_printf("Write underrun!\n"); + status = status & 0xbf; + } + return status; + } + break; + case 0x03: /* q7 = 1, q6 = 1 */ + iwm_printf("read iwm q7_q6=3!\n"); + return 0; + break; + } + } + halt_printf("Got to end of read_iwm, loc: %02x!\n", loc); + + return 0; +} + +void +write_iwm(int loc, int val, dword64 dfcyc) +{ + Disk *dsk; + word32 state; + int on, q7_q6, drive, fast_writes; + + loc = loc & 0xf; + on = loc & 1; + + dsk = iwm_touch_switches(loc, dfcyc); + + state = g_iwm.state; + q7_q6 = (state >> IWM_BIT_Q6) & 3; + drive = (state >> IWM_BIT_DRIVE_SEL) & 1; + fast_writes = g_fast_disk_emul; + if(state & IWM_STATE_C031_APPLE35SEL) { + dsk = &(g_iwm.drive35[drive]); + } else { + dsk = &(g_iwm.drive525[drive]); + fast_writes = !g_slow_525_emul_wr && fast_writes; + } + + if(on) { + /* odd address, write something */ + if(q7_q6 == 3) { + /* q7, q6 = 1,1 */ + if(state & IWM_STATE_MOTOR_ON) { + if(state & IWM_STATE_ENABLE2) { + iwm_write_enable2(val); + } else { + iwm_write_data(dsk, val, dfcyc); + } + } else { + /* write mode register */ + // bit 0: latch mode (should set if async hand) + // bit 1: async handshake + // bit 2: immediate motor off (no 1 sec delay) + // bit 3: 2us bit timing + // bit 4: Divide input clock by 8 (instead of 7) + val = val & 0x1f; + state = (state & (~0x1f)) | val; + g_iwm.state = state; + if(val & 0x10) { + iwm_printf("set iwm_mode:%02x!\n",val); + } + } + } else { + if(state & IWM_STATE_ENABLE2) { + iwm_write_enable2(val); + } else { +#if 0 +// Flobynoid writes to 0xc0e9 causing these messages... + printf("Write iwm1, st: %02x, loc: %x: %02x\n", + q7_q6, loc, val); +#endif + } + } + return; + } else { + /* even address */ + if(state & IWM_STATE_ENABLE2) { + iwm_write_enable2(val); + } else { + iwm_printf("Write iwm2, st: %02x, loc: %x: %02x\n", + q7_q6, loc, val); + } + return; + } + + return; +} + + +int +iwm_read_enable2(dword64 dfcyc) +{ + iwm_printf("Read under enable2 %016llx!\n", dfcyc); + return 0xff; +} + +int g_cnt_enable2_handshake = 0; + +int +iwm_read_enable2_handshake(dword64 dfcyc) +{ + int val; + + iwm_printf("Read handshake under enable2, %016llx!\n", dfcyc); + + val = 0xc0; + g_cnt_enable2_handshake++; + if(g_cnt_enable2_handshake > 3) { + g_cnt_enable2_handshake = 0; + val = 0x80; + } + + return val; +} + +void +iwm_write_enable2(int val) +{ + // Smartport is selected (PH3=1, PH1=1, Sel35=0), just ignore this data + iwm_printf("Write under enable2: %02x!\n", val); + + return; +} + +word32 +iwm_fastemul_start_write(Disk *dsk, dword64 dfcyc) +{ + double new_fast_cycs; + dword64 dfcyc_passed; + word32 fbit_pos, fbit_diff, track_bits; + + // Nox Archaist doesn't finish reading sector header's checksum, but + // instead waits for 7 bytes to pass and then writes. This code + // tries to allow fast_disk_emul mode to not overwrite the checksum. + // Move the fbit_pos forward to try to account for a delay from the + // last read to the current write. But accesses to I/O locations + // would still take a lot of time. Let's assume there were + // 2 slow cycles, and clamp the skip to a min of 1 byte, max 3 bytes. + dfcyc_passed = dfcyc - g_iwm.dfcyc_last_fastemul_read; + new_fast_cycs = (((double)dfcyc_passed) - 0x20000) / + ((double)engine.fplus_ptr->dplus_1); + // new_fast_cycs approximates the number of fast cycles that have + // passed, so 4.0 means 4 fast cycles have passed + + iwm_printf("start write, dfcyc:%016llx, new_f_cycs:%f, plus_1:%08llx\n", + dfcyc_passed, new_fast_cycs, engine.fplus_ptr->dplus_1); + + fbit_diff = (word32)(new_fast_cycs * dsk->fbit_mult); + if(new_fast_cycs < 0.0) { + fbit_diff = 8*512; // 8 bits + } else { + if(fbit_diff < 8*512) { // 8 bits + fbit_diff = 8*512; + } else if(fbit_diff > (32*512)) { + fbit_diff = 32*512; + } + } + track_bits = dsk->cur_track_bits; + fbit_pos = dsk->cur_fbit_pos; + + if(track_bits == 0) { + return fbit_pos; + } + + fbit_pos = fbit_pos + fbit_diff; + if(fbit_pos >= (track_bits * 512)) { + fbit_pos = fbit_pos - (track_bits * 512); + } +#if 0 + printf(" adjusted fbit_pos from %07x to %07x\n", + dsk->cur_fbit_pos, fbit_pos); +#endif + dbg_log_info(dfcyc, fbit_pos, dsk->cur_fbit_pos, 0xee); + dsk->cur_fbit_pos = fbit_pos; + + return fbit_pos; +} + +word32 +iwm_read_data_fast(Disk *dsk, dword64 dfcyc) +{ + dword64 dval, dsync_val_tmp, dsync_val; + word32 bit_pos, new_bit_pos, track_bits, val; + int msb, dec; + + if(!g_fast_disk_unnib) { + g_iwm.dfcyc_last_fastemul_read = dfcyc; + } + track_bits = dsk->cur_track_bits; + bit_pos = dsk->cur_fbit_pos >> 9; + + // fbit_pos points just after the LSB of the last nibble. Read +8 + // past to get the next nibble. Get more to ensure a valid nibble + new_bit_pos = iwm_calc_bit_sum(bit_pos, 15, track_bits); + dval = iwm_get_raw_bits(dsk, new_bit_pos, 16, &dsync_val_tmp); + dsync_val = dsync_val_tmp; +#if 0 + printf("fast %010llx syncs:%010llx bit:%07x\n", dval, dsync_val, + bit_pos * 2); +#endif + + if(!g_fast_disk_unnib) { + //dbg_log_info(dfcyc, dval, dsync_val, 0xeb); + } + // Find the sync val closest to 15 without going over + msb = (dsync_val >> 8) & 0xff; + if((msb > 15) || (msb == 0)) { + msb = dsync_val & 0xff; + } + if(msb > 15) { + msb = 8; // Just return something + } + if(msb < 7) { + // This can happen when the arm is moved from a long track to a + // shorter track, fbit_pos at an arbitrary position at the + // track start, placing us at dsync_val=0x1006. Just ignore + msb = 7; + } + val = (word32)(dval >> (msb - 7)); + + // We've moved new_fbit_pos forward 15 bits, move back 7 bits for msb=15 + dec = 15 - msb - 7; + if(!g_iwm_motor_on && (msb == 15) && !g_fast_disk_unnib) { + // Return this byte properly...but not the next one for the + // DOS3.3 RWTS motor on detect code, which reads the drive + // without enabling the motor, to see if the motor is on + dec--; + } + if((msb != 15) && !g_fast_disk_unnib) { + // Pull a trick to make the disk motor-on test pass ($bd34 in + // DOS 3.3 RWTS): if this is a sync byte, don't return whole + // byte, but return whole byte next time + val = val & 0x7f; + dec = dec - 8; + } + dsk->cur_fbit_pos = iwm_calc_bit_sum(new_bit_pos, dec, track_bits) << 9; +#if 0 + printf(" val:%02x fbit:%07x new_fbit:%07x dec:%d, msb:%d\n", + val & 0xff, dsk->cur_fbit_pos, new_fbit_pos, dec, msb); +#endif + if(!g_fast_disk_unnib) { + dbg_log_info(dfcyc, dsk->cur_fbit_pos, + (dec << 24) | (bit_pos * 2), (val << 16) | 0xeb); + } + return val & 0xff; +} + +dword64 +iwm_return_rand_data(Disk *dsk, dword64 dfcyc) +{ + dword64 dval, dval2; + + if(dsk) { + // Use dsk + } + dval = dfcyc >> 16; + dval2 = dval ^ (dval >> 16) ^ (dval << 10) ^ (dval << 26) ^ + (dval << 41); + dval = dval2 & ((dval >> 6) ^ (dval >> 17)); + return dval; +} + +dword64 +iwm_get_raw_bits(Disk *dsk, word32 bit_pos, int num_bits, dword64 *dsyncs_ptr) +{ + byte *bptr, *sync_ptr; + dword64 dval, dtmp_val, sync_dval; + word32 track_bits; + int pos, bits, total_bits, sync_pos, sync; + + track_bits = dsk->cur_track_bits; + if(track_bits == 0) { + halt_printf("iwm_get_raw_bits track_bits 0, %08x\n", + dsk->cur_frac_track); + return 0; + } + total_bits = 0; + bits = 1 + (bit_pos & 7); // 1..8 + pos = bit_pos >> 3; + dval = 0; + sync_dval = 0; + bptr = &(dsk->cur_trk_ptr->raw_bptr[0]); + sync_ptr = &(dsk->cur_trk_ptr->sync_ptr[0]); + sync_pos = 0; + if((bptr == 0) || (sync_ptr == 0)) { + halt_printf("bptr:%p, sync:%p, bit_pos:%08x, track_bits:%08x, " + "cur_trk_ptr eff:%05lx\n", bptr, sync_ptr, bit_pos, + track_bits, dsk->cur_trk_ptr - &(dsk->trks[0])); + *dsyncs_ptr = 0xffffffffffffffULL; + return 0; + } + while(total_bits < num_bits) { + dtmp_val = bptr[pos]; + dtmp_val = dtmp_val >> (8 - bits); + dval = dval | (dtmp_val << total_bits); + sync = sync_ptr[pos]; + if((sync < 8) && (sync >= (8 - bits))) { // MSB is here + sync = sync - (8 - bits); + sync = sync + total_bits; + if((sync_pos < 64) && (sync != (sync_dval & 0xff))){ + sync_dval |= ((dword64)sync & 0xff) << sync_pos; + sync_pos += 8; + } + } + total_bits += bits; + bits = 8; + pos--; + if(pos < 0) { + pos = (track_bits - 1) >> 3; + bits = 1 + ((track_bits - 1) & 7); + } + } + + if(sync_pos == 0) { + sync_dval = 0xff; + } + *dsyncs_ptr = sync_dval; + return dval; +} + +word32 +iwm_calc_bit_diff(word32 first, word32 last, word32 track_bits) +{ + word32 val; + + val = first - last; + if(val >= track_bits) { + val = val + track_bits; + } + return val; +} + +word32 +iwm_calc_bit_sum(word32 bit_pos, int add_ival, word32 track_bits) +{ + word32 val; + + val = bit_pos + add_ival; + if(val >= track_bits) { + if(add_ival < 0) { + val = val + track_bits; + } else { + val = val - track_bits; + } + } + return val; +} + +dword64 +iwm_calc_forced_sync(dword64 dval, int forced_bit) +{ + dword64 sync_dval; + + // Something like the "E7" protection scheme has toggled + // $c0ed in the middle of reading a nibble, causing a new sync. This + // is used to "move" sync bits inside disk nibbles, to defeat + // nibble copiers (since a 36-cycle sync nibble cannot be + // differentiated from a 40-cycle sync nibble in one read pass) + // Return these misaligned nibbles until we re-sync (then clear + // g_iwm.forced_sync_bit. Toggling $c0ed can set the data latch + // to $ff is the disk is write-protected, but this gets cleared + // to $00 within 4 CPU cycles always (or less). A phantom 1 will + // also shift in + + dval |= (1ULL << forced_bit); + sync_dval = 30; // A default for the previous nibble + while(forced_bit >= 0) { + // Scan until this bit is set + if((dval >> forced_bit) & 1) { + // this bit is the MSB of a nibble, note it + sync_dval = (sync_dval << 8) | forced_bit; + forced_bit -= 7; + } + forced_bit--; + } + return sync_dval; +} + +int +iwm_calc_forced_sync_0s(dword64 sync_dval, int bits) +{ + int sync, forced_bits, done; + int i; + + // Return number between 7 and bits as to the oldest valid sync bit + // in the sync_dval array. 0xff in the first position means no syncs + if(bits <= 8) { + return bits; + } + forced_bits = 8; + done = 0; + if((sync_dval & 0xff) <= 7) { + sync_dval = sync_dval >> 8; + } + for(i = 0; i < 8; i++) { + sync = sync_dval & 0xff; + if(sync == 0xff) { + return bits; + } + sync_dval = sync_dval >> 8; + while(sync > bits) { + sync -= 8; // Bring it back into range + done = 1; + } + if(sync < forced_bits) { + break; + } + if(sync < bits) { + forced_bits = sync; + } + if(done) { + break; + } + } + return forced_bits; +} + +word32 +iwm_read_data(Disk *dsk, dword64 dfcyc) +{ + dword64 dval, sync_dval, dsync_val_tmp, dval0, dval2; + word32 track_bits, fbit_pos, bit_pos, val, forced_sync_bit; + int msb_bit, bits, forced_bits, diff; + + track_bits = dsk->cur_track_bits; + + fbit_pos = dsk->cur_fbit_pos; + bit_pos = fbit_pos >> 9; + if((track_bits == 0) || (dsk->cur_trk_ptr == 0)) { + val = ((fbit_pos * 25) >> 11) & 0xff; + iwm_printf("Reading c0ec, track_len 0, returning %02x\n", val); + return val; + } + + if(g_fast_disk_emul) { + return iwm_read_data_fast(dsk, dfcyc); + } + + // First, get the last few bytes of data + bits = iwm_calc_bit_diff(bit_pos, g_iwm.last_rd_bit, track_bits) + 10; + if(bits < 25) { + bits = 25; + } else if(bits > 60) { + bits = 60; + } + forced_sync_bit = g_iwm.forced_sync_bit; + forced_bits = -1; + if(forced_sync_bit < track_bits) { + forced_bits = iwm_calc_bit_diff(bit_pos, forced_sync_bit, + track_bits); + if(forced_bits >= 64) { + forced_bits = 63; + } + if(forced_bits > bits) { + bits = forced_bits; + } + } + dval = iwm_get_raw_bits(dsk, bit_pos, bits, &dsync_val_tmp); + sync_dval = dsync_val_tmp; + + // See if there are runs of more than three 0 bits...introduce noise + dval0 = (1ULL << bits) | dval; + dval0 = dval0 | (dval0 >> 1) | (dval0 >> 2) | (dval0 >> 3); + dval0 = (~dval0) & ((1ULL << bits) - 1); + + if(dval0) { + // Each set bit of dval0 indicates the previous 3 bits are 0 + dval2 = dval0 | (dval0 << 1); + dval2 = dval0 & iwm_return_rand_data(dsk, dfcyc); +#if 0 + printf("dval0 is %016llx, dval2:%016llx, bits:%d\n", dval0, + dval2, bits); +#endif + dval = dval ^ dval2; + if(forced_bits < 0) { + forced_bits = iwm_calc_forced_sync_0s(sync_dval, bits); + g_iwm.forced_sync_bit = iwm_calc_bit_sum(bit_pos, + 0 - forced_bits, track_bits); + dbg_log_info(dfcyc, g_iwm.forced_sync_bit * 2, + (forced_bits << 24) | (bit_pos * 2), 0x400ec); +#if 0 + printf("Forced bits are %d at %06x, %016llx " + "%016llx\n", forced_bits, bit_pos * 2, dval, + sync_dval); +#endif + } + } + if(forced_bits >= 0) { + sync_dval = iwm_calc_forced_sync(dval, forced_bits); + dval = dval & ((2ULL << forced_bits) - 1LL); + dbg_log_info(dfcyc, (word32)dval, forced_sync_bit * 32, + (forced_bits << 16) | 0xea); + if(((dsync_val_tmp & 0xff) == (sync_dval & 0xff)) && (!dval0)) { + // Only clear it if there are no 0's in the dval, + // otherwise we'll reenter and could calc a diff sync + g_iwm.forced_sync_bit = 234567*4; + //printf("cleared forced_sync\n"); + } else { + g_iwm.forced_sync_bit = iwm_calc_bit_sum(bit_pos, + 0 - (sync_dval & 0xf), track_bits); + } + +#if 0 + printf("Forced_bits were %d, sync_was %016llx\n", + forced_bits, dsync_val_tmp); +#endif + } +#if 0 + printf(" Raw bits: %016llx, dsync:%016llx, fbit:%08x\n", dval, + sync_dval, fbit_pos); +#endif + + dbg_log_info(dfcyc, (word32)dval, + (bits << 24) | ((word32)sync_dval & 0x00ffffffUL), 0x300ec); + msb_bit = sync_dval & 0xff; + if(g_iwm.state & 1) { // Latch mode (3.5" disk) + // last_rd_bit points just past the last bit read (it is + // fbit_pos normally, which is one bit past the read bit). + // Use latched data if that bit is before the latched LSB + diff = iwm_calc_bit_diff(bit_pos, g_iwm.last_rd_bit, + track_bits); + diff = diff + 8; // Calc to the MSB + msb_bit = (sync_dval >> 8) & 0xff; + if((msb_bit >= 8) && (diff > msb_bit)) { + // We've not seen the LSB of this data: ret latched data + dbg_log_info(dfcyc, bit_pos * 2, + (msb_bit << 16) | (diff & 0xffff), + 0x2200ec); + bit_pos = iwm_calc_bit_sum(bit_pos, -msb_bit + 8, + track_bits); + dbg_log_info(dfcyc, bit_pos * 2, g_iwm.last_rd_bit*2, + 0x200ec); + } else { + msb_bit = sync_dval & 0xff; + if(diff <= msb_bit) { + // Handle case of 10-bit nibble which we've + // already returned...don't return it again! + msb_bit = 1; + } + } +#if 0 + printf("Latch mode diff:%d, msb_bit:%d, new_msb:%d\n", + diff, msb_bit, (int)(sync_dval & 0xff)); +#endif + dbg_log_info(dfcyc, ((word32)diff << 12) | (msb_bit & 0xff), + (word32)sync_dval, 0x100ec); + } else { + if((sync_dval & 0xff) <= 1) { + // The LSbit or the next one is a sync bit--but we need + // to ignore it. The LSbit is not valid yet, and + // the next bit being the MSB of the next nibble will + // be ignored to make the 7usec hold time of the + // previous nibble work + sync_dval = sync_dval >> 8; + msb_bit = sync_dval & 0xff; + } + } + val = (word32)dval; + if(msb_bit < 8) { + // We only have a partial byte + val = val & ((2ULL << msb_bit) - 1); + } else if(msb_bit < 63) { + val = (word32)(dval >> (msb_bit - 8)); + } + + // val is now valid from bit 8 down to bit 1 + // We've allowed in to dval an extra bit which we must toss + val = val >> 1; + + g_iwm.last_rd_bit = bit_pos; + dbg_log_info(dfcyc, (msb_bit << 24) | (fbit_pos >> 8), + (word32)(dval0 << 8) | (val & 0xff), 0xec); +#if 0 + if(forced_bits >= 0) { + printf("Forced bits, msb:%d, val:%02x, dval:%016llx b_p:%06x\n", + msb_bit, val, dval, bit_pos * 2); + } +#endif + return val & 0xff; +} + +void +iwm_write_data(Disk *dsk, word32 val, dword64 dfcyc) +{ + Trk *trk; + word32 track_bits, bit_pos, fbit_pos, bit_last, tmp_val; + int bits; + + trk = dsk->cur_trk_ptr; + if((trk == 0) || dsk->write_prot) { + return; + } + track_bits = dsk->cur_track_bits; + + bit_pos = (dsk->cur_fbit_pos + 511) >> 9; + if(track_bits && (bit_pos >= track_bits)) { + bit_pos = bit_pos - track_bits; + if(bit_pos >= track_bits) { + bit_pos = bit_pos % track_bits; + } + } +#if 0 + printf("iwm_write_data: %02x %016llx, bit*2:%06x\n", val, dfcyc, + bit_pos *2); +#endif + if(dsk->disk_525) { + if(!g_slow_525_emul_wr) { + g_slow_525_emul_wr = 1; + engine_recalc_events(); + if(track_bits && g_fast_disk_emul) { + fbit_pos = iwm_fastemul_start_write(dsk, dfcyc); + bit_pos = fbit_pos >> 9; + } + } + } + dsk->cur_fbit_pos = bit_pos * 512; + if(g_iwm.num_active_writes == 0) { + // No write was pending, enter write mode now + // printf("Starting write of data to the track, track now:\n"); + // iwm_show_track(-1, -1, dfcyc); + // printf("Write data to track at bit*2:%06x\n", bit_pos); + iwm_start_write(dsk, bit_pos, val, 0); + return; + } + if(track_bits == 0) { + halt_printf("Impossible: track_bits: 0\n"); + return; + } + if(g_iwm.state & 2) { // async handshake mode, 3.5" + iwm_write_data35(dsk, val, dfcyc); + return; + } + + // From here on, it's 5.25" disks only + bit_last = g_iwm.wr_last_bit[0]; + bits = iwm_calc_bit_diff(bit_pos, bit_last, track_bits); + if(bits >= 500) { + halt_printf("bits are %d. bit*2:%06x, bit_last*2:%06x\n", + bits, bit_pos * 2, bit_last * 2); + bits = 40; + } + iwm_write_one_nib(dsk, bits, dfcyc); + + tmp_val = g_iwm.write_val; + dbg_log_info(dfcyc, (g_iwm.wr_last_bit[0] << 25) | (bit_last * 2), + (bits << 16) | ((val & 0xff) << 8) | (tmp_val & 0xff), 0xed); + g_iwm.write_val = val; +} + +void +iwm_start_write(Disk *dsk, word32 bit_pos, word32 val, int no_prior) +{ + int num; + + g_iwm.write_val = val; + num = 0; + num = iwm_start_write_act(dsk, bit_pos, num, no_prior, 0); + num = iwm_start_write_act(dsk, bit_pos, num, no_prior, -1); // -0.25 + num = iwm_start_write_act(dsk, bit_pos, num, no_prior, +1); // +0.25 + num = iwm_start_write_act(dsk, bit_pos, num, no_prior, -2); // -0.50 + num = iwm_start_write_act(dsk, bit_pos, num, no_prior, +2); // +0.50 + g_iwm.num_active_writes = num; + + woz_maybe_reparse(dsk); +} + +int +iwm_start_write_act(Disk *dsk, word32 bit_pos, int num, int no_prior, int delta) +{ + Trk *trkptr; + word32 qtr_track, track_bits, bit_diff, prior_sum, allow_diff; + + qtr_track = (dsk->cur_frac_track + 0x8000) >> 16; + qtr_track += delta; + if(qtr_track >= 160) { // Could be unsigned wrap around + if(delta == 0) { + halt_printf("iwm_start_write_act0, qtr_track:%04x\n", + qtr_track); + g_iwm.num_active_writes = 0; + } + return num; + } + if(!dsk->disk_525 && delta) { + return num; // 3.5" does not affect adjacent trks + } + + trkptr = &(dsk->trks[qtr_track]); + track_bits = trkptr->track_bits; + if(track_bits == 0) { + if((delta == -2) || (delta == 2)) { + return num; // Nothing to do + } + if((delta == -1) && (qtr_track > 0)) { + if(trkptr[-1].track_bits == 0) { + return num; // Nothing to do + } + } + if((delta == 1) && (qtr_track < 159)) { + if(trkptr[1].track_bits == 0) { + return num; // Nothing to do + } + } + // Otherwise, we need to create this track and write to it + track_bits = woz_add_a_track(dsk, qtr_track); + } + if(track_bits) { + bit_pos = bit_pos % track_bits; + } + bit_diff = iwm_calc_bit_diff(bit_pos, g_iwm.wr_last_bit[num], + track_bits); + prior_sum = 0; + allow_diff = 16 + (16 * g_fast_disk_emul); + if((g_iwm.wr_qtr_track[num] == qtr_track) && (bit_diff < allow_diff)) { + // consider this write a continuation of the previous write + prior_sum = g_iwm.wr_prior_num_bits[num] + + g_iwm.wr_num_bits[num] + bit_diff; +#if 0 + printf("prior_sum is %d, qtr_track:%04x, bit_diff:%d\n", + prior_sum, qtr_track, bit_diff); +#endif + } else { +#if 0 + printf("No prior_sum, qtr_track:%04x vs %04x, bit_diff:%08x, " + "bit_pos:%05x wr_last_bit:%05x\n", + g_iwm.wr_qtr_track[num], qtr_track, bit_diff, + bit_pos * 2, g_iwm.wr_last_bit[num]*2); +#endif + } + if(no_prior) { + prior_sum = 0; + } + if(delta == 0) { + dsk->cur_fbit_pos = bit_pos << 9; + iwm_move_to_qtr_track(dsk, qtr_track); + } + g_iwm.wr_last_bit[num] = bit_pos; + g_iwm.wr_qtr_track[num] = qtr_track; + g_iwm.wr_num_bits[num] = 0; + g_iwm.wr_prior_num_bits[num] = prior_sum; + g_iwm.wr_delta[num] = abs(delta); // 0, 1 or 2 + return num + 1; +} + +void +iwm_write_data35(Disk *dsk, word32 val, dword64 dfcyc) +{ + // Just always write 8 bits to the track + iwm_write_one_nib(dsk, 8, dfcyc); + g_iwm.write_val = val; + dsk->cur_fbit_pos = g_iwm.wr_last_bit[0] * 512; +} + +void +iwm_write_end(Disk *dsk, int write_through_now, dword64 dfcyc) +{ + Trk *trkptr; + word32 last_bit, qtr_track, num_bits, bit_start, delta, track_bits; + word32 prior_sum; + int num_active_writes; + int i; + + // Flush out previous write, then turn writing off + num_active_writes = g_iwm.num_active_writes; +#if 0 + printf("In iwm_write_end at %016llx, num:%d, %d\n", dfcyc, + num_active_writes, dsk->disk_dirty); +#endif + if(num_active_writes == 0) { + return; // Invalid, not in a write + } + if(write_through_now) { + iwm_write_data(dsk, 0, dfcyc); + } + + for(i = 0; i < num_active_writes; i++) { + last_bit = g_iwm.wr_last_bit[i]; + qtr_track = g_iwm.wr_qtr_track[i]; + num_bits = g_iwm.wr_num_bits[i]; + delta = g_iwm.wr_delta[i]; +#if 0 + printf(" end %d, last_bit:%05x, qtrk:%04x, num_b:%d, " + "delta:%d\n", i, last_bit * 2, qtr_track, num_bits, + delta); +#endif + if((num_bits == 0) || (qtr_track >= 160)) { + continue; + } + trkptr = &(dsk->trks[qtr_track]); + track_bits = trkptr->track_bits; + prior_sum = g_iwm.wr_prior_num_bits[i]; + if((num_bits + prior_sum) >= track_bits) { + // Full track write. If delta != 0, erase this track + printf("Full track write at qtrk:%04x %016llx\n", + qtr_track, dfcyc); +#if 0 + if(qtr_track == 4) { + halt_printf("Full track at qtr_trk:4\n"); + } +#endif + if(delta != 0) { + woz_remove_a_track(dsk, qtr_track); + printf("TRACK %04x REMOVED\n", qtr_track); + continue; + } + g_iwm.wr_prior_num_bits[i] = 0; + } + + // Otherwise, recalc sync for this track + if(num_bits >= track_bits) { + num_bits = num_bits % track_bits; + } + bit_start = iwm_calc_bit_sum(last_bit, -(int)num_bits - 24, + track_bits); + iwm_recalc_sync_from(dsk, qtr_track, bit_start, dfcyc); +#if 0 + printf("Wrote %d bits to qtrk:%04x at %05x %016llx, i:%d,%d, " + "%d\n", num_bits, qtr_track, bit_start*2, dfcyc, i, + num_active_writes, dsk->disk_dirty); +#endif + } + g_iwm.num_active_writes = 0; + + woz_maybe_reparse(dsk); +} + +void +iwm_write_one_nib(Disk *dsk, int bits, dword64 dfcyc) +{ + word32 qtr_track, bit_pos, delta, val, track_bits; + int num; + int i; + + num = g_iwm.num_active_writes; + val = g_iwm.write_val; + for(i = 0; i < num; i++) { + qtr_track = g_iwm.wr_qtr_track[i]; + bit_pos = g_iwm.wr_last_bit[i]; + delta = g_iwm.wr_delta[i]; + dbg_log_info(dfcyc, val, bit_pos * 2, 0x200ed); + if(delta == 2) { // Trk +0.50 and -0.50: corrupt + val = (val & 0x7f) ^ i ^ 0x0c; + } + bit_pos = disk_nib_out_raw(dsk, qtr_track, val, bits, bit_pos, + dfcyc); + track_bits = dsk->trks[qtr_track].track_bits; + if(bit_pos >= track_bits) { + bit_pos = bit_pos - track_bits; + } + g_iwm.wr_last_bit[i] = bit_pos; + g_iwm.wr_num_bits[i] += bits; + dbg_log_info(dfcyc, (bits << 24) | (bit_pos * 2), + 2*iwm_calc_bit_sum(bit_pos, 0-g_iwm.wr_num_bits[i], + track_bits), 0x300ed); + } +} + +void +iwm_recalc_sync_from(Disk *dsk, word32 qtr_track, word32 bit_pos, dword64 dfcyc) +{ + Trk *trkptr; + byte *bptr, *sync_ptr; + word32 track_bits, val, this_sync, sync0; + int pos, next_pos, wrap, bit, last_bit, last_byte, match, this_bits; + + // We are called with a bit_pos 3 bytes before the desired byte. + // Look at pos, pos-1, pos+1 in a safe way, and find a valid sync_num + if(dfcyc) { + //printf("new sync from %d, %016llx %p\n", bit_pos, dfcyc, dsk); + } + if(qtr_track >= 160) { + halt_printf("iwm_recalc_sync_from bad qtr:%04x bit_pos:%06x\n", + qtr_track, bit_pos); + return; + } + trkptr = &(dsk->trks[qtr_track]); + + track_bits = trkptr->track_bits; + if(track_bits == 0) { + halt_printf("iwm_recalc_sync_from track_bits 0 for %04x\n", + qtr_track); + return; + } + last_byte = (track_bits - 1) >> 3; + last_bit = ((track_bits - 1) & 7) + 1; // 1...8 + + sync_ptr = &(trkptr->sync_ptr[0]); + bptr = &(trkptr->raw_bptr[0]); + pos = bit_pos >> 3; + bit = bit_pos & 7; // 0...7 + sync0 = sync_ptr[pos]; + if(sync0 >= 8) { + if(pos > 0) { + pos--; + } else { + pos++; // pos is now 1 + } + sync0 = sync_ptr[pos]; + } + bit = (7 - sync0) & 7; + match = 0; + wrap = 0; + next_pos = pos + 1; + // cnt = 0; + // printf("recalc_sync_from pos:%04x, bit:%d\n", pos, bit); + while(1) { + val = bptr[pos]; + this_bits = 8; + this_sync = sync_ptr[pos]; + sync_ptr[pos] = 0x20; + next_pos = pos + 1; + if(pos >= last_byte) { +#if 0 + printf("At last_byte, val:%02x, pos:%04x, last_bit:%d, " + "bit:%d\n", val, pos, last_bit, bit); +#endif + this_bits = last_bit; + val = val & (0xff00 >> last_bit); + next_pos = 0; + wrap++; + if(wrap >= 3) { + halt_printf("no stable sync found\n"); + return; + } + if(bit >= this_bits) { + // Skip over this partial byte to byte 0 + bit -= this_bits; + pos = 0; + continue; + } + } +#if 0 + if(wrap || (pos >= 0x2630)) { + printf("pos:%04x, bit:%d, val:%04x, this_bits:%d\n", + pos, bit, val, this_bits); + } +#endif +#if 0 + if((cnt++ < 10) && (dsk->cur_frac_track == 0)) { + printf("sync[%04x]=%02x, val:%02x bit:%d, this_b:%d\n", + pos, this_sync, val, bit, this_bits); + } +#endif + // bit is within this byte. Find next set bit + while(bit < this_bits) { + if(((val << bit) & 0x80) == 0) { + // Slide to next bit + bit++; + continue; + } + sync_ptr[pos] = 7 - bit; + if(this_sync == (word32)(7 - bit)) { + match++; + if(match >= 7) { +#if 0 + printf("match %d at pos:%04x wrap:%d\n", + match, pos, wrap); +#endif + return; + } + } else { + match = 0; + } + break; + } + bit = (bit + 8 - this_bits) & 7; + pos = next_pos; + } +} + +/* c600 */ +void +sector_to_partial_nib(byte *in, byte *nib_ptr) +{ + byte *aux_buf, *nib_out; + word32 val, val2; + int x; + int i; + + /* Convert 256(+1) data bytes to 342+1 disk nibbles */ + + aux_buf = nib_ptr; + nib_out = nib_ptr + 0x56; + + for(i = 0; i < 0x56; i++) { + aux_buf[i] = 0; + } + + x = 0x55; + for(i = 0x101; i >= 0; i--) { + val = in[i]; + if(i >= 0x100) { + val = 0; + } + val2 = (aux_buf[x] << 1) + (val & 1); + val = val >> 1; + val2 = (val2 << 1) + (val & 1); + val = val >> 1; + nib_out[i] = val; + aux_buf[x] = val2; + x--; + if(x < 0) { + x = 0x55; + } + } +} + + +int +disk_unnib_4x4(Disk *dsk) +{ + int val1; + int val2; + + val1 = iwm_read_data_fast(dsk, 0); + val2 = iwm_read_data_fast(dsk, 0); + + return ((val1 << 1) + 1) & val2; +} + +int +iwm_denib_track525(Disk *dsk, word32 qtr_track, byte *outbuf) +{ + byte aux_buf[0x80]; + int sector_done[16]; + byte *buf; + word32 val, val2, prev_val, save_frac_track; + int track_len, vol, track, phys_sec, log_sec, cksum, x, my_nib_cnt; + int save_fbit_pos, tmp_fbit_pos, status, ret, num_sectors_done; + int i; + + //printf("iwm_denib_track525\n"); + + save_fbit_pos = dsk->cur_fbit_pos; + save_frac_track = dsk->cur_frac_track; + iwm_move_to_qtr_track(dsk, qtr_track); + + dsk->cur_fbit_pos = 0; + g_fast_disk_unnib = 1; + + track_len = (dsk->cur_track_bits + 7) >> 3; + + for(i = 0; i < 16; i++) { + sector_done[i] = 0; + } + + num_sectors_done = 0; + + val = 0; + status = -1; + my_nib_cnt = 0; + while(my_nib_cnt++ < 2*track_len) { + /* look for start of a sector */ + if(val != 0xd5) { + val = iwm_read_data_fast(dsk, 0); + continue; + } + + val = iwm_read_data_fast(dsk, 0); + if(val != 0xaa) { + continue; + } + + val = iwm_read_data_fast(dsk, 0); + if(val != 0x96) { + continue; + } + + /* It's a sector start */ + vol = disk_unnib_4x4(dsk); + track = disk_unnib_4x4(dsk); + phys_sec = disk_unnib_4x4(dsk); + if(phys_sec < 0 || phys_sec > 15) { + printf("Track %02x, read sec as %02x\n", + qtr_track >> 2, phys_sec); + break; + } + if(dsk->image_type == DSK_TYPE_DOS33) { + log_sec = phys_to_dos_sec[phys_sec]; + } else { + log_sec = phys_to_prodos_sec[phys_sec]; + } + cksum = disk_unnib_4x4(dsk); + if((vol ^ track ^ phys_sec ^ cksum) != 0) { + /* not correct format */ + printf("Track %02x not DOS 3.3 since hdr cksum, %02x " + "%02x %02x %02x\n", qtr_track >> 2, + vol, track, phys_sec, cksum); + break; + } + + /* see what sector it is */ + if(track != (qtr_track >> 2) || (phys_sec < 0) || + (phys_sec > 15)) { + printf("Track %02x bad since track: %02x, sec: %02x\n", + qtr_track>>2, track, phys_sec); + break; + } + + if(sector_done[phys_sec]) { + printf("Already done sector %02x on track %02x!\n", + phys_sec, qtr_track>>2); + break; + } + + /* So far so good, let's do it! */ + val = 0; + i = 0; + while(i < NIBS_FROM_ADDR_TO_DATA) { + i++; + if(val != 0xd5) { + val = iwm_read_data_fast(dsk, 0); + continue; + } + + val = iwm_read_data_fast(dsk, 0); + if(val != 0xaa) { + continue; + } + + val = iwm_read_data_fast(dsk, 0); + if(val != 0xad) { + continue; + } + + /* got it, just break */ + break; + } + + if(i >= NIBS_FROM_ADDR_TO_DATA) { + printf("No data header, track %02x, sec %02x at %07x\n", + qtr_track>>2, phys_sec, dsk->cur_fbit_pos); + break; + } + + buf = outbuf + 0x100*log_sec; + + /* Data start! */ + prev_val = 0; + for(i = 0x55; i >= 0; i--) { + val = iwm_read_data_fast(dsk, 0); + val2 = g_from_disk_byte[val]; + if(val2 >= 0x100) { + printf("Bad data area1, val:%02x,val2:%03x\n", + val, val2); + printf(" i:%03x, fbit_pos:%08x\n", i, + dsk->cur_fbit_pos); + break; + } + prev_val = val2 ^ prev_val; + aux_buf[i] = prev_val; + } + + /* rest of data area */ + for(i = 0; i < 0x100; i++) { + val = iwm_read_data_fast(dsk, 0); + val2 = g_from_disk_byte[val]; + if(val2 >= 0x100) { + printf("Bad data area2, read: %02x\n", val); + printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); + break; + } + prev_val = val2 ^ prev_val; + buf[i] = prev_val; + } + + /* checksum */ + val = iwm_read_data_fast(dsk, 0); + val2 = g_from_disk_byte[val]; + if(val2 >= 0x100) { + printf("Bad data area3, read: %02x\n", val); + printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); + break; + } + if(val2 != prev_val) { + printf("Bad data cksum, got %02x, wanted: %02x\n", + val2, prev_val); + printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); + break; + } + + val = iwm_read_data_fast(dsk, 0); + if(val != 0xde) { + printf("No 0xde at end of sector data:%02x\n", val); + printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); + break; + } + + val = iwm_read_data_fast(dsk, 0); + if(val != 0xaa) { + printf("No 0xde,0xaa at end of sector:%02x\n", val); + printf(" fbit_pos: %07x\n", dsk->cur_fbit_pos); + break; + } + + /* Got this far, data is good, merge aux_buf into buf */ + x = 0x55; + for(i = 0; i < 0x100; i++) { + val = aux_buf[x]; + val2 = (buf[i] << 1) + (val & 1); + val = val >> 1; + val2 = (val2 << 1) + (val & 1); + buf[i] = val2; + val = val >> 1; + aux_buf[x] = val; + x--; + if(x < 0) { + x = 0x55; + } + } + sector_done[phys_sec] = 1; + num_sectors_done++; + if(num_sectors_done >= 16) { + status = 0; + break; + } + } + + tmp_fbit_pos = dsk->cur_fbit_pos; + g_fast_disk_unnib = 0; + + ret = 0; + if(status != 0) { + printf("Nibblization not done, %02x sectors found qtrk %04x, " + "drive:%d, slot:%d\n", num_sectors_done, qtr_track, + dsk->drive, dsk->disk_525 + 5); + printf("my_nib_cnt: %05x, fbit_pos:%07x, trk_len:%05x\n", + my_nib_cnt, tmp_fbit_pos, track_len); + ret = 16; + for(i = 0; i < 16; i++) { + printf("sector_done[%d] = %d\n", i, sector_done[i]); + if(sector_done[i]) { + ret--; + } + } + iwm_show_a_track(dsk, dsk->cur_trk_ptr, 0); + if(!ret) { + ret = -1; + } + } else { + //printf("iwm_denib_track525 succeeded\n"); + } + + dsk->cur_fbit_pos = save_fbit_pos; + iwm_move_to_ftrack(dsk, save_frac_track, 0, 0); + + return ret; +} + +int +iwm_denib_track35(Disk *dsk, word32 qtr_track, byte *outbuf) +{ + word32 buf_c00[0x100]; + word32 buf_d00[0x100]; + word32 buf_e00[0x100]; + int sector_done[16]; + byte *buf; + word32 tmp_5c, tmp_5d, tmp_5e, tmp_66, tmp_67, val, val2; + word32 save_frac_track; + int num_sectors_done, track_len, phys_track, phys_sec, phys_side; + int phys_capacity, cksum, tmp, track, side, num_sectors, x, y; + int carry, my_nib_cnt, save_fbit_pos, status, ret; + int i; + + save_fbit_pos = dsk->cur_fbit_pos; + save_frac_track = dsk->cur_frac_track; + iwm_move_to_qtr_track(dsk, qtr_track); + + if(dsk->cur_trk_ptr == 0) { + return 0; + } + + dsk->cur_fbit_pos = 0; + g_fast_disk_unnib = 1; + + track_len = dsk->cur_track_bits >> 3; + + num_sectors = g_track_bytes_35[qtr_track >> 5] >> 9; + + for(i = 0; i < num_sectors; i++) { + sector_done[i] = 0; + } + + num_sectors_done = 0; + + val = 0; + status = -1; + my_nib_cnt = 0; + + track = qtr_track >> 1; + side = qtr_track & 1; + + while(my_nib_cnt++ < 2*track_len) { + /* look for start of a sector */ + if(val != 0xd5) { + val = iwm_read_data_fast(dsk, 0); + continue; + } + + val = iwm_read_data_fast(dsk, 0); + if(val != 0xaa) { + continue; + } + + val = iwm_read_data_fast(dsk, 0); + if(val != 0x96) { + continue; + } + + /* It's a sector start */ + val = iwm_read_data_fast(dsk, 0); + phys_track = g_from_disk_byte[val]; + if(phys_track != (track & 0x3f)) { + printf("Track %02x.%d, read track %02x, %02x\n", + track, side, phys_track, val); + break; + } + + phys_sec = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; + if((phys_sec < 0) || (phys_sec >= num_sectors)) { + printf("Track %02x.%d, read sector %02x??\n", + track, side, phys_sec); + break; + } + phys_side = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; + + if(phys_side != ((side << 5) + (track >> 6))) { + printf("Track %02x.%d, read side %02x??\n", + track, side, phys_side); + break; + } + phys_capacity = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; + if(phys_capacity != 0x24 && phys_capacity != 0x22) { + printf("Track %02x.%x capacity: %02x != 0x24/22\n", + track, side, phys_capacity); + } + cksum = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; + + tmp = phys_track ^ phys_sec ^ phys_side ^ phys_capacity; + if(cksum != tmp) { + printf("Track %02x.%d, sector %02x, cksum: %02x.%02x\n", + track, side, phys_sec, cksum, tmp); + break; + } + + + if(sector_done[phys_sec]) { + printf("Already done sector %02x on track %02x.%x!\n", + phys_sec, track, side); + break; + } + + /* So far so good, let's do it! */ + val = 0; + for(i = 0; i < 38; i++) { + val = iwm_read_data_fast(dsk, 0); + if(val == 0xd5) { + break; + } + } + if(val != 0xd5) { + printf("No data header, track %02x.%x, sec %02x\n", + track, side, phys_sec); + break; + } + + val = iwm_read_data_fast(dsk, 0); + if(val != 0xaa) { + printf("Bad data hdr1,val:%02x trk %02x.%x, sec %02x\n", + val, track, side, phys_sec); + printf("fbit_pos: %08x\n", dsk->cur_fbit_pos); + break; + } + + val = iwm_read_data_fast(dsk, 0); + if(val != 0xad) { + printf("Bad data hdr2,val:%02x trk %02x.%x, sec %02x\n", + val, track, side, phys_sec); + printf("dsk->cur_fbit_pos:%07x\n", dsk->cur_fbit_pos); + break; + } + + buf = outbuf + (phys_sec << 9); + + /* check sector again */ + tmp = g_from_disk_byte[iwm_read_data_fast(dsk, 0)]; + if(tmp != phys_sec) { + printf("Bad data hdr3,val:%02x trk %02x.%x, sec %02x\n", + val, track, side, phys_sec); + break; + } + + /* Data start! */ + tmp_5c = 0; + tmp_5d = 0; + tmp_5e = 0; + y = 0xaf; + carry = 0; + + while(y > 0) { +/* 626f */ + val = iwm_read_data_fast(dsk, 0); + val2 = g_from_disk_byte[val]; + if(val2 >= 0x100) { + printf("Bad data area1b, read: %02x\n", val); + printf(" i:%03x, fbit_pos:%07x\n", i, + dsk->cur_fbit_pos); + break; + } + tmp_66 = val2; + + tmp_5c = tmp_5c << 1; + carry = (tmp_5c >> 8); + tmp_5c = (tmp_5c + carry) & 0xff; + + val = iwm_read_data_fast(dsk, 0); + val2 = g_from_disk_byte[val]; + if(val2 >= 0x100) { + printf("Bad data area2, read: %02x\n", val); + break; + } + + val2 = val2 + ((tmp_66 << 2) & 0xc0); + + val2 = val2 ^ tmp_5c; + buf_c00[y] = val2; + + tmp_5e = val2 + tmp_5e + carry; + carry = (tmp_5e >> 8); + tmp_5e = tmp_5e & 0xff; +/* 62b8 */ + val = iwm_read_data_fast(dsk, 0); + val2 = g_from_disk_byte[val]; + val2 = val2 + ((tmp_66 << 4) & 0xc0); + val2 = val2 ^ tmp_5e; + buf_d00[y] = val2; + tmp_5d = val2 + tmp_5d + carry; + + carry = (tmp_5d >> 8); + tmp_5d = tmp_5d & 0xff; + + y--; + if(y <= 0) { + break; + } + +/* 6274 */ + val = iwm_read_data_fast(dsk, 0); + val2 = g_from_disk_byte[val]; + val2 = val2 + ((tmp_66 << 6) & 0xc0); + val2 = val2 ^ tmp_5d; + buf_e00[y+1] = val2; + + tmp_5c = val2 + tmp_5c + carry; + carry = (tmp_5c >> 8); + tmp_5c = tmp_5c & 0xff; + } + +/* 62d0 */ + val = iwm_read_data_fast(dsk, 0); + val2 = g_from_disk_byte[val]; + + tmp_66 = (val2 << 6) & 0xc0; + tmp_67 = (val2 << 4) & 0xc0; + val2 = (val2 << 2) & 0xc0; + + val = iwm_read_data_fast(dsk, 0); + val2 = g_from_disk_byte[val] + val2; + if(tmp_5e != (word32)val2) { + printf("Checksum 5e bad: %02x vs %02x\n", tmp_5e, val2); + printf("val:%02x trk %02x.%x, sec %02x\n", + val, track, side, phys_sec); + break; + } + + val = iwm_read_data_fast(dsk, 0); + val2 = g_from_disk_byte[val] + tmp_67; + if(tmp_5d != (word32)val2) { + printf("Checksum 5d bad: %02x vs %02x\n", tmp_5e, val2); + printf("val:%02x trk %02x.%x, sec %02x\n", + val, track, side, phys_sec); + break; + } + + val = iwm_read_data_fast(dsk, 0); + val2 = g_from_disk_byte[val] + tmp_66; + if(tmp_5c != (word32)val2) { + printf("Checksum 5c bad: %02x vs %02x\n", tmp_5e, val2); + printf("val:%02x trk %02x.%x, sec %02x\n", + val, track, side, phys_sec); + break; + } + + /* Whew, got it!...check for DE AA */ + val = iwm_read_data_fast(dsk, 0); + if(val != 0xde) { + printf("Bad data epi1,val:%02x trk %02x.%x, sec %02x\n", + val, track, side, phys_sec); + printf("fbit_pos: %08x\n", dsk->cur_fbit_pos); + break; + } + + val = iwm_read_data_fast(dsk, 0); + if(val != 0xaa) { + printf("Bad data epi2,val:%02x trk %02x.%x, sec %02x\n", + val, track, side, phys_sec); + break; + } + + /* Now, convert buf_c/d/e to output */ +/* 6459 */ + y = 0; + for(x = 0xab; x >= 0; x--) { + *buf++ = buf_c00[x]; + y++; + if(y >= 0x200) { + break; + } + + *buf++ = buf_d00[x]; + y++; + if(y >= 0x200) { + break; + } + + *buf++ = buf_e00[x]; + y++; + if(y >= 0x200) { + break; + } + } + + sector_done[phys_sec] = 1; + num_sectors_done++; + if(num_sectors_done >= num_sectors) { + status = 0; + break; + } + val = 0; + } + + g_fast_disk_unnib = 0; + + ret = 0; + if(status != 0) { + printf("dsk->fbit_pos: %07x, status: %d\n", dsk->cur_fbit_pos, + status); + for(i = 0; i < num_sectors; i++) { + printf("sector done[%d] = %d\n", i, sector_done[i]); + } + printf("Nibblization not done, %02x blocks found qtrk %04x\n", + num_sectors_done, qtr_track); + + ret = -1; + } + + dsk->cur_fbit_pos = save_fbit_pos; + iwm_move_to_ftrack(dsk, save_frac_track, 0, 0); + + return ret; + +} + +/* ret = 1 -> dirty data written out */ +/* ret = 0 -> not dirty, no error */ +/* ret < 0 -> error */ +int +iwm_track_to_unix(Disk *dsk, word32 qtr_track, byte *outbuf) +{ + Trk *trk; + dword64 dunix_pos, dret, unix_len; + int ret; + + trk = &(dsk->trks[qtr_track]); + if((trk->track_bits == 0) || (trk->dirty == 0)) { + return 0; + } + + printf("iwm_track_to_unix dirty qtr:%04x, dirty:%d\n", qtr_track, + trk->dirty); +#if 0 + if((qtr_track & 3) && disk_525) { + halt_printf("You wrote to phase %02x! Can't wr bk to unix!\n", + qtr_track); + dsk->write_through_to_unix = 0; + return -1; + } +#endif + + if(dsk->wozinfo_ptr) { // WOZ disk + outbuf = trk->raw_bptr; + ret = 0; + } else { + if(dsk->disk_525) { + if(qtr_track & 3) { + // Not a valid track + ret = -1; + } else { + ret = iwm_denib_track525(dsk, qtr_track, + outbuf); + } + } else { + ret = iwm_denib_track35(dsk, qtr_track, outbuf); + } + } + + if(ret != 0) { + return -1; + } + + /* Write it out */ + dunix_pos = trk->dunix_pos; + unix_len = trk->unix_len; + if(unix_len < 0x1000) { + halt_printf("Disk:%s trk:%06x, dunix_pos:%08llx, len:%08llx\n", + dsk->name_ptr, dsk->cur_frac_track, dunix_pos, + unix_len); + return -1; + } + + trk->dirty = 0; + if(dsk->dynapro_info_ptr) { + return dynapro_write(dsk, outbuf, dunix_pos, (word32)unix_len); + } + + dret = cfg_write_to_fd(dsk->fd, outbuf, dunix_pos, unix_len); +#if 0 + printf("Write: qtr_trk:%04x, dunix_pos:%08llx, %s\n", qtr_track, + dunix_pos, dsk->name_ptr); +#endif + if(dret != unix_len) { + printf("write: %08llx, errno:%d, trk: %06x, disk: %s\n", + dret, errno, dsk->cur_frac_track, dsk->name_ptr); + return -1; + } + if(dsk->wozinfo_ptr) { // WOZ disk + printf("Wrote track %07x to fd:%d off:%08llx, len:%07llx\n", + dsk->cur_frac_track, dsk->fd, dunix_pos, unix_len); + woz_rewrite_crc(dsk, 0); + } + + return 1; +} + +void +show_hex_data(byte *buf, int count) +{ + int i; + + for(i = 0; i < count; i += 16) { + printf("%04x: %02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x\n", i, + buf[i+0], buf[i+1], buf[i+2], buf[i+3], + buf[i+4], buf[i+5], buf[i+6], buf[i+7], + buf[i+8], buf[i+9], buf[i+10], buf[i+11], + buf[i+12], buf[i+13], buf[i+14], buf[i+15]); + } +} + +void +iwm_check_nibblization(dword64 dfcyc) +{ + Disk *dsk; + int slot, drive, sel35; + + drive = (g_iwm.state >> IWM_BIT_DRIVE_SEL) & 1; + slot = 6; + if(g_iwm.state & IWM_STATE_MOTOR_ON) { + sel35 = (g_iwm.state >> IWM_BIT_C031_APPLE35SEL) & 1; + } else { + sel35 = (g_iwm.state >> IWM_BIT_LAST_SEL35) & 1; + } + if(sel35) { + dsk = &(g_iwm.drive35[drive]); + slot = 5; + } else { + dsk = &(g_iwm.drive525[drive]); + } + printf("iwm_check_nibblization, s%d d%d\n", slot, drive); + disk_check_nibblization(dsk, 0, 4096, dfcyc); +} + +void +disk_check_nibblization(Disk *dsk, byte *in_buf, int size, dword64 dfcyc) +{ + byte buffer[0x3000]; + word32 qtr_track; + int ret, ret2; + int i; + + if(size > 0x3000) { + printf("size %08x is > 0x3000, disk_check_nibblization\n",size); + exit(3); + } + + for(i = 0; i < size; i++) { + buffer[i] = 0; + } + + //printf("About to call iwm_denib_track*, here's the track:\n"); + //iwm_show_a_track(dsk, dsk->cur_trk_ptr, dfcyc); + + qtr_track = (word32)(dsk->cur_trk_ptr - &(dsk->trks[0])); + if(qtr_track >= 160) { + halt_printf("cur_trk_ptr points to bad qtr_track:%08x\n", + qtr_track); + return; + } + if(dsk->disk_525) { + ret = iwm_denib_track525(dsk, qtr_track, &(buffer[0])); + } else { + ret = iwm_denib_track35(dsk, qtr_track, &(buffer[0])); + } + + ret2 = -1; + if(in_buf) { + for(i = 0; i < size; i++) { + if(buffer[i] != in_buf[i]) { + printf("buffer[%04x]: %02x != %02x\n", i, + buffer[i], in_buf[i]); + ret2 = i; + break; + } + } + } + + if((ret != 0) || (ret2 >= 0)) { + printf("disk_check_nib ret:%d, ret2:%d for track %06x\n", + ret, ret2, dsk->cur_frac_track); + if(in_buf) { + show_hex_data(in_buf, 0x1000); + } + show_hex_data(buffer, size); + iwm_show_a_track(dsk, dsk->cur_trk_ptr, dfcyc); + if(ret == 16) { + printf("No sectors found, ignore it\n"); + return; + } + + halt_printf("Stop\n"); + exit(2); + } +} + +#define TRACK_BUF_LEN 0x2000 + +void +disk_unix_to_nib(Disk *dsk, int qtr_track, dword64 dunix_pos, word32 unix_len, + int len_bits, dword64 dfcyc) +{ + byte track_buf[TRACK_BUF_LEN]; + Trk *trk; + byte *bptr; + dword64 dret, dlen; + word32 num_bytes, must_clear_track; + int i; + + /* Read track from dsk int track_buf */ +#if 0 + printf("disk_unix_to_nib: qtr:%04x, unix_pos:%08llx, unix_len:%08x, " + "len_bits:%06x\n", qtr_track, dunix_pos, unix_len, len_bits); +#endif + + must_clear_track = 0; + + if(unix_len > TRACK_BUF_LEN) { + printf("diks_unix_to_nib: requested len of image %s = %05x\n", + dsk->name_ptr, unix_len); + } + + bptr = dsk->raw_data; + if(bptr != 0) { + // raw_data is valid, so use it + if((dunix_pos + unix_len) > dsk->raw_dsize) { + must_clear_track = 1; + } else { + bptr += dunix_pos; + for(i = 0; i < (int)unix_len; i++) { + track_buf[i] = bptr[i]; + } + } + } else if(unix_len > 0) { + dret = kegs_lseek(dsk->fd, dunix_pos, SEEK_SET); + if(dret != dunix_pos) { + printf("lseek of disk %s len 0x%llx ret: %lld, errno:" + "%d\n", dsk->name_ptr, dunix_pos, dret, errno); + must_clear_track = 1; + } + + dlen = read(dsk->fd, track_buf, unix_len); + if(dlen != unix_len) { + printf("read of disk %s q_trk %d ret: %lld, errno:%d\n", + dsk->name_ptr, qtr_track, dlen, errno); + must_clear_track = 1; + } + } + + if(must_clear_track) { + for(i = 0; i < TRACK_BUF_LEN; i++) { + track_buf[i] = 0; + } + } + +#if 0 + printf("Q_track %02x dumped out\n", qtr_track); + + for(i = 0; i < 4096; i += 32) { + printf("%04x: %02x%02x%02x%02x%02x%02x%02x%02x " + "%02x%02x%02x%02x%02x%02x%02x%02x " + "%02x%02x%02x%02x%02x%02x%02x%02x " + "%02x%02x%02x%02x%02x%02x%02x%02x\n", i, + track_buf[i+0], track_buf[i+1], track_buf[i+2], + track_buf[i+3], track_buf[i+4], track_buf[i+5], + track_buf[i+6], track_buf[i+7], track_buf[i+8], + track_buf[i+9], track_buf[i+10], track_buf[i+11], + track_buf[i+12], track_buf[i+13], track_buf[i+14], + track_buf[i+15], track_buf[i+16], track_buf[i+17], + track_buf[i+18], track_buf[i+19], track_buf[i+20], + track_buf[i+21], track_buf[i+22], track_buf[i+23], + track_buf[i+24], track_buf[i+25], track_buf[i+26], + track_buf[i+27], track_buf[i+28], track_buf[i+29], + track_buf[i+30], track_buf[i+31]); + } +#endif + + dsk->cur_fbit_pos = 0; /* for consistency */ + dsk->raw_bptr_malloc = 1; + + trk = &(dsk->trks[qtr_track]); + num_bytes = 2 + ((len_bits + 7) >> 3) + 4; + trk->track_bits = len_bits; + trk->dunix_pos = dunix_pos; + trk->unix_len = unix_len; + trk->dirty = 0; + trk->raw_bptr = (byte *)malloc(num_bytes); + trk->sync_ptr = (byte *)malloc(num_bytes); + + iwm_move_to_ftrack(dsk, qtr_track << 16, 0, dfcyc); + + /* create nibblized image */ + + if(dsk->disk_525 && (dsk->image_type == DSK_TYPE_NIB)) { + iwm_nibblize_track_nib525(dsk, track_buf, qtr_track, unix_len); + } else if(dsk->disk_525) { + iwm_nibblize_track_525(dsk, track_buf, qtr_track, dfcyc); + } else { + iwm_nibblize_track_35(dsk, track_buf, qtr_track, unix_len, + dfcyc); + } + + //printf("For qtr_track:%04x, trk->dirty:%d\n", qtr_track, trk->dirty); + trk->dirty = 0; +} + +void +iwm_nibblize_track_nib525(Disk *dsk, byte *track_buf, int qtr_track, + word32 unix_len) +{ + byte *bptr, *sync_ptr; + int len; + int i; + + // This is the old, dumb .nib format. It consists of 0x1a00 bytes + // per track, but there's no sync information. Just mark each byte + // as being sync=7 + len = unix_len; + bptr = &(dsk->cur_trk_ptr->raw_bptr[0]); + sync_ptr = &(dsk->cur_trk_ptr->sync_ptr[0]); + for(i = 0; i < len; i++) { + bptr[i] = track_buf[i]; + } + for(i = 0; i < len; i++) { + sync_ptr[i] = 7; + } + if(dsk->cur_track_bits != (unix_len * 8)) { + fatal_printf("Track %d.%02d of nib image should be bits:%06x " + "but it is: %06x\n", qtr_track >> 2, (qtr_track & 3)*25, + unix_len * 8, dsk->cur_track_bits); + } + + iwm_printf("Nibblized q_track %02x\n", qtr_track); +} + +void +iwm_nibblize_track_525(Disk *dsk, byte *track_buf, int qtr_track, dword64 dfcyc) +{ + byte partial_nib_buf[0x300]; + word32 val, last_val; + int phys_sec, log_sec, num_sync; + int i; + +#if 0 + printf("nibblize track 525, qtr_track:%04x, trk:%p, trk->raw_bptr:%p, " + "sync_ptr:%p\n", qtr_track, trk, trk->raw_bptr, trk->sync_ptr); +#endif + + for(phys_sec = 0; phys_sec < 16; phys_sec++) { + if(dsk->image_type == DSK_TYPE_DOS33) { + log_sec = phys_to_dos_sec[phys_sec]; + } else { + log_sec = phys_to_prodos_sec[phys_sec]; + } + + /* Create sync headers */ + if(phys_sec == 0) { + num_sync = 70; + } else { + num_sync = 22; + } + + for(i = 0; i < num_sync; i++) { + disk_nib_out(dsk, 0xff, 10); + } + disk_nib_out(dsk, 0xd5, 8); // prolog: d5,aa,96 + disk_nib_out(dsk, 0xaa, 8); + disk_nib_out(dsk, 0x96, 8); + disk_4x4_nib_out(dsk, dsk->vol_num); + disk_4x4_nib_out(dsk, qtr_track >> 2); + disk_4x4_nib_out(dsk, phys_sec); + disk_4x4_nib_out(dsk, dsk->vol_num ^ (qtr_track>>2) ^ phys_sec); + disk_nib_out(dsk, 0xde, 8); // epilog: de,aa,eb + disk_nib_out(dsk, 0xaa, 8); + disk_nib_out(dsk, 0xeb, 8); + + /* Inter sync */ + disk_nib_out(dsk, 0xff, 10); + for(i = 0; i < 6; i++) { + disk_nib_out(dsk, 0xff, 10); + } + disk_nib_out(dsk, 0xd5, 8); // data prolog: d5,aa,ad + disk_nib_out(dsk, 0xaa, 8); + disk_nib_out(dsk, 0xad, 8); + + sector_to_partial_nib( &(track_buf[log_sec*256]), + &(partial_nib_buf[0])); + + last_val = 0; + for(i = 0; i < 0x156; i++) { + val = partial_nib_buf[i]; + disk_nib_out(dsk, to_disk_byte[last_val ^ val], 8); + last_val = val; + } + disk_nib_out(dsk, to_disk_byte[last_val], 8); + + /* data epilog */ + disk_nib_out(dsk, 0xde, 8); // data epilog: de,aa,eb + disk_nib_out(dsk, 0xaa, 8); + disk_nib_out(dsk, 0xeb, 8); + } + + /* finish nibblization */ + disk_nib_end_track(dsk, dfcyc); + + iwm_printf("Nibblized q_track %02x\n", qtr_track); + + if(g_check_nibblization) { + disk_check_nibblization(dsk, &(track_buf[0]), 0x1000, dfcyc); + } + + //printf("Showing track after nibblization:\n"); + //iwm_show_a_track(dsk, dsk->cur_trk_ptr, dfcyc); +} + +void +iwm_nibblize_track_35(Disk *dsk, byte *track_buf, int qtr_track, + word32 unix_len, dword64 dfcyc) +{ + int phys_to_log_sec[16]; + word32 buf_c00[0x100]; + word32 buf_d00[0x100]; + word32 buf_e00[0x100]; + byte *buf; + word32 val, phys_track, phys_side, capacity, cksum, acc_hi; + word32 tmp_5c, tmp_5d, tmp_5e, tmp_5f, tmp_63, tmp_64, tmp_65; + int num_sectors, log_sec, track, side, num_sync, carry; + int interleave, x, y; + int i, phys_sec; + + if(dsk->cur_fbit_pos & 511) { + halt_printf("fbit_pos:%07x is not bit-aligned!\n", + dsk->cur_fbit_pos); + } + + num_sectors = (unix_len >> 9); + + for(i = 0; i < num_sectors; i++) { + phys_to_log_sec[i] = -1; + } + + phys_sec = 0; + interleave = 2; + for(log_sec = 0; log_sec < num_sectors; log_sec++) { + while(phys_to_log_sec[phys_sec] >= 0) { + phys_sec++; + if(phys_sec >= num_sectors) { + phys_sec = 0; + } + } + phys_to_log_sec[phys_sec] = log_sec; + phys_sec += interleave; + if(phys_sec >= num_sectors) { + phys_sec -= num_sectors; + } + } + + track = qtr_track >> 1; + side = qtr_track & 1; + for(phys_sec = 0; phys_sec < num_sectors; phys_sec++) { + + log_sec = phys_to_log_sec[phys_sec]; + if(log_sec < 0) { + printf("Track: %02x.%x phys_sec: %02x = %d!\n", + track, side, phys_sec, log_sec); + exit(2); + } + + /* Create sync headers */ + if(phys_sec == 0) { + num_sync = 400; + } else { + num_sync = 54; + } + + for(i = 0; i < num_sync; i++) { + disk_nib_out(dsk, 0xff, 10); + } + + disk_nib_out(dsk, 0xd5, 8); /* prolog */ + disk_nib_out(dsk, 0xaa, 8); /* prolog */ + disk_nib_out(dsk, 0x96, 8); /* prolog */ + + phys_track = track & 0x3f; + phys_side = (side << 5) + (track >> 6); + capacity = 0x22; + disk_nib_out(dsk, to_disk_byte[phys_track], 8); /* trk */ + disk_nib_out(dsk, to_disk_byte[log_sec], 8); /* sec */ + disk_nib_out(dsk, to_disk_byte[phys_side], 8); /* sides+trk */ + disk_nib_out(dsk, to_disk_byte[capacity], 8); /* capacity*/ + + cksum = (phys_track ^ log_sec ^ phys_side ^ capacity) & 0x3f; + disk_nib_out(dsk, to_disk_byte[cksum], 8); /* cksum*/ + + disk_nib_out(dsk, 0xde, 8); /* epi */ + disk_nib_out(dsk, 0xaa, 8); /* epi */ + + /* Inter sync */ + for(i = 0; i < 5; i++) { + disk_nib_out(dsk, 0xff, 10); + } + disk_nib_out(dsk, 0xd5, 8); /* data prolog */ + disk_nib_out(dsk, 0xaa, 8); /* data prolog */ + disk_nib_out(dsk, 0xad, 8); /* data prolog */ + disk_nib_out(dsk, to_disk_byte[log_sec], 8); /* sec again */ + + /* do nibblizing! */ + buf = track_buf + (log_sec << 9); + +/* 6320 */ + tmp_5e = 0; + tmp_5d = 0; + tmp_5c = 0; + y = 0; + x = 0xaf; + buf_c00[0] = 0; + buf_d00[0] = 0; + buf_e00[0] = 0; + buf_e00[1] = 0; + for(y = 0x4; y > 0; y--) { + buf_c00[x] = 0; + buf_d00[x] = 0; + buf_e00[x] = 0; + x--; + } + + while(x >= 0) { +/* 6338 */ + tmp_5c = tmp_5c << 1; + carry = (tmp_5c >> 8); + tmp_5c = (tmp_5c + carry) & 0xff; + + val = buf[y]; + tmp_5e = val + tmp_5e + carry; + carry = (tmp_5e >> 8); + tmp_5e = tmp_5e & 0xff; + + val = val ^ tmp_5c; + buf_c00[x] = val; + y++; +/* 634c */ + val = buf[y]; + tmp_5d = tmp_5d + val + carry; + carry = (tmp_5d >> 8); + tmp_5d = tmp_5d & 0xff; + val = val ^ tmp_5e; + buf_d00[x] = val; + y++; + x--; + if(x <= 0) { + break; + } + +/* 632a */ + val = buf[y]; + tmp_5c = tmp_5c + val + carry; + carry = (tmp_5c >> 8); + tmp_5c = tmp_5c & 0xff; + + val = val ^ tmp_5d; + buf_e00[x+1] = val; + y++; + } + +/* 635f */ + val = ((tmp_5c >> 2) ^ tmp_5d) & 0x3f; +/* 6367 */ + val = (val ^ tmp_5d) >> 2; +/* 636b */ + val = (val ^ tmp_5e) & 0x3f; +/* 636f */ + val = (val ^ tmp_5e) >> 2; +/* 6373 */ + tmp_5f = val; +/* 6375 */ + tmp_63 = 0; + tmp_64 = 0; + tmp_65 = 0; + acc_hi = 0; + + + y = 0xae; + while(y >= 0) { +/* 63e4 */ + /* write out acc_hi */ + val = to_disk_byte[acc_hi & 0x3f]; + disk_nib_out(dsk, val, 8); + +/* 63f2 */ + val = to_disk_byte[tmp_63 & 0x3f]; + tmp_63 = buf_c00[y]; + acc_hi = tmp_63 >> 6; + disk_nib_out(dsk, val, 8); +/* 640b */ + val = to_disk_byte[tmp_64 & 0x3f]; + tmp_64 = buf_d00[y]; + acc_hi = (acc_hi << 2) + (tmp_64 >> 6); + disk_nib_out(dsk, val, 8); + y--; + if(y < 0) { + break; + } + +/* 63cb */ + val = to_disk_byte[tmp_65 & 0x3f]; + tmp_65 = buf_e00[y+1]; + acc_hi = (acc_hi << 2) + (tmp_65 >> 6); + disk_nib_out(dsk, val, 8); + } +/* 6429 */ + val = to_disk_byte[tmp_5f & 0x3f]; + disk_nib_out(dsk, val, 8); + + val = to_disk_byte[tmp_5e & 0x3f]; + disk_nib_out(dsk, val, 8); + + val = to_disk_byte[tmp_5d & 0x3f]; + disk_nib_out(dsk, val, 8); + + val = to_disk_byte[tmp_5c & 0x3f]; + disk_nib_out(dsk, val, 8); + +/* 6440 */ + /* data epilog */ + disk_nib_out(dsk, 0xde, 8); /* epi */ + disk_nib_out(dsk, 0xaa, 8); /* epi */ + disk_nib_out(dsk, 0xff, 8); + } + + disk_nib_end_track(dsk, dfcyc); + + if(g_check_nibblization) { + disk_check_nibblization(dsk, &(track_buf[0]), unix_len, dfcyc); + } +} + +void +disk_4x4_nib_out(Disk *dsk, word32 val) +{ + disk_nib_out(dsk, 0xaa | (val >> 1), 8); + disk_nib_out(dsk, 0xaa | val, 8); +} + +void +disk_nib_out(Disk *dsk, word32 val, int size) +{ + word32 bit_pos; + + bit_pos = dsk->cur_fbit_pos >> 9; + dsk->cur_fbit_pos = disk_nib_out_raw(dsk, + (dsk->cur_frac_track + 0x8000) >> 16, val, size, bit_pos, 0) * + 512; +} + +void +disk_nib_end_track(Disk *dsk, dword64 dfcyc) +{ + // printf("disk_nib_end_track %p\n", dsk); + + dsk->cur_fbit_pos = 0; + dsk->disk_dirty = 0; + iwm_recalc_sync_from(dsk, (word32)(dsk->cur_trk_ptr - &(dsk->trks[0])), + 0, dfcyc); +} + +word32 +disk_nib_out_raw(Disk *dsk, word32 qtr_track, word32 val, int bits, + word32 bit_pos, dword64 dfcyc) +{ + Trk *trkptr; + byte *bptr, *sync_ptr; + word32 track_bits, tmp, mask; + int pos, next_pos, bit, to_do, shift_left, shift_right, last_byte; + int this_bits; + int do_print; + + // write bits from val[7:x]. If bits=3 and val=0xaf, write bits 101. + // If bits=10 and val=0xaf, write 0xaf then bits 00. + + if(qtr_track >= 160) { + return bit_pos; + } + trkptr = &(dsk->trks[qtr_track]); + track_bits = trkptr->track_bits; + if(track_bits == 0) { + halt_printf("disk_nib_out_raw track_bits=0, %04x\n", qtr_track); + return bit_pos; + } + + last_byte = (track_bits - 1) >> 3; + bit = bit_pos & 7; + pos = bit_pos >> 3; + bptr = &(trkptr->raw_bptr[0]); + sync_ptr = &(trkptr->sync_ptr[0]); + if(dfcyc != 0) { + dbg_log_info(dfcyc, (bits << 24) | (bit_pos << 1), + (track_bits << 16) | (val & 0xffff), 0x100ed); + } + dsk->disk_dirty = 1; + trkptr->dirty = 1; + do_print = ((pos <= 0x10) || (pos >= 0x18e0)) && + (dsk->cur_frac_track == 0xb0000); + do_print = 0; + if(do_print) { + printf("disk_nib_out %02x, %d, %06x\n", val, bits, bit_pos*2); + } + while(1) { + this_bits = 8; + next_pos = pos + 1; + if(pos >= last_byte) { + this_bits = ((track_bits - 1) & 7) + 1; // 1..8 + next_pos = 0; + } + this_bits = (this_bits - bit); // 1..8 + to_do = bits; // 1...inf + if(to_do > this_bits) { + to_do = this_bits; // 1..8 + } + shift_left = (8 - bit - to_do) & 7; + shift_right = 8 - to_do; + mask = (1U << to_do) - 1; + tmp = (val >> shift_right) & mask; + mask = mask << shift_left; + if(do_print) { + printf(" pos:%04x bit:%d tmp:%02x mask:%02x bits:%d " + "bptr[]=%02x new:%02x todo:%d, r:%d l:%d\n", + pos, bit, tmp, mask, bits, bptr[pos], + (bptr[pos] & (~mask)) | + ((tmp << shift_left) & mask), + to_do, shift_right, shift_left); + } + bptr[pos] = (bptr[pos] & (~mask)) | + ((tmp << shift_left) & mask); + sync_ptr[pos] = 0xff; + bits -= to_do; + if(bits <= 0) { + pos = (pos * 8) + bit + to_do; + if((bit + to_do) >= 8) { + pos = next_pos * 8; + } + if((word32)pos >= track_bits) { + pos -= track_bits; + } + if(do_print) { + printf(" returning %05x, bits:%d orig:%05x " + "%05x\n", + pos*2, bits, bit_pos*2, last_byte); + } + return pos; + } + val = (val << to_do) & 0xff; + bit = 0; + pos = next_pos; + } +} + +Disk * +iwm_get_dsk_from_slot_drive(int slot, int drive) +{ + Disk *dsk; + int max_drive; + + // pass in slot=5,6,7 drive=0,1 (or more for slot 7) + max_drive = 2; + switch(slot) { + case 5: + dsk = &(g_iwm.drive35[drive]); + break; + case 6: + dsk = &(g_iwm.drive525[drive]); + break; + default: // slot 7 + max_drive = MAX_C7_DISKS; + dsk = &(g_iwm.smartport[drive]); + } + if(drive >= max_drive) { + dsk -= drive; // Move back to drive 0 effectively + } + + return dsk; +} + +void +iwm_toggle_lock(Disk *dsk) +{ + printf("iwm_toggle_lock: write_prot:%d, write_through:%d\n", + dsk->write_prot, dsk->write_through_to_unix); + if(dsk->write_prot == 2) { + // nothing to do + return; + } + + if(dsk->write_prot) { + dsk->write_prot = 0; + } else { + dsk->write_prot = 1; + } + printf("New dsk->write_prot: %d\n", dsk->write_prot); + if(dsk->wozinfo_ptr && dsk->write_through_to_unix) { + woz_rewrite_lock(dsk); + printf("Called woz_rewrite_lock()\n"); + return; + } +} + +void +iwm_eject_named_disk(int slot, int drive, const char *name, + const char *partition_name) +{ + Disk *dsk; + + dsk = iwm_get_dsk_from_slot_drive(slot, drive); + if(dsk->fd < 0) { + return; + } + + /* If name matches, eject the disk! */ + if(!strcmp(dsk->name_ptr, name)) { + /* It matches, eject it */ + if((partition_name != 0) && (dsk->partition_name != 0)) { + /* If both have partitions, and they differ, then */ + /* don't eject. Otherwise, eject */ + if(strcmp(dsk->partition_name, partition_name) != 0) { + /* Don't eject */ + return; + } + } + iwm_eject_disk(dsk); + } +} + +void +iwm_eject_disk_by_num(int slot, int drive) +{ + Disk *dsk; + + dsk = iwm_get_dsk_from_slot_drive(slot, drive); + + iwm_eject_disk(dsk); +} + +void +iwm_eject_disk(Disk *dsk) +{ + Woz_info *wozinfo_ptr; + word32 state; + int motor_on; + int i; + + printf("Ejecting dsk: %s, fd:%d\n", dsk->name_ptr, dsk->fd); + + if(dsk->fd < 0) { + return; + } + + g_config_kegs_update_needed = 1; + + state = g_iwm.state; + motor_on = state & IWM_STATE_MOTOR_ON; + if(state & IWM_STATE_C031_APPLE35SEL) { + motor_on = state & IWM_STATE_MOTOR_ON35; + } + if(motor_on) { + halt_printf("Try eject dsk:%s, but motor_on!\n", dsk->name_ptr); + } + + dynapro_try_fix_damaged_disk(dsk); + iwm_flush_disk_to_unix(dsk); + + printf("Ejecting disk: %s\n", dsk->name_ptr); + + /* Free all memory, close file */ + + /* free the tracks first */ + if(dsk->trks != 0) { + for(i = 0; i < MAX_TRACKS; i++) { + if(dsk->raw_bptr_malloc) { + free(dsk->trks[i].raw_bptr); + } + free(dsk->trks[i].sync_ptr); + dsk->trks[i].raw_bptr = 0; + dsk->trks[i].sync_ptr = 0; + dsk->trks[i].track_bits = 0; + } + } + dsk->num_tracks = 0; + dsk->raw_bptr_malloc = 0; + + wozinfo_ptr = dsk->wozinfo_ptr; + if(wozinfo_ptr) { + if(dsk->raw_data == 0) { + free(wozinfo_ptr->wozptr); + } + wozinfo_ptr->wozptr = 0; + free(wozinfo_ptr); + } + dsk->wozinfo_ptr = 0; + + dynapro_free_dynapro_info(dsk); + + /* close file, clean up dsk struct */ + if(dsk->raw_data) { + free(dsk->raw_data); + } else { + close(dsk->fd); + } + + dsk->fd = -1; + dsk->raw_dsize = 0; + dsk->raw_data = 0; + dsk->dimage_start = 0; + dsk->dimage_size = 0; + dsk->cur_fbit_pos = 0; + dsk->cur_track_bits = 0; + dsk->disk_dirty = 0; + dsk->write_through_to_unix = 0; + dsk->write_prot = 1; + dsk->just_ejected = 1; + + /* Leave name_ptr valid */ +} + +void +iwm_show_track(int slot_drive, int track, dword64 dfcyc) +{ + Disk *dsk; + Trk *trk; + word32 state; + int drive, sel35, qtr_track; + + state = g_iwm.state; + if(slot_drive < 0) { + drive = (state >> IWM_BIT_DRIVE_SEL) & 1; + if(state & IWM_STATE_MOTOR_ON) { + sel35 = (state >> IWM_BIT_C031_APPLE35SEL) & 1; + } else { + sel35 = (state >> IWM_BIT_LAST_SEL35) & 1; + } + } else { + drive = slot_drive & 1; + sel35 = !((slot_drive >> 1) & 1); + } + if(sel35) { + dsk = &(g_iwm.drive35[drive]); + } else { + dsk = &(g_iwm.drive525[drive]); + } + + if(track < 0) { + qtr_track = dsk->cur_frac_track >> 16; + } else { + qtr_track = track; + } + if((dsk->trks == 0) || (qtr_track >= 160)) { + return; + } + trk = &(dsk->trks[qtr_track]); + + if(trk->track_bits == 0) { + dbg_printf("Track_bits: %d\n", trk->track_bits); + dbg_printf("No track for type: %d, drive: %d, qtrk:0x%02x\n", + sel35, drive, qtr_track); + return; + } + + dbg_printf("Current s%dd%d, q_track:0x%02x\n", 6 - sel35, + drive + 1, qtr_track); + + iwm_show_a_track(dsk, trk, dfcyc); +} + +void +iwm_show_a_track(Disk *dsk, Trk *trk, dword64 dfcyc) +{ + byte *bptr; + byte *sync_ptr; + word32 val, track_bits, len, shift, line_len, sync; + int i, j; + + track_bits = trk->track_bits; + dbg_printf(" Showtrack:track_bits*2: %06x, fbit_pos: %07x, " + "trk:%06x, dfcyc:%016llx\n", track_bits*2, dsk->cur_fbit_pos, + dsk->cur_frac_track, dfcyc); + dbg_printf(" disk_525:%d, drive:%d name:%s fd:%d, dimage_start:" + "%08llx, dimage_size:%08llx\n", dsk->disk_525, dsk->drive, + dsk->name_ptr, dsk->fd, dsk->dimage_start, dsk->dimage_size); + dbg_printf(" image_type:%d, vol_num:%02x, write_prot:%d, " + "write_through:%d, disk_dirty:%d\n", dsk->image_type, + dsk->vol_num, dsk->write_prot, dsk->write_through_to_unix, + dsk->disk_dirty); + dbg_printf(" just_ejected:%d, last_phases:%d, num_tracks:%d\n", + dsk->just_ejected, dsk->last_phases, dsk->num_tracks); + + len = (track_bits + 7) >> 3; + if(len >= 0x3000) { + len = 0x3000; + dbg_printf("len too big, using %04x\n", len); + } + + bptr = trk->raw_bptr; + sync_ptr = trk->sync_ptr; + + len = len + 2; // Show an extra 2 bytes + for(i = 0; i < (int)len; i += 16) { + line_len = 16; + if((i + line_len) > len) { + line_len = len - i; + } + // First, print raw bptr bytes + dbg_printf("%04x: ", i); + for(j = 0; j < (int)line_len; j++) { + dbg_printf(" %02x", bptr[i + j]); + if(((i + j) * 8U) >= track_bits) { + dbg_printf("*"); + } + } + dbg_printf("\n"); + dbg_printf(" sync:"); + for(j = 0; j < (int)line_len; j++) { + dbg_printf(" %2d", sync_ptr[i + j]); + } + dbg_printf("\n"); + dbg_printf(" nibs:"); + for(j = 0; j < (int)line_len; j++) { + sync = sync_ptr[i+j]; + if(sync >= 8) { + dbg_printf(" XX"); + } else { + shift = (7 - sync) & 7; + val = (bptr[i + j] << 8) | bptr[i + j + 1]; + val = ((val << shift) >> 8) & 0xff; + dbg_printf(" %02x", val); + } + } + dbg_printf("\n"); + } +} + + +void +dummy1(word32 psr) +{ + printf("dummy1 psr: %05x\n", psr); +} + +void +dummy2(word32 psr) +{ + printf("dummy2 psr: %05x\n", psr); +} diff --git a/gsplus/src/iwm.h b/gsplus/src/iwm.h new file mode 100644 index 0000000..06df1fd --- /dev/null +++ b/gsplus/src/iwm.h @@ -0,0 +1,206 @@ +#ifdef INCLUDE_RCSID_C +#endif + +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#define MAX_TRACKS (2*80) +#define MAX_C7_DISKS 16 + +#define NIB_LEN_525 0x18f2 /* 51088 bits per track */ +// Expected bits per track: (1020484/5)/4 = 51024. A little extra seems good + +#define NIBS_FROM_ADDR_TO_DATA 28 +// Copy II+ Manual Sector Copy fails if this is 20, so make it 28 + +// image_type settings. 0 means unknown type +#define DSK_TYPE_PRODOS 1 +#define DSK_TYPE_DOS33 2 +#define DSK_TYPE_DYNAPRO 3 +#define DSK_TYPE_NIB 4 +#define DSK_TYPE_WOZ 5 + +// Note: C031_APPLE35SEL must be 6, C031_CTRL must be 7, MOTOR_ON must be 5! +// Q7 needs to be adjacent and higher than Q6 +// Bits 4:0 are IWM mode register: 0: latch mode; 1: async handshake; +// 2: immediate motor off (no 1 sec delay); 3: 2us bit timing; +// 4: Divide input clock by 8 (instead of 7) +#define IWM_BIT_MOTOR_ON 5 +#define IWM_BIT_C031_APPLE35SEL 6 +#define IWM_BIT_C031_CTRL 7 +#define IWM_BIT_STEP_DIRECTION35 8 +#define IWM_BIT_MOTOR_ON35 9 +#define IWM_BIT_MOTOR_OFF 10 +#define IWM_BIT_DRIVE_SEL 11 +#define IWM_BIT_Q6 12 +#define IWM_BIT_Q7 13 +#define IWM_BIT_ENABLE2 14 +#define IWM_BIT_LAST_SEL35 15 +#define IWM_BIT_PHASES 16 +#define IWM_BIT_RESET 20 + +#define IWM_STATE_MOTOR_ON (1 << IWM_BIT_MOTOR_ON) +#define IWM_STATE_C031_APPLE35SEL (1 << IWM_BIT_C031_APPLE35SEL) +#define IWM_STATE_C031_CTRL (1 << IWM_BIT_C031_CTRL) +#define IWM_STATE_STEP_DIRECTION35 (1 << IWM_BIT_STEP_DIRECTION35) +#define IWM_STATE_MOTOR_ON35 (1 << IWM_BIT_MOTOR_ON35) +#define IWM_STATE_MOTOR_OFF (1 << IWM_BIT_MOTOR_OFF) +#define IWM_STATE_DRIVE_SEL (1 << IWM_BIT_DRIVE_SEL) +#define IWM_STATE_Q6 (1 << IWM_BIT_Q6) +#define IWM_STATE_Q7 (1 << IWM_BIT_Q7) +#define IWM_STATE_ENABLE2 (1 << IWM_BIT_ENABLE2) +#define IWM_STATE_LAST_SEL35 (1 << IWM_BIT_LAST_SEL35) +#define IWM_STATE_PHASES (1 << IWM_BIT_PHASES) +#define IWM_STATE_RESET (1 << IWM_BIT_RESET) + +STRUCT(Trk) { + byte *raw_bptr; + byte *sync_ptr; + dword64 dunix_pos; + word16 unix_len; + word16 dirty; + word32 track_bits; +}; + +STRUCT(Woz_info) { + byte *wozptr; + word32 woz_size; + int version; + int reparse_needed; + word32 max_trk_blocks; + int meta_size; + int trks_size; + int tmap_offset; + int trks_offset; + int info_offset; + int meta_offset; +}; + +typedef struct Dynapro_map_st Dynapro_map; + +STRUCT(Dynapro_file) { + Dynapro_file *next_ptr; + Dynapro_file *parent_ptr; + Dynapro_file *subdir_ptr; + char *unix_path; + byte *buffer_ptr; + byte prodos_name[17]; // +0x00-0x0f: [0] is len, nul at end + word32 dir_byte; // Byte address of this file's dir ent + word32 eof; // +0x15-0x17 + word32 blocks_used; // +0x13-0x14 + word32 creation_time; // +0x18-0x1b + word32 lastmod_time; // +0x21-0x24 + word16 upper_lower; // +0x1c-0x1d: Versions: lowercase flags + word16 key_block; // +0x11-0x12 + word16 aux_type; // +0x1f-0x20 + word16 header_pointer; // +0x25-0x26 + word16 map_first_block; + byte file_type; // +0x10 + byte modified_flag; + byte damaged; +}; + +struct Dynapro_map_st { + Dynapro_file *file_ptr; + word16 next_map_block; + word16 modified; +}; + +STRUCT(Dynapro_info) { + char *root_path; + Dynapro_file *volume_ptr; + Dynapro_map *block_map_ptr; + int damaged; +}; + +STRUCT(Disk) { + dword64 dfcyc_last_read; + byte *raw_data; + Woz_info *wozinfo_ptr; + Dynapro_info *dynapro_info_ptr; + char *name_ptr; + char *partition_name; + int partition_num; + int fd; + word32 dynapro_blocks; + dword64 raw_dsize; + dword64 dimage_start; + dword64 dimage_size; + int smartport; + int disk_525; + int drive; + word32 cur_frac_track; + int image_type; + int vol_num; + int write_prot; + int write_through_to_unix; + int disk_dirty; + int just_ejected; + int last_phases; + dword64 dfcyc_last_phases; + word32 cur_fbit_pos; + word32 fbit_mult; + word32 cur_track_bits; + int raw_bptr_malloc; + Trk *cur_trk_ptr; + int num_tracks; + Trk *trks; +}; + +STRUCT(Iwm) { + Disk drive525[2]; + Disk drive35[2]; + Disk smartport[MAX_C7_DISKS]; + dword64 dfcyc_last_fastemul_read; + word32 state; + word32 motor_off_vbl_count; + word32 forced_sync_bit; + word32 last_rd_bit; + word32 write_val; + word32 wr_last_bit[5]; + word32 wr_qtr_track[5]; + word32 wr_num_bits[5]; + word32 wr_prior_num_bits[5]; + word32 wr_delta[5]; + int num_active_writes; +}; + +STRUCT(Driver_desc) { + word16 sig; + word16 blk_size; + word32 blk_count; + word16 dev_type; + word16 dev_id; + word32 data; + word16 drvr_count; +}; + +STRUCT(Part_map) { + word16 sig; + word16 sigpad; + word32 map_blk_cnt; + word32 phys_part_start; + word32 part_blk_cnt; + char part_name[32]; + char part_type[32]; + word32 data_start; + word32 data_cnt; + word32 part_status; + word32 log_boot_start; + word32 boot_size; + word32 boot_load; + word32 boot_load2; + word32 boot_entry; + word32 boot_entry2; + word32 boot_cksum; + char processor[16]; + char junk[128]; +}; + diff --git a/gsplus/src/joystick_driver.c b/gsplus/src/joystick_driver.c new file mode 100644 index 0000000..72e63cf --- /dev/null +++ b/gsplus/src/joystick_driver.c @@ -0,0 +1,468 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include "defc.h" + +#ifdef __linux__ +# include +#endif + +#ifdef _WIN32 +# include +# include +#endif + +extern int g_joystick_native_type1; /* in paddles.c */ +extern int g_joystick_native_type2; /* in paddles.c */ +extern int g_joystick_native_type; /* in paddles.c */ +extern int g_paddle_buttons; +extern int g_paddle_val[]; + + +const char *g_joystick_dev = "/dev/js0"; /* default joystick dev file */ +#define MAX_JOY_NAME 128 + +int g_joystick_native_fd = -1; +int g_joystick_num_axes = 0; +int g_joystick_num_buttons = 0; + +int g_joystick_callback_buttons = 0; +int g_joystick_callback_x = 32767; +int g_joystick_callback_y = 32767; + +#ifdef __linux__ +# define JOYSTICK_DEFINED +void +joystick_init() +{ + char joy_name[MAX_JOY_NAME]; + int version, fd; + int i; + + fd = open(g_joystick_dev, O_RDONLY | O_NONBLOCK); + if(fd < 0) { + printf("Unable to open joystick dev file: %s, errno: %d\n", + g_joystick_dev, errno); + printf("Defaulting to mouse joystick\n"); + return; + } + + strcpy(&joy_name[0], "Unknown Joystick"); + version = 0x800; + + ioctl(fd, JSIOCGNAME(MAX_JOY_NAME), &joy_name[0]); + ioctl(fd, JSIOCGAXES, &g_joystick_num_axes); + ioctl(fd, JSIOCGBUTTONS, &g_joystick_num_buttons); + ioctl(fd, JSIOCGVERSION, &version); + + printf("Detected joystick: %s [%d axes, %d buttons vers: %08x]\n", + joy_name, g_joystick_num_axes, g_joystick_num_buttons, + version); + + g_joystick_native_type1 = 1; + g_joystick_native_type2 = -1; + g_joystick_native_fd = fd; + for(i = 0; i < 4; i++) { + g_paddle_val[i] = 32767; + } + g_paddle_buttons = 0xc; +} + +/* joystick_update_linux() called from paddles.c. Update g_paddle_val[] */ +/* and g_paddle_buttons with current information */ +void +joystick_update(dword64 dfcyc) +{ + struct js_event js; /* the linux joystick event record */ + int mask, val, num, type, ret, len; + int i; + + /* suck up to 20 events, then give up */ + len = sizeof(struct js_event); + for(i = 0; i < 20; i++) { + ret = read(g_joystick_native_fd, &js, len); + if(ret != len) { + /* just get out */ + break; + } + type = js.type & ~JS_EVENT_INIT; + val = js.value; + num = js.number & 3; /* clamp to 0-3 */ + switch(type) { + case JS_EVENT_BUTTON: + mask = 1 << num; + if(val) { + val = mask; + } + g_paddle_buttons = (g_paddle_buttons & ~mask) | val; + break; + case JS_EVENT_AXIS: + /* val is -32767 to +32767 */ + g_paddle_val[num] = val; + break; + } + } + + if(i > 0) { + paddle_update_trigger_dcycs(dfcyc); + } +} + +void +joystick_update_buttons() +{ +} +#endif /* LINUX */ + +#ifdef _WIN32 +# define JOYSTICK_DEFINED +#undef JOYSTICK_DEFINED + // HACK: remove +#if 0 +void +joystick_init() +{ + JOYINFO info; + JOYCAPS joycap; + MMRESULT ret1, ret2; + int i; + + // Check that there is a joystick device + if(joyGetNumDevs() <= 0) { + printf("No joystick hardware detected\n"); + g_joystick_native_type1 = -1; + g_joystick_native_type2 = -1; + return; + } + + g_joystick_native_type1 = -1; + g_joystick_native_type2 = -1; + + // Check that at least joystick 1 or joystick 2 is available + ret1 = joyGetPos(JOYSTICKID1, &info); + ret2 = joyGetDevCaps(JOYSTICKID1, &joycap, sizeof(joycap)); + if(ret1 == JOYERR_NOERROR && ret2 == JOYERR_NOERROR) { + g_joystick_native_type1 = JOYSTICKID1; + printf("Joystick #1 = %s\n", joycap.szPname); + g_joystick_native_type = JOYSTICKID1; + } + ret1 = joyGetPos(JOYSTICKID2, &info); + ret2 = joyGetDevCaps(JOYSTICKID2, &joycap, sizeof(joycap)); + if(ret1 == JOYERR_NOERROR && ret2 == JOYERR_NOERROR) { + g_joystick_native_type2 = JOYSTICKID2; + printf("Joystick #2 = %s\n", joycap.szPname); + if(g_joystick_native_type < 0) { + g_joystick_native_type = JOYSTICKID2; + } + } + + for(i = 0; i < 4; i++) { + g_paddle_val[i] = 32767; + } + g_paddle_buttons = 0xc; +} + +void +joystick_update(dword64 dfcyc) +{ + JOYCAPS joycap; + JOYINFO info; + UINT id; + MMRESULT ret1, ret2; + + id = g_joystick_native_type; + + ret1 = joyGetDevCaps(id, &joycap, sizeof(joycap)); + ret2 = joyGetPos(id, &info); + if(ret1 == JOYERR_NOERROR && ret2 == JOYERR_NOERROR) { + g_paddle_val[0] = (info.wXpos - joycap.wXmin) * 32768 / + (joycap.wXmax - joycap.wXmin); + g_paddle_val[1] = (info.wYpos - joycap.wYmin) * 32768 / + (joycap.wYmax - joycap.wYmin); + if(info.wButtons & JOY_BUTTON1) { + g_paddle_buttons = g_paddle_buttons | 1; + } else { + g_paddle_buttons = g_paddle_buttons & (~1); + } + if(info.wButtons & JOY_BUTTON2) { + g_paddle_buttons = g_paddle_buttons | 2; + } else { + g_paddle_buttons = g_paddle_buttons & (~2); + } + paddle_update_trigger_dcycs(dfcyc); + } +} + +void +joystick_update_buttons() +{ + JOYINFOEX info; + UINT id; + + id = g_joystick_native_type; + + info.dwSize = sizeof(JOYINFOEX); + info.dwFlags = JOY_RETURNBUTTONS; + if(joyGetPosEx(id, &info) == JOYERR_NOERROR) { + if(info.dwButtons & JOY_BUTTON1) { + g_paddle_buttons = g_paddle_buttons | 1; + } else { + g_paddle_buttons = g_paddle_buttons & (~1); + } + if(info.dwButtons & JOY_BUTTON2) { + g_paddle_buttons = g_paddle_buttons | 2; + } else { + g_paddle_buttons = g_paddle_buttons & (~2); + } + } +} +#endif +#endif + +#ifdef MAC +# define JOYSTICK_DEFINED + +#include +#include +#include +#include +#include +// Headers are at: /Applications/Xcode.app/Contents/Developer/Platforms/ +// MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/ +// Frameworks/IOKit.framework/Headers +// Thanks to VirtualC64 and hidapi library for coding example + +CFIndex g_joystick_min = 0; +CFIndex g_joystick_range = 256; +int g_joystick_minmax_valid = 0; +int g_joystick_dummy = 0; + +void +hid_device_callback(void *ptr, IOReturn result, void *sender, + IOHIDValueRef value) +{ + IOHIDElementRef element; + word32 mask; + int usage_page, usage, ival, button; + + // This is a callback routine, and it's unclear to me what the state is + // For safety, do no printfs() (other than for debug). + if((ptr || result || sender || 1) == 0) { + printf("Bad\n"); // Avoid unused var warning + } + + element = IOHIDValueGetElement(value); + usage_page = IOHIDElementGetUsagePage(element); + usage = IOHIDElementGetUsage(element); + ival = IOHIDValueGetIntegerValue(value); +#if 0 + printf(" usage_page:%d, usage:%d, value:%d\n", usage_page, usage, ival); +#endif + if((usage_page == kHIDPage_GenericDesktop) && + ((usage >= kHIDUsage_GD_X) && + (usage <= kHIDUsage_GD_Y)) && + !g_joystick_minmax_valid) { + g_joystick_min = IOHIDElementGetLogicalMin(element); + g_joystick_range = IOHIDElementGetLogicalMax(element) + 1 - + g_joystick_min; + // printf("min:%lld range:%lld\n", (dword64)g_joystick_min, + // (dword64)g_joystick_range); + if(g_joystick_range == 0) { + g_joystick_range = 1; + } + g_joystick_minmax_valid = 1; + } + if((usage_page == kHIDPage_GenericDesktop) && + (usage == kHIDUsage_GD_X)) { + g_joystick_callback_x = ((ival * 65536) / g_joystick_range) - + 32768; + //printf("g_joystick_callback_x = %d\n", g_joystick_callback_x); + } + if((usage_page == kHIDPage_GenericDesktop) && + (usage == kHIDUsage_GD_Y)) { + g_joystick_callback_y = ((ival * 65536) / g_joystick_range) - + 32768; + //printf("g_joystick_callback_y = %d\n", g_joystick_callback_y); + } + if((usage_page == kHIDPage_Button) && (usage >= 1) && (usage <= 10)) { + // Buttons: usage=1 is button 0, usage=2 is button 1, etc. + button = (~usage) & 1; + mask = 1 << button; + //printf("Button %d (%d) pressed:%d\n", button, usage, ival); + if(ival == 0) { // Button released + g_joystick_callback_buttons &= (~mask); + } else { // Button pressed + g_joystick_callback_buttons |= mask; + } + } +} + +int +hid_get_int_property(IOHIDDeviceRef device, CFStringRef key_cfstr) +{ + CFTypeRef ref; + Boolean bret; + int int_val; + + ref = IOHIDDeviceGetProperty(device, key_cfstr); + if(ref) { + bret = CFNumberGetValue((CFNumberRef)ref, kCFNumberIntType, + &int_val); + if(bret) { + return int_val; + } + } + return 0; +} + +void +joystick_init() +{ + IOHIDManagerRef hid_mgr; + CFSetRef devices_set; + CFIndex num_devices; + IOHIDDeviceRef *devices_array, device; + int vendor, usage_page, usage; + int i; + + g_joystick_native_type1 = -1; + g_joystick_native_type2 = -1; + + hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, + kIOHIDOptionsTypeNone); + IOHIDManagerSetDeviceMatching(hid_mgr, 0); + IOHIDManagerOpen(hid_mgr, kIOHIDOptionsTypeNone); + + devices_set = IOHIDManagerCopyDevices(hid_mgr); + num_devices = CFSetGetCount(devices_set); + + // Sets are hashtables, so we cannot directly access it. The only way + // to iterate over all values is to use CFSetGetValues to get a simple + // array of the values, and iterate over that + devices_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); + CFSetGetValues(devices_set, (const void **)devices_array); + + for(i = 0; i < num_devices; i++) { + device = devices_array[i]; + vendor = hid_get_int_property(device, CFSTR(kIOHIDVendorIDKey)); + // printf(" vendor: %d\n", vendor); + usage_page = hid_get_int_property(device, + CFSTR(kIOHIDDeviceUsagePageKey)); + usage = hid_get_int_property(device, + CFSTR(kIOHIDDeviceUsageKey)); + // printf(" usage_page:%d, usage:%d\n", usage_page, usage); + usage_page = hid_get_int_property(device, + CFSTR(kIOHIDPrimaryUsagePageKey)); + usage = hid_get_int_property(device, + CFSTR(kIOHIDPrimaryUsageKey)); + // printf(" primary_usage_page:%d, usage:%d\n", usage_page, + // usage); + if(usage_page != kHIDPage_GenericDesktop) { + continue; + } + if((usage != kHIDUsage_GD_Joystick) && + (usage != kHIDUsage_GD_GamePad) && + (usage != kHIDUsage_GD_MultiAxisController)) { + continue; + } + printf(" JOYSTICK FOUND, vendor:%08x!\n", vendor); + IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone); + IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), + kCFRunLoopCommonModes); + IOHIDDeviceRegisterInputValueCallback(device, + hid_device_callback, 0); + g_joystick_native_type1 = 1; + return; + // Now, hid_device_callback will be called whenever a joystick + // value changes. Only set global variables for joystick. + } +} + +void +joystick_update(dword64 dfcyc) +{ + int i; + + if(dfcyc) { + // Avoid unused parameter warnings + } + for(i = 0; i < 4; i++) { + g_paddle_val[i] = 32767; + } + g_paddle_buttons = 0xc; + if(g_joystick_native_type1 >= 0) { + g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); + g_paddle_val[0] = g_joystick_callback_x; + g_paddle_val[1] = g_joystick_callback_y; + paddle_update_trigger_dcycs(dfcyc); + } +} + +void +joystick_update_buttons() +{ + if(g_joystick_native_type1 >= 0) { + g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); + } +} +#endif + +#ifndef JOYSTICK_DEFINED +/* stubs for the routines */ +void +joystick_init() +{ + g_joystick_native_type1 = -1; + g_joystick_native_type2 = -1; + g_joystick_native_type = -1; +} + +void +joystick_update(dword64 dfcyc) +{ + int i; + + if(dfcyc) { + // Avoid unused parameter warnings + } + for(i = 0; i < 4; i++) { + g_paddle_val[i] = 32767; + } + g_paddle_buttons = 0xc; + if(g_joystick_native_type1 >= 0) { + g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); + g_paddle_val[0] = g_joystick_callback_x; + g_paddle_val[1] = g_joystick_callback_y; + paddle_update_trigger_dcycs(dfcyc); + } +} + +void +joystick_update_buttons() +{ + if(g_joystick_native_type1 >= 0) { + g_paddle_buttons = 0xc | (g_joystick_callback_buttons & 3); + } +} +#endif + +void +joystick_callback_init(int native_type) +{ + g_joystick_native_type1 = native_type; +} + +void +joystick_callback_update(word32 buttons, int paddle_x, int paddle_y) +{ + g_joystick_callback_buttons = (g_paddle_buttons & (~3)) | (buttons & 3); + g_joystick_callback_x = paddle_x; + g_joystick_callback_y = paddle_y; +} diff --git a/gsplus/src/kegs.icns b/gsplus/src/kegs.icns new file mode 100644 index 0000000..8cf7224 Binary files /dev/null and b/gsplus/src/kegs.icns differ diff --git a/gsplus/src/kegsfont.h b/gsplus/src/kegsfont.h new file mode 100644 index 0000000..d4394a7 --- /dev/null +++ b/gsplus/src/kegsfont.h @@ -0,0 +1,514 @@ +/* $KmKId: kegsfont.h,v 1.1 2002-11-10 03:31:51-05 kadickey Exp $ */ + +/* char 0x00 (raw 0x40) */ + { 0xc7, 0xbb, 0xab, 0xa3, 0xa7, 0xbf, 0xc3, 0xff }, +/* char 0x01 (raw 0x41) */ + { 0xef, 0xd7, 0xbb, 0xbb, 0x83, 0xbb, 0xbb, 0xff }, +/* char 0x02 (raw 0x42) */ + { 0x87, 0xbb, 0xbb, 0x87, 0xbb, 0xbb, 0x87, 0xff }, +/* char 0x03 (raw 0x43) */ + { 0xc7, 0xbb, 0xbf, 0xbf, 0xbf, 0xbb, 0xc7, 0xff }, +/* char 0x04 (raw 0x44) */ + { 0x87, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0x87, 0xff }, +/* char 0x05 (raw 0x45) */ + { 0x83, 0xbf, 0xbf, 0x87, 0xbf, 0xbf, 0x83, 0xff }, +/* char 0x06 (raw 0x46) */ + { 0x83, 0xbf, 0xbf, 0x87, 0xbf, 0xbf, 0xbf, 0xff }, +/* char 0x07 (raw 0x47) */ + { 0xc3, 0xbf, 0xbf, 0xbf, 0xb3, 0xbb, 0xc3, 0xff }, +/* char 0x08 (raw 0x48) */ + { 0xbb, 0xbb, 0xbb, 0x83, 0xbb, 0xbb, 0xbb, 0xff }, +/* char 0x09 (raw 0x49) */ + { 0xc7, 0xef, 0xef, 0xef, 0xef, 0xef, 0xc7, 0xff }, +/* char 0x0a (raw 0x4a) */ + { 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xbb, 0xc7, 0xff }, +/* char 0x0b (raw 0x4b) */ + { 0xbb, 0xb7, 0xaf, 0x9f, 0xaf, 0xb7, 0xbb, 0xff }, +/* char 0x0c (raw 0x4c) */ + { 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x83, 0xff }, +/* char 0x0d (raw 0x4d) */ + { 0xbb, 0x93, 0xab, 0xab, 0xbb, 0xbb, 0xbb, 0xff }, +/* char 0x0e (raw 0x4e) */ + { 0xbb, 0xbb, 0x9b, 0xab, 0xb3, 0xbb, 0xbb, 0xff }, +/* char 0x0f (raw 0x4f) */ + { 0xc7, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xc7, 0xff }, +/* char 0x10 (raw 0x50) */ + { 0x87, 0xbb, 0xbb, 0x87, 0xbf, 0xbf, 0xbf, 0xff }, +/* char 0x11 (raw 0x51) */ + { 0xc7, 0xbb, 0xbb, 0xbb, 0xab, 0xb7, 0xcb, 0xff }, +/* char 0x12 (raw 0x52) */ + { 0x87, 0xbb, 0xbb, 0x87, 0xaf, 0xb7, 0xbb, 0xff }, +/* char 0x13 (raw 0x53) */ + { 0xc7, 0xbb, 0xbf, 0xc7, 0xfb, 0xbb, 0xc7, 0xff }, +/* char 0x14 (raw 0x54) */ + { 0x83, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xff }, +/* char 0x15 (raw 0x55) */ + { 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xc7, 0xff }, +/* char 0x16 (raw 0x56) */ + { 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xd7, 0xef, 0xff }, +/* char 0x17 (raw 0x57) */ + { 0xbb, 0xbb, 0xbb, 0xab, 0xab, 0x93, 0xbb, 0xff }, +/* char 0x18 (raw 0x58) */ + { 0xbb, 0xbb, 0xd7, 0xef, 0xd7, 0xbb, 0xbb, 0xff }, +/* char 0x19 (raw 0x59) */ + { 0xbb, 0xbb, 0xd7, 0xef, 0xef, 0xef, 0xef, 0xff }, +/* char 0x1a (raw 0x5a) */ + { 0x83, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x83, 0xff }, +/* char 0x1b (raw 0x5b) */ + { 0x83, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x83, 0xff }, +/* char 0x1c (raw 0x5c) */ + { 0xff, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xff, 0xff }, +/* char 0x1d (raw 0x5d) */ + { 0x83, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0x83, 0xff }, +/* char 0x1e (raw 0x5e) */ + { 0xff, 0xff, 0xef, 0xd7, 0xbb, 0xff, 0xff, 0xff }, +/* char 0x1f (raw 0x5f) */ + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01 }, +/* char 0x20 (raw 0x20) */ + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, +/* char 0x21 (raw 0x21) */ + { 0xef, 0xef, 0xef, 0xef, 0xef, 0xff, 0xef, 0xff }, +/* char 0x22 (raw 0x22) */ + { 0xd7, 0xd7, 0xd7, 0xff, 0xff, 0xff, 0xff, 0xff }, +/* char 0x23 (raw 0x23) */ + { 0xd7, 0xd7, 0x83, 0xd7, 0x83, 0xd7, 0xd7, 0xff }, +/* char 0x24 (raw 0x24) */ + { 0xef, 0xc3, 0xaf, 0xc7, 0xeb, 0x87, 0xef, 0xff }, +/* char 0x25 (raw 0x25) */ + { 0x9f, 0x9b, 0xf7, 0xef, 0xdf, 0xb3, 0xf3, 0xff }, +/* char 0x26 (raw 0x26) */ + { 0xdf, 0xaf, 0xaf, 0xdf, 0xab, 0xb7, 0xcb, 0xff }, +/* char 0x27 (raw 0x27) */ + { 0xef, 0xef, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff }, +/* char 0x28 (raw 0x28) */ + { 0xef, 0xdf, 0xbf, 0xbf, 0xbf, 0xdf, 0xef, 0xff }, +/* char 0x29 (raw 0x29) */ + { 0xef, 0xf7, 0xfb, 0xfb, 0xfb, 0xf7, 0xef, 0xff }, +/* char 0x2a (raw 0x2a) */ + { 0xef, 0xab, 0xc7, 0xef, 0xc7, 0xab, 0xef, 0xff }, +/* char 0x2b (raw 0x2b) */ + { 0xff, 0xef, 0xef, 0x83, 0xef, 0xef, 0xff, 0xff }, +/* char 0x2c (raw 0x2c) */ + { 0xff, 0xff, 0xff, 0xff, 0xef, 0xef, 0xdf, 0xff }, +/* char 0x2d (raw 0x2d) */ + { 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xff }, +/* char 0x2e (raw 0x2e) */ + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff }, +/* char 0x2f (raw 0x2f) */ + { 0xff, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0xff, 0xff }, +/* char 0x30 (raw 0x30) */ + { 0xc7, 0xbb, 0xb3, 0xab, 0x9b, 0xbb, 0xc7, 0xff }, +/* char 0x31 (raw 0x31) */ + { 0xef, 0xcf, 0xef, 0xef, 0xef, 0xef, 0xc7, 0xff }, +/* char 0x32 (raw 0x32) */ + { 0xc7, 0xbb, 0xfb, 0xe7, 0xdf, 0xbf, 0x83, 0xff }, +/* char 0x33 (raw 0x33) */ + { 0x83, 0xfb, 0xf7, 0xe7, 0xfb, 0xbb, 0xc7, 0xff }, +/* char 0x34 (raw 0x34) */ + { 0xf7, 0xe7, 0xd7, 0xb7, 0x83, 0xf7, 0xf7, 0xff }, +/* char 0x35 (raw 0x35) */ + { 0x83, 0xbf, 0x87, 0xfb, 0xfb, 0xbb, 0xc7, 0xff }, +/* char 0x36 (raw 0x36) */ + { 0xe3, 0xdf, 0xbf, 0x87, 0xbb, 0xbb, 0xc7, 0xff }, +/* char 0x37 (raw 0x37) */ + { 0x83, 0xfb, 0xf7, 0xef, 0xdf, 0xdf, 0xdf, 0xff }, +/* char 0x38 (raw 0x38) */ + { 0xc7, 0xbb, 0xbb, 0xc7, 0xbb, 0xbb, 0xc7, 0xff }, +/* char 0x39 (raw 0x39) */ + { 0xc7, 0xbb, 0xbb, 0xc3, 0xfb, 0xf7, 0x8f, 0xff }, +/* char 0x3a (raw 0x3a) */ + { 0xff, 0xff, 0xef, 0xff, 0xef, 0xff, 0xff, 0xff }, +/* char 0x3b (raw 0x3b) */ + { 0xff, 0xff, 0xef, 0xff, 0xef, 0xef, 0xdf, 0xff }, +/* char 0x3c (raw 0x3c) */ + { 0xf7, 0xef, 0xdf, 0xbf, 0xdf, 0xef, 0xf7, 0xff }, +/* char 0x3d (raw 0x3d) */ + { 0xff, 0xff, 0x83, 0xff, 0x83, 0xff, 0xff, 0xff }, +/* char 0x3e (raw 0x3e) */ + { 0xdf, 0xef, 0xf7, 0xfb, 0xf7, 0xef, 0xdf, 0xff }, +/* char 0x3f (raw 0x3f) */ + { 0xc7, 0xbb, 0xf7, 0xef, 0xef, 0xff, 0xef, 0xff }, +/* char 0x40 (raw 0x14) */ + { 0x08, 0x10, 0x6c, 0xfe, 0xfc, 0xfc, 0x7e, 0x6c }, +/* char 0x41 (raw 0x11) */ + { 0x08, 0x10, 0x6c, 0x82, 0x84, 0x84, 0x52, 0x6c }, +/* char 0x42 (raw 0xf5) */ + { 0x00, 0x00, 0x40, 0x60, 0x70, 0x78, 0x6c, 0x42 }, +/* char 0x43 (raw 0x82) */ + { 0xfe, 0x44, 0x28, 0x10, 0x10, 0x28, 0x54, 0xfe }, +/* char 0x44 (raw 0xeb) */ + { 0x00, 0x02, 0x04, 0x88, 0x50, 0x20, 0x20, 0x00 }, +/* char 0x45 (raw 0xe4) */ + { 0xfe, 0xfc, 0xfa, 0x36, 0xae, 0xde, 0xde, 0xfe }, +/* char 0x46 (raw 0xec) */ + { 0xfc, 0xfc, 0xfc, 0xdc, 0x9c, 0x00, 0x9e, 0xde }, +/* char 0x47 (raw 0xed) */ + { 0xfe, 0x00, 0xfe, 0x00, 0xfe, 0x00, 0x00, 0xfe }, +/* char 0x48 (raw 0xee) */ + { 0x10, 0x20, 0x40, 0xfe, 0x40, 0x20, 0x10, 0x00 }, +/* char 0x49 (raw 0xe9) */ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54 }, +/* char 0x4a (raw 0xef) */ + { 0x10, 0x10, 0x10, 0x10, 0x92, 0x54, 0x38, 0x10 }, +/* char 0x4b (raw 0xf0) */ + { 0x10, 0x38, 0x54, 0x92, 0x10, 0x10, 0x10, 0x10 }, +/* char 0x4c (raw 0xf1) */ + { 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, +/* char 0x4d (raw 0xf7) */ + { 0x02, 0x02, 0x02, 0x22, 0x62, 0xfe, 0x60, 0x20 }, +/* char 0x4e (raw 0xf6) */ + { 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc }, +/* char 0x4f (raw 0xaf) */ + { 0xc8, 0x18, 0x38, 0x7e, 0x38, 0x18, 0x08, 0xf6 }, +/* char 0x50 (raw 0xb8) */ + { 0x26, 0x30, 0x38, 0xfc, 0x38, 0x30, 0x20, 0xde }, +/* char 0x51 (raw 0xce) */ + { 0x02, 0x12, 0x10, 0xfe, 0x7c, 0x38, 0x12, 0x02 }, +/* char 0x52 (raw 0xe5) */ + { 0x02, 0x12, 0x38, 0x7c, 0xfe, 0x10, 0x12, 0x02 }, +/* char 0x53 (raw 0xea) */ + { 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00 }, +/* char 0x54 (raw 0xe6) */ + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xfe }, +/* char 0x55 (raw 0xe8) */ + { 0x10, 0x08, 0x04, 0xfe, 0x04, 0x08, 0x10, 0x00 }, +/* char 0x56 (raw 0xd7) */ + { 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa }, +/* char 0x57 (raw 0xe3) */ + { 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54 }, +/* char 0x58 (raw 0xf4) */ + { 0x00, 0x7c, 0x82, 0x80, 0x80, 0x80, 0xfe, 0x00 }, +/* char 0x59 (raw 0xe7) */ + { 0x00, 0x00, 0xfc, 0x02, 0x02, 0x02, 0xfe, 0x00 }, +/* char 0x5a (raw 0xf3) */ + { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }, +/* char 0x5b (raw 0xd2) */ + { 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x00 }, +/* char 0x5c (raw 0xc7) */ + { 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe }, +/* char 0x5d (raw 0xd4) */ + { 0x28, 0x28, 0xee, 0x00, 0xee, 0x28, 0x28, 0x00 }, +/* char 0x5e (raw 0xdf) */ + { 0xfe, 0x02, 0x02, 0x32, 0x32, 0x02, 0x02, 0xfe }, +/* char 0x5f (raw 0xd1) */ + { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, +/* char 0x60 (raw 0x60) */ + { 0xdf, 0xef, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff }, +/* char 0x61 (raw 0x61) */ + { 0xff, 0xff, 0xc7, 0xfb, 0xc3, 0xbb, 0xc3, 0xff }, +/* char 0x62 (raw 0x62) */ + { 0xbf, 0xbf, 0x87, 0xbb, 0xbb, 0xbb, 0x87, 0xff }, +/* char 0x63 (raw 0x63) */ + { 0xff, 0xff, 0xc3, 0xbf, 0xbf, 0xbf, 0xc3, 0xff }, +/* char 0x64 (raw 0x64) */ + { 0xfb, 0xfb, 0xc3, 0xbb, 0xbb, 0xbb, 0xc3, 0xff }, +/* char 0x65 (raw 0x65) */ + { 0xff, 0xff, 0xc7, 0xbb, 0x83, 0xbf, 0xc3, 0xff }, +/* char 0x66 (raw 0x66) */ + { 0xe7, 0xdb, 0xdf, 0x87, 0xdf, 0xdf, 0xdf, 0xff }, +/* char 0x67 (raw 0x67) */ + { 0xff, 0xff, 0xc7, 0xbb, 0xbb, 0xc3, 0xfb, 0xc7 }, +/* char 0x68 (raw 0x68) */ + { 0xbf, 0xbf, 0x87, 0xbb, 0xbb, 0xbb, 0xbb, 0xff }, +/* char 0x69 (raw 0x69) */ + { 0xef, 0xff, 0xcf, 0xef, 0xef, 0xef, 0xc7, 0xff }, +/* char 0x6a (raw 0x6a) */ + { 0xf7, 0xff, 0xe7, 0xf7, 0xf7, 0xf7, 0xb7, 0xcf }, +/* char 0x6b (raw 0x6b) */ + { 0xbf, 0xbf, 0xbb, 0xb7, 0x8f, 0xb7, 0xbb, 0xff }, +/* char 0x6c (raw 0x6c) */ + { 0xcf, 0xef, 0xef, 0xef, 0xef, 0xef, 0xc7, 0xff }, +/* char 0x6d (raw 0x6d) */ + { 0xff, 0xff, 0x93, 0xab, 0xab, 0xab, 0xbb, 0xff }, +/* char 0x6e (raw 0x6e) */ + { 0xff, 0xff, 0x87, 0xbb, 0xbb, 0xbb, 0xbb, 0xff }, +/* char 0x6f (raw 0x6f) */ + { 0xff, 0xff, 0xc7, 0xbb, 0xbb, 0xbb, 0xc7, 0xff }, +/* char 0x70 (raw 0x70) */ + { 0xff, 0xff, 0x87, 0xbb, 0xbb, 0x87, 0xbf, 0xbf }, +/* char 0x71 (raw 0x71) */ + { 0xff, 0xff, 0xc3, 0xbb, 0xbb, 0xc3, 0xfb, 0xfb }, +/* char 0x72 (raw 0x72) */ + { 0xff, 0xff, 0xa3, 0x9f, 0xbf, 0xbf, 0xbf, 0xff }, +/* char 0x73 (raw 0x73) */ + { 0xff, 0xff, 0xc3, 0xbf, 0xc7, 0xfb, 0x87, 0xff }, +/* char 0x74 (raw 0x74) */ + { 0xdf, 0xdf, 0x87, 0xdf, 0xdf, 0xdb, 0xe7, 0xff }, +/* char 0x75 (raw 0x75) */ + { 0xff, 0xff, 0xbb, 0xbb, 0xbb, 0xb3, 0xcb, 0xff }, +/* char 0x76 (raw 0x76) */ + { 0xff, 0xff, 0xbb, 0xbb, 0xbb, 0xd7, 0xef, 0xff }, +/* char 0x77 (raw 0x77) */ + { 0xff, 0xff, 0xbb, 0xbb, 0xab, 0xab, 0x93, 0xff }, +/* char 0x78 (raw 0x78) */ + { 0xff, 0xff, 0xbb, 0xd7, 0xef, 0xd7, 0xbb, 0xff }, +/* char 0x79 (raw 0x79) */ + { 0xff, 0xff, 0xbb, 0xbb, 0xbb, 0xc3, 0xfb, 0xc7 }, +/* char 0x7a (raw 0x7a) */ + { 0xff, 0xff, 0x83, 0xf7, 0xef, 0xdf, 0x83, 0xff }, +/* char 0x7b (raw 0x7b) */ + { 0xe3, 0xcf, 0xcf, 0x9f, 0xcf, 0xcf, 0xe3, 0xff }, +/* char 0x7c (raw 0x7c) */ + { 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef }, +/* char 0x7d (raw 0x7d) */ + { 0x8f, 0xe7, 0xe7, 0xf3, 0xe7, 0xe7, 0x8f, 0xff }, +/* char 0x7e (raw 0x7e) */ + { 0xcb, 0xa7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, +/* char 0x7f (raw 0x7f) */ + { 0xff, 0xab, 0xd7, 0xab, 0xd7, 0xab, 0xff, 0xff }, +/* char 0x80 (raw 0x40) */ + { 0x38, 0x44, 0x54, 0x5c, 0x58, 0x40, 0x3c, 0x00 }, +/* char 0x81 (raw 0x41) */ + { 0x10, 0x28, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x00 }, +/* char 0x82 (raw 0x42) */ + { 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x78, 0x00 }, +/* char 0x83 (raw 0x43) */ + { 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00 }, +/* char 0x84 (raw 0x44) */ + { 0x78, 0x44, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00 }, +/* char 0x85 (raw 0x45) */ + { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x7c, 0x00 }, +/* char 0x86 (raw 0x46) */ + { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x00 }, +/* char 0x87 (raw 0x47) */ + { 0x3c, 0x40, 0x40, 0x40, 0x4c, 0x44, 0x3c, 0x00 }, +/* char 0x88 (raw 0x48) */ + { 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x00 }, +/* char 0x89 (raw 0x49) */ + { 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, +/* char 0x8a (raw 0x4a) */ + { 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00 }, +/* char 0x8b (raw 0x4b) */ + { 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x00 }, +/* char 0x8c (raw 0x4c) */ + { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x00 }, +/* char 0x8d (raw 0x4d) */ + { 0x44, 0x6c, 0x54, 0x54, 0x44, 0x44, 0x44, 0x00 }, +/* char 0x8e (raw 0x4e) */ + { 0x44, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x44, 0x00 }, +/* char 0x8f (raw 0x4f) */ + { 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, +/* char 0x90 (raw 0x50) */ + { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x00 }, +/* char 0x91 (raw 0x51) */ + { 0x38, 0x44, 0x44, 0x44, 0x54, 0x48, 0x34, 0x00 }, +/* char 0x92 (raw 0x52) */ + { 0x78, 0x44, 0x44, 0x78, 0x50, 0x48, 0x44, 0x00 }, +/* char 0x93 (raw 0x53) */ + { 0x38, 0x44, 0x40, 0x38, 0x04, 0x44, 0x38, 0x00 }, +/* char 0x94 (raw 0x54) */ + { 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 }, +/* char 0x95 (raw 0x55) */ + { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, +/* char 0x96 (raw 0x56) */ + { 0x44, 0x44, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 }, +/* char 0x97 (raw 0x57) */ + { 0x44, 0x44, 0x44, 0x54, 0x54, 0x6c, 0x44, 0x00 }, +/* char 0x98 (raw 0x58) */ + { 0x44, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00 }, +/* char 0x99 (raw 0x59) */ + { 0x44, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x00 }, +/* char 0x9a (raw 0x5a) */ + { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7c, 0x00 }, +/* char 0x9b (raw 0x5b) */ + { 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x00 }, +/* char 0x9c (raw 0x5c) */ + { 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00 }, +/* char 0x9d (raw 0x5d) */ + { 0x7c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x7c, 0x00 }, +/* char 0x9e (raw 0x5e) */ + { 0x00, 0x00, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00 }, +/* char 0x9f (raw 0x5f) */ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe }, +/* char 0xa0 (raw 0x20) */ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, +/* char 0xa1 (raw 0x21) */ + { 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00 }, +/* char 0xa2 (raw 0x22) */ + { 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00 }, +/* char 0xa3 (raw 0x23) */ + { 0x28, 0x28, 0x7c, 0x28, 0x7c, 0x28, 0x28, 0x00 }, +/* char 0xa4 (raw 0x24) */ + { 0x10, 0x3c, 0x50, 0x38, 0x14, 0x78, 0x10, 0x00 }, +/* char 0xa5 (raw 0x25) */ + { 0x60, 0x64, 0x08, 0x10, 0x20, 0x4c, 0x0c, 0x00 }, +/* char 0xa6 (raw 0x26) */ + { 0x20, 0x50, 0x50, 0x20, 0x54, 0x48, 0x34, 0x00 }, +/* char 0xa7 (raw 0x27) */ + { 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 }, +/* char 0xa8 (raw 0x28) */ + { 0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10, 0x00 }, +/* char 0xa9 (raw 0x29) */ + { 0x10, 0x08, 0x04, 0x04, 0x04, 0x08, 0x10, 0x00 }, +/* char 0xaa (raw 0x2a) */ + { 0x10, 0x54, 0x38, 0x10, 0x38, 0x54, 0x10, 0x00 }, +/* char 0xab (raw 0x2b) */ + { 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00 }, +/* char 0xac (raw 0x2c) */ + { 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x20, 0x00 }, +/* char 0xad (raw 0x2d) */ + { 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00 }, +/* char 0xae (raw 0x2e) */ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00 }, +/* char 0xaf (raw 0x2f) */ + { 0x00, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00 }, +/* char 0xb0 (raw 0x30) */ + { 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38, 0x00 }, +/* char 0xb1 (raw 0x31) */ + { 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, +/* char 0xb2 (raw 0x32) */ + { 0x38, 0x44, 0x04, 0x18, 0x20, 0x40, 0x7c, 0x00 }, +/* char 0xb3 (raw 0x33) */ + { 0x7c, 0x04, 0x08, 0x18, 0x04, 0x44, 0x38, 0x00 }, +/* char 0xb4 (raw 0x34) */ + { 0x08, 0x18, 0x28, 0x48, 0x7c, 0x08, 0x08, 0x00 }, +/* char 0xb5 (raw 0x35) */ + { 0x7c, 0x40, 0x78, 0x04, 0x04, 0x44, 0x38, 0x00 }, +/* char 0xb6 (raw 0x36) */ + { 0x1c, 0x20, 0x40, 0x78, 0x44, 0x44, 0x38, 0x00 }, +/* char 0xb7 (raw 0x37) */ + { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x20, 0x20, 0x00 }, +/* char 0xb8 (raw 0x38) */ + { 0x38, 0x44, 0x44, 0x38, 0x44, 0x44, 0x38, 0x00 }, +/* char 0xb9 (raw 0x39) */ + { 0x38, 0x44, 0x44, 0x3c, 0x04, 0x08, 0x70, 0x00 }, +/* char 0xba (raw 0x3a) */ + { 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00 }, +/* char 0xbb (raw 0x3b) */ + { 0x00, 0x00, 0x10, 0x00, 0x10, 0x10, 0x20, 0x00 }, +/* char 0xbc (raw 0x3c) */ + { 0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x00 }, +/* char 0xbd (raw 0x3d) */ + { 0x00, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x00, 0x00 }, +/* char 0xbe (raw 0x3e) */ + { 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x00 }, +/* char 0xbf (raw 0x3f) */ + { 0x38, 0x44, 0x08, 0x10, 0x10, 0x00, 0x10, 0x00 }, +/* char 0xc0 (raw 0x40) */ + { 0x38, 0x44, 0x54, 0x5c, 0x58, 0x40, 0x3c, 0x00 }, +/* char 0xc1 (raw 0x41) */ + { 0x10, 0x28, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x00 }, +/* char 0xc2 (raw 0x42) */ + { 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x78, 0x00 }, +/* char 0xc3 (raw 0x43) */ + { 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00 }, +/* char 0xc4 (raw 0x44) */ + { 0x78, 0x44, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00 }, +/* char 0xc5 (raw 0x45) */ + { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x7c, 0x00 }, +/* char 0xc6 (raw 0x46) */ + { 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x00 }, +/* char 0xc7 (raw 0x47) */ + { 0x3c, 0x40, 0x40, 0x40, 0x4c, 0x44, 0x3c, 0x00 }, +/* char 0xc8 (raw 0x48) */ + { 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x00 }, +/* char 0xc9 (raw 0x49) */ + { 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, +/* char 0xca (raw 0x4a) */ + { 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00 }, +/* char 0xcb (raw 0x4b) */ + { 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x00 }, +/* char 0xcc (raw 0x4c) */ + { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x00 }, +/* char 0xcd (raw 0x4d) */ + { 0x44, 0x6c, 0x54, 0x54, 0x44, 0x44, 0x44, 0x00 }, +/* char 0xce (raw 0x4e) */ + { 0x44, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x44, 0x00 }, +/* char 0xcf (raw 0x4f) */ + { 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, +/* char 0xd0 (raw 0x50) */ + { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x00 }, +/* char 0xd1 (raw 0x51) */ + { 0x38, 0x44, 0x44, 0x44, 0x54, 0x48, 0x34, 0x00 }, +/* char 0xd2 (raw 0x52) */ + { 0x78, 0x44, 0x44, 0x78, 0x50, 0x48, 0x44, 0x00 }, +/* char 0xd3 (raw 0x53) */ + { 0x38, 0x44, 0x40, 0x38, 0x04, 0x44, 0x38, 0x00 }, +/* char 0xd4 (raw 0x54) */ + { 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 }, +/* char 0xd5 (raw 0x55) */ + { 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00 }, +/* char 0xd6 (raw 0x56) */ + { 0x44, 0x44, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 }, +/* char 0xd7 (raw 0x57) */ + { 0x44, 0x44, 0x44, 0x54, 0x54, 0x6c, 0x44, 0x00 }, +/* char 0xd8 (raw 0x58) */ + { 0x44, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00 }, +/* char 0xd9 (raw 0x59) */ + { 0x44, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x00 }, +/* char 0xda (raw 0x5a) */ + { 0x7c, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7c, 0x00 }, +/* char 0xdb (raw 0x5b) */ + { 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x00 }, +/* char 0xdc (raw 0x5c) */ + { 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00 }, +/* char 0xdd (raw 0x5d) */ + { 0x7c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x7c, 0x00 }, +/* char 0xde (raw 0x5e) */ + { 0x00, 0x00, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00 }, +/* char 0xdf (raw 0x5f) */ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe }, +/* char 0xe0 (raw 0x60) */ + { 0x20, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, +/* char 0xe1 (raw 0x61) */ + { 0x00, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00 }, +/* char 0xe2 (raw 0x62) */ + { 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x78, 0x00 }, +/* char 0xe3 (raw 0x63) */ + { 0x00, 0x00, 0x3c, 0x40, 0x40, 0x40, 0x3c, 0x00 }, +/* char 0xe4 (raw 0x64) */ + { 0x04, 0x04, 0x3c, 0x44, 0x44, 0x44, 0x3c, 0x00 }, +/* char 0xe5 (raw 0x65) */ + { 0x00, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x3c, 0x00 }, +/* char 0xe6 (raw 0x66) */ + { 0x18, 0x24, 0x20, 0x78, 0x20, 0x20, 0x20, 0x00 }, +/* char 0xe7 (raw 0x67) */ + { 0x00, 0x00, 0x38, 0x44, 0x44, 0x3c, 0x04, 0x38 }, +/* char 0xe8 (raw 0x68) */ + { 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00 }, +/* char 0xe9 (raw 0x69) */ + { 0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x38, 0x00 }, +/* char 0xea (raw 0x6a) */ + { 0x08, 0x00, 0x18, 0x08, 0x08, 0x08, 0x48, 0x30 }, +/* char 0xeb (raw 0x6b) */ + { 0x40, 0x40, 0x44, 0x48, 0x70, 0x48, 0x44, 0x00 }, +/* char 0xec (raw 0x6c) */ + { 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00 }, +/* char 0xed (raw 0x6d) */ + { 0x00, 0x00, 0x6c, 0x54, 0x54, 0x54, 0x44, 0x00 }, +/* char 0xee (raw 0x6e) */ + { 0x00, 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00 }, +/* char 0xef (raw 0x6f) */ + { 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, +/* char 0xf0 (raw 0x70) */ + { 0x00, 0x00, 0x78, 0x44, 0x44, 0x78, 0x40, 0x40 }, +/* char 0xf1 (raw 0x71) */ + { 0x00, 0x00, 0x3c, 0x44, 0x44, 0x3c, 0x04, 0x04 }, +/* char 0xf2 (raw 0x72) */ + { 0x00, 0x00, 0x5c, 0x60, 0x40, 0x40, 0x40, 0x00 }, +/* char 0xf3 (raw 0x73) */ + { 0x00, 0x00, 0x3c, 0x40, 0x38, 0x04, 0x78, 0x00 }, +/* char 0xf4 (raw 0x74) */ + { 0x20, 0x20, 0x78, 0x20, 0x20, 0x24, 0x18, 0x00 }, +/* char 0xf5 (raw 0x75) */ + { 0x00, 0x00, 0x44, 0x44, 0x44, 0x4c, 0x34, 0x00 }, +/* char 0xf6 (raw 0x76) */ + { 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00 }, +/* char 0xf7 (raw 0x77) */ + { 0x00, 0x00, 0x44, 0x44, 0x54, 0x54, 0x6c, 0x00 }, +/* char 0xf8 (raw 0x78) */ + { 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, +/* char 0xf9 (raw 0x79) */ + { 0x00, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x04, 0x38 }, +/* char 0xfa (raw 0x7a) */ + { 0x00, 0x00, 0x7c, 0x08, 0x10, 0x20, 0x7c, 0x00 }, +/* char 0xfb (raw 0x7b) */ + { 0x1c, 0x30, 0x30, 0x60, 0x30, 0x30, 0x1c, 0x00 }, +/* char 0xfc (raw 0x7c) */ + { 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10 }, +/* char 0xfd (raw 0x7d) */ + { 0x70, 0x18, 0x18, 0x0c, 0x18, 0x18, 0x70, 0x00 }, +/* char 0xfe (raw 0x7e) */ + { 0x34, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, +/* char 0xff (raw 0x7f) */ + { 0x00, 0x54, 0x28, 0x54, 0x28, 0x54, 0x00, 0x00 }, diff --git a/gsplus/src/kegswin.sln b/gsplus/src/kegswin.sln new file mode 100644 index 0000000..bb2bea4 --- /dev/null +++ b/gsplus/src/kegswin.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kegswin", "kegswin.vcxproj", "{C24D318A-501E-4B8C-901A-15977CB9C00C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x64.ActiveCfg = Release|x64 + {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x64.Build.0 = Release|x64 + {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x86.ActiveCfg = Debug|Win32 + {C24D318A-501E-4B8C-901A-15977CB9C00C}.Debug|x86.Build.0 = Debug|Win32 + {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x64.ActiveCfg = Release|x64 + {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x64.Build.0 = Release|x64 + {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x86.ActiveCfg = Release|Win32 + {C24D318A-501E-4B8C-901A-15977CB9C00C}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {32BE13D0-68A7-486D-874C-EA158125775B} + EndGlobalSection +EndGlobal diff --git a/gsplus/src/kegswin.vcxproj b/gsplus/src/kegswin.vcxproj new file mode 100644 index 0000000..66b57b9 --- /dev/null +++ b/gsplus/src/kegswin.vcxproj @@ -0,0 +1,169 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + {C24D318A-501E-4B8C-901A-15977CB9C00C} + Win32Proj + + + + Application + true + v143 + + + Application + false + v143 + + + Application + true + v143 + + + Application + false + v143 + + + + + + + + + + + + + + + + + + + + + true + + + true + + + + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + + + MachineX86 + true + Windows + + + + + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + + + MachineX86 + true + Console + true + true + wsock32.lib;dsound.lib;winmm.lib;%(AdditionalDependencies) + + + + + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;wsock32.lib;dsound.lib;winmm.lib;odbccp32.lib;%(AdditionalDependencies) + Console + + + true + Level3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gsplus/src/ldvars b/gsplus/src/ldvars new file mode 100644 index 0000000..cb148b0 --- /dev/null +++ b/gsplus/src/ldvars @@ -0,0 +1,2 @@ +OBJECTS = adb.o engine_c.o clock.o config.o debugger.o scc.o scc_socket_driver.o scc_windriver.o scc_unixdriver.o iwm.o joystick_driver.o moremem.o paddles.o mockingboard.o sim65816.o smartport.o doc.o sound.o sound_driver.o woz.o unshk.o undeflate.o dynapro.o dyna_type.o dyna_filt.o dyna_validate.o applesingle.o video.o voc.o +PROJROOT = .. diff --git a/gsplus/src/macsnd_driver.c b/gsplus/src/macsnd_driver.c new file mode 100644 index 0000000..c1bae00 --- /dev/null +++ b/gsplus/src/macsnd_driver.c @@ -0,0 +1,272 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2022 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// Headers at: /Applications/Xcode.app/Contents/Developer/Platforms/ +// MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/ +// Frameworks/AudioToolbox.framework/Headers + +// Some ideas from SDL code: +// http://www-personal.umich.edu/~bazald/l/api/_s_d_l__coreaudio_8c_source.html + +#include "defc.h" +#include "sound.h" + +#include +#include +#include + +// Mac OS X 12.0 deprecates kAudioObjectPropertyElementMaster. So now we +// need to use kAudioObjectPropertyElementMain, and set it to ...Master if it +// isn't already set. This is beyond dumb +#if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) +# if __MAC_OS_X_VERSION_MAX_ALLOWED < 120000 +# define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster +# endif +#endif + +#define MACSND_REBUF_SIZE (64*1024) + +word32 g_macsnd_rebuf[MACSND_REBUF_SIZE]; +volatile int g_macsnd_rd_pos; +volatile int g_macsnd_wr_pos; +volatile int g_macsnd_playing = 0; +int g_macsnd_channel_warn = 0; +extern int g_sound_min_samples; // About 33ms +extern int g_sound_max_multiplier; // About 6, so 6*33 ~= 200ms. + +extern int Verbose; + +extern int g_preferred_rate; +extern word32 *g_sound_shm_addr; +extern int g_sound_size; + +int g_call_num = 0; + +AURenderCallbackStruct g_callback_struct = { 0 }; + +static OSStatus +audio_callback(void *in_ref_ptr, AudioUnitRenderActionFlags *aura_flags_ptr, + const AudioTimeStamp *a_timestamp_ptr, UInt32 bus_number, + UInt32 in_num_frame, AudioBufferList *abuflist_ptr) +{ + word32 *wptr; + int num_buffers, num_channels, num_samps, sample_num, samps_avail; + int max_samples, min_samples; + int i, j; + + if(in_ref_ptr || aura_flags_ptr || a_timestamp_ptr || bus_number) { + // Avoid unused parameter warning + } +#if 0 + printf("CB: audio_callback called, in_ref:%p, bus:%d, in_frame:%d\n", + in_ref_ptr, bus_number, in_num_frame); +#endif + num_buffers = abuflist_ptr->mNumberBuffers; + min_samples = g_sound_min_samples; + max_samples = min_samples * g_sound_max_multiplier; +#if 0 + printf("CB: num_buffers: %d. sample_time:%lf, host_time:%016llx " + "rate_scalar:%lf, word_clock_time:%016llx, flags:%04x\n", + num_buffers, a_timestamp_ptr->mSampleTime, + a_timestamp_ptr->mHostTime, a_timestamp_ptr->mRateScalar, + a_timestamp_ptr->mWordClockTime, a_timestamp_ptr->mFlags); +#endif + + sample_num = g_macsnd_rd_pos; + samps_avail = (g_macsnd_wr_pos - sample_num) & (MACSND_REBUF_SIZE - 1); + if((int)in_num_frame > samps_avail) { + // We don't have enough samples, must pause + g_macsnd_playing = 0; + sample_num = g_macsnd_wr_pos; // Eat remaining samps + } + if((g_macsnd_playing == 0) && + (samps_avail > (min_samples + (int)in_num_frame))) { + // We can unpause + g_macsnd_playing = 1; + } + if(g_macsnd_playing && (samps_avail > max_samples)) { + printf("JUMP SAMPLE_NUM by %d samples!\n", max_samples / 2); + sample_num += (max_samples / 2); + sample_num = sample_num & (MACSND_REBUF_SIZE - 1); + } +#if 0 + printf("CB: in_frame:%d, samps_avail:%d, playing:%d\n", in_num_frame, + samps_avail, g_macsnd_playing); +#endif + + for(i = 0; i < num_buffers; i++) { + num_channels = abuflist_ptr->mBuffers[i].mNumberChannels; + if((num_channels != 2) && !g_macsnd_channel_warn) { + printf("mNumberChannels:%d\n", num_channels); + g_macsnd_channel_warn = 1; + } + num_samps = abuflist_ptr->mBuffers[i].mDataByteSize / 4; + wptr = (word32 *)abuflist_ptr->mBuffers[i].mData; +#if 0 + printf("CB buf[%d]: num_ch:%d, num_samps:%d, wptr:%p\n", i, + num_channels, num_samps, wptr); +#endif +#if 0 + if((g_call_num & 31) == 0) { + printf("%d play %d samples, samps_avail:%d\n", + g_call_num, num_samps, samps_avail); + } +#endif + if(g_macsnd_playing) { + for(j = 0; j < num_samps; j++) { + wptr[j] = g_macsnd_rebuf[sample_num]; + sample_num++; + if(sample_num >= MACSND_REBUF_SIZE) { + sample_num = 0; + } + } + } else { + for(j = 0; j < num_samps; j++) { + wptr[j] = 0; + } + } + } + + g_call_num++; + g_macsnd_rd_pos = sample_num; + + return 0; +} + +int +mac_send_audio(byte *ptr, int in_size) +{ + word32 *wptr, *macptr; + int samps, sample_num; + int i; + + samps = in_size / 4; + wptr = (word32 *)ptr; + sample_num = g_macsnd_wr_pos; + macptr = &(g_macsnd_rebuf[0]); + for(i = 0; i < samps; i++) { + macptr[sample_num] = *wptr++; + sample_num++; + if(sample_num >= MACSND_REBUF_SIZE) { + sample_num = 0; + } + } + + g_macsnd_wr_pos = sample_num; + + return in_size; +} + +AudioObjectPropertyAddress g_aopa = { 0 }; + +void +macsnd_init() +{ + AudioComponentInstance ac_inst; + AudioStreamBasicDescription *str_desc_ptr; + AudioComponentDescription *ac_descr_ptr; + AudioComponent ac; + AudioDeviceID device; + OSStatus result; + UInt32 size; + + g_macsnd_rd_pos = 0; + g_macsnd_wr_pos = 0; + mac_printf("macsnd_init called\n"); + + g_aopa.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + g_aopa.mScope = kAudioObjectPropertyScopeGlobal; + g_aopa.mElement = kAudioObjectPropertyElementMain; + + size = 4; + result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &g_aopa, + 0, 0, &size, &device); + if(result != 0) { + printf("AudioObjectGetPropertData on DefaultOutputDevice:%d\n", + result); + return; + } + mac_printf("Audio Device number: %d\n", device); + + str_desc_ptr = calloc(1, sizeof(AudioStreamBasicDescription)); + str_desc_ptr->mFormatID = kAudioFormatLinearPCM; + str_desc_ptr->mFormatFlags = kLinearPCMFormatFlagIsPacked | + kLinearPCMFormatFlagIsSignedInteger; + str_desc_ptr->mChannelsPerFrame = 2; + str_desc_ptr->mSampleRate = g_preferred_rate; + str_desc_ptr->mFramesPerPacket = 1; + str_desc_ptr->mBitsPerChannel = 16; // 16-bit samples + str_desc_ptr->mBytesPerFrame = (16 * 2) / 8; + str_desc_ptr->mBytesPerPacket = str_desc_ptr->mBytesPerFrame; + + ac_descr_ptr = calloc(1, sizeof(AudioComponentDescription)); + ac_descr_ptr->componentType = kAudioUnitType_Output; + ac_descr_ptr->componentManufacturer = kAudioUnitManufacturer_Apple; + ac_descr_ptr->componentSubType = kAudioUnitSubType_DefaultOutput; + ac = AudioComponentFindNext(0, ac_descr_ptr); + mac_printf("AudioComponentFindNext ret: %p\n", ac); + if(ac == 0) { + exit(1); + } + + result = AudioComponentInstanceNew(ac, &ac_inst); + mac_printf("AudioComponentInstanceNew ret:%d\n", result); + if(result != 0) { + exit(1); + } + + result = AudioUnitSetProperty(ac_inst, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &device, + sizeof(device)); + mac_printf("AudioUnitSetProperty CurrentDevice ret: %d\n", result); + if(result != 0) { + exit(1); + } + + result = AudioUnitSetProperty(ac_inst, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, str_desc_ptr, + sizeof(*str_desc_ptr)); + mac_printf("AudioUnitSetProperty StreamFormat ret: %d\n", result); + if(result != 0) { + exit(1); + } + + g_callback_struct.inputProc = audio_callback; + g_callback_struct.inputProcRefCon = (void *)1; + result = AudioUnitSetProperty(ac_inst, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, &g_callback_struct, + sizeof(g_callback_struct)); + + mac_printf("AudioUnitSetProperty SetRenderCallback ret: %d\n", result); + if(result != 0) { + exit(1); + } + + result = AudioUnitInitialize(ac_inst); + mac_printf("AudioUnitInitialize ret: %d\n", result); + if(result != 0) { + exit(1); + } + + result = AudioOutputUnitStart(ac_inst); + mac_printf("AudioOutputUnitStart ret: %d\n", result); + if(result != 0) { + exit(1); + } + + sound_set_audio_rate(g_preferred_rate); + + mac_printf("End of child_sound_init_mac\n"); + fflush(stdout); +} + diff --git a/gsplus/src/mockingboard.c b/gsplus/src/mockingboard.c new file mode 100644 index 0000000..78e3758 --- /dev/null +++ b/gsplus/src/mockingboard.c @@ -0,0 +1,665 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include "defc.h" +#include "sound.h" + +// Mockingboard contains two pairs of a 6522/AY-8913, where the 6522 interfaces +// the AY-8913 (which makes the sounds) to the Apple II. Each AY-8913 +// contains 3 channels of sound: A,B,C. Model each pair separately. +// The AY-8913 has 16 registers. The documentation numbers them using octal! +// The AY8913 is accessed using ORB of the 6522 as control: 0 = Reset, 4=Idle +// 5=Reg read; 6=Write reg; 7=Latch reg address + +extern Mockingboard g_mockingboard; +dword64 g_mockingboard_last_int_dusec = 0ULL; +dword64 g_mockingboard_event_int_dusec = 0ULL; // 0 -> no event pending + +extern int g_irq_pending; +extern double g_dsamps_per_dfcyc; + +void +mock_ay8913_reset(int pair_num, dword64 dfcyc) +{ + Ay8913 *ay8913ptr; + int i; + + if(dfcyc) { + // Avoid unused parameter warning + } + ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); + ay8913ptr->reg_addr_latch = 16; // out-of-range, mb-audit1.3 + for(i = 0; i < 16; i++) { + ay8913ptr->regs[i] = 0; + } + for(i = 0; i < 3; i++) { + ay8913ptr->toggle_tone[i] = 0; + ay8913ptr->tone_samp[i] = 0; + } + ay8913ptr->noise_val = 0x12345678; + ay8913ptr->noise_samp = 0; + ay8913ptr->env_dsamp = 0; +} + +void +mockingboard_reset(dword64 dfcyc) +{ + word32 timer1_latch; + + timer1_latch = g_mockingboard.pair[0].mos6522.timer1_latch; + memset(&g_mockingboard, 0, sizeof(g_mockingboard)); + + g_mockingboard_last_int_dusec = (dfcyc >> 16) << 16; + if(g_mockingboard_event_int_dusec != 0) { + (void)remove_event_mockingboard(); + } + g_mockingboard_event_int_dusec = 0; + printf("At reset, timer1_latch: %08x\n", timer1_latch); + if((timer1_latch & 0xffff) == 0x234) { // MB-audit + g_mockingboard.pair[0].mos6522.timer1_latch = timer1_latch; + } else { + g_mockingboard.pair[0].mos6522.timer1_latch = 0xff00; + } + g_mockingboard.pair[0].mos6522.timer1_counter = 0x2ff00; + g_mockingboard.pair[0].mos6522.timer2_counter = 0x2ff00; + g_mockingboard.pair[1].mos6522.timer1_latch = 0xff00; + g_mockingboard.pair[1].mos6522.timer1_counter = 0x2ff00; + g_mockingboard.pair[1].mos6522.timer2_counter = 0x2ff00; + + mock_ay8913_reset(0, dfcyc); + mock_ay8913_reset(1, dfcyc); +} + +void +mock_show_pair(int pair_num, dword64 dfcyc, const char *str) +{ + Mos6522 *mos6522ptr; + + mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); + printf("Mock %d %s, t1_lat:%05x, t1_c:%05x, t2_l:%05x t2_c:%05x, ifr:" + "%02x, acr:%02x, ier:%02x\n", pair_num, str, + mos6522ptr->timer1_latch, mos6522ptr->timer1_counter, + mos6522ptr->timer2_latch, mos6522ptr->timer2_counter, + mos6522ptr->ifr, mos6522ptr->acr, mos6522ptr->ier); + printf(" dfcyc:%016llx, event_int:%lld\n", dfcyc, + g_mockingboard_event_int_dusec); + +} + +// Timers work as follows: if written with '10' in cycle 0, on cycle 1 the +// counters loads with '10', and counts down to 9 on cycle 2...down to 1 +// on cycle 10, then 0 on cycle 11, and then signals an interrupt on cycle 12 +// To handle this, timers are "read value + 1" so that timer==0 means the +// timer should interrupt right now. So to write '10' to a timer, the code +// needs to write 10+2=12 to the variable, so that on the next cycle, it is +// 11, which will be returned as 10 to software reading the reg. + +void +mock_update_timers(int doit, dword64 dfcyc) +{ + Mos6522 *mos6522ptr; + dword64 dusec, ddiff, dleft, timer1_int_dusec, timer2_int_dusec; + dword64 closest_int_dusec, event_int_dusec; + word32 timer_val, ier, timer_eff, timer_latch, log_ifr; + int i; + + dbg_log_info(dfcyc, doit, g_mockingboard.pair[0].mos6522.ifr | + (g_mockingboard.pair[0].mos6522.ier << 8), 0xc0); + + dusec = dfcyc >> 16; + ddiff = dusec - g_mockingboard_last_int_dusec; + if(!doit && (dusec <= g_mockingboard_last_int_dusec)) { + return; // Nothing more to do + } + + // printf("mock_update_timers at %lf %016llx, ddiff:%llx\n", dfcyc, + // dusec, ddiff); + // Update timers by ddiff integer cycles, calculate next event time + g_mockingboard_last_int_dusec = dusec; + closest_int_dusec = 0; + for(i = 0; i < 2; i++) { // pair_num + mos6522ptr = &(g_mockingboard.pair[i].mos6522); + timer1_int_dusec = 0; + timer_val = mos6522ptr->timer1_counter; + ier = mos6522ptr->ier; + timer_eff = (timer_val & 0x1ffff); + dleft = ddiff; + timer_latch = mos6522ptr->timer1_latch + 2; + dbg_log_info(dfcyc, (word32)dleft, timer_val, 0xcb); + dbg_log_info(dfcyc, ier, timer_latch, 0xcb); + if(dleft < timer_eff) { + // Move ahead only a little, no triggering + timer_val = timer_val - (word32)dleft; + if(ddiff) { + // printf("New timer1_val:%05x, dleft:%08llx\n", + // timer_val, dleft); + } + if(((mos6522ptr->ifr & 0x40) == 0) && (ier & 0x40)) { + // IFR not set yet, prepare an event + timer1_int_dusec = dusec + + (timer_val & 0x1ffff); + dbg_log_info(dfcyc, (word32)timer1_int_dusec, + timer_val, 0xcd); + // printf("t1_int_dusec: %016llx\n", + // timer1_int_dusec); + } + } else { + // Timer1 has triggered now (maybe rolled over more + // than once). + log_ifr = 0; + if((timer_val & 0x20000) == 0) { + // Either free-running, or not one-shot already + // triggered + // Set interrupt: Ensure IFR | 0x40 is set + mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, i, + mos6522ptr->ifr | 0x40, ier); + log_ifr = 1; + } + dleft -= timer_eff; + if(dleft >= timer_latch) { + // It's rolled over several times, remove those + dleft = dleft % timer_latch; + dbg_log_info(dfcyc, (word32)dleft, timer_latch, + 0xcc); + } + if(dleft == 0) { + dleft = timer_latch; + } + timer_val = (timer_latch - dleft) & 0x1ffff; + if((mos6522ptr->acr & 0x40) == 0) { + // ACR6=0: One shot mode, mark it as triggered + timer_val |= 0x20000; + } + if(log_ifr) { + dbg_log_info(dfcyc, + mos6522ptr->ifr | (ier << 8), + timer_val, 0xc3); + } + } + +#if 0 + printf("%016llx ch%d timer1 was %05x, now %05x\n", dfcyc, i, + mos6522ptr->timer1_counter, timer_val); +#endif + + mos6522ptr->timer1_counter = timer_val; + dbg_log_info(dfcyc, timer_val, timer_latch, 0xce); + + // Handle timer2 + timer2_int_dusec = 0; + timer_val = mos6522ptr->timer2_counter; + timer_eff = timer_val & 0x1ffff; + dleft = ddiff; + if(mos6522ptr->acr & 0x20) { + // Count pulses mode. Just don't count + dleft = 0; + } + if(dleft < timer_eff) { + // Move ahead only a little, no triggering + timer_val = timer_val - (word32)dleft; + if(((mos6522ptr->ifr & 0x20) == 0) && (ier & 0x20)) { + // IFR not set yet, prepare an event + timer2_int_dusec = dusec + + (timer_val & 0x1ffff); + //printf("t2_int_dusec: %016llx\n", + // timer1_int_dusec); + } + } else if(timer_val & 0x20000) { + // And already triggered once, just update count + timer_val = ((timer_eff - dleft) & 0xffff) | 0x20000; + } else { + // Has not triggered once yet, but it will now + mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, i, + mos6522ptr->ifr | 0x20, ier); + timer_val = ((timer_val - dleft) & 0xffff) | 0x20000; + dbg_log_info(dfcyc, mos6522ptr->ifr | (ier << 8), + timer_val, 0xc4); + } + + // printf("ch%d timer2 was %05x, now %05x\n", i, + // mos6522ptr->timer2_counter, timer_val); + + mos6522ptr->timer2_counter = timer_val; + + if(timer1_int_dusec && timer2_int_dusec) { + timer1_int_dusec = MY_MIN(timer1_int_dusec, + timer2_int_dusec); + } + if(timer1_int_dusec) { + if(closest_int_dusec) { + closest_int_dusec = MY_MIN(closest_int_dusec, + timer1_int_dusec); + } else { + closest_int_dusec = timer1_int_dusec; + } + } + } + + event_int_dusec = g_mockingboard_event_int_dusec; + if(closest_int_dusec) { + // See if this is sooner than the current pending event + // printf("closest_int_dusec: %016llx, event_int:%016llx\n", + // closest_int_dusec, event_int_dusec); + doit = 0; + if(event_int_dusec && (closest_int_dusec < event_int_dusec)) { + // There was a pending event. Discard it + // printf("Call remove_event_mockingboard\n"); + remove_event_mockingboard(); + doit = 1; + } + if(!event_int_dusec || doit) { + //printf("Call add_event_mockingboard: %016llx %lld\n", + // closest_int_dusec, closest_int_dusec); + add_event_mockingboard(closest_int_dusec << 16); + g_mockingboard_event_int_dusec = closest_int_dusec; + dbg_log_info(dfcyc, + (word32)(closest_int_dusec - (dfcyc >> 16)), + 0, 0xc1); + } + } +} + +void +mockingboard_event(dword64 dfcyc) +{ + // Received an event--we believe we may need to set an IRQ now. + // Event was already removed from the event queue + // printf("Mockingboard_event received at %016llx\n", dfcyc); + dbg_log_info(dfcyc, 0, 0, 0xc2); + g_mockingboard_event_int_dusec = 0; + mock_update_timers(1, dfcyc); +} + +word32 +mockingboard_read(word32 loc, dword64 dfcyc) +{ + int pair_num; + + // printf("mockingboard read: %04x\n", loc); + pair_num = (loc >> 7) & 1; // 0 or 1 + return mock_6522_read(pair_num, loc & 0xf, dfcyc); +} + +void +mockingboard_write(word32 loc, word32 val, dword64 dfcyc) +{ + int pair_num; + + // printf("mockingboard write: %04x=%02x\n", loc, val); + pair_num = (loc >> 7) & 1; // 0 or 1 + mock_6522_write(pair_num, loc & 0xf, val, dfcyc); +} + +word32 +mock_6522_read(int pair_num, word32 loc, dword64 dfcyc) +{ + Mos6522 *mos6522ptr; + word32 val; + + // Read from 6522 #pair_num loc (0-15) + mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); + val = 0; + switch(loc) { + case 0x0: // ORB/IRB + // Connected to AY8913 { RESET, BDIR, BC1 } + val = mos6522ptr->orb; + // There are no outputs from AY8913 to the 6522 B Port + break; + case 0x1: // ORA + case 0xf: // ORA, no handshake + val = mos6522ptr->ora; + break; + case 0x2: // DDRB + val = mos6522ptr->ddrb; + break; + case 0x3: // DDRA + val = mos6522ptr->ddra; + break; + case 0x4: // T1C-L (timer[0]) + mock_update_timers(1, dfcyc); + val = (mos6522ptr->timer1_counter - 1) & 0xff; + mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, + mos6522ptr->ifr & (~0x40), mos6522ptr->ier); + // Clear Bit 6 + mock_update_timers(1, dfcyc); // Prepare another int (maybe) + dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc5); + break; + case 0x5: // T1C-H + mock_update_timers(1, dfcyc); + val = ((mos6522ptr->timer1_counter - 1) >> 8) & 0xff; + break; + case 0x6: // T1L-L + val = mos6522ptr->timer1_latch & 0xff; + break; + case 0x7: // T1L-H + val = (mos6522ptr->timer1_latch >> 8) & 0xff; + break; + case 0x8: // T2C-L + mock_update_timers(1, dfcyc); + val = (mos6522ptr->timer2_counter - 1) & 0xff; + mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, + mos6522ptr->ifr & (~0x20), mos6522ptr->ier); + // Clear Bit 5 + mock_update_timers(1, dfcyc); // Prepare another int (maybe) + dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc6); + break; + case 0x9: // T2C-H + mock_update_timers(1, dfcyc); + val = ((mos6522ptr->timer2_counter - 1) >> 8) & 0xff; + break; + case 0xa: // SR + val = mos6522ptr->sr; + //halt_printf("Reading SR %d %02x\n", pair_num, val); + break; + case 0xb: // ACR + val = mos6522ptr->acr; + break; + case 0xc: // PCR + val = mos6522ptr->pcr; + break; + case 0xd: // IFR + mock_update_timers(1, dfcyc); + val = mos6522ptr->ifr; + break; + case 0xe: // IER + val = mos6522ptr->ier | 0x80; // Despite MOS6522 + break; // datasheet, bit 7 = 1 + } + // printf("6522 %d loc:%x ret:%02x\n", pair_num, loc, val); + return val; +} + +void +mock_6522_write(int pair_num, word32 loc, word32 val, dword64 dfcyc) +{ + Mos6522 *mos6522ptr; + word32 ora, orb, new_val, mask; + + // Write to 6522 #num6522 loc (0-15) + + // printf("6522 %d loc:%x write:%02x\n", pair_num, loc, val); + + mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); + switch(loc) { + case 0x0: // ORB + mask = mos6522ptr->ddrb; + orb = mos6522ptr->orb; + new_val = (val & mask) | (orb & (~mask)); + if(orb != new_val) { + mock_ay8913_control_update(pair_num, new_val, orb, + dfcyc); + } + mos6522ptr->orb = new_val; + break; + case 0x1: // ORA + case 0xf: // ORA, no handshake + mask = mos6522ptr->ddra; + ora = mos6522ptr->ora; + new_val = (val & mask) | (ora & (~mask)); + mos6522ptr->ora = new_val; + break; + case 0x2: // DDRB + orb = mos6522ptr->orb; + new_val = (orb & val) | (orb & (~val)); + if(orb != new_val) { + mock_ay8913_control_update(pair_num, new_val, orb, + dfcyc); + } + mos6522ptr->orb = new_val; + mos6522ptr->ddrb = val; + return; + case 0x3: // DDRA + ora = mos6522ptr->ora; + mos6522ptr->ora = (ora & val) | (ora & (~val)); + mos6522ptr->ddra = val; + return; + case 0x4: // T1C-L + mock_update_timers(0, dfcyc); + mos6522ptr->timer1_latch = + (mos6522ptr->timer1_latch & 0x1ff00) | val; + // printf("Set T1C-L, timer1_latch=%05x\n", + // mos6522ptr->timer1_latch); + break; + case 0x5: // T1C-H + mock_update_timers(1, dfcyc); + val = (mos6522ptr->timer1_latch & 0xff) | (val << 8); + mos6522ptr->timer1_latch = val; + mos6522ptr->timer1_counter = val + 2; + // The actual timer1_counter update happens next cycle, + // so we want val+1, plus another 1 + // printf("Set T1C-H, timer1_latch=%05x\n", + // mos6522ptr->timer1_latch); + mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, + mos6522ptr->ifr & (~0x40), mos6522ptr->ier); + // Clear Bit 6 + mock_update_timers(1, dfcyc); + dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc7); + break; + case 0x6: // T1L-L + mock_update_timers(0, dfcyc); + mos6522ptr->timer1_latch = + (mos6522ptr->timer1_latch & 0x1ff00) | val; + break; + case 0x7: // T1L-H + mock_update_timers(1, dfcyc); + val = (mos6522ptr->timer1_latch & 0xff) | (val << 8); + mos6522ptr->timer1_latch = val; + mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, + mos6522ptr->ifr & (~0x40), mos6522ptr->ier); + // Clear Bit 6 + mock_update_timers(1, dfcyc); + // mock_show_pair(pair_num, dfcyc, "Wrote T1L-H"); + dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc8); + break; + case 0x8: // T2C-L + mos6522ptr->timer2_latch = (mos6522ptr->timer2_latch & 0xff00) | + val; + break; + case 0x9: // T2C-H + mock_update_timers(1, dfcyc); + val = (mos6522ptr->timer2_latch & 0xff) | (val << 8); + mos6522ptr->timer2_latch = val; + mos6522ptr->timer2_counter = val + 2; + mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, + mos6522ptr->ifr & (~0x20), mos6522ptr->ier); + // Clear bit 5 + mock_update_timers(1, dfcyc); + break; + case 0xa: // SR + mos6522ptr->sr = val; + halt_printf("Wrote SR reg: %d %02x\n", pair_num, val); + break; + case 0xb: // ACR + mock_update_timers(0, dfcyc); + mos6522ptr->acr = val; + mock_update_timers(1, dfcyc); + break; + case 0xc: // PCR + mos6522ptr->pcr = val; + break; + case 0xd: // IFR + mock_update_timers(1, dfcyc); + mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, + mos6522ptr->ifr & (~val), mos6522ptr->ier); + mock_update_timers(1, dfcyc); + dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xc9); + break; + case 0xe: // IER + // Recalculate effective IFR with new IER + mock_update_timers(1, dfcyc); + if(val & 0x80) { // Set EIR bits + val = mos6522ptr->ier | val; + } else { // Clear EIR bits + val = mos6522ptr->ier & (~val); + } + val = val & 0x7f; + mos6522ptr->ier = val; + mos6522ptr->ifr = mock_6522_new_ifr(dfcyc, pair_num, + mos6522ptr->ifr, val); + mock_update_timers(1, dfcyc); + // mock_show_pair(pair_num, dfcyc, "Wrote IER"); + dbg_log_info(dfcyc, mos6522ptr->ifr, val, 0xca); + break; + } +} + +word32 +mock_6522_new_ifr(dword64 dfcyc, int pair_num, word32 ifr, word32 ier) +{ + word32 irq_mask; + + // Determine if there are any interrupts pending now + irq_mask = IRQ_PENDING_MOCKINGBOARDA << pair_num; + if((ifr & ier & 0x7f) == 0) { + // No IRQ pending anymore + ifr = ifr & 0x7f; // Clear bit 7 + if(g_irq_pending & irq_mask) { + // printf("MOCK INT OFF\n"); + } + remove_irq(irq_mask); + dbg_log_info(dfcyc, (ier << 8) | ifr, g_irq_pending, 0xcf); + } else { + // IRQ is pending + ifr = ifr | 0x80; // Set bit 7 + if(!(g_irq_pending & irq_mask)) { + // printf("MOCK INT ON\n"); + } + add_irq(irq_mask); + dbg_log_info(dfcyc, (ier << 8) | ifr, g_irq_pending, 0xcf); + } + return ifr; +} + +void +mock_ay8913_reg_read(int pair_num) +{ + Mos6522 *mos6522ptr; + Ay8913 *ay8913ptr; + word32 reg_addr_latch, mask, val, ora; + + mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); + ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); + reg_addr_latch = ay8913ptr->reg_addr_latch; + val = 0; + if(reg_addr_latch < 16) { + val = ay8913ptr->regs[reg_addr_latch]; + } + // ORA at 6522 is merge of ORA using DDRA + mask = mos6522ptr->ddra; + ora = mos6522ptr->ora; + mos6522ptr->ora = (ora & mask) | (val & (~mask)); +} + +word32 g_mock_channel_regs[3] = { + 0x39c3, // channel A: regs 0,1,6,7,8,11,12,13 + 0x3acc, // channel B: regs 2,3,6,7,9,11,12,13 + 0x3cf0 // channel C: regs 4,5,6,7,10,11,12,13 +}; + +void +mock_ay8913_reg_write(int pair_num, dword64 dfcyc) +{ + Mos6522 *mos6522ptr; + Ay8913 *ay8913ptr; + double dsamps; + word32 reg_addr_latch, ora, mask, rmask, do_print; + int i; + + mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); + ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); + reg_addr_latch = ay8913ptr->reg_addr_latch; + ora = mos6522ptr->ora; + dsamps = dfcyc * g_dsamps_per_dfcyc; + if(reg_addr_latch < 16) { + mask = (g_mockingboard.disable_mask >> (3*pair_num)) & 7; + rmask = 0; + do_print = 0; + for(i = 0; i < 3; i++) { + if(((mask >> i) & 1) == 0) { + rmask |= g_mock_channel_regs[i]; + } + } + do_print = (rmask >> reg_addr_latch) & 1; + if((ora != ay8913ptr->regs[reg_addr_latch]) || + (reg_addr_latch == 13)) { + // New value, or writing to Envelope control + do_print = 0; + if(do_print) { + printf("%.2lf %016llx mock pair%d reg[%d]=" + "%02x. [2,3]=%02x_%02x [67]=%02x,%02x, " + "[9]=%02x, [12,11]=%02x_%02x [13]=" + "%02x\n", dsamps, dfcyc, pair_num, + reg_addr_latch, ora, + ay8913ptr->regs[3], ay8913ptr->regs[2], + ay8913ptr->regs[6], ay8913ptr->regs[7], + ay8913ptr->regs[9], ay8913ptr->regs[12], + ay8913ptr->regs[11], + ay8913ptr->regs[13]); + } + sound_play(dfcyc); + } + ay8913ptr->regs[reg_addr_latch] = ora; + if(reg_addr_latch == 13) { // Envelope control + ay8913ptr->env_dsamp &= 0x1fffffffffffULL; + // Clear "hold" in (env_val & (0x80 << 40)) + } + } +} + +void +mock_ay8913_control_update(int pair_num, word32 new_val, word32 prev_val, + dword64 dfcyc) +{ + Mos6522 *mos6522ptr; + Ay8913 *ay8913ptr; + + mos6522ptr = &(g_mockingboard.pair[pair_num].mos6522); + ay8913ptr = &(g_mockingboard.pair[pair_num].ay8913); + // printf("ay8913 %d control now:%02x\n", pair_num, new_val); + + // new_val and prev_val are { reset_l, BDIR, BC1 } + // 4=Idle; 5=Read; 6=Write; 7=Latch_addr + // Latch new address and write data at the time the ctl changes to Idle + // Do read as soon as the ctl indicates to do a read. + + if((new_val & 4) == 0) { + mock_ay8913_reset(pair_num, dfcyc); + return; + } + new_val = new_val & 7; + prev_val = prev_val & 7; + if(prev_val == 7) { // Latch new address, latch it now + ay8913ptr->reg_addr_latch = mos6522ptr->ora; + } else if(prev_val == 6) { // Write data, do it now + mock_ay8913_reg_write(pair_num, dfcyc); + } + if(new_val == 5) { + mock_ay8913_reg_read(pair_num); + } +} + +void +mockingboard_show(int got_num, word32 disable_mask) +{ + int i, j; + + if(got_num) { + g_mockingboard.disable_mask = disable_mask; + } + printf("g_mockingboard.disable_mask:%02x\n", + g_mockingboard.disable_mask); + for(i = 0; i < 2; i++) { + for(j = 0; j < 14; j++) { + printf("Mockingboard pair[%d].reg[%d]=%02x\n", i, j, + g_mockingboard.pair[i].ay8913.regs[j]); + } + } +} + diff --git a/gsplus/src/moremem.c b/gsplus/src/moremem.c new file mode 100644 index 0000000..c4c51e7 --- /dev/null +++ b/gsplus/src/moremem.c @@ -0,0 +1,2274 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2024 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include "defc.h" + +extern char g_kegs_version_str[]; + +extern byte *g_memory_ptr; +extern byte *g_dummy_memory1_ptr; +extern byte *g_slow_memory_ptr; +extern byte *g_rom_fc_ff_ptr; +extern byte *g_rom_cards_ptr; + +extern word32 g_slow_mem_changed[]; + +extern int g_num_breakpoints; +extern Break_point g_break_pts[]; + +extern Page_info page_info_rd_wr[]; + +extern int Verbose; +extern int g_rom_version; +extern int g_user_page2_shadow; +extern Iwm g_iwm; +extern int g_halt_sim; +extern int g_config_control_panel; + + +/* from iwm.c */ +int g_num_shadow_all_banks = 0; + +#define IOR(val) ( (val) ? 0x80 : 0x00 ) + + +extern int g_cur_a2_stat; + +int g_em_emubyte_cnt = 0; +int g_paddle_buttons = 0; +int g_irq_pending = 0; + +word32 g_c023_val = 0; +word32 g_c029_val_some = 0x41; +word32 g_c02b_val = 0x08; +word32 g_c02d_int_crom = 0; +word32 g_c033_data = 0; +word32 g_c034_val = 0; +word32 g_c035_shadow_reg = 0x08; // A bit set inhibits that shadowing + // [6]=Inhibit I/O and LC, [4]=Inhibit Aux hires, [3]=Inh SHR + // [2]=Inh hires pg 2, [1]=Inh hires pg 1, [0]=Inh text pages +word32 g_c036_val_speed = 0x80; // [7]=Fast, [4]=Shadow in all banks, + // [3:0]=slot 7..4 disk motor detect +word32 g_c03ef_doc_ptr = 0; +word32 g_c041_val = 0; /* C041_EN_25SEC_INTS, C041_EN_MOVE_INTS */ +word32 g_c046_val = 0; +word32 g_c05x_annuncs = 0; +word32 g_c068_statereg = 0; // [7]=ALTZP, [6]=PAGE2, [5]=RAMRD, [4]=RAMWRT + // [3]=RDROM, [2]=LCBANK2, [1]=ROMBANK, [0]=INTCXROM + // KEGS special: [8]=PREWRITE_LC, [9]=WRDEFRAM, + // [10]=INTC8ROM +word32 g_c06d_val = 0; +word32 g_c06f_val = 0; +word32 g_zipgs_unlock = 0; +word32 g_zipgs_reg_c059 = 0x5f; + // 7=LC cache dis, 6==5ms paddle del en, 5==5ms ext del en, + // 4==5ms c02e enab, 3==CPS follow enab, 2-0: 111 +word32 g_zipgs_reg_c05a = 0x0f; + // 7:4 = current ZIP speed, 0=100%, 1=93.75%, F=6.25% + // 3:0: always 1111 +word32 g_zipgs_reg_c05b = 0x40; + // 7==1ms clock, 6==cshupd: tag data at c05f updated + // 5==LC cache disable, 4==bd is disabled, 3==delay in effect, + // 2==rombank, 1-0==ram size (00:8K, 01=16K, 10=32K, 11=64K) +word32 g_zipgs_reg_c05c = 0x00; + // 7:1==slot delay enable (for 52-54ms), 0==speaker 5ms delay + +word32 g_c06c_latched_cyc = 0; + +#define SLINKY_RAM_SIZE 0x100000 /* 1MB */ +word32 g_slinky_addr = 0; +byte g_slinky_ram[SLINKY_RAM_SIZE]; + + +#define EMUSTATE(a) { #a, &a } + +Emustate_intlist g_emustate_intlist[] = { + // EMUSTATE(g_cur_a2_stat), + // EMUSTATE(g_paddle_buttons), + + // EMUSTATE(g_em_emubyte_cnt), + // EMUSTATE(g_irq_pending), + EMUSTATE(g_c023_val), + EMUSTATE(g_c029_val_some), + EMUSTATE(g_c02b_val), + EMUSTATE(g_c02d_int_crom), + EMUSTATE(g_c033_data), + EMUSTATE(g_c034_val), + EMUSTATE(g_c035_shadow_reg), + EMUSTATE(g_c036_val_speed), + EMUSTATE(g_c041_val), + EMUSTATE(g_c046_val), + EMUSTATE(g_c05x_annuncs), + EMUSTATE(g_c068_statereg), + EMUSTATE(g_zipgs_unlock), + EMUSTATE(g_zipgs_reg_c059), + EMUSTATE(g_zipgs_reg_c05a), + EMUSTATE(g_zipgs_reg_c05b), + EMUSTATE(g_zipgs_reg_c05c), + { 0, 0, } +}; + +extern dword64 g_paddle_trig_dfcyc; +extern dword64 g_last_vbl_dfcyc; + +Emustate_dword64list g_emustate_dword64list[] = { + EMUSTATE(g_paddle_trig_dfcyc), + EMUSTATE(g_last_vbl_dfcyc), + { 0, 0, } +}; + +extern word32 g_mem_size_total; + +Emustate_word32list g_emustate_word32list[] = { + EMUSTATE(g_mem_size_total), + EMUSTATE(g_c03ef_doc_ptr), + { 0, 0, } +}; + + +#define UNIMPL_READ \ + halt_printf("UNIMP READ to addr %08x\n", loc); \ + return 0; + +#define UNIMPL_WRITE \ + halt_printf("UNIMP WRITE to addr %08x, val: %04x\n", loc, val); \ + return; + +void +fixup_brks() +{ + word32 page, tmp, tmp2, end_page; + Pg_info val; + int is_wr_only, num; + int i; + + num = g_num_breakpoints; + for(i = 0; i < num; i++) { + page = (g_break_pts[i].start_addr >> 8) & 0xffff; + end_page = (g_break_pts[i].end_addr >> 8) & 0xffff; + is_wr_only = (g_break_pts[i].start_addr >> 24) & 1; + while(page <= end_page) { + if(!is_wr_only) { + val = GET_PAGE_INFO_RD(page); + tmp = PTR2WORD(val) & 0xff; + tmp2 = tmp | BANK_IO_TMP | BANK_BREAK; + SET_PAGE_INFO_RD(page, val - tmp + tmp2); + } + val = GET_PAGE_INFO_WR(page); + tmp = PTR2WORD(val) & 0xff; + tmp2 = tmp | BANK_IO_TMP | BANK_BREAK; + SET_PAGE_INFO_WR(page, val - tmp + tmp2); + page++; + } + } +} + +void +fixup_hires_on() +{ + if((g_cur_a2_stat & ALL_STAT_ST80) == 0) { + return; + } + + fixup_bank0_2000_4000(); + fixup_brks(); +} + +void +fixup_bank0_2000_4000() +{ + byte *mem0rd; + byte *mem0wr; + word32 start_page; + int i; + + // Do banks $00 and $E0 + + for(i = 0; i < 2; i++) { + mem0rd = &(g_memory_ptr[0x2000]); + start_page = 0x20; + if(i) { + start_page += 0xe000; // Bank $E0 + mem0rd = &(g_slow_memory_ptr[0x2000]); + } + mem0wr = mem0rd; + if((g_cur_a2_stat & ALL_STAT_ST80) && + (g_cur_a2_stat & ALL_STAT_HIRES)) { + if(g_cur_a2_stat & ALL_STAT_PAGE2) { + mem0rd += 0x10000; + mem0wr += 0x10000; + if(((g_c035_shadow_reg & 0x12) == 0) || + ((g_c035_shadow_reg & 0x8) == 0) || i) { + mem0wr += BANK_SHADOW2; + } + } else if(((g_c035_shadow_reg & 0x02) == 0) || i) { + mem0wr += BANK_SHADOW; + } + } else { + if(RAMRD) { + mem0rd += 0x10000; + } + if(RAMWRT) { + mem0wr += 0x10000; + if(((g_c035_shadow_reg & 0x12) == 0) || + ((g_c035_shadow_reg & 0x8) == 0) || i) { + mem0wr += BANK_SHADOW2; + } + } else if(((g_c035_shadow_reg & 0x02) == 0) || i) { + mem0wr += BANK_SHADOW; + } + } + fixup_any_bank_any_page(start_page, 0x20, mem0rd, mem0wr); + } +} + +void +fixup_bank0_0400_0800() +{ + byte *mem0rd; + byte *mem0wr; + word32 start_page, shadow; + int i; + + for(i = 0; i < 2; i++) { + mem0rd = &(g_memory_ptr[0x400]); + start_page = 4; + if(i) { + start_page += 0xe000; // Bank $E0 + mem0rd = &(g_slow_memory_ptr[0x400]); + } + mem0wr = mem0rd; + shadow = BANK_SHADOW; + if(g_cur_a2_stat & ALL_STAT_ST80) { + if(g_cur_a2_stat & ALL_STAT_PAGE2) { + shadow = BANK_SHADOW2; + mem0rd += 0x10000; + mem0wr += 0x10000; + } + } else { + if(RAMWRT) { + shadow = BANK_SHADOW2; + mem0wr += 0x10000; + } + if(RAMRD) { + mem0rd += 0x10000; + } + } + if(((g_c035_shadow_reg & 0x01) == 0) || i) { + mem0wr += shadow; + } + + fixup_any_bank_any_page(start_page, 4, mem0rd, mem0wr); + } +} + +void +fixup_any_bank_any_page(word32 start_page, int num_pages, byte *mem0rd, + byte *mem0wr) +{ + int i; + + for(i = 0; i < num_pages; i++) { + SET_PAGE_INFO_RD(i + start_page, mem0rd); + mem0rd += 0x100; + } + + for(i = 0; i < num_pages; i++) { + SET_PAGE_INFO_WR(i + start_page, mem0wr); + mem0wr += 0x100; + } + +} + +void +fixup_intcx() +{ + byte *rom10000; + byte *rom_inc; + word32 intcx, mask; + int no_io_shadow, off, start_k; + int j, k; + + rom10000 = &(g_rom_fc_ff_ptr[0x30000]); + + no_io_shadow = (g_c035_shadow_reg & 0x40); + + start_k = 0; + if(no_io_shadow) { + /* if not shadowing, banks 0 and 1 are not affected by intcx */ + start_k = 2; + } + + intcx = (g_c068_statereg & 1); // set means use internal rom + // printf("fixup_intcx, intcx:%d, no_io:%d, c02d:%02x\n", intcx, + // no_io_shadow, g_c02d_int_crom); + for(k = start_k; k < 4; k++) { + off = k; + if(k >= 2) { + off += (0xe0 - 2); + } + /* step off through 0x00, 0x01, 0xe0, 0xe1 */ + + off = off << 8; // Now 0x0000, 0x0100, 0xe000, 0xe100 + SET_PAGE_INFO_RD(0xc0 + off, SET_BANK_IO); + + for(j = 0xc1; j < 0xc8; j++) { + mask = 1 << (j & 0xf); + rom_inc = SET_BANK_IO; + if(((g_c02d_int_crom & mask) == 0) || intcx) { + rom_inc = rom10000 + (j << 8); + } else { + // User-slot rom + rom_inc = &(g_rom_cards_ptr[0]) + + ((j - 0xc0) << 8); + if(j == 0xc4) { // Mockingboard + rom_inc = SET_BANK_IO; + } + } + if((j == 0xc3) && !(g_c068_statereg & 0x400)) { + // If INTC8ROM not set, we need to + // watch for reads from C3xx to set it + rom_inc = SET_BANK_IO; + } + SET_PAGE_INFO_RD(j + off, rom_inc); + } + for(j = 0xc8; j < 0xd0; j++) { + /* c800 - cfff */ + if(intcx || (g_c068_statereg & 0x400)) { + // INTCXROM or INTC8ROM is set + rom_inc = rom10000 + (j << 8); + if((j == 0xcf) && (g_c068_statereg & 0x400)) { + rom_inc = SET_BANK_IO; + } + } else { // c800 space not necessarily mapped + rom_inc = SET_BANK_IO; + } + SET_PAGE_INFO_RD(j + off, rom_inc); + } + for(j = 0xc0; j < 0xd0; j++) { + SET_PAGE_INFO_WR(j + off, SET_BANK_IO); + } + } + + fixup_brks(); +} + +void +fixup_st80col(dword64 dfcyc) +{ + int cur_a2_stat; + + cur_a2_stat = g_cur_a2_stat; + + fixup_bank0_0400_0800(); + + if(cur_a2_stat & ALL_STAT_HIRES) { + /* fixup no matter what PAGE2 since PAGE2 and RAMRD/WR */ + /* can work against each other */ + fixup_bank0_2000_4000(); + } + + if(cur_a2_stat & ALL_STAT_PAGE2) { + change_display_mode(dfcyc); + } + + fixup_brks(); +} + +void +fixup_altzp() +{ + byte *mem0rd; + word32 start_page, altzp; + int i; + + altzp = ALTZP; + for(i = 0; i < 2; i++) { + start_page = 0; + mem0rd = &(g_memory_ptr[0]); + if(i) { + start_page = 0xe000; + mem0rd = &(g_slow_memory_ptr[0]); + } + if(altzp) { + mem0rd += 0x10000; + } + fixup_any_bank_any_page(start_page, 2, mem0rd, mem0rd); + } + + /* No need for fixup_brks since called from set_statereg() */ +} + +void +fixup_page2(dword64 dfcyc) +{ + if((g_cur_a2_stat & ALL_STAT_ST80)) { + fixup_bank0_0400_0800(); + if((g_cur_a2_stat & ALL_STAT_HIRES)) { + fixup_bank0_2000_4000(); + } + } else { + change_display_mode(dfcyc); + } +} + +void +fixup_ramrd() +{ + byte *mem0rd; + word32 start_page, cur_a2_stat; + int i, j; + + cur_a2_stat = g_cur_a2_stat; + + if((cur_a2_stat & ALL_STAT_ST80) == 0) { + fixup_bank0_0400_0800(); + } + if( ((cur_a2_stat & ALL_STAT_ST80) == 0) || + ((cur_a2_stat & ALL_STAT_HIRES) == 0) ) { + fixup_bank0_2000_4000(); + } + + for(i = 0; i < 2; i++) { + start_page = 0; + mem0rd = &(g_memory_ptr[0x0000]); + if(i) { + start_page = 0xe000; + mem0rd = &(g_slow_memory_ptr[0]); + } + + if(RAMRD) { + mem0rd += 0x10000; + } + + SET_PAGE_INFO_RD(start_page + 2, mem0rd + 0x200); + SET_PAGE_INFO_RD(start_page + 3, mem0rd + 0x300); + + for(j = 8; j < 0x20; j++) { + SET_PAGE_INFO_RD(start_page + j, mem0rd + j*0x100); + } + + for(j = 0x40; j < 0xc0; j++) { + SET_PAGE_INFO_RD(start_page + j, mem0rd + j*0x100); + } + } + + /* No need for fixup_brks since only called from set_statereg() */ +} + +void +fixup_ramwrt() +{ + byte *mem0wr; + word32 start_page, cur_a2_stat, shadow, ramwrt; + int i, j; + + cur_a2_stat = g_cur_a2_stat; + + if((cur_a2_stat & ALL_STAT_ST80) == 0) { + fixup_bank0_0400_0800(); + } + if( ((cur_a2_stat & ALL_STAT_ST80) == 0) || + ((cur_a2_stat & ALL_STAT_HIRES) == 0) ) { + fixup_bank0_2000_4000(); + } + + for(i = 0; i < 2; i++) { + start_page = 0; + mem0wr = &(g_memory_ptr[0x0000]); + if(i) { + start_page = 0xe000; + mem0wr = &(g_slow_memory_ptr[0]); + } + + ramwrt = RAMWRT; + if(ramwrt) { + mem0wr += 0x10000; + } + + SET_PAGE_INFO_WR(start_page + 2, mem0wr + 0x200); + SET_PAGE_INFO_WR(start_page + 3, mem0wr + 0x300); + + shadow = BANK_SHADOW; + if(ramwrt) { + shadow = BANK_SHADOW2; + } + if( ((g_c035_shadow_reg & 0x20) != 0) || + ((g_rom_version == 1) && !g_user_page2_shadow)) { + if(!i) { + shadow = 0; + } + } + for(j = 8; j < 0x0c; j++) { + SET_PAGE_INFO_WR(start_page + j, + mem0wr + j*0x100 + shadow); + } + + for(j = 0xc; j < 0x20; j++) { + SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100); + } + + shadow = 0; + if(ramwrt) { + if(((g_c035_shadow_reg & 0x14) == 0) || + ((g_c035_shadow_reg & 0x08) == 0) || i) { + shadow = BANK_SHADOW2; + } + } else if(((g_c035_shadow_reg & 0x04) == 0) || i) { + shadow = BANK_SHADOW; + } + for(j = 0x40; j < 0x60; j++) { + SET_PAGE_INFO_WR(start_page + j, + mem0wr + j*0x100 + shadow); + } + + shadow = 0; + if(ramwrt && (((g_c035_shadow_reg & 0x08) == 0) || i)) { + /* shr shadowing */ + shadow = BANK_SHADOW2; + } + for(j = 0x60; j < 0xa0; j++) { + SET_PAGE_INFO_WR(start_page + j, + mem0wr + j*0x100 + shadow); + } + + for(j = 0xa0; j < 0xc0; j++) { + SET_PAGE_INFO_WR(start_page + j, mem0wr + j*0x100); + } + } + + /* No need for fixup_brks() since only called from set_statereg() */ +} + +void +fixup_lc() +{ + byte *mem0rd, *mem0wr; + word32 bank, lcbank2, c08x_wrdefram, rdrom; + int k; + + // Fixup memory from 0xd000 - 0xffff in banks 0, 1, $e0, $e1 + for(k = 0; k < 4; k++) { + bank = k; + mem0rd = &(g_memory_ptr[k << 16]); + if(k >= 2) { + bank += (0xe0 - 2); //k==2->bank=$e0, k==3->bank=$e1 + mem0rd = &(g_slow_memory_ptr[(k & 1) << 16]); + } + /* step bank through 0x00, 0x01, 0xe0, 0xe1 */ + + if(((k & 1) == 0) && ALTZP) { + mem0rd += 0x10000; + } + lcbank2 = LCBANK2; + c08x_wrdefram = g_c068_statereg & 0x200; + rdrom = RDROM; + if((k < 2) && (g_c035_shadow_reg & 0x40)) { + lcbank2 = 0; + c08x_wrdefram = 1; + rdrom = 0; + } + mem0wr = mem0rd; + if((k < 2) && !c08x_wrdefram) { + mem0wr += (BANK_IO_TMP | BANK_IO2_TMP); + } + if((k < 2) && rdrom) { + mem0rd = &(g_rom_fc_ff_ptr[0x30000]); + } + fixup_any_bank_any_page(bank*0x100 + 0xe0, 0x20, + mem0rd + 0xe000, mem0wr + 0xe000); + if(! lcbank2) { // lcbank1, 0xd000 is ram at 0xc000-cfff + if(!rdrom) { + mem0rd -= 0x1000; + } + mem0wr -= 0x1000; + } + fixup_any_bank_any_page(bank*0x100 + 0xd0, 0x10, + mem0rd + 0xd000, mem0wr + 0xd000); + } + + /* No need for fixup_brks() since only called from set_statereg(), */ + /* or from other routines which will handle it */ +} + +void +set_statereg(dword64 dfcyc, word32 val) +{ + word32 xor; + + dbg_log_info(dfcyc, val, g_c068_statereg, 0xc068); + + xor = val ^ g_c068_statereg; + g_c068_statereg = val; + if(xor == 0) { + return; + } + + if(xor & 0x28c) { // WRDEFRAM, ALTZP, RDROM, LCBANK2 + fixup_lc(); + if(xor & 0x80) { /* altzp */ + fixup_altzp(); + } + } + if(xor & 0x40) { // page2 + g_cur_a2_stat = (g_cur_a2_stat & ~ALL_STAT_PAGE2) | + (val & ALL_STAT_PAGE2); + fixup_page2(dfcyc); + } + + if(xor & 0x20) { // RAMRD + fixup_ramrd(); + } + + if(xor & 0x10) { // RAMWRT + fixup_ramwrt(); + } + + if(xor & 0x02) { // ROMBANK + halt_printf("Just set rombank = %d\n", ROMB); + } + + if(xor & 0x401) { // INTC8ROM, INTCX + fixup_intcx(); + } + + if(xor) { + fixup_brks(); + } +} + +void +fixup_shadow_txt1() +{ + byte *mem0wr; + int j; + + fixup_bank0_0400_0800(); + + mem0wr = &(g_memory_ptr[0x10000]); + if((g_c035_shadow_reg & 0x01) == 0) { + mem0wr += BANK_SHADOW2; + } + for(j = 4; j < 8; j++) { + SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); + } +} + +void +fixup_shadow_txt2() +{ + byte *mem0wr; + int shadow; + int j; + + /* bank 0 */ + mem0wr = &(g_memory_ptr[0x00000]); + shadow = BANK_SHADOW; + if(RAMWRT) { + mem0wr += 0x10000; + shadow = BANK_SHADOW2; + } + if(((g_c035_shadow_reg & 0x20) == 0) && + ((g_rom_version != 1) || g_user_page2_shadow)) { + mem0wr += shadow; + } + for(j = 8; j < 0xc; j++) { + SET_PAGE_INFO_WR(j, mem0wr + j*0x100); + } + + /* and bank 1 */ + mem0wr = &(g_memory_ptr[0x10000]); + if(((g_c035_shadow_reg & 0x20) == 0) && + ((g_rom_version != 1) || g_user_page2_shadow)) { + mem0wr += BANK_SHADOW2; + } + for(j = 8; j < 0xc; j++) { + SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); + } +} + +void +fixup_shadow_hires1() +{ + byte *mem0wr; + int j; + + fixup_bank0_2000_4000(); + + /* and bank 1 */ + mem0wr = &(g_memory_ptr[0x10000]); + if((g_c035_shadow_reg & 0x12) == 0 || (g_c035_shadow_reg & 0x8) == 0) { + mem0wr += BANK_SHADOW2; + } + for(j = 0x20; j < 0x40; j++) { + SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); + } +} + +void +fixup_shadow_hires2() +{ + byte *mem0wr; + int j; + + /* bank 0 */ + mem0wr = &(g_memory_ptr[0x00000]); + if(RAMWRT) { + mem0wr += 0x10000; + if((g_c035_shadow_reg & 0x14) == 0 || + (g_c035_shadow_reg & 0x8) == 0) { + mem0wr += BANK_SHADOW2; + } + } else if((g_c035_shadow_reg & 0x04) == 0) { + mem0wr += BANK_SHADOW; + } + for(j = 0x40; j < 0x60; j++) { + SET_PAGE_INFO_WR(j, mem0wr + j*0x100); + } + + /* and bank 1 */ + mem0wr = &(g_memory_ptr[0x10000]); + if((g_c035_shadow_reg & 0x14) == 0 || (g_c035_shadow_reg & 0x8) == 0) { + mem0wr += BANK_SHADOW2; + } + for(j = 0x40; j < 0x60; j++) { + SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); + } +} + +void +fixup_shadow_shr() +{ + byte *mem0wr; + int j; + + /* bank 0, only pages 0x60 - 0xa0 */ + mem0wr = &(g_memory_ptr[0x00000]); + if(RAMWRT) { + mem0wr += 0x10000; + if((g_c035_shadow_reg & 0x8) == 0) { + mem0wr += BANK_SHADOW2; + } + } + for(j = 0x60; j < 0xa0; j++) { + SET_PAGE_INFO_WR(j, mem0wr + j*0x100); + } + + /* and bank 1, only pages 0x60 - 0xa0 */ + mem0wr = &(g_memory_ptr[0x10000]); + if((g_c035_shadow_reg & 0x8) == 0) { + mem0wr += BANK_SHADOW2; + } + for(j = 0x60; j < 0xa0; j++) { + SET_PAGE_INFO_WR(0x100 + j, mem0wr + j*0x100); + } +} + +void +fixup_shadow_iolc() +{ + byte *mem0rd; + int k; + + if(g_c035_shadow_reg & 0x40) { + /* Disable language card area */ + for(k = 0; k < 2; k++) { + mem0rd = &(g_memory_ptr[k << 16]); + fixup_any_bank_any_page((k << 8) + 0xc0, 0x10, + mem0rd + 0xd000, mem0rd + 0xd000); + if(k == 0 && ALTZP) { + mem0rd += 0x10000; + } + fixup_any_bank_any_page((k << 8) + 0xd0, 0x10, + mem0rd + 0xc000, mem0rd + 0xc000); + fixup_any_bank_any_page((k << 8) + 0xe0, 0x20, + mem0rd + 0xe000, mem0rd + 0xe000); + } + } else { + /* 0xc000 area */ + fixup_intcx(); + + // Fix 0xd000-0xffff banks 0, 1, 0xe0, 0xe1 + fixup_lc(); + } +} + +void +update_shadow_reg(dword64 dfcyc, word32 val) +{ + int xor; + + dbg_log_info(dfcyc, val, g_c035_shadow_reg, 0xc035); + + if(g_c035_shadow_reg == val) { + return; + } + + xor = g_c035_shadow_reg ^ val; + g_c035_shadow_reg = val; + + if(xor & 8) { + fixup_shadow_hires1(); + fixup_shadow_hires2(); + fixup_shadow_shr(); + xor = xor & (~0x16); + } + if(xor & 0x10) { + fixup_shadow_hires1(); + fixup_shadow_hires2(); + xor = xor & (~0x6); + } + if(xor & 2) { + fixup_shadow_hires1(); + } + if(xor & 4) { + fixup_shadow_hires2(); + } + if(xor & 1) { + fixup_shadow_txt1(); + } + if((xor & 0x20) && ((g_rom_version != 1) || g_user_page2_shadow)) { + fixup_shadow_txt2(); + } + if(xor & 0x40) { + fixup_shadow_iolc(); + } + if(xor) { + fixup_brks(); + } +} + +void +fixup_shadow_all_banks() +{ + byte *mem0rd; + int shadow; + int num_banks; + int j, k; + + /* Assume Ninja Force Megademo */ + /* only do banks 3 - num_banks by 2, shadowing into e1 */ + + shadow = 0; + if((g_c036_val_speed & 0x10) && ((g_c035_shadow_reg & 0x08) == 0)) { + shadow = BANK_SHADOW2; + } + num_banks = g_mem_size_total >> 16; + for(k = 3; k < num_banks; k += 2) { + mem0rd = &(g_memory_ptr[k*0x10000 + 0x2000]) + shadow; + for(j = 0x20; j < 0xa0; j++) { + SET_PAGE_INFO_WR(k*0x100 + j, mem0rd); + mem0rd += 0x100; + } + } + + fixup_brks(); +} + +void +setup_pageinfo() +{ + byte *mem0rd; + word32 mem_size_pages; + + /* first, set all of memory to point to itself */ + mem_size_pages = g_mem_size_total >> 8; + mem0rd = &(g_memory_ptr[0]); + fixup_any_bank_any_page(0, mem_size_pages, mem0rd, mem0rd); + + /* mark unused memory as BAD_MEM */ + fixup_any_bank_any_page(mem_size_pages, 0xfc00-mem_size_pages, + BANK_BAD_MEM, BANK_BAD_MEM); + + fixup_shadow_all_banks(); + + /* ROM */ + mem0rd = &(g_rom_fc_ff_ptr[0]); + fixup_any_bank_any_page(0xfc00, 0x400, mem0rd, + mem0rd + (BANK_IO_TMP | BANK_IO2_TMP)); + + /* banks e0, e1 */ + mem0rd = &(g_slow_memory_ptr[0]); + fixup_any_bank_any_page(0xe000, 0x200, mem0rd, mem0rd); + // First, did all 128KB + + fixup_any_bank_any_page(0xe004, 0x08, mem0rd + 0x0400, + mem0rd + 0x0400 + BANK_SHADOW); + fixup_any_bank_any_page(0xe020, 0x80, mem0rd + 0x2000, + mem0rd + 0x2000 + BANK_SHADOW); + + mem0rd = &(g_slow_memory_ptr[0x10000]); + fixup_any_bank_any_page(0xe104, 0x08, mem0rd + 0x0400, + mem0rd + 0x0400 + BANK_SHADOW2); + fixup_any_bank_any_page(0xe120, 0x80, mem0rd + 0x2000, + mem0rd + 0x2000 + BANK_SHADOW2); + + fixup_intcx(); /* correct banks 0xe0,0xe1, 0xc000-0xcfff area */ + fixup_lc(); /* correct 0xd000-0xdfff area */ + + fixup_bank0_2000_4000(); + fixup_bank0_0400_0800(); + fixup_altzp(); + fixup_ramrd(); + fixup_ramwrt(); + fixup_shadow_txt1(); + fixup_shadow_txt2(); + fixup_shadow_hires1(); + fixup_shadow_hires2(); + fixup_shadow_shr(); + fixup_shadow_iolc(); + fixup_brks(); +} + +void +show_bankptrs_bank0rdwr() +{ + show_bankptrs(0); + show_bankptrs(1); + show_bankptrs(0xe0); + show_bankptrs(0xe1); + printf("statereg: %02x\n", g_c068_statereg); +} + +void +show_bankptrs(int bnk) +{ + int i; + Pg_info rd, wr; + byte *ptr_rd, *ptr_wr; + + printf("g_memory_ptr: %p, dummy_mem: %p, slow_mem_ptr: %p\n", + g_memory_ptr, g_dummy_memory1_ptr, g_slow_memory_ptr); + printf("g_rom_fc_ff_ptr: %p\n", g_rom_fc_ff_ptr); + + printf("Showing bank_info array for %02x\n", bnk); + for(i = 0; i < 256; i++) { + rd = GET_PAGE_INFO_RD(bnk*0x100 + i); + wr = GET_PAGE_INFO_WR(bnk*0x100 + i); + ptr_rd = (byte *)rd; + ptr_wr = (byte *)wr; + printf("%04x rd: ", bnk*256 + i); + show_addr(ptr_rd); + printf(" wr: "); + show_addr(ptr_wr); + printf("\n"); + } +} + +void +show_addr(byte *ptr) +{ + word32 mem_size; + + mem_size = g_mem_size_total; + if(ptr >= g_memory_ptr && ptr < &g_memory_ptr[mem_size]) { + printf("%p--memory[%06x]", ptr, + (word32)(ptr - g_memory_ptr)); + } else if(ptr >= g_rom_fc_ff_ptr && ptr < &g_rom_fc_ff_ptr[256*1024]) { + printf("%p--rom_fc_ff[%06x]", ptr, + (word32)(ptr - g_rom_fc_ff_ptr)); + } else if(ptr >= g_slow_memory_ptr && ptr<&g_slow_memory_ptr[128*1024]){ + printf("%p--slow_memory[%06x]", ptr, + (word32)(ptr - g_slow_memory_ptr)); + } else if(ptr >=g_dummy_memory1_ptr && ptr < &g_dummy_memory1_ptr[256]){ + printf("%p--dummy_memory[%06x]", ptr, + (word32)(ptr - g_dummy_memory1_ptr)); + } else { + printf("%p--unknown", ptr); + } +} + +word32 +moremem_fix_vector_pull(word32 addr) +{ + // Default vector for BRK will come from 0xfffffe. But if + // I/O shadowing is off, or we're a //e, then get from bank0 + if((g_c035_shadow_reg & 0x40) || (g_rom_version == 0)) { + // I/O shadowing off, or Apple //e: use RAM loc + addr = addr & 0xffff; + } + return addr; +} + +word32 +io_read(word32 loc, dword64 *cyc_ptr) +{ + dword64 dfcyc; + word32 val; + word32 new_lcbank2, new_wrdefram, new_prewrite, new_rdrom, tmp; + int i; + + dfcyc = *cyc_ptr; + +/* IO space */ + switch((loc >> 8) & 0xf) { + case 0: /* 0xc000 - 0xc0ff */ + switch(loc & 0xff) { + /* 0xc000 - 0xc00f */ + case 0x00: case 0x01: case 0x02: case 0x03: + case 0x04: case 0x05: case 0x06: case 0x07: + case 0x08: case 0x09: case 0x0a: case 0x0b: + case 0x0c: case 0x0d: case 0x0e: case 0x0f: + return(adb_read_c000()); + + /* 0xc010 - 0xc01f */ + case 0x10: /* c010 */ + return(adb_access_c010()); + case 0x11: /* c011 = RDLCBANK2 */ + return IOR(LCBANK2); + case 0x12: /* c012= RDLCRAM */ + return IOR(!RDROM); + case 0x13: /* c013=rdramd */ + return IOR(RAMRD); + case 0x14: /* c014=rdramwrt */ + return IOR(RAMWRT); + case 0x15: /* c015 = RDCXROM = INTCX */ + return IOR(g_c068_statereg & 1); + case 0x16: /* c016: Read ALTZP, 0 = Read Main ZP; 1 = Alt ZP */ + return IOR(ALTZP); + case 0x17: /* c017: rdc3rom */ + return IOR(g_c02d_int_crom & (1 << 3)); + case 0x18: /* c018: rd80c0l */ + return IOR((g_cur_a2_stat & ALL_STAT_ST80)); + case 0x19: /* c019: rdvblbar: MSB set if in VBL */ + tmp = in_vblank(dfcyc); + if(g_rom_version == 0) { // Apple //e + tmp = tmp ^ 1; // 1=not in VBL on //e + } + return IOR(tmp); + case 0x1a: /* c01a: rdtext */ + return IOR(g_cur_a2_stat & ALL_STAT_TEXT); + case 0x1b: /* c01b: rdmix */ + return IOR(g_cur_a2_stat & ALL_STAT_MIX_T_GR); + case 0x1c: /* c01c: rdpage2 */ + return IOR(g_cur_a2_stat & ALL_STAT_PAGE2); + case 0x1d: /* c01d: rdhires */ + return IOR(g_cur_a2_stat & ALL_STAT_HIRES); + case 0x1e: /* c01e: altcharset on? */ + return IOR(g_cur_a2_stat & ALL_STAT_ALTCHARSET); + case 0x1f: /* c01f: rd80vid */ + return IOR(g_cur_a2_stat & ALL_STAT_VID80); + + /* 0xc020 - 0xc02f */ + case 0x20: /* 0xc020 */ + /* Click cassette port */ + return float_bus(dfcyc); + case 0x21: /* 0xc021 */ + /* Not documented, but let's return COLOR_C021 */ + return IOR(g_cur_a2_stat & ALL_STAT_COLOR_C021); + case 0x22: /* 0xc022 */ + return (g_cur_a2_stat >> BIT_ALL_STAT_BG_COLOR) & 0xff; + case 0x23: /* 0xc023 */ + return g_c023_val; + case 0x24: /* 0xc024 */ + return mouse_read_c024(dfcyc); + case 0x25: /* 0xc025 */ + return adb_read_c025(); + case 0x26: /* 0xc026 */ + return adb_read_c026(); + case 0x27: /* 0xc027 */ + return adb_read_c027(); + case 0x28: /* 0xc028 */ + return 0; + case 0x29: /* 0xc029 */ + return((g_cur_a2_stat & 0xa0) | g_c029_val_some); + case 0x2a: /* 0xc02a */ +#if 0 + printf("Reading c02a...returning 0\n"); +#endif + return 0; + case 0x2b: /* 0xc02b */ + return g_c02b_val; + case 0x2c: /* 0xc02c */ + /* printf("reading c02c, returning 0\n"); */ + if(g_c06d_val == 0x40) { // Test mode $40 + return read_video_data(dfcyc); + } + return 0; + case 0x2d: /* 0xc02d */ + return g_c02d_int_crom; + case 0x2e: /* 0xc02e */ + case 0x2f: /* 0xc02f */ + return read_vid_counters(loc, dfcyc); + + /* 0xc030 - 0xc03f */ + case 0x30: /* 0xc030 */ + /* click speaker */ + return sound_read_c030(dfcyc); + case 0x31: /* 0xc031 */ + /* 3.5" control */ + return g_iwm.state & 0xc0; + case 0x32: /* 0xc032 */ + /* scan int */ + return 0; + case 0x33: /* 0xc033 = CLOCKDATA*/ + return g_c033_data; + case 0x34: /* 0xc034 = CLOCKCTL */ + return g_c034_val; + case 0x35: /* 0xc035 */ + return g_c035_shadow_reg; + case 0x36: /* 0xc036 = CYAREG */ + return g_c036_val_speed; + case 0x37: /* 0xc037 */ + return 0; + case 0x38: /* 0xc038 */ + return scc_read_reg(dfcyc, 1); + case 0x39: /* 0xc039 */ + return scc_read_reg(dfcyc, 0); + case 0x3a: /* 0xc03a */ + return scc_read_data(dfcyc, 1); + case 0x3b: /* 0xc03b */ + return scc_read_data(dfcyc, 0); + case 0x3c: /* 0xc03c */ + /* doc control */ + return doc_read_c03c(); + case 0x3d: /* 0xc03d */ + return doc_read_c03d(dfcyc); + case 0x3e: /* 0xc03e */ + return (g_c03ef_doc_ptr & 0xff); + case 0x3f: /* 0xc03f */ + return (g_c03ef_doc_ptr >> 8); + + /* 0xc040 - 0xc04f */ + case 0x40: /* 0xc040 */ + /* cassette */ + return 0; + case 0x41: /* 0xc041 */ + return g_c041_val; + case 0x45: /* 0xc045 */ + //halt_printf("Mega II mouse read: c045\n"); + return 0; + case 0x46: /* 0xc046 */ + tmp = g_c046_val; + g_c046_val = (tmp & 0xbf) + ((tmp & 0x80) >> 1); + return tmp; + case 0x47: /* 0xc047 */ + remove_irq(IRQ_PENDING_C046_25SEC | + IRQ_PENDING_C046_VBL); + g_c046_val &= 0xe7; /* clear vbl_int, 1/4sec int*/ + return 0; + case 0x42: /* 0xc042 */ + case 0x43: /* 0xc043 */ + return 0; + case 0x4f: /* 0xc04f */ + /* for information on c04f, see: */ + /* www.sheppyware.net/tech/hardware/softswitches.html */ + /* write to $c04f to start. Then read $c04f to get */ + /* emulator ($16=sweet16, $fe=bernie II). */ + /* Then read again to get version: $21 == 2.1 */ + switch(g_em_emubyte_cnt) { + case 1: + g_em_emubyte_cnt = 2; + return 'K'; + case 2: + g_em_emubyte_cnt = 0; + tmp = g_kegs_version_str[0] - '0'; + i = g_kegs_version_str[2] - '0'; + return ((tmp & 0xf) << 4) + (i & 0xf); + default: + g_em_emubyte_cnt = 0; + return 0; + } + case 0x44: /* 0xc044 */ + case 0x48: /* 0xc048 */ + case 0x49: /* 0xc049 */ + case 0x4a: /* 0xc04a */ + case 0x4b: /* 0xc04b */ + case 0x4c: /* 0xc04c */ + case 0x4d: /* 0xc04d */ + case 0x4e: /* 0xc04e */ + UNIMPL_READ; + + /* 0xc050 - 0xc05f */ + case 0x50: /* 0xc050 */ + val = float_bus(dfcyc); + if(g_cur_a2_stat & ALL_STAT_TEXT) { + g_cur_a2_stat &= (~ALL_STAT_TEXT); + change_display_mode(dfcyc); + } + return val; + case 0x51: /* 0xc051 */ + val = float_bus(dfcyc); + if((g_cur_a2_stat & ALL_STAT_TEXT) == 0) { + g_cur_a2_stat |= (ALL_STAT_TEXT); + change_display_mode(dfcyc); + } + return val; + case 0x52: /* 0xc052 */ + val = float_bus(dfcyc); + if(g_cur_a2_stat & ALL_STAT_MIX_T_GR) { + g_cur_a2_stat &= (~ALL_STAT_MIX_T_GR); + change_display_mode(dfcyc); + } + return val; + case 0x53: /* 0xc053 */ + val = float_bus(dfcyc); + if((g_cur_a2_stat & ALL_STAT_MIX_T_GR) == 0) { + g_cur_a2_stat |= (ALL_STAT_MIX_T_GR); + change_display_mode(dfcyc); + } + return val; + case 0x54: /* 0xc054 */ + val = float_bus(dfcyc); + set_statereg(dfcyc, g_c068_statereg & (~0x40)); + return val; + return float_bus(dfcyc); + case 0x55: /* 0xc055 */ + val = float_bus(dfcyc); + set_statereg(dfcyc, g_c068_statereg | 0x40); + return val; + case 0x56: /* 0xc056 */ + val = float_bus(dfcyc); + if(g_cur_a2_stat & ALL_STAT_HIRES) { + g_cur_a2_stat &= (~ALL_STAT_HIRES); + fixup_hires_on(); + change_display_mode(dfcyc); + } + return val; + case 0x57: /* 0xc057 */ + val = float_bus(dfcyc); + if((g_cur_a2_stat & ALL_STAT_HIRES) == 0) { + g_cur_a2_stat |= (ALL_STAT_HIRES); + fixup_hires_on(); + change_display_mode(dfcyc); + } + return val; + case 0x58: /* 0xc058 */ + if(g_zipgs_unlock < 4) { + g_c05x_annuncs &= (~1); + } + return 0; + case 0x59: /* 0xc059 */ + if(g_zipgs_unlock >= 4) { + return g_zipgs_reg_c059; + } else { + g_c05x_annuncs |= 1; + } + return 0; + case 0x5a: /* 0xc05a */ + if(g_zipgs_unlock >= 4) { + return g_zipgs_reg_c05a; + } else { + g_c05x_annuncs &= (~2); + } + return 0; + case 0x5b: /* 0xc05b */ + if(g_zipgs_unlock >= 4) { + // Bit 7 is 1msclk, clock with 1ms period. + tmp = (dfcyc >> 25) & 1; + return (tmp << 7) + (g_zipgs_reg_c05b & 0x7f); + } else { + g_c05x_annuncs |= 2; + } + return 0; + case 0x5c: /* 0xc05c */ + if(g_zipgs_unlock >= 4) { + return g_zipgs_reg_c05c; + } else { + g_c05x_annuncs &= (~4); + } + return 0; + case 0x5d: /* 0xc05d */ + if(g_zipgs_unlock >= 4) { + halt_printf("Reading ZipGS $c05d!\n"); + } else { + g_c05x_annuncs |= 4; + } + return 0; + case 0x5e: /* 0xc05e */ + if(g_zipgs_unlock >= 4) { + halt_printf("Reading ZipGS $c05e!\n"); + } else if(g_cur_a2_stat & ALL_STAT_ANNUNC3) { + g_cur_a2_stat &= (~ALL_STAT_ANNUNC3); + change_display_mode(dfcyc); + } + return 0; + case 0x5f: /* 0xc05f */ + if(g_zipgs_unlock >= 4) { + halt_printf("Reading ZipGS $c05f!\n"); + } else if((g_cur_a2_stat & ALL_STAT_ANNUNC3) == 0) { + g_cur_a2_stat |= (ALL_STAT_ANNUNC3); + change_display_mode(dfcyc); + } + return 0; + + + /* 0xc060 - 0xc06f */ + case 0x60: /* 0xc060 */ + return IOR(g_paddle_buttons & 8); + case 0x61: /* 0xc061 */ + return IOR(adb_is_cmd_key_down() || + g_paddle_buttons & 1); + case 0x62: /* 0xc062 */ + return IOR(adb_is_option_key_down() || + g_paddle_buttons & 2); + case 0x63: /* 0xc063 */ + return IOR(g_paddle_buttons & 4); + case 0x64: /* 0xc064 */ + return read_paddles(dfcyc, 0); + case 0x65: /* 0xc065 */ + return read_paddles(dfcyc, 1); + case 0x66: /* 0xc066 */ + return read_paddles(dfcyc, 2); + case 0x67: /* 0xc067 */ + return read_paddles(dfcyc, 3); + case 0x68: /* 0xc068 = STATEREG */ + return g_c068_statereg & 0xff; + case 0x69: /* 0xc069 */ + /* Reserved reg, return 0 */ + return 0; + case 0x6a: /* 0xc06a */ + case 0x6b: /* 0xc06b */ + UNIMPL_READ; + case 0x6c: /* 0xc06c */ + case 0x6d: /* 0xc06d */ + case 0x6e: /* 0xc06e */ + case 0x6f: /* 0xc06f */ + return g_c06c_latched_cyc >> (8 * (loc & 3)) & 0xff; + + /* 0xc070 - 0xc07f */ + case 0x70: /* c070 */ + paddle_trigger(dfcyc); + return float_bus(dfcyc); + case 0x71: /* 0xc071 */ + case 0x72: case 0x73: + case 0x74: case 0x75: case 0x76: case 0x77: + case 0x78: case 0x79: case 0x7a: case 0x7b: + case 0x7c: case 0x7d: case 0x7e: case 0x7f: + return g_rom_fc_ff_ptr[3*65536 + 0xc000 + (loc & 0xff)]; + + /* 0xc080 - 0xc08f */ + case 0x80: case 0x81: case 0x82: case 0x83: + case 0x84: case 0x85: case 0x86: case 0x87: + case 0x88: case 0x89: case 0x8a: case 0x8b: + case 0x8c: case 0x8d: case 0x8e: case 0x8f: + new_lcbank2 = ((loc >> 1) & 0x4) ^ 0x4; + new_rdrom = ((loc << 3) ^ (loc << 2)) & 8; + // new_rdrom is set if loc[0] ^ loc[1] is true + // 8=RDROM, 0=read from LC RAM + new_prewrite = (loc & 1) << 8; // Odd read set PREWRITE + new_wrdefram = g_c068_statereg & 0x200; + if((loc & 1) == 0) { + new_wrdefram = 0; // Any even access clrs + } else { + new_wrdefram |= (g_c068_statereg << 1) & 0x200; + // Odd read also makes Ram wr if PREWR was set + } + set_statereg(dfcyc, (g_c068_statereg & ~(0x30c)) | + new_lcbank2 | new_rdrom | + new_prewrite | new_wrdefram); + // access 0-7: lcbank1, 8-f: lcbank0 + // a0!=a1: set rdrom (c081,c082,c085,c086, etc.) + // a0=1 && rd set prewrite. a0=0, or wr: clear prewrite + // wrdefram is clr if a0=0, set if prewrite and + // old_prewrite are set, otherwise stays same. + // From Apple language card schematics: + // wr_def = (wr_def_ff || (prewr && prewr_ff)) && a0; + // prewr = read && a0 + return float_bus(dfcyc); + /* 0xc090 - 0xc09f */ + case 0x90: case 0x91: case 0x92: case 0x93: + case 0x94: case 0x95: case 0x96: case 0x97: + case 0x98: case 0x99: case 0x9a: case 0x9b: + case 0x9c: case 0x9d: case 0x9e: case 0x9f: + /* UNIMPL_READ; */ + return 0; + /* 0xc0a0 - 0xc0af */ + case 0xa0: case 0xa1: case 0xa2: case 0xa3: + case 0xa4: case 0xa5: case 0xa6: case 0xa7: + case 0xa8: case 0xa9: case 0xaa: case 0xab: + case 0xac: case 0xad: case 0xae: case 0xaf: + return 0; + /* UNIMPL_READ; */ + + /* 0xc0b0 - 0xc0bf */ + case 0xb0: case 0xb1: case 0xb2: case 0xb3: + case 0xb4: case 0xb5: case 0xb6: case 0xb7: + case 0xb8: case 0xb9: case 0xba: case 0xbb: + case 0xbc: case 0xbd: case 0xbe: case 0xbf: + return voc_devsel_read(loc, dfcyc); + + /* 0xc0c0 - 0xc0cf */ + case 0xc0: case 0xc1: case 0xc2: case 0xc3: + // Slot 4 has a Slinky RAM card + return slinky_devsel_read(dfcyc, loc); + case 0xc4: case 0xc5: case 0xc6: case 0xc7: + case 0xc8: case 0xc9: case 0xca: case 0xcb: + case 0xcc: case 0xcd: case 0xce: case 0xcf: + return 0; + /* 0xc0d0 - 0xc0df */ + case 0xd0: case 0xd1: case 0xd2: case 0xd3: + case 0xd4: case 0xd5: case 0xd6: case 0xd7: + case 0xd8: case 0xd9: case 0xda: case 0xdb: + case 0xdc: case 0xdd: case 0xde: case 0xdf: + return 0; + /* 0xc0e0 - 0xc0ef */ + case 0xe0: case 0xe1: case 0xe2: case 0xe3: + case 0xe4: case 0xe5: case 0xe6: case 0xe7: + case 0xe8: case 0xe9: case 0xea: case 0xeb: + case 0xec: case 0xed: case 0xee: case 0xef: + return read_iwm(loc, dfcyc); + /* 0xc0f0 - 0xc0ff */ + case 0xf0: case 0xf1: case 0xf2: case 0xf3: + case 0xf4: case 0xf5: case 0xf6: case 0xf7: + case 0xf8: case 0xf9: case 0xfa: case 0xfb: + case 0xfc: case 0xfd: case 0xfe: case 0xff: + return 0; + + default: + printf("loc: %04x bad\n", loc); + UNIMPL_READ; + } + case 1: case 2: case 5: case 6: case 7: + /* c100 - c7ff, (except c3xx, c4xx) */ + return float_bus(dfcyc); + case 3: // c300 + return c3xx_read(dfcyc, loc); + case 4: + return mockingboard_read(loc, dfcyc); + case 8: case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: + return float_bus(dfcyc); + // UNIMPL_READ; + case 0xf: // $CFxx + if(g_c068_statereg & 0x401) { // INTC8ROM or INTCX + val = g_rom_fc_ff_ptr[0x3cf00 + (loc & 0xff)]; + } else { + val = float_bus(dfcyc); + } + if((loc & 0xfff) == 0xfff) { + if(g_c068_statereg & 0x400) { + set_statereg(dfcyc, g_c068_statereg & (~0x400)); + } + } + return val; + // UNIMPL_READ; + } + + halt_printf("io_read: hit end, loc: %06x\n", loc); + + return 0xff; +} + +void +io_write(word32 loc, word32 val, dword64 *cyc_ptr) +{ + dword64 dfcyc; + word32 new_lcbank2, new_wrdefram, new_rdrom, new_prewrite, tmp; + word32 new_tmp; + int fixup; + + dfcyc = *cyc_ptr; + + val = val & 0xff; + switch((loc >> 8) & 0xf) { + case 0: /* 0xc000 - 0xc0ff */ + switch(loc & 0xff) { + /* 0xc000 - 0xc00f */ + case 0x00: /* 0xc000: CLR80COL */ + if(g_cur_a2_stat & ALL_STAT_ST80) { + g_cur_a2_stat &= (~ALL_STAT_ST80); + fixup_st80col(dfcyc); + } + return; + case 0x01: /* 0xc001: SET80COL, enable 80-column store */ + if((g_cur_a2_stat & ALL_STAT_ST80) == 0) { + g_cur_a2_stat |= (ALL_STAT_ST80); + fixup_st80col(dfcyc); + } + return; + case 0x02: /* 0xc002: Set RDMAINRAM */ + set_statereg(dfcyc, g_c068_statereg & ~0x20); + return; + case 0x03: /* 0xc003: Set RDCARDRAM (alt) */ + set_statereg(dfcyc, g_c068_statereg | 0x20); + return; + case 0x04: /* 0xc004: Set WRMAINRAM */ + set_statereg(dfcyc, g_c068_statereg & ~0x10); + return; + case 0x05: /* 0xc005: Set WRCARDRAM (alt) */ + set_statereg(dfcyc, g_c068_statereg | 0x10); + return; + case 0x06: /* 0xc006 = SETSLOTCXROM, use slot rom c800 */ + set_statereg(dfcyc, g_c068_statereg & ~0x01); + return; + case 0x07: /* 0xc007 = SETINTXROM, use int rom c800 */ + set_statereg(dfcyc, g_c068_statereg | 0x01); + return; + case 0x08: /* 0xc008: SETSTDZP (main ZP/LC) */ + set_statereg(dfcyc, g_c068_statereg & ~0x80); + return; + case 0x09: /* 0xc009: SETALTZP (alt ZP/LC) */ + set_statereg(dfcyc, g_c068_statereg | 0x80); + return; + case 0x0a: /* 0xc00a = SETINTC3ROM, use internal ROM slot 3*/ + tmp = 1 << 3; + if((g_c02d_int_crom & tmp) != 0) { + g_c02d_int_crom &= ~tmp; + fixup_intcx(); + } + return; + case 0x0b: /* 0xc00b = SETSLOTC3ROM, use slot rom slot 3 */ + tmp = 1 << 3; + if((g_c02d_int_crom & tmp) == 0) { + g_c02d_int_crom |= tmp; + fixup_intcx(); + } + return; + case 0x0c: /* 0xc00c = CLR80VID, disable 80 col hardware */ + if(g_cur_a2_stat & ALL_STAT_VID80) { + g_cur_a2_stat &= (~ALL_STAT_VID80); + change_display_mode(dfcyc); + } + return; + case 0x0d: /* 0xc00d: SET80VID, enable 80 col hardware */ + if((g_cur_a2_stat & ALL_STAT_VID80) == 0) { + g_cur_a2_stat |= (ALL_STAT_VID80); + change_display_mode(dfcyc); + } + return; + case 0x0e: /* 0xc00e: CLRALTCHAR */ + if(g_cur_a2_stat & ALL_STAT_ALTCHARSET) { + g_cur_a2_stat &= (~ALL_STAT_ALTCHARSET); + change_display_mode(dfcyc); + } + return; + case 0x0f: /* 0xc00f: SETALTCHAR */ + if((g_cur_a2_stat & ALL_STAT_ALTCHARSET) == 0) { + g_cur_a2_stat |= (ALL_STAT_ALTCHARSET); + change_display_mode(dfcyc); + } + return; + /* 0xc010 - 0xc01f */ + case 0x10: case 0x11: case 0x12: case 0x13: + case 0x14: case 0x15: case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1a: case 0x1b: + case 0x1c: case 0x1d: case 0x1e: case 0x1f: + adb_access_c010(); + return; + /* 0xc020 - 0xc02f */ + case 0x20: /* 0xc020 */ + /* WRITE CASSETTE?? */ + return; + case 0x21: /* 0xc021 */ + new_tmp = ((val >> 7) & 1) << + (31 - BIT_ALL_STAT_COLOR_C021); + if((g_cur_a2_stat & ALL_STAT_COLOR_C021) != new_tmp) { + g_cur_a2_stat ^= new_tmp; + change_display_mode(dfcyc); + } + return; + case 0x22: /* 0xc022 */ + /* change text color */ + tmp = (g_cur_a2_stat >> BIT_ALL_STAT_BG_COLOR) & 0xff; + if(val != tmp) { + /* change text/bg color! */ + g_cur_a2_stat &= ~(ALL_STAT_TEXT_COLOR | + ALL_STAT_BG_COLOR); + g_cur_a2_stat += (val << BIT_ALL_STAT_BG_COLOR); + change_display_mode(dfcyc); + } + return; + case 0x23: /* 0xc023 */ + if((val & 0x19) != 0) { + halt_printf("c023 write of %02x!!!\n", val); + } + tmp = (g_c023_val & 0x70) | (val & 0x0f); + if((tmp & 0x22) == 0x22) { + add_irq(IRQ_PENDING_C023_SCAN); + } + if(!(tmp & 2)) { + remove_irq(IRQ_PENDING_C023_SCAN); + } + if((tmp & 0x44) == 0x44) { + add_irq(IRQ_PENDING_C023_1SEC); + } + if(!(tmp & 0x4)) { + remove_irq(IRQ_PENDING_C023_1SEC); + } + + if(g_irq_pending & (IRQ_PENDING_C023_SCAN | + IRQ_PENDING_C023_1SEC)) { + tmp |= 0x80; + } + g_c023_val = tmp; + return; + case 0x24: /* 0xc024 */ + /* Write to mouse reg: Throw it away */ + return; + case 0x26: /* 0xc026 */ + adb_write_c026(val); + return; + case 0x27: /* 0xc027 */ + adb_write_c027(val); + return; + case 0x29: /* 0xc029 */ + g_c029_val_some = val & 0x41; + if((val & 1) == 0) { + halt_printf("c029: %02x\n", val); + } + new_tmp = val & 0xa0; + if(new_tmp != (g_cur_a2_stat & 0xa0)) { + g_cur_a2_stat = (g_cur_a2_stat & (~0xa0)) + + new_tmp; + change_display_mode(dfcyc); + } + return; + case 0x2a: /* 0xc02a */ +#if 0 + printf("Writing c02a with %02x\n", val); +#endif + return; + case 0x2b: /* 0xc02b */ + g_c02b_val = val; + if((val != 0x08) && (val != 0x00)) { + printf("Writing c02b with %02x\n", val); + } + return; + case 0x2d: /* 0xc02d = Slot Reg. Bit set means use slot rom */ + if((val & 0x9) != 0) { + halt_printf("Illegal c02d write: %02x!\n", val); + } + fixup = (val != g_c02d_int_crom); + g_c02d_int_crom = val; + if(fixup) { + vid_printf("Write c02d of %02x\n", val); + fixup_intcx(); + } + return; + case 0x28: /* 0xc028 */ + case 0x2c: /* 0xc02c */ + UNIMPL_WRITE; + case 0x25: /* 0xc025 */ + /* Space Shark writes to c025--ignore */ + case 0x2e: /* 0xc02e */ + case 0x2f: /* 0xc02f */ + /* Modulae writes to this--just ignore them */ + return; + break; + + /* 0xc030 - 0xc03f */ + case 0x30: /* 0xc030 */ +#if 0 + printf("Write speaker?\n"); +#endif + sound_write_c030(dfcyc); + return; + case 0x31: /* 0xc031 */ + tmp = val ^ g_iwm.state; + iwm_flush_cur_disk(); // In case APPLE35SEL changes + g_iwm.state = (g_iwm.state & (~0xc0)) | (val & 0xc0); + if(tmp & IWM_STATE_C031_APPLE35SEL) { + /* apple35_sel changed, maybe speed change */ + engine_recalc_events(); + } + return; + case 0x32: /* 0xc032 */ + tmp = g_c023_val & 0x7f; + if(((val & 0x40) == 0) && (tmp & 0x40)) { + /* clear 1 sec int */ + remove_irq(IRQ_PENDING_C023_1SEC); + tmp &= 0xbf; + g_c023_val = tmp; + } + if(((val & 0x20) == 0) && (tmp & 0x20)) { + /* clear scan line int */ + remove_irq(IRQ_PENDING_C023_SCAN); + g_c023_val = tmp & 0xdf; + check_for_new_scan_int(dfcyc); + } + if(g_irq_pending & (IRQ_PENDING_C023_1SEC | + IRQ_PENDING_C023_SCAN)) { + g_c023_val |= 0x80; + } + if((val & 0x9f) != 0x9f) { + irq_printf("c032: wrote %02x!\n", val); + } + return; + case 0x33: /* 0xc033 = CLOCKDATA*/ + g_c033_data = val; + return; + case 0x34: /* 0xc034 = CLOCKCTL */ + tmp = val ^ g_c034_val; + clock_write_c034(val); + if(tmp & 0xf) { + change_border_color(dfcyc, val & 0xf); + } + return; + case 0x35: /* 0xc035 */ + update_shadow_reg(dfcyc, val); + return; + case 0x36: /* 0xc036 = CYAREG */ + tmp = val ^ g_c036_val_speed; + g_c036_val_speed = (val & ~0x20); /* clr bit 5 */ + if(tmp & 0x80) { + /* to recalculate times since speed changing */ + engine_recalc_events(); + } + if(tmp & 0xf) { + /* slot_motor_detect changed */ + engine_recalc_events(); + } + + if((val & 0x60) != 0) { + /* for ROM 03, 0x40 is the power-on status */ + /* and can be read/write */ + if(((val & 0x60) != 0x40) || + (g_rom_version < 3)) { + g_c036_val_speed &= (~0x60); + halt_printf("c036: %2x\n", val); + } + } + if(tmp & 0x10) { /* shadow in all banks! */ + if(g_num_shadow_all_banks++ == 0) { + printf("Shadowing all banks...This " + "must be the NFC Megademo\n"); + } + fixup_shadow_all_banks(); + } + return; + case 0x37: /* 0xc037 */ + /* just ignore, probably someone writing c036 m=0 */ + return; + case 0x38: /* 0xc038 */ + scc_write_reg(dfcyc, 1, val); + return; + case 0x39: /* 0xc039 */ + scc_write_reg(dfcyc, 0, val); + return; + case 0x3a: /* 0xc03a */ + scc_write_data(dfcyc, 1, val); + return; + case 0x3b: /* 0xc03b */ + scc_write_data(dfcyc, 0, val); + return; + case 0x3c: /* 0xc03c */ + /* doc ctl */ + doc_write_c03c(dfcyc, val); + return; + case 0x3d: /* 0xc03d */ + /* doc data reg */ + doc_write_c03d(dfcyc, val); + return; + case 0x3e: /* 0xc03e */ + g_c03ef_doc_ptr = (g_c03ef_doc_ptr & 0xff00) + val; + return; + case 0x3f: /* 0xc03f */ + g_c03ef_doc_ptr = (g_c03ef_doc_ptr & 0xff) + (val << 8); + return; + + /* 0xc040 - 0xc04f */ + case 0x41: /* c041 */ + g_c041_val = val & 0x1f; + if((val & 0xe7) != 0) { + halt_printf("write c041: %02x\n", val); + } + + if(!(val & C041_EN_VBL_INTS)) { + /* no more vbl interrupt */ + remove_irq(IRQ_PENDING_C046_VBL); + } + if(!(val & C041_EN_25SEC_INTS)) { + remove_irq(IRQ_PENDING_C046_25SEC); + } + return; + case 0x46: /* c046 */ + /* ignore writes to c046 */ + return; + case 0x47: /* c047 */ + remove_irq(IRQ_PENDING_C046_VBL | + IRQ_PENDING_C046_25SEC); + g_c046_val &= 0xe7; /* clear vblint, 1/4sec int*/ + return; + case 0x48: /* c048 */ + /* diversitune writes this--ignore it */ + return; + case 0x42: /* c042 */ + case 0x43: /* c043 */ + return; + case 0x4f: /* c04f */ + g_em_emubyte_cnt = 1; + return; + case 0x45: /* c045 */ + return; + case 0x40: /* c040 */ + case 0x44: /* c044 */ + case 0x49: /* c049 */ + case 0x4a: /* c04a */ + case 0x4b: /* c04b */ + case 0x4c: /* c04c */ + case 0x4d: /* c04d */ + case 0x4e: /* c04e */ + UNIMPL_WRITE; + + /* 0xc050 - 0xc05f */ + case 0x50: /* 0xc050 */ + if(g_cur_a2_stat & ALL_STAT_TEXT) { + g_cur_a2_stat &= (~ALL_STAT_TEXT); + change_display_mode(dfcyc); + } + return; + case 0x51: /* 0xc051 */ + if((g_cur_a2_stat & ALL_STAT_TEXT) == 0) { + g_cur_a2_stat |= (ALL_STAT_TEXT); + change_display_mode(dfcyc); + } + return; + case 0x52: /* 0xc052 */ + if(g_cur_a2_stat & ALL_STAT_MIX_T_GR) { + g_cur_a2_stat &= (~ALL_STAT_MIX_T_GR); + change_display_mode(dfcyc); + } + return; + case 0x53: /* 0xc053 */ + if((g_cur_a2_stat & ALL_STAT_MIX_T_GR) == 0) { + g_cur_a2_stat |= (ALL_STAT_MIX_T_GR); + change_display_mode(dfcyc); + } + return; + case 0x54: /* 0xc054 */ + set_statereg(dfcyc, g_c068_statereg & (~0x40)); + return; + case 0x55: /* 0xc055 */ + set_statereg(dfcyc, g_c068_statereg | 0x40); + return; + case 0x56: /* 0xc056 */ + if(g_cur_a2_stat & ALL_STAT_HIRES) { + g_cur_a2_stat &= (~ALL_STAT_HIRES); + fixup_hires_on(); + change_display_mode(dfcyc); + } + return; + case 0x57: /* 0xc057 */ + if((g_cur_a2_stat & ALL_STAT_HIRES) == 0) { + g_cur_a2_stat |= (ALL_STAT_HIRES); + fixup_hires_on(); + change_display_mode(dfcyc); + } + return; + case 0x58: /* 0xc058 */ + if(g_zipgs_unlock >= 4) { + g_zipgs_reg_c059 &= 0x4; /* last reset cold */ + } else { + g_c05x_annuncs &= (~1); + } + return; + case 0x59: /* 0xc059 */ + if(g_zipgs_unlock >= 4) { + g_zipgs_reg_c059 = (val & 0xf8) | + (g_zipgs_reg_c059 & 0x7); + } else { + g_c05x_annuncs |= 1; + } + return; + case 0x5a: /* 0xc05a */ + g_c05x_annuncs &= (~2); + if((val & 0xf0) == 0x50) { + g_zipgs_unlock++; + } else if((val & 0xf0) == 0xa0) { + g_zipgs_unlock = 0; + } else if(g_zipgs_unlock >= 4) { + if((g_zipgs_reg_c05b & 0x10) == 0) { + /* to recalculate times */ + engine_recalc_events(); + } + g_zipgs_reg_c05b |= 0x10; // disable + } + return; + case 0x5b: /* 0xc05b */ + if(g_zipgs_unlock >= 4) { + if((g_zipgs_reg_c05b & 0x10) != 0) { + /* to recalculate times */ + engine_recalc_events(); + } + g_zipgs_reg_c05b &= (~0x10); // enable + } else { + g_c05x_annuncs |= 2; + } + return; + case 0x5c: /* 0xc05c */ + if(g_zipgs_unlock >= 4) { + g_zipgs_reg_c05c = val; + } else { + g_c05x_annuncs &= (~4); + } + return; + case 0x5d: /* 0xc05d */ + if(g_zipgs_unlock >= 4) { + if(((g_zipgs_reg_c05a ^ val) >= 0x10) && + ((g_zipgs_reg_c05b & 0x10) == 0)) { + engine_recalc_events(); + } + g_zipgs_reg_c05a = val | 0xf; + } else { + g_c05x_annuncs |= 4; + } + return; + case 0x5e: /* 0xc05e: SETAN3, clear AN3, double-hires on */ + if(g_zipgs_unlock >= 4) { + /* Zippy writes 0x80 and 0x00 here... */ + } else if(g_cur_a2_stat & ALL_STAT_ANNUNC3) { + g_cur_a2_stat &= (~ALL_STAT_ANNUNC3); + change_display_mode(dfcyc); + } + return; + case 0x5f: /* 0xc05f: CLRAN3, set AN3, double-hires off */ + if(g_zipgs_unlock >= 4) { + halt_printf("Wrote ZipGS $c05f: %02x\n", val); + } else if((g_cur_a2_stat & ALL_STAT_ANNUNC3) == 0) { + g_cur_a2_stat |= (ALL_STAT_ANNUNC3); + change_display_mode(dfcyc); + } + return; + + + /* 0xc060 - 0xc06f */ + case 0x60: /* 0xc060 */ + case 0x61: /* 0xc061 */ + case 0x62: /* 0xc062 */ + case 0x63: /* 0xc063 */ + case 0x64: /* 0xc064 */ + case 0x65: /* 0xc065 */ + case 0x66: /* 0xc066 */ + case 0x67: /* 0xc067 */ + /* all the above do nothing--return */ + return; + case 0x68: /* 0xc068 = STATEREG */ + set_statereg(dfcyc, (g_c068_statereg & ~0xff) | val); + return; + case 0x69: /* 0xc069 */ + /* just ignore, someone writing c068 with m=0 */ + return; + case 0x6a: /* 0xc06a */ + case 0x6b: /* 0xc06b */ + UNIMPL_WRITE; + case 0x6c: /* 0xc06c */ + g_c06c_latched_cyc = (word32)(dfcyc >> 16); + return; + case 0x6d: /* 0xc06d */ + // Affect what reads to $C02C can see, only $40 now + if((g_c06f_val & 0x100) == 0) { // Locked + val = 0; + } + g_c06d_val = val; // Test mode + return; + case 0x6e: /* 0xc06e */ + g_c06f_val = 0; + return; + case 0x6f: /* 0xc06f */ + if((g_c06f_val == 0xda) && (val == 0x61)) { + val |= 0x100; // Unlock + } + g_c06f_val = val; + return; + + /* 0xc070 - 0xc07f */ + case 0x70: /* 0xc070 = Trigger paddles */ + paddle_trigger(dfcyc); + return; + case 0x73: /* 0xc073 = multibank ram card bank addr? */ + return; + case 0x71: /* 0xc071 = another multibank ram card enable? */ + case 0x7e: /* 0xc07e */ + case 0x7f: /* 0xc07f */ + return; + case 0x72: /* 0xc072 */ + case 0x74: /* 0xc074 */ + case 0x75: /* 0xc075 */ + case 0x76: /* 0xc076 */ + case 0x77: /* 0xc077 */ + case 0x78: /* 0xc078 */ + case 0x79: /* 0xc079 */ + case 0x7a: /* 0xc07a */ + case 0x7b: /* 0xc07b */ + case 0x7c: /* 0xc07c */ + case 0x7d: /* 0xc07d */ + return; + + /* 0xc080 - 0xc08f */ + case 0x80: case 0x81: case 0x82: case 0x83: + case 0x84: case 0x85: case 0x86: case 0x87: + case 0x88: case 0x89: case 0x8a: case 0x8b: + case 0x8c: case 0x8d: case 0x8e: case 0x8f: + new_lcbank2 = ((loc >> 1) & 0x4) ^ 0x4; + new_rdrom = ((loc << 3) ^ (loc << 2)) & 8; + // new_rdrom is set if loc0 ^ loc1 is set + // 8=RDROM, 0=read from LC RAM + new_prewrite = 0; // Writes clear PREWRITE + new_wrdefram = g_c068_statereg & 0x200; + if((loc & 1) == 0) { + new_wrdefram = 0; // Any even access clrs + } + set_statereg(dfcyc, (g_c068_statereg & ~(0x30c)) | + new_lcbank2 | new_rdrom | + new_prewrite | new_wrdefram); + return; + + /* 0xc090 - 0xc09f */ + case 0x90: case 0x91: case 0x92: case 0x93: + case 0x94: case 0x95: case 0x96: case 0x97: + case 0x98: case 0x99: case 0x9a: case 0x9b: + case 0x9c: case 0x9d: case 0x9e: case 0x9f: + UNIMPL_WRITE; + + /* 0xc0a0 - 0xc0af */ + case 0xa0: case 0xa1: case 0xa3: + case 0xa4: case 0xa5: case 0xa6: case 0xa7: + case 0xa9: case 0xaa: case 0xab: + case 0xac: case 0xad: case 0xae: case 0xaf: + UNIMPL_WRITE; + case 0xa2: /* Burger Times writes here on error */ + case 0xa8: + /* Kurzweil SMP writes to 0xc0a8, ignore it */ + return; + + /* 0xc0b0 - 0xc0bf */ + case 0xb0: case 0xb1: case 0xb2: case 0xb3: + case 0xb4: case 0xb5: case 0xb6: case 0xb7: + case 0xb8: case 0xb9: case 0xba: case 0xbb: + case 0xbc: case 0xbd: case 0xbe: case 0xbf: + voc_devsel_write(loc, val, dfcyc); + return; + + /* 0xc0c0 - 0xc0cf */ + case 0xc0: case 0xc1: case 0xc2: case 0xc3: + // Slot 4 has a Slinky RAM card + slinky_devsel_write(dfcyc, loc, val); + return; + case 0xc4: case 0xc5: case 0xc6: case 0xc7: + case 0xc8: case 0xc9: case 0xca: case 0xcb: + case 0xcc: case 0xcd: case 0xce: case 0xcf: + UNIMPL_WRITE; + + /* 0xc0d0 - 0xc0df */ + case 0xd0: case 0xd1: case 0xd2: case 0xd3: + case 0xd4: case 0xd5: case 0xd6: case 0xd7: + case 0xd8: case 0xd9: case 0xda: case 0xdb: + case 0xdc: case 0xdd: case 0xde: case 0xdf: + UNIMPL_WRITE; + + /* 0xc0e0 - 0xc0ef */ + case 0xe0: case 0xe1: case 0xe2: case 0xe3: + case 0xe4: case 0xe5: case 0xe6: case 0xe7: + case 0xe8: case 0xe9: case 0xea: case 0xeb: + case 0xec: case 0xed: case 0xee: case 0xef: + write_iwm(loc, val, dfcyc); + return; + + /* 0xc0f0 - 0xc0ff */ + case 0xf0: case 0xf1: case 0xf2: case 0xf3: + case 0xf4: case 0xf5: case 0xf6: case 0xf7: + case 0xf8: case 0xf9: case 0xfa: case 0xfb: + case 0xfc: case 0xfd: case 0xfe: case 0xff: + UNIMPL_WRITE; + default: + printf("Write loc: %x\n",loc); + exit(-300); + } + break; + case 1: case 2: case 5: case 6: case 7: + /* c1000 - c7ff (but not c3xx,c4xx) */ + if((loc & 0xff) == 0) { // Frogger writes $ff to $Cx00 + return; + } + UNIMPL_WRITE; + case 3: + if(((g_c02d_int_crom & 8) == 0) && + ((g_c068_statereg & 0x400) == 0)) { + // SLOTC3ROM clear, INTC8rom clear: Set INTC8ROM + set_statereg(dfcyc, g_c068_statereg | 0x400); + } + return; + case 4: + if((g_c02d_int_crom & 0x10) && !(g_c068_statereg & 1)) { + // Slot 4 is set to Your Card and not INTCX + mockingboard_write(loc, val, dfcyc); + return; + } + return; + case 8: case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: + // UNIMPL_WRITE; + return; + case 0xf: + if((loc & 0xfff) == 0xfff) { + /* cfff */ + if(g_c068_statereg & 0x400) { + set_statereg(dfcyc, g_c068_statereg & (~0x400)); + } + return; + } + // UNIMPL_WRITE; + // Wings of Fury writes to $cf00-$cfff when reading a 0x300 + // sector where it wants to load 0x200 to 0xd000-0xd1ff + return; + } + printf("Huh2? Write loc: %x\n", loc); + exit(-290); +} + +word32 +slinky_devsel_read(dword64 dfcyc, word32 loc) +{ + word32 val; + + loc = loc & 0xf; + val = 0; + if(loc <= 2) { + val = (g_slinky_addr >> (8*loc)) & 0xff; + } + if(loc == 3) { + val = g_slinky_ram[g_slinky_addr & (SLINKY_RAM_SIZE - 1)]; + dbg_log_info(dfcyc, g_slinky_addr, val, 0xc0c3); + g_slinky_addr++; + } + return val; +} + +void +slinky_devsel_write(dword64 dfcyc, word32 loc, word32 val) +{ + word32 mask; + + loc = loc & 0xf; + dbg_log_info(dfcyc, g_slinky_addr, val, 0xc0c0 + loc); + if(loc <= 2) { + mask = 0xff << (8 * loc); + val = val * 0x010101; + g_slinky_addr = (g_slinky_addr & (~mask)) | (val & mask); + } + if(loc == 3) { + g_slinky_ram[g_slinky_addr & (SLINKY_RAM_SIZE - 1)] = val; + g_slinky_addr++; + } +} + +word32 +c3xx_read(dword64 dfcyc, word32 loc) +{ + // We may have been marked IO so that we could set INTC8ROM, + // but we still need to return ROM + if(((g_c02d_int_crom & 8) == 0) && ((g_c068_statereg & 0x400) == 0)) { + // SLOTC3ROM is not set: Set INTC8ROM + set_statereg(dfcyc, g_c068_statereg | 0x400); + } + if(((g_c02d_int_crom & 8) == 0) || (g_c068_statereg & 1) || + (g_rom_version == 0)) { + // Access ROM for slot 3 + return g_rom_fc_ff_ptr[0x3c300 + (loc & 0xff)]; + } + return float_bus(dfcyc); +} + +// IIgs vertical line counters +// 0x7d - 0x7f: in vbl, top of screen +// 0x80 - 0xdf: not in vbl, drawing screen +// 0xe0 - 0xff: in vbl, bottom of screen +// Note: lines are then 0-0x60 effectively, for 192 lines, from 0x80-0xdf +// vertical blanking engages on line 192, even if in super hires mode +// (Last 8 lines in SHR are drawn with vbl_active set + +word32 +get_lines_since_vbl(dword64 dfcyc) +{ + double dusecs_since_last_vbl, dlines_since_vbl, dcyc_line_start; + word32 lines_since_vbl; + int offset; + + dusecs_since_last_vbl = (double)((dfcyc >> 16) - + (g_last_vbl_dfcyc >> 16)); + + dlines_since_vbl = dusecs_since_last_vbl * (1.0 / 65.0); + lines_since_vbl = (int)dlines_since_vbl; + dcyc_line_start = (double)lines_since_vbl * 65.0; + + offset = ((int)(dusecs_since_last_vbl - dcyc_line_start)) & 0xff; + + lines_since_vbl = (lines_since_vbl << 8) + offset; + + if(!g_halt_sim && !g_config_control_panel) { + dbg_log_info(dfcyc, (word32)dusecs_since_last_vbl, + lines_since_vbl, 0xc02e); + } + if(lines_since_vbl < 0x10541) { + return lines_since_vbl; + } else { + // We've entered the next frame, but update_60hz() hasn't been + // called yet. Produce the proper c02e/c02f counter values + // for the first line of the display + lines_since_vbl = lines_since_vbl - 0x10600; +#if 0 + printf("lines_since_vbl was:%08x, now:%08x\n", + lines_since_vbl + 0x10600, lines_since_vbl); + halt_printf("lines_since_vbl: %08x!\n", lines_since_vbl); + printf("du_s_l_v: %f, dfcyc: %016llx, last_vbl_cycs: %016llx\n", + dusecs_since_last_vbl, dfcyc, g_last_vbl_dfcyc); + show_dtime_array(); + show_all_events(); + /* U_STACK_TRACE(); */ +#endif + } + + return lines_since_vbl; +} + +int +in_vblank(dword64 dfcyc) +{ + word32 lines_since_vbl; + + lines_since_vbl = get_lines_since_vbl(dfcyc); + + // Testing indicates $c019 is a cycle delayed from $C02F counter + if(lines_since_vbl > 0xc000) { // Exclude line 192 first cycle! + return 1; // 1=in VBL + } + if(lines_since_vbl == 0) { + return 1; // Handle 1-cycle delay in reading c019 + } + + return 0; +} + +// horizontal video counter goes from 0x00,0x40 - 0x7f, then 0x80,0xc0-0xff +// over 2*65 cycles. The last visible screen pos is 0x7f and 0xff +// KEGS starts it's "line 0" at the start of the right border for line -1 +// See get_lines_since_vbl comment for the format of the vertical counter +int +read_vid_counters(int loc, dword64 dfcyc) +{ + word32 mask; + int lines_since_vbl; + + loc = loc & 0xf; + + lines_since_vbl = get_lines_since_vbl(dfcyc); + + lines_since_vbl += 0x10000; + if(lines_since_vbl >= 0x20000) { + lines_since_vbl = lines_since_vbl - 0x20000 + 0xfa00; + } + + if(lines_since_vbl > 0x1ffff) { + halt_printf("lines_since_vbl: %04x, dfcyc: %016llx, last_vbl:" + "%016llx\n", lines_since_vbl, dfcyc, g_last_vbl_dfcyc); + } + + if(loc == 0xe) { // c02e: Vertical count + return (lines_since_vbl >> 9) & 0xff; + } + + mask = (lines_since_vbl >> 1) & 0x80; + + lines_since_vbl = (lines_since_vbl & 0xff); + if(lines_since_vbl >= 0x01) { + lines_since_vbl = (lines_since_vbl + 0x3f) & 0x7f; + } + return (mask | (lines_since_vbl & 0xff)); +} + diff --git a/gsplus/src/op_routs.h b/gsplus/src/op_routs.h new file mode 100644 index 0000000..fa19702 --- /dev/null +++ b/gsplus/src/op_routs.h @@ -0,0 +1,143 @@ +// $KmKId: op_routs.h,v 1.47 2023-11-05 16:21:51+00 kentd Exp $ + +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2021 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + + +#define GET_DLOC_X_IND_WR() \ + CYCLES_PLUS_1; \ + INC_KPC_2; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + arg = arg + xreg + direct; \ + GET_MEMORY_DIRECT_PAGE16(arg & 0xffff, arg, 1); \ + arg = (dbank << 16) + arg; + + +#define GET_DLOC_X_IND_ADDR() \ + GET_1BYTE_ARG; \ + GET_DLOC_X_IND_WR() + +#define GET_DISP8_S_WR() \ + CYCLES_PLUS_1; \ + arg = (arg + stack) & 0xffff; \ + INC_KPC_2; + + +#define GET_DISP8_S_ADDR() \ + GET_1BYTE_ARG; \ + GET_DISP8_S_WR() + +#define GET_DLOC_WR() \ + arg = (arg + direct) & 0xffff; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + INC_KPC_2; + +#define GET_DLOC_ADDR() \ + GET_1BYTE_ARG; \ + GET_DLOC_WR() + +#define GET_DLOC_L_IND_WR() \ + arg = (arg + direct) & 0xffff; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + INC_KPC_2; \ + GET_MEMORY24(arg, arg, 1); + +#define GET_DLOC_L_IND_ADDR() \ + GET_1BYTE_ARG; \ + GET_DLOC_L_IND_WR() + + +#define GET_DLOC_IND_WR() \ + INC_KPC_2; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + GET_MEMORY_DIRECT_PAGE16((direct + arg) & 0xffff, arg, 0); \ + arg = (dbank << 16) + arg; + + +#define GET_DLOC_IND_ADDR() \ + GET_1BYTE_ARG; \ + GET_DLOC_IND_WR(); + +#define GET_DLOC_INDEX_WR(index_reg) \ + CYCLES_PLUS_1; \ + arg = (arg & 0xff) + index_reg; \ + INC_KPC_2; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + if((psr & 0x100) && ((direct & 0xff) == 0)) { \ + arg = (arg & 0xff); \ + } \ + arg = (arg + direct) & 0xffff; + +#define GET_DLOC_X_WR() \ + GET_DLOC_INDEX_WR(xreg) +#define GET_DLOC_Y_WR() \ + GET_DLOC_INDEX_WR(yreg) + +#define GET_DLOC_X_ADDR() \ + GET_1BYTE_ARG; \ + GET_DLOC_INDEX_WR(xreg) + +#define GET_DLOC_Y_ADDR() \ + GET_1BYTE_ARG; \ + GET_DLOC_INDEX_WR(yreg) + +#define GET_DISP8_S_IND_Y_WR() \ + arg = (stack + arg) & 0xffff; \ + GET_MEMORY16(arg,arg,1); \ + CYCLES_PLUS_2; \ + arg += (dbank << 16); \ + INC_KPC_2; \ + arg = (arg + yreg) & 0xffffff; + +#define GET_DISP8_S_IND_Y_ADDR() \ + GET_1BYTE_ARG; \ + GET_DISP8_S_IND_Y_WR() + +#define GET_DLOC_L_IND_Y_WR() \ + arg = (direct + arg) & 0xffff; \ + if(direct & 0xff) { \ + CYCLES_PLUS_1; \ + } \ + GET_MEMORY24(arg,arg,1); \ + INC_KPC_2; \ + arg = (arg + yreg) & 0xffffff; + +#define GET_DLOC_L_IND_Y_ADDR() \ + GET_1BYTE_ARG; \ + GET_DLOC_L_IND_Y_WR() + + +#define GET_ABS_ADDR() \ + GET_2BYTE_ARG; \ + CYCLES_PLUS_1; \ + arg = arg + (dbank << 16); \ + INC_KPC_3; + +#define GET_LONG_ADDR() \ + GET_3BYTE_ARG; \ + CYCLES_PLUS_2; \ + INC_KPC_4; + +#define GET_LONG_X_ADDR_FOR_WR() \ + GET_3BYTE_ARG; \ + INC_KPC_4; \ + arg = (arg + xreg) & 0xffffff; \ + CYCLES_PLUS_2; + diff --git a/gsplus/src/paddles.c b/gsplus/src/paddles.c new file mode 100644 index 0000000..66c90f0 --- /dev/null +++ b/gsplus/src/paddles.c @@ -0,0 +1,187 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include "defc.h" + +extern int g_mouse_raw_x; /* from adb.c */ +extern int g_mouse_raw_y; + +dword64 g_paddle_trig_dfcyc = 0; +int g_swap_paddles = 0; +int g_invert_paddles = 0; +int g_joystick_scale_factor_x = 0x100; +int g_joystick_scale_factor_y = 0x100; +int g_joystick_trim_amount_x = 0; +int g_joystick_trim_amount_y = 0; + +int g_joystick_type = 0; /* 0 = Keypad Joystick */ +int g_joystick_native_type1 = -1; +int g_joystick_native_type2 = -1; +int g_joystick_native_type = -1; + +extern int g_paddle_buttons; + +int g_paddle_val[4] = { 0, 0, 0, 0 }; + /* g_paddle_val[0]: Joystick X coord, [1]:Y coord */ + +dword64 g_paddle_dfcyc[4] = { 0, 0, 0, 0 }; + /* g_paddle_dfcyc are the dfcyc the paddle goes to 0 */ + + +void +paddle_fixup_joystick_type() +{ + /* If g_joystick_type points to an illegal value, change it */ + if(g_joystick_type == 2) { + g_joystick_native_type = g_joystick_native_type1; + if(g_joystick_native_type1 < 0) { + g_joystick_type = 0; + } + } + if(g_joystick_type == 3) { + g_joystick_native_type = g_joystick_native_type2; + if(g_joystick_native_type2 < 0) { + g_joystick_type = 0; + } + } +} + +void +paddle_trigger(dword64 dfcyc) +{ + /* Called by read/write to $c070 */ + g_paddle_trig_dfcyc = dfcyc; + + /* Determine what all the paddle values are right now */ + paddle_fixup_joystick_type(); + + switch(g_joystick_type) { + case 0: /* Keypad Joystick */ + paddle_trigger_keypad(dfcyc); + break; + case 1: /* Mouse Joystick */ + paddle_trigger_mouse(dfcyc); + break; + default: + joystick_update(dfcyc); + } +} + +void +paddle_trigger_mouse(dword64 dfcyc) +{ + int val_x, val_y; + int mouse_x, mouse_y; + + val_x = 0; + + mouse_x = g_mouse_raw_x; + mouse_y = g_mouse_raw_y; + /* mous_phys_x is 0->560, convert that to -32768 to + 32767 cyc */ + /* So subtract 280 then mult by 117 */ + val_x = (mouse_x - 280) * 117; + + /* mous_phys_y is 0->384, convert that to -32768 to + 32767 cyc */ + /* so subtract 192 then mult by 180 to overscale it a bit */ + val_y = (mouse_y - 192) * 180; + + g_paddle_val[0] = val_x; + g_paddle_val[1] = val_y; + g_paddle_val[2] = 32767; + g_paddle_val[3] = 32767; + g_paddle_buttons |= 0xc; + paddle_update_trigger_dcycs(dfcyc); +} + +void +paddle_trigger_keypad(dword64 dfcyc) +{ + int get_y, val_x, val_y; + + val_x = adb_get_keypad_xy(get_y=0); + val_y = adb_get_keypad_xy(get_y=1); + /* val_x and val_y are already scale -32768 to +32768 */ + + g_paddle_val[0] = val_x; + g_paddle_val[1] = val_y; + g_paddle_val[2] = 32767; + g_paddle_val[3] = 32767; + g_paddle_buttons |= 0xc; + paddle_update_trigger_dcycs(dfcyc); +} + +void +paddle_update_trigger_dcycs(dword64 dfcyc) +{ + dword64 trig_dfcyc; + int val, paddle_num, scale, trim; + int i; + + for(i = 0; i < 4; i++) { + paddle_num = i; + if(g_swap_paddles) { + paddle_num = i ^ 1; + } + val = g_paddle_val[paddle_num]; + if(g_invert_paddles) { + val = -val; + } + /* convert -32768 to +32768 into 0->2816.0 cycles (the */ + /* paddle delay const) */ + /* First multiply by the scale factor to adjust range */ + if(paddle_num & 1) { + scale = g_joystick_scale_factor_y; + trim = g_joystick_trim_amount_y; + } else { + scale = g_joystick_scale_factor_x; + trim = g_joystick_trim_amount_x; + } +#if 0 + if(i == 0) { + printf("val was %04x(%d) * scale %03x = %d\n", + val, val, scale, (val*scale)>>16); + } +#endif + val = (val * scale) >> 16; + /* Val is now from -128 to + 128 since scale is */ + /* 256=1.0, 128 = 0.5 */ + val = val + 128 + trim; + if(val >= 255) { + val = 280; /* increase range */ + } + trig_dfcyc = dfcyc + (dword64)((val * (2816/255.0)) * 65536); + g_paddle_dfcyc[i] = trig_dfcyc; + if(i < 2) { + dbg_log_info(dfcyc, (scale << 16) | (val & 0xffff), + (trim << 16) | i, 0x70); + } + } +} + +int +read_paddles(dword64 dfcyc, int paddle) +{ + dword64 trig_dfcyc; + + trig_dfcyc = g_paddle_dfcyc[paddle & 3]; + + if(dfcyc < trig_dfcyc) { + return 0x80; + } else { + return 0x00; + } +} + +void +paddle_update_buttons() +{ + paddle_fixup_joystick_type(); + joystick_update_buttons(); +} diff --git a/gsplus/src/protos.h b/gsplus/src/protos.h new file mode 100644 index 0000000..68ee5c4 --- /dev/null +++ b/gsplus/src/protos.h @@ -0,0 +1,14 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2019 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#ifdef INCLUDE_RCSID_C +#endif + +#include "protos_base.h" diff --git a/gsplus/src/protos_base.h b/gsplus/src/protos_base.h new file mode 100644 index 0000000..5b31153 --- /dev/null +++ b/gsplus/src/protos_base.h @@ -0,0 +1,895 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2024 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#ifdef INCLUDE_RCSID_C +#endif + +#ifdef __GNUC__ +void halt_printf(const char *fmt, ...) __attribute__ (( + __format__(printf, 1, 2))); +void cfg_err_printf(const char *pre_str, const char *fmt, ...) __attribute__ (( + __format__(printf, 2, 3))); +void dynapro_error(Disk *dsk, const char *fmt, ...) __attribute__ (( + __format__(printf, 2, 3))); +#endif + +/* xdriver.c and macdriver.c and windriver.c */ +int win_nonblock_read_stdin(int fd, char *bufptr, int len); + +/* special scc_unixdriver.c prototypes */ +void scc_serial_unix_open(int port); +void scc_serial_unix_close(int port); +void scc_serial_unix_change_params(int port); +void scc_serial_unix_fill_readbuf(dword64 dfcyc, int port, int space_left); +void scc_serial_unix_empty_writebuf(int port); + +/* special scc_windriver.c prototypes */ +void scc_serial_win_open(int port); +void scc_serial_win_close(int port); +void scc_serial_win_change_params(int port); +void scc_serial_win_fill_readbuf(dword64 dfcyc, int port, int space_left); +void scc_serial_win_empty_writebuf(int port); + +/* special joystick_driver.c prototypes */ +void joystick_init(void); +void joystick_update(dword64 dfcyc); +void joystick_update_buttons(void); + +/* END_HDR */ + +/* adb.c */ +int adb_get_hide_warp_info(Kimage *kimage_ptr, int *warpptr); +int adb_get_copy_requested(void); +void adb_nonmain_check(void); +void adb_init(void); +void adb_reset(void); +void adb_log(word32 addr, int val); +void show_adb_log(void); +void adb_error(void); +void adb_add_kbd_srq(void); +void adb_clear_kbd_srq(void); +void adb_add_data_int(void); +void adb_add_mouse_int(void); +void adb_clear_data_int(void); +void adb_clear_mouse_int(void); +void adb_send_bytes(int num_bytes, word32 val0, word32 val1, word32 val2); +void adb_send_1byte(word32 val); +void adb_response_packet(int num_bytes, word32 val); +void adb_kbd_reg0_data(int a2code, int is_up); +void adb_kbd_talk_reg0(void); +void adb_set_config(word32 val0, word32 val1, word32 val2); +void adb_set_new_mode(word32 val); +int adb_read_c026(void); +void adb_write_c026(int val); +void do_adb_cmd(void); +int adb_read_c027(void); +void adb_write_c027(int val); +int read_adb_ram(word32 addr); +void write_adb_ram(word32 addr, int val); +int adb_get_keypad_xy(int get_y); +int adb_update_mouse(Kimage *kimage_ptr, int x, int y, int button_states, int buttons_valid); +int mouse_read_c024(dword64 dfcyc); +void mouse_compress_fifo(dword64 dfcyc); +void adb_paste_update_state(void); +int adb_paste_add_buf(word32 key); +void adb_key_event(int a2code, int is_up); +word32 adb_read_c000(void); +word32 adb_access_c010(void); +word32 adb_read_c025(void); +int adb_is_cmd_key_down(void); +int adb_is_option_key_down(void); +void adb_increment_speed(void); +void adb_update_c025_mask(Kimage *kimage_ptr, word32 new_c025_val, word32 mask); +int adb_ascii_to_a2code(int unicode_c, int a2code, int *shift_down_ptr); +void adb_physical_key_update(Kimage *kimage_ptr, int raw_a2code, word32 unicode_c, int is_up); +void adb_maybe_virtual_key_update(int a2code, int is_up); +void adb_virtual_key_update(int a2code, int is_up); +void adb_kbd_repeat_off(void); +void adb_mainwin_focus(int has_focus); + + + +/* engine_c.c */ +word32 get_memory8_io_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, dword64 dplus_x_m1); +word32 get_memory16_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank); +word32 get_memory24_pieces_stub(word32 addr, byte *stat, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank); +void set_memory8_io_stub(word32 addr, word32 val, byte *stat, dword64 *dcycs_ptr, dword64 dplus_x_m1); +void set_memory16_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, dword64 dplus_1, dword64 dplus_x_m1, int in_bank); +void set_memory24_pieces_stub(word32 addr, word32 val, dword64 *dcycs_ptr, Fplus *fplus_ptr, int in_bank); +word32 get_memory_c(word32 addr); +word32 get_memory16_c(word32 addr); +word32 get_memory24_c(word32 addr); +void set_memory_c(word32 addr, word32 val, int do_log); +void set_memory16_c(word32 addr, word32 val, int do_log); +void set_memory24_c(word32 addr, word32 val); +word32 do_adc_sbc8(word32 in1, word32 in2, word32 psr, int sub); +word32 do_adc_sbc16(word32 in1, word32 in2, word32 psr, int sub); +void fixed_memory_ptrs_init(void); +word32 get_itimer(void); +void engine_recalc_events(void); +void set_halt_act(int val); +void clr_halt_act(void); +word32 get_remaining_operands(word32 addr, word32 opcode, word32 psr, dword64 *dcyc_ptr, Fplus *fplus_ptr); +int enter_engine(Engine_reg *engine_ptr); + + + +/* clock.c */ +double get_dtime(void); +int micro_sleep(double dtime); +void clk_bram_zero(void); +void clk_bram_set(int bram_num, int offset, int val); +void clk_setup_bram_version(void); +void clk_write_bram(FILE *fconf); +void update_cur_time(void); +void clock_update(void); +void clock_update_if_needed(void); +void clock_write_c034(word32 val); +void do_clock_data(void); + + + +/* compile_time.c */ + + + +/* config.c */ +int config_add_argv_override(const char *str1, const char *str2); +void config_set_config_kegs_name(const char *str1); +void config_init_menus(Cfg_menu *menuptr); +void config_init(void); +void cfg_find_config_kegs_file(void); +int config_setup_kegs_file(char *outname, int maxlen, const char **name_ptr); +int config_expand_path(char *out_ptr, const char *in_ptr, int maxlen); +char *cfg_exit(int get_status); +void cfg_err_vprintf(const char *pre_str, const char *fmt, va_list ap); +void cfg_err_printf(const char *pre_str, const char *fmt, ...); +void cfg_toggle_config_panel(void); +void cfg_set_config_panel(int panel); +char *cfg_text_screen_dump(int get_status); +char *cfg_text_screen_str(void); +char *cfg_get_serial0_status(int get_status); +char *cfg_get_serial1_status(int get_status); +char *cfg_get_current_copy_selection(void); +void config_vbl_update(int doit_3_persec); +void cfg_file_update_rom(const char *str); +void cfg_file_update_ptr(char **strptr, const char *str, int need_update); +void cfg_int_update(int *iptr, int new_val); +void cfg_load_charrom(void); +void config_load_roms(void); +void config_parse_config_kegs_file(void); +void cfg_parse_one_line(char *buf, int line); +void cfg_parse_bram(char *buf, int pos, int len); +void cfg_parse_sxdx(char *buf, int pos, int len); +void config_generate_config_kegs_name(char *outstr, int maxlen, Disk *dsk, int with_extras); +char *config_write_config_kegs_file(int get_status); +void insert_disk(int slot, int drive, const char *name, int ejected, const char *partition_name, int part_num, word32 dynamic_blocks); +dword64 cfg_detect_dc42(byte *bptr, dword64 dsize, dword64 exp_dsize, int slot); +dword64 cfg_get_fd_size(int fd); +dword64 cfg_read_from_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize); +dword64 cfg_write_to_fd(int fd, byte *bufptr, dword64 dpos, dword64 dsize); +int cfg_partition_maybe_add_dotdot(void); +int cfg_partition_name_check(const byte *name_ptr, int name_len); +int cfg_partition_read_block(int fd, void *buf, dword64 blk, int blk_size); +int cfg_partition_find_by_name_or_num(Disk *dsk, const char *in_partnamestr, int part_num); +int cfg_partition_make_list_from_name(const char *namestr); +int cfg_partition_make_list(int fd); +int cfg_maybe_insert_disk(int slot, int drive, const char *namestr); +void cfg_insert_disk_dynapro(int slot, int drive, const char *name); +int cfg_stat(char *path, struct stat *sb, int do_lstat); +word32 cfg_get_le16(byte *bptr); +word32 cfg_get_le32(byte *bptr); +dword64 cfg_get_le64(byte *bptr); +word32 cfg_get_be_word16(word16 *ptr); +word32 cfg_get_be_word32(word32 *ptr); +void cfg_set_le32(byte *bptr, word32 val); +void config_file_to_pipe(Disk *dsk, const char *cmd_ptr, const char *name_ptr); +void cfg_htab_vtab(int x, int y); +void cfg_home(void); +void cfg_cleol(void); +void cfg_putchar(int c); +void cfg_printf(const char *fmt, ...); +void cfg_print_dnum(dword64 dnum, int max_len); +int cfg_get_disk_name(char *outstr, int maxlen, int type_ext, int with_extras); +int cfg_get_disk_locked(int type_ext); +void cfg_parse_menu(Cfg_menu *menuptr, int menu_pos, int highlight_pos, int change); +void cfg_get_base_path(char *pathptr, const char *inptr, int go_up); +char *cfg_name_new_image(int get_status); +void cfg_dup_existing_image(word32 slotdrive); +void cfg_dup_image_selected(void); +void cfg_validate_image(word32 slotdrive); +void cfg_toggle_lock_disk(word32 slotdrive); +int cfg_create_new_image_act(const char *str, int type, int size_blocks); +void cfg_create_new_image(void); +void cfg_file_init(void); +void cfg_free_alldirents(Cfg_listhdr *listhdrptr); +void cfg_file_add_dirent_unique(Cfg_listhdr *listhdrptr, const char *nameptr, int is_dir, dword64 dsize, dword64 dimage_start, dword64 compr_dsize, int part_num); +void cfg_file_add_dirent(Cfg_listhdr *listhdrptr, const char *nameptr, int is_dir, dword64 dsize, dword64 dimage_start, dword64 compr_dsize, int part_num); +int cfg_dirent_sortfn(const void *obj1, const void *obj2); +int cfg_str_match(const char *str1, const char *str2, int len); +int cfg_str_match_maybecase(const char *str1, const char *str2, int len, int ignorecase); +int cfgcasecmp(const char *str1, const char *str2); +int cfg_strlcat(char *dstptr, const char *srcptr, int dstsize); +char *cfg_strncpy(char *dstptr, const char *srcptr, int dstsize); +const char *cfg_str_basename(const char *str); +char *cfg_strncpy_dirname(char *dstptr, const char *srcptr, int dstsize); +void cfg_file_readdir(const char *pathptr); +char *cfg_shorten_filename(const char *in_ptr, int maxlen); +void cfg_fix_topent(Cfg_listhdr *listhdrptr); +void cfg_file_draw(void); +void cfg_partition_select_all(void); +void cfg_partition_selected(void); +void cfg_file_selected(void); +void cfg_file_handle_key(int key); +void cfg_draw_menu(void); +void cfg_newdisk_pick_menu(word32 slotdrive); +int cfg_control_panel_update(void); +void cfg_edit_mode_key(int key); +int cfg_control_panel_update1(void); + + + +/* debugger.c */ +void debugger_init(void); +void check_breakpoints(word32 addr, dword64 dfcyc, word32 maybe_stack, word32 type); +void debug_hit_bp(word32 addr, dword64 dfcyc, word32 maybe_stack, word32 type, int pos); +int debugger_run_16ms(void); +void dbg_log_info(dword64 dfcyc, word32 info1, word32 info2, word32 type); +void debugger_update_list_kpc(void); +void debugger_key_event(Kimage *kimage_ptr, int a2code, int is_up); +void debugger_page_updown(int isup); +void debugger_redraw_screen(Kimage *kimage_ptr); +void debug_draw_debug_line(Kimage *kimage_ptr, int line, int vid_line); +void debugger_help(void); +void dbg_help_show_strs(int help_depth, const char *str, const char *help_str); +const char *debug_find_cmd_in_table(const char *line_ptr, Dbg_longcmd *longptr, int help_depth); +void do_debug_cmd(const char *in_str); +word32 dis_get_memory_ptr(word32 addr); +void show_one_toolset(FILE *toolfile, int toolnum, word32 addr); +void show_toolset_tables(word32 a2bank, word32 addr); +word32 debug_getnum(const char **str_ptr); +char *debug_get_filename(const char **str_ptr); +void debug_help(const char *str); +void debug_bp(const char *str); +void debug_bp_set(const char *str); +void debug_bp_clear(const char *str); +void debug_bp_clear_all(const char *str); +void debug_bp_setclr(const char *str, int is_set_clear); +void debug_soundfile(const char *cmd_str); +void debug_logpc(const char *str); +void debug_logpc_on(const char *str); +void debug_logpc_off(const char *str); +void debug_logpc_out_data(FILE *pcfile, Data_log *log_data_ptr, dword64 start_dcyc); +Data_log *debug_show_data_info(FILE *pcfile, Data_log *log_data_ptr, dword64 base_dcyc, dword64 dfcyc, dword64 start_dcyc, int *data_wrap_ptr, int *count_ptr); +void debug_logpc_save(const char *cmd_str); +void set_bp(word32 addr, word32 end_addr, word32 acc_type); +void show_bp(void); +void delete_bp(word32 addr, word32 end_addr); +void debug_iwm(const char *str); +void debug_iwm_check(const char *str); +int do_blank(int mode, int old_mode); +void do_go(void); +void do_step(void); +void xam_mem(int count); +void show_hex_mem(word32 startbank, word32 start, word32 end, int count); +void do_debug_list(void); +void dis_do_memmove(void); +void dis_do_pattern_search(void); +void dis_do_compare(void); +const char *do_debug_unix(const char *str, int old_mode); +void do_debug_load(void); +char *do_dis(word32 kpc, int accsize, int xsize, int op_provided, word32 instr, int *size_ptr); +int debug_get_view_line(int back); +int debug_add_output_line(char *in_str); +void debug_add_output_string(char *in_str, int len); +void debug_add_output_chars(char *str); +int dbg_printf(const char *fmt, ...); +int dbg_vprintf(const char *fmt, va_list args); +void halt_printf(const char *fmt, ...); +void halt2_printf(const char *fmt, ...); + + + +/* scc.c */ +void scc_init(void); +void scc_reset(void); +void scc_hard_reset_port(int port); +void scc_reset_port(int port); +void scc_regen_clocks(int port); +void scc_port_close(int port); +void scc_port_open(dword64 dfcyc, int port); +int scc_is_port_closed(dword64 dfcyc, int port); +char *scc_get_serial_status(int get_status, int port); +void scc_config_changed(int port, int cfg_changed, int remote_changed, int serial_dev_changed); +void scc_update(dword64 dfcyc); +void scc_try_to_empty_writebuf(dword64 dfcyc, int port); +void scc_try_fill_readbuf(dword64 dfcyc, int port); +void scc_do_event(dword64 dfcyc, int type); +void show_scc_state(void); +word32 scc_read_reg(dword64 dfcyc, int port); +void scc_write_reg(dword64 dfcyc, int port, word32 val); +word32 scc_read_data(dword64 dfcyc, int port); +void scc_write_data(dword64 dfcyc, int port, word32 val); +word32 scc_do_read_rr2b(void); +void scc_maybe_br_event(dword64 dfcyc, int port); +void scc_evaluate_ints(int port); +void scc_maybe_rx_event(dword64 dfcyc, int port); +void scc_maybe_rx_int(int port); +void scc_clr_rx_int(int port); +void scc_handle_tx_event(int port); +void scc_maybe_tx_event(dword64 dfcyc, int port); +void scc_clr_tx_int(int port); +void scc_set_zerocnt_int(int port); +void scc_clr_zerocnt_int(int port); +void scc_add_to_readbuf(dword64 dfcyc, int port, word32 val); +void scc_add_to_readbufv(dword64 dfcyc, int port, const char *fmt, ...); +void scc_transmit(dword64 dfcyc, int port, word32 val); +void scc_add_to_writebuf(dword64 dfcyc, int port, word32 val); + + + +/* scc_socket_driver.c */ +void scc_socket_open(dword64 dfcyc, int port, int cfg); +void scc_socket_close(int port); +void scc_socket_close_extended(dword64 dfcyc, int port, int allow_retry); +void scc_socket_maybe_open(dword64 dfcyc, int port, int must); +void scc_socket_open_incoming(dword64 dfcyc, int port); +void scc_socket_open_outgoing(dword64 dfcyc, int port, const char *remote_ip_str, int remote_port); +void scc_socket_make_nonblock(dword64 dfcyc, int port); +void scc_accept_socket(dword64 dfcyc, int port); +void scc_socket_telnet_reqs(dword64 dfcyc, int port); +void scc_socket_fill_readbuf(dword64 dfcyc, int port, int space_left); +void scc_socket_recvd_char(dword64 dfcyc, int port, int c); +void scc_socket_empty_writebuf(dword64 dfcyc, int port); +void scc_socket_modem_write(dword64 dfcyc, int port, int c); +void scc_socket_do_cmd_str(dword64 dfcyc, int port); +void scc_socket_send_modem_code(dword64 dfcyc, int port, int code); +void scc_socket_modem_connect(dword64 dfcyc, int port); +void scc_socket_modem_do_ring(dword64 dfcyc, int port); +void scc_socket_do_answer(dword64 dfcyc, int port); + + + +/* scc_windriver.c */ + + + +/* scc_unixdriver.c */ + + + +/* iwm.c */ +void iwm_init_drive(Disk *dsk, int smartport, int drive, int disk_525); +void iwm_init(void); +void iwm_reset(void); +void disk_set_num_tracks(Disk *dsk, int num_tracks); +word32 iwm_get_default_track_bits(Disk *dsk, word32 qtr_trk); +void draw_iwm_status(int line, char *buf); +void iwm_flush_cur_disk(void); +void iwm_flush_disk_to_unix(Disk *dsk); +void iwm_vbl_update(void); +void iwm_update_fast_disk_emul(int fast_disk_emul_en); +void iwm_show_stats(int slot_drive); +Disk *iwm_get_dsk(word32 state); +Disk *iwm_touch_switches(int loc, dword64 dfcyc); +void iwm_move_to_ftrack(Disk *dsk, word32 new_frac_track, int delta, dword64 dfcyc); +void iwm_move_to_qtr_track(Disk *dsk, word32 qtr_track); +void iwm525_update_phases(Disk *dsk, dword64 dfcyc); +void iwm525_update_head(Disk *dsk, dword64 dfcyc); +int iwm_read_status35(dword64 dfcyc); +void iwm_do_action35(dword64 dfcyc); +int read_iwm(int loc, dword64 dfcyc); +void write_iwm(int loc, int val, dword64 dfcyc); +int iwm_read_enable2(dword64 dfcyc); +int iwm_read_enable2_handshake(dword64 dfcyc); +void iwm_write_enable2(int val); +word32 iwm_fastemul_start_write(Disk *dsk, dword64 dfcyc); +word32 iwm_read_data_fast(Disk *dsk, dword64 dfcyc); +dword64 iwm_return_rand_data(Disk *dsk, dword64 dfcyc); +dword64 iwm_get_raw_bits(Disk *dsk, word32 bit_pos, int num_bits, dword64 *dsyncs_ptr); +word32 iwm_calc_bit_diff(word32 first, word32 last, word32 track_bits); +word32 iwm_calc_bit_sum(word32 bit_pos, int add_ival, word32 track_bits); +dword64 iwm_calc_forced_sync(dword64 dval, int forced_bit); +int iwm_calc_forced_sync_0s(dword64 sync_dval, int bits); +word32 iwm_read_data(Disk *dsk, dword64 dfcyc); +void iwm_write_data(Disk *dsk, word32 val, dword64 dfcyc); +void iwm_start_write(Disk *dsk, word32 bit_pos, word32 val, int no_prior); +int iwm_start_write_act(Disk *dsk, word32 bit_pos, int num, int no_prior, int delta); +void iwm_write_data35(Disk *dsk, word32 val, dword64 dfcyc); +void iwm_write_end(Disk *dsk, int write_through_now, dword64 dfcyc); +void iwm_write_one_nib(Disk *dsk, int bits, dword64 dfcyc); +void iwm_recalc_sync_from(Disk *dsk, word32 qtr_track, word32 bit_pos, dword64 dfcyc); +void sector_to_partial_nib(byte *in, byte *nib_ptr); +int disk_unnib_4x4(Disk *dsk); +int iwm_denib_track525(Disk *dsk, word32 qtr_track, byte *outbuf); +int iwm_denib_track35(Disk *dsk, word32 qtr_track, byte *outbuf); +int iwm_track_to_unix(Disk *dsk, word32 qtr_track, byte *outbuf); +void show_hex_data(byte *buf, int count); +void iwm_check_nibblization(dword64 dfcyc); +void disk_check_nibblization(Disk *dsk, byte *in_buf, int size, dword64 dfcyc); +void disk_unix_to_nib(Disk *dsk, int qtr_track, dword64 dunix_pos, word32 unix_len, int len_bits, dword64 dfcyc); +void iwm_nibblize_track_nib525(Disk *dsk, byte *track_buf, int qtr_track, word32 unix_len); +void iwm_nibblize_track_525(Disk *dsk, byte *track_buf, int qtr_track, dword64 dfcyc); +void iwm_nibblize_track_35(Disk *dsk, byte *track_buf, int qtr_track, word32 unix_len, dword64 dfcyc); +void disk_4x4_nib_out(Disk *dsk, word32 val); +void disk_nib_out(Disk *dsk, word32 val, int size); +void disk_nib_end_track(Disk *dsk, dword64 dfcyc); +word32 disk_nib_out_raw(Disk *dsk, word32 qtr_track, word32 val, int bits, word32 bit_pos, dword64 dfcyc); +Disk *iwm_get_dsk_from_slot_drive(int slot, int drive); +void iwm_toggle_lock(Disk *dsk); +void iwm_eject_named_disk(int slot, int drive, const char *name, const char *partition_name); +void iwm_eject_disk_by_num(int slot, int drive); +void iwm_eject_disk(Disk *dsk); +void iwm_show_track(int slot_drive, int track, dword64 dfcyc); +void iwm_show_a_track(Disk *dsk, Trk *trk, dword64 dfcyc); +void dummy1(word32 psr); +void dummy2(word32 psr); + + + +/* joystick_driver.c */ +void joystick_callback_init(int native_type); +void joystick_callback_update(word32 buttons, int paddle_x, int paddle_y); + + + +/* moremem.c */ +void fixup_brks(void); +void fixup_hires_on(void); +void fixup_bank0_2000_4000(void); +void fixup_bank0_0400_0800(void); +void fixup_any_bank_any_page(word32 start_page, int num_pages, byte *mem0rd, byte *mem0wr); +void fixup_intcx(void); +void fixup_st80col(dword64 dfcyc); +void fixup_altzp(void); +void fixup_page2(dword64 dfcyc); +void fixup_ramrd(void); +void fixup_ramwrt(void); +void fixup_lc(void); +void set_statereg(dword64 dfcyc, word32 val); +void fixup_shadow_txt1(void); +void fixup_shadow_txt2(void); +void fixup_shadow_hires1(void); +void fixup_shadow_hires2(void); +void fixup_shadow_shr(void); +void fixup_shadow_iolc(void); +void update_shadow_reg(dword64 dfcyc, word32 val); +void fixup_shadow_all_banks(void); +void setup_pageinfo(void); +void show_bankptrs_bank0rdwr(void); +void show_bankptrs(int bnk); +void show_addr(byte *ptr); +word32 moremem_fix_vector_pull(word32 addr); +word32 io_read(word32 loc, dword64 *cyc_ptr); +void io_write(word32 loc, word32 val, dword64 *cyc_ptr); +word32 slinky_devsel_read(dword64 dfcyc, word32 loc); +void slinky_devsel_write(dword64 dfcyc, word32 loc, word32 val); +word32 c3xx_read(dword64 dfcyc, word32 loc); +word32 get_lines_since_vbl(dword64 dfcyc); +int in_vblank(dword64 dfcyc); +int read_vid_counters(int loc, dword64 dfcyc); + + + +/* paddles.c */ +void paddle_fixup_joystick_type(void); +void paddle_trigger(dword64 dfcyc); +void paddle_trigger_mouse(dword64 dfcyc); +void paddle_trigger_keypad(dword64 dfcyc); +void paddle_update_trigger_dcycs(dword64 dfcyc); +int read_paddles(dword64 dfcyc, int paddle); +void paddle_update_buttons(void); + + + +/* mockingboard.c */ +void mock_ay8913_reset(int pair_num, dword64 dfcyc); +void mockingboard_reset(dword64 dfcyc); +void mock_show_pair(int pair_num, dword64 dfcyc, const char *str); +void mock_update_timers(int doit, dword64 dfcyc); +void mockingboard_event(dword64 dfcyc); +word32 mockingboard_read(word32 loc, dword64 dfcyc); +void mockingboard_write(word32 loc, word32 val, dword64 dfcyc); +word32 mock_6522_read(int pair_num, word32 loc, dword64 dfcyc); +void mock_6522_write(int pair_num, word32 loc, word32 val, dword64 dfcyc); +word32 mock_6522_new_ifr(dword64 dfcyc, int pair_num, word32 ifr, word32 ier); +void mock_ay8913_reg_read(int pair_num); +void mock_ay8913_reg_write(int pair_num, dword64 dfcyc); +void mock_ay8913_control_update(int pair_num, word32 new_val, word32 prev_val, dword64 dfcyc); +void mockingboard_show(int got_num, word32 disable_mask); + + + +/* sim65816.c */ +int sim_get_force_depth(void); +int sim_get_use_shmem(void); +void sim_set_use_shmem(int use_shmem); +word32 toolbox_debug_4byte(word32 addr); +void toolbox_debug_c(word32 xreg, word32 stack, dword64 *dcyc_ptr); +void show_toolbox_log(void); +word32 get_memory_io(word32 loc, dword64 *dcyc_ptr); +void set_memory_io(word32 loc, int val, dword64 *dcyc_ptr); +void show_regs_act(Engine_reg *eptr); +void show_regs(void); +void my_exit(int ret); +void do_reset(void); +byte *memalloc_align(int size, int skip_amt, void **alloc_ptr); +void memory_ptr_init(void); +int parse_argv(int argc, char **argv, int slashes_to_find); +int kegs_init(int mdepth, int screen_width, int screen_height, int no_scale_window); +void load_roms_init_memory(void); +void initialize_events(void); +void check_for_one_event_type(int type, word32 mask); +void add_event_entry(dword64 dfcyc, int type); +dword64 remove_event_entry(int type, word32 mask); +void add_event_stop(dword64 dfcyc); +void add_event_doc(dword64 dfcyc, int osc); +void add_event_scc(dword64 dfcyc, int type); +void add_event_vbl(void); +void add_event_vid_upd(int line); +void add_event_mockingboard(dword64 dfcyc); +void add_event_scan_int(dword64 dfcyc, int line); +dword64 remove_event_doc(int osc); +dword64 remove_event_scc(int type); +void remove_event_mockingboard(void); +void show_all_events(void); +void show_pmhz(void); +void setup_zip_speeds(void); +int run_16ms(void); +int run_a2_one_vbl(void); +void add_irq(word32 irq_mask); +void remove_irq(word32 irq_mask); +void take_irq(void); +void show_dtime_array(void); +void update_60hz(dword64 dfcyc, double dtime_now); +void do_vbl_int(void); +void do_scan_int(dword64 dfcyc, int line); +void check_scan_line_int(int cur_video_line); +void check_for_new_scan_int(dword64 dfcyc); +void scb_changed(dword64 dfcyc, word32 addr, word32 new_val, word32 old_val); +void init_reg(void); +void handle_action(word32 ret); +void do_break(word32 ret); +void do_cop(word32 ret); +void do_wdm(word32 arg); +void do_wai(void); +void do_stp(void); +void do_wdm_emulator_id(void); +void size_fail(int val, word32 v1, word32 v2); +int fatal_printf(const char *fmt, ...); +int kegs_vprintf(const char *fmt, va_list ap); +dword64 must_write(int fd, byte *bufptr, dword64 dsize); +void clear_fatal_logs(void); +char *kegs_malloc_str(const char *in_str); +dword64 kegs_lseek(int fd, dword64 offs, int whence); + + + +/* smartport.c */ +void smartport_error(void); +void smartport_log(word32 start_addr, word32 cmd, word32 rts_addr, word32 cmd_list); +void do_c70d(word32 arg0); +void do_c70a(word32 arg0); +int do_read_c7(int unit_num, word32 buf, word32 blk); +int do_write_c7(int unit_num, word32 buf, word32 blk); +int smartport_memory_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size); +int do_format_c7(int unit_num); +void do_c700(word32 ret); + + + +/* doc.c */ +void doc_init(void); +void doc_reset(dword64 dfcyc); +int doc_play(dword64 dfcyc, double last_dsamp, double dsamp_now, int num_samps, int snd_buf_init, int *outptr_start); +void doc_handle_event(int osc, dword64 dfcyc); +void doc_sound_end(int osc, int can_repeat, double eff_dsamps, double dsamps); +void doc_add_sound_irq(int osc); +void doc_remove_sound_irq(int osc, int must); +void doc_start_sound2(int osc, dword64 dfcyc); +void doc_start_sound(int osc, double eff_dsamps, double dsamps); +void doc_wave_end_estimate2(int osc, dword64 dfcyc); +void doc_wave_end_estimate(int osc, double eff_dsamps, double dsamps); +void doc_remove_sound_event(int osc); +void doc_write_ctl_reg(dword64 dfcyc, int osc, int val); +void doc_recalc_sound_parms(dword64 dfcyc, int osc); +int doc_read_c03c(void); +int doc_read_c03d(dword64 dfcyc); +void doc_write_c03c(dword64 dfcyc, word32 val); +void doc_write_c03d(dword64 dfcyc, word32 val); +void doc_show_ensoniq_state(void); + + + +/* sound.c */ +void sound_init(void); +void sound_set_audio_rate(int rate); +void sound_reset(dword64 dfcyc); +void sound_shutdown(void); +void sound_update(dword64 dfcyc); +void sound_file_start(char *filename); +void sound_file_open(void); +void sound_file_close(void); +void send_sound_to_file(word32 *wptr, int shm_pos, int num_samps, int real_samps); +void show_c030_state(dword64 dfcyc); +void show_c030_samps(dword64 dfcyc, int *outptr, int num); +int sound_play_c030(dword64 dfcyc, dword64 dsamp, int *outptr_start, int num_samps); +void sound_play(dword64 dfcyc); +void sound_mock_envelope(int pair, int *env_ptr, int num_samps, int *vol_ptr); +void sound_mock_noise(int pair, byte *noise_ptr, int num_samps); +void sound_mock_play(int pair, int channel, int *outptr, int *env_ptr, byte *noise_ptr, int *vol_ptr, int num_samps); +word32 sound_read_c030(dword64 dfcyc); +void sound_write_c030(dword64 dfcyc); + + + +/* sound_driver.c */ +void snddrv_init(void); +void sound_child_fork(int size); +void parent_sound_get_sample_rate(int read_fd); +void snddrv_shutdown(void); +void snddrv_send_sound(int real_samps, int size); +void child_sound_playit(word32 tmp); +void reliable_buf_write(word32 *shm_addr, int pos, int size); +void reliable_zero_write(int amt); +int child_send_samples(byte *ptr, int size); +void child_sound_loop(int read_fd, int write_fd, word32 *shm_addr); + + + +/* woz.c */ +void woz_crc_init(void); +word32 woz_calc_crc32(byte *bptr, dword64 dlen, word32 bytes_to_skip); +void woz_rewrite_crc(Disk *dsk, int min_write_size); +void woz_rewrite_lock(Disk *dsk); +void woz_check_file(Disk *dsk); +void woz_parse_meta(Disk *dsk, int offset, int size); +void woz_parse_info(Disk *dsk, int offset, int size); +void woz_parse_tmap(Disk *dsk, int offset, int size); +void woz_parse_trks(Disk *dsk, int offset, int size); +int woz_add_track(Disk *dsk, int qtr_track, word32 tmap, dword64 dfcyc); +int woz_parse_header(Disk *dsk); +Woz_info *woz_malloc(byte *wozptr, word32 woz_size); +int woz_reopen(Disk *dsk, dword64 dfcyc); +int woz_open(Disk *dsk, dword64 dfcyc); +byte *woz_append_bytes(byte *wozptr, byte *in_bptr, int len); +byte *woz_append_word32(byte *wozptr, word32 val); +int woz_append_chunk(Woz_info *wozinfo_ptr, word32 chunk_id, word32 length, byte *bptr); +byte *woz_append_a_trk(Woz_info *wozinfo_ptr, Disk *dsk, int trk_num, byte *bptr, word32 *num_blocks_ptr, dword64 *tmap_dptr); +Woz_info *woz_new_from_woz(Disk *dsk, int disk_525); +int woz_new(int fd, const char *str, int size_kb); +void woz_maybe_reparse(Disk *dsk); +void woz_set_reparse(Disk *dsk); +void woz_reparse_woz(Disk *dsk); +void woz_remove_a_track(Disk *dsk, word32 qtr_track); +word32 woz_add_a_track(Disk *dsk, word32 qtr_track); + + + +/* unshk.c */ +word32 unshk_get_long4(byte *bptr); +word32 unshk_get_word2(byte *bptr); +word32 unshk_calc_crc(byte *bptr, int size, word32 start_crc); +int unshk_unrle(byte *cptr, int len, word32 rle_delim, byte *ucptr); +void unshk_lzw_clear(Lzw_state *lzw_ptr); +byte *unshk_unlzw(byte *cptr, Lzw_state *lzw_ptr, byte *ucptr, word32 uclen); +void unshk_data(Disk *dsk, byte *cptr, word32 compr_size, byte *ucptr, word32 uncompr_size, word32 thread_format, byte *base_cptr); +void unshk_parse_header(Disk *dsk, byte *cptr, int compr_size, byte *base_cptr); +void unshk(Disk *dsk, const char *name_str); +void unshk_dsk_raw_data(Disk *dsk); + + + +/* undeflate.c */ +void *undeflate_realloc(void *ptr, dword64 dsize); +void *undeflate_malloc(dword64 dsize); +void show_bits(unsigned *llptr, int nl); +void show_huftb(unsigned *tabptr, int bits); +void undeflate_init_len_dist_tab(word32 *tabptr, dword64 drepeats, word32 start); +void undeflate_init_bit_rev_tab(word32 *tabptr, int num); +word32 undeflate_bit_reverse(word32 val, word32 bits); +word32 undeflate_calc_crc32(byte *bptr, word32 len); +byte *undeflate_ensure_dest_len(Disk *dsk, byte *ucptr, word32 len); +void undeflate_add_tab_code(word32 *tabptr, word32 tabsz_lg2, word32 code, word32 entry); +word32 *undeflate_init_fixed_tabs(void); +word32 *undeflate_init_tables(void); +void undeflate_free_tables(void); +void undeflate_check_bit_reverse(void); +word32 *undeflate_build_huff_tab(word32 *tabptr, word32 *entry_ptr, word32 len_size, word32 *bl_count_ptr, int max_bits); +word32 *undeflate_dynamic_table(byte *cptr, word32 *bit_pos_ptr, byte *cptr_base); +byte *undeflate_block(Disk *dsk, byte *cptr, word32 *bit_pos_ptr, byte *cptr_base, byte *cptr_end); +byte *undeflate_gzip_header(Disk *dsk, byte *cptr, word32 compr_size); +void undeflate_gzip(Disk *dsk, const char *name_str); +byte *undeflate_zipfile_blocks(Disk *dsk, byte *cptr, dword64 dcompr_size); +int undeflate_zipfile(Disk *dsk, int fd, dword64 dlocal_header_off, dword64 uncompr_dsize, dword64 compr_dsize); +int undeflate_zipfile_search(byte *bptr, byte *cmp_ptr, int size, int cmp_len, int min_size); +int undeflate_zipfile_make_list(int fd); + + + +/* dynapro.c */ +word32 dynapro_get_word32(byte *bptr); +word32 dynapro_get_word24(byte *bptr); +word32 dynapro_get_word16(byte *bptr); +void dynapro_set_word24(byte *bptr, word32 val); +void dynapro_set_word32(byte *bptr, word32 val); +void dynapro_set_word16(byte *bptr, word32 val); +void dynapro_error(Disk *dsk, const char *fmt, ...); +Dynapro_file *dynapro_alloc_file(void); +void dynapro_free_file(Dynapro_file *fileptr, int check_map); +void dynapro_free_recursive_file(Dynapro_file *fileptr, int check_map); +void dynapro_free_dynapro_info(Disk *dsk); +word32 dynapro_find_free_block_internal(Disk *dsk); +word32 dynapro_find_free_block(Disk *dsk); +byte *dynapro_malloc_file(char *path_ptr, dword64 *dsize_ptr, int extra_size); +void dynapro_join_path_and_file(char *outstr, const char *unix_path, const char *str, int path_max); +word32 dynapro_fill_fileptr_from_prodos(Disk *dsk, Dynapro_file *fileptr, char *buf32_ptr, word32 dir_byte); +word32 dynapro_diff_fileptrs(Dynapro_file *oldfileptr, Dynapro_file *newfileptr); +word32 dynapro_do_one_dir_entry(Disk *dsk, Dynapro_file *fileptr, Dynapro_file *localfile_ptr, char *buf32_ptr, word32 dir_byte); +void dynapro_fix_damaged_entry(Disk *dsk, Dynapro_file *fileptr); +void dynapro_try_fix_damage(Disk *dsk, Dynapro_file *fileptr); +void dynapro_try_fix_damaged_disk(Disk *dsk); +void dynapro_new_unix_path(Dynapro_file *fileptr, const char *path_str, const char *name_str); +Dynapro_file *dynapro_process_write_dir(Disk *dsk, Dynapro_file *parent_ptr, Dynapro_file **head_ptr_ptr, word32 dir_byte); +void dynapro_handle_write_dir(Disk *dsk, Dynapro_file *parent_ptr, Dynapro_file *head_ptr, word32 dir_byte); +word32 dynapro_process_write_file(Disk *dsk, Dynapro_file *fileptr); +void dynapro_handle_write_file(Disk *dsk, Dynapro_file *fileptr); +void dynapro_handle_changed_entry(Disk *dsk, Dynapro_file *fileptr); +word32 dynapro_write_to_unix_file(const char *unix_path, byte *data_ptr, word32 size); +void dynapro_unmap_file(Disk *dsk, Dynapro_file *fileptr); +void dynapro_unlink_file(Dynapro_file *fileptr); +void dynapro_erase_free_entry(Disk *dsk, Dynapro_file *fileptr); +void dynapro_erase_free_dir(Disk *dsk, Dynapro_file *fileptr); +void dynapro_mark_damaged(Disk *dsk, Dynapro_file *fileptr); +int dynapro_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size); +void dynapro_debug_update(Disk *dsk); +void dynapro_debug_map(Disk *dsk, const char *str); +void dynapro_debug_recursive_file_map(Dynapro_file *fileptr, int start); +word32 dynapro_unix_to_prodos_time(const time_t *time_ptr); +int dynapro_create_prodos_name(Dynapro_file *newfileptr, Dynapro_file *matchptr, word32 storage_type); +Dynapro_file *dynapro_new_unix_file(const char *path, Dynapro_file *parent_ptr, Dynapro_file *match_ptr, word32 storage_type); +int dynapro_create_dir(Disk *dsk, char *unix_path, Dynapro_file *parent_ptr, word32 dir_byte); +word32 dynapro_add_file_entry(Disk *dsk, Dynapro_file *fileptr, Dynapro_file *head_ptr, word32 dir_byte, word32 inc); +word32 dynapro_fork_from_unix(Disk *dsk, byte *fptr, word32 *storage_type_ptr, word32 key_block, dword64 dsize); +word32 dynapro_file_from_unix(Disk *dsk, Dynapro_file *fileptr); +word32 dynapro_prep_image(Disk *dsk, const char *dir_path, word32 num_blocks); +word32 dynapro_map_one_file_block(Disk *dsk, Dynapro_file *fileptr, word32 block_num, word32 file_offset, word32 eof); +word32 dynapro_map_file_blocks(Disk *dsk, Dynapro_file *fileptr, word32 block_num, int level, word32 file_offset, word32 eof); +word32 dynapro_map_file(Disk *dsk, Dynapro_file *fileptr, int do_file_data); +word32 dynapro_map_dir_blocks(Disk *dsk, Dynapro_file *fileptr); +word32 dynapro_build_map(Disk *dsk, Dynapro_file *fileptr); +int dynapro_mount(Disk *dsk, char *dir_path, word32 num_blocks); + + + +/* dyna_type.c */ +word32 dynatype_scan_extensions(const char *str); +word32 dynatype_find_prodos_type(const char *str); +const char *dynatype_find_file_type(word32 file_type); +word32 dynatype_detect_file_type(Dynapro_file *fileptr, const char *path_ptr, word32 storage_type); +int dynatype_get_extension(const char *str, char *out_ptr, int buf_len); +int dynatype_comma_arg(const char *str, word32 *type_or_aux_ptr); +void dynatype_fix_unix_name(Dynapro_file *fileptr, char *outbuf_ptr, int path_max); + + + +/* dyna_filt.c */ + + + +/* dyna_validate.c */ +word32 dynapro_validate_header(Disk *dsk, Dynapro_file *fileptr, word32 dir_byte, word32 parent_dir_byte); +void dynapro_validate_init_freeblks(byte *freeblks_ptr, word32 num_blocks); +word32 dynapro_validate_freeblk(Disk *dsk, byte *freeblks_ptr, word32 block); +word32 dynapro_validate_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, word32 eof, int level_first); +word32 dynapro_validate_forked_file(Disk *dsk, byte *freeblks_ptr, word32 block_num, word32 eof); +word32 dynapro_validate_dir(Disk *dsk, byte *freeblks_ptr, word32 dir_byte, word32 parent_dir_byte, word32 exp_blocks_used); +int dynapro_validate_disk(Disk *dsk); +void dynapro_validate_any_image(Disk *dsk); + + + +/* applesingle.c */ +word32 applesingle_get_be32(const byte *bptr); +word32 applesingle_get_be16(const byte *bptr); +void applesingle_set_be32(byte *bptr, word32 val); +void applesingle_set_be16(byte *bptr, word32 val); +word32 applesingle_map_from_prodos(Disk *dsk, Dynapro_file *fileptr, int do_file_data); +word32 applesingle_from_unix(Disk *dsk, Dynapro_file *fileptr, byte *fptr, dword64 dsize); +word32 applesingle_make_prodos_fork(Disk *dsk, byte *fptr, byte *tptr, word32 length); + + + +/* video.c */ +void video_set_red_mask(word32 red_mask); +void video_set_green_mask(word32 green_mask); +void video_set_blue_mask(word32 blue_mask); +void video_set_alpha_mask(word32 alpha_mask); +void video_set_mask_and_shift(word32 x_mask, word32 *mask_ptr, int *shift_left_ptr, int *shift_right_ptr); +void video_set_palette(void); +void video_set_redraw_skip_amt(int amt); +Kimage *video_get_kimage(int win_id); +char *video_get_status_ptr(int line); +void video_set_x_refresh_needed(Kimage *kimage_ptr, int do_refresh); +int video_get_active(Kimage *kimage_ptr); +void video_set_active(Kimage *kimage_ptr, int active); +void video_init(int mdepth, int screen_width, int screen_height, int no_scale_window); +void video_init_kimage(Kimage *kimage_ptr, int width, int height, int screen_width, int screen_height); +void show_a2_line_stuff(void); +void video_reset(void); +void video_update(void); +word32 video_all_stat_to_filt_stat(int line, word32 new_all_stat); +void change_display_mode(dword64 dfcyc); +void video_add_new_all_stat(dword64 dfcyc, word32 lines_since_vbl); +void change_border_color(dword64 dfcyc, int val); +void update_border_info(void); +void update_border_line(int st_line_offset, int end_line_offset, int color); +void video_border_pixel_write(Kimage *kimage_ptr, int starty, int num_lines, int color, int st_off, int end_off); +word32 video_get_ch_mask(word32 mem_ptr, word32 filt_stat, int reparse); +void video_update_edges(int line, int left, int right, const char *str); +void redraw_changed_text(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat); +void redraw_changed_string(const byte *bptr, word32 line_bytes, word32 ch_mask, word32 *in_wptr, word32 bg_pixel, word32 fg_pixel, int pixels_per_line, int dbl); +void redraw_changed_gr(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat); +void video_hgr_line_segment(byte *slow_mem_ptr, word32 *wptr, int start_byte, int end_byte, int pixels_per_line, word32 filt_stat); +void redraw_changed_hgr(word32 line_bytes, int reparse, word32 *in_wptr, int pixels_per_line, word32 filt_stat); +int video_rebuild_super_hires_palette(int bank, word32 scan_info, int line, int reparse); +word32 redraw_changed_super_hires_oneline(int bank, word32 *in_wptr, int pixels_per_line, int y, int scan, word32 ch_mask); +void redraw_changed_super_hires_bank(int bank, int start_line, int reparse, word32 *wptr, int pixels_per_line); +void redraw_changed_super_hires(word32 line_bytes, int reparse, word32 *wptr, int pixels_per_line, word32 filt_stat); +void video_copy_changed2(void); +void video_update_event_line(int line); +void video_force_reparse(void); +void video_update_through_line(int line); +void video_do_partial_line(word32 lines_since_vbl, int end, word32 cur_all_stat); +void video_refresh_line(word32 line_bytes, int must_reparse, word32 filt_stat); +void prepare_a2_font(void); +void prepare_a2_romx_font(byte *font_ptr); +void video_add_rect(Kimage *kimage_ptr, int x, int y, int width, int height); +void video_add_a2_rect(int start_line, int end_line, int left_pix, int right_pix); +void video_form_change_rects(void); +int video_get_a2_width(Kimage *kimage_ptr); +int video_get_x_width(Kimage *kimage_ptr); +int video_get_a2_height(Kimage *kimage_ptr); +int video_get_x_height(Kimage *kimage_ptr); +int video_get_x_xpos(Kimage *kimage_ptr); +int video_get_x_ypos(Kimage *kimage_ptr); +void video_update_xpos_ypos(Kimage *kimage_ptr, int x_xpos, int x_ypos); +int video_change_aspect_needed(Kimage *kimage_ptr, int x_width, int x_height); +void video_update_status_enable(Kimage *kimage_ptr); +int video_out_query(Kimage *kimage_ptr); +void video_out_done(Kimage *kimage_ptr); +int video_out_data(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr, int pos); +int video_out_data_intscaled(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr); +int video_out_data_scaled(void *vptr, Kimage *kimage_ptr, int out_width_act, Change_rect *rectptr); +word32 video_scale_calc_frac(int pos, word32 max, word32 frac_inc, word32 frac_inc_inv); +void video_update_scale(Kimage *kimage_ptr, int out_width, int out_height, int must_update); +int video_scale_mouse_x(Kimage *kimage_ptr, int raw_x, int x_width); +int video_scale_mouse_y(Kimage *kimage_ptr, int raw_y, int y_height); +int video_unscale_mouse_x(Kimage *kimage_ptr, int a2_x, int x_width); +int video_unscale_mouse_y(Kimage *kimage_ptr, int a2_y, int y_height); +void video_update_color_raw(int bank, int col_num, int a2_color); +void video_update_status_line(int line, const char *string); +void video_draw_a2_string(int line, const byte *bptr); +void video_show_debug_info(void); +word32 read_video_data(dword64 dfcyc); +word32 float_bus(dword64 dfcyc); +word32 float_bus_lines(dword64 dfcyc, word32 lines_since_vbl); + + + +/* voc.c */ +word32 voc_devsel_read(word32 loc, dword64 dfcyc); +void voc_devsel_write(word32 loc, word32 val, dword64 dfcyc); +void voc_iosel_c300_write(word32 loc, word32 val, dword64 dfcyc); +void voc_reset(void); +word32 voc_read_reg0(dword64 dfcyc); +void voc_update_interlace(dword64 dfcyc); + + diff --git a/gsplus/src/protos_macdriver.h b/gsplus/src/protos_macdriver.h new file mode 100644 index 0000000..1213dee --- /dev/null +++ b/gsplus/src/protos_macdriver.h @@ -0,0 +1,43 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2019 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + + +/* END_HDR */ + +/* macdriver.c */ +pascal OSStatus quit_event_handler(EventHandlerCallRef call_ref, EventRef event, void *ignore); +void show_simple_alert(char *str1, char *str2, char *str3, int num); +void x_dialog_create_kegs_conf(const char *str); +int x_show_alert(int is_fatal, const char *str); +pascal OSStatus my_cmd_handler(EventHandlerCallRef handlerRef, EventRef event, void *userdata); +void update_window(void); +void show_event(UInt32 event_class, UInt32 event_kind, int handled); +pascal OSStatus my_win_handler(EventHandlerCallRef handlerRef, EventRef event, void *userdata); +pascal OSStatus dummy_event_handler(EventHandlerCallRef call_ref, EventRef in_event, void *ignore); +void mac_update_modifiers(word32 state); +void mac_warp_mouse(void); +void check_input_events(void); +void temp_run_application_event_loop(void); +int main(int argc, char *argv[]); +void x_update_color(int col_num, int red, int green, int blue, word32 rgb); +void x_update_physical_colormap(void); +void show_xcolor_array(void); +void xdriver_end(void); +void x_get_kimage(Kimage *kimage_ptr); +void dev_video_init(void); +void x_redraw_status_lines(void); +void x_push_kimage(Kimage *kimage_ptr, int destx, int desty, int srcx, int srcy, int width, int height); +void x_push_done(void); +void x_auto_repeat_on(int must); +void x_auto_repeat_off(int must); +void x_hide_pointer(int do_hide); +void x_full_screen(int do_full); +void update_main_window_size(void); + diff --git a/gsplus/src/protos_macsnd_driver.h b/gsplus/src/protos_macsnd_driver.h new file mode 100644 index 0000000..87748a1 --- /dev/null +++ b/gsplus/src/protos_macsnd_driver.h @@ -0,0 +1,18 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + + +/* END_HDR */ + +/* macsnd_driver.c */ +int mac_send_audio(byte *ptr, int in_size); +void macsnd_init(void); + + diff --git a/gsplus/src/protos_pulseaudio_driver.h b/gsplus/src/protos_pulseaudio_driver.h new file mode 100644 index 0000000..9ccc457 --- /dev/null +++ b/gsplus/src/protos_pulseaudio_driver.h @@ -0,0 +1,22 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2020 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + + +/* END_HDR */ + +/* pulseaudio_driver.c */ +int pulse_audio_send_audio(byte *ptr, int in_size); +void pulse_audio_main_events(void); +void pulse_audio_write_to_stream(int dbg_count, int in_sz); +int pulse_audio_start_stream(void); +int pulse_audio_do_init(void); +int pulse_audio_init(void); +void pulse_audio_shutdown(void); + diff --git a/gsplus/src/protos_windriver.h b/gsplus/src/protos_windriver.h new file mode 100644 index 0000000..51ebc3d --- /dev/null +++ b/gsplus/src/protos_windriver.h @@ -0,0 +1,55 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2022 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// $KmKId: protos_windriver.h,v 1.15 2023-05-17 22:37:57+00 kentd Exp $ + +/* END_HDR */ + +/* windriver.c */ +Window_info *win_find_win_info_ptr(HWND hwnd); +void win_hide_pointer(Window_info *win_info_ptr, int do_hide); +int win_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, int button_states, int buttons_valid); +void win_event_mouse(HWND hwnd, WPARAM wParam, LPARAM lParam); +void win_event_key(HWND hwnd, WPARAM wParam, LPARAM lParam, int down); +void win_event_redraw(HWND hwnd); +void win_event_destroy(HWND hwnd); +void win_event_size(HWND hwnd, WPARAM wParam, LPARAM lParam); +void win_event_minmaxinfo(HWND hwnd, LPARAM lParam); +void win_event_focus(HWND hwnd, int gain_focus); +LRESULT CALLBACK win_event_handler(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam); +int main(int argc, char **argv); +void check_input_events(void); +void win_video_init(int mdepth); +void win_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str, int mdepth); +void win_create_window(Window_info *win_info_ptr); +void xdriver_end(void); +void win_resize_window(Window_info *win_info_ptr); +void x_update_display(Window_info *win_info_ptr); +void x_hide_pointer(int do_hide); +int opendir_int(DIR *dirp, const char *in_filename); +DIR *opendir(const char *in_filename); +struct dirent *readdir(DIR *dirp); +int closedir(DIR *dirp); +int lstat(const char *path, struct stat *bufptr); +int ftruncate(int fd, word32 length); + + + +/* win32snd_driver.c */ +void win32snd_init(word32 *shmaddr); +void win32snd_shutdown(void); +void CALLBACK handle_wav_snd(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2); +void check_wave_error(int res, char *str); +void child_sound_init_win32(void); +void win32snd_set_playing(int snd_playing); +void win32_send_audio2(byte *ptr, int size); +int win32_send_audio(byte *ptr, int in_size); + + diff --git a/gsplus/src/protos_xdriver.h b/gsplus/src/protos_xdriver.h new file mode 100644 index 0000000..120165e --- /dev/null +++ b/gsplus/src/protos_xdriver.h @@ -0,0 +1,49 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + + +/* END_HDR */ + +/* xdriver.c */ +int main(int argc, char **argv); +int my_error_handler(Display *display, XErrorEvent *ev); +void xdriver_end(void); +void x_try_xset_r(void); +void x_badpipe(int signum); +int kegs_x_io_error_handler(Display *display); +int x_video_get_mdepth(void); +int x_try_find_visual(int depth, int screen_num); +void x_video_init(void); +void x_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str); +void x_create_window(Window_info *win_info_ptr); +int xhandle_shm_error(Display *display, XErrorEvent *event); +void x_allocate_window_data(Window_info *win_info_ptr); +void get_shm(Window_info *win_info_ptr, int width, int height); +void get_ximage(Window_info *win_info_ptr, int width, int height); +void x_set_size_hints(Window_info *win_info_ptr); +void x_resize_window(Window_info *win_info_ptr); +void x_update_display(Window_info *win_info_ptr); +Window_info *x_find_xwin(Window in_win); +void x_send_copy_data(Window_info *win_info_ptr); +void x_handle_copy(XSelectionRequestEvent *req_ev_ptr); +void x_handle_targets(XSelectionRequestEvent *req_ev_ptr); +void x_request_paste_data(Window_info *win_info_ptr); +void x_handle_paste(Window w, Atom property); +int x_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, int button_states, int buttons_valid); +void x_input_events(void); +void x_hide_pointer(Window_info *win_info_ptr, int do_hide); +void x_handle_keysym(XEvent *xev_in); +int x_keysym_to_a2code(Window_info *win_info_ptr, int keysym, int is_up); +void x_update_modifier_state(Window_info *win_info_ptr, int state); +void x_auto_repeat_on(int must); +void x_auto_repeat_off(int must); +void x_full_screen(int do_full); + + diff --git a/gsplus/src/pulseaudio_driver.c b/gsplus/src/pulseaudio_driver.c new file mode 100644 index 0000000..f8d7474 --- /dev/null +++ b/gsplus/src/pulseaudio_driver.c @@ -0,0 +1,364 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2020 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#ifdef PULSE_AUDIO +// Ignore entire file if PULSE_AUDIO is not defined! + +// Some ideas from Sample code: +// https://github.com/gavv/snippets/blob/master/pa/pa_play_async_poll.c + +#include "defc.h" +#include "sound.h" +#include "protos_pulseaudio_driver.h" + +#include +#include + +#define PULSE_REBUF_SIZE (64*1024) + +word32 g_pulseaudio_rebuf[PULSE_REBUF_SIZE]; +volatile int g_pulseaudio_rd_pos; +volatile int g_pulseaudio_wr_pos; +volatile int g_pulseaudio_playing = 0; + +extern int Verbose; + +extern int g_preferred_rate; +extern int g_audio_enable; +extern word32 *g_sound_shm_addr; +extern int g_sound_min_samples; +extern int g_sound_max_multiplier; +extern int g_sound_size; +extern word32 g_vbl_count; + +int g_call_num = 0; + +pa_mainloop *g_pa_mainloop_ptr = 0; +pa_context *g_pa_context_ptr = 0; +pa_stream *g_pa_stream_ptr = 0; +pa_sample_spec g_pa_sample_spec = { 0 }; +pa_buffer_attr g_pa_buffer_attr = { 0 }; + + +int +pulse_audio_send_audio(byte *ptr, int in_size) +{ + word32 *wptr, *pa_wptr; + int samps, sample_num; + int i; + + samps = in_size / 4; + wptr = (word32 *)ptr; + sample_num = g_pulseaudio_wr_pos; + pa_wptr = &(g_pulseaudio_rebuf[0]); + for(i = 0; i < samps; i++) { + pa_wptr[sample_num] = *wptr++; + sample_num++; + if(sample_num >= PULSE_REBUF_SIZE) { + sample_num = 0; + } + } + + g_pulseaudio_wr_pos = sample_num; + + pulse_audio_main_events(); + + return in_size; +} + +void +pulse_audio_main_events() +{ + pa_stream_state_t stream_state; + pa_context_state_t context_state; + int count, num, sz, do_write; + + count = 0; + do_write = 1; + while(1) { + // Do a few mainloop cycles to see if samples are needed + num = pa_mainloop_iterate(g_pa_mainloop_ptr, 0, 0); + //printf("pa_mainloop_iterate ret:%d count:%d\n", num, count); + if(num < 0) { + return; + } + context_state = pa_context_get_state(g_pa_context_ptr); + if((context_state == PA_CONTEXT_FAILED) || + (context_state == PA_CONTEXT_TERMINATED)) { + printf("context_state is bad: %d\n", context_state); + g_audio_enable = 0; + return; + } + stream_state = pa_stream_get_state(g_pa_stream_ptr); + if((stream_state == PA_STREAM_FAILED) || + (stream_state == PA_STREAM_TERMINATED)) { + printf("stream state bad: %d\n", stream_state); + g_audio_enable = 0; + return; + } + if(do_write) { + sz = pa_stream_writable_size(g_pa_stream_ptr); + if(sz > 0) { + pulse_audio_write_to_stream(count, sz); + do_write = 0; + } + } + count++; + if(count > 50) { + printf("pulse_audio_main_events() looped %d times\n", + count); + return; + } + if(num == 0) { // Nothing else to do + return; + } + } +} + +void +pulse_audio_write_to_stream(int dbg_count, int in_sz) +{ + word32 *wptr; + void *vptr; + // const pa_timing_info *pa_timing_info_ptr; + // pa_usec_t pa_latency; + size_t sz; + int num_samps, sample_num, samps_avail, err, samps_needed; + int min_samples, max_samples; + int i; + + samps_needed = in_sz / 4; + sample_num = g_pulseaudio_rd_pos; + samps_avail = (g_pulseaudio_wr_pos - sample_num) & + (PULSE_REBUF_SIZE - 1); + min_samples = g_sound_min_samples; + max_samples = min_samples * g_sound_max_multiplier; + if(samps_needed > min_samples) { + min_samples = samps_needed; + } +#if 0 + if(samps_needed > samps_avail) { + // We don't have enough samples, must pause + g_pulseaudio_playing = 0; + sample_num = g_pulseaudio_wr_pos; // Eat remaining samps + } +#endif + if((g_pulseaudio_playing == 0) && (samps_avail > min_samples)) { + // We can unpause + g_pulseaudio_playing = 1; + } + if(g_pulseaudio_playing && (samps_avail > max_samples)) { + printf("JUMP SAMPLE_NUM by %d samples!\n", max_samples / 2); + sample_num += (max_samples / 2); + sample_num = sample_num & (PULSE_REBUF_SIZE - 1); + } + +#if 0 + if(g_call_num < 100) { + printf("call_num:%d playing:%d g_vbl_count:%d samps_needed:%d, " + "samps_avail:%d\n", g_call_num, g_pulseaudio_playing, + g_vbl_count, samps_needed, samps_avail); + } +#endif + g_call_num++; + num_samps = MIN(samps_avail, samps_needed); + if(g_pulseaudio_playing) { + vptr = 0; // Let it allocate for us + sz = num_samps * 4; + err = pa_stream_begin_write(g_pa_stream_ptr, &vptr, &sz); + wptr = vptr; + if(err) { + g_audio_enable = 0; + printf("pa_stream_begin_write failed: %s\n", + pa_strerror(err)); + return; + } + num_samps = sz / 4; + for(i = 0; i < num_samps; i++) { + wptr[i] = g_pulseaudio_rebuf[sample_num]; + sample_num++; + if(sample_num >= PULSE_REBUF_SIZE) { + sample_num = 0; + } + } + } else { + err = pa_stream_cancel_write(g_pa_stream_ptr); + // Just get out...don't let us get further behind by sending + // silence frames. + return; + } + + g_pulseaudio_rd_pos = sample_num; + +#if 0 + pa_timing_info_ptr = pa_stream_get_timing_info(g_pa_stream_ptr); + err = pa_stream_get_latency(g_pa_stream_ptr, &pa_latency, 0); + printf(" will send %d samples to the stream, write_index:%lld, " + "latency:%lld\n", num_samps * 4, + (word64)pa_timing_info_ptr->write_index, (word64)pa_latency); +#endif + err = pa_stream_write(g_pa_stream_ptr, wptr, num_samps * 4, 0, 0, + PA_SEEK_RELATIVE); + if(err) { + printf("pa_stream_write: %s\n", pa_strerror(err)); + g_audio_enable = 0; + } +} + +int +pulse_audio_start_stream() +{ + int flags, ret; + + g_pa_sample_spec.format = PA_SAMPLE_S16LE; + g_pa_sample_spec.rate = g_preferred_rate; + g_pa_sample_spec.channels = 2; + printf("Set requested rate=%d\n", g_pa_sample_spec.rate); + g_pa_stream_ptr = pa_stream_new(g_pa_context_ptr, "KEGS", + &g_pa_sample_spec, 0); + if(!g_pa_stream_ptr) { + printf("pa_stream_new failed\n"); + return 1; + } + + g_pa_buffer_attr.maxlength = -1; // Maximum server buffer + g_pa_buffer_attr.tlength = 4*g_preferred_rate/10; // 1/10th sec + //g_pa_buffer_attr.prebuf = 4*g_preferred_rate/100; // 1/100th sec + g_pa_buffer_attr.prebuf = -1; + g_pa_buffer_attr.minreq = 4*g_preferred_rate/60; // 1/60th sec + + flags = PA_STREAM_ADJUST_LATENCY; + //flags = PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | + // PA_STREAM_ADJUST_LATENCY; + // PA_STREAM_AUTO_TIMING_UPDATE and PA_STREAM_INTERPOLATE_TIMING are + // to get latency info from the server. PA_STREAM_ADJUST_LATENCY + // means the total latency including the server output sink buffer + // tries to be tlength + + ret = pa_stream_connect_playback(g_pa_stream_ptr, 0, &g_pa_buffer_attr, + flags, 0, 0); + if(ret) { + printf("pa_stream_connect_playback failed: %d\n", ret); + return ret; + } + + return 0; // Success! +} + +int +pulse_audio_do_init() +{ + pa_stream_state_t stream_state; + pa_context_state_t context_state; + int ret, count, num; + + g_pa_mainloop_ptr = pa_mainloop_new(); + g_pa_context_ptr = pa_context_new( + pa_mainloop_get_api(g_pa_mainloop_ptr), "KEGS"); + if(!g_pa_context_ptr) { + printf("Pulse Audio pa_context_new() failed\n"); + return 1; + } + + ret = pa_context_connect(g_pa_context_ptr, 0, 0, 0); + if(ret != 0) { + printf("pa_context_connect failed: %d\n", ret); + return 1; + } + + count = 0; + while(1) { + // Do a few mainloop cycles to get stream initialized + num = pa_mainloop_iterate(g_pa_mainloop_ptr, 0, 0); +#if 0 + printf("pa_mainloop_iterate ret: %d, count:%d g_pa_stream_ptr:" + "%p\n", num, count, g_pa_stream_ptr); +#endif + if(num < 0) { + return 1; + } + if(num == 0) { + usleep(10*1000); + if(count++ > 50) { + // Waited more than 500ms, just give up + printf("Timed out waiting for Pulse Audio to " + "start\n"); + return 1; + } + } + // See if context is ready + context_state = pa_context_get_state(g_pa_context_ptr); + if((context_state == PA_CONTEXT_FAILED) || + (context_state == PA_CONTEXT_TERMINATED)) { + printf("context_state is bad: %d\n", context_state); + return 1; + } + if((context_state == PA_CONTEXT_READY) && !g_pa_stream_ptr) { + ret = pulse_audio_start_stream(); + if(ret) { + return ret; + } + } + if(g_pa_stream_ptr) { + stream_state = pa_stream_get_state(g_pa_stream_ptr); + if((stream_state == PA_STREAM_FAILED) || + (stream_state == PA_STREAM_TERMINATED)){ + printf("stream state bad: %d\n", stream_state); + return 1; + } + if(stream_state == PA_STREAM_READY) { + printf("Pulse Audio stream is now ready!\n"); + return 0; + } + } + } +} + +int +pulse_audio_init() +{ + int ret; + + g_pulseaudio_rd_pos = 0; + g_pulseaudio_wr_pos = 0; + + ret = pulse_audio_do_init(); + // printf("pulse_audio_init ret:%d\n", ret); + if(ret != 0) { + // Free structures, disable sound + if(g_pa_stream_ptr) { + pa_stream_disconnect(g_pa_stream_ptr); + pa_stream_unref(g_pa_stream_ptr); + g_pa_stream_ptr = 0; + } + if(g_pa_context_ptr) { + pa_context_disconnect(g_pa_context_ptr); + pa_context_unref(g_pa_context_ptr); + g_pa_context_ptr = 0; + } + if(g_pa_mainloop_ptr) { + pa_mainloop_free(g_pa_mainloop_ptr); + g_pa_mainloop_ptr = 0; + } + return ret; + } + + sound_set_audio_rate(g_preferred_rate); + + return 0; +} + +void +pulse_audio_shutdown() +{ + printf("pulse_audio_shutdown\n"); +} + +#endif /* PULSE_AUDIO */ diff --git a/gsplus/src/scc.c b/gsplus/src/scc.c new file mode 100644 index 0000000..ed4ec26 --- /dev/null +++ b/gsplus/src/scc.c @@ -0,0 +1,1389 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2025 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// Driver for the Zilog SCC Z8530, which implements two channels (A,B) of +// serial ports, controlled by $C038-$C03B + +#include "defc.h" + +extern int Verbose; +extern int g_code_yellow; +extern dword64 g_cur_dfcyc; +extern int g_serial_cfg[2]; +extern int g_serial_mask[2]; +extern char *g_serial_remote_ip[2]; +extern int g_serial_remote_port[2]; +extern char *g_serial_device[2]; +extern int g_serial_win_device[2]; +extern int g_irq_pending; + +/* scc port 0 == channel A = slot 1 = c039/c03b */ +/* port 1 == channel B = slot 2 = c038/c03a */ + +#include "scc.h" +#define SCC_R14_DPLL_SOURCE_BRG 0x100 +#define SCC_R14_DPLL_SOURCE_RTXC 0x200 + +#define SCC_DCYCS_PER_PCLK ((DCYCS_1_MHZ) / ((DCYCS_28_MHZ) /8)) +#define SCC_DCYCS_PER_XTAL ((DCYCS_1_MHZ) / 3686400.0) + +// PCLK is 3.5795MHz + +#define SCC_BR_EVENT 1 +#define SCC_TX_EVENT 2 +#define SCC_RX_EVENT 3 +#define SCC_MAKE_EVENT(port, a) (((a) << 1) + (port)) + +Scc g_scc[2]; + +int g_baud_table[] = { + 110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 +}; + +int g_scc_overflow = 0; +int g_scc_init = 0; + +// cur_state >= 0 and matches g_serial_cfg[port]: port is in that mode +// cur_state = -1: port is in no particular mode and should go to g_serial_cfg[] +// cur_state = -2: port failed to enter g_serial_cfg[], do not try again until +// something changes + +void +scc_init() +{ + Scc *scc_ptr; + int i, j; + + for(i = 0; i < 2; i++) { + scc_ptr = &(g_scc[i]); + memset(scc_ptr, 0, sizeof(*scc_ptr)); + scc_ptr->cur_state = -1; + scc_ptr->modem_state = 0; + scc_ptr->sockfd = INVALID_SOCKET; + scc_ptr->rdwrfd = INVALID_SOCKET; + scc_ptr->sockaddr_ptr = 0; + scc_ptr->sockaddr_size = 0; + scc_ptr->unix_dev_fd = -1; + scc_ptr->win_com_handle = 0; + scc_ptr->win_dcb_ptr = 0; + scc_ptr->br_event_pending = 0; + scc_ptr->rx_event_pending = 0; + scc_ptr->tx_event_pending = 0; + scc_ptr->char_size = 8; + scc_ptr->baud_rate = 9600; + scc_ptr->telnet_mode = 0; + scc_ptr->telnet_iac = 0; + scc_ptr->out_char_dfcyc = 0; + scc_ptr->socket_error = 0; + scc_ptr->socket_num_rings = 0; + scc_ptr->socket_last_ring_dfcyc = 0; + scc_ptr->modem_mode = 0; + scc_ptr->modem_plus_mode = 0; + scc_ptr->modem_s0_val = 0; + scc_ptr->modem_s2_val = '+'; + scc_ptr->modem_cmd_len = 0; + scc_ptr->modem_out_portnum = 23; + scc_ptr->modem_cmd_str[0] = 0; + for(j = 0; j < 2; j++) { + scc_ptr->telnet_local_mode[j] = 0; + scc_ptr->telnet_remote_mode[j] = 0; + scc_ptr->telnet_reqwill_mode[j] = 0; + scc_ptr->telnet_reqdo_mode[j] = 0; + } + } + + g_scc_init = 1; +} + +void +scc_reset() +{ + Scc *scc_ptr; + int i; + + if(!g_scc_init) { + halt_printf("scc_reset called before init\n"); + return; + } + for(i = 0; i < 2; i++) { + scc_ptr = &(g_scc[i]); + + scc_ptr->mode = 0; + scc_ptr->reg_ptr = 0; + scc_ptr->in_rdptr = 0; + scc_ptr->in_wrptr = 0; + scc_ptr->out_rdptr = 0; + scc_ptr->out_wrptr = 0; + scc_ptr->dcd = 1 - i; // 1 for slot 1, 0 for slot 2 + scc_ptr->wantint_rx = 0; + scc_ptr->wantint_tx = 0; + scc_ptr->wantint_zerocnt = 0; + scc_ptr->read_called_this_vbl = 0; + scc_ptr->write_called_this_vbl = 0; + scc_evaluate_ints(i); + scc_hard_reset_port(i); + } +} + +void +scc_hard_reset_port(int port) +{ + Scc *scc_ptr; + + scc_reset_port(port); + + scc_ptr = &(g_scc[port]); + scc_ptr->reg[14] = 0; /* zero bottom two bits */ + scc_ptr->reg[13] = 0; + scc_ptr->reg[12] = 0; + scc_ptr->reg[11] = 0x08; + scc_ptr->reg[10] = 0; + scc_ptr->reg[7] = 0; + scc_ptr->reg[6] = 0; + scc_ptr->reg[5] = 0; + scc_ptr->reg[4] = 0x04; + scc_ptr->reg[3] = 0; + scc_ptr->reg[2] = 0; + scc_ptr->reg[1] = 0; + + /* HACK HACK: */ + g_scc[0].reg[9] = 0; /* Clear all interrupts */ + + scc_evaluate_ints(port); + + scc_regen_clocks(port); +} + +void +scc_reset_port(int port) +{ + Scc *scc_ptr; + + scc_ptr = &(g_scc[port]); + scc_ptr->reg[15] = 0xf8; + scc_ptr->reg[14] &= 0x03; /* 0 most (including >= 0x100) bits */ + scc_ptr->reg[10] = 0; + scc_ptr->reg[5] &= 0x65; /* leave tx bits and sdlc/crc bits */ + scc_ptr->reg[4] |= 0x04; /* Set async mode */ + scc_ptr->reg[3] &= 0xfe; /* clear receiver enable */ + scc_ptr->reg[1] &= 0xfe; /* clear ext int enable */ + + scc_ptr->br_is_zero = 0; + scc_ptr->tx_buf_empty = 1; + + scc_ptr->wantint_rx = 0; + scc_ptr->wantint_tx = 0; + scc_ptr->wantint_zerocnt = 0; + + scc_ptr->rx_queue_depth = 0; + + scc_evaluate_ints(port); + + scc_regen_clocks(port); + + scc_clr_tx_int(port); + scc_clr_rx_int(port); +} + +void +scc_regen_clocks(int port) +{ + Scc *scc_ptr; + double br_dcycs, tx_dcycs, rx_dcycs, rx_char_size, tx_char_size; + double clock_mult, dpll_dcycs; + word32 reg4, reg11, reg14, br_const, max_diff, diff; + int baud, cur_state, baud_entries, pos; + int i; + + /* Always do baud rate generator */ + scc_ptr = &(g_scc[port]); + br_const = (scc_ptr->reg[13] << 8) + scc_ptr->reg[12]; + br_const += 2; /* counts down past 0 */ + + reg4 = scc_ptr->reg[4]; // Transmit/Receive misc params + clock_mult = 1.0; + switch((reg4 >> 6) & 3) { + case 0: /* x1 */ + clock_mult = 1.0; + break; + case 1: /* x16 */ + clock_mult = 16.0; + break; + case 2: /* x32 */ + clock_mult = 32.0; + break; + case 3: /* x64 */ + clock_mult = 64.0; + break; + } + + br_dcycs = 0.01; + reg14 = scc_ptr->reg[14]; + if(reg14 & 0x1) { + br_dcycs = SCC_DCYCS_PER_XTAL; + if(reg14 & 0x2) { + br_dcycs = SCC_DCYCS_PER_PCLK; + } + } + + br_dcycs = br_dcycs * (double)br_const * 2.0; + + dpll_dcycs = 0.1; + if(reg14 & SCC_R14_DPLL_SOURCE_BRG) { + dpll_dcycs = br_dcycs; + } else if(reg14 & SCC_R14_DPLL_SOURCE_RTXC) { + dpll_dcycs = SCC_DCYCS_PER_XTAL; + } + + tx_dcycs = 1; + reg11 = scc_ptr->reg[11]; + switch((reg11 >> 3) & 3) { + case 0: // /RTxC pin + tx_dcycs = SCC_DCYCS_PER_XTAL; + break; + case 2: // BR generator output + tx_dcycs = br_dcycs; + break; + case 3: // DPLL output + tx_dcycs = dpll_dcycs; + break; + } + + tx_dcycs = tx_dcycs * clock_mult; + + rx_dcycs = 1; + switch((reg11 >> 5) & 3) { + case 0: // /RTxC pin + rx_dcycs = SCC_DCYCS_PER_XTAL; + break; + case 2: // BR generator output + rx_dcycs = br_dcycs; + break; + case 3: // DPLL output + rx_dcycs = dpll_dcycs; + break; + } + rx_dcycs = rx_dcycs * clock_mult; + + tx_char_size = 8.0; + switch((scc_ptr->reg[5] >> 5) & 0x3) { + case 0: // 5 bits + tx_char_size = 5.0; + break; + case 1: // 7 bits + tx_char_size = 7.0; + break; + case 2: // 6 bits + tx_char_size = 6.0; + break; + } + + scc_ptr->char_size = (int)tx_char_size; + + switch((scc_ptr->reg[4] >> 2) & 0x3) { + case 1: // 1 stop bit + tx_char_size += 2.0; // 1 stop + 1 start bit + break; + case 2: // 1.5 stop bit + tx_char_size += 2.5; // 1.5 stop + 1 start bit + break; + case 3: // 2 stop bits + tx_char_size += 3.0; // 2.0 stop + 1 start bit + break; + } + + if(scc_ptr->reg[4] & 1) { + // parity enabled + tx_char_size += 1.0; + } + + if(scc_ptr->reg[14] & 0x10) { + /* loopback mode, make it go faster...*/ + rx_char_size = 1.0; + tx_char_size = 1.0; + } + + rx_char_size = tx_char_size; /* HACK */ + + baud = (int)(DCYCS_1_MHZ / tx_dcycs); + max_diff = 5000000; + pos = 0; + baud_entries = sizeof(g_baud_table)/sizeof(g_baud_table[0]); + for(i = 0; i < baud_entries; i++) { + diff = abs(g_baud_table[i] - baud); + if(diff < max_diff) { + pos = i; + max_diff = diff; + } + } + + scc_ptr->baud_rate = g_baud_table[pos]; + + scc_ptr->br_dcycs = br_dcycs; + scc_ptr->tx_dcycs = tx_dcycs * tx_char_size; + scc_ptr->rx_dcycs = rx_dcycs * rx_char_size; + + cur_state = scc_ptr->cur_state; + if(cur_state == 0) { // real serial ports +#ifdef _WIN32 + scc_serial_win_change_params(port); +#else + scc_serial_unix_change_params(port); +#endif + } +} + +void +scc_port_close(int port) +{ + Scc *scc_ptr; + + scc_ptr = &(g_scc[port]); + +#ifdef _WIN32 + scc_serial_win_close(port); +#else + scc_serial_unix_close(port); +#endif + scc_socket_close(port); + + scc_ptr->cur_state = -1; // Nothing open +} + +void +scc_port_open(dword64 dfcyc, int port) +{ + int cfg; + + cfg = g_serial_cfg[port]; + printf("scc_port_open port:%d cfg:%d\n", port, cfg); + + if(cfg == 0) { // Real host serial port +#ifdef _WIN32 + scc_serial_win_open(port); +#else + scc_serial_unix_open(port); +#endif + } else if(cfg >= 1) { + scc_socket_open(dfcyc, port, cfg); + } + printf(" open socketfd:%ld\n", (long)g_scc[port].sockfd); +} + +int +scc_is_port_closed(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + + // Returns 1 is the port is closed (not working). Returns 0 + // if the port is open. Tries to open the port if it is not in error + scc_ptr = &(g_scc[port]); + if(scc_ptr->cur_state == -1) { + scc_port_open(dfcyc, port); + } + if(scc_ptr->cur_state < 0) { + scc_ptr->cur_state = -2; + //printf("scc_is_port_closed p:%d returning 0\n", port); + return 1; // Not open + } + return 0; // Port is open! +} + +char * +scc_get_serial_status(int get_status, int port) +{ + char buf[80]; + char *str; + int cur_state; + + if(get_status == 0) { + return 0; + } + cur_state = g_scc[port].cur_state; + str = ""; + switch(cur_state) { + case -1: + str = "Not initialized yet"; + break; + case 0: + snprintf(buf, 80, "Opened %s OK", g_serial_device[port]); + str = buf; + break; + case 1: + snprintf(buf, 80, "Virtual modem, sockfd:%d", + (int)g_scc[port].sockfd); + str = buf; + break; + case 2: + snprintf(buf, 80, "Outgoing to %s:%d", + g_serial_remote_ip[port], g_serial_remote_port[port]); + str = buf; + break; + case 3: + snprintf(buf, 80, "Opened %d, sockfd:%d", 6501 + port, + (int)g_scc[port].sockfd); + str = buf; + break; + default: + str = "Open failed, port is closed"; + } + return kegs_malloc_str(str); +} + + +void +scc_config_changed(int port, int cfg_changed, int remote_changed, + int serial_dev_changed) +{ + Scc *scc_ptr; + int must_change; + + // Check if scc_init() was called, if not get out + if(!g_scc_init) { + return; + } + + // F4 may have changed the serial port config. If so, close old + // port, open new one. + + scc_ptr = &(g_scc[port]); + must_change = cfg_changed; + switch(scc_ptr->cur_state) { + case 0: // Using serial port + must_change |= serial_dev_changed; + break; + case 2: // Using remote connection + must_change |= remote_changed; + break; + } + if(must_change) { + scc_port_close(port); + } +} + +void +scc_update(dword64 dfcyc) +{ + int i; + + // called each VBL update + for(i = 0; i < 2; i++) { + g_scc[i].write_called_this_vbl = 0; + g_scc[i].read_called_this_vbl = 0; + + // These calls will try to open the port if it's closed + scc_try_to_empty_writebuf(dfcyc, i); + scc_try_fill_readbuf(dfcyc, i); + + g_scc[i].write_called_this_vbl = 0; + g_scc[i].read_called_this_vbl = 0; + } +} + +void +scc_try_to_empty_writebuf(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + int cur_state; + + scc_ptr = &(g_scc[port]); + cur_state = scc_ptr->cur_state; + if(scc_ptr->write_called_this_vbl) { + return; + } + if(scc_is_port_closed(dfcyc, port)) { + return; // Port is not open + } + + scc_ptr->write_called_this_vbl = 1; + + if(cur_state == 0) { +#if defined(_WIN32) + scc_serial_win_empty_writebuf(port); +#else + scc_serial_unix_empty_writebuf(port); +#endif + } else if(cur_state >= 1) { + scc_socket_empty_writebuf(dfcyc, port); + } +} + +void +scc_try_fill_readbuf(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + int space_used, space_left, cur_state; + + scc_ptr = &(g_scc[port]); + + space_used = scc_ptr->in_wrptr - scc_ptr->in_rdptr; + if(space_used < 0) { + space_used += SCC_INBUF_SIZE; + } + space_left = (7*SCC_INBUF_SIZE/8) - space_used; + if(space_left < 1) { + /* Buffer is pretty full, don't try to get more */ + return; + } + + if(scc_is_port_closed(dfcyc, port)) { + return; // Port is not open + } + +#if 0 + if(scc_ptr->read_called_this_vbl) { + return; + } +#endif + + scc_ptr->read_called_this_vbl = 1; + + cur_state = scc_ptr->cur_state; + if(cur_state == 0) { +#if defined(_WIN32) + scc_serial_win_fill_readbuf(dfcyc, port, space_left); +#else + scc_serial_unix_fill_readbuf(dfcyc, port, space_left); +#endif + } else if(cur_state >= 1) { + scc_socket_fill_readbuf(dfcyc, port, space_left); + } +} + +void +scc_do_event(dword64 dfcyc, int type) +{ + Scc *scc_ptr; + int port; + + port = type & 1; + type = (type >> 1); + + scc_ptr = &(g_scc[port]); + if(type == SCC_BR_EVENT) { + /* baud rate generator counted down to 0 */ + scc_ptr->br_event_pending = 0; + scc_set_zerocnt_int(port); + scc_maybe_br_event(dfcyc, port); + } else if(type == SCC_TX_EVENT) { + scc_ptr->tx_event_pending = 0; + scc_ptr->tx_buf_empty = 1; + scc_handle_tx_event(port); + } else if(type == SCC_RX_EVENT) { + scc_ptr->rx_event_pending = 0; + scc_maybe_rx_event(dfcyc, port); + scc_maybe_rx_int(port); + } else { + halt_printf("scc_do_event: %08x!\n", type); + } + return; +} + +void +show_scc_state() +{ + Scc *scc_ptr; + int i, j; + + for(i = 0; i < 2; i++) { + scc_ptr = &(g_scc[i]); + printf("SCC port: %d\n", i); + for(j = 0; j < 16; j += 4) { + printf("Reg %2d-%2d: %02x %02x %02x %02x\n", j, j+3, + scc_ptr->reg[j], scc_ptr->reg[j+1], + scc_ptr->reg[j+2], scc_ptr->reg[j+3]); + } + printf("state: %d, sockfd:%llx rdwrfd:%llx, win_com:%p, " + "win_dcb:%p\n", scc_ptr->cur_state, + (dword64)scc_ptr->sockfd, (dword64)scc_ptr->rdwrfd, + scc_ptr->win_com_handle, scc_ptr->win_dcb_ptr); + printf("in_rdptr: %04x, in_wr:%04x, out_rd:%04x, out_wr:%04x\n", + scc_ptr->in_rdptr, scc_ptr->in_wrptr, + scc_ptr->out_rdptr, scc_ptr->out_wrptr); + printf("rx_queue_depth: %d, queue: %02x, %02x, %02x, %02x\n", + scc_ptr->rx_queue_depth, scc_ptr->rx_queue[0], + scc_ptr->rx_queue[1], scc_ptr->rx_queue[2], + scc_ptr->rx_queue[3]); + printf("want_ints: rx:%d, tx:%d, zc:%d\n", + scc_ptr->wantint_rx, scc_ptr->wantint_tx, + scc_ptr->wantint_zerocnt); + printf("ev_pendings: rx:%d, tx:%d, br:%d\n", + scc_ptr->rx_event_pending, + scc_ptr->tx_event_pending, + scc_ptr->br_event_pending); + printf("br_dcycs: %f, tx_dcycs: %f, rx_dcycs: %f\n", + scc_ptr->br_dcycs, scc_ptr->tx_dcycs, + scc_ptr->rx_dcycs); + printf("char_size: %d, baud_rate: %d, mode: %d\n", + scc_ptr->char_size, scc_ptr->baud_rate, + scc_ptr->mode); + printf("modem_state: %dtelnet_mode:%d iac:%d, " + "modem_cmd_len:%d\n", scc_ptr->modem_state, + scc_ptr->telnet_mode, scc_ptr->telnet_iac, + scc_ptr->modem_cmd_len); + printf("telnet_loc_modes:%08x %08x, telnet_rem_motes:" + "%08x %08x\n", scc_ptr->telnet_local_mode[0], + scc_ptr->telnet_local_mode[1], + scc_ptr->telnet_remote_mode[0], + scc_ptr->telnet_remote_mode[1]); + printf("modem_mode:%08x plus_mode:%d, out_char_dfcyc:%016llx\n", + scc_ptr->modem_mode, scc_ptr->modem_plus_mode, + scc_ptr->out_char_dfcyc); + } + +} + +word32 +scc_read_reg(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + word32 ret; + int regnum; + + scc_ptr = &(g_scc[port]); + scc_ptr->mode = 0; + regnum = scc_ptr->reg_ptr; + + /* port 0 is channel A, port 1 is channel B */ + switch(regnum) { + case 0: + case 4: + ret = 0x60; /* 0x44 = no dcd, no cts,0x6c = dcd ok, cts ok*/ + if(scc_ptr->dcd) { + ret |= 0x08; + } + //ret |= 0x8; /* HACK HACK */ + if(scc_ptr->rx_queue_depth) { + ret |= 0x01; + } + if(scc_ptr->tx_buf_empty) { + ret |= 0x04; + } + if(scc_ptr->br_is_zero) { + ret |= 0x02; + } + //printf("Read scc[%d] stat: %016llx : %02x dcd:%d\n", port, + // dfcyc, ret, scc_ptr->dcd); + break; + case 1: + case 5: + /* HACK: residue codes not right */ + ret = 0x07; /* all sent */ + break; + case 2: + case 6: + if(port == 0) { + ret = scc_ptr->reg[2]; + } else { // Port B, read RR2B int stat + // The TELNET.SYSTEM by Colin Leroy-Mira uses RR2B + ret = scc_do_read_rr2b() << 1; + if(g_scc[0].reg[9] & 0x10) { // wr9 status high + // Map bit 3->4, 2->5, 1->6 + ret = ((ret << 1) & 0x10) | + ((ret << 3) & 0x20) | + ((ret << 5) & 0x40); + } + } + break; + case 3: + case 7: + if(port == 0) { + ret = (g_irq_pending & 0x3f); + } else { + ret = 0; + } + break; + case 8: + ret = scc_read_data(dfcyc, port); + break; + case 9: + case 13: + ret = scc_ptr->reg[13]; + break; + case 10: + case 14: + ret = 0; + break; + case 11: + case 15: + ret = scc_ptr->reg[15]; + break; + case 12: + ret = scc_ptr->reg[12]; + break; + default: + halt_printf("Tried reading c03%x with regnum: %d!\n", 8+port, + regnum); + ret = 0; + } + + scc_ptr->reg_ptr = 0; + scc_printf("Read c03%x, rr%d, ret: %02x\n", 8+port, regnum, ret); + dbg_log_info(dfcyc, 0, ret, (regnum << 20) | (0xc039 - port)); + + return ret; +} + +void +scc_write_reg(dword64 dfcyc, int port, word32 val) +{ + Scc *scc_ptr; + word32 old_val, changed_bits, irq_mask; + int regnum, mode, tmp1; + + scc_ptr = &(g_scc[port]); + regnum = scc_ptr->reg_ptr & 0xf; + mode = scc_ptr->mode; + + if(mode == 0) { + if((val & 0xf0) == 0) { + /* Set reg_ptr */ + scc_ptr->reg_ptr = val & 0xf; + regnum = 0; + scc_ptr->mode = 1; + } + } else { + scc_ptr->reg_ptr = 0; + scc_ptr->mode = 0; + } + old_val = scc_ptr->reg[regnum]; + changed_bits = (old_val ^ val) & 0xff; + + dbg_log_info(dfcyc, (mode << 16) | scc_ptr->reg_ptr, + (changed_bits << 16) | val, + (regnum << 20) | (0x1c039 - port)); + + /* Set reg reg */ + switch(regnum) { + case 0: /* wr0 */ + tmp1 = (val >> 3) & 0x7; + switch(tmp1) { + case 0x0: + case 0x1: + break; + case 0x2: /* reset ext/status ints */ + /* should clear other ext ints */ + scc_clr_zerocnt_int(port); + break; + case 0x5: /* reset tx int pending */ + scc_clr_tx_int(port); + break; + case 0x6: /* reset rr1 bits */ + break; + case 0x7: /* reset highest pri int pending */ + irq_mask = g_irq_pending; + if(port == 0) { + /* Move SCC0 ints into SCC1 positions */ + irq_mask = irq_mask >> 3; + } + if(irq_mask & IRQ_PENDING_SCC1_RX) { + scc_clr_rx_int(port); + } else if(irq_mask & IRQ_PENDING_SCC1_TX) { + scc_clr_tx_int(port); + } else if(irq_mask & IRQ_PENDING_SCC1_ZEROCNT) { + scc_clr_zerocnt_int(port); + } + break; + case 0x4: /* enable int on next rx char */ + default: + halt_printf("Wr c03%x to wr0 of %02x, bad cmd cd:%x!\n", + 9-port, val, tmp1); + } + tmp1 = (val >> 6) & 0x3; + switch(tmp1) { + case 0x0: /* null code */ + break; + case 0x1: /* reset rx crc */ + case 0x2: /* reset tx crc */ + printf("Wr c03%x to wr0 of %02x!\n", 9-port, val); + break; + case 0x3: /* reset tx underrun/eom latch */ + /* if no extern status pending, or being reset now */ + /* and tx disabled, ext int with tx underrun */ + /* ah, just do nothing */ + break; + } + return; + case 1: /* wr1 */ + /* proterm sets this == 0x10, which is int on all rx */ + scc_ptr->reg[regnum] = val; + return; + case 2: /* wr2 */ + /* All values do nothing, let 'em all through! */ + scc_ptr->reg[regnum] = val; + return; + case 3: /* wr3 */ + if((val & 0x1e) != 0x0) { + halt_printf("Wr c03%x to wr3 of %02x!\n", 8+port, val); + } + scc_ptr->reg[regnum] = val; + return; + case 4: /* wr4 */ + if((val & 0x30) != 0x00 || (val & 0x0c) == 0) { + halt_printf("Wr c03%x to wr4 of %02x!\n", 8+port, val); + } + scc_ptr->reg[regnum] = val; + if(changed_bits) { + scc_regen_clocks(port); + } + return; + case 5: /* wr5 */ + if((val & 0x15) != 0x0) { + halt_printf("Wr c03%x to wr5 of %02x!\n", 8+port, val); + } + scc_ptr->reg[regnum] = val; + if(changed_bits & 0x60) { + scc_regen_clocks(port); + } + if(changed_bits & 0x80) { + scc_printf("SCC port %d DTR:%d\n", port, val & 0x80); + } + return; + case 6: /* wr6 */ + if(val != 0) { + halt_printf("Wr c03%x to wr6 of %02x!\n", 8+port, val); + } + scc_ptr->reg[regnum] = val; + return; + case 7: /* wr7 */ + if(val != 0) { + halt_printf("Wr c03%x to wr7 of %02x!\n", 8+port, val); + } + scc_ptr->reg[regnum] = val; + return; + case 8: /* wr8 */ + scc_write_data(dfcyc, port, val); + return; + case 9: /* wr9 */ + if((val & 0xc0)) { + if(val & 0x80) { + scc_reset_port(0); + } + if(val & 0x40) { + scc_reset_port(1); + } + if((val & 0xc0) == 0xc0) { + scc_hard_reset_port(0); + scc_hard_reset_port(1); + } + } + // Bit 5 is software interrupt ack, which does not exist on NMOS + // Bit 2 sets IEO pin low, which doesn't exist either + old_val = g_scc[0].reg[9]; + g_scc[0].reg[9] = val; + scc_evaluate_ints(0); + scc_evaluate_ints(1); + return; + case 10: /* wr10 */ + if((val & 0xff) != 0x00) { + printf("Wr c03%x to wr10 of %02x!\n", 8+port, val); + } + scc_ptr->reg[regnum] = val; + return; + case 11: /* wr11 */ + scc_ptr->reg[regnum] = val; + if(changed_bits) { + scc_regen_clocks(port); + } + return; + case 12: /* wr12 */ + scc_ptr->reg[regnum] = val; + if(changed_bits) { + scc_regen_clocks(port); + } + return; + case 13: /* wr13 */ + scc_ptr->reg[regnum] = val; + if(changed_bits) { + scc_regen_clocks(port); + } + return; + case 14: /* wr14 */ + val = val | (old_val & (~0xffU)); + switch((val >> 5) & 0x7) { + case 0x0: // Null command (change nothing) + break; + case 0x1: // Enter search mode + case 0x2: // Reset missing clock + case 0x3: // Disable PLL + case 0x6: // Set FM mode + case 0x7: // Set NRZI mode + // Disable the PLL effectively + val = val & 0xff; // Clear all upper bits + break; + case 0x4: // DPLL source is BR gen + val = (val & 0xff) | SCC_R14_DPLL_SOURCE_BRG; + break; + case 0x5: // DPLL source is RTxC + val = (val & 0xff) | SCC_R14_DPLL_SOURCE_RTXC; + break; + default: + halt_printf("Wr c03%x to wr14 of %02x, bad dpll cd!\n", + 8+port, val); + } + if((val & 0x0c) != 0x0) { + halt_printf("Wr c03%x to wr14 of %02x!\n", 8+port, val); + } + scc_ptr->reg[regnum] = val; + if(changed_bits || (val != old_val)) { + scc_regen_clocks(port); + } + scc_maybe_br_event(dfcyc, port); + return; + case 15: /* wr15 */ + /* ignore all accesses since IIgs self test messes with it */ + if((val & 0xff) != 0x0) { + scc_printf("Write c03%x to wr15 of %02x!\n", 8+port, + val); + } + if((g_scc[0].reg[9] & 0x8) && (val != 0)) { + printf("Write wr15:%02x and master int en = 1!\n",val); + /* set_halt(1); */ + } + scc_ptr->reg[regnum] = val; + scc_maybe_br_event(dfcyc, port); + scc_evaluate_ints(port); + return; + default: + halt_printf("Wr c03%x to wr%d of %02x!\n", 8+port, regnum, val); + return; + } +} + +// scc_read_data: Read from 0xc03b or 0xc03a +word32 +scc_read_data(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + word32 ret; + int depth; + int i; + + scc_ptr = &(g_scc[port]); + + scc_try_fill_readbuf(dfcyc, port); + + depth = scc_ptr->rx_queue_depth; + + ret = 0; + if(depth != 0) { + ret = scc_ptr->rx_queue[0]; + for(i = 1; i < depth; i++) { + scc_ptr->rx_queue[i-1] = scc_ptr->rx_queue[i]; + } + scc_ptr->rx_queue_depth = depth - 1; + scc_maybe_rx_event(dfcyc, port); + scc_maybe_rx_int(port); + } + + scc_printf("SCC read %04x: ret %02x, depth:%d\n", 0xc03b-port, ret, + depth); + + dbg_log_info(dfcyc, 0, ret, 0xc03b - port); + + return ret; +} + +void +scc_write_data(dword64 dfcyc, int port, word32 val) +{ + Scc *scc_ptr; + + scc_printf("SCC write %04x: %02x\n", 0xc03b-port, val); + dbg_log_info(dfcyc, val, 0, 0x1c03b - port); + + scc_ptr = &(g_scc[port]); + if(scc_ptr->reg[14] & 0x10) { + /* local loopback! */ + scc_add_to_readbuf(dfcyc, port, val); + } else { + scc_transmit(dfcyc, port, val); + } + scc_try_to_empty_writebuf(dfcyc, port); + + scc_maybe_tx_event(dfcyc, port); +} + +word32 +scc_do_read_rr2b() +{ + word32 val; + + val = g_irq_pending & 0x3f; + if(val == 0) { + return 3; // 011 if no interrupts pending + } + // Do Channel A first. Priority order from SCC documentation + if(val & IRQ_PENDING_SCC0_RX) { + return 6; // 110 Ch A Rx char available + } + if(val & IRQ_PENDING_SCC0_TX) { + return 4; // 100 Ch A Tx buffer empty + } + if(val & IRQ_PENDING_SCC0_ZEROCNT) { + return 5; // 101 Ch A External/Status change + } + if(val & IRQ_PENDING_SCC1_RX) { + return 2; // 010 Ch B Rx char available + } + if(val & IRQ_PENDING_SCC1_TX) { + return 0; // 000 Ch B Tx buffer empty + } + if(val & IRQ_PENDING_SCC1_ZEROCNT) { + return 1; // 001 Ch B External/Status change + } + + return 3; +} + +void +scc_maybe_br_event(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + double br_dcycs; + + scc_ptr = &(g_scc[port]); + + if(((scc_ptr->reg[14] & 0x01) == 0) || scc_ptr->br_event_pending) { + return; + } + /* also, if ext ints not enabled, don't do baud rate ints */ + if(((scc_ptr->reg[15] & 0x02) == 0) || ((scc_ptr->reg[9] & 8) == 0)) { + return; + } + + br_dcycs = scc_ptr->br_dcycs; + if(br_dcycs < 1.0) { + halt_printf("br_dcycs: %f!\n", br_dcycs); +#if 0 + printf("br_dcycs: %f!\n", br_dcycs); + dbg_log_info(dfcyc, (word32)(br_dcycs * 65536), + (scc_ptr->reg[15] << 24) | (scc_ptr->reg[14] << 16) | + (scc_ptr->reg[13] << 8) | scc_ptr->reg[12], 0xdc1c); + dbg_log_info(dfcyc, + (scc_ptr->reg[11] << 24) | (scc_ptr->reg[10] << 16) | + (scc_ptr->reg[9] << 8) | scc_ptr->reg[5], + (scc_ptr->reg[4] << 24) | (scc_ptr->reg[3] << 16) | + (scc_ptr->reg[1] << 8) | scc_ptr->reg[0], 0xdc1b); +#endif + } + + scc_ptr->br_event_pending = 1; + add_event_scc(dfcyc + (dword64)(br_dcycs * 65536.0), + SCC_MAKE_EVENT(port, SCC_BR_EVENT)); +} + +void +scc_evaluate_ints(int port) +{ + Scc *scc_ptr; + word32 irq_add_mask, irq_remove_mask; + int mie; + + scc_ptr = &(g_scc[port]); + mie = g_scc[0].reg[9] & 0x8; /* Master int en */ + + if(!mie) { + /* There can be no interrupts if MIE=0 */ + remove_irq(IRQ_PENDING_SCC1_RX | IRQ_PENDING_SCC1_TX | + IRQ_PENDING_SCC1_ZEROCNT | + IRQ_PENDING_SCC0_RX | IRQ_PENDING_SCC0_TX | + IRQ_PENDING_SCC0_ZEROCNT); + return; + } + + irq_add_mask = 0; + irq_remove_mask = 0; + if(scc_ptr->wantint_rx) { + irq_add_mask |= IRQ_PENDING_SCC1_RX; + } else { + irq_remove_mask |= IRQ_PENDING_SCC1_RX; + } + if(scc_ptr->wantint_tx) { + irq_add_mask |= IRQ_PENDING_SCC1_TX; + } else { + irq_remove_mask |= IRQ_PENDING_SCC1_TX; + } + if(scc_ptr->wantint_zerocnt) { + irq_add_mask |= IRQ_PENDING_SCC1_ZEROCNT; + } else { + irq_remove_mask |= IRQ_PENDING_SCC1_ZEROCNT; + } + if(port == 0) { + /* Port 1 is in bits 0-2 and port 0 is in bits 3-5 */ + irq_add_mask = irq_add_mask << 3; + irq_remove_mask = irq_remove_mask << 3; + } + if(irq_add_mask) { + add_irq(irq_add_mask); + } + if(irq_remove_mask) { + remove_irq(irq_remove_mask); + } +} + +void +scc_maybe_rx_event(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + double rx_dcycs; + int in_rdptr, in_wrptr, depth; + + scc_ptr = &(g_scc[port]); + + if(scc_ptr->rx_event_pending) { + /* one pending already, wait for the event to arrive */ + return; + } + + in_rdptr = scc_ptr->in_rdptr; + in_wrptr = scc_ptr->in_wrptr; + depth = scc_ptr->rx_queue_depth; + if((in_rdptr == in_wrptr) || (depth >= 3)) { + /* no more chars or no more space, just get out */ + return; + } + + if(depth < 0) { + depth = 0; + } + + /* pull char from in_rdptr into queue */ + scc_ptr->rx_queue[depth] = scc_ptr->in_buf[in_rdptr]; + scc_ptr->in_rdptr = (in_rdptr + 1) & (SCC_INBUF_SIZE - 1); + scc_ptr->rx_queue_depth = depth + 1; + scc_maybe_rx_int(port); + rx_dcycs = scc_ptr->rx_dcycs; + scc_ptr->rx_event_pending = 1; + add_event_scc(dfcyc + (dword64)(rx_dcycs*65536.0), + SCC_MAKE_EVENT(port, SCC_RX_EVENT)); +} + +void +scc_maybe_rx_int(int port) +{ + Scc *scc_ptr; + int depth; + int rx_int_mode; + + scc_ptr = &(g_scc[port]); + + depth = scc_ptr->rx_queue_depth; + if(depth <= 0) { + /* no more chars, just get out */ + scc_clr_rx_int(port); + return; + } + rx_int_mode = (scc_ptr->reg[1] >> 3) & 0x3; + if((rx_int_mode == 1) || (rx_int_mode == 2)) { + scc_ptr->wantint_rx = 1; + } + scc_evaluate_ints(port); +} + +void +scc_clr_rx_int(int port) +{ + g_scc[port].wantint_rx = 0; + scc_evaluate_ints(port); +} + +void +scc_handle_tx_event(int port) +{ + Scc *scc_ptr; + int tx_int_mode; + + scc_ptr = &(g_scc[port]); + + /* nothing pending, see if ints on */ + tx_int_mode = (scc_ptr->reg[1] & 0x2); + if(tx_int_mode) { + scc_ptr->wantint_tx = 1; + } + scc_evaluate_ints(port); +} + +void +scc_maybe_tx_event(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + double tx_dcycs; + + scc_ptr = &(g_scc[port]); + + if(scc_ptr->tx_event_pending) { + /* one pending already, tx_buf is full */ + scc_ptr->tx_buf_empty = 0; + } else { + /* nothing pending, see ints on */ + scc_ptr->tx_buf_empty = 0; + scc_evaluate_ints(port); + tx_dcycs = scc_ptr->tx_dcycs; + scc_ptr->tx_event_pending = 1; + add_event_scc(dfcyc + (dword64)(tx_dcycs * 65536.0), + SCC_MAKE_EVENT(port, SCC_TX_EVENT)); + } +} + +void +scc_clr_tx_int(int port) +{ + g_scc[port].wantint_tx = 0; + scc_evaluate_ints(port); +} + +void +scc_set_zerocnt_int(int port) +{ + Scc *scc_ptr; + + scc_ptr = &(g_scc[port]); + + if(scc_ptr->reg[15] & 0x2) { + scc_ptr->wantint_zerocnt = 1; + } + scc_evaluate_ints(port); +} + +void +scc_clr_zerocnt_int(int port) +{ + g_scc[port].wantint_zerocnt = 0; + scc_evaluate_ints(port); +} + +void +scc_add_to_readbuf(dword64 dfcyc, int port, word32 val) +{ + Scc *scc_ptr; + int in_wrptr, in_wrptr_next, in_rdptr, safe_val; + + scc_ptr = &(g_scc[port]); + + if((scc_ptr->reg[5] & 0x60) != 0x60) { // HACK: this is tx char size! + val = val & 0x7f; + } + in_wrptr = scc_ptr->in_wrptr; + in_rdptr = scc_ptr->in_rdptr; + in_wrptr_next = (in_wrptr + 1) & (SCC_INBUF_SIZE - 1); + if(in_wrptr_next != in_rdptr) { + scc_ptr->in_buf[in_wrptr] = val; + scc_ptr->in_wrptr = in_wrptr_next; + safe_val = val & 0x7f; + if((safe_val < 0x20) || (safe_val >= 0x7f)) { + safe_val = '.'; + } + scc_printf("scc in port[%d] add 0x%02x (%c), %d,%d != %d\n", + port, val, safe_val, in_wrptr, in_wrptr_next, in_rdptr); + g_scc_overflow = 0; + } else { + if(g_scc_overflow == 0) { + g_code_yellow++; + printf("scc inbuf overflow port %d\n", port); + } + g_scc_overflow = 1; + } + + scc_maybe_rx_event(dfcyc, port); + scc_maybe_rx_int(port); +} + +void +scc_add_to_readbufv(dword64 dfcyc, int port, const char *fmt, ...) +{ + va_list ap; + char *bufptr; + int len, c; + int i; + + va_start(ap, fmt); + bufptr = malloc(4096); + bufptr[0] = 0; + vsnprintf(bufptr, 4090, fmt, ap); + len = (int)strlen(bufptr); + for(i = 0; i < len; i++) { + c = bufptr[i]; + if(c == 0x0a) { + scc_add_to_readbuf(dfcyc, port, 0x0d); + } + scc_add_to_readbuf(dfcyc, port, c); + } + va_end(ap); +} + +void +scc_transmit(dword64 dfcyc, int port, word32 val) +{ + Scc *scc_ptr; + int out_wrptr, out_rdptr; + + scc_ptr = &(g_scc[port]); + + // printf("scc_transmit port:%d val:%02x \n", port, val); + /* See if port initialized, if not, do so now */ + if(scc_is_port_closed(dfcyc, port)) { + printf(" port %d is closed, cur_state:%d\n", port, + scc_ptr->cur_state); + return; // No working serial port, just toss it and go + } + if(!scc_ptr->tx_buf_empty) { + /* toss character! */ + printf("Tossing char\n"); + return; + } + + out_wrptr = scc_ptr->out_wrptr; + out_rdptr = scc_ptr->out_rdptr; + if(scc_ptr->tx_dcycs < 1.0) { + if(out_wrptr != out_rdptr) { + /* do just one char, then get out */ + printf("tx_dcycs < 1\n"); + return; + } + } + if(g_serial_mask[port] || (scc_ptr->reg[5] & 0x60) != 0x60) { + val = val & 0x7f; + } + + scc_add_to_writebuf(dfcyc, port, val); +} + +void +scc_add_to_writebuf(dword64 dfcyc, int port, word32 val) +{ + Scc *scc_ptr; + int out_wrptr, out_wrptr_next, out_rdptr; + + // printf("scc_add_to_writebuf p:%d, val:%02x\n", port, val); + if(scc_is_port_closed(dfcyc, port)) { + return; // Port is closed + } + scc_ptr = &(g_scc[port]); + + out_wrptr = scc_ptr->out_wrptr; + out_rdptr = scc_ptr->out_rdptr; + + out_wrptr_next = (out_wrptr + 1) & (SCC_OUTBUF_SIZE - 1); + if(out_wrptr_next != out_rdptr) { + scc_ptr->out_buf[out_wrptr] = val; + scc_ptr->out_wrptr = out_wrptr_next; + scc_printf("scc wrbuf port %d had char 0x%02x added\n", + port, val); + g_scc_overflow = 0; + } else { + if(g_scc_overflow == 0) { + g_code_yellow++; + printf("scc outbuf overflow port %d\n", port); + } + g_scc_overflow = 1; + } +} + diff --git a/gsplus/src/scc.h b/gsplus/src/scc.h new file mode 100644 index 0000000..6f10d3b --- /dev/null +++ b/gsplus/src/scc.h @@ -0,0 +1,111 @@ +#ifdef INCLUDE_RCSID_C +#endif + +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2025 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include + +#ifdef _WIN32 +# include +#else +# include +# include +# include +#endif + +#if defined(HPUX) || defined(__linux__) || defined(SOLARIS) +# define SCC_SOCKETS +#endif +#if defined(MAC) || defined(__MACH__) || defined(_WIN32) +# define SCC_SOCKETS +#endif + + +/* my scc port 0 == channel A, port 1 = channel B */ + +#define SCC_INBUF_SIZE 512 /* must be a power of 2 */ +#define SCC_OUTBUF_SIZE 512 /* must be a power of 2 */ + +#define SCC_MODEM_MAX_CMD_STR 128 + +#ifndef _WIN32 +# define SOCKET int /* For non-Windows */ +# define INVALID_SOCKET (-1) /* For non-Windows */ +#endif + +STRUCT(Scc) { + int cur_state; + int modem_state; + SOCKET sockfd; + SOCKET rdwrfd; + void *sockaddr_ptr; // Socket: pointer to sockaddr struct + int sockaddr_size; // Socket: sizeof(sockaddr_in) + int unix_dev_fd; // Unix fd to real serial device + void *win_com_handle; // Win32 handle to COMx port + void *win_dcb_ptr; // Win32 ptr to COMx DCB + int read_called_this_vbl; + int write_called_this_vbl; + + byte dcd; + byte reg_ptr; + byte br_is_zero; + byte tx_buf_empty; + word32 mode; + byte reg[16]; + + int rx_queue_depth; + byte rx_queue[4]; + + int in_rdptr; + int in_wrptr; + byte in_buf[SCC_INBUF_SIZE]; + + int out_rdptr; + int out_wrptr; + byte out_buf[SCC_OUTBUF_SIZE]; + + int wantint_rx; + int wantint_tx; + int wantint_zerocnt; + + double br_dcycs; + double tx_dcycs; + double rx_dcycs; + + int br_event_pending; + int rx_event_pending; + int tx_event_pending; + + int char_size; + int baud_rate; + dword64 out_char_dfcyc; + + int socket_error; + int socket_num_rings; + dword64 socket_last_ring_dfcyc; + word32 modem_mode; + int modem_plus_mode; + int modem_s0_val; + int modem_s2_val; + int telnet_mode; + int telnet_iac; + word32 telnet_local_mode[2]; + word32 telnet_remote_mode[2]; + word32 telnet_reqwill_mode[2]; + word32 telnet_reqdo_mode[2]; + word32 modem_out_portnum; + int modem_cmd_len; + byte modem_cmd_str[SCC_MODEM_MAX_CMD_STR + 5]; +}; + +#define SCCMODEM_NOECHO 0x0001 +#define SCCMODEM_NOVERBOSE 0x0002 + diff --git a/gsplus/src/scc_socket_driver.c b/gsplus/src/scc_socket_driver.c new file mode 100644 index 0000000..ef27281 --- /dev/null +++ b/gsplus/src/scc_socket_driver.c @@ -0,0 +1,1235 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2025 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// This file contains the socket calls for Win32 and Mac/Linux +// Win32: see: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/ + +// Modem init string GBBS GSPORT.HST: ats0=1s2=128v0 +// Modem init string Warp6: atm0e0v0s0=0s7=25 atx4h0 at&c1&d2 + +#include "defc.h" + +#include "scc.h" +#include + +extern int Verbose; +extern Scc g_scc[2]; +extern int g_serial_cfg[2]; +extern char *g_serial_remote_ip[2]; +extern int g_serial_remote_port[2]; +extern int g_serial_modem_response_code; +extern int g_serial_modem_allow_incoming; +extern int g_serial_modem_init_telnet; + +#ifdef _WIN32 +#include +typedef int socklen_t; +#endif + +#ifndef _WIN32 +extern int h_errno; +#endif + +int g_wsastartup_called = 0; + +#ifndef _WIN32 +// SOCKET is defined in scc.h, and is an "int" for non-Windows systems +// INVALID_SOCKET is -1 for non-WINDOWS +# define closesocket(s) close(s) +#endif + +/* Usage: scc_socket_open() called to init socket mode */ +/* At all times, we try to have a listen running on the incoming socket */ +/* If we want to dial out, we close the incoming socket and create a new */ +/* outgoing socket. Any hang-up causes the socket to be closed and it will */ +/* then re-open on a subsequent call to scc_socket_open */ + +void +scc_socket_open(dword64 dfcyc, int port, int cfg) +{ + Scc *scc_ptr; + + // printf("scc_socket_open p:%d cfg:%d\n", port, cfg); +#if defined(_WIN32) && defined(SCC_SOCKETS) + WSADATA wsadata; + int ret; + + if(g_wsastartup_called == 0) { + ret = WSAStartup(MAKEWORD(2,0), &wsadata); + printf("WSAStartup ret: %d\n", ret); + g_wsastartup_called = 1; + } +#endif + scc_ptr = &(g_scc[port]); + scc_ptr->cur_state = cfg; // successful socket + scc_ptr->sockfd = INVALID_SOCKET; /* Indicate no socket open yet */ + scc_ptr->rdwrfd = INVALID_SOCKET; /* Indicate no socket open yet */ + scc_ptr->modem_state = 0; /* 0 means talk to socket */ + /* 1 means talk to modem */ + scc_ptr->socket_error = 0; + scc_ptr->socket_num_rings = 0; + scc_ptr->socket_last_ring_dfcyc = 0; + scc_ptr->dcd = 1; /* 1 means carrier */ + scc_ptr->modem_s0_val = 0; // Number of rings before answer + scc_ptr->sockaddr_size = sizeof(struct sockaddr_in); + free(scc_ptr->sockaddr_ptr); + scc_ptr->sockaddr_ptr = malloc(scc_ptr->sockaddr_size); + + // cfg==1: Virtual Modem. All set up, wait for AT commands now + // cfg==2: Outgoing connection to g_serial_remote_ip[], do it now + // cfg==3: Incoming connection on 6501/6502. Do it now + if(cfg == 2) { + scc_ptr->modem_state = 0; + // Do not open it now, it will be opened when output is + // sent to the port + } + if(cfg == 1) { + scc_ptr->modem_state = 1; + scc_ptr->dcd = 0; + scc_socket_open_incoming(dfcyc, port); + } else if(cfg == 3) { + scc_ptr->modem_state = 0; + scc_socket_open_incoming(dfcyc, port); + } +} + +void +scc_socket_close(int port) +{ +#ifdef SCC_SOCKETS + Scc *scc_ptr; + SOCKET rdwrfd; + SOCKET sockfd; + int do_init; + int i; + + scc_ptr = &(g_scc[port]); + + // printf("In scc_socket_close, %d\n", port); + + rdwrfd = scc_ptr->rdwrfd; + do_init = 0; + if(rdwrfd != INVALID_SOCKET) { + printf("socket_close: rdwrfd=%llx, closing\n", (dword64)rdwrfd); + closesocket(rdwrfd); + do_init = 1; + } + sockfd = scc_ptr->sockfd; + if((sockfd != INVALID_SOCKET) && (rdwrfd != sockfd)) { + printf("socket_close: sockfd=%llx, closing\n", (dword64)sockfd); + closesocket(sockfd); + do_init = 1; + } + + scc_ptr->modem_state = 0; + scc_ptr->socket_num_rings = 0; + if(scc_ptr->cur_state == 1) { // Virtual modem + scc_ptr->modem_state = 1; + scc_ptr->dcd = 0; + } + if(do_init) { + scc_ptr->modem_cmd_len = 0; + scc_ptr->telnet_iac = 0; + for(i = 0; i < 2; i++) { + scc_ptr->telnet_local_mode[i] = 0; + scc_ptr->telnet_remote_mode[i] = 0; + scc_ptr->telnet_reqwill_mode[i] = 0; + scc_ptr->telnet_reqdo_mode[i] = 0; + } + scc_ptr->rdwrfd = INVALID_SOCKET; + scc_ptr->sockfd = INVALID_SOCKET; + } +#endif +} + +void +scc_socket_close_extended(dword64 dfcyc, int port, int allow_retry) +{ +#ifdef SCC_SOCKETS + Scc *scc_ptr; + + // printf("In scc_socket_close_extended, %d, %016llx\n", port, dfcyc); + scc_socket_close(port); + + scc_ptr = &(g_scc[port]); + if(scc_ptr->cur_state == 1) { // Virtual modem mode + scc_socket_send_modem_code(dfcyc, port, 3); + scc_ptr->modem_state = 1; // and go back to modem mode + } else if((scc_ptr->cur_state == 2) && !allow_retry) { // Remote IP + scc_ptr->cur_state = -2; // Error, give up + } +#endif +} + +void +scc_socket_maybe_open(dword64 dfcyc, int port, int must) +{ + Scc *scc_ptr; + + scc_ptr = &(g_scc[port]); + + if(scc_ptr->sockfd != INVALID_SOCKET) { + // Valid socket. See if we should hang up + if((scc_ptr->reg[5] & 0x80) && (scc_ptr->cur_state == 1)) { + // Handle DTR forcing modem hang-up now + printf("DTR is deasserted, hanging up\n"); + scc_socket_close(port); + } + return; + } + if(scc_ptr->cur_state == 2) { + if(must) { + scc_socket_open_outgoing(dfcyc, port, + g_serial_remote_ip[port], + g_serial_remote_port[port]); + } + } else { + scc_socket_open_incoming(dfcyc, port); + } +} + +void +scc_socket_open_incoming(dword64 dfcyc, int port) +{ +#ifdef SCC_SOCKETS + SOCKET sockfd; + struct sockaddr_in sa_in; + Scc *scc_ptr; + int on, ret, inc; + + inc = 0; + + scc_ptr = &(g_scc[port]); + + if(scc_ptr->sockfd != INVALID_SOCKET) { + /* it's already open, get out */ + return; + } + + //printf("scc_socket_open_incoming: scc socket close being called\n"); + scc_socket_close(port); + + scc_ptr->socket_num_rings = 0; + + memset(scc_ptr->sockaddr_ptr, 0, scc_ptr->sockaddr_size); + + if(scc_ptr->cur_state == 2) { + // Outgoing connection only, never accept an incoming connect + return; + } + if(scc_ptr->cur_state == 1) { + if(!g_serial_modem_allow_incoming) { + // Virtual modem with incoming connections disallowed + return; + } + if(scc_ptr->reg[5] & 0x80) { + // DTR is forcing hang-up. Don't open incoming socket + return; + } + } + + while(1) { + sockfd = socket(AF_INET, SOCK_STREAM, 0); + //printf("sockfd ret: %llx\n", (dword64)sockfd); + if(sockfd == INVALID_SOCKET) { + printf("socket ret: -1, errno: %d\n", errno); + scc_socket_close(port); + scc_ptr->socket_error = -1; + return; + } + /* printf("socket ret: %d\n", sockfd); */ + + on = 1; + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, + (char *)&on, sizeof(on)); + if(ret < 0) { + printf("setsockopt REUSEADDR ret: %d, err:%d\n", + ret, errno); + scc_socket_close(port); + return; + } + + memset(&sa_in, 0, sizeof(sa_in)); + sa_in.sin_family = AF_INET; + sa_in.sin_port = htons(6501 + port + inc); + sa_in.sin_addr.s_addr = htonl(INADDR_ANY); + + ret = bind(sockfd, (struct sockaddr *)&sa_in, sizeof(sa_in)); + printf("bind ret:%d\n", ret); + + if(ret >= 0) { + ret = listen(sockfd, 1); + break; + } + /* else ret to bind was < 0 */ + printf("bind or listen ret: %d, errno: %d\n", ret, errno); + inc++; + closesocket(sockfd); + printf("Trying next port: %d\n", 6501 + port + inc); + if(inc >= 10) { + printf("Too many retries, quitting\n"); + scc_socket_close(port); + scc_ptr->socket_error = -1; + return; + } + } + + printf("SCC port %d is at unix port %d\n", port, 6501 + port + inc); + + scc_ptr->sockfd = sockfd; + scc_ptr->socket_error = 0; + + scc_socket_make_nonblock(dfcyc, port); +#endif +} + +void +scc_socket_open_outgoing(dword64 dfcyc, int port, const char *remote_ip_str, + int remote_port) +{ +#ifdef SCC_SOCKETS + Scc *scc_ptr; + struct sockaddr_in sa_in; + struct hostent *hostentptr; + int on; + int ret; + SOCKET sockfd; + + scc_ptr = &(g_scc[port]); + + // printf("scc socket close being called from socket_open_out\n"); + scc_socket_close(port); + + memset(scc_ptr->sockaddr_ptr, 0, scc_ptr->sockaddr_size); + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + // printf("outgoing sockfd ret: %llx\n", (dword64)sockfd); + if(sockfd == INVALID_SOCKET) { + printf("socket ret: %llx, errno: %d\n", (dword64)sockfd, errno); + scc_socket_close_extended(dfcyc, port, 0); + return; + } + /* printf("socket ret: %d\n", sockfd); */ + + on = 1; + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, + (char *)&on, sizeof(on)); + if(ret < 0) { + printf("setsockopt REUSEADDR ret: %d, err:%d\n", + ret, errno); + scc_socket_close_extended(dfcyc, port, 0); + return; + } + + memset(&sa_in, 0, sizeof(sa_in)); + sa_in.sin_family = AF_INET; + sa_in.sin_port = htons(remote_port); + while(*remote_ip_str == ' ') { + remote_ip_str++; // Skip leading blanks + } + hostentptr = gethostbyname(remote_ip_str); + printf("Connecting to %s, port:%d\n", remote_ip_str, remote_port); + if(hostentptr == 0) { +#ifdef _WIN32 + fatal_printf("Lookup host %s failed\n", + &scc_ptr->modem_cmd_str[0]); +#else + fatal_printf("Lookup host %s failed, herrno: %d\n", + &scc_ptr->modem_cmd_str[0], h_errno); +#endif + closesocket(sockfd); + scc_socket_close_extended(dfcyc, port, 0); + return; + } + memcpy(&sa_in.sin_addr.s_addr, hostentptr->h_addr, + hostentptr->h_length); + /* The above copies the 32-bit internet address into */ + /* sin_addr.s_addr. It's in correct network format */ + + ret = connect(sockfd, (struct sockaddr *)&sa_in, sizeof(sa_in)); + if(ret < 0) { + printf("connect ret: %d, errno: %d\n", ret, errno); + closesocket(sockfd); + scc_socket_close_extended(dfcyc, port, 0); + return; + } + scc_socket_modem_connect(dfcyc, port); + scc_ptr->dcd = 1; /* carrier on */ + scc_ptr->modem_state = 0; /* talk to socket */ + scc_ptr->socket_num_rings = 0; + + printf("SCC port %d is now outgoing to %s:%d\n", port, remote_ip_str, + remote_port); + + scc_ptr->sockfd = sockfd; + + scc_socket_make_nonblock(dfcyc, port); + scc_ptr->rdwrfd = scc_ptr->sockfd; +#endif +} + +void +scc_socket_make_nonblock(dword64 dfcyc, int port) +{ +#ifdef SCC_SOCKETS + Scc *scc_ptr; + SOCKET sockfd; + int ret; +# ifdef _WIN32 + u_long flags; +# else + int flags; +# endif + + scc_ptr = &(g_scc[port]); + sockfd = scc_ptr->sockfd; + +# ifdef _WIN32 + flags = 1; + ret = ioctlsocket(sockfd, FIONBIO, &flags); + if(ret != 0) { + printf("ioctlsocket ret: %d\n", ret); + } +# else + flags = fcntl(sockfd, F_GETFL, 0); + if(flags == -1) { + printf("fcntl GETFL ret: %d, errno: %d\n", flags, errno); + scc_socket_close_extended(dfcyc, port, 0); + return; + } + ret = fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + if(ret == -1) { + printf("fcntl SETFL ret: %d, errno: %d\n", ret, errno); + scc_socket_close_extended(dfcyc, port, 0); + return; + } +# endif +#endif +} + +void +scc_accept_socket(dword64 dfcyc, int port) +{ +#ifdef SCC_SOCKETS + Scc *scc_ptr; + SOCKET rdwrfd; + socklen_t address_len; + int flags, num_rings, ret; + + scc_ptr = &(g_scc[port]); + + if(scc_ptr->sockfd == INVALID_SOCKET) { + // printf("in accept_socket, call socket_open\n"); + scc_socket_open_incoming(dfcyc, port); + } + if(scc_ptr->sockfd == INVALID_SOCKET) { + return; /* just give up */ + } + if(scc_ptr->rdwrfd == INVALID_SOCKET) { + address_len = scc_ptr->sockaddr_size; + rdwrfd = accept(scc_ptr->sockfd, scc_ptr->sockaddr_ptr, + &address_len); + scc_ptr->sockaddr_size = (int)address_len; + if(rdwrfd == INVALID_SOCKET) { + return; + } + + flags = 0; + ret = 0; +#ifndef _WIN32 + /* For Linux, we need to set O_NONBLOCK on the rdwrfd */ + flags = fcntl(rdwrfd, F_GETFL, 0); + if(flags == -1) { + printf("fcntl GETFL ret: %d, errno: %d\n", flags,errno); + return; + } + ret = fcntl(rdwrfd, F_SETFL, flags | O_NONBLOCK); + if(ret == -1) { + printf("fcntl SETFL ret: %d, errno: %d\n", ret, errno); + return; + } +#endif + scc_ptr->rdwrfd = rdwrfd; + printf("Set port[%d].rdwrfd = %llx\n", port, (dword64)rdwrfd); + + num_rings = 4; + if(scc_ptr->modem_s0_val > 0) { + num_rings = scc_ptr->modem_s0_val; + } + scc_ptr->socket_num_rings = num_rings; + scc_ptr->socket_last_ring_dfcyc = 0; /* do ring now*/ + + /* and send some telnet codes */ + if(g_serial_modem_init_telnet) { + scc_ptr->telnet_reqwill_mode[0] = 0xa; + scc_ptr->telnet_reqdo_mode[0] = 0xa; + /* 3=GO_AH and 1=ECHO */ + scc_ptr->telnet_reqdo_mode[1] = 0x4; // 34=LINEMODE + } + printf("Telnet reqwill and reqdo's initialized: %08x_%08x," + "%08x_%08x\n", + scc_ptr->telnet_reqwill_mode[1], + scc_ptr->telnet_reqwill_mode[0], + scc_ptr->telnet_reqdo_mode[1], + scc_ptr->telnet_reqdo_mode[0]); + + scc_socket_modem_do_ring(dfcyc, port); + } +#endif +} + +void +scc_socket_telnet_reqs(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + word32 mask, willmask, domask; + int i, j; + + scc_ptr = &(g_scc[port]); + for(i = 0; i < 64; i++) { + j = i >> 5; + mask = 1 << (i & 31); + willmask = scc_ptr->telnet_reqwill_mode[j]; + if(willmask & mask) { + scc_printf("Telnet reqwill %d\n", i); + scc_add_to_writebuf(dfcyc, port, 0xff); + scc_add_to_writebuf(dfcyc, port, 0xfb); /* WILL */ + scc_add_to_writebuf(dfcyc, port, i); + } + domask = scc_ptr->telnet_reqdo_mode[j]; + if(domask & mask) { + scc_printf("Telnet reqdo %d\n", i); + scc_add_to_writebuf(dfcyc, port, 0xff); + scc_add_to_writebuf(dfcyc, port, 0xfd); /* DO */ + scc_add_to_writebuf(dfcyc, port, i); + } + } +} + +void +scc_socket_fill_readbuf(dword64 dfcyc, int port, int space_left) +{ +#ifdef SCC_SOCKETS + byte tmp_buf[256]; + Scc *scc_ptr; + SOCKET rdwrfd; + int ret; + int i; + + scc_ptr = &(g_scc[port]); + + scc_accept_socket(dfcyc, port); + scc_socket_modem_do_ring(dfcyc, port); + + if(scc_ptr->modem_state) { + /* Just get out, this is modem mode */ + /* The transmit function stuffs any bytes in receive buf */ + return; + } + rdwrfd = scc_ptr->rdwrfd; + if(rdwrfd == INVALID_SOCKET) { + return; /* just get out */ + } + + /* Try reading some bytes */ + space_left = MY_MIN(space_left, 256); + ret = (int)recv(rdwrfd, tmp_buf, space_left, 0); + if(ret > 0) { + for(i = 0; i < ret; i++) { + if(tmp_buf[i] == 0) { + /* Skip null chars */ + continue; + } + scc_socket_recvd_char(dfcyc, port, tmp_buf[i]); + } + } else if(ret == 0) { + /* assume socket close */ + printf("recv got 0 from rdwrfd=%llx, closing\n", + (dword64)rdwrfd); + scc_socket_close_extended(dfcyc, port, 1); + } +#endif +} + +void +scc_socket_recvd_char(dword64 dfcyc, int port, int c) +{ + Scc *scc_ptr; + word32 locmask, remmask, mask, reqwillmask, reqdomask; + int telnet_mode, telnet_iac, eff_c, cpos, reply; + + scc_ptr = &(g_scc[port]); + + telnet_mode = scc_ptr->telnet_mode; + telnet_iac = scc_ptr->telnet_iac; + + eff_c = c; + if(scc_ptr->cur_state == 2) { + // Outgoing only connection, support 8-bit transfers with + // no telnet command support + telnet_mode = 0; + telnet_iac = 0; + } else if(telnet_iac == 0) { + if(c == 0xff) { + scc_ptr->telnet_iac = 0xff; + return; /* and just get out */ + } + } else { + /* last char was 0xff, see if this is 0xff */ + if(c != 0xff) { + /* this is some kind of command */ + eff_c = eff_c | 0x100; /* indicate prev char IAC */ + } + } + scc_ptr->telnet_iac = 0; + + mask = 1 << (c & 31); + cpos = (c >> 5) & 1; + locmask = scc_ptr->telnet_local_mode[cpos] & mask; + remmask = scc_ptr->telnet_remote_mode[cpos] & mask; + reqwillmask = scc_ptr->telnet_reqwill_mode[cpos] & mask; + reqdomask = scc_ptr->telnet_reqdo_mode[cpos] & mask; + switch(telnet_mode) { + case 0: /* just passing through bytes */ + switch(eff_c) { + case 0x1fe: /* DON'T */ + case 0x1fd: /* DO */ + case 0x1fc: /* WON'T */ + case 0x1fb: /* WILL */ + case 0x1fa: /* SB */ + telnet_mode = c; + break; + default: + if(eff_c < 0x100) { + scc_add_to_readbuf(dfcyc, port, c); + } + break; + } + break; + case 3: /* LINEMODE SB SLC, octet 0 */ + if(eff_c == 0x1f0) { + /* SE, the end */ + telnet_mode = 0; + } + scc_printf("LINEMODE SLC octet 0: %02x\n", c); + telnet_mode = 4; + break; + case 4: /* LINEMODE SB SLC, octet 1 */ + scc_printf("LINEMODE SLC octet 1: %02x\n", c); + telnet_mode = 5; + if(eff_c == 0x1f0) { + /* SE, the end */ + scc_printf("Got SE at octet 1...\n"); + telnet_mode = 0; + } + break; + case 5: /* LINEMODE SB SLC, octet 2 */ + scc_printf("LINEMODE SLC octet 2: %02x\n", c); + telnet_mode = 3; + if(eff_c == 0x1f0) { + /* SE, the end */ + printf("Got Telnet SE at octet 2...\n"); + telnet_mode = 0; + } + break; + case 34: /* LINEMODE SB beginning */ + switch(c) { + case 3: /* SLC */ + telnet_mode = 3; + break; + default: + telnet_mode = 0xee; /* go to SB eat */ + break; + } + break; + case 0xfa: /* in 0xfa = SB mode, eat up subcommands */ + switch(c) { + case 34: /* LINEMODE */ + telnet_mode = 34; + break; + default: + telnet_mode = 0xee; /* SB eat mode */ + break; + } + break; + case 0xee: /* in SB eat mode */ + if(eff_c == 0x1f0) { /* SE, end of sub-command */ + telnet_mode = 0; + } else { + /* just stay in eat mode */ + } + break; + case 0xfe: /* previous char was "DON'T" */ + if(locmask && (reqwillmask == 0)) { + /* it's a mode change */ + /* always OK to turn off a mode that we had on */ + scc_add_to_writebuf(dfcyc, port, 0xff); + scc_add_to_writebuf(dfcyc, port, 0xfc); /* WON'T */ + scc_add_to_writebuf(dfcyc, port, c); + } + scc_ptr->telnet_local_mode[cpos] &= ~mask; + scc_ptr->telnet_reqwill_mode[cpos] &= ~mask; + telnet_mode = 0; + break; + case 0xfd: /* previous char was "DO" */ + reply = 0xfc; + if(locmask == 0 && (reqwillmask == 0)) { + /* it's a mode change, send some response */ + reply = 0xfc; /* nack it with WON'T */ + if(c == 0x03 || c == 0x01) { + reply = 0xfb; /* Ack with WILL */ + } + scc_add_to_writebuf(dfcyc, port, 0xff); + scc_add_to_writebuf(dfcyc, port, reply); + scc_add_to_writebuf(dfcyc, port, c); + } + if(reqwillmask || (reply == 0xfb)) { + scc_ptr->telnet_local_mode[cpos] |= mask; + } + scc_ptr->telnet_reqwill_mode[cpos] &= ~mask; + telnet_mode = 0; + break; + case 0xfc: /* previous char was "WON'T" */ + if(remmask && (reqdomask == 0)) { + /* it's a mode change, ack with DON'T */ + scc_add_to_writebuf(dfcyc, port, 0xff); + scc_add_to_writebuf(dfcyc, port, 0xfe); /* DON'T */ + scc_add_to_writebuf(dfcyc, port, c); + } + scc_ptr->telnet_remote_mode[cpos] &= ~mask; + scc_ptr->telnet_reqdo_mode[cpos] &= ~mask; + telnet_mode = 0; + break; + case 0xfb: /* previous char was "WILL" */ + reply = 0xfe; /* nack it with DON'T */ + if(remmask == 0 && (reqdomask == 0)) { + /* it's a mode change, send some response */ + if(c == 0x03 || c == 0x01) { + reply = 0xfd; /* Ack with DO */ + } + scc_add_to_writebuf(dfcyc, port, 0xff); + scc_add_to_writebuf(dfcyc, port, reply); + scc_add_to_writebuf(dfcyc, port, c); + } + if(reqdomask || (reply == 0xfd)) { + scc_ptr->telnet_remote_mode[cpos] |= mask; + } + scc_ptr->telnet_reqdo_mode[cpos] &= ~mask; + telnet_mode = 0; + break; + default: + telnet_mode = 0; + break; + } + scc_ptr->telnet_mode = telnet_mode; +} + +void +scc_socket_empty_writebuf(dword64 dfcyc, int port) +{ +#ifdef SCC_SOCKETS +# ifndef _WIN32 + struct sigaction newact, oldact; +# endif + Scc *scc_ptr; + dword64 diff_dusec; + SOCKET rdwrfd; + int plus_mode, plus_char, rdptr, wrptr, done, ret, len, c; + int i; + + scc_socket_maybe_open(dfcyc, port, 0); + + scc_ptr = &(g_scc[port]); + + /* See if +++ done and we should go to command mode */ + diff_dusec = (dfcyc - scc_ptr->out_char_dfcyc) >> 16; + if((diff_dusec > 900LL*1000) && (scc_ptr->modem_plus_mode == 3) && + (scc_ptr->modem_state == 0) && + (scc_ptr->cur_state == 1)) { + scc_ptr->modem_state = 1; /* go modem mode, stay connect*/ + scc_ptr->modem_plus_mode = 0; + scc_socket_send_modem_code(dfcyc, port, 0); // "OK" + } + + /* Try writing some bytes */ + done = 0; + while(!done) { + rdptr = scc_ptr->out_rdptr; + wrptr = scc_ptr->out_wrptr; + if(rdptr == wrptr) { + done = 1; + break; + } + rdwrfd = scc_ptr->rdwrfd; + len = wrptr - rdptr; + if(len < 0) { + len = SCC_OUTBUF_SIZE - rdptr; + } + if(len > 32) { + len = 32; + } + //printf("Writing data, %d bytes, modem_state:%d\n", len, + // scc_ptr->modem_state); + if(len <= 0) { + done = 1; + break; + } + scc_socket_maybe_open(dfcyc, port, 1); + + if(scc_ptr->modem_state) { + len = 1; + scc_socket_modem_write(dfcyc, port, + scc_ptr->out_buf[rdptr]); + scc_ptr->write_called_this_vbl = 0; + ret = 1; + } else { + if(rdwrfd == INVALID_SOCKET) { + return; + } + plus_char = scc_ptr->modem_s2_val; + if((plus_char == 0) || (plus_char >= 128)) { + plus_char = 0xfff; // Invalid + scc_ptr->modem_plus_mode = 0; + } + // Look for '+++' within .8 seconds + for(i = 0; i < len; i++) { + c = scc_ptr->out_buf[rdptr + i]; + plus_mode = scc_ptr->modem_plus_mode; + diff_dusec = + (dfcyc - scc_ptr->out_char_dfcyc) >> 16; + if(c == plus_char && plus_mode == 0) { + if(diff_dusec > 500LL*1000) { + scc_ptr->modem_plus_mode = 1; + } + } else if(c == plus_char) { + if(diff_dusec < 800LL*1000) { + plus_mode++; + scc_ptr->modem_plus_mode = + plus_mode; + } + } else { + scc_ptr->modem_plus_mode = 0; + } + scc_ptr->out_char_dfcyc = dfcyc; + } +# ifndef _WIN32 + // ignore SIGPIPE around writes to the socket, so we + // can catch a closed socket and prepare to accept + // a new connection. Otherwise, SIGPIPE kills KEGS + sigemptyset(&newact.sa_mask); + newact.sa_handler = SIG_IGN; + newact.sa_flags = 0; + sigaction(SIGPIPE, &newact, &oldact); +# endif + + ret = (int)send(rdwrfd, &(scc_ptr->out_buf[rdptr]), + len, 0); + +# ifndef _WIN32 + sigaction(SIGPIPE, &oldact, 0); + /* restore previous SIGPIPE behavior */ +# endif + +#if 0 + printf("sock output: %02x, len:%d\n", + scc_ptr->out_buf[rdptr], len); +#endif + + } + + if(ret == 0) { + done = 1; /* give up for now */ + break; + } else if(ret < 0) { + /* assume socket is dead */ + printf("socket write failed, resuming modem mode\n"); + scc_socket_close_extended(dfcyc, port, 1); + done = 1; + break; + } else { + rdptr = rdptr + ret; + if(rdptr >= SCC_OUTBUF_SIZE) { + rdptr = rdptr - SCC_OUTBUF_SIZE; + } + scc_ptr->out_rdptr = rdptr; + } + } +#endif +} + +void +scc_socket_modem_write(dword64 dfcyc, int port, int c) +{ + Scc *scc_ptr; + char *str; + word32 modem_mode; + int do_echo, got_at, len; + + scc_ptr = &(g_scc[port]); + + if(scc_ptr->sockfd == INVALID_SOCKET) { + scc_socket_open_incoming(dfcyc, port); + } + + modem_mode = scc_ptr->modem_mode; + str = (char *)&(scc_ptr->modem_cmd_str[0]); + + do_echo = ((modem_mode & SCCMODEM_NOECHO) == 0); + len = scc_ptr->modem_cmd_len; + got_at = 0; +#if 0 + printf("T:%016llx M[%d][%d]: %02x\n", dfcyc, port, len, c); +#endif + if(len >= 2 && str[0] == 'a' && str[1] == 't') { + /* we've got an 'at', do not back up past it */ + got_at = 1; + } + if(c == 0x0d) { + if(do_echo) { + scc_add_to_readbuf(dfcyc, port, c); /* echo cr */ + scc_add_to_readbuf(dfcyc, port, 0x0a); /* echo lf */ + } + do_echo = 0; /* already did the echo */ + scc_socket_do_cmd_str(dfcyc, port); + scc_ptr->modem_cmd_len = 0; + len = 0; + str[0] = 0; + } else if(c == 0x08) { + if(len <= 0) { + do_echo = 0; /* do not go past left margin */ + } else if(len == 2 && got_at) { + do_echo = 0; /* do not erase "AT" */ + } else { + /* erase a character */ + len--; + str[len] = 0; + } + } else if(c < 0x20) { + /* ignore all control characters, don't echo */ + /* includes line feeds and nulls */ + do_echo = 0; + } else { + /* other characters */ + if(len < SCC_MODEM_MAX_CMD_STR) { + str[len] = tolower(c); + str[len+1] = 0; + len++; + } + } + scc_ptr->modem_cmd_len = len; + if(do_echo) { + scc_add_to_readbuf(dfcyc, port, c); /* echo */ + } +} + +void +scc_socket_do_cmd_str(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + char *str; + int pos, len, ret_val, reg, reg_val, was_amp, out_port, c; + int i; + + scc_ptr = &(g_scc[port]); + + str = (char *)&(scc_ptr->modem_cmd_str[0]); + printf("Got modem string :%s:=%02x %02x %02x\n", str, str[0], str[1], + str[2]); + + len = scc_ptr->modem_cmd_len; + str[len] = 0; + str[len+1] = 0; + str[len+2] = 0; + pos = -1; + if(len < 2) { + /* just ignore it */ + return; + } + if(str[0] != 'a' || str[1] != 't') { + return; + } + + /* Some AT command received--make sure socket 6501/6502 is open */ + printf("Some AT command received, sockfd=%llx\n", + (dword64)scc_ptr->sockfd); + + pos = 2 - 1; + ret_val = 0; /* "OK" */ + was_amp = 0; + while(++pos < len) { + c = str[pos] + was_amp; + was_amp = 0; + switch(c) { + case '&': /* at& */ + was_amp = 0x100; + break; + case 'z': /* atz */ + scc_ptr->modem_mode = 0; + scc_ptr->modem_s0_val = 0; + pos = len; /* ignore any other commands */ + break; + case 'e': /* ate = echo */ + c = str[pos+1]; + if(c == '1') { + scc_ptr->modem_mode &= ~SCCMODEM_NOECHO; + pos++; + } else { + scc_ptr->modem_mode |= SCCMODEM_NOECHO; + pos++; + } + break; + case 'v': /* atv = verbose */ + c = str[pos+1]; + if(c == '1') { + scc_ptr->modem_mode &= ~SCCMODEM_NOVERBOSE; + pos++; + } else { + scc_ptr->modem_mode |= SCCMODEM_NOVERBOSE; + pos++; + } + break; + case 'o': /* ato = go online */ + printf("ato\n"); + if(scc_ptr->dcd && scc_ptr->modem_state && + (scc_ptr->rdwrfd != INVALID_SOCKET)) { + printf("Going back online\n"); + scc_socket_modem_connect(dfcyc, port); + scc_ptr->modem_state = 0; + // talk to socket + ret_val = -1; + } + break; + case 'h': /* ath = hang up */ + printf("ath, hanging up\n"); + scc_socket_close(port); + if(scc_ptr->rdwrfd != INVALID_SOCKET) { + scc_socket_close_extended(dfcyc, port, 0); + } + /* scc_socket_maybe_open_incoming(dfcyc, port); */ + /* reopen listen */ + break; + case 'a': /* ata */ + printf("Doing ATA\n"); + scc_socket_do_answer(dfcyc, port); + ret_val = -1; + break; + case 'd': /* atd */ + scc_ptr->modem_out_portnum = 23; + pos++; + c = str[pos]; + if(c == 't' || c == 'p') { + /* skip tone or pulse */ + pos++; + } + /* see if it is 111 */ + if(strcmp(&str[pos], "111") == 0) { + /* Do PPP! */ + } else { + /* get string to connect to */ + /* Shift string so hostname moves to str[0] */ + for(i = 0; i < len; i++) { + c = str[pos]; + if(c == ':') { + /* get port number now */ + out_port = (int)strtol( + &str[pos+1], 0, 10); + if(out_port <= 1) { + out_port = 23; + } + scc_ptr->modem_out_portnum = + out_port; + c = 0; + } + str[i] = c; + if((pos >= len) || (c == 0)) { + break; + } + pos++; + } + } + scc_socket_open_outgoing(dfcyc, port, + (char *)&scc_ptr->modem_cmd_str[0], + scc_ptr->modem_out_portnum); + ret_val = -1; + pos = len; /* always eat rest of the line */ + break; + case 's': /* atsnn=yy */ + pos++; + reg = 0; + while(1) { + c = str[pos]; + if(c < '0' || c > '9') { + break; + } + reg = (reg * 10) + c - '0'; + pos++; + } + if(c == '?') { + /* display S-register */ + if(reg == 0) { + scc_add_to_readbufv(dfcyc, port, + "S0=%d\n", + scc_ptr->modem_s0_val); + } + break; + } + if(c != '=') { + break; + } + pos++; + reg_val = 0; + while(1) { + c = str[pos]; + if(c < '0' || c >'9') { + break; + } + reg_val = (reg_val * 10) + c - '0'; + pos++; + } + printf("ats%d = %d\n", reg, reg_val); + if(reg == 0) { + scc_ptr->modem_s0_val = reg_val; + } + if(reg == 2) { + scc_ptr->modem_s2_val = reg_val; + } + pos--; + break; + default: + /* some command--peek into next chars to finish it */ + while(1) { + c = str[pos+1]; + if(c >= '0' && c <= '9') { + /* eat numbers */ + pos++; + continue; + } + if(c == '=') { + /* eat this as well */ + pos++; + continue; + } + /* else get out */ + break; + } + } + } + + if(ret_val >= 0) { + scc_socket_send_modem_code(dfcyc, port, ret_val); + } +} + +void +scc_socket_send_modem_code(dword64 dfcyc, int port, int code) +{ + Scc *scc_ptr; + char *str; + word32 modem_mode; + + scc_ptr = &(g_scc[port]); + + switch(code) { + case 0: str = "OK"; break; + case 1: str = "CONNECT"; break; + case 2: str = "RING"; break; + case 3: str = "NO CARRIER"; break; + case 4: str = "ERROR"; break; + case 5: str = "CONNECT 1200"; break; + case 10: str = "CONNECT 2400"; break; + case 12: str = "CONNECT 9600"; break; // Generic AT docs/Warp6 BBS + case 13: str = "CONNECT 9600"; break; // US Robotics Sportster/HST + case 14: str = "CONNECT 19200"; break; // Warp6 BBS + case 16: str = "CONNECT 19200"; break; // Generic AT docs + case 25: str = "CONNECT 14400"; break; // US Robotics Sportster + case 85: str = "CONNECT 19200"; break; // US Robotics Sportster + case 18: str = "CONNECT 57600"; break; // Generic AT docs/Warp6 BBS + case 28: str = "CONNECT 38400"; break; // Warp6 BBS/Hayes + default: + str = "ERROR"; + } + + printf("Sending modem code %d = %s\n", code, str); + + modem_mode = scc_ptr->modem_mode; + if(modem_mode & SCCMODEM_NOVERBOSE) { + /* just the number */ + scc_add_to_readbufv(dfcyc, port, "%d", code); + scc_add_to_readbuf(dfcyc, port, 0x0d); + } else { + scc_add_to_readbufv(dfcyc, port, "%s\n", str); + } +} + +void +scc_socket_modem_connect(dword64 dfcyc, int port) +{ + // Send code telling Term program the connect speed + if(g_scc[port].cur_state == 1) { // Virtual modem + scc_socket_send_modem_code(dfcyc, port, + g_serial_modem_response_code); /*28=38400*/ + } +} + +void +scc_socket_modem_do_ring(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + dword64 diff_dusecs; + int num_rings; + + scc_ptr = &(g_scc[port]); + num_rings = scc_ptr->socket_num_rings; + if((num_rings > 0) && scc_ptr->modem_state) { + num_rings--; + diff_dusecs = (dfcyc - scc_ptr->socket_last_ring_dfcyc) >> 16; + if(diff_dusecs < 2LL*1000*1000) { + return; /* nothing more to do */ + } + + scc_ptr->socket_num_rings = num_rings; + scc_ptr->socket_last_ring_dfcyc = dfcyc; + if(num_rings <= 0) { + /* decide on answering */ + if(scc_ptr->modem_s0_val) { + scc_socket_do_answer(dfcyc, port); + } else { + printf("No answer, closing socket\n"); + scc_socket_close(port); + } + } else { + scc_socket_send_modem_code(dfcyc, port, 2); /* RING */ + } + } +} + +void +scc_socket_do_answer(dword64 dfcyc, int port) +{ + Scc *scc_ptr; + + scc_ptr = &(g_scc[port]); + scc_accept_socket(dfcyc, port); + if(scc_ptr->rdwrfd == INVALID_SOCKET) { + printf("Answer when rdwrfd=-1, closing\n"); + scc_socket_close_extended(dfcyc, port, 0); + /* send NO CARRIER message */ + } else { + scc_socket_telnet_reqs(dfcyc, port); + printf("Send telnet reqs\n"); + if(scc_ptr->modem_state) { + scc_socket_modem_connect(dfcyc, port); + } + scc_ptr->modem_state = 0; // Talk to socket + scc_ptr->dcd = 1; // carrier on + scc_ptr->socket_num_rings = 0; + } +} + diff --git a/gsplus/src/scc_unixdriver.c b/gsplus/src/scc_unixdriver.c new file mode 100644 index 0000000..53a5f03 --- /dev/null +++ b/gsplus/src/scc_unixdriver.c @@ -0,0 +1,224 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2025 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +/* This file contains the Mac/Linux calls to a real serial port */ + +#include "defc.h" +#include "scc.h" + +#ifndef _WIN32 +# include +#endif + +extern Scc g_scc[2]; +extern word32 g_c025_val; +extern char *g_serial_device[2]; + +#ifndef _WIN32 +void +scc_serial_unix_open(int port) +{ + Scc *scc_ptr; + int fd; + + scc_ptr = &(g_scc[port]); + + fd = open(&g_serial_device[port][0], O_RDWR | O_NONBLOCK); + scc_ptr->unix_dev_fd = fd; + + printf("scc_serial_unix_init %d called, fd: %d\n", port, fd); + + if(fd < 0) { + scc_ptr->unix_dev_fd = -1; + scc_ptr->cur_state = -1; // Failed to open + return; + } + + scc_serial_unix_change_params(port); + + scc_ptr->cur_state = 0; // Actual Serial device +} + +void +scc_serial_unix_close(int port) +{ + Scc *scc_ptr; + int fd; + + scc_ptr = &(g_scc[port]); + + fd = scc_ptr->unix_dev_fd; + if(fd >= 0) { + close(fd); + } + scc_ptr->unix_dev_fd = -1; +} + +void +scc_serial_unix_change_params(int port) +{ + struct termios termios_buf; + Scc *scc_ptr; + int fd, csz, ret; + + scc_ptr = &(g_scc[port]); + + fd = scc_ptr->unix_dev_fd; + printf("scc_serial_unix_change_parms port: %d, fd: %d\n", port, fd); + if(fd <= 0) { + return; + } + + ret = tcgetattr(fd, &termios_buf); + if(ret != 0) { + printf("tcgetattr port%d ret: %d\n", port, ret); + } + +#if 1 + printf("baudrate: %d, iflag:%x, oflag:%x, cflag:%x, lflag:%x\n", + (int)termios_buf.c_ispeed, (int)termios_buf.c_iflag, + (int)termios_buf.c_oflag, (int)termios_buf.c_cflag, + (int)termios_buf.c_lflag); +#endif + + memset(&termios_buf, 0, sizeof(struct termios)); + cfmakeraw(&termios_buf); + cfsetspeed(&termios_buf, scc_ptr->baud_rate); + + csz = scc_ptr->char_size; + termios_buf.c_cflag = CREAD | CLOCAL; + termios_buf.c_cflag |= (csz == 5) ? CS5 : + (csz == 6) ? CS6 : + (csz == 7) ? CS7 : + CS8; + switch((scc_ptr->reg[4] >> 2) & 0x3) { + case 2: // 1.5 stop bits + termios_buf.c_cflag |= CSTOPB; /* no 1.5 stop bit setting.*/ + break; + case 3: // 2 stop bits + termios_buf.c_cflag |= CSTOPB; + break; + } + + switch((scc_ptr->reg[4]) & 0x3) { + case 1: // Odd parity + termios_buf.c_cflag |= (PARENB | PARODD); + break; + case 3: // Even parity + termios_buf.c_cflag |= PARENB; + break; + } + + /* always enabled DTR and RTS control */ +#ifdef CRTSCTS + termios_buf.c_cflag |= CRTSCTS; // Linux: CTS/RTS +#endif +#ifdef CRTS_IFLOW + termios_buf.c_cflag |= CDTR_IFLOW | CRTS_IFLOW; // Mac: CTS/RTS +#endif + + printf("fd: %d, baudrate: %d, iflag:%x, oflag:%x, cflag:%x, lflag:%x\n", + fd, (int)termios_buf.c_ispeed, (int)termios_buf.c_iflag, + (int)termios_buf.c_oflag, (int)termios_buf.c_cflag, + (int)termios_buf.c_lflag); + ret = tcsetattr(fd, TCSANOW, &termios_buf); + if(ret != 0) { + printf("tcsetattr ret: %d\n", ret); + } +} + +void +scc_serial_unix_fill_readbuf(dword64 dfcyc, int port, int space_left) +{ + byte tmp_buf[256]; + Scc *scc_ptr; + int fd, ret, flags, dcd; + int i; + + scc_ptr = &(g_scc[port]); + + fd = scc_ptr->unix_dev_fd; + if(fd <= 0) { + return; + } + + /* Try reading some bytes */ + space_left = MY_MIN(space_left, 256); + ret = read(fd, tmp_buf, space_left); + + if(ret > 0) { + for(i = 0; i < ret; i++) { + scc_add_to_readbuf(dfcyc, port, tmp_buf[i]); + } + } + flags = 0; + dcd = 0; + +#if defined(TIOCMGET) && defined(TIOCM_CAR) + ret = ioctl(fd, TIOCMGET, &flags); + if(ret == 0) { + dcd = 0; + if(flags & TIOCM_CAR) { // DCD + dcd = 1; + } + scc_ptr->dcd = dcd; + } +#endif +} + +void +scc_serial_unix_empty_writebuf(int port) +{ + Scc *scc_ptr; + int fd, rdptr, wrptr, done, ret, len; + + scc_ptr = &(g_scc[port]); + + fd = scc_ptr->unix_dev_fd; + if(fd <= 0) { + return; + } + + /* Try writing some bytes */ + done = 0; + while(!done) { + rdptr = scc_ptr->out_rdptr; + wrptr = scc_ptr->out_wrptr; + if(rdptr == wrptr) { + //printf("...rdptr == wrptr\n"); + done = 1; + break; + } + len = wrptr - rdptr; + if(len < 0) { + len = SCC_OUTBUF_SIZE - rdptr; + } + if(len > 32) { + len = 32; + } + if(len <= 0) { + done = 1; + break; + } + ret = write(fd, &(scc_ptr->out_buf[rdptr]), len); + + if(ret <= 0) { + done = 1; + break; + } else { + rdptr = rdptr + ret; + if(rdptr >= SCC_OUTBUF_SIZE) { + rdptr = rdptr - SCC_OUTBUF_SIZE; + } + scc_ptr->out_rdptr = rdptr; + } + } +} +#endif /* !_WIN32 */ diff --git a/gsplus/src/scc_windriver.c b/gsplus/src/scc_windriver.c new file mode 100644 index 0000000..2b32263 --- /dev/null +++ b/gsplus/src/scc_windriver.c @@ -0,0 +1,257 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +/* This file contains the Win32 COM1/COM2 calls */ + +#include "defc.h" +#include "scc.h" + +extern Scc g_scc[2]; +extern word32 g_c025_val; +extern int g_serial_win_device[2]; + +#ifdef _WIN32 +void +scc_serial_win_open(int port) +{ + COMMTIMEOUTS commtimeouts; + char str_buf[32]; + Scc *scc_ptr; + HANDLE com_handle; + int ret; + + scc_ptr = &(g_scc[port]); + + snprintf(&str_buf[0], sizeof(str_buf), "COM%d", + g_serial_win_device[port]); + + com_handle = CreateFile(&str_buf[0], GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, 0, NULL); + + scc_ptr->win_com_handle = com_handle; + + printf("scc_serial_win_init %d called, com_handle: %p\n", port, + com_handle); + + if(com_handle == INVALID_HANDLE_VALUE) { + scc_ptr->cur_state = -1; // Failed to open + return; + } + scc_ptr->win_dcb_ptr = malloc(sizeof(DCB)); + + scc_serial_win_change_params(port); + + commtimeouts.ReadIntervalTimeout = MAXDWORD; + commtimeouts.ReadTotalTimeoutMultiplier = 0; + commtimeouts.ReadTotalTimeoutConstant = 0; + commtimeouts.WriteTotalTimeoutMultiplier = 0; + commtimeouts.WriteTotalTimeoutConstant = 10; + ret = SetCommTimeouts(com_handle, &commtimeouts); + if(ret == 0) { + printf("setcommtimeout ret: %d\n", ret); + } + scc_ptr->cur_state = 0; // COM* is open +} + +void +scc_serial_win_close(int port) +{ + Scc *scc_ptr; + HANDLE com_handle; + + scc_ptr = &(g_scc[port]); + com_handle = scc_ptr->win_com_handle; + scc_ptr->win_com_handle = INVALID_HANDLE_VALUE; + if(com_handle == INVALID_HANDLE_VALUE) { + return; + } + CloseHandle(com_handle); + free(scc_ptr->win_dcb_ptr); + scc_ptr->win_dcb_ptr = 0; +} + +void +scc_serial_win_change_params(int port) +{ + DCB *dcbptr; + HANDLE com_handle; + Scc *scc_ptr; + int ret; + + scc_ptr = &(g_scc[port]); + + com_handle = scc_ptr->win_com_handle; + dcbptr = scc_ptr->win_dcb_ptr; + if((com_handle == INVALID_HANDLE_VALUE) || (scc_ptr->cur_state != 0)) { + return; + } + + ret = GetCommState(com_handle, dcbptr); + if(ret == 0) { + printf("getcomm port%d ret: %d\n", port, ret); + } + +#if 1 + printf("dcb.baudrate: %d, bytesize:%d, stops:%d, parity:%d\n", + (int)dcbptr->BaudRate, (int)dcbptr->ByteSize, + (int)dcbptr->StopBits, (int)dcbptr->Parity); + printf("dcb.binary: %d, ctsflow: %d, dsrflow: %d, dtr: %d, dsr: %d\n", + (int)dcbptr->fBinary, + (int)dcbptr->fOutxCtsFlow, + (int)dcbptr->fOutxDsrFlow, + (int)dcbptr->fDtrControl, + (int)dcbptr->fDsrSensitivity); + printf("dcb.txonxoff:%d, outx:%d, inx: %d, null: %d, rts: %d\n", + (int)dcbptr->fTXContinueOnXoff, + (int)dcbptr->fOutX, + (int)dcbptr->fInX, + (int)dcbptr->fNull, + (int)dcbptr->fRtsControl); + printf("dcb.fAbortOnErr:%d, fParity:%d\n", (int)dcbptr->fAbortOnError, + (int)dcbptr->fParity); +#endif + + dcbptr->fAbortOnError = 0; + + dcbptr->BaudRate = scc_ptr->baud_rate; + dcbptr->ByteSize = scc_ptr->char_size; + dcbptr->StopBits = ONESTOPBIT; + switch((scc_ptr->reg[4] >> 2) & 0x3) { + case 2: // 1.5 stop bits + dcbptr->StopBits = ONE5STOPBITS; + break; + case 3: // 2 stop bits + dcbptr->StopBits = TWOSTOPBITS; + break; + } + + dcbptr->Parity = NOPARITY; + switch((scc_ptr->reg[4]) & 0x3) { + case 1: // Odd parity + dcbptr->Parity = ODDPARITY; + break; + case 3: // Even parity + dcbptr->Parity = EVENPARITY; + break; + } + + dcbptr->fNull = 0; + dcbptr->fDtrControl = DTR_CONTROL_ENABLE; + dcbptr->fDsrSensitivity = 0; + dcbptr->fOutxCtsFlow = 0; + dcbptr->fOutxDsrFlow = 0; + dcbptr->fParity = 0; + dcbptr->fInX = 0; + dcbptr->fOutX = 0; + dcbptr->fRtsControl = RTS_CONTROL_ENABLE; + + ret = SetCommState(com_handle, dcbptr); + if(ret == 0) { + printf("SetCommState ret: %d, new baud: %d\n", ret, + (int)dcbptr->BaudRate); + } +} + +void +scc_serial_win_fill_readbuf(dword64 dfcyc, int port, int space_left) +{ + byte tmp_buf[256]; + Scc *scc_ptr; + HANDLE com_handle; + DWORD bytes_read; + int ret; + int i; + + scc_ptr = &(g_scc[port]); + + com_handle = scc_ptr->win_com_handle; + if(com_handle == INVALID_HANDLE_VALUE) { + return; + } + + /* Try reading some bytes */ + space_left = MY_MIN(256, space_left); + ret = ReadFile(com_handle, tmp_buf, space_left, &bytes_read, NULL); + + if(ret == 0) { + printf("ReadFile ret 0\n"); + } + + if(ret && (bytes_read > 0)) { + for(i = 0; i < (int)bytes_read; i++) { + scc_add_to_readbuf(dfcyc, port, tmp_buf[i]); + } + } +} + +void +scc_serial_win_empty_writebuf(int port) +{ + Scc *scc_ptr; + HANDLE com_handle; + DWORD bytes_written; + word32 err_code; + int rdptr, wrptr, done, ret, len; + + scc_ptr = &(g_scc[port]); + + //printf("win_empty_writebuf, com_h: %d\n", scc_ptr->win_com_handle); + com_handle = scc_ptr->win_com_handle; + if(com_handle == INVALID_HANDLE_VALUE) { + return; + } + + /* Try writing some bytes */ + done = 0; + while(!done) { + rdptr = scc_ptr->out_rdptr; + wrptr = scc_ptr->out_wrptr; + if(rdptr == wrptr) { + //printf("...rdptr == wrptr\n"); + done = 1; + break; + } + len = wrptr - rdptr; + if(len < 0) { + len = SCC_OUTBUF_SIZE - rdptr; + } + if(len > 32) { + len = 32; + } + if(len <= 0) { + done = 1; + break; + } + bytes_written = 1; + ret = WriteFile(com_handle, &(scc_ptr->out_buf[rdptr]), len, + &bytes_written, NULL); + printf("WriteFile ret: %d, bytes_written:%d, len:%d\n", ret, + (int)bytes_written, len); + + err_code = (word32)-1; + if(ret == 0) { + err_code = (word32)GetLastError(); + printf("WriteFile ret:0, err_code: %08x\n", err_code); + } + + if(ret == 0 || (bytes_written == 0)) { + done = 1; + break; + } else { + rdptr = rdptr + bytes_written; + if(rdptr >= SCC_OUTBUF_SIZE) { + rdptr = rdptr - SCC_OUTBUF_SIZE; + } + scc_ptr->out_rdptr = rdptr; + } + } +} + +#endif diff --git a/gsplus/src/sim65816.c b/gsplus/src/sim65816.c new file mode 100644 index 0000000..2228c3e --- /dev/null +++ b/gsplus/src/sim65816.c @@ -0,0 +1,2103 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2025 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include + +#define INCLUDE_RCSID_C +#include "defc.h" +#undef INCLUDE_RCSID_C + +double g_dtime_sleep = 0; +double g_dtime_in_sleep = 0; +extern char *g_argv0_path; + +#define MAX_EVENTS 64 + +/* All EV_* must be less than 256, since upper bits reserved for other use */ +/* e.g., DOC_INT uses upper bits to encode oscillator */ +#define EV_60HZ 1 +#define EV_STOP 2 +#define EV_SCAN_INT 3 +#define EV_DOC_INT 4 +#define EV_VBL_INT 5 +#define EV_SCC 6 +#define EV_VID_UPD 7 +#define EV_MOCKINGBOARD 8 + +extern int g_stepping; + +extern word32 g_c068_statereg; +extern int g_cur_a2_stat; + +extern word32 g_c02d_int_crom; + +extern word32 g_c035_shadow_reg; +extern word32 g_c036_val_speed; + +extern word32 g_c023_val; +extern word32 g_c041_val; +extern word32 g_c046_val; +extern word32 g_zipgs_reg_c059; +extern word32 g_zipgs_reg_c05a; +extern word32 g_zipgs_reg_c05b; +extern word32 g_zipgs_unlock; +extern Iwm g_iwm; + +Engine_reg engine; +extern word32 table8[]; +extern word32 table16[]; + +extern byte doc_ram[]; + +extern int g_iwm_motor_on; +extern int g_fast_disk_emul; +extern int g_slow_525_emul_wr; +extern int g_config_control_panel; + +extern int g_audio_enable; +extern int g_preferred_rate; + +int g_a2_fatal_err = 0; +dword64 g_dcycles_end = 0; +int g_halt_sim = 0; +int g_rom_version = -1; +int g_user_halt_bad = 0; +int g_halt_on_bad_read = 0; +int g_ignore_bad_acc = 1; +int g_ignore_halts = 1; +int g_code_red = 0; +int g_code_yellow = 0; +int g_emul_6502_ind_page_cross_bug = 0; + +int g_config_iwm_vbl_count = 0; +const char g_kegs_version_str[] = "1.38"; + +dword64 g_last_vbl_dfcyc = 0; +dword64 g_cur_dfcyc = 1; + +double g_last_vbl_dadjcycs = 0; +double g_dadjcycs = 0; + + +int g_wait_pending = 0; +int g_stp_pending = 0; +extern int g_irq_pending; + +int g_num_irq = 0; +int g_num_brk = 0; +int g_num_cop = 0; +int g_num_enter_engine = 0; +int g_io_amt = 0; +int g_engine_action = 0; +int g_engine_recalc_event = 0; +int g_engine_scan_int = 0; +int g_engine_doc_int = 0; + +#define MAX_FATAL_LOGS 20 + +int g_debug_file_fd = -1; +int g_fatal_log = -1; +char *g_fatal_log_strs[MAX_FATAL_LOGS]; + +word32 stop_run_at; + +int g_25sec_cntr = 0; +int g_1sec_cntr = 0; + +double g_dnatcycs_1sec = 0.0; +word32 g_natcycs_lastvbl = 0; + +int Verbose = 0; +int Halt_on = 0; + +word32 g_mem_size_base = 128*1024; /* size of motherboard memory */ +word32 g_mem_size_exp = 8*1024*1024; /* size of expansion RAM card */ +word32 g_mem_size_total = 128*1024; /* Total contiguous RAM from 0 */ + +extern word32 g_slow_mem_changed[]; + +byte *g_slow_memory_ptr = 0; +byte *g_memory_ptr = 0; +byte *g_dummy_memory1_ptr = 0; +byte *g_rom_fc_ff_ptr = 0; +byte *g_rom_cards_ptr = 0; + +void *g_memory_alloc_ptr = 0; /* for freeing memory area */ + +Page_info page_info_rd_wr[2*65536 + PAGE_INFO_PAD_SIZE]; + +word32 g_word32_tmp = 0; +int g_force_depth = -1; +int g_use_shmem = 1; + +extern word32 g_cycs_in_40col; +extern word32 g_cycs_in_xredraw; +extern word32 g_cycs_in_refresh_line; +extern word32 g_cycs_outside_run_16ms; +extern word32 g_refresh_bytes_xfer; + +extern int g_num_snd_plays; +extern int g_num_recalc_snd_parms; + +extern int g_doc_vol; + +extern int g_status_refresh_needed; + +int +sim_get_force_depth() +{ + return g_force_depth; +} + +int +sim_get_use_shmem() +{ + return g_use_shmem; +} + +void +sim_set_use_shmem(int use_shmem) +{ + g_use_shmem = use_shmem; +} + +#define TOOLBOX_LOG_LEN 64 + +int g_toolbox_log_pos = 0; +word32 g_toolbox_log_array[TOOLBOX_LOG_LEN][8]; + +word32 +toolbox_debug_4byte(word32 addr) +{ + word32 part1, part2; + + /* If addr looks safe, use it */ + if(addr > 0xbffc) { + return (word32)-1; + } + + part1 = get_memory16_c(addr); + part1 = (part1 >> 8) + ((part1 & 0xff) << 8); + part2 = get_memory16_c(addr+2); + part2 = (part2 >> 8) + ((part2 & 0xff) << 8); + + return (part1 << 16) + part2; +} + +void +toolbox_debug_c(word32 xreg, word32 stack, dword64 *dcyc_ptr) +{ + int pos; + + pos = g_toolbox_log_pos; + + stack += 9; + g_toolbox_log_array[pos][0] = (word32) + ((g_last_vbl_dfcyc + *dcyc_ptr) >> 16); + g_toolbox_log_array[pos][1] = stack+1; + g_toolbox_log_array[pos][2] = xreg; + g_toolbox_log_array[pos][3] = toolbox_debug_4byte(stack+1); + g_toolbox_log_array[pos][4] = toolbox_debug_4byte(stack+5); + g_toolbox_log_array[pos][5] = toolbox_debug_4byte(stack+9); + g_toolbox_log_array[pos][6] = toolbox_debug_4byte(stack+13); + g_toolbox_log_array[pos][7] = toolbox_debug_4byte(stack+17); + + pos++; + if(pos >= TOOLBOX_LOG_LEN) { + pos = 0; + } + + g_toolbox_log_pos = pos; +} + +void +show_toolbox_log() +{ + int pos; + int i; + + pos = g_toolbox_log_pos; + + for(i = TOOLBOX_LOG_LEN - 1; i >= 0; i--) { + printf("%2d:%2d: %08x %06x %04x: %08x %08x %08x %08x %08x\n", + i, pos, + g_toolbox_log_array[pos][0], + g_toolbox_log_array[pos][1], + g_toolbox_log_array[pos][2], + g_toolbox_log_array[pos][3], + g_toolbox_log_array[pos][4], + g_toolbox_log_array[pos][5], + g_toolbox_log_array[pos][6], + g_toolbox_log_array[pos][7]); + pos++; + if(pos >= TOOLBOX_LOG_LEN) { + pos = 0; + } + } +} + +word32 +get_memory_io(word32 loc, dword64 *dcyc_ptr) +{ + int tmp; + + if(loc > 0xffffff) { + halt_printf("get_memory_io:%08x out of range==halt!\n", loc); + return 0; + } + + tmp = loc & 0xfef000; + if(tmp == 0xc000 || tmp == 0xe0c000) { + return(io_read(loc & 0xfff, dcyc_ptr)); + } + + /* Else it's an illegal addr...skip if memory sizing */ + if(loc >= g_mem_size_total) { + if((loc & 0xfffe) == 0) { + //printf("get_io assuming mem sizing, not halting\n"); + return 0; + } + } + + /* Skip reads to f80000 and f00000, just return 0 */ + if((loc & 0xf70000) == 0xf00000) { + return 0; + } + + if((loc & 0xff0000) == 0xef0000) { + /* DOC RAM */ + return (doc_ram[loc & 0xffff]); + } + if((loc & 0xffff00) == 0xbcff00) { + /* TWGS mapped some ROM here, we'll force in all 0's */ + /* If user has selected >= 12MB of memory, then mem will be */ + /* returned and we won't ever get here */ + return 0; + } + + g_code_yellow++; + if(g_ignore_bad_acc && !g_user_halt_bad) { + /* print no message, just get out. User doesn't want */ + /* to be bothered by buggy programs */ + return 0; + } + + printf("get_memory_io for addr: %06x\n", loc); + printf("stat for addr: %06x = %p\n", loc, + GET_PAGE_INFO_RD((loc >> 8) & 0xffff)); + set_halt(g_halt_on_bad_read | g_user_halt_bad); + + return 0; +} + +void +set_memory_io(word32 loc, int val, dword64 *dcyc_ptr) +{ + word32 tmp; + + tmp = loc & 0xfef000; + if(tmp == 0xc000 || tmp == 0xe0c000) { + io_write(loc, val, dcyc_ptr); + return; + } + + /* Else it's an illegal addr */ + if(loc >= g_mem_size_total) { + if((loc & 0xfffe) == 0) { + //printf("set_io assuming mem sizing, not halting\n"); + return; + } + } + + /* ignore writes to ROM */ + if((loc & 0xfc0000) == 0xfc0000) { + return; + } + + if((loc & 0xff0000) == 0xef0000) { + /* DOC RAM */ + doc_ram[loc & 0xffff] = val; + return; + } + + if(g_ignore_bad_acc && !g_user_halt_bad) { + /* print no message, just get out. User doesn't want */ + /* to be bothered by buggy programs */ + return; + } + + if((loc & 0xffc000) == 0x00c000) { + printf("set_memory %06x = %02x, warning\n", loc, val); + return; + } + + halt_printf("set_memory %06x = %02x, stopping\n", loc, val); + + return; +} + +void +show_regs_act(Engine_reg *eptr) +{ + int tmp_acc, tmp_x, tmp_y, tmp_psw, kpc, direct_page, dbank, stack; + + kpc = eptr->kpc; + tmp_acc = eptr->acc; + direct_page = eptr->direct; + dbank = eptr->dbank; + stack = eptr->stack; + + tmp_x = eptr->xreg; + tmp_y = eptr->yreg; + + tmp_psw = eptr->psr; + + dbg_printf(" PC=%02x.%04x A=%04x X=%04x Y=%04x P=%03x", + kpc>>16, kpc & 0xffff ,tmp_acc,tmp_x,tmp_y,tmp_psw); + dbg_printf(" S=%04x D=%04x B=%02x,cyc:%016llx\n", stack, direct_page, + dbank, g_cur_dfcyc); +} + +void +show_regs() +{ + show_regs_act(&engine); +} + +void +my_exit(int ret) +{ + g_a2_fatal_err = 0x10 + ret; + printf("exiting\n"); +} + + +void +do_reset() +{ + g_c035_shadow_reg = 0; + + g_c068_statereg = 0x200 | 0x08 | 0x04; // Set wrdefram, rdrom, lcbank2 + g_c02d_int_crom = 0xff; + if(g_rom_version != 0) { // IIgs ROM01 or ROM03 + g_c068_statereg |= 0x01; // also set intcx + g_c02d_int_crom = 0; + } + g_c023_val = 0; + g_c041_val = 0; + + engine.psr = (engine.psr | 0x134) & ~(0x08); + engine.stack = 0x100 + (engine.stack & 0xff); + engine.dbank = 0; + engine.direct = 0; + engine.xreg &= 0xff; + engine.yreg &= 0xff; + g_wait_pending = 0; + g_stp_pending = 0; + + video_reset(); + adb_reset(); + iwm_reset(); + scc_reset(); + sound_reset(g_cur_dfcyc); + setup_pageinfo(); + change_display_mode(g_cur_dfcyc); + + g_irq_pending = 0; + g_code_yellow = 0; + g_code_red = 0; + + engine.kpc = get_memory16_c(0x00fffc); + + g_stepping = 0; +} + +#define CHECK(start, var, value, var1, var2) \ + var2 = PTR2WORD(&(var)); \ + var1 = PTR2WORD((start)); \ + if((var2 - var1) != value) { \ + printf("CHECK: " #var " is 0x%x, but " #value " is 0x%x\n", \ + (var2 - var1), value); \ + exit(5); \ + } + +byte * +memalloc_align(int size, int skip_amt, void **alloc_ptr) +{ + byte *bptr; + word32 addr; + word32 offset; + + skip_amt = MY_MAX(256, skip_amt); + bptr = calloc(size + skip_amt, 1); + if(alloc_ptr) { + /* Save allocation address */ + *alloc_ptr = bptr; + } + + addr = PTR2WORD(bptr) & 0xff; + + /* must align bptr to be 256-byte aligned */ + /* this code should work even if ptrs are > 32 bits */ + + offset = ((addr + skip_amt - 1) & (~0xff)) - addr; + + return (bptr + offset); +} + +void +memory_ptr_init() +{ + word32 mem_size; + + /* This routine may be called several times--each time the ROM file */ + /* changes this will be called */ + mem_size = MY_MIN(0xdf0000, g_mem_size_base + g_mem_size_exp); + if(g_rom_version == 0) { // Apple //e + mem_size = g_mem_size_base; + } + g_mem_size_total = mem_size; + if(g_memory_alloc_ptr) { + free(g_memory_alloc_ptr); + g_memory_alloc_ptr = 0; + } + g_memory_ptr = memalloc_align(mem_size, 256, &g_memory_alloc_ptr); + + printf("RAM size is 0 - %06x (%.2fMB)\n", mem_size, + (double)mem_size/(1024.0*1024.0)); +} + +extern int g_screen_redraw_skip_amt; +extern int g_use_dhr140; +extern int g_use_bw_hires; + +char g_display_env[512]; +int g_screen_depth = 8; + +int +parse_argv(int argc, char **argv, int slashes_to_find) +{ + byte *bptr; + char *str, *arg2_str; + int skip_amt, tmp1, len; + int i; + +#if 0 + // If launched from Finder, no stdout, so send it to /tmp/out1 + fflush(stdout); + setvbuf(stdout, 0, _IONBF, 0); + close(1); + (void)open("/tmp/out1", O_WRONLY | O_CREAT | O_TRUNC, 0x1b6); +#endif + + printf("Starting KEGS v%s\n", &g_kegs_version_str[0]); + + // parse arguments + // First, Check if KEGS_BIG_ENDIAN is set correctly + g_word32_tmp = 0x01020304; + bptr = (byte *)&(g_word32_tmp); +#ifdef KEGS_BIG_ENDIAN + bptr[0] = 6; + bptr[3] = 5; +#else + bptr[0] = 5; + bptr[3] = 6; +#endif + if(g_word32_tmp != 0x06020305) { + fatal_printf("KEGS_BIG_ENDIAN is not properly set\n"); + return 1; + } + + str = kegs_malloc_str(argv[0]); + len = (int)strlen(str); + for(i = len; i > 0; i--) { + if(str[i] == '/') { + if(--slashes_to_find <= 0) { + str[i] = 0; + g_argv0_path = str; + break; + } + } + } + printf("g_argv0_path: %s\n", g_argv0_path); + + i = 0; + while(++i < argc) { + printf("argv[%d] = %s\n", i, argv[i]); + if(!strcmp("-badrd", argv[i])) { + printf("Halting on bad reads\n"); + g_halt_on_bad_read = 2; + } else if(!strcmp("-noignbadacc", argv[i])) { + printf("Not ignoring bad memory accesses\n"); + g_ignore_bad_acc = 0; + } else if(!strcmp("-noignhalt", argv[i])) { + printf("Not ignoring code red halts\n"); + g_ignore_halts = 0; + } else if(!strcmp("-24", argv[i])) { + printf("Using 24-bit visual\n"); + g_force_depth = 24; + } else if(!strcmp("-16", argv[i])) { + printf("Using 16-bit visual\n"); + g_force_depth = 16; + } else if(!strcmp("-15", argv[i])) { + printf("Using 15-bit visual\n"); + g_force_depth = 15; + } else if(!strcmp("-mem", argv[i])) { + if((i+1) >= argc) { + printf("Missing argument\n"); + return 1; + } + g_mem_size_exp = strtol(argv[i+1], 0, 0) & 0x00ff0000; + printf("Using %d as memory size\n", g_mem_size_exp); + i++; + } else if(!strcmp("-skip", argv[i])) { + if((i+1) >= argc) { + printf("Missing to skip argument\n"); + return 1; + } + skip_amt = (int)strtol(argv[i+1], 0, 0); + printf("Using %d as skip_amt\n", skip_amt); + g_screen_redraw_skip_amt = skip_amt; + i++; + } else if(!strcmp("-audio", argv[i])) { + if((i+1) >= argc) { + printf("Missing argument to -audio\n"); + return 1; + } + tmp1 = (int)strtol(argv[i+1], 0, 0); + printf("Using %d as audio enable val\n", tmp1); + g_audio_enable = tmp1; + i++; + } else if(!strcmp("-arate", argv[i])) { + if((i+1) >= argc) { + printf("Missing argument to -arate\n"); + return 1; + } + tmp1 = (int)strtol(argv[i+1], 0, 0); + printf("Using %d as preferred audio rate\n", tmp1); + g_preferred_rate = tmp1; + i++; + } else if(!strcmp("-v", argv[i])) { + if((i+1) >= argc) { + printf("Missing argument to -v\n"); + return 1; + } + tmp1 = (int)strtol(argv[i+1], 0, 0); + printf("Setting Verbose = 0x%03x\n", tmp1); + Verbose = tmp1; + i++; + } else if(!strcmp("-display", argv[i])) { + if((i+1) >= argc) { + printf("Missing argument\n"); + exit(1); + } + printf("Using %s as display\n", argv[i+1]); + snprintf(g_display_env, sizeof(g_display_env), + "DISPLAY=%s", argv[i+1]); + putenv(&g_display_env[0]); + i++; + } else if(!strcmp("-noshm", argv[i])) { + printf("Not using X shared memory\n"); + g_use_shmem = 0; + } else if(!strcmp("-joystick", argv[i])) { + printf("Ignoring -joystick option\n"); + } else if(!strcmp("-dhr140", argv[i])) { + printf("Using simple dhires color map\n"); + g_use_dhr140 = 1; + } else if(!strcmp("-bw", argv[i])) { + printf("Forcing black-and-white hires modes\n"); + g_cur_a2_stat |= ALL_STAT_COLOR_C021; + g_use_bw_hires = 1; + } else if(!strncmp("-NS", argv[i], 3)) { + // Some Mac argument, just ignore it + if((i + 1) < argc) { + // If the next argument doesn't start with '-', + // then ignore it, too + if(argv[i+1][0] != '-') { + i++; + } + } + } else if(!strcmp("-logpc", argv[i])) { + printf("Force logpc enable\n"); + debug_logpc_on("on"); + } else if(!strncmp("-cfg", argv[i], 4)) { + if((i + 1) < argc) { + config_set_config_kegs_name(argv[i+1]); + } + i++; + } else if(argv[i][0] == '-') { + arg2_str = 0; + if((i + 1) < argc) { + arg2_str = argv[i+1]; + } + i += config_add_argv_override(&argv[i][1], arg2_str); + } else { + printf("Bad option: %s\n", argv[i]); + return 3; + } + } + + return 0; +} + +int +kegs_init(int mdepth, int screen_width, int screen_height, int no_scale_window) +{ + g_config_control_panel = 0; + + woz_crc_init(); + fixed_memory_ptrs_init(); + + if(sizeof(word32) != 4) { + printf("sizeof(word32) = %d, must be 4!\n", + (int)sizeof(word32)); + return 1; + } + prepare_a2_font(); // Prepare default built-in font + scc_init(); + iwm_init(); + init_reg(); + adb_init(); + initialize_events(); + debugger_init(); + setup_pageinfo(); + + config_init(); + load_roms_init_memory(); + + clear_halt(); + + video_init(mdepth, screen_width, screen_height, no_scale_window); + + sound_init(); + joystick_init(); + + if(g_rom_version >= 3) { + g_c036_val_speed |= 0x40; /* set power-on bit */ + } + + do_reset(); + + g_stepping = 0; + clear_halt(); + cfg_set_config_panel(g_config_control_panel); + + return 0; +} + +void +load_roms_init_memory() +{ + config_load_roms(); + memory_ptr_init(); + clk_setup_bram_version(); /* Must be after config_load_roms */ + if(g_rom_version >= 3) { + g_c036_val_speed |= 0x40; /* set power-on bit */ + } else { + g_c036_val_speed &= (~0x40); /* clear the bit */ + } + // Do not call do_reset(), caller is responsible for that + + /* if user booted ROM 01, switches to ROM 03, then switches back */ + /* to ROM 01, then the reset routines call to Tool $0102 looks */ + /* at uninitialized $e1/15fe and if it is negative it will JMP */ + /* through $e1/1688 which ROM 03 left pointing to fc/0199 */ + /* So set e1/15fe = 0 */ + set_memory16_c(0xe115fe, 0, 1); +} + +Event g_event_list[MAX_EVENTS]; +Event g_event_free; +Event g_event_start; + +void +initialize_events() +{ + int i; + + for(i = 1; i < MAX_EVENTS; i++) { + g_event_list[i-1].next = &g_event_list[i]; + } + g_event_free.next = &g_event_list[0]; + g_event_list[MAX_EVENTS-1].next = 0; + + g_event_start.next = 0; + g_event_start.dfcyc = 0; + + add_event_entry(CYCLES_IN_16MS_RAW << 16, EV_60HZ); +} + +void +check_for_one_event_type(int type, word32 mask) +{ + Event *ptr; + int count, depth; + + type = type & 0xff; + count = 0; + depth = 0; + ptr = g_event_start.next; + while(ptr != 0) { + depth++; + if((ptr->type & mask) == (word32)type) { + count++; + if(count != 1) { + halt_printf("in check_for_1, type %04x found " + "at depth: %d, count: %d, at %016llx\n", + ptr->type, depth, count, ptr->dfcyc); + } + } + ptr = ptr->next; + } +} + +void +add_event_entry(dword64 dfcyc, int type) +{ + Event *this_event; + Event *ptr, *prev_ptr; + int done; + + this_event = g_event_free.next; + if(this_event == 0) { + halt_printf("Out of queue entries!\n"); + show_all_events(); + return; + } + g_event_free.next = this_event->next; + + this_event->type = type; + + if((dfcyc > (g_cur_dfcyc + (50LL*1000*1000 << 16))) || + (dfcyc < g_cur_dfcyc)) { + halt_printf("add_event bad dfcyc:%016llx, type:%05x, " + "cur_dfcyc: %016llx!\n", dfcyc, type, g_cur_dfcyc); + dfcyc = g_cur_dfcyc + (1000LL << 16); + } + + ptr = g_event_start.next; + if(ptr && (dfcyc < ptr->dfcyc)) { + /* create event before next expected event */ + /* do this by calling engine_recalc_events */ + engine_recalc_events(); + } + + prev_ptr = &g_event_start; + ptr = g_event_start.next; + + done = 0; + while(!done) { + if(ptr == 0) { + this_event->next = ptr; + this_event->dfcyc = dfcyc; + prev_ptr->next = this_event; + break; + } else { + if(ptr->dfcyc < dfcyc) { // step across this guy + prev_ptr = ptr; + ptr = ptr->next; + } else { // go before this guy */ + this_event->dfcyc = dfcyc; + this_event->next = ptr; + prev_ptr->next = this_event; + break; + } + } + } + + check_for_one_event_type(type, 0xffff); +} + +extern int g_doc_saved_ctl; + +dword64 +remove_event_entry(int type, word32 mask) +{ + Event *ptr, *prev_ptr; + Event *next_ptr; + + ptr = g_event_start.next; + prev_ptr = &g_event_start; + + while(ptr != 0) { + if((ptr->type & mask) == (word32)type) { // got it + next_ptr = ptr->next; // remove it + prev_ptr->next = next_ptr; + + /* Add ptr to free list */ + ptr->next = g_event_free.next; + g_event_free.next = ptr; + + return ptr->dfcyc; + } + prev_ptr = ptr; + ptr = ptr->next; + } + + halt_printf("remove event_entry: %08x, but not found!\n", type); + if((type & 0xff) == EV_DOC_INT) { + printf("DOC, g_doc_saved_ctl = %02x\n", g_doc_saved_ctl); + } + show_all_events(); + + return 0; +} + +void +add_event_stop(dword64 dfcyc) +{ + add_event_entry(dfcyc, EV_STOP); +} + +void +add_event_doc(dword64 dfcyc, int osc) +{ + if(dfcyc < g_cur_dfcyc) { + dfcyc = g_cur_dfcyc; + //halt_printf("add_event_doc: dfcyc:%016llx, cur_dfcyc:" + // "%016llx\n", dfcyc, g_cur_dfcyc); + } + + add_event_entry(dfcyc, EV_DOC_INT + (osc << 8)); +} + +void +add_event_scc(dword64 dfcyc, int type) +{ + if(dfcyc < g_cur_dfcyc) { + dfcyc = g_cur_dfcyc; + } + + add_event_entry(dfcyc, EV_SCC + (type << 8)); +} + +void +add_event_vbl() +{ + dword64 dfcyc; + + dfcyc = g_last_vbl_dfcyc + ((192*65LL) << 16); + add_event_entry(dfcyc, EV_VBL_INT); +} + +void +add_event_vid_upd(int line) +{ + dword64 dfcyc; + + dfcyc = g_last_vbl_dfcyc + (((line + 1) * 65LL) << 16); + add_event_entry(dfcyc, EV_VID_UPD + (line << 8)); + // Redraw line when video counters first read video data +} + +void +add_event_mockingboard(dword64 dfcyc) +{ + if(dfcyc < g_cur_dfcyc) { + dfcyc = g_cur_dfcyc; + } + add_event_entry(dfcyc, EV_MOCKINGBOARD); +} + +dword64 g_dfcyc_scan_int = 0; + +void +add_event_scan_int(dword64 dfcyc, int line) +{ + dword64 dfcyc_scan_int; + + dfcyc_scan_int = g_dfcyc_scan_int; + if(dfcyc_scan_int) { // Event is pending + if(dfcyc >= g_dfcyc_scan_int) { + // We are after (or the same) as current, do nothing + return; + } + remove_event_entry(EV_SCAN_INT, 0xff); + } + if(dfcyc < g_cur_dfcyc) { + // scan_int for line 0 is found during EV_60HZ, and some + // cycles may have passed before the EV_60HZ was handled. + // We need it to happen now, so just adjust dfcyc + dfcyc = g_cur_dfcyc; + } + add_event_entry(dfcyc, EV_SCAN_INT + (line << 8)); + g_dfcyc_scan_int = dfcyc; + + check_for_one_event_type(EV_SCAN_INT, 0xff); +} + + +dword64 +remove_event_doc(int osc) +{ + return remove_event_entry(EV_DOC_INT + (osc << 8), 0xffff); +} + +dword64 +remove_event_scc(int type) +{ + return remove_event_entry(EV_SCC + (type << 8), 0xffff); +} + +void +remove_event_mockingboard() +{ + (void)remove_event_entry(EV_MOCKINGBOARD, 0xff); +} + +void +show_all_events() +{ + Event *ptr; + dword64 dfcyc; + int count; + + count = 0; + ptr = g_event_start.next; + while(ptr != 0) { + dfcyc = ptr->dfcyc; + printf("Event: %02x: type: %05x, dfcyc: %016llx (%08llx)\n", + count, ptr->type, dfcyc, dfcyc - g_cur_dfcyc); + ptr = ptr->next; + count++; + } + +} + +word32 g_vbl_count = 0; +int g_vbl_index_count = 0; +double dtime_array[60]; +double g_dadjcycs_array[60]; +double g_dtime_diff3_array[60]; +double g_dtime_this_vbl_array[60]; +double g_dtime_exp_array[60]; +double g_dtime_pmhz_array[60]; +double g_dtime_eff_pmhz_array[60]; +double g_dtime_in_run_16ms = 0; +double g_dtime_outside_run_16ms = 0; +double g_dtime_end_16ms = 0; +int g_limit_speed = 3; +int g_zip_speed_mhz = 16; // 16MHz default +double sim_time[60]; +double g_sim_sum = 0.0; + +double g_cur_sim_dtime = 0.0; +double g_projected_pmhz = 1.0; +double g_zip_pmhz = 8.0; +double g_sim_mhz = 100.0; +int g_line_ref_amt = 1; +int g_video_line_update_interval = 0; +dword64 g_video_pixel_dcount = 0; + +Fplus g_recip_projected_pmhz_slow; +Fplus g_recip_projected_pmhz_fast; +Fplus g_recip_projected_pmhz_zip; +Fplus g_recip_projected_pmhz_unl; + +void +show_pmhz() +{ + printf("Pmhz: %f, c036:%02x, limit: %d\n", + g_projected_pmhz, g_c036_val_speed, g_limit_speed); + +} + +void +setup_zip_speeds() +{ + dword64 drecip; + double fmhz; + int mult; + + mult = 16 - ((g_zipgs_reg_c05a >> 4) & 0xf); + // 16 = full speed, 1 = 1/16th speed + fmhz = (g_zip_speed_mhz * mult) / 16.0; +#if 0 + if(mult == 16) { + /* increase full speed by 19% to make zipgs freq measuring */ + /* programs work correctly */ + fmhz = fmhz * 1.19; + } +#endif + drecip = (dword64)(65536 / fmhz); + g_zip_pmhz = fmhz; + g_recip_projected_pmhz_zip.dplus_1 = drecip; + if(fmhz <= 2.0) { + g_recip_projected_pmhz_zip.dplus_x_minus_1 = + (dword64)(1.01 * 65536); + } else { + g_recip_projected_pmhz_zip.dplus_x_minus_1 = + (dword64)(1.01 * 65536 - drecip); + } +} + +word32 g_cycs_end_16ms = 0; + +int +run_16ms() +{ + double dtime_start, dtime_end, dtime_end2, dtime_outside; + int ret; + + dtime_start = get_dtime(); + ret = 0; + fflush(stdout); + g_dtime_sleep = 1.0/61.0; // For control_panel/debugger + if(g_config_control_panel) { + ret = cfg_control_panel_update(); + if(!g_config_control_panel) { + return 0; // Was just switched off, get out + } + } else { + if(g_halt_sim) { + ret = debugger_run_16ms(); + } else { + ret = run_a2_one_vbl(); + } + } + video_update(); + g_vbl_count++; + dtime_end = get_dtime(); + g_dtime_in_run_16ms += (dtime_end - dtime_start); + + // If we are ahead, then do the sleep now + micro_sleep(g_dtime_sleep); + dtime_end2 = get_dtime(); + //printf("Did sleep for %f, dtime passed:%f\n", g_dtime_sleep, + // dtime_end2 - dtime_end); + g_dtime_sleep = 0.0; + + g_dtime_in_sleep += (dtime_end2 - dtime_end); + + dtime_outside = 0.0; + if(g_vbl_count > 1) { + dtime_outside += (dtime_start - g_dtime_end_16ms); + } + g_dtime_outside_run_16ms += dtime_outside; + g_dtime_end_16ms = dtime_end2; +#if 0 + if((g_vbl_count & 0xf) == 0) { + printf("run_16ms end at %.3lf, dtime_16ms:%1.5lf out:%1.5lf\n", + dtime_end, dtime_end - dtime_start, dtime_outside); + } +#endif + return ret | g_a2_fatal_err; +} + +int +run_a2_one_vbl() +{ + Fplus *fplus_ptr; + Event *this_event, *db1; + double now_dtime, prev_dtime, fspeed_mult; + dword64 dfcyc, dfcyc_stop, prerun_dfcyc; + word32 ret, zip_speed_0tof, zip_speed_0tof_new; + int zip_en, zip_follow_cps, type, motor_on, iwm_1, iwm_25, fast; + int limit_speed, apple35_sel, zip_speed, faster_than_28, unl_speed; + + fflush(stdout); + + g_cur_sim_dtime = 0.0; + + g_recip_projected_pmhz_slow.dplus_1 = 0x10000; + g_recip_projected_pmhz_slow.dplus_x_minus_1 = (dword64)(0.9 * 0x10000); + + g_recip_projected_pmhz_fast.dplus_1 = (dword64)(0x10000 / 2.8); + g_recip_projected_pmhz_fast.dplus_x_minus_1 = (dword64) + ((1.98 - (1.0/2.8)) * 0x10000); + + zip_speed_0tof = g_zipgs_reg_c05a & 0xf0; + setup_zip_speeds(); + + if(engine.fplus_ptr == 0) { + g_recip_projected_pmhz_unl = g_recip_projected_pmhz_slow; + } + + while(1) { + fflush(stdout); + + if(g_irq_pending && !(engine.psr & 0x4)) { + irq_printf("taking an irq!\n"); + take_irq(); + /* Interrupt! */ + } + + motor_on = g_iwm_motor_on; + limit_speed = g_limit_speed; + apple35_sel = g_iwm.state & IWM_STATE_C031_APPLE35SEL; + zip_en = ((g_zipgs_reg_c05b & 0x10) == 0); + zip_follow_cps = ((g_zipgs_reg_c059 & 0x8) != 0); + zip_speed_0tof_new = g_zipgs_reg_c05a & 0xf0; + fast = (g_c036_val_speed & 0x80) || (zip_en && !zip_follow_cps); + + if(zip_speed_0tof_new != zip_speed_0tof) { + zip_speed_0tof = zip_speed_0tof_new; + setup_zip_speeds(); + } + + iwm_1 = motor_on && !apple35_sel && + (g_c036_val_speed & 0x4) && + (g_slow_525_emul_wr || !g_fast_disk_emul); + iwm_25 = (motor_on && apple35_sel) && !g_fast_disk_emul; + faster_than_28 = fast && (!iwm_1 && !iwm_25) && zip_en && + ((limit_speed == 0) || (limit_speed == 3)); + zip_speed = faster_than_28 && + ((zip_speed_0tof != 0) || (limit_speed == 3) || + (g_zipgs_unlock >= 4) ); + unl_speed = faster_than_28 && !zip_speed; + if(unl_speed) { + /* use unlimited speed */ + fspeed_mult = g_projected_pmhz; + fplus_ptr = &g_recip_projected_pmhz_unl; + } else if(zip_speed) { + fspeed_mult = g_zip_pmhz; + fplus_ptr = &g_recip_projected_pmhz_zip; + } else if(fast && !iwm_1 && (iwm_25 || (limit_speed != 1))) { + fspeed_mult = 2.5; + fplus_ptr = &g_recip_projected_pmhz_fast; + } else { + /* else run slow */ + fspeed_mult = 1.0; + fplus_ptr = &g_recip_projected_pmhz_slow; + } + + engine.fplus_ptr = fplus_ptr; + + prerun_dfcyc = g_cur_dfcyc; + engine.dfcyc = prerun_dfcyc; + dfcyc_stop = g_event_start.next->dfcyc + 1; + if(g_stepping) { + dfcyc_stop = prerun_dfcyc + 1; + } + g_dcycles_end = dfcyc_stop; + +#if 0 + printf("Enter engine, fcycs: %f, stop: %f\n", + prerun_fcycles, fcycles_stop); + printf("g_cur_dfcyc:%016llx, last_vbl_dfcyc:%016llx\n", + g_cur_dfcyc, g_last_vbl_dfcyc); +#endif + + g_num_enter_engine++; + prev_dtime = get_dtime(); + + ret = enter_engine(&engine); + + now_dtime = get_dtime(); + + g_cur_sim_dtime += (now_dtime - prev_dtime); + + dfcyc = engine.dfcyc; + g_cur_dfcyc = dfcyc; + + g_dadjcycs += (engine.dfcyc - prerun_dfcyc) * (1/65536.0) * + fspeed_mult; + +#if 0 + printf("...back, engine.dfcyc: %016llx, dfcyc: %016llx\n", + (double)engine.dfcyc, dfcyc); +#endif + + if(ret != 0) { + g_engine_action++; + handle_action(ret); + } + + this_event = g_event_start.next; + while(dfcyc >= this_event->dfcyc) { + /* Pop this guy off of the queue */ + g_event_start.next = this_event->next; + + type = this_event->type; + this_event->next = g_event_free.next; + g_event_free.next = this_event; + dbg_log_info(dfcyc, type, 0, 0x101); + switch(type & 0xff) { + case EV_60HZ: + update_60hz(dfcyc, now_dtime); + return 0; + break; + case EV_STOP: + printf("type: EV_STOP\n"); + printf("next: %p, dfcyc: %016llx\n", + g_event_start.next, dfcyc); + db1 = g_event_start.next; + halt_printf("next.dfcyc:%016llx\n", db1->dfcyc); + break; + case EV_SCAN_INT: + g_engine_scan_int++; + irq_printf("type: scan int\n"); + do_scan_int(dfcyc, type >> 8); + break; + case EV_DOC_INT: + g_engine_doc_int++; + doc_handle_event(type >> 8, dfcyc); + break; + case EV_VBL_INT: + do_vbl_int(); + break; + case EV_SCC: + scc_do_event(dfcyc, type >> 8); + break; + case EV_VID_UPD: + video_update_event_line(type >> 8); + break; + case EV_MOCKINGBOARD: + mockingboard_event(dfcyc); + break; + default: + printf("Unknown event: %d!\n", type); + exit(3); + } + + this_event = g_event_start.next; + + } + + if(g_event_start.next == 0) { + halt_printf("ERROR...run_prog, event_start.n=0!\n"); + } + + if(g_halt_sim) { + break; + } + if(g_stepping) { + break; + } + } + + printf("leaving run_prog, g_halt_sim:%d\n", g_halt_sim); + + return 0; +} + +void +add_irq(word32 irq_mask) +{ + if(g_irq_pending & irq_mask) { + /* Already requested, just get out */ + return; + } + g_irq_pending |= irq_mask; + engine_recalc_events(); +} + +void +remove_irq(word32 irq_mask) +{ + g_irq_pending = g_irq_pending & (~irq_mask); +} + +void +take_irq() +{ + word32 new_kpc, va; + + irq_printf("Taking irq, at: %02x/%04x, psw: %02x, dfcyc:%016llx\n", + engine.kpc >> 16, engine.kpc & 0xffff, engine.psr, + g_cur_dfcyc); + + g_num_irq++; + if(g_wait_pending) { + /* step over WAI instruction */ + engine.kpc = (engine.kpc & 0xff0000) | + ((engine.kpc + 1) & 0xffff); + g_wait_pending = 0; + } + + if(engine.psr & 0x100) { + /* Emulation */ + set_memory_c(engine.stack, (engine.kpc >> 8) & 0xff, 1); + engine.stack = ((engine.stack -1) & 0xff) + 0x100; + + set_memory_c(engine.stack, engine.kpc & 0xff, 1); + engine.stack = ((engine.stack -1) & 0xff) + 0x100; + + set_memory_c(engine.stack, (engine.psr & 0xef), 1); + /* Clear B bit in psr on stack */ + engine.stack = ((engine.stack -1) & 0xff) + 0x100; + + va = 0xfffffe; + } else { + /* native */ + set_memory_c(engine.stack, (engine.kpc >> 16) & 0xff, 1); + engine.stack = ((engine.stack -1) & 0xffff); + + set_memory_c(engine.stack, (engine.kpc >> 8) & 0xff, 1); + engine.stack = ((engine.stack -1) & 0xffff); + + set_memory_c(engine.stack, engine.kpc & 0xff, 1); + engine.stack = ((engine.stack -1) & 0xffff); + + set_memory_c(engine.stack, engine.psr & 0xff, 1); + engine.stack = ((engine.stack -1) & 0xffff); + + va = 0xffffee; + } + + va = moremem_fix_vector_pull(va); + new_kpc = get_memory_c(va); + new_kpc = new_kpc + (get_memory_c(va + 1) << 8); + + engine.psr = ((engine.psr & 0x1f3) | 0x4); + + engine.kpc = new_kpc; + HALT_ON(HALT_ON_IRQ, "Halting on IRQ\n"); + +} + +double g_dtime_last_vbl = 0.0; +double g_dtime_expected = (1.0/VBL_RATE); // Approximately 1.0/60.0 + +int g_scan_int_events = 0; + +void +show_dtime_array() +{ + double dfirst_time; + double first_total_cycs; + int i; + int pos; + + dfirst_time = 0.0; + first_total_cycs = 0.0; + + + for(i = 0; i < 60; i++) { + pos = (g_vbl_index_count + i) % 60; + printf("%2d:%2d dt:%.5f adjc:%9.1f this_vbl:%.6f " + "exp:%.5f p:%2.2f ep:%2.2f\n", + i, pos, + dtime_array[pos] - dfirst_time, + g_dadjcycs_array[pos] - first_total_cycs, + g_dtime_this_vbl_array[pos], + g_dtime_exp_array[pos] - dfirst_time, + g_dtime_pmhz_array[pos], + g_dtime_eff_pmhz_array[pos]); + dfirst_time = dtime_array[pos]; + first_total_cycs = g_dadjcycs_array[pos]; + } +} + + +void +update_60hz(dword64 dfcyc, double dtime_now) +{ + register word32 end_time; + char status_buf[1024]; + char sim_mhz_buf[128]; + char total_mhz_buf[128]; + char sp_buf[128]; + char *sim_mhz_ptr, *total_mhz_ptr, *code_str1, *code_str2, *sp_str; + dword64 planned_dcyc; + double eff_pmhz, predicted_pmhz, recip_predicted_pmhz; + double dtime_this_vbl_sim, dtime_diff_1sec, dratio, dtime_diff; + double dtime_till_expected, dtime_this_vbl, dadjcycs_this_vbl; + double dadj_cycles_1sec, dnatcycs_1sec; + int tmp, doit_3_persec, cur_vbl_index, prev_vbl_index; + + /* NOTE: this event is defined to occur before line 0 */ + /* It's actually happening at the start of the border for line (-1) */ + /* All other timings should be adjusted for this */ + + irq_printf("vbl_60hz: vbl: %d, dfcyc:%016llx, last_vbl_dfcyc:%016llx\n", + g_vbl_count, dfcyc, g_last_vbl_dfcyc); + + planned_dcyc = CYCLES_IN_16MS_RAW << 16; + + g_last_vbl_dfcyc = g_last_vbl_dfcyc + planned_dcyc; + + add_event_entry(g_last_vbl_dfcyc + planned_dcyc, EV_60HZ); + check_for_one_event_type(EV_60HZ, 0xff); + + cur_vbl_index = g_vbl_index_count; + + /* figure out dtime spent running SIM, not all the overhead */ + dtime_this_vbl_sim = g_cur_sim_dtime; + g_cur_sim_dtime = 0.0; + g_sim_sum = g_sim_sum - sim_time[cur_vbl_index] + dtime_this_vbl_sim; + sim_time[cur_vbl_index] = dtime_this_vbl_sim; + + dadj_cycles_1sec = g_dadjcycs - g_dadjcycs_array[cur_vbl_index]; + + /* dtime_diff_1sec is dtime total spent over the last 60 ticks */ + dtime_diff_1sec = dtime_now - dtime_array[cur_vbl_index]; + + dtime_array[cur_vbl_index] = dtime_now; + g_dadjcycs_array[cur_vbl_index] = g_dadjcycs; + + prev_vbl_index = cur_vbl_index; + cur_vbl_index = prev_vbl_index + 1; + if(cur_vbl_index >= 60) { + cur_vbl_index = 0; + } + g_vbl_index_count = cur_vbl_index; + + GET_ITIMER(end_time); + g_dnatcycs_1sec += (double)(end_time - g_natcycs_lastvbl); + g_natcycs_lastvbl = end_time; + + if(prev_vbl_index == 0) { + if(g_sim_sum < (1.0/250.0)) { + sim_mhz_ptr = "???"; + g_sim_mhz = 250.0; + } else { + g_sim_mhz = (dadj_cycles_1sec / g_sim_sum) / + (1000.0*1000.0); + if(g_sim_mhz > 8000.0) { + g_sim_mhz = 8000.0; + } + snprintf(sim_mhz_buf, sizeof(sim_mhz_buf), "%6.2f", + g_sim_mhz); + sim_mhz_ptr = sim_mhz_buf; + } + if(dtime_diff_1sec < (1.0/250.0)) { + total_mhz_ptr = "???"; + } else { + snprintf(total_mhz_buf, sizeof(total_mhz_buf), "%6.2f", + (dadj_cycles_1sec / dtime_diff_1sec) / + (1000000.0)); + total_mhz_ptr = total_mhz_buf; + } + + switch(g_limit_speed) { + case 1: sp_str = "1Mhz"; break; + case 2: sp_str = "2.8Mhz"; break; + case 3: sp_str = "8.0Mhz"; break; + default: sp_str = "Unlimited"; break; + } + if(g_limit_speed == 3) { // ZipGS + snprintf(sp_buf, sizeof(sp_buf), "%1.1fMHz", + g_zip_pmhz); + sp_str = sp_buf; + } + + snprintf(status_buf, sizeof(status_buf), "dfcyc:%7.1f sim " + "MHz:%s Eff MHz:%s, sec:%1.3f vol:%02x Limit:%s", + (double)(dfcyc >> 20)/65536.0, sim_mhz_ptr, + total_mhz_ptr, dtime_diff_1sec, g_doc_vol, sp_str); + video_update_status_line(0, status_buf); + + if(g_video_line_update_interval == 0) { + if(g_sim_mhz > 12.0) { + /* just set video line_ref_amt to 1 */ + g_line_ref_amt = 1; + } else if(g_line_ref_amt == 1 && g_sim_mhz < 4.0) { + g_line_ref_amt = 8; + } + } else { + g_line_ref_amt = g_video_line_update_interval; + } + + dnatcycs_1sec = g_dnatcycs_1sec; + if(g_dnatcycs_1sec < (1000.0*1000.0)) { + /* make it so large that all %'s become 0 */ + dnatcycs_1sec = 800.0*1000.0*1000.0*1000.0; + } + dnatcycs_1sec = dnatcycs_1sec / 100.0; /* eff mult by 100 */ + + g_video_pixel_dcount = 0; + + code_str1 = ""; + code_str2 = ""; + if(g_code_yellow) { + code_str1 = "Code: Yellow"; + code_str2 = "Emulated state suspect"; + } + if(g_code_red) { + code_str1 = "Code: RED"; + code_str2 = "Emulated state corrupt?"; + } + snprintf(status_buf, sizeof(status_buf), "sleep_dtime:%8.6f, " + "out_16ms:%8.6f, in_16ms:%8.6f, snd_pl:%d", + g_dtime_in_sleep, g_dtime_outside_run_16ms, + g_dtime_in_run_16ms, g_num_snd_plays); + video_update_status_line(1, status_buf); + + draw_iwm_status(2, status_buf); + + snprintf(status_buf, sizeof(status_buf), " KEGS v%-6s " + "Press F4 for Config Menu %s %s", + g_kegs_version_str, code_str1, code_str2); + video_update_status_line(3, status_buf); + + g_status_refresh_needed = 1; + + g_num_irq = 0; + g_num_brk = 0; + g_num_cop = 0; + g_num_enter_engine = 0; + g_io_amt = 0; + g_engine_action = 0; + g_engine_recalc_event = 0; + g_engine_scan_int = 0; + g_engine_doc_int = 0; + + g_cycs_in_40col = 0; + g_cycs_in_xredraw = 0; + g_cycs_in_refresh_line = 0; + g_dnatcycs_1sec = 0.0; + g_dtime_outside_run_16ms = 0.0; + g_dtime_in_run_16ms = 0.0; + g_refresh_bytes_xfer = 0; + + g_dtime_in_sleep = 0; + g_num_snd_plays = 0; + g_num_recalc_snd_parms = 0; + } + + dtime_this_vbl = dtime_now - g_dtime_last_vbl; + if(dtime_this_vbl < 0.001) { + dtime_this_vbl = 0.001; + } + + g_dtime_last_vbl = dtime_now; + + dadjcycs_this_vbl = g_dadjcycs - g_last_vbl_dadjcycs; + g_last_vbl_dadjcycs = g_dadjcycs; + + g_dtime_expected += (1.0/VBL_RATE); // Approx. 1/60 + + eff_pmhz = (dadjcycs_this_vbl / dtime_this_vbl) / DCYCS_1_MHZ; + + /* using eff_pmhz, predict how many cycles can be run by */ + /* g_dtime_expected */ + + dtime_till_expected = g_dtime_expected - dtime_now; + + dratio = VBL_RATE * dtime_till_expected; // Approx. 60*dtime_exp + + predicted_pmhz = eff_pmhz * dratio; + + if(! (predicted_pmhz < (1.4 * g_projected_pmhz))) { + predicted_pmhz = 1.4 * g_projected_pmhz; + } + + if(! (predicted_pmhz > (0.7 * g_projected_pmhz))) { + predicted_pmhz = 0.7 * g_projected_pmhz; + } + + if(!(predicted_pmhz >= 1.0)) { + irq_printf("predicted: %f, setting to 1.0\n", predicted_pmhz); + predicted_pmhz = 1.0; + } + + if(!(predicted_pmhz < 4500.0)) { + irq_printf("predicted: %f, set to 1900.0\n", predicted_pmhz); + predicted_pmhz = 4500.0; + } + + recip_predicted_pmhz = 1.0/predicted_pmhz; + g_projected_pmhz = predicted_pmhz; + + g_recip_projected_pmhz_unl.dplus_1 = (dword64) + (65536 * recip_predicted_pmhz); + g_recip_projected_pmhz_unl.dplus_x_minus_1 = + (dword64)(65536 * (1.01 - recip_predicted_pmhz)); + + if(dtime_till_expected < -0.125) { + /* If we were way off, get back on track */ + /* this happens because our sim took much longer than */ + /* expected, so we're going to skip some VBL */ + irq_printf("adj1: dtexp:%f, dt_new:%f\n", + g_dtime_expected, dtime_now); + + dtime_diff = -dtime_till_expected; + + irq_printf("dtime_till_exp:%f, dtime_diff:%f, dfcyc:%016llx\n", + dtime_till_expected, dtime_diff, dfcyc); + + g_dtime_expected += dtime_diff; + } + + g_dtime_sleep = 0.0; + if(dtime_till_expected > (1.0/VBL_RATE)) { + /* we're running fast, usleep */ + g_dtime_sleep = dtime_till_expected - (1.0/VBL_RATE); + } +#if 0 + printf("Sleep %f, till_exp:%f, dtime_now:%f, exp:%f\n", + g_dtime_sleep, dtime_till_expected, dtime_now, + g_dtime_expected); +#endif + + g_dtime_this_vbl_array[prev_vbl_index] = dtime_this_vbl; + g_dtime_exp_array[prev_vbl_index] = g_dtime_expected; + g_dtime_pmhz_array[prev_vbl_index] = predicted_pmhz; + g_dtime_eff_pmhz_array[prev_vbl_index] = eff_pmhz; + + + if(g_c041_val & C041_EN_VBL_INTS) { + add_event_vbl(); + } + + g_25sec_cntr++; + if(g_25sec_cntr >= 16) { + g_25sec_cntr = 0; + if(g_c041_val & C041_EN_25SEC_INTS) { + add_irq(IRQ_PENDING_C046_25SEC); + g_c046_val |= 0x10; + irq_printf("Setting c046 .25 sec int, g_irq_pend:%d\n", + g_irq_pending); + } + } + + g_1sec_cntr++; + if(g_1sec_cntr >= 60) { + g_1sec_cntr = 0; + tmp = g_c023_val; + tmp |= 0x40; /* set 1sec int */ + if(tmp & 0x04) { + tmp |= 0x80; + add_irq(IRQ_PENDING_C023_1SEC); + irq_printf("Setting c023 to %02x irq_pend: %d\n", + tmp, g_irq_pending); + } + g_c023_val = tmp; + } + + if(!g_scan_int_events) { + check_scan_line_int(0); + } + + doit_3_persec = 0; + if(g_config_iwm_vbl_count > 0) { + g_config_iwm_vbl_count--; + } else { + g_config_iwm_vbl_count = 20; + doit_3_persec = 1; + } + + iwm_vbl_update(); + config_vbl_update(doit_3_persec); + + sound_update(dfcyc); + clock_update(); + scc_update(dfcyc); + paddle_update_buttons(); +} + +void +do_vbl_int() +{ + if(g_c041_val & C041_EN_VBL_INTS) { + g_c046_val |= 0x08; + add_irq(IRQ_PENDING_C046_VBL); + irq_printf("Setting c046 vbl_int_status to 1, irq_pend: %d\n", + g_irq_pending); + } +} + +void +do_scan_int(dword64 dfcyc, int line) +{ + int c023_val; + + if(dfcyc) { + // Avoid unused param warning + } + + g_scan_int_events = 0; + g_dfcyc_scan_int = 0; + + c023_val = g_c023_val; + if(c023_val & 0x20) { + halt_printf("c023 scan_int and another on line %03x\n", line); + } + +#if 0 + dvbl = (dfcyc >> 16) / 17030; + dline = ((dfcyc >> 16) - (dvbl * 17030)) / 65; + printf("do_scan_int at time %lld (%lld,line %lld), line:%d, SCB:%02x, " + "a2_stat_ok:%d, c023:%02x\n", dfcyc >> 16, dvbl, dline, line, + (g_slow_memory_ptr[0x19d00 + line] & 0x40), + (g_cur_a2_stat & ALL_STAT_SUPER_HIRES) != 0, c023_val); + for(i = 0; i < 200; i++) { + if(g_slow_memory_ptr[0x19d00 + i] & 0x40) { + printf(" Line %d has SCB:%02x\n", i, + g_slow_memory_ptr[0x19d00 + i]); + } + } +#endif + + /* make sure scan int is still enabled for this line */ + if((g_slow_memory_ptr[0x19d00 + line] & 0x40) && + (g_cur_a2_stat & ALL_STAT_SUPER_HIRES)) { + /* valid interrupt, do it */ + c023_val |= 0xa0; /* vgc_int and scan_int */ + if(c023_val & 0x02) { + add_irq(IRQ_PENDING_C023_SCAN); + irq_printf("Setting c023 to %02x, irq_pend: %d\n", + c023_val, g_irq_pending); + } + g_c023_val = c023_val; + HALT_ON(HALT_ON_SCAN_INT, "In do_scan_int\n"); + } else { + /* scan int bit cleared on scan line control byte */ + /* look for next line, if any */ + check_scan_line_int(line+1); + } +} + +void +check_scan_line_int(int cur_video_line) +{ + dword64 ddelay; + int start, line; + int i; + + /* Called during VBL interrupt phase */ + + if(!(g_cur_a2_stat & ALL_STAT_SUPER_HIRES)) { + return; + } + + if(g_c023_val & 0x20) { + /* don't check for any more */ + return; + } + + start = cur_video_line; + if(start < 0) { + halt_printf("check_scan_line_int: cur_video_line: %d\n", + cur_video_line); + start = 0; + } + + for(line = start; line < 200; line++) { + i = line; + + if(i < 0 || i >= 200) { + halt_printf("check_new_scan_int:i:%d, line:%d, st:%d\n", + i, line, start); + i = 0; + } + if(g_slow_memory_ptr[0x19d00 + i] & 0x40) { + irq_printf("Adding scan_int for line %d\n", i); + ddelay = (65ULL * line) << 16; + add_event_scan_int(g_last_vbl_dfcyc + ddelay, line); + g_scan_int_events = 1; + break; + } + } +} + +void +check_for_new_scan_int(dword64 dfcyc) +{ + int cur_video_line; + + cur_video_line = get_lines_since_vbl(dfcyc) >> 8; + check_scan_line_int(cur_video_line); +} + +void +scb_changed(dword64 dfcyc, word32 addr, word32 new_val, word32 old_val) +{ + if(new_val & (~old_val) & 0x40) { + check_for_new_scan_int(dfcyc); + } + if(addr) { + } +} + +void +init_reg() +{ + memset(&engine, 0, sizeof(engine)); + + engine.acc = 0; + engine.xreg = 0; + engine.yreg = 0; + engine.stack = 0x1ff; + engine.direct = 0; + engine.psr = 0x134; + engine.fplus_ptr = 0; +} + +void +handle_action(word32 ret) +{ + int type, arg; + + type = ret & 0xff; + arg = ret >> 8; + switch(type) { + case RET_BREAK: + do_break(arg); + break; + case RET_COP: + do_cop(arg); + break; + case RET_IRQ: + irq_printf("Special fast IRQ response. irq_pending: %x\n", + g_irq_pending); + break; + case RET_WDM: + do_wdm(arg); + break; + case RET_STP: + do_stp(); + break; + case RET_TOOLTRACE: + dbg_log_info(g_cur_dfcyc, engine.kpc, engine.xreg, + (engine.stack << 16) | 0xe100); + break; + default: + halt_printf("Unknown special action: %08x!\n", ret); + } +} + + +void +do_break(word32 ret) +{ + printf("I think I got a break, second byte: %02x!\n", ret); + printf("kpc: %06x\n", engine.kpc); + + halt_printf("do_break, kpc: %06x\n", engine.kpc); +} + +void +do_cop(word32 ret) +{ + halt_printf("COP instr %02x!\n", ret); + fflush(stdout); +} + +void +do_wdm(word32 arg) +{ + if(arg == 0x00c7) { + // WDM, 0xc7, 0x00: WDM in Slot 7 + if(engine.psr & 0x40) { + // Overflow set: $C700 called + do_c700(arg); + } else if(engine.psr & 1) { // V=0, C=1: $C70D called + do_c70d(arg); + } else { // V=0, C=0, $C70A called + do_c70a(arg); + } + return; + } + if(arg == 0x00ea) { + // WDM, 0xea, 0x00: WDM emulator ID + do_wdm_emulator_id(); + return; + } + if((arg == 0xeaea) && ((engine.psr & 0x171) == 0x41) && + (engine.acc == 0x4d44)) { + // WDM $EA,$EA with V=1,C=1 and ACC=0x4d44 ("EM") + engine.psr = engine.psr & 0x1bf; // V=0 + // printf("WDM $EA,$EA, cleared V=0, psr:%04x\n", engine.psr); + return; + } + + switch(arg & 0xff) { + case 0x8d: /* Bouncin Ferno does WDM 8d */ + break; + case 0xea: // Detectiong feature, don't flag an error + break; + case 0xfc: // HOST.FST "head_call" for ATINIT for ProDOS 8 + case 0xfd: // HOST.FST "tail_call" for ATINIT for ProDOS 8 + case 0xff: // HOST.FST "call_host" for GS/OS driver + break; + default: + halt_printf("do_wdm: %04x!\n", arg); + } +} + +void +do_wai() +{ + halt_printf("do_wai!\n"); +} + +void +do_stp() +{ + if(!g_stp_pending) { + g_stp_pending = 1; + halt_printf("Hit STP instruction at: %06x, press RESET to " + "continue\n", engine.kpc); + } +} + +char g_emulator_name[64]; + +void +do_wdm_emulator_id() +{ + word32 addr, version, subvers; + int maxlen, len, c, got_dot; + int i; + + // WDM, $EA, $00: WDM emulator ID + // dbank.acc = address to write emulator description string + // X = size of buffer to hold string + // Y = 0 (not checked) + // Returns: X: actual length of emulator string (always <= + // value in X at call) + // ACC: emulator version as: $VVMN as BCD, so 1.32 is $0132 + // Y: Emulation feature flags. bit 0: $c06c-$c06f timer available + // Works in emulation mode + + printf("WDM EA at %06x. acc:%04x, dbank:%02x xreg:%04x\n", engine.kpc, + engine.acc, engine.dbank, engine.xreg); + maxlen = engine.xreg; + cfg_strncpy(&g_emulator_name[0], "KEGS v", 64); + cfg_strlcat(&g_emulator_name[0], &g_kegs_version_str[0], 64); + len = (int)strlen(&g_emulator_name[0]); + addr = engine.acc; + engine.xreg = 0; + for(i = 0; i < len; i++) { + if(i >= maxlen) { + break; + } + addr = (engine.dbank << 8) | (addr & 0xffff); + set_memory_c(addr, 0x80 | g_emulator_name[i], 1); + addr++; + engine.xreg = i + 1; + } + + version = 0; + subvers = 0; + len = (int)strlen(&g_kegs_version_str[0]); + got_dot = 0; + for(i = 0; i < len; i++) { + c = g_kegs_version_str[i]; + if(c == '.') { + got_dot++; + } + if(got_dot >= 3) { + break; + } + if((c >= '0') && (c <= '9')) { + c = c - '0'; + if(got_dot) { + subvers = (subvers << 4) | c; + got_dot++; + } else { + version = (version << 4) | c; + } + } + } + + engine.acc = ((version & 0xff) << 8) | (subvers & 0xff); + engine.yreg = 0x01; // $C06C timer available +} + +void +size_fail(int val, word32 v1, word32 v2) +{ + halt_printf("Size failure, val: %08x, %08x %08x\n", val, v1, v2); +} + +int +fatal_printf(const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + + if(g_fatal_log < 0) { + g_fatal_log = 0; + } + ret = kegs_vprintf(fmt, ap); + va_end(ap); + + return ret; +} + +int +kegs_vprintf(const char *fmt, va_list ap) +{ + char *bufptr, *buf2ptr; + int len, ret; + + bufptr = malloc(4096); + ret = vsnprintf(bufptr, 4090, fmt, ap); + + len = (int)strlen(bufptr); + if(g_fatal_log >= 0 && g_fatal_log < MAX_FATAL_LOGS) { + buf2ptr = malloc(len+1); + memcpy(buf2ptr, bufptr, len+1); + g_fatal_log_strs[g_fatal_log++] = buf2ptr; + } + (void)must_write(1, (byte *)bufptr, len); + if(g_debug_file_fd >= 0) { + (void)must_write(g_debug_file_fd, (byte *)bufptr, len); + } + free(bufptr); + + return ret; +} + +dword64 +must_write(int fd, byte *bufptr, dword64 dsize) +{ + dword64 dlen; + long long ret; + word32 this_len; + + dlen = dsize; + while(dlen != 0) { + // Support Windows64, which can only rd/wr 2GB max per call + this_len = (1UL << 30); + if(dlen < this_len) { + this_len = (word32)dlen; + } + ret = write(fd, bufptr, this_len); + if(ret >= 0) { + dlen -= ret; + bufptr += ret; + } else if((errno != EAGAIN) && (errno != EINTR)) { + return 0; // just get out + } + } + return dsize; +} + +void +clear_fatal_logs() +{ + int i; + + for(i = 0; i < g_fatal_log; i++) { + free(g_fatal_log_strs[i]); + g_fatal_log_strs[i] = 0; + } + g_fatal_log = -1; +} + +char * +kegs_malloc_str(const char *in_str) +{ + char *str; + int len; + + len = (int)strlen(in_str) + 1; + str = malloc(len); + memcpy(str, in_str, len); + + return str; +} + +dword64 +kegs_lseek(int fd, dword64 offs, int whence) +{ +#ifdef _WIN32 + return _lseeki64(fd, offs, whence); +#else + return lseek(fd, offs, whence); +#endif +} + diff --git a/gsplus/src/size_c.h b/gsplus/src/size_c.h new file mode 100644 index 0000000..5bc671e --- /dev/null +++ b/gsplus/src/size_c.h @@ -0,0 +1,269 @@ +// "@(#)$KmKId: size_c.h,v 1.2 2023-11-12 15:32:17+00 kentd Exp $" +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2020 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + + + 0x1, /* 00 */ /* brk */ + 0x1, /* 01 */ /* ORA (Dloc,X) */ + 0x1, /* 02 */ /* COP */ + 0x1, /* 03 */ /* ORA Disp8,S */ + 0x1, /* 04 */ /* TSB Dloc */ + 0x1, /* 05 */ /* ORA Dloc */ + 0x1, /* 06 */ /* ASL Dloc */ + 0x1, /* 07 */ /* ORA [Dloc] */ + 0x0, /* 08 */ /* PHP */ + 0x4, /* 09 */ /* ORA #imm */ + 0x0, /* 0a */ /* ASL a */ + 0x0, /* 0b */ /* PHD */ + 0x2, /* 0c */ /* TSB abs */ + 0x2, /* 0d */ /* ORA abs */ + 0x2, /* 0e */ /* ASL abs */ + 0x3, /* 0f */ /* ORA long */ + 0x1, /* 10 */ /* BPL disp8 */ + 0x1, /* 11 */ /* ORA (),y */ + 0x1, /* 12 */ /* ORA () */ + 0x1, /* 13 */ /* ORA (disp8,s),y */ + 0x1, /* 14 */ /* TRB Dloc */ + 0x1, /* 15 */ /* ORA Dloc,x */ + 0x1, /* 16 */ /* ASL Dloc,x */ + 0x1, /* 17 */ /* ORA [],y */ + 0x0, /* 18 */ /* clc */ + 0x2, /* 19 */ /* ORA abs,y */ + 0x0, /* 1a */ /* INC a */ + 0x0, /* 1b */ /* TCS */ + 0x2, /* 1c */ /* TRB Abs */ + 0x2, /* 1d */ /* ORA Abs,X */ + 0x2, /* 1e */ /* ASL abs,x */ + 0x3, /* 1f */ /* ORA Long,x */ + 0x2, /* 20 */ /* JSR abs */ + 0x1, /* 21 */ /* AND (Dloc,X) */ + 0x3, /* 22 */ /* JSL Abslong */ + 0x1, /* 23 */ /* AND Disp8,S */ + 0x1, /* 24 */ /* BIT Dloc */ + 0x1, /* 25 */ /* AND Dloc */ + 0x1, /* 26 */ /* ROL Dloc */ + 0x1, /* 27 */ /* AND [Dloc] */ + 0x0, /* 28 */ /* PLP */ + 0x4, /* 29 */ /* AND #imm */ + 0x0, /* 2a */ /* ROL a */ + 0x0, /* 2b */ /* PLD */ + 0x2, /* 2c */ /* BIT abs */ + 0x2, /* 2d */ /* AND abs */ + 0x2, /* 2e */ /* ROL abs */ + 0x3, /* 2f */ /* AND long */ + 0x1, /* 30 */ /* BMI disp8 */ + 0x1, /* 31 */ /* AND (),y */ + 0x1, /* 32 */ /* AND () */ + 0x1, /* 33 */ /* AND (disp8,s),y */ + 0x1, /* 34 */ /* BIT Dloc,X */ + 0x1, /* 35 */ /* AND Dloc,x */ + 0x1, /* 36 */ /* ROL Dloc,x */ + 0x1, /* 37 */ /* AND [],y */ + 0x0, /* 38 */ /* SEC */ + 0x2, /* 39 */ /* AND abs,y */ + 0x0, /* 3a */ /* DEC a */ + 0x0, /* 3b */ /* TSC */ + 0x2, /* 3c */ /* BIT Abs,X */ + 0x2, /* 3d */ /* AND Abs,X */ + 0x2, /* 3e */ /* ROL abs,x */ + 0x3, /* 3f */ /* AND Long,x */ + 0x0, /* 40 */ /* RTI */ + 0x1, /* 41 */ /* EOR (Dloc,X) */ + 0x2, /* 42 */ /* WDM HACK: uses 2 args */ + 0x1, /* 43 */ /* EOR Disp8,S */ + 0x2, /* 44 */ /* MVP I,J */ + 0x1, /* 45 */ /* EOR Dloc */ + 0x1, /* 46 */ /* LSR Dloc */ + 0x1, /* 47 */ /* EOR [Dloc] */ + 0x0, /* 48 */ /* PHA */ + 0x4, /* 49 */ /* EOR #imm */ + 0x0, /* 4a */ /* LSR a */ + 0x0, /* 4b */ /* PHK */ + 0x2, /* 4c */ /* JMP abs */ + 0x2, /* 4d */ /* EOR abs */ + 0x2, /* 4e */ /* LSR abs */ + 0x3, /* 4f */ /* EOR long */ + 0x1, /* 50 */ /* BVC disp8 */ + 0x1, /* 51 */ /* EOR (),y */ + 0x1, /* 52 */ /* EOR () */ + 0x1, /* 53 */ /* EOR (disp8,s),y */ + 0x2, /* 54 */ /* MVN I,J */ + 0x1, /* 55 */ /* EOR Dloc,x */ + 0x1, /* 56 */ /* LSR Dloc,x */ + 0x1, /* 57 */ /* EOR [],y */ + 0x0, /* 58 */ /* CLI */ + 0x2, /* 59 */ /* EOR abs,y */ + 0x0, /* 5a */ /* PHY */ + 0x0, /* 5b */ /* TCD */ + 0x3, /* 5c */ /* JMP Long */ + 0x2, /* 5d */ /* EOR Abs,X */ + 0x2, /* 5e */ /* LSR abs,x */ + 0x3, /* 5f */ /* EOR Long,x */ + 0x0, /* 60 */ /* RTS */ + 0x1, /* 61 */ /* ADC (Dloc,X) */ + 0x2, /* 62 */ /* PER DISP16 */ + 0x1, /* 63 */ /* ADC Disp8,S */ + 0x1, /* 64 */ /* STZ Dloc */ + 0x1, /* 65 */ /* ADC Dloc */ + 0x1, /* 66 */ /* ROR Dloc */ + 0x1, /* 67 */ /* ADC [Dloc] */ + 0x0, /* 68 */ /* PLA */ + 0x4, /* 69 */ /* ADC #imm */ + 0x0, /* 6a */ /* ROR a */ + 0x0, /* 6b */ /* RTL */ + 0x2, /* 6c */ /* JMP (abs) */ + 0x2, /* 6d */ /* ADC abs */ + 0x2, /* 6e */ /* ROR abs */ + 0x3, /* 6f */ /* ADC long */ + 0x1, /* 70 */ /* BVS disp8 */ + 0x1, /* 71 */ /* ADC (),y */ + 0x1, /* 72 */ /* ADC () */ + 0x1, /* 73 */ /* ADC (disp8,s),y */ + 0x1, /* 74 */ /* STZ Dloc,X */ + 0x1, /* 75 */ /* ADC Dloc,x */ + 0x1, /* 76 */ /* ROR Dloc,x */ + 0x1, /* 77 */ /* ADC [],y */ + 0x0, /* 78 */ /* SEI */ + 0x2, /* 79 */ /* ADC abs,y */ + 0x0, /* 7a */ /* PLY */ + 0x0, /* 7b */ /* TDC */ + 0x2, /* 7c */ /* JMP (abs,x) */ + 0x2, /* 7d */ /* ADC Abs,X */ + 0x2, /* 7e */ /* ROR abs,x */ + 0x3, /* 7f */ /* ADC Long,x */ + 0x1, /* 80 */ /* BRA Disp8 */ + 0x1, /* 81 */ /* STA (Dloc,X) */ + 0x2, /* 82 */ /* BRL DISP16 */ + 0x1, /* 83 */ /* STA Disp8,S */ + 0x1, /* 84 */ /* STY Dloc */ + 0x1, /* 85 */ /* STA Dloc */ + 0x1, /* 86 */ /* STX Dloc */ + 0x1, /* 87 */ /* STA [Dloc] */ + 0x0, /* 88 */ /* DEY */ + 0x4, /* 89 */ /* BIT #imm */ + 0x0, /* 8a */ /* TXA */ + 0x0, /* 8b */ /* PHB */ + 0x2, /* 8c */ /* STY abs */ + 0x2, /* 8d */ /* STA abs */ + 0x2, /* 8e */ /* STX abs */ + 0x3, /* 8f */ /* STA long */ + 0x1, /* 90 */ /* BCC disp8 */ + 0x1, /* 91 */ /* STA (),y */ + 0x1, /* 92 */ /* STA () */ + 0x1, /* 93 */ /* STA (disp8,s),y */ + 0x1, /* 94 */ /* STY Dloc,X */ + 0x1, /* 95 */ /* STA Dloc,x */ + 0x1, /* 96 */ /* STX Dloc,y */ + 0x1, /* 97 */ /* STA [],y */ + 0x0, /* 98 */ /* TYA */ + 0x2, /* 99 */ /* STA abs,y */ + 0x0, /* 9a */ /* TXS */ + 0x0, /* 9b */ /* TXY */ + 0x2, /* 9c */ /* STX abs */ + 0x2, /* 9d */ /* STA Abs,X */ + 0x2, /* 9e */ /* STZ abs,x */ + 0x3, /* 9f */ /* STA Long,x */ + 0x5, /* a0 */ /* LDY #imm */ + 0x1, /* a1 */ /* LDA (Dloc,X) */ + 0x5, /* a2 */ /* LDX #imm */ + 0x1, /* a3 */ /* LDA Disp8,S */ + 0x1, /* a4 */ /* LDY Dloc */ + 0x1, /* a5 */ /* LDA Dloc */ + 0x1, /* a6 */ /* LDX Dloc */ + 0x1, /* a7 */ /* LDA [Dloc] */ + 0x0, /* a8 */ /* TAY */ + 0x4, /* a9 */ /* LDA #imm */ + 0x0, /* aa */ /* TAX */ + 0x0, /* ab */ /* PLB */ + 0x2, /* ac */ /* LDY abs */ + 0x2, /* ad */ /* LDA abs */ + 0x2, /* ae */ /* LDX abs */ + 0x3, /* af */ /* LDA long */ + 0x1, /* b0 */ /* BCS disp8 */ + 0x1, /* b1 */ /* LDA (),y */ + 0x1, /* b2 */ /* LDA () */ + 0x1, /* b3 */ /* LDA (disp8,s),y */ + 0x1, /* b4 */ /* LDY Dloc,X */ + 0x1, /* b5 */ /* LDA Dloc,x */ + 0x1, /* b6 */ /* LDX Dloc,y */ + 0x1, /* b7 */ /* LDA [],y */ + 0x0, /* b8 */ /* CLV */ + 0x2, /* b9 */ /* LDA abs,y */ + 0x0, /* ba */ /* TSX */ + 0x0, /* bb */ /* TYX */ + 0x2, /* bc */ /* LDY abs,x */ + 0x2, /* bd */ /* LDA Abs,X */ + 0x2, /* be */ /* LDX abs,y */ + 0x3, /* bf */ /* LDA Long,x */ + 0x5, /* c0 */ /* CPY #Imm */ + 0x1, /* c1 */ /* CMP (Dloc,X) */ + 0x1, /* c2 */ /* REP #8bit */ + 0x1, /* c3 */ /* CMP Disp8,S */ + 0x1, /* c4 */ /* CPY Dloc */ + 0x1, /* c5 */ /* CMP Dloc */ + 0x1, /* c6 */ /* DEC Dloc */ + 0x1, /* c7 */ /* CMP [Dloc] */ + 0x0, /* c8 */ /* INY */ + 0x4, /* c9 */ /* CMP #imm */ + 0x0, /* ca */ /* DEX */ + 0x0, /* cb */ /* WAI */ + 0x2, /* cc */ /* CPY abs */ + 0x2, /* cd */ /* CMP abs */ + 0x2, /* ce */ /* DEC abs */ + 0x3, /* cf */ /* CMP long */ + 0x1, /* d0 */ /* BNE disp8 */ + 0x1, /* d1 */ /* CMP (),y */ + 0x1, /* d2 */ /* CMP () */ + 0x1, /* d3 */ /* CMP (disp8,s),y */ + 0x1, /* d4 */ /* PEI Dloc */ + 0x1, /* d5 */ /* CMP Dloc,x */ + 0x1, /* d6 */ /* DEC Dloc,x */ + 0x1, /* d7 */ /* CMP [],y */ + 0x0, /* d8 */ /* CLD */ + 0x2, /* d9 */ /* CMP abs,y */ + 0x0, /* da */ /* PHX */ + 0x0, /* db */ /* STP */ + 0x2, /* dc */ /* JML (Abs) */ + 0x2, /* dd */ /* CMP Abs,X */ + 0x2, /* de */ /* DEC abs,x */ + 0x3, /* df */ /* CMP Long,x */ + 0x5, /* e0 */ /* CPX #Imm */ + 0x1, /* e1 */ /* SBC (Dloc,X) */ + 0x1, /* e2 */ /* SEP #8bit */ + 0x1, /* e3 */ /* SBC Disp8,S */ + 0x1, /* e4 */ /* CPX Dloc */ + 0x1, /* e5 */ /* SBC Dloc */ + 0x1, /* e6 */ /* INC Dloc */ + 0x1, /* e7 */ /* SBC [Dloc] */ + 0x0, /* e8 */ /* INX */ + 0x4, /* e9 */ /* SBC #imm */ + 0x0, /* ea */ /* NOP */ + 0x0, /* eb */ /* XBA */ + 0x2, /* ec */ /* CPX abs */ + 0x2, /* ed */ /* SBC abs */ + 0x2, /* ee */ /* INC abs */ + 0x3, /* ef */ /* SBC long */ + 0x1, /* f0 */ /* BEQ disp8 */ + 0x1, /* f1 */ /* SBC (),y */ + 0x1, /* f2 */ /* SBC () */ + 0x1, /* f3 */ /* SBC (disp8,s),y */ + 0x2, /* f4 */ /* PEA Imm */ + 0x1, /* f5 */ /* SBC Dloc,x */ + 0x1, /* f6 */ /* INC Dloc,x */ + 0x1, /* f7 */ /* SBC [],y */ + 0x0, /* f8 */ /* SED */ + 0x2, /* f9 */ /* SBC abs,y */ + 0x0, /* fa */ /* PLX */ + 0x0, /* fb */ /* XCE */ + 0x2, /* fc */ /* JSR (Abs,x) */ + 0x2, /* fd */ /* SBC Abs,X */ + 0x2, /* fe */ /* INC abs,x */ + 0x3, /* ff */ /* SBC Long,x */ + diff --git a/gsplus/src/smartport.c b/gsplus/src/smartport.c new file mode 100644 index 0000000..b3dd1ea --- /dev/null +++ b/gsplus/src/smartport.c @@ -0,0 +1,808 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2024 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include "defc.h" + +extern int Verbose; +extern int Halt_on; +extern int g_rom_version; +extern int g_io_amt; +extern int g_highest_smartport_unit; +extern dword64 g_cur_dfcyc; + +extern Engine_reg engine; + +extern Iwm g_iwm; + +#define LEN_SMPT_LOG 16 +STRUCT(Smpt_log) { + word32 start_addr; + int cmd; + int rts_addr; + int cmd_list; + int extras; + int unit; + int buf; + int blk; +}; + +Smpt_log g_smpt_log[LEN_SMPT_LOG]; +int g_smpt_log_pos = 0; + +void +smartport_error(void) +{ + int pos; + int i; + + pos = g_smpt_log_pos; + printf("Smartport log pos: %d\n", pos); + for(i = 0; i < LEN_SMPT_LOG; i++) { + pos--; + if(pos < 0) { + pos = LEN_SMPT_LOG - 1; + } + printf("%d:%d: t:%04x, cmd:%02x, rts:%04x, " + "cmd_l:%04x, x:%d, unit:%d, buf:%04x, blk:%04x\n", + i, pos, + g_smpt_log[pos].start_addr, + g_smpt_log[pos].cmd, + g_smpt_log[pos].rts_addr, + g_smpt_log[pos].cmd_list, + g_smpt_log[pos].extras, + g_smpt_log[pos].unit, + g_smpt_log[pos].buf, + g_smpt_log[pos].blk); + } +} +void +smartport_log(word32 start_addr, word32 cmd, word32 rts_addr, word32 cmd_list) +{ + int pos; + + pos = g_smpt_log_pos; + if(start_addr != 0) { + g_smpt_log[pos].start_addr = start_addr; + g_smpt_log[pos].cmd = cmd; + g_smpt_log[pos].rts_addr = rts_addr; + g_smpt_log[pos].cmd_list = cmd_list; + g_smpt_log[pos].extras = 0; + g_smpt_log[pos].unit = 0; + g_smpt_log[pos].buf = 0; + g_smpt_log[pos].blk = 0; + } else { + pos--; + if(pos < 0) { + pos = LEN_SMPT_LOG - 1; + } + g_smpt_log[pos].extras = 1; + g_smpt_log[pos].unit = cmd; + g_smpt_log[pos].buf = rts_addr; + g_smpt_log[pos].blk = cmd_list; + } + pos++; + if(pos >= LEN_SMPT_LOG) { + pos = 0; + } + g_smpt_log_pos = pos; +} + +void +do_c70d(word32 arg0) +{ + dword64 dsize; + word32 status_ptr, rts_addr, cmd_list, cmd_list_lo, cmd_list_mid; + word32 cmd_list_hi, status_ptr_lo, status_ptr_mid, status_ptr_hi; + word32 rts_lo, rts_hi, buf_ptr_lo, buf_ptr_hi, buf_ptr, mask, cmd; + word32 block_lo, block_mid, block_hi, block_hi2, unit, ctl_code; + word32 ctl_ptr_lo, ctl_ptr_hi, ctl_ptr, block, stat_val; + int param_cnt, ret, ext, slot; + int i; + + slot = (engine.kpc >> 8) & 7; + set_memory_c(0x7f8, 0xc0 | slot, 1); + + if((engine.psr & 0x100) == 0) { + disk_printf("c70d %02x called in native mode!\n", arg0); + if((engine.psr & 0x30) != 0x30) { + halt_printf("c70d called native, psr: %03x!\n", + engine.psr); + } + } + + engine.stack = ((engine.stack + 1) & 0xff) + 0x100; + rts_lo = get_memory_c(engine.stack); + engine.stack = ((engine.stack + 1) & 0xff) + 0x100; + rts_hi = get_memory_c(engine.stack); + rts_addr = (rts_lo + (256*rts_hi) + 1) & 0xffff; + disk_printf("rts_addr: %04x\n", rts_addr); + + cmd = get_memory_c(rts_addr); + cmd_list_lo = get_memory_c((rts_addr + 1) & 0xffff); + cmd_list_mid = get_memory_c((rts_addr + 2) & 0xffff); + cmd_list_hi = 0; + mask = 0xffff; + ext = 0; + if(cmd & 0x40) { + ext = 2; + mask = 0xffffff; + cmd_list_hi = get_memory_c((rts_addr + 3) & 0xffff); + } + + cmd_list = cmd_list_lo + (256*cmd_list_mid) + (65536*cmd_list_hi); + + disk_printf("cmd: %02x, cmd_list: %06x\n", cmd, cmd_list); + param_cnt = get_memory_c(cmd_list); + unit = get_memory_c((cmd_list + 1) & mask); + ctl_code = get_memory_c((cmd_list + 4 + ext) & mask); + + smartport_log(0xc70d, cmd, rts_addr, cmd_list); + dbg_log_info(g_cur_dfcyc, (rts_addr << 16) | (unit << 8) | cmd, + cmd_list, 0xc70d); +#if 0 + if(cmd != 0x41) { + printf("SMTPT: c70d %08x, %08x at %016llx\n", + (rts_addr << 16) | (unit << 8) | cmd, cmd_list, + g_cur_dfcyc); + } +#endif + ret = 0; + if((unit >= 1) && (unit <= MAX_C7_DISKS) && ext) { + if(g_iwm.smartport[unit-1].just_ejected) { + ret = 0x2e; // DISKSW error + } + g_iwm.smartport[unit-1].just_ejected = 0; + } + + switch(cmd & 0x3f) { + case 0x00: /* Status == 0x00 and 0x40 */ + if(param_cnt != 3) { + disk_printf("param_cnt %d is != 3!\n", param_cnt); + ret = 0x04; // BADPCNT + break; + } + status_ptr_lo = get_memory_c((cmd_list+2) & mask); + status_ptr_mid = get_memory_c((cmd_list+3) & mask); + status_ptr_hi = 0; + if(cmd & 0x40) { + status_ptr_hi = get_memory_c((cmd_list+4) & mask); + } + + status_ptr = status_ptr_lo + (256*status_ptr_mid) + + (65536*status_ptr_hi); + + smartport_log(0, unit, status_ptr, ctl_code); + dbg_log_info(g_cur_dfcyc, (ctl_code << 16) | unit, + cmd_list, 0xc700); + + disk_printf("unit: %02x, status_ptr: %06x, code: %02x\n", + unit, status_ptr, ctl_code); + if((unit == 0) && (ctl_code == 0)) { + /* Smartport driver status */ + /* see technotes/smpt/tn-smpt-002 */ + set_memory_c(status_ptr, MAX_C7_DISKS, 1); + set_memory_c(status_ptr+1, 0xff, 1); // intrpt stat + set_memory16_c(status_ptr+2, 0x004b, 1); // vendor id + set_memory16_c(status_ptr+4, 0x1000, 1); // version + set_memory16_c(status_ptr+6, 0x0000, 1); + //printf(" driver status, highest_unit:%02x\n", + // g_highest_smartport_unit+1); + + engine.xreg = 8; + engine.yreg = 0; + } else if((unit > 0) && (ctl_code == 0)) { + /* status for unit x */ + if((unit > MAX_C7_DISKS) || + (g_iwm.smartport[unit-1].fd < 0)) { + stat_val = 0x80; + dsize = 0; + ret = 0; // Not DISK_SWITCHed error + } else { + stat_val = 0xf8; + dsize = g_iwm.smartport[unit-1].dimage_size; + dsize = (dsize+511) / 512; + if(g_iwm.smartport[unit-1].write_prot) { + stat_val |= 4; // Write prot + } + } +#if 0 + printf(" status unit:%02x just_ejected:%d, " + "stat_val:%02x\n", unit, + g_iwm.smartport[unit-1].just_ejected, + stat_val); +#endif + set_memory_c(status_ptr, stat_val, 1); + set_memory24_c(status_ptr + 1, (word32)dsize); + engine.xreg = 4; + if(cmd & 0x40) { + set_memory_c(status_ptr + 4, + (dsize >> 24) & 0xff, 1); + engine.xreg = 5; + } + engine.yreg = 0; + disk_printf("just finished unit %d, stat 0\n", unit); + } else if(ctl_code == 3) { + if((unit > MAX_C7_DISKS) || + (g_iwm.smartport[unit-1].fd < 0)) { + stat_val = 0x80; + dsize = 0; + ret = 0; // Not a disk-switched error + } else { + stat_val = 0xf8; + dsize = g_iwm.smartport[unit-1].dimage_size; + dsize = (dsize + 511) / 512; + } + if(cmd & 0x40) { + disk_printf("extended for stat_code 3!\n"); + } + /* DIB for unit 1 */ + set_memory_c(status_ptr, stat_val, 1); + set_memory24_c(status_ptr + 1, (word32)dsize); + if(cmd & 0x40) { + set_memory_c(status_ptr + 4, + (dsize >> 24) & 0xff, 1); + status_ptr++; + } + set_memory_c(status_ptr + 4, 4, 1); + for(i = 5; i < 21; i++) { + set_memory_c(status_ptr + i, 0x20, 1); + } + set_memory_c(status_ptr + 5, 'K', 1); + set_memory_c(status_ptr + 6, 'E', 1); + set_memory_c(status_ptr + 7, 'G', 1); + set_memory_c(status_ptr + 8, 'S', 1); + + // Profile hard disk supporting extended calls+disk_sw + set_memory16_c(status_ptr + 21, 0xc002, 1); + set_memory16_c(status_ptr + 23, 0x0000, 1); + + if(cmd & 0x40) { + engine.xreg = 26; + } else { + engine.xreg = 25; + } +#if 0 + printf(" DIB unit:%02x just_ejected:%d, " + "stat_val:%02x\n", unit, + g_iwm.smartport[unit-1].just_ejected, + stat_val); +#endif + engine.yreg = 0; + + disk_printf("Just finished unit %d, stat 3\n", unit); + if(unit == 0 || unit > MAX_C7_DISKS) { + ret = 0x28; // NODRIVE error + } + } else { + printf("cmd: 00, unknown unit/status code %02x!\n", + ctl_code); + ret = 0x21; // BADCTL + } + break; + case 0x01: /* Read Block == 0x01 and 0x41 */ + if(param_cnt != 3) { + halt_printf("param_cnt %d is != 3!\n", param_cnt); + ret = 0x04; // BADPCNT + break; + } + buf_ptr_lo = get_memory_c((cmd_list+2) & mask); + buf_ptr_hi = get_memory_c((cmd_list+3) & mask); + + buf_ptr = buf_ptr_lo + (256*buf_ptr_hi); + if(cmd & 0x40) { + buf_ptr_lo = get_memory_c((cmd_list+4) & mask); + buf_ptr_hi = get_memory_c((cmd_list+5) & mask); + buf_ptr += ((buf_ptr_hi*256) + buf_ptr_lo)*65536; + cmd_list += 2; + } + block_lo = get_memory_c((cmd_list+4) & mask); + block_mid = get_memory_c((cmd_list+5) & mask); + block_hi = get_memory_c((cmd_list+6) & mask); + block_hi2 = 0; + if(cmd & 0x40) { + block_hi2 = get_memory_c((cmd_list+7) & mask); + } + block = (block_hi2 << 24) | (block_hi << 16) | + (block_mid << 8) | block_lo; + disk_printf("smartport read unit %d of block %06x to %06x\n", + unit, block, buf_ptr); + if(unit < 1 || unit > MAX_C7_DISKS) { + halt_printf("Unknown unit #: %d\n", unit); + } + + smartport_log(0, unit - 1, buf_ptr, block); + + if(ret == 0) { + ret = do_read_c7(unit - 1, buf_ptr, block); + } + engine.xreg = 0; + engine.yreg = 2; + break; + case 0x02: /* Write Block == 0x02 and 0x42 */ + if(param_cnt != 3) { + halt_printf("param_cnt %d is != 3!\n", param_cnt); + ret = 0x04; // BADPCNT + break; + } + buf_ptr_lo = get_memory_c((cmd_list+2) & mask); + buf_ptr_hi = get_memory_c((cmd_list+3) & mask); + + buf_ptr = buf_ptr_lo + (256*buf_ptr_hi); + if(cmd & 0x40) { + buf_ptr_lo = get_memory_c((cmd_list+4) & mask); + buf_ptr_hi = get_memory_c((cmd_list+5) & mask); + buf_ptr += ((buf_ptr_hi*256) + buf_ptr_lo)*65536; + cmd_list += 2; + } + block_lo = get_memory_c((cmd_list+4) & mask); + block_mid = get_memory_c((cmd_list+5) & mask); + block_hi = get_memory_c((cmd_list+6) & mask); + block_hi2 = 0; + if(cmd & 0x40) { + block_hi2 = get_memory_c((cmd_list+7) & mask); + } + block = (block_hi2 << 24) | (block_hi << 16) | + (block_mid << 8) | block_lo; + disk_printf("smartport write unit %d of block %04x from %04x\n", + unit, block, buf_ptr); + if(unit < 1 || unit > MAX_C7_DISKS) { + halt_printf("Unknown unit #: %d\n", unit); + } + + smartport_log(0, unit - 1, buf_ptr, block); + + if(ret == 0) { + ret = do_write_c7(unit - 1, buf_ptr, block); + } + engine.xreg = 0; + engine.yreg = 2; + + HALT_ON(HALT_ON_C70D_WRITES, "c70d Write done\n"); + break; + case 0x03: /* Format == 0x03 and 0x43 */ + if(param_cnt != 1) { + halt_printf("param_cnt %d is != 1!\n", param_cnt); + ret = 0x04; // BADPCNT + break; + } + if((unit < 1) || (unit > MAX_C7_DISKS)) { + halt_printf("Unknown unit #: %d\n", unit); + ret = 0x11; // BADUNIT + } + + smartport_log(0, unit - 1, 0, 0); + + if(ret == 0) { + ret = do_format_c7(unit - 1); + } + engine.xreg = 0; + engine.yreg = 2; + + HALT_ON(HALT_ON_C70D_WRITES, "c70d Format done\n"); + break; + case 0x04: /* Control == 0x04 and 0x44 */ + if(cmd == 0x44) { + halt_printf("smartport code 0x44 not supported\n"); + } + if(param_cnt != 3) { + halt_printf("param_cnt %d is != 3!\n", param_cnt); + break; + } + ctl_ptr_lo = get_memory_c((cmd_list+2) & mask); + ctl_ptr_hi = get_memory_c((cmd_list+3) & mask); + ctl_ptr = (ctl_ptr_hi << 8) + ctl_ptr_lo; + if(cmd & 0x40) { + ctl_ptr_lo = get_memory_c((cmd_list+4) & mask); + ctl_ptr_hi = get_memory_c((cmd_list+5) & mask); + ctl_ptr += ((ctl_ptr_hi << 8) + ctl_ptr_lo) << 16; + cmd_list += 2; + } + + switch(ctl_code) { + case 0x00: + printf("Performing a reset on unit %d\n", unit); + break; + default: + halt_printf("control code: %02x ptr:%06x unknown!\n", + ctl_code, ctl_ptr); + } + // printf("CONTROL, ctl_code:%02x\n", ctl_code); + + engine.xreg = 0; + engine.yreg = 2; + break; + default: /* Unknown command! */ + /* set acc = 1, and set carry, and set kpc */ + engine.xreg = (rts_addr) & 0xff; + engine.yreg = (rts_addr >> 8) & 0xff; + ret = 0x01; // BADCMD error + if((cmd != 0x4b) && (cmd != 0x48) && (cmd != 0x4a)) { + // Finder does 0x4a before dialog for formatting disk + // Finder does 0x4b call before formatting disk + // Many things do 0x48 call to see online drives + // So: ignore those, just return BADCMD + halt_printf("Just did smtport cmd:%02x rts_addr:%04x, " + "cmdlst:%06x\n", cmd, rts_addr, cmd_list); + } + } + + engine.acc = (engine.acc & 0xff00) | (ret & 0xff); + engine.psr &= ~1; + if(ret) { + engine.psr |= 1; + printf("Smtport cmd:%02x unit:%02x ctl_code:%02x ret:%02x\n", + cmd, unit, ctl_code, ret); + } + engine.kpc = (rts_addr + 3 + ext) & 0xffff; + // printf(" ret:%02x psr_c:%d\n", ret & 0xff, engine.psr & 1); +} + +// $C70A is the ProDOS entry point, documented in ProDOS 8 Technical Ref +// Manual, section 6.3. +void +do_c70a(word32 arg0) +{ + dword64 dsize; + word32 cmd, unit, buf_lo, buf_hi, blk_lo, blk_hi, blk, buf; + word32 prodos_unit; + int ret, slot; + + slot = (engine.kpc >> 8) & 7; + set_memory_c(0x7f8, 0xc0 | slot, 1); + + cmd = get_memory_c((engine.direct + 0x42) & 0xffff); + prodos_unit = get_memory_c((engine.direct + 0x43) & 0xffff); + buf_lo = get_memory_c((engine.direct + 0x44) & 0xffff); + buf_hi = get_memory_c((engine.direct + 0x45) & 0xffff); + blk_lo = get_memory_c((engine.direct + 0x46) & 0xffff); + blk_hi = get_memory_c((engine.direct + 0x47) & 0xffff); + + blk = (blk_hi << 8) + blk_lo; + buf = (buf_hi << 8) + buf_lo; + disk_printf("c70a %02x cmd:%02x, pro_unit:%02x, buf:%04x, blk:%04x\n", + arg0, cmd, prodos_unit, buf, blk); + + unit = 0 + (prodos_unit >> 7); // units 0,1 + if((prodos_unit & 0x7f) != (slot << 4)) { + unit += 2; // units 2,3 + } + + smartport_log(0xc70a, cmd, blk, buf); + dbg_log_info(g_cur_dfcyc, + (buf << 16) | ((unit & 0xff) << 8) | (cmd & 0xff), blk, 0xc70a); + +#if 0 + if(cmd != 0x1ff) { + printf("SMTPT: c70a %08x %08x\n", + (buf << 16) | ((unit & 0xff) << 8) | (cmd & 0xff), blk); + } +#endif + engine.psr &= ~1; /* clear carry */ + + ret = 0x27; /* I/O error */ + if(cmd == 0x00) { + dsize = g_iwm.smartport[unit].dimage_size; + dsize = (dsize + 511) / 512; + + smartport_log(0, unit, (word32)dsize, 0); + dbg_log_info(g_cur_dfcyc, ((unit & 0xff) << 8) | (cmd & 0xff), + (word32)dsize, 0x1c700); + + ret = 0; + engine.xreg = dsize & 0xff; + engine.yreg = (word32)(dsize >> 8); + } else if(cmd == 0x01) { + smartport_log(0, unit, buf, blk); + ret = do_read_c7(unit, buf, blk); + } else if(cmd == 0x02) { + smartport_log(0, unit, buf, blk); + ret = do_write_c7(unit, buf, blk); + } else if(cmd == 0x03) { /* format */ + smartport_log(0, unit, buf, blk); + ret = do_format_c7(unit); + } + + engine.acc = (engine.acc & 0xff00) | (ret & 0xff); + if(ret != 0) { + engine.psr |= 1; // Set carry + } + return; +} + +int +do_read_c7(int unit_num, word32 buf, word32 blk) +{ + byte local_buf[0x200]; + Disk *dsk; + byte *bptr; + dword64 dimage_start, dimage_size, dret; + word32 val; + int len, fd; + int i; + + dbg_log_info(g_cur_dfcyc, (buf << 8) | (unit_num & 0xff), blk, 0xc701); + if((unit_num < 0) || (unit_num > MAX_C7_DISKS)) { + halt_printf("do_read_c7: unit_num: %d\n", unit_num); + smartport_error(); + return 0x28; + } + + dsk = &(g_iwm.smartport[unit_num]); + fd = dsk->fd; + dimage_start = dsk->dimage_start; + dimage_size = dsk->dimage_size; + if(fd < 0) { + printf("c7_fd == %d!\n", fd); +#if 0 + if(blk != 2 && blk != 0) { + /* don't print error if only reading directory */ + smartport_error(); + halt_printf("Read unit:%02x blk:%04x\n", unit_num, blk); + } +#endif + return 0x2f; + } + if(((blk + 1) * 0x200ULL) > (dimage_start + dimage_size)) { + halt_printf("Tried to read past %08llx on disk (blk:%04x)\n", + dimage_start + dimage_size, blk); + smartport_error(); + return 0x27; + } + + if(dsk->raw_data) { + // image was compressed and is in dsk->raw_data + bptr = dsk->raw_data + dimage_start + (blk*0x200ULL); + for(i = 0; i < 0x200; i++) { + local_buf[i] = bptr[i]; + } + } else { + dret = kegs_lseek(fd, dimage_start + blk*0x200ULL, SEEK_SET); + if(dret != (dimage_start + blk*0x200ULL)) { + halt_printf("lseek ret %08llx, errno:%d\n", dret, + errno); + smartport_error(); + return 0x27; + } + + len = (int)read(fd, &local_buf[0], 0x200); + if(len != 0x200) { + printf("read returned %08x, errno:%d, blk:%04x, unit:" + "%02x\n", len, errno, blk, unit_num); + halt_printf("name: %s\n", dsk->name_ptr); + smartport_error(); + return 0x27; + } + } + + g_io_amt += 0x200; + + if(buf >= 0xfc0000) { + disk_printf("reading into ROM, just returning\n"); + return 0; + } + + for(i = 0; i < 0x200; i += 2) { + val = (local_buf[i+1] << 8) + local_buf[i]; + set_memory16_c(buf + i, val, 0); + } + + return 0; +} + +int +do_write_c7(int unit_num, word32 buf, word32 blk) +{ + byte local_buf[0x200]; + Disk *dsk; + dword64 dret, dimage_start, dimage_size; + int len, fd, ret; + int i; + + dbg_log_info(g_cur_dfcyc, (buf << 16) | (unit_num & 0xff), blk, 0xc702); + + if(unit_num < 0 || unit_num > MAX_C7_DISKS) { + halt_printf("do_write_c7: unit_num: %d\n", unit_num); + smartport_error(); + return 0x28; + } + + dsk = &(g_iwm.smartport[unit_num]); + fd = dsk->fd; + dimage_start = dsk->dimage_start; + dimage_size = dsk->dimage_size; + if(fd < 0) { + halt_printf("c7_fd == %d!\n", fd); + smartport_error(); + return 0x28; + } + + for(i = 0; i < 0x200; i++) { + local_buf[i] = get_memory_c(buf + i); + } + + if(dsk->write_prot) { + printf("Write, but s7d%d %s is write protected!\n", + unit_num + 1, dsk->name_ptr); + return 0x2b; + } + + if(dsk->write_through_to_unix == 0) { + //halt_printf("Write to %s, but not wr_thru!\n", dsk->name_ptr); + if(dsk->raw_data) { + // Update the memory copy + ret = smartport_memory_write(dsk, &local_buf[0], + blk * 0x200ULL, 0x200); + if(ret) { + return 0x27; // I/O Error + } + } + return 0x00; + } + + if(dsk->dynapro_info_ptr) { + dynapro_write(dsk, &local_buf[0], blk*0x200UL, 0x200); + } else { + dret = kegs_lseek(fd, dimage_start + blk*0x200ULL, SEEK_SET); + if(dret != (dimage_start + blk*0x200ULL)) { + halt_printf("lseek returned %08llx, errno: %d\n", dret, + errno); + smartport_error(); + return 0x27; + } + + if(dret >= (dimage_start + dimage_size)) { + halt_printf("Tried to write to %08llx\n", dret); + smartport_error(); + return 0x27; + } + + len = (int)write(fd, &local_buf[0], 0x200); + if(len != 0x200) { + halt_printf("write ret %08x bytes, errno: %d\n", len, + errno); + smartport_error(); + dsk->write_prot = 1; + return 0x2b; // Write protected + } + } + + g_io_amt += 0x200; + + return 0; +} + +int +smartport_memory_write(Disk *dsk, byte *bufptr, dword64 doffset, word32 size) +{ + byte *bptr; + word32 ui; + + bptr = dsk->raw_data; + if((bptr == 0) || ((doffset + size) > dsk->dimage_size)) { + printf("Write to %s failed, %08llx past end %08llx\n", + dsk->name_ptr, doffset, dsk->dimage_size); + return -1; + } + for(ui = 0; ui < size; ui++) { + bptr[doffset + ui] = bufptr[ui]; + } + + return 0; +} + +int +do_format_c7(int unit_num) +{ + byte local_buf[0x1000]; + Disk *dsk; + dword64 dimage_start, dimage_size, dret, dtotal, dsum; + int len, max, fd, ret; + int i; + + dbg_log_info(g_cur_dfcyc, (unit_num & 0xff), 0, 0xc703); + + if(unit_num < 0 || unit_num > MAX_C7_DISKS) { + halt_printf("do_format_c7: unit_num: %d\n", unit_num); + smartport_error(); + return 0x28; + } + + dsk = &(g_iwm.smartport[unit_num]); + fd = dsk->fd; + dimage_start = dsk->dimage_start; + dimage_size = dsk->dimage_size; + if(fd < 0) { + halt_printf("c7_fd == %d!\n", fd); + smartport_error(); + return 0x28; + } + + if(dsk->write_prot || (dsk->raw_data && !dsk->dynapro_info_ptr)) { + printf("Format, but %s is write protected!\n", dsk->name_ptr); + return 0x2b; + } + + if(dsk->write_through_to_unix == 0) { + if(!dsk->raw_data) { + printf("Format of %s ignored\n", dsk->name_ptr); + return 0x00; + } + } + + for(i = 0; i < 0x1000; i++) { + local_buf[i] = 0; + } + + if(!dsk->dynapro_info_ptr) { + dret = kegs_lseek(fd, dimage_start, SEEK_SET); + if(dret != dimage_start) { + halt_printf("lseek returned %08llx, errno: %d\n", dret, + errno); + smartport_error(); + return 0x27; + } + } + + dsum = 0; + dtotal = dimage_size; + + while(dsum < dtotal) { + max = (int)MY_MIN(0x1000, dtotal - dsum); + len = max; + if(dsk->dynapro_info_ptr) { + dynapro_write(dsk, &local_buf[0], dsum, max); + } else if(dsk->raw_data) { + ret = smartport_memory_write(dsk, &local_buf[0], + dsum, max); + if(ret) { + return 0x27; // I/O Error + } + } else { + len = (int)write(fd, &local_buf[0], max); + } + if(len != max) { + halt_printf("write ret %08x, errno:%d\n", len, errno); + smartport_error(); + dsk->write_prot = 1; + return 0x2b; // Write-protected + } + dsum += len; + } + + return 0; +} + +void +do_c700(word32 ret) +{ + int slot; + + disk_printf("do_c700 called, ret: %08x\n", ret); + dbg_log_info(g_cur_dfcyc, 0, 0, 0xc700); + + slot = (engine.kpc >> 8) & 7; + ret = do_read_c7(0, 0x800, 0); // Always read unit 0, block 0 + + set_memory_c(0x7f8, slot, 1); + set_memory16_c(0x42, (slot << 12) | 1, 1); + set_memory16_c(0x44, 0x0800, 1); + set_memory16_c(0x46, 0x0000, 1); + engine.xreg = slot << 4; // 0x70 for slot 7 + engine.kpc = 0x801; + + if(ret != 0) { + printf("Failure reading boot disk in s7d1, trying slot 5!\n"); + engine.kpc = 0xc500; // Try to boot slot 5 + if((slot == 5) || (g_rom_version == 0)) { + engine.kpc = 0xc600; // Try to boot slot 6 + } + } +} + diff --git a/gsplus/src/sound.c b/gsplus/src/sound.c new file mode 100644 index 0000000..8d5df3c --- /dev/null +++ b/gsplus/src/sound.c @@ -0,0 +1,1003 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include "defc.h" + +#define INCLUDE_RCSID_C +#include "sound.h" +#undef INCLUDE_RCSID_C + +#define DOC_LOG(a,b,c,d) + +extern int Verbose; +extern int g_use_shmem; +extern word32 g_vbl_count; +extern int g_preferred_rate; + +extern word32 g_c03ef_doc_ptr; + +extern int g_doc_vol; + +extern dword64 g_last_vbl_dfcyc; + +int g_queued_samps = 0; +int g_queued_nonsamps = 0; + +#if defined(HPUX) || defined(__linux__) || defined(_WIN32) || defined(MAC) +int g_audio_enable = -1; +#else +int g_audio_enable = 0; /* Not supported: default to off */ +#endif +int g_sound_min_msecs = 32; // 32 msecs +int g_sound_min_msecs_pulse = 150; // 150 msecs +int g_sound_max_multiplier = 6; // 6*32 = ~200 msecs +int g_sound_min_samples = 48000 * 32/1000; // 32 msecs + +Mockingboard g_mockingboard; + +// The AY8913 chip has non-linear amplitudes (it has 16 levels) and the +// documentation does not match measured results. But all the measurements +// should really be done at the final speaker/jack since all the stuff in +// the path affects it. But: no one's done this for Mockingboard that I +// have found, so I'm taking measurements from the AY8913 chip itself. +// AY8913 amplitudes from https://groups.google.com/forum/#!original/ +// comp.sys.sinclair/-zCR2kxMryY/XgvaDICaldUJ +// by Matthew Westcott on December 21, 2001. +double g_ay8913_ampl_factor_westcott[16] = { // NOT USED + 0.000, // level[0] + 0.010, // level[1] + 0.015, // level[2] + 0.022, // level[3] + 0.031, // level[4] + 0.046, // level[5] + 0.064, // level[6] + 0.106, // level[7] + 0.132, // level[8] + 0.216, // level[9] + 0.297, // level[10] + 0.391, // level[11] + 0.513, // level[12] + 0.637, // level[13] + 0.819, // level[14] + 1.000, // level[15] +}; +// https://sourceforge.net/p/fuse-emulator/mailman/message/34065660/ +// refers to some Russian-language measurements at: +// http://forum.tslabs.info/viewtopic.php?f=6&t=539 (translate from +// Russian), they give: +// 0000,028F,03B3,0564, 07DC,0BA9,1083,1B7C, +// 2068,347A,4ACE,5F72, 7E16,A2A4,CE3A,FFFF +double g_ay8913_ampl_factor[16] = { + 0.000, // level[0] + 0.010, // level[1] + 0.014, // level[2] + 0.021, // level[3] + 0.031, // level[4] + 0.046, // level[5] + 0.064, // level[6] + 0.107, // level[7] + 0.127, // level[8] + 0.205, // level[9] + 0.292, // level[10] + 0.373, // level[11] + 0.493, // level[12] + 0.635, // level[13] + 0.806, // level[14] + 1.000, // level[15] +}; +// MAME also appears to try to figure out how the channels get "summed" +// together. KEGS code adds them in a completely independent way, and due +// to the circuit used on the AY8913, this is certainly incorrect. +#define MAX_MOCK_ENV_SAMPLES 2000 +int g_mock_env_vol[MAX_MOCK_ENV_SAMPLES]; +byte g_mock_noise_bytes[MAX_MOCK_ENV_SAMPLES]; +int g_mock_volume[16]; // Sample for each of the 16 amplitudes +word32 g_last_mock_vbl_count = 0; + +#define VAL_MOCK_RANGE (39000) + +int g_audio_rate = 0; +double g_daudio_rate = 0.0; +double g_drecip_audio_rate = 0.0; +double g_dsamps_per_dfcyc = 0.0; +double g_fcyc_per_samp = 0.0; + +double g_last_sound_play_dsamp = 0.0; + +#define VAL_C030_POS_VAL (20400*16/15) + // C030_POS_VAL is multiplied by g_doc_vol (0-15) and then + // divided by 16. So scale this value up by 16/15 so that + // g_doc_vol==15 gives the intended value (+/-20400) + +#define MAX_C030_TIMES 18000 +float g_c030_fsamps[MAX_C030_TIMES + 2]; +int g_num_c030_fsamps = 0; +int g_c030_state = 0; +int g_c030_val = (VAL_C030_POS_VAL / 2); +dword64 g_c030_dsamp_last_toggle = 0; + +word32 *g_sound_shm_addr = 0; +int g_sound_shm_pos = 0; + +extern dword64 g_cur_dfcyc; + +#define MAX_SND_BUF 65536 + +int g_samp_buf[2*MAX_SND_BUF]; +byte g_zero_buf[4096]; + +double g_doc_dsamps_extra = 0.0; + +int g_num_snd_plays = 0; +int g_num_recalc_snd_parms = 0; + +char *g_sound_file_str = 0; +int g_sound_file_fd = -1; +int g_sound_file_bytes = 0; + +// WAV file information: +// From https://docs.fileformat.com/audio/wav/ +// left channel is first: https://web.archive.org/web/20080113195252/ +// http://www.borg.com/~jglatt/tech/wave.htm + +byte g_wav_hdr[44] = { + 'R', 'I', 'F', 'F', 0xff, 0xff, 0xff, 0xff, // 0x00-0x07 + 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ', // 0x08-0x0f + 16, 0, 0, 0, 1, 0, 2, 0, // 0x10-0x17 + // 16=length of 'fmt ' chunk, 1=PCM, 2=stereo. + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0x18-0x1f + 4, 0, 16, 0, 'd', 'a', 't', 'a', // 0x20-0x27 + 0xff, 0xff, 0xff, 0xff // 0x28-0x2b +}; + // Bytes 4-7 are the total file size-8, so [0x28:0x2b]+0x24 + // Bytes [0x18-0x1b]is the sample rate (so 44100 or so) + // [0x1c-0x1f] is bytes-per-second: [0x18-0x1b]*[0x10]*[0x16]/8 + + +void +sound_init() +{ + doc_init(); + snddrv_init(); +} + +void +sound_set_audio_rate(int rate) +{ + g_audio_rate = rate; + g_daudio_rate = (rate)*1.0; + g_drecip_audio_rate = 1.0/(rate); + g_dsamps_per_dfcyc = ((rate*1.0) / (DCYCS_1_MHZ * 65536.0)); + g_fcyc_per_samp = (DCYCS_1_MHZ * 65536.0 / (rate*1.0)); + g_sound_min_samples = rate * g_sound_min_msecs / 1000; + + printf("Set g_audio_rate = %d in main KEGS process, min_samples:%d\n", + rate, g_sound_min_samples); +} + +void +sound_reset(dword64 dfcyc) +{ + doc_reset(dfcyc); + mockingboard_reset(dfcyc); +} + +void +sound_shutdown() +{ + snddrv_shutdown(); +} + +void +sound_update(dword64 dfcyc) +{ + /* Called every VBL time to update sound status */ + + /* "play" sounds for this vbl */ + + //DOC_LOG("do_snd_pl", -1, dsamps, 0); + sound_play(dfcyc); +} + +void +sound_file_start(char *filename) +{ + sound_file_close(); + + g_sound_file_str = filename; // Can be NULL, if so, do not start + if(filename) { + printf("Set audio save file to: %s\n", filename); + } +} + +void +sound_file_open() +{ + char *filename; + word32 exp_size; + int fd; + + filename = g_sound_file_str; + if(!filename) { + return; + } + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0x1b6); + if(fd < 0) { + printf("open_sound_file open ret: %d, errno: %d\n", fd, errno); + sound_file_close(); + return; + } + + exp_size = 1024*1024; // Default to 1MB, changed at close + dynapro_set_word32(&g_wav_hdr[4], exp_size + 44 - 8); // File size + dynapro_set_word32(&g_wav_hdr[0x28], exp_size); // data size + dynapro_set_word32(&g_wav_hdr[0x18], g_audio_rate); // Sample rate + dynapro_set_word32(&g_wav_hdr[0x1c], g_audio_rate * 2 * 2); + // bytes-per-sec + + (void)cfg_write_to_fd(fd, &g_wav_hdr[0], 0, 44); + g_sound_file_fd = fd; + g_sound_file_bytes = 0; + printf("Opened file %s for sound\n", filename); +} + +void +sound_file_close() +{ + int fd; + + fd = g_sound_file_fd; + if(fd >= 0) { + dynapro_set_word32(&g_wav_hdr[0x28], g_sound_file_bytes); + dynapro_set_word32(&g_wav_hdr[4], g_sound_file_bytes + 44 - 8); + cfg_write_to_fd(fd, &g_wav_hdr[0], 0, 44); + // Rewrite first 44 bytes with WAV header + printf("Close sound file %s, fd:%d\n", g_sound_file_str, fd); + close(fd); + } + + free(g_sound_file_str); + g_sound_file_fd = -1; + g_sound_file_str = 0; +} + +void +send_sound_to_file(word32 *wptr, int shm_pos, int num_samps, int real_samps) +{ + int size, this_size; + + if(!real_samps && g_sound_file_bytes) { + // Don't do anything + return; + } + if(g_sound_file_fd < 0) { + sound_file_open(); + } + + if(!wptr) { + // No real samps + size = real_samps * 4; + while(size) { + this_size = size; + if(this_size > 4096) { + this_size = 4096; + } + must_write(g_sound_file_fd, &g_zero_buf[0], this_size); + size -= this_size; + } + return; + } + + size = 0; + if((num_samps + shm_pos) > SOUND_SHM_SAMP_SIZE) { + size = SOUND_SHM_SAMP_SIZE - shm_pos; + g_sound_file_bytes += (size * 4); + + must_write(g_sound_file_fd, (byte *)&(wptr[shm_pos]), 4*size); + shm_pos = 0; + num_samps -= size; + } + + g_sound_file_bytes += (num_samps * 4); + + must_write(g_sound_file_fd, (byte *)&(wptr[shm_pos]), 4*num_samps); +} + +void +show_c030_state(dword64 dfcyc) +{ + show_c030_samps(dfcyc, &(g_samp_buf[0]), 100); +} + +void +show_c030_samps(dword64 dfcyc, int *outptr, int num) +{ + int last; + int i; + + if(!g_num_c030_fsamps) { + return; + + } + printf("c030_fsamps[]: %d, dfcyc:%015llx\n", g_num_c030_fsamps, dfcyc); + + for(i = 0; i < g_num_c030_fsamps+2; i++) { + printf("%3d: %5.3f\n", i, g_c030_fsamps[i]); + } + + printf("Samples[] = %d\n", num); + + last = 0x0dadbeef; + for(i = 0; i < num; i++) { + if((last != outptr[0]) || (i == (num - 1))) { + printf("Samp[%4d]: %d\n", i, outptr[0]); + last = outptr[0]; + } + outptr += 2; + } +} + +int +sound_play_c030(dword64 dfcyc, dword64 dsamp, int *outptr_start, int num_samps) +{ + int *outptr; + dword64 dsamp_min; + float ftmp, fsampnum, next_fsampnum, fpercent; + int val, num, c030_state, c030_val, pos, sampnum, next_sampnum; + int doc_vol, min_i, mul; + int i, j; + + // Handle $C030 speaker clicks. Clicks for the past num_samps are + // in g_c030_fsamps[] giving the sample position when the click + // occurred. Turn this into samples, tracking multiple clicks per + // sample into an intermediate value. After 500ms of no clicks, + // transition the speakers from +/-20400 to 0, so it's idle. + // The speaker is affected by the DOC volume in g_doc_vol, like a real + // IIgs (this is used during the system beep). This code reacts + // to DOC volume changes when they happen by causing sound_play() + // to be called, so all samples with the old volume are played then + // new clicks are collected. + + num = g_num_c030_fsamps; // Number of clicks + if(!num) { + if(g_c030_val == 0) { + return 0; // Speaker is at rest + } + } + + pos = 0; + outptr = outptr_start; + c030_state = g_c030_state; + c030_val = g_c030_val; + // c030_val may be less than max due decay after 500ms. + // Always use it first, until speaker toggles, which should + // restore the full speaker range + doc_vol = g_doc_vol; + + if(!num) { + // No clicks. See if we should begin transitioning the + // speaker output to 0. I tried multiplying by .9999 but + // that seemed to take too long at the end, so just use a + // linear ramp down. Do this ramp based on the last click + // time, not VBL, since this is more consistent + + dsamp_min = g_c030_dsamp_last_toggle + (g_audio_rate >> 4); + if(dsamp >= dsamp_min) { + min_i = 0; + } else { + min_i = (int)(dsamp_min - dsamp); + } + mul = (2 * c030_state - 1) * doc_vol; + val = (c030_val * mul) >> 4; + for(i = 0; i < num_samps; i++) { + if(i >= min_i) { + if(c030_val > 4) { + c030_val -= 4; + } else { + c030_val = 0; + } + val = (c030_val * mul) >> 4; + } + outptr[0] = val; + outptr[1] = val; + outptr += 2; + } +#if 0 + printf("at %015llx, num_samps:%d val at start:%d, at end:%d, " + "min_i:%d\n", dfcyc, num_samps, g_c030_val, c030_val, + min_i); +#endif + g_c030_val = c030_val; + if(c030_val == 0) { + //printf("Speaker at rest\n"); + } + + return 1; + } + + g_c030_fsamps[num] = (float)(num_samps); + + num++; + fsampnum = g_c030_fsamps[0]; + sampnum = (int)fsampnum; + fpercent = (float)0.0; + i = 0; + + while(i < num) { + if(sampnum < 0 || sampnum > num_samps) { + halt_printf("play c030: [%d]:%f is %d, > %d\n", + i, fsampnum, sampnum, num_samps); + break; + } + + /* write in samples to all samps < me */ + val = ((2 * c030_state) - 1) * ((c030_val * doc_vol) >> 4); + if(num <= 1) { + printf("num:%d i:%d pos:%d, sampnum:%d c030_state:%d " + " at %015llx\n", num, i, pos, sampnum, + c030_state, dfcyc); + } + for(j = pos; j < sampnum; j++) { + outptr[0] = val; + outptr[1] = val; + outptr += 2; + pos++; + } + + if((sampnum >= num_samps) || ((i + 1) >= num)) { + break; // All done + } + + /* now, calculate me */ + fpercent = (float)0.0; + if(c030_state) { + fpercent = (fsampnum - (float)sampnum); + } + + c030_state = !c030_state; + c030_val = VAL_C030_POS_VAL; + g_c030_dsamp_last_toggle = dsamp + i; + + next_fsampnum = g_c030_fsamps[i+1]; + next_sampnum = (int)next_fsampnum; + // Handle all the changes during this one sample + while(next_sampnum == sampnum) { + if(c030_state) { + fpercent += (next_fsampnum - fsampnum); + } + i++; + fsampnum = next_fsampnum; + + if(i > num) { + break; // This should not happen! + } + next_fsampnum = g_c030_fsamps[i+1]; + next_sampnum = (int)next_fsampnum; + c030_state = !c030_state; + } + + if(c030_state) { + // add in time until next sample + ftmp = (float)(int)(fsampnum + (float)1.0); + fpercent += (ftmp - fsampnum); + } + + if((fpercent < (float)0.0) || (fpercent > (float)1.0)) { + halt_printf("fpercent: %d = %f\n", i, fpercent); + show_c030_samps(dfcyc, outptr_start, num_samps); + break; + } + + val = (int)((2*fpercent - 1) * ((c030_val * doc_vol) >> 4)); + outptr[0] = val; + outptr[1] = val; + outptr += 2; + pos++; + i++; + + sampnum = next_sampnum; + fsampnum = next_fsampnum; + } + + g_c030_state = c030_state; + g_c030_val = c030_val; + +#if 0 + if(g_sound_file_str) { + show_c030_samps(dfcyc, outptr_start, num_samps); + } +#endif + + // See if there are any entries >= fsampnum, copy them back down + // to the beginning of the array + pos = 0; + num--; + fsampnum = (float)num_samps; + while(i < num) { + g_c030_fsamps[pos] = g_c030_fsamps[i] - fsampnum; +#if 0 + printf("Copied [%d] %f to [%d] as %f\n", i, g_c030_fsamps[i], + pos, g_c030_fsamps[pos]); +#endif + i++; + pos++; + } + g_num_c030_fsamps = pos; + + return 1; +} + + +int g_sound_play_depth = 0; + +// sound_play(): forms the samples from the last sample time to the current +// time. Can be called anytime from anywhere. This is how KEGS handles +// dynamic sound changes (say, disabling an Ensoniq oscillator manually): +// when it's turned off, call sound_play() to play up to this moment, then +// the next time sound_play() is called, it will just know this osc is off +// So, on any sound-related state change, call sound_play(). + +void +sound_play(dword64 dfcyc) +{ + Ay8913 *ay8913ptr; + int *outptr, *outptr_start; + word32 *sndptr; + double last_dsamp, dsamp_now, dvolume, dsamps; + word32 uval1, uval0; + int val, val0, val1, pos, snd_buf_init, num_samps, num_pairs; + int sound_mask, ivol; + int i, j; + + g_num_snd_plays++; + if(g_sound_play_depth) { + halt_printf("Nested sound_play!\n"); + } + + g_sound_play_depth++; + + /* calc sample num */ + + dsamps = dfcyc * g_dsamps_per_dfcyc; + last_dsamp = g_last_sound_play_dsamp; + num_samps = (int)(dsamps - g_last_sound_play_dsamp); + + dsamp_now = last_dsamp + (double)num_samps; + + if(num_samps < 1) { + /* just say no */ + g_sound_play_depth--; + return; + } + + dbg_log_info(dfcyc, (word32)(dword64)dsamp_now, num_samps, 0x200); + + if(num_samps > MAX_SND_BUF) { + printf("num_samps: %d, too big!\n", num_samps); + g_sound_play_depth--; + return; + } + + outptr_start = &(g_samp_buf[0]); + outptr = outptr_start; + + snd_buf_init = sound_play_c030(dfcyc, (dword64)dsamp_now, outptr_start, + num_samps); + + snd_buf_init = doc_play(dfcyc, last_dsamp, dsamp_now, num_samps, + snd_buf_init, outptr_start); + + num_pairs = 0; + // Do Mockinboard channels + for(i = 0; i < 2; i++) { // Pair: 0 or 1 + ay8913ptr = &(g_mockingboard.pair[i].ay8913); + for(j = 0; j < 3; j++) { // Channels: A, B, or C + if((ay8913ptr->regs[8 + j] & 0x1f) == 0) { + continue; + } + num_pairs = 2; + g_last_mock_vbl_count = g_vbl_count; + break; + } + } + if((g_vbl_count - g_last_mock_vbl_count) < 120) { + // Keep playing for 2 seconds, to avoid some static issues + num_pairs = 2; + } + if(num_pairs) { + sound_mask = -1; + if(snd_buf_init == 0) { + sound_mask = 0; + snd_buf_init++; + } + outptr = outptr_start; + ivol = -((VAL_MOCK_RANGE * 3 / (8 * 15)) * g_doc_vol); + // Do 3/8 of range below 0, leaving 5/8 above 0 + for(i = 0; i < num_samps; i++) { + outptr[0] = (outptr[0] & sound_mask) + ivol; + outptr[1] = (outptr[1] & sound_mask) + ivol; + outptr += 2; + } + for(i = 0; i < 16; i++) { + dvolume = (g_doc_vol * VAL_MOCK_RANGE) / (15.0 * 3.0); + ivol = (int)(g_ay8913_ampl_factor[i] * dvolume); + g_mock_volume[i] = ivol; + } + } + for(i = 0; i < num_pairs; i++) { + if(g_mockingboard.disable_mask) { + printf("dsamp:%lf\n", dsamps); + } + + sound_mock_envelope(i, &(g_mock_env_vol[0]), num_samps, + &(g_mock_volume[0])); + sound_mock_noise(i, &(g_mock_noise_bytes[0]), num_samps); + for(j = 0; j < 3; j++) { + sound_mock_play(i, j, outptr_start, + &(g_mock_env_vol[0]), &(g_mock_noise_bytes[0]), + &(g_mock_volume[0]), num_samps); + } + } + + g_last_sound_play_dsamp = dsamp_now; + + outptr = outptr_start; + pos = g_sound_shm_pos; + sndptr = g_sound_shm_addr; + +#if 0 + printf("samps_left: %d, num_samps: %d\n", samps_left, num_samps); +#endif + + if(g_audio_enable != 0) { + if(snd_buf_init) { + /* convert sound buf */ + for(i = 0; i < num_samps; i++) { + val0 = outptr[0]; + val1 = outptr[1]; + val = val0; + if(val0 > 32767) { + val = 32767; + } + if(val0 < -32768) { + val = -32768; + } + uval0 = val & 0xffffU; + val = val1; + if(val1 > 32767) { + val = 32767; + } + if(val1 < -32768) { + val = -32768; + } + uval1 = val & 0xffffU; + outptr += 2; + +#if defined(__linux__) || defined(OSS) + /* Linux seems to expect little-endian */ + /* samples always, even on PowerPC */ +# ifdef KEGS_BIG_ENDIAN + sndptr[pos] = ((uval1 & 0xff) << 24) + + ((uval1 & 0xff00) << 8) + + ((uval0 & 0xff) << 8) + + ((uval0 >> 8) & 0xff); +# else + sndptr[pos] = (uval1 << 16) + (uval0 & 0xffff); +# endif +#else +# ifdef KEGS_BIG_ENDIAN + sndptr[pos] = (uval0 << 16) + uval1; +# else + sndptr[pos] = (uval1 << 16) + uval0; +# endif +#endif + pos++; + if(pos >= SOUND_SHM_SAMP_SIZE) { + pos = 0; + } + } + if(g_queued_nonsamps) { + /* force out old 0 samps */ + snddrv_send_sound(0, g_queued_nonsamps); + g_queued_nonsamps = 0; + } + if(g_sound_file_str) { + send_sound_to_file(g_sound_shm_addr, + g_sound_shm_pos, num_samps, 1); + } + g_queued_samps += num_samps; + } else { + /* move pos */ + pos += num_samps; + while(pos >= SOUND_SHM_SAMP_SIZE) { + pos -= SOUND_SHM_SAMP_SIZE; + } + if(g_sound_file_str) { + send_sound_to_file(0, g_sound_shm_pos, + num_samps, 0); + } + if(g_queued_samps) { + /* force out old non-0 samps */ + snddrv_send_sound(1, g_queued_samps); + g_queued_samps = 0; + } + g_queued_nonsamps += num_samps; + } + } + + g_sound_shm_pos = pos; + + if(g_audio_enable != 0) { + if(g_queued_samps >= (g_audio_rate/60)) { + snddrv_send_sound(1, g_queued_samps); + g_queued_samps = 0; + } + + if(g_queued_nonsamps >= (g_audio_rate/60)) { + snddrv_send_sound(0, g_queued_nonsamps); + g_queued_nonsamps = 0; + } + } + + g_last_sound_play_dsamp = dsamp_now; + + g_sound_play_depth--; +} + +void +sound_mock_envelope(int pair, int *env_ptr, int num_samps, int *vol_ptr) +{ + Ay8913 *ay8913ptr; + double dmul, denv_period, dusecs_per_samp; + dword64 env_dsamp, dsamp_inc; + word32 ampl, eff_ampl, reg13, env_val, env_period; + int i; + + // This routine calculates a fixed-point increment to apply + // to env_dsamp, where the envelope value is in bits 44:40 (bit + // 44 is to track the alternating waveform, 43:40 is the env_ampl). + // This algorithm does not properly handle dynamically changing the + // envelope period in the middle of a step. In the AY8913, the + // part counts up to the env_period, and if the period is changed + // to a value smaller than the current count, it steps immediately + // to the next step. This routine will wait for enough fraction + // to accumulate before stepping. At most, this can delay the step + // by almost the new count time (if the new period is smaller), but + // no more. I suspect this is not noticeable. + if(num_samps > MAX_MOCK_ENV_SAMPLES) { + halt_printf("envelope overflow!: %d\n", num_samps); + return; + } + + ay8913ptr = &(g_mockingboard.pair[pair].ay8913); + ampl = ay8913ptr->regs[8] | ay8913ptr->regs[9] | ay8913ptr->regs[10]; + if((ampl & 0x10) == 0) { + // No one uses the envelope + return; + } + + env_dsamp = ay8913ptr->env_dsamp; + env_period = ay8913ptr->regs[11] + (256 * ay8913ptr->regs[12]); + if(env_period == 0) { + denv_period = 0.5; // To match MAME + } else { + denv_period = (double)env_period; + } + dmul = (1.0 / 16.0) * (1 << 20) * (1 << 20); // (1ULL << 40) / 16.0 + // Calculate amount counter will count in one sample. + // inc_per_tick 62.5KHz tick: (1/env_period) + // inc_per_dfcyc: (1/(16*env_period)) + // inc_per_samp = inc_per_dfcyc * g_fcyc_per_samp + dusecs_per_samp = g_fcyc_per_samp / 65536.0; + dsamp_inc = (dword64)((dmul * dusecs_per_samp / denv_period)); + // Amount to inc per sample, fixed point, 40 bit frac + + reg13 = ay8913ptr->regs[13]; // "reg15", env ctrl + eff_ampl = 0; + for(i = 0; i < num_samps; i++) { + env_dsamp = (env_dsamp + dsamp_inc) & 0x9fffffffffffULL; + env_val = (env_dsamp >> 40) & 0xff; + eff_ampl = env_val & 0xf; + if((reg13 & 4) == 0) { + eff_ampl = 15 - eff_ampl; // not attack + } + if((reg13 & 8) && (reg13 & 2)) { + // continue and alternate + if(env_val & 0x10) { + eff_ampl = 15 - eff_ampl; + } + } + if(((reg13 & 8) == 0) && (env_val >= 0x10)) { + eff_ampl = 0; + ampl = 0; // Turn off envelope + env_dsamp |= (0x80ULL << 40); + } else if((reg13 & 1) && (env_val >= 0x10)) { + eff_ampl = ((reg13 >> 1) ^ (reg13 >> 2)) & 1; + eff_ampl = eff_ampl * 15; + ampl = eff_ampl; // Turn off envelope + env_dsamp |= (0x80ULL << 40); + } + *env_ptr++ = vol_ptr[eff_ampl & 0xf]; + } + ay8913ptr->env_dsamp = env_dsamp; +} + +void +sound_mock_noise(int pair, byte *noise_ptr, int num_samps) +{ + Ay8913 *ay8913ptr; + word32 ampl, mix, noise_val, noise_samp, noise_period, xor, samp_inc; + int doit; + int i; + + if(num_samps > MAX_MOCK_ENV_SAMPLES) { + halt_printf("noise overflow!: %d\n", num_samps); + return; + } + + ay8913ptr = &(g_mockingboard.pair[pair].ay8913); + doit = 0; + for(i = 0; i < 3; i++) { + ampl = ay8913ptr->regs[8 + i]; + mix = ay8913ptr->regs[7] >> i; + if((ampl != 0) && ((mix & 8) == 0)) { + doit = 1; + break; + } + } + if(!doit) { + // No channel looks at noise, don't bother + return; + } + + noise_val = ay8913ptr->noise_val; + noise_samp = ay8913ptr->noise_samp; + noise_period = (ay8913ptr->regs[6] & 0x1f); + noise_period = noise_period << 16; + samp_inc = (word32)(g_fcyc_per_samp / 16.0); + // Amount to inc per sample + if(noise_samp >= noise_period) { + // Period changed during sound, reset + noise_samp = noise_period; + } + for(i = 0; i < num_samps; i++) { + noise_samp += samp_inc; + if(noise_samp >= noise_period) { + // HACK: handle fraction + // 17-bit LFSR, algorithm from MAME:sound/ay8910.cpp + // val = val ^ (((val & 1) ^ ((val >> 3) & 1)) << 17) + xor = 0; + xor = (noise_val ^ (noise_val >> 3)) & 1; + noise_val = (noise_val ^ (xor << 17)) >> 1; + noise_samp -= noise_period; + } + noise_ptr[i] = noise_val & 1; + } + ay8913ptr->noise_samp = noise_samp; + ay8913ptr->noise_val = noise_val; +} + +int g_did_mock_print = 100; + +void +sound_mock_play(int pair, int channel, int *outptr, int *env_ptr, + byte *noise_ptr, int *vol_ptr, int num_samps) +{ + Ay8913 *ay8913ptr; + word32 ampl, mix, tone_samp, tone_period, toggle_tone; + word32 samp_inc, noise_val; + int out, ival, do_print; + int i; + + if((g_mockingboard.disable_mask >> ((pair * 3) + channel)) & 1) { + // This channel is disabled + return; + } + + ay8913ptr = &(g_mockingboard.pair[pair].ay8913); + ampl = ay8913ptr->regs[8 + channel] & 0x1f; + if(ampl == 0) { + return; + } + toggle_tone = ay8913ptr->toggle_tone[channel]; // 0 or 1 + mix = (ay8913ptr->regs[7] >> channel) & 9; + if(mix == 9) { + // constant tone: output will be ampl for this channel. + if(ampl & 0x10) { // Envelope! + // The envelope can make the tone, so must calculate it + } else { + // HACK: do nothing for now + return; + } + } + outptr += pair; // pair[1] is right + tone_samp = ay8913ptr->tone_samp[channel]; + tone_period = ay8913ptr->regs[2*channel] + + (256 * ay8913ptr->regs[2*channel + 1]); + tone_period = tone_period << 16; + samp_inc = (word32)(g_fcyc_per_samp / 8.0); + // Amount to inc per sample + do_print = 0; + if(g_mockingboard.disable_mask) { + printf("Doing %d samps, mix:%d, ampl:%02x\n", num_samps, mix, + ampl); + do_print = 1; + g_did_mock_print = 0; + } + if((num_samps > 500) && (g_did_mock_print == 0)) { + do_print = 1; + g_did_mock_print = 1; + printf("Start of %d sample, channel %d mix:%02x ampl:%02x " + "toggle_tone:%02x\n", num_samps, channel, mix, ampl, + toggle_tone); + printf(" tone_period:%08x, tone_samp:%08x, samp_inc:%08x\n", + tone_period, tone_samp, samp_inc); + } + if(tone_samp >= tone_period) { + // Period changed during sound, reset it + tone_samp = tone_period; + } + for(i = 0; i < num_samps; i++) { + tone_samp += samp_inc; + if(tone_samp >= tone_period) { + // HACK: handle toggling mid-sample... + toggle_tone ^= 1; + if(do_print) { + printf("i:%d tone_toggled to %d, tone_period:" + "%04x, pre tone_samp:%08x\n", i, + toggle_tone, tone_period, tone_samp); + } + tone_samp -= tone_period; + if(do_print) { + printf("post tone_samp:%08x\n", tone_samp); + } + } + noise_val = noise_ptr[i] & 1; + out = (toggle_tone || (mix & 1)) & + ((noise_val & 1) || (mix & 8)); + // Careful mix of || and & above... + ival = vol_ptr[ampl & 0xf]; + if(ampl & 0x10) { // Envelope + ival = env_ptr[i]; + } + *outptr += ival*out; + outptr += 2; + } + ay8913ptr->tone_samp[channel] = tone_samp; + ay8913ptr->toggle_tone[channel] = toggle_tone; +} + +word32 +sound_read_c030(dword64 dfcyc) +{ + sound_write_c030(dfcyc); + return float_bus(dfcyc); +} + +void +sound_write_c030(dword64 dfcyc) +{ + int num; + + num = g_num_c030_fsamps; + if(num >= MAX_C030_TIMES) { + halt_printf("Too many clicks per vbl: %d\n", num); + return; + } + + g_c030_fsamps[num] = (float)(dfcyc * g_dsamps_per_dfcyc - + g_last_sound_play_dsamp); + g_num_c030_fsamps = num + 1; + dbg_log_info(dfcyc, num, 0, 0xc030); + + doc_printf("touch c030, num this vbl: %04x\n", num); +} + diff --git a/gsplus/src/sound.h b/gsplus/src/sound.h new file mode 100644 index 0000000..01925e1 --- /dev/null +++ b/gsplus/src/sound.h @@ -0,0 +1,101 @@ +#ifdef INCLUDE_RCSID_C +#endif + +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#if !defined(_WIN32) && !defined(__CYGWIN__) +# include +# include +#endif + +#define SOUND_SHM_SAMP_SIZE (32*1024) + +#define SAMPLE_SIZE 2 +#define NUM_CHANNELS 2 +#define SAMPLE_CHAN_SIZE (SAMPLE_SIZE * NUM_CHANNELS) + +STRUCT(Doc_reg) { + double dsamp_ev; + double dsamp_ev2; + double complete_dsamp; + int samps_left; + word32 cur_acc; + word32 cur_inc; + word32 cur_start; + word32 cur_end; + word32 cur_mask; + int size_bytes; + int event; + int running; + int has_irq_pending; + word32 freq; + word32 vol; + word32 waveptr; + word32 ctl; + word32 wavesize; + word32 last_samp_val; +}; + +// Mockingboard contains two pairs. Each pair is a 6522 interfacing +// to an AY-8913 to generate sounds. Eacho AY-8913 contains 3 channels of +// sound. Model each pair separately. + +STRUCT(Mos6522) { + byte orb; + byte ora; + byte ddrb; + byte ddra; + word32 timer1_latch; + word32 timer1_counter; + word32 timer2_latch; + word32 timer2_counter; + byte sr; + byte acr; + byte pcr; + byte ifr; + byte ier; +}; + +STRUCT(Ay8913) { + byte regs[16]; + byte reg_addr_latch; + byte toggle_tone[3]; // Channel A,B,C: 0 = low, 1 = high + word32 tone_samp[3]; + word32 noise_val; + word32 noise_samp; + dword64 env_dsamp; +}; + +STRUCT(Mock_pair) { + Mos6522 mos6522; + Ay8913 ay8913; +}; + +STRUCT(Mockingboard) { + Mock_pair pair[2]; + word32 disable_mask; +}; + +/* prototypes for win32snd_driver.c functions */ +void win32snd_init(word32 *); +void win32snd_shutdown(void); +void child_sound_init_win32(void); +int win32_send_audio(byte *ptr, int size); + +/* Prototypes for macsnd_driver.c functions */ +int mac_send_audio(byte *ptr, int in_size); +void macsnd_init(); + +/* Prototypes for pulseaudio_driver.c functions */ +int pulse_audio_init(); +int pulse_audio_send_audio(byte *ptr, int in_size); +void pulse_audio_shutdown(void); + diff --git a/gsplus/src/sound_driver.c b/gsplus/src/sound_driver.c new file mode 100644 index 0000000..58bbc23 --- /dev/null +++ b/gsplus/src/sound_driver.c @@ -0,0 +1,546 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// Routines for managing sending sound samples to the hardware. The +// primary routines are snddrv_init() for initializing the sound hardware, +// and snddrv_send_sound() which calls the driver for the sound hardware +// in use to play samples. +// Linux forks a child process to manage /dev/dsp (so KEGS will not block) +// so the lowerlevel routines for all sound hardware start with child_(). + +#include "defc.h" +#include "sound.h" + +#if defined(__linux__) || defined(OSS) +# include +#endif + +#ifndef _WIN32 +# include +# include +#endif +#include + +#if defined(_WIN32) || defined(__CYGWIN__) || defined(MAC) +# define KEGS_CAN_FORK 0 +#else + // Linux, or other Unix, we may fork and run sound in the child +# define KEGS_CAN_FORK 1 +#endif + +extern int Verbose; + +extern int g_audio_rate; +extern int g_audio_enable; +extern double g_last_sound_play_dsamp; +extern word32 *g_sound_shm_addr; + +int g_preferred_rate = 48000; +int g_audio_socket = -1; +int g_bytes_written = 0; +int g_pulse_audio = 0; +int g_pipe_fd[2] = { -1, -1 }; +int g_pipe2_fd[2] = { -1, -1 }; + +#define ZERO_BUF_SIZE 2048 + +word32 g_snd_zero_buf[ZERO_BUF_SIZE]; + +#define ZERO_PAUSE_SAFETY_SAMPS (g_audio_rate >> 5) +#define ZERO_PAUSE_NUM_SAMPS (4*g_audio_rate) + +int g_zeroes_buffered = 0; +int g_zeroes_seen = 0; +int g_sound_paused = 0; +int g_childsnd_vbl = 0; +int g_childsnd_pos = 0; + +int child_sound_init_linux(void); + +void +snddrv_init() +{ + word32 *shmaddr; + int size, ret, use_shm; + + ret = 0; + if(ret) { // Avoid unused var warning + } + + g_zeroes_buffered = 0; + g_zeroes_seen = 0; + g_sound_paused = 0; + + g_childsnd_pos = 0; + g_childsnd_vbl = 0; + + if(g_audio_enable == 0) { + sound_set_audio_rate(g_preferred_rate); + return; + } + printf("snddrv_init, g_audio_enable:%d\n", g_audio_enable); + + size = SOUND_SHM_SAMP_SIZE * SAMPLE_CHAN_SIZE; + + use_shm = KEGS_CAN_FORK; +#ifdef PULSE_AUDIO + use_shm = 0; +#endif + if(!use_shm) { + /* windows and mac, and Linux Pulse Audio */ + shmaddr = malloc(size); + memset(shmaddr, 0, size); + g_sound_shm_addr = shmaddr; +#ifdef MAC + macsnd_init(); + return; +#endif +#ifdef _WIN32 + win32snd_init(shmaddr); + return; +#endif +#ifdef PULSE_AUDIO + ret = pulse_audio_init(shmaddr); + if(ret == 0) { + g_pulse_audio = 1; + return; // Success! + } + free(shmaddr); + g_sound_shm_addr = 0; + use_shm = 1; + // Otherwise, fall back on /dev/dsp +#endif + } + + if(use_shm) { + sound_child_fork(size); + } +} + +void +sound_child_fork(int size) +{ +#if KEGS_CAN_FORK + word32 *shmaddr; + int shmid, pid, tmp, ret; + int i; + + doc_printf("In sound_child_fork, size:%d\n", size); + shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777); + if(shmid < 0) { + printf("sound_init: shmget ret: %d, errno: %d\n", shmid, + errno); + exit(2); + } + + shmaddr = shmat(shmid, 0, 0); + tmp = (int)PTR2WORD(shmaddr); + if(tmp == -1) { + printf("sound_init: shmat ret: %p, errno: %d\n", shmaddr, + errno); + exit(3); + } + + ret = shmctl(shmid, IPC_RMID, 0); + if(ret < 0) { + printf("sound_init: shmctl ret: %d, errno: %d\n", ret, errno); + exit(4); + } + + g_sound_shm_addr = shmaddr; + printf("shmaddr: %p\n", shmaddr); + + fflush(stdout); + + /* prepare pipe so parent can signal child each other */ + /* pipe[0] = read side, pipe[1] = write end */ + ret = pipe(&g_pipe_fd[0]); + if(ret < 0) { + printf("sound_init: pipe ret: %d, errno: %d\n", ret, errno); + exit(5); + } + ret = pipe(&g_pipe2_fd[0]); + if(ret < 0) { + printf("sound_init: pipe ret: %d, errno: %d\n", ret, errno); + exit(5); + } + + + doc_printf("pipes: pipe_fd = %d, %d pipe2_fd: %d,%d\n", + g_pipe_fd[0], g_pipe_fd[1], g_pipe2_fd[0], g_pipe2_fd[1]); + fflush(stdout); + + pid = fork(); + switch(pid) { + case 0: + /* child */ + /* close stdin and write-side of pipe */ + close(0); + /* Close other fds to make sure X window fd is closed */ + for(i = 3; i < 100; i++) { + if((i != g_pipe_fd[0]) && (i != g_pipe2_fd[1])) { + close(i); + } + } + close(g_pipe_fd[1]); /*make sure write pipe closed*/ + close(g_pipe2_fd[0]); /*make sure read pipe closed*/ + child_sound_loop(g_pipe_fd[0], g_pipe2_fd[1], g_sound_shm_addr); + printf("Child sound loop returned\n"); + exit(0); + case -1: + /* error */ + printf("sound_init: fork ret: -1, errno: %d\n", errno); + exit(6); + default: + /* parent */ + /* close read-side of pipe1, and the write side of pipe2 */ + close(g_pipe_fd[0]); + close(g_pipe2_fd[1]); + doc_printf("Child is pid: %d\n", pid); + } + + parent_sound_get_sample_rate(g_pipe2_fd[0]); +#endif + if(size) { + // Avoid unused param warning + } +} + +void +parent_sound_get_sample_rate(int read_fd) +{ + word32 audio_rate, tmp; + int ret; + + ret = (int)read(read_fd, &audio_rate, 4); + if(ret != 4) { + printf("parent dying, could not get sample rate from child\n"); + printf("ret: %d, fd: %d errno:%d\n", ret, read_fd, errno); + exit(1); + } + ret = (int)read(read_fd, &tmp, 4); + if(ret != 4) { + printf("parent dying, could not get audio status from child\n"); + printf("ret: %d, fd: %d errno:%d\n", ret, read_fd, errno); + exit(1); + } + if(tmp == 0) { + g_audio_enable = 0; + printf("Failed to init Sound, turning off audio\n"); + } + close(read_fd); + + sound_set_audio_rate(audio_rate); +} + +void +snddrv_shutdown() +{ +#ifdef _WIN32 + win32snd_shutdown(); +#else + if((g_audio_enable != 0) && (g_pipe_fd[1] >= 0)) { + close(g_pipe_fd[1]); + } +#endif +#ifdef PULSE_AUDIO + if(g_pulse_audio) { + pulse_audio_shutdown(); + } +#endif +} + +void +snddrv_send_sound(int real_samps, int size) +{ + word32 tmp; + int ret, call_playit; + + if(g_audio_enable == 0) { + printf("Entered send_sound but audio off!\n"); + exit(2); + } + + if(real_samps) { + tmp = size + 0xa2000000; + } else { + tmp = size + 0xa1000000; + } + //doc_log_rout("send_sound", -1, g_last_sound_play_dsamp, + // (real_samps << 30) + size); + + call_playit = 0; +#if defined(MAC) || defined(_WIN32) + call_playit = 1; // Never fork child mac/windows +#endif + if(call_playit || g_pulse_audio) { + child_sound_playit(tmp); + return; + } + + /* Although this looks like a big/little-endian issue, since the */ + /* child is also reading an int, it just works with no byte swap */ + ret = (int)write(g_pipe_fd[1], &tmp, 4); + if(ret != 4) { + halt_printf("send_sound, wr ret: %d, errno: %d\n", ret, errno); + } +} + +void +child_sound_playit(word32 tmp) +{ + int size; + + size = tmp & 0xffffff; + + //printf("child_sound_playit: %08x\n", tmp); + + if((tmp >> 24) == 0xa2) { // play sound + if(g_zeroes_buffered) { + reliable_zero_write(g_zeroes_buffered); + } + + g_zeroes_buffered = 0; + g_zeroes_seen = 0; + + if((size + g_childsnd_pos) > SOUND_SHM_SAMP_SIZE) { + reliable_buf_write(g_sound_shm_addr, g_childsnd_pos, + SOUND_SHM_SAMP_SIZE - g_childsnd_pos); + size = (g_childsnd_pos + size) - SOUND_SHM_SAMP_SIZE; + g_childsnd_pos = 0; + } + + reliable_buf_write(g_sound_shm_addr, g_childsnd_pos, size); + + if(g_sound_paused) { + printf("Unpausing sound, zb: %d\n", g_zeroes_buffered); + g_sound_paused = 0; + } + + } else if((tmp >> 24) == 0xa1) { // play zeroes + if(g_sound_paused) { + if(g_zeroes_buffered < ZERO_PAUSE_SAFETY_SAMPS) { + g_zeroes_buffered += size; + } + } else { + /* not paused, send it through */ + g_zeroes_seen += size; + + reliable_zero_write(size); + + if(g_zeroes_seen >= ZERO_PAUSE_NUM_SAMPS) { + printf("Pausing sound\n"); + g_sound_paused = 1; + } + } + } else { + printf("tmp received bad: %08x\n", tmp); + exit(3); + } + + g_childsnd_pos += size; + while(g_childsnd_pos >= SOUND_SHM_SAMP_SIZE) { + g_childsnd_pos -= SOUND_SHM_SAMP_SIZE; + } + + g_childsnd_vbl++; + if(g_childsnd_vbl >= 60) { + g_childsnd_vbl = 0; +#if 0 + printf("sound bytes written: %06x\n", g_bytes_written); +#endif + g_bytes_written = 0; + } +} + +void +reliable_buf_write(word32 *shm_addr, int pos, int size) +{ + byte *ptr; + int ret; + + if(size < 1 || pos < 0 || pos > SOUND_SHM_SAMP_SIZE || + size > SOUND_SHM_SAMP_SIZE || + (pos + size) > SOUND_SHM_SAMP_SIZE) { + printf("reliable_buf_write: pos: %04x, size: %04x\n", + pos, size); + exit(1); + } + + ptr = (byte *)&(shm_addr[pos]); + size = size * 4; + + while(size > 0) { + ret = child_send_samples(ptr, size); + + if(ret < 0) { + printf("audio write, errno: %d %s\n", errno, + strerror(errno)); + exit(1); + } + size = size - ret; + ptr += ret; + g_bytes_written += ret; + } + +} + +void +reliable_zero_write(int amt) +{ + int len; + + while(amt > 0) { + len = MY_MIN(amt, ZERO_BUF_SIZE); + reliable_buf_write(g_snd_zero_buf, 0, len); + amt -= len; + } +} + + +int +child_send_samples(byte *ptr, int size) +{ +#ifdef _WIN32 + return win32_send_audio(ptr, size); +#else +# ifdef MAC + return mac_send_audio(ptr, size); +# else +# ifdef PULSE_AUDIO + if(g_pulse_audio) { + return pulse_audio_send_audio(ptr, size); + } +# endif + return (int)write(g_audio_socket, ptr, size); +# endif +#endif +} + +// child_sound_loop(): used by Linux child process as the main loop, to read +// from pipe to get sample info every VBL, and use shm_addr to get samples +void +child_sound_loop(int read_fd, int write_fd, word32 *shm_addr) +{ + word32 tmp, did_init; + int ret, ret1, ret2; + + doc_printf("Child pipe fd: %d, shm_addr:%p\n", read_fd, shm_addr); + + g_audio_rate = g_preferred_rate; + + did_init = 0; +#if defined(__linux__) || defined(OSS) + did_init = child_sound_init_linux(); +#endif + + tmp = g_audio_rate; + ret1 = (int)write(write_fd, &tmp, 4); + tmp = did_init; + ret2 = (int)write(write_fd, &tmp, 4); + if((ret1) != 4 || (ret2 != 4)) { + printf("Unable to send back audio rate to parent\n"); + printf("ret1: %d,%d fd: %d, errno:%d\n", ret1, ret2, write_fd, + errno); + exit(1); + } + doc_printf("Wrote to fd %d the audio rate\n", write_fd); + + close(write_fd); + + while(1) { + errno = 0; + ret = (int)read(read_fd, &tmp, 4); + if(ret <= 0) { + printf("child dying from ret: %d, errno: %d\n", + ret, errno); + break; + } + + child_sound_playit(tmp); + } + + close(g_audio_socket); + + exit(0); +} + + +#if defined(__linux__) || defined(OSS) +int +child_sound_init_linux() +{ + int stereo, sample_size, rate, fmt, ret; + + g_audio_socket = open("/dev/dsp", O_WRONLY, 0); + if(g_audio_socket < 0) { + printf("open /dev/dsp failed, ret: %d, errno:%d\n", + g_audio_socket, errno); + return 0; + } + +#if 0 + fragment = 0x00200009; + ret = ioctl(g_audio_socket, SNDCTL_DSP_SETFRAGMENT, &fragment); + if(ret < 0) { + printf("ioctl SETFRAGEMNT failed, ret:%d, errno:%d\n", + ret, errno); + return 0; + } +#endif + + sample_size = 16; + ret = ioctl(g_audio_socket, SNDCTL_DSP_SAMPLESIZE, &sample_size); + if(ret < 0) { + printf("ioctl SNDCTL_DSP_SAMPLESIZE failed, ret:%d, errno:%d\n", + ret, errno); + return 0; + } + +#ifdef KEGS_BIG_ENDIAN + fmt = AFMT_S16_BE; +#else + fmt = AFMT_S16_LE; +#endif + ret = ioctl(g_audio_socket, SNDCTL_DSP_SETFMT, &fmt); + if(ret < 0) { + printf("ioctl SNDCTL_DSP_SETFMT failed, ret:%d, errno:%d\n", + ret, errno); + return 0; + } + + stereo = 1; + ret = ioctl(g_audio_socket, SNDCTL_DSP_STEREO, &stereo); + if(ret < 0) { + printf("ioctl SNDCTL_DSP_STEREO failed, ret:%d, errno:%d\n", + ret, errno); + return 0; + } + + rate = g_audio_rate; + ret = ioctl(g_audio_socket, SNDCTL_DSP_SPEED, &rate); + if(ret < 0) { + printf("ioctl SNDCTL_DSP_SPEED failed, ret:%d, errno:%d\n", + ret, errno); + return 0; + } + if(ret > 0) { + rate = ret; /* rate is returned value */ + } + if(rate < 8000) { + printf("Audio rate of %d which is < 8000!\n", rate); + return 0; + } + g_audio_rate = rate; + + printf("Sound initialized\n"); + return 1; +} +#endif diff --git a/gsplus/src/style_check b/gsplus/src/style_check new file mode 100644 index 0000000..1b6b554 --- /dev/null +++ b/gsplus/src/style_check @@ -0,0 +1,147 @@ +#!/usr/bin/perl -w +# $KmKId: style_check,v 1.1 2020-06-14 02:52:13+00 kentd Exp $ + +# Perl script to check for coding conventions +use English; + +$some_bad = 0; + +while($#ARGV >= 0) { + $file = shift; + $check_spaces = 0; + if($file =~ /\.c$/) { + $check_spaces = 1; + } elsif($file =~ /\.s$/) { + $check_spaces = 1; + } elsif($file =~ /\.k$/) { + $check_spaces = 1; + } elsif($file =~ /\.h$/) { + $check_spaces = 1; + if($file =~ /^protos.*.h$/) { + next; # skip global_names.h + } + if($file =~ /^global_names.h$/) { + next; # skip global_names.h + } + if($file =~ /^knobs.h$/) { + next; # skip global_names.h + } + } else { + next; # skip this file + } + print "Style check: $file\n"; + if(-x $file) { + print "File mode is executable\n"; + $some_bad++; + } + open(FILE, "<$file") || die "Open: $file: $1\n"; + $line_num = 1; + foreach $line () { + chomp($line); + $ign_tab_space = 0; + if($line =~ m:^[/\t]+{.*}:) { + $ign_tab_space = 1; + } + $len = 0; + $prev = 0; + $pprev = 0; + $bad = 0; + $last_tab = -10; + if($check_spaces) { + if($line =~ / $/ || $line =~ / $/) { + print "Line ends in tab or space\n"; + $bad++; + } + } + if($line =~ /\r$/) { # Line ends with Ctrl-M + print "Windows linebreak detected\n"; + $bad = 101; + } + while($line =~ m/^([^"]*)("[^"]*")(.*)$/) { + # Convert text in strings to '-' to avoid space checks + $prev = $1; + $post = $3; + $quot = &dotify($2); + $line = $prev . $quot . $post; + } + while($line =~ m:^(.*)//(.*)$:) { + # Convert text in comments to '-' to avoid space checks + $prev = $1; + $quot = &dotify($2); + $line = $prev . "::" . $quot; + } + @chars = split(//, $line); + foreach $char (@chars) { + $len++; + if(!$check_spaces) { + # do nothing + } elsif($char eq "\t") { + $len = (($len + 7) >> 3) * 8; + if($prev eq ' ') { + print "Space followed by tab\n"; + $bad++; + } + $last_tab = $len; + } elsif($char eq " ") { + if($prev eq "\t" && !$ign_tab_space) { + print "Tab followed by space\n"; + $bad++; + } + if($prev eq " " && $pprev eq " " && + (($len - $last_tab) > 4)) { + print "Too many spaces\n"; + $bad++; + last; + } + } + $pprev = $prev; + $prev = $char + } + if($check_spaces) { + if(($len > 80) && ($line_num > 2)) { + print "Line more than 80 columns\n"; + $bad++; + } + #print "line $line has len $len\n"; + } + if($bad) { + $some_bad++; + print "...at line $line_num in file $file\n"; + if($some_bad > 20) { + die "Too many style errors\n"; + } + if($bad >= 100) { + next; # Skip to next file + } + } + $line_num++; + } +} + +if($some_bad) { + die "Style errors\n"; +} + +exit 0; + +sub +dotify +{ + my ($str) = @_; + my @chars = (); + my @outchars = (); + my ($char, $result); + + @chars = split(//, $str); + @outchars = (); + foreach $char (@chars) { + if($char ne "\t") { + $char = "-"; + } + push(@outchars, $char); + } + $result = join('', @outchars); + #print "Old quote :$str:\n"; + #print "New quote :$result:\n"; + return $result; +} diff --git a/gsplus/src/undeflate.c b/gsplus/src/undeflate.c new file mode 100644 index 0000000..8e0591d --- /dev/null +++ b/gsplus/src/undeflate.c @@ -0,0 +1,1450 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// This file has routines for the undeflate uncompression algorithm for +// gzip/zip, and routines for reading .zip files. + +// Based on https://www.ietf.org/rfc/rfc1951.txt for Deflate algorithm, +// and https://www.ietf.org/rfc/rfc1952.txt for gzip file format. + +// .zip file format from: +// https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.3.TXT + +#include "defc.h" + +// FILE *g_outf = 0; + +#define LENGTH_ENCODED 0xffffff444449ULL + // LENGTH_ENCODED encodes the first table of section 3.2.5 for + // fixed Huffman: 257-264 = 0 extra bits, length=3-10 (so 8 entries) + // then 265-268 = 1 extra bit, length=11... (so 4 entries), etc. + // which is encoded in each nibble of this word. Code 285 (entry 29) + // has extra_bits=0, indicated by the encoded nibble being 0xf. +#define DIST_ENCODED 0xff22222222222224ULL + // DIST_ENCODED encodes the second table of section 3.2.5 for the + // fixed Huffman table: Codes 0-3 have 0 extra bits, dist=1,2,3,4 + // (so 4 entries), codes 4,5 have 1 extra bit, dest=5... (so 2 + // entries), etc. 0xf indicates an invalid entry + +word32 g_undeflate_fixed_len_tab[512+1]; + // extrabits << 20 | bits << 16 | len/literal +word32 g_undeflate_fixed_dist_tab[32+1]; + // extrabits << 20 | bits << 16 | distance +word32 g_undeflate_length_tab[32+1]; // extra_bits << 20 | len +word32 g_undeflate_dist_tab[32+1]; // extra_bits << 20 | dist +word32 g_undeflate_bit_rev[512]; +word32 g_undeflate_lencode_positions[19] = +{ 0x200310, 0x300311, 0x700b12, + 0x100, 0x108, 0x107, 0x109, 0x106, 0x10a, 0x105, 0x10b, + 0x104, 0x10c, 0x103, 0x10d, 0x102, 0x10e, 0x101, 0x10f }; +word32 g_undeflate_lencode_tab[128 + 1]; +word32 *g_undeflate_dynamic_tabptr = 0; +word32 g_undeflate_dynamic_bits = 0; +word32 *g_undeflate_dynamic_dist_tabptr = 0; +word32 g_undeflate_dynamic_dist_bits = 0; + +void * +undeflate_realloc(void *ptr, dword64 dsize) +{ + if((size_t)dsize != dsize) { + return 0; + } + return realloc(ptr, (size_t)dsize); +} + +void * +undeflate_malloc(dword64 dsize) +{ + if((size_t)dsize != dsize) { + return 0; + } + return malloc((size_t)dsize); +} + +void +show_bits(unsigned *llptr, int nl) +{ + int i; + + fprintf(stdout, "Showing %03x bits entries\n", nl); + for(i = 0; i < nl; i++) { + fprintf(stdout, "%03x: %03x\n", i, (llptr[i] >> 16) & 0xf); + } +} + +void +show_huftb(unsigned *tabptr, int bits) +{ + unsigned char seen[512]; + word32 entry, code, val; + int i, j; + + printf("Showing hufftab of %d bits\n", bits); + + for(i = 0; i < 256+32; i++) { + seen[i] = 0; + } + for(i = 0; i < (1 << bits); i++) { + entry = tabptr[i]; + code = entry & 0xff; + if((entry >> 24) & 1) { + val = entry & 0xfff0ffffUL; + for(j = 0; j < 32; j++) { + if(val == g_undeflate_length_tab[j]) { + code = 256 + j; + break; + } + } + if(code < 256) { + printf("entry %08x (%08x) not found, [0]=%08x " + "[1]=%08x\n", entry, val, + g_undeflate_length_tab[0], + g_undeflate_length_tab[1]); + code = 256 + 31; + } + } + if(!seen[code]) { + printf("code %03x has bits:%d huffcode:%04x\n", code, + (entry >> 16) & 0xf, i); + seen[code] = 1; + } + } +} + +void +undeflate_init_len_dist_tab(word32 *tabptr, dword64 drepeats, word32 start) +{ + word32 pos, repeats, extra_bits; + int i; + + // Initializes g_undeflate_length_tab[] and g_underflate_dist_tab[] + // printf("undeflate len_dist_tab repeats:%016llx\n", drepeats); + pos = 0; + extra_bits = 0; + while(pos < 30) { + repeats = drepeats & 0xf; + drepeats = drepeats >> 4; + if(repeats == 0xf) { + // Special handling for code=285 (pos=29) + extra_bits = 0; + start--; // 258, not 259 + repeats = 1; + } + for(i = 0; i < (int)repeats; i++) { + tabptr[pos] = start | (extra_bits << 20) | (1 << 24); + //printf("Set table[%d]=%08x (%d) i:%d out of %d\n", + // pos, tabptr[pos], start, i, repeats); + pos++; + start += (1 << extra_bits); + } + extra_bits++; + } +} + +void +undeflate_init_bit_rev_tab(word32 *tabptr, int num) +{ + word32 pos, val; + int i, j; + + // Initializes g_undeflate_bit_rev[] + for(i = 0; i < num; i++) { + pos = i; + val = 0; + for(j = 0; j < 9; j++) { + val = (val << 1) | (pos & 1); + pos = pos >> 1; + } + tabptr[i] = val; + // printf("Bit reverse[%03x]=%03x\n", i, val); + } +} + +word32 +undeflate_bit_reverse(word32 val, word32 bits) +{ + word32 new_val, val2, shift; + + new_val = g_undeflate_bit_rev[val & 0x1ff]; // at most 9 bits + shift = 9 - bits; + if(bits <= 9) { + return new_val >> shift; + } else if(bits <= 18) { + shift += 9; // bits:10->shift=8, bits:11->shift=7,.. + val2 = g_undeflate_bit_rev[(val >> 9) & 0x1ff] >> shift; + shift = bits - 9; + return (new_val << shift) | val2; + } + printf("Cannot reverse %08x bits:%d!\n", val, bits); + return 0; +} + +word32 +undeflate_calc_crc32(byte *bptr, word32 len) +{ + word32 crc, c, xor; + int i; + + // Old version, don't use other than for testing purposes. + // Use woz_calc_crc32() instead. + + // Generate CCITT-32 CRC, with remainder initialized to -1 and return + // the complement of the CRC value + // This is slow--but it doesn't matter for KEGS, where the images + // are generally only 800KB max. + crc = (word32)-1; + while(len != 0) { + c = *bptr++; + len--; + for(i = 0; i < 8; i++) { + xor = 0; + if((crc ^ c) & 1) { + xor = 0xedb88320UL; + } + crc = (crc >> 1) ^ xor; + c = c >> 1; + } + } + return (~crc); +} + +byte * +undeflate_ensure_dest_len(Disk *dsk, byte *ucptr, word32 len) +{ + byte *raw_ptr; + dword64 raw_dsize, dimage_size; + word32 new_image_size; + + raw_dsize = dsk->raw_dsize; + dimage_size = dsk->dimage_size; + if(ucptr) { + new_image_size = (word32)(ucptr - dsk->raw_data); + if(new_image_size < dimage_size) { + printf("ucptr moved backwards!\n"); + return 0; + } + if((new_image_size >> 31) != 0) { + printf("Output file > 2GB, failing\n"); + return 0; + } + dimage_size = new_image_size; + dsk->dimage_size = dimage_size; + } + if(dimage_size > raw_dsize) { + printf("dimage_size %08llx overflowed raw_dsize %08llx\n", + dimage_size, raw_dsize); + return 0; + } + if((dimage_size + len) > raw_dsize) { + raw_dsize = ((dimage_size + len) * 3ULL) / 2; + raw_ptr = undeflate_realloc(dsk->raw_data, raw_dsize); + //printf("Did realloc to %08x, new new_data:%p, was %p\n", + // raw_size, raw_ptr, dsk->raw_data); + if(raw_ptr == 0) { + printf("undeflate realloc failed\n"); + free(dsk->raw_data); + dsk->raw_data = 0; + return 0; + } + dsk->raw_data = raw_ptr; + dsk->raw_dsize = raw_dsize; + } +#if 0 + printf("undeflate_ensure_dest_len will ret %p, dsk->raw_data:%p, " + "image_size:%08llx, raw_dsize:%08llx\n", + dsk->raw_data + dimage_size, dsk->raw_data, dimage_size, + raw_dsize); +#endif + return dsk->raw_data + dimage_size; +} + +void +undeflate_add_tab_code(word32 *tabptr, word32 tabsz_lg2, word32 code, + word32 entry) +{ + word32 rev_code, bits, pos, tab_size; + int num; + int i; + + if(tabsz_lg2 > 15) { + printf("tabsz_lg2: %04x is not supported\n", tabsz_lg2); + return; + } + tab_size = 1 << tabsz_lg2; + rev_code = 0; + bits = (entry >> 16) & 0xf; + rev_code = undeflate_bit_reverse(code, bits); + if(rev_code >= tab_size) { + printf("rev_code:%04x out of range for entry %08x\n", rev_code, + entry); + tabptr[tab_size] = 1; + return; + } + num = 1 << (tabsz_lg2 - bits); + if(num < 0) { + printf("num %d out of range for entry %08x\n", num, entry); + tabptr[tab_size] = 1; + return; + } + for(i = 0; i < num; i++) { + pos = rev_code | (i << bits); + if(tabptr[pos] != 0) { + printf("Overwriting old [%04x]=%08x with %08x\n", pos, + tabptr[pos], entry); + tabptr[tab_size] = 1; + } +#if 0 + if(i >= 0) { + printf("Set code tab[%04x]=%08x (code:%04x)\n", pos, + entry, save_code); + } +#endif + tabptr[pos] = entry; + } +} + +word32 * +undeflate_init_fixed_tabs() +{ + word32 *tabptr; + int i; + + tabptr = &(g_undeflate_fixed_len_tab[0]); + // Init g_undeflate_fixed_len_tab[] for the fixed Huffman code + for(i = 0; i < 513; i++) { + tabptr[i] = 0; + } + // printf("Add fixed_len_tab for literals 0 - 143\n"); + for(i = 0; i < 144; i++) { + undeflate_add_tab_code(tabptr, 9, 0x30 + i, (8 << 16) | i); + } + // printf("Add fixed_len_tab for literals 144 - 255\n"); + for(i = 144; i < 256; i++) { + undeflate_add_tab_code(tabptr, 9, 0x190 + i - 144, + (9 << 16) | i); + } + // printf("Add fixed_len_tab for length codes 256 - 279\n"); + for(i = 256; i < 280; i++) { + // printf("code: %03x fixed_len_tab[%03x]=%08x\n", i, i - 256, + // g_undeflate_length_tab[i - 256]); + undeflate_add_tab_code(tabptr, 9, 0 + i - 256, + (7 << 16) | g_undeflate_length_tab[i - 256]); + } + // printf("Add fixed_len_tab for length codes 280 - 287\n"); + for(i = 280; i < 288; i++) { + undeflate_add_tab_code(tabptr, 9, 0xc0 + i - 280, + (8 << 16) | g_undeflate_length_tab[i - 256]); + } + if(tabptr[512]) { + return 0; + } + + // And init g_undeflate_fixed_dist_tab[] + tabptr = &(g_undeflate_fixed_dist_tab[0]); + for(i = 0; i < 33; i++) { + tabptr[i] = 0; + } + // printf("Add fixed_dist_tab for codes 0 - 29\n"); + for(i = 0; i < 30; i++) { + undeflate_add_tab_code(tabptr, 5, i, + (5 << 16) | g_undeflate_dist_tab[i]); + } + + if(tabptr[32]) { + return 0; + } + return tabptr; +} + +word32 * +undeflate_init_tables() +{ + undeflate_init_len_dist_tab(&(g_undeflate_length_tab[0]), + LENGTH_ENCODED, 2); + // code=257 has length 3, but the first entry is really code=256 + // so set 256 to length=2 + undeflate_init_len_dist_tab(&(g_undeflate_dist_tab[0]), DIST_ENCODED, + 1); + undeflate_init_bit_rev_tab(&(g_undeflate_bit_rev[0]), 512); + // undeflate_check_bit_reverse(); + return undeflate_init_fixed_tabs(); +} + +void +undeflate_free_tables() +{ + free(g_undeflate_dynamic_tabptr); + g_undeflate_dynamic_tabptr = 0; + free(g_undeflate_dynamic_dist_tabptr); + g_undeflate_dynamic_dist_tabptr = 0; +} + +void +undeflate_check_bit_reverse() +{ + word32 rev, tmp, checked; + int i, j, bits; + + // Check bit-reverse function. Reverse all values from 0-32767 + checked = 0; + for(bits = 1; bits <= 16; bits++) { + // printf("Checking bit reverse bits=%d\n", bits); + for(i = 0; i < 65536; i++) { + if(i >= (1 << bits)) { + break; + } + tmp = i; + rev = 0; + for(j = 0; j < bits; j++) { + rev = (rev << 1) | (tmp & 1); + tmp = tmp >> 1; + } + tmp = undeflate_bit_reverse(i, bits); + if(tmp != rev) { + printf("Reverse %04x bits:%d ret:%04x, " + "exp:%04x\n", i, bits, tmp, rev); + exit(2); + } + checked++; + } + } + printf("Checked %08x values\n", checked); +} + +word32 * +undeflate_build_huff_tab(word32 *tabptr, word32 *entry_ptr, word32 len_size, + word32 *bl_count_ptr, int max_bits) +{ + word32 next_code[16]; + word32 code, tab_size, bits, entry; + int i; + + tab_size = (1 << max_bits); + if(max_bits > 15) { + printf("max_bits: %d out of range\n", max_bits); + return 0; + } + next_code[0] = 0; + bl_count_ptr[0] = 0; // Force number of 0-bit lengths to 0 + code = 0; + // printf("build_huff_tab, max_bits:%d, tab_size:%08x\n", max_bits, + // tab_size); + for(i = 1; i <= max_bits; i++) { + // printf("bl_count[%d] = %03x\n", i - 1, bl_count_ptr[i-1]); + code = (code + bl_count_ptr[i - 1]) << 1; + next_code[i] = code; + // printf("Set next_code[%d] = %03x\n", i, code); + } + for(i = 0; i < (int)tab_size; i++) { + tabptr[i] = 0; + } + tabptr[tab_size] = 0; + + for(i = 0; i < (int)len_size; i++) { + entry = entry_ptr[i]; + bits = (entry >> 16) & 0xf; + //printf("i:%03x, bits:%d, entry:%08x\n", i, bits, entry); + if(!bits) { + continue; + } + code = next_code[bits]++; + //printf("Set tab code:%03x = %08x\n", code, entry); + undeflate_add_tab_code(tabptr, max_bits, code, entry); + } + + // printf("All done, returning tabptr\n"); + return tabptr; +} + +word32 * +undeflate_dynamic_table(byte *cptr, word32 *bit_pos_ptr, byte *cptr_base) +{ + word32 code_list[256+32+32+1]; + word32 len_codes[19]; + word32 bl_count[19], bl_count_dist[16]; + word32 *tabptr, *tabptr_dist; + byte *cptr_start; + word32 bit_pos, val, hlit, hdist, hclen, pos, max_bits, code_pos; + word32 total_codes_needed, repeat, mask, entry, bits; + word32 max_length_bits, max_distance_bits, extra_bits; + int i; + + // This is compressed compressed huffman lengths. First + // get the length codes, then get the actual lengths + // Get 14 bits, which always fits in 3 bytes + bit_pos = *bit_pos_ptr; + cptr_start = cptr; + val = (cptr[0] + (cptr[1] << 8) + (cptr[2] << 16)) >> bit_pos; + hlit = (val & 0x1f) + 257; // 257 - 288 + hdist = ((val >> 5) & 0x1f) + 1; + hclen = (val >> 10) & 0xf; +#if 0 + printf("At +%06x, bit:%d, hlit:%02x hdist:%02x, hclen:%02x\n", + (word32)(cptr - cptr_base), bit_pos, hlit, hdist, hclen); +#endif + if(cptr_base) { + // Avoid unused parameter warning + } + bit_pos += 14; + cptr += (bit_pos >> 3); + bit_pos = bit_pos & 7; + for(i = 0; i < 19; i++) { + len_codes[i] = 0; + bl_count[i] = 0; + } + hclen += 4; // 19*3 = 57 bits, at most + max_bits = 0; + for(i = 0; i < (int)hclen; i++) { + val = ((cptr[0] + (cptr[1] << 8)) >> bit_pos) & 7; + entry = g_undeflate_lencode_positions[i]; + entry = entry & (~0xf0000); // clear bits from entry + pos = entry & 0x1f; + len_codes[pos] = entry | (val << 16); + // printf("len_codes[%d]=%08x\n", pos, len_codes[pos]); + bl_count[val]++; + if(val > max_bits) { + max_bits = val; + } + // printf("Num bits for len code %02x = %d\n", pos, val); + bit_pos += 3; + cptr += (bit_pos >> 3); + bit_pos = bit_pos & 7; + } + // Build huffman table + tabptr = undeflate_build_huff_tab(&(g_undeflate_lencode_tab[0]), + &(len_codes[0]), 19, &(bl_count[0]), max_bits); + if(tabptr == 0) { + printf("Bad table\n"); + return 0; + } + + // Now we've made the table in tabptr. Read the length codes now + total_codes_needed = hlit + hdist; + // printf("Getting %04x total codes\n", total_codes_needed); + code_pos = 0; + mask = (1 << max_bits) - 1; + if(total_codes_needed > (256+32+32)) { + printf("total_codes_needed high: %04x\n", total_codes_needed); + return 0; + } + for(i = 0; i < 16; i++) { + bl_count[i] = 0; + bl_count_dist[i] = 0; + } + while(code_pos < total_codes_needed) { + pos = (cptr[0] | (cptr[1] << 8)) >> bit_pos; + pos = pos & mask; + entry = tabptr[pos & mask]; +#if 0 + printf("At +%06x, bit:%d: Raw code: %02x, entry:%08x\n", + (word32)(cptr - cptr_base), bit_pos, pos, entry); +#endif + val = entry & 0x1f; + + bits = (entry >> 16) & 7; + extra_bits = (entry >> 20) & 7; + repeat = (entry >> 8) & 0xf; + entry = (val << 16); // Set bits + bit_pos += bits; + cptr += (bit_pos >> 3); + bit_pos = bit_pos & 7; + pos = (cptr[0] | (cptr[1] << 8)) >> bit_pos; +#if 0 + printf("At +%06x, bit:%d: Raw pos:%04x\n", + (word32)(cptr - cptr_base), bit_pos, pos); +#endif + pos = pos & ((1 << extra_bits) - 1); + repeat = repeat + pos; + bit_pos += extra_bits; + cptr += (bit_pos >> 3); + bit_pos = bit_pos & 7; + if(!repeat) { + printf("Bad repeat value\n"); + return 0; + } + if(val >= 0x10) { + entry = 0; + if(val == 0x10) { // Repeat prev entry + entry = code_list[code_pos - 1]; + if(!code_pos) { + printf("Got repeat code 0x10 at 0!\n"); + return 0; + } + } + } + for(i = 0; i < (int)repeat; i++) { + code_list[code_pos] = entry; + // printf("Added code_list[%03x] = %08x\n", code_pos, + // entry); + code_pos++; + } + } + + // Fix lengths and literals + max_length_bits = 0; + for(i = 0; i < (int)hlit; i++) { + entry = code_list[i]; + bits = (entry >> 16) & 0xf; + bl_count[bits]++; + if(i >= 256) { + entry |= g_undeflate_length_tab[i - 256]; + } else { + entry |= i; + } + code_list[i] = entry; + if(bits > max_length_bits) { + max_length_bits = bits; + } + } + + // Fix distances + max_distance_bits = 0; + for(i = 0; i < (int)hdist; i++) { + entry = code_list[i + hlit]; + bits = (entry >> 16) & 0xf; + bl_count_dist[bits]++; + entry |= g_undeflate_dist_tab[i]; + code_list[i + hlit] = entry; + if(bits > max_distance_bits) { + max_distance_bits = bits; + } + } + if(code_pos != total_codes_needed) { + printf("Got %03x codes, needed %03x codes\n", code_pos, + total_codes_needed); + return 0; + } + // printf("max_length_bits: %d, max_distance_bits: %d\n", + // max_length_bits, max_distance_bits); + tabptr = g_undeflate_dynamic_tabptr; + if(!tabptr) { + tabptr = malloc(sizeof(word32)*((1 << 15) + 1)); + g_undeflate_dynamic_tabptr = tabptr; + // printf("malloc literal table\n"); + } + g_undeflate_dynamic_bits = max_length_bits; + //printf("Building literal/length table, %d entries, %d bits\n", hlit, + // max_length_bits); + //show_bits(&(code_list[0]), hlit); + + tabptr = undeflate_build_huff_tab(tabptr, &(code_list[0]), + hlit, &(bl_count[0]), max_length_bits); + if(tabptr == 0) { + printf("Building literal table failed\n"); + return 0; + } + //show_huftb(tabptr, max_length_bits); + + tabptr_dist = g_undeflate_dynamic_dist_tabptr; + if(!tabptr_dist) { + tabptr_dist = malloc(sizeof(word32) * ((1 << 15) + 1)); + g_undeflate_dynamic_dist_tabptr = tabptr_dist; + // printf("malloc dist table\n"); + } + g_undeflate_dynamic_dist_bits = max_distance_bits; + tabptr_dist = undeflate_build_huff_tab(tabptr_dist, &(code_list[hlit]), + hdist, &(bl_count_dist[0]), max_distance_bits); + if(tabptr_dist == 0) { + printf("Building dist table failed\n"); + return 0; + } + + // Update *bit_pos_ptr to skip over the table + *bit_pos_ptr = bit_pos + (int)(8*(cptr - cptr_start)); + return tabptr; +} + +byte * +undeflate_block(Disk *dsk, byte *cptr, word32 *bit_pos_ptr, byte *cptr_base, + byte *cptr_end) +{ + word32 *lit_tabptr, *dist_tabptr; + byte *ucptr, *ucptr_end; + word32 bfinal, btype, bit_pos, len, pos, extra_bits, entry, dist_entry; + word32 bits, is_len, dist, lit_mask, dist_mask, tmp; + int i; + + bit_pos = *bit_pos_ptr; + + // printf("At file offset %08x,bit %d cptr[0]:%02x %02x\n", + // (word32)(cptr - cptr_base), bit_pos, cptr[0], cptr[1]); + bfinal = (cptr[0] >> bit_pos) & 1; + bit_pos++; + btype = (((cptr[1] << 8) | cptr[0]) >> bit_pos) & 3; + bit_pos += 2; + cptr += (bit_pos >> 3); + bit_pos = bit_pos & 7; + // printf("bfinal:%d, btype:%d\n", bfinal, btype); + + if(bfinal) { + dsk->fd = 0; // Last block + } + if(btype == 3) { // Reserved: error + return 0; + } else if(btype == 0) { // uncompressed + // Align cptr to next byte + bit_pos += 7; + cptr += (bit_pos >> 3); + *bit_pos_ptr = 0; + len = cptr[0] + (cptr[1] << 8); + ucptr = undeflate_ensure_dest_len(dsk, 0, len); + if(!ucptr) { + return 0; + } + cptr += 4; + for(i = 0; i < (int)len; i++) { + *ucptr++ = *cptr++; + } + dsk->dimage_size += len; + return cptr; + } + + if(btype == 1) { // Fixed Huffman codes + lit_tabptr = &(g_undeflate_fixed_len_tab[0]); + dist_tabptr = &(g_undeflate_fixed_dist_tab[0]); + lit_mask = 0x1ff; + dist_mask = 0x1f; + } else { // Dynamic Huffman codes + *bit_pos_ptr = bit_pos; + lit_tabptr = undeflate_dynamic_table(cptr, bit_pos_ptr, + cptr_base); + dist_tabptr = g_undeflate_dynamic_dist_tabptr; + // printf("dynamic table used %d bits\n", + // *bit_pos_ptr - bit_pos); + lit_mask = (1 << g_undeflate_dynamic_bits) - 1; + dist_mask = (1 << g_undeflate_dynamic_dist_bits) - 1; + bit_pos = *bit_pos_ptr; + cptr += (bit_pos >> 3); + bit_pos = bit_pos & 7; + } + if(!lit_tabptr || !dist_tabptr) { + printf("Code table failure\n"); + return 0; + } + + ucptr = undeflate_ensure_dest_len(dsk, 0, 65536); // Just a guess + if(!ucptr) { + return 0; + } + ucptr_end = dsk->raw_data + dsk->raw_dsize - 500; + + while(cptr < cptr_end) { +#if 0 + printf("Top of loop, cptr:%p, lit_tabptr:%p, dsk->raw:%p\n", + cptr, lit_tabptr, dsk->raw_data); +#endif + + if(ucptr > ucptr_end) { + ucptr = undeflate_ensure_dest_len(dsk, ucptr, 65536); + ucptr_end = dsk->raw_data + dsk->raw_dsize - 500; + // printf("Update ucptr to %p\n", ucptr); + if(!ucptr) { + return 0; + } + } + pos = cptr[0] | (cptr[1] << 8) | (cptr[2] << 16); + pos = pos >> bit_pos; + entry = lit_tabptr[pos & lit_mask]; + bits = (entry >> 16) & 0xf; + is_len = (entry >> 24) & 1; + len = entry & 0xffff; +#if 0 + printf("At offset +%08x bit:%d, huffcode=%04x, is %d bits, " + "entry=%08x\n", (int)(cptr - cptr_base), bit_pos, + pos & lit_mask, bits, entry); +#endif + if(bits == 0) { + printf("bits=0, %08x bad table\n", lit_mask); + return 0; + } + bit_pos += bits; + cptr += (bit_pos >> 3); + bit_pos = bit_pos & 7; + if(!is_len) { // Literal + // literal byte + // printf(" Out +%06x: %02x\n", + // (int)(ucptr - dsk->raw_data), len & 0xff); + //putc(len, g_outf); + *ucptr++ = len; + } else { + if(len == 2) { // Code=0x100, end block + // All done + // printf("Got the 0x100 code! All done!\n"); + *bit_pos_ptr = bit_pos; + dsk->dimage_size = ucptr - dsk->raw_data; + // printf("Set dsk->image_size = %08x\n", + // dsk->image_size); + return cptr; + } + extra_bits = (entry >> 20) & 7; + if(extra_bits) { + pos = cptr[0] | (cptr[1] << 8); + pos = pos >> bit_pos; + pos = pos & ((1 << extra_bits) - 1); + len += pos; + } +#if 0 + printf("At offset +%08x, bit:%d got extra_bits=%d, " + "len=%08x\n", (int)(cptr - cptr_base), bit_pos, + extra_bits, len); +#endif + bit_pos += extra_bits; + cptr += (bit_pos >> 3); + bit_pos = bit_pos & 7; + + // Get distance code + pos = cptr[0] | (cptr[1] << 8) | (cptr[2] << 16); + pos = pos >> bit_pos; +#if 0 + printf("At offset +%08x, bit:%d raw distance code: " + "%02x\n", (int)(cptr - cptr_base), bit_pos, + pos & dist_mask); +#endif + dist_entry = dist_tabptr[pos & dist_mask]; + bits = (dist_entry >> 16) & 0xf; + if(bits == 0) { + printf("bits=0 for dist_entry:%08x %08x\n", + dist_entry, pos); + } + extra_bits = (dist_entry >> 20) & 0xf; + dist = dist_entry & 0xffff; + //printf("dist_entry:%08x, extra_bits:%d, dist:%05x\n", + // dist_entry, extra_bits, dist); + bit_pos += bits; + cptr += (bit_pos >> 3); + bit_pos = bit_pos & 7; + if(extra_bits) { + pos = (cptr[0] | (cptr[1] << 8) | + (cptr[2] << 16)) >> bit_pos; +#if 0 + printf(" At offset +%08x, bit:%d, raw ex:" + "%08x\n", (int)(cptr - cptr_base), + bit_pos, pos); +#endif + tmp = pos & ((1 << extra_bits) - 1); + dist += tmp; +#if 0 + printf("at offset +%08x, got %d extra dist " + "for total dist=%d (%05x)\n", + (int)(cptr - cptr_base), extra_bits, + dist, pos); +#endif + bit_pos += extra_bits; + cptr += (bit_pos >> 3); + bit_pos = bit_pos & 7; + } + //printf("Repeating %d bytes from dist:%05x\n", len, + // dist); + if(ucptr < (dsk->raw_data + dist)) { + printf("Dist out of bounds:%04x %p %p\n", + dist, ucptr, dsk->raw_data); + return 0; + } + for(i = 0; i < (int)len; i++) { + ucptr[0] = ucptr[0-(int)dist]; +#if 0 + putc(ucptr[0], g_outf); + printf(" Out +%06x: %02x\n", + (int)(ucptr - dsk->raw_data), + ucptr[0]); +#endif + ucptr++; + } + } + } + + printf("Ran out of compressed data, bad gzip file\n"); + + return 0; +} + +byte * +undeflate_gzip_header(Disk *dsk, byte *cptr, word32 compr_size) +{ + word32 *wptr; + byte *cptr_base, *cptr_end; + word32 flg, xfl, xlen, bit_offset, exp_crc, len, crc; + + cptr_base = cptr; + cptr_end = cptr + compr_size; + + if((cptr[0] != 0x1f) || (cptr[1] != 0x8b) || (cptr[2] != 0x08)) { + printf("Not gzip file, exiting\n"); + return 0; + } + + flg = cptr[3]; + xfl = cptr[8]; + printf("flg:%02x and xflags:%02x\n", flg, xfl); + cptr += 10; + + if(flg & 4) { // FEXTRA set + xlen = cptr[0] + (cptr[1] * 256); + printf("FEXTRA XLEN is %d, skipping that many bytes\n", xlen); + cptr += 2 + xlen; + } + + if(flg & 8) { // FNAME set + cptr += strlen((char *)cptr) + 1; + } + if(flg & 0x10) { // FCOMMENT set + cptr += strlen((char *)cptr) + 1; + } + if(flg & 2) { // FHCRC set + cptr += 2; + } + printf("gzip header was %02x bytes long\n", (int)(cptr - cptr_base)); + + dsk->raw_dsize = 140*1024; // Just a guess, alloc size + dsk->raw_data = undeflate_malloc(dsk->raw_dsize); + if(dsk->raw_data == 0) { + return 0; + } + printf("Initial malloc (not realloc) set raw_data=%p\n", dsk->raw_data); + + dsk->dimage_size = 0; // Used size + + wptr = undeflate_init_tables(); + if(wptr == 0) { + return 0; // Some sort of error, get out + } + + bit_offset = 0; + while(cptr < cptr_end) { + cptr = undeflate_block(dsk, cptr, &bit_offset, cptr_base, + cptr_end); + if(cptr == 0) { + // Failed + break; + } + if(dsk->fd == 0) { + printf("undeflate_block set fd=0, success\n"); + // Done, success! + // Check crc + if(bit_offset) { + cptr++; + } + if((cptr + 8) > cptr_end) { + printf("No CRC or LEN fields at end\n"); + break; + } + exp_crc = cptr[0] | (cptr[1] << 8) | (cptr[2] << 16) | + (cptr[3] << 24); + len = cptr[4] | (cptr[5] << 8) | (cptr[6] << 16) | + (cptr[7] << 24); + if(len != dsk->dimage_size) { + printf("Len mismatch: exp %08x != %08llx\n", + len, dsk->dimage_size); + break; + } + crc = woz_calc_crc32(dsk->raw_data, len, 0); + if(crc != exp_crc) { + printf("CRC mismatch: %08x != exp %08x\n", + crc, exp_crc); + break; + } + // Real success, set raw_dsize + dsk->raw_data = undeflate_realloc(dsk->raw_data, + dsk->dimage_size); + dsk->raw_dsize = dsk->dimage_size; + return cptr; + } + } + + printf("Failed\n"); + // Disk image thread not found, get out + free(dsk->raw_data); + dsk->fd = -1; + dsk->dimage_size = 0; + dsk->raw_data = 0; + dsk->raw_dsize = 0; + return 0; +} + +void +undeflate_gzip(Disk *dsk, const char *name_str) +{ + byte *cptr; + dword64 compr_dsize, dret; + word32 compr_size; + int fd; + int i; + + // On success, set dsk->fd=0 and dsk->raw_data,raw_dsize properly. + printf("undeflate_gzip on file %s\n", name_str); + fd = open(name_str, O_RDONLY | O_BINARY, 0x1b6); + if(fd < 0) { + return; + } + compr_dsize = cfg_get_fd_size(fd); + printf("size: %lld\n", compr_dsize); + if((compr_dsize >> 31) != 0) { + // > 2GB...too big for this code + printf("gzip file is too large\n"); + dsk->fd = -1; + return; + } + compr_size = (word32)compr_dsize; + + cptr = malloc(compr_size + 0x1000); + for(i = 0; i < 0x1000; i++) { + cptr[compr_size + i] = 0; + } + dret = cfg_read_from_fd(fd, cptr, 0, compr_size); + if(dret != compr_size) { + compr_size = 0; // Make header searching fail + } + //g_outf = fopen("out.dbg", "w"); + undeflate_gzip_header(dsk, cptr, compr_size); + + free(cptr); + undeflate_free_tables(); +} + +byte * +undeflate_zipfile_blocks(Disk *dsk, byte *cptr, dword64 dcompr_size) +{ + word32 *wptr; + byte *cptr_base, *cptr_end; + word32 bit_offset; + + cptr_base = cptr; + cptr_end = cptr + dcompr_size; + + dsk->raw_data = undeflate_malloc(dsk->raw_dsize); + if(dsk->raw_data == 0) { + return 0; + } + printf("Initial malloc (not realloc) set raw_data=%p\n", dsk->raw_data); + + dsk->dimage_size = 0; // Used size + + wptr = undeflate_init_tables(); + if(wptr == 0) { + return 0; // Some sort of error, get out + } + + bit_offset = 0; + while(cptr < cptr_end) { + cptr = undeflate_block(dsk, cptr, &bit_offset, cptr_base, + cptr_end); + if(cptr == 0) { + // Failed + break; + } + if(dsk->fd == 0) { + printf("undeflate_block set fd=0, success\n"); + // Done, success! + // Check crc + if(bit_offset) { + cptr++; + } + // Real success, set raw_dsize + dsk->raw_data = undeflate_realloc(dsk->raw_data, + dsk->dimage_size); + dsk->raw_dsize = dsk->dimage_size; + return cptr; + } + } + + printf("Failed\n"); + // Disk image thread not found, get out + free(dsk->raw_data); + dsk->fd = -1; + dsk->dimage_size = 0; + dsk->raw_data = 0; + dsk->raw_dsize = 0; + return 0; +} + +byte g_zip_local_file_header[] = { 0x50, 0x4b, 0x03, 0x04 }; +byte g_zip_central_file_header[] = { 0x50, 0x4b, 0x01, 0x02 }; +byte g_zip_end_central_dir_header[] = { 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0 }; +byte g_zip64_end_central_dir_locator[] = { 0x50, 0x4b, 0x06, 0x07, 0, 0, 0, 0 }; +byte g_zip64_end_central_dir_header[] = { 0x50, 0x4b, 0x06, 0x06 }; + +extern Cfg_listhdr g_cfg_partitionlist; + + +int +undeflate_zipfile(Disk *dsk, int fd, dword64 dlocal_header_off, + dword64 uncompr_dsize, dword64 compr_dsize) +{ + byte buf[64]; + byte *cptr, *cptr2; + dword64 dret, compr_doffset; + word32 compr_method, name_len, extra_len; + word32 bit_flags; + int ret; + int i; + + // return -1 on failure, >= 0 on success + + printf("undeflate_zipfile called, fd:%d, offset:%08llx, unc:%lld " + "compr:%lld\n", fd, dlocal_header_off, uncompr_dsize, + compr_dsize); + + dret = cfg_read_from_fd(fd, &buf[0], dlocal_header_off, 64); + if(dret != 64) { + printf("read dret:%08llx != 64\n", dret); + return -1; + } + + for(i = 0; i < 4; i++) { + if(buf[i] != g_zip_local_file_header[i]) { + printf("hdr[%d]=%02x\n", i, buf[i]); + return -1; + } + } + + if(((uncompr_dsize | compr_dsize) >> 31) != 0) { + printf("Size >2GB, not supported\n"); + return -1; + } + bit_flags = cfg_get_le16(&(buf[6])); + compr_method = cfg_get_le16(&(buf[8])); + // compr_size = cfg_get_le32(&(buf[18])); // Probably 0 + // uncompr_size = cfg_get_le32(&(buf[22])); // Probably 0 + name_len = cfg_get_le16(&(buf[26])); + extra_len = cfg_get_le16(&(buf[28])); + + // The ZIP file format is annoying, the local header doesn't have + // compr_size and uncompr_size generally (if bit_flags bit 3 is set). + // Even if it does, it's fine to always use the central directory + printf("bit_flags: %04x\n", bit_flags); + + compr_doffset = dlocal_header_off + 30 + name_len + extra_len; + + cptr = undeflate_malloc(compr_dsize + 0x1000); + for(i = 0; i < 0x1000; i++) { + cptr[compr_dsize + i] = 0; + } + + dret = cfg_read_from_fd(fd, cptr, compr_doffset, compr_dsize); + if(dret != compr_dsize) { + return -1; + } + dsk->raw_dsize = uncompr_dsize; + dsk->dimage_size = uncompr_dsize; + + ret = -1; + if(compr_method == 0) { // Stored, just use cptr + dsk->raw_data = cptr; + dsk->raw_dsize = uncompr_dsize; + dsk->dimage_start = 0; + close(fd); + dsk->fd = 0; + cptr = 0; // So free(cptr) does nothing + ret = 0; + } else if(compr_method == 8) { // Deflate + cptr2 = undeflate_zipfile_blocks(dsk, cptr, compr_dsize); + printf("undeflate_zipfile_blocks ret:%p\n", cptr2); + if(cptr2 != 0) { + ret = 0; + } + } else { + printf("Unknown compr_method:%04x\n", compr_method); + } + free(cptr); + undeflate_free_tables(); + + return ret; +} + +int +undeflate_zipfile_search(byte *bptr, byte *cmp_ptr, int size, int cmp_len, + int min_size) +{ + int pos, good; + int i; + + // Search for cmp_ptr in the bptr buffer (basically, look for "PKxx" + // header strings). + pos = size - min_size; + good = 0; + while(pos >= 0) { + good = 1; + for(i = 0; i < cmp_len; i++) { + if(bptr[pos + i] != cmp_ptr[i]) { + good = 0; + break; + } + } + if(good) { + break; + } + pos--; + } + + if(!good) { + return -1; + } + return pos; +} + +int +undeflate_zipfile_make_list(int fd) +{ + byte buf[1024]; + dword64 dret, dsize, dir_doff, dir_dsize, unc_dsize, compr_dsize; + dword64 local_dheader, dneg1, dval, doff, dpos, dlen; + byte *dirptr, *name_ptr, *bptr, *bptr2; + char *str; + word32 extra_len, comment_len, ent, entries, part_len, inc; + word32 tmp_off, ex_off, this_size, this_id; + int pos, good, add_it, need_compr, need_unc, need_dheader; + int name_len; + int i; + + dret = cfg_read_from_fd(fd, &buf[0], 0, 64); + if(dret != 64) { + return 0; // Not a ZIP file + } + + // See if it's a PKZIP file, starting 0x50, 0x4b, 0x03, 0x04 + for(i = 0; i < 4; i++) { + if(buf[i] != g_zip_local_file_header[i]) { + return 0; + } + } + + printf("This looks like a .zip file\n"); + + // Find end of central directory record in last 1024 bytes. If it's + // not there, this is too complex of a ZIP file for us, give up + dsize = cfg_get_fd_size(fd); + + for(i = 0; i < 1024; i++) { + buf[i] = 0; + } + dpos = 0; + dlen = dsize; + if(dsize > 1024) { + dpos = dsize - 1024; + dlen = 1024; + } + dret = cfg_read_from_fd(fd, &buf[0], dpos, dlen); + if(dret != dlen) { + return 0; // Unknown problem + } + + pos = undeflate_zipfile_search(&buf[0], + &g_zip_end_central_dir_header[0], 1024, 8, 22); + // End of Central Directory is at least 22 bytes + if(pos < 0) { + printf("Cannot parse this .zip file\n"); + return 0; + } + + entries = cfg_get_le16(&(buf[pos + 8])); + dir_dsize = cfg_get_le32(&(buf[pos + 12])); + dir_doff = cfg_get_le32(&(buf[pos + 16])); + +#if 0 + printf(".zip entries:%04x, dir_dsize:%06llx, dir_doff:%08llx\n", + entries, dir_dsize, dir_doff); +#endif + dneg1 = 0xffffffffULL; + if(dir_doff == dneg1) { + printf("We must look for the ZIP64 end dir locator\n"); + pos = undeflate_zipfile_search(&buf[0], + &g_zip64_end_central_dir_locator[0], 1024, 8, 20); + if(pos < 0) { + printf("Cannot parse this ZIP64 file\n"); + return 0; + } + doff = cfg_get_le64(&(buf[pos + 8])); + printf("ZIP64 end of central dir record at 0x%08llx\n", doff); + if((doff + 64) > dsize) { + printf("End Central Dir record out of bounds\n"); + return 0; + } + // Now read end of central directory record. Just read 64 bytes + // It has to be at least 56 bytes, and the locator had to be + // after, so it must fit + dret = cfg_read_from_fd(fd, &buf[0], doff, 64); + if(dret != 64) { + return 0; // Unknown problem + } + pos = undeflate_zipfile_search(&buf[0], + &g_zip64_end_central_dir_header[0], 64, 4, 64); + if(pos != 0) { + printf("ZIP64 end of central dir record not found\n"); + return 0; + } + + entries = cfg_get_le32(&(buf[32])); + dir_dsize = cfg_get_le64(&(buf[40])); + dir_doff = cfg_get_le64(&(buf[48])); + } + + if((entries < 1) || (dir_dsize > dsize) || (dir_dsize > (1L << 20)) || + ((dir_doff + dir_dsize) > dsize)) { + printf("Malformed zip file\n"); + return 0; + } + + dirptr = undeflate_malloc(dir_dsize); + dret = cfg_read_from_fd(fd, dirptr, dir_doff, dir_dsize); + if(dret != dir_dsize) { + printf("Couldn't read central dir\n"); + return 0; + } + + part_len = cfg_partition_maybe_add_dotdot(); + // part_len is strlen(g_cfg_part_path[]); + + pos = 0; + ent = 0; + while(pos < (int)dir_dsize) { +#if 0 + printf("Working on ent %d at pos %d\n", ent, pos); +#endif + if(ent >= entries) { + break; // all done + } + good = 1; + for(i = 0; i < 4; i++) { + if(dirptr[pos + i] != g_zip_central_file_header[i]) { + // corrupt index, get out + printf("At pos %04x, i:%d bad hdr\n", pos, i); + good = 0; + break; + } + } + if(!good) { + break; + } + compr_dsize = cfg_get_le32(&dirptr[pos + 20]); + unc_dsize = cfg_get_le32(&dirptr[pos + 24]); + name_len = cfg_get_le16(&dirptr[pos + 28]); + extra_len = cfg_get_le16(&dirptr[pos + 30]); + comment_len = cfg_get_le16(&dirptr[pos + 32]); + local_dheader = cfg_get_le32(&dirptr[pos + 42]); + if((pos + 46UL + name_len) > dir_dsize) { + printf("Corrupt entry: pos:%04x, name_len:%04x, " + "dir_dsize:%05llx\n", pos, name_len, dir_dsize); + break; + } + + need_unc = (unc_dsize == dneg1); + need_compr = (compr_dsize == dneg1); + need_dheader = (local_dheader == dneg1); + + // Walk extras to update unc/compr size and file offset, if + // the standard fields are 0xffffffff. + bptr = &(dirptr[pos + 46 + name_len]); + ex_off = 0; + add_it = 1; + while(ex_off < extra_len) { +#if 0 + printf("Working on ex_off:%d out of %d\n", ex_off, + extra_len); +#endif + this_id = cfg_get_le16(&bptr[ex_off]); + this_size = cfg_get_le16(&bptr[ex_off + 2]); + if((this_size + ex_off + pos + 46UL + name_len) > + dir_dsize) { + printf("Corrupt ZIP64 extra info entry\n"); + add_it = 0; + break; + } + ex_off += 4; + if(this_id == 0x0001) { + tmp_off = 0; + bptr2 = &(bptr[ex_off]); + while(tmp_off < this_size) { + dval = cfg_get_le64(bptr2); +#if 0 + printf("tmp_off %d of %d, dval:" + "%016llx\n", tmp_off, + this_size, dval); +#endif + if(need_compr) { + compr_dsize = dval; + need_compr = 0; + } else if(need_unc) { + unc_dsize = dval; + need_unc = 0; + } else if(need_dheader) { + local_dheader = dval; + need_dheader = 0; + } else { + printf("Corrupt ZIP64\n"); + add_it = 0; + } + tmp_off += 8; + bptr += 8; + } + } + ex_off += this_size; + } + + if(need_unc || need_compr || need_dheader) { + printf("Bad ZIP64 overrides\n"); + add_it = 0; + } + + // See if filename is at the proper depth + name_ptr = &(dirptr[pos + 46]); + if(add_it) { + add_it = cfg_partition_name_check(name_ptr, name_len); + } + + //printf("ent:%d name:%s len:%d had add_it:%d, part_len:%d\n", + // ent, name_ptr, name_len, add_it, part_len); + + inc = 46 + name_len + extra_len + comment_len; + if(add_it) { + // Handle directories either explicitly listed, as + // foo/, foo/bar/, foo/bar/1, foo/bar/2 ; or + // implied: foo/bar/1 and foo/bar/2 as entries + // implies foo/ and foo/bar/ are directories. + // Add any name at the current part_len level, but + // make sure it's unique (don't add lots of "foo"s). + name_ptr += part_len; + name_len -= part_len; + if(name_len <= 0) { + add_it = 0; + } + for(i = 0; i < name_len; i++) { + if(name_ptr[i] == '/') { + // This ends this name at this level + if(i > 0) { + add_it = 2; + name_len = i + 1; + } else { + add_it = 0; + } + break; + } + } + } + if((add_it < 2) && (unc_dsize < 140*1024)) { + add_it = 0; + } + if(add_it) { + str = malloc(name_len + 1); + cfg_strncpy(str, (char *)&name_ptr[0], name_len + 1); + cfg_file_add_dirent_unique(&g_cfg_partitionlist, str, + add_it - 1, unc_dsize, local_dheader, + compr_dsize, ent); + free(str); + } + pos += inc; + ent++; + } + free(dirptr); + + printf("Returning %d, pos:%05x, dir_dsize:%05llx\n", ent, pos, + dir_dsize); + return g_cfg_partitionlist.last; +} + diff --git a/gsplus/src/unshk.c b/gsplus/src/unshk.c new file mode 100644 index 0000000..85e0fec --- /dev/null +++ b/gsplus/src/unshk.c @@ -0,0 +1,548 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2021 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// This code is based on the official NuFX documentation in Apple II FTN.e08002 +// available at: http://nulib.com/library/FTN.e08002.htm. Andy McFadden has +// reverse-engineered GSHK (and its quirks) in Nulib, at: http://nulib.com/, +// and that code was very helpful in getting the basic algorithms correct. + +#include "defc.h" + +word32 +unshk_get_long4(byte *bptr) +{ + word32 val; + int i; + + // Get 4 bytes in little-endian form + val = 0; + for(i = 3; i >=0 ; i--) { + val = (val << 8) | bptr[i]; + } + return val; +} + +word32 +unshk_get_word2(byte *bptr) +{ + // Get 2 bytes in little-endian form + return (bptr[1] << 8) | bptr[0]; +} + +word32 +unshk_calc_crc(byte *bptr, int size, word32 start_crc) +{ + word32 crc; + int i, j; + + // No table used: do basic CRC operation on size bytes. For CCITT-16 + // (the one used in ShrinkIt), xor the byte into the upper 8 bits of + // the current crc, then xor in 0x1021 for each '1' bit shifted out + // the top. Use a 32-bit crc variable to get the bit shifted out. + crc = start_crc & 0xffff; + for(i = 0; i < size; i++) { + crc = crc ^ (bptr[i] << 8); + for(j = 0; j < 8; j++) { + crc = crc << 1; + if(crc & 0x10000) { + crc = crc ^ 0x11021; + // XOR in 0x1021, and clear bit 16 as well. + } + } + // printf("CRC after [%04x]=%02x is %04x\n", i, bptr[i], crc); + } + return crc & 0xffff; +} + +int +unshk_unrle(byte *cptr, int len, word32 rle_delim, byte *ucptr) +{ + byte *start_ucptr; + word32 c; + int outlen, count; + int i; + + // RLE is 3 bytes: { 0xdb, char, count}, where count==0 means output + // one char. + start_ucptr = ucptr; + while(len > 0) { + c = *cptr++; + len--; + if(c == rle_delim) { + c = *cptr++; + count = *cptr++; + len -= 2; + for(i = 0; i <= count; i++) { + *ucptr++ = c; + } + } else { + *ucptr++ = c; + } + } + outlen = (int)(ucptr - start_ucptr); + if(outlen != 0x1000) { + printf("RLE failed, output %d bytes\n", outlen); + return 1; + } + return 0; +} + +void +unshk_lzw_clear(Lzw_state *lzw_ptr) +{ + int i; + + lzw_ptr->entry = 0x100; // First expected table pos + lzw_ptr->bits = 9; + for(i = 0; i < 256; i++) { + lzw_ptr->table[i] = i << 12; // Encodes depth==0 as well + } + lzw_ptr->table[0x100] = 0; +} + +// LZW Table format in 32-bit word: { depth[11:0], finalc[7:0], code[11:0] } + +byte * +unshk_unlzw(byte *cptr, Lzw_state *lzw_ptr, byte *ucptr, word32 uclen) +{ + byte *end_ucptr, *bptr; + word32 mask, val, entry, newcode, finalc_code; + int bit_pos, depth, bits; + + // This routine handles ShrinkIt LZW/1 and LZW/2 streams. It expects + // the caller has set entry=0x100 and bits=9 at the start of each + // LZW/1 chunk + + entry = lzw_ptr->entry; + bits = lzw_ptr->bits; + end_ucptr = ucptr + uclen; + //printf("unlzw block: format:%d, uclen:%04x\n", thread_format, uclen); + mask = (1 << bits) - 1; + bit_pos = 0; + + while(ucptr < end_ucptr) { + newcode = (cptr[2] << 16) | (cptr[1] << 8) | cptr[0]; + newcode = (newcode >> bit_pos) & mask; + bit_pos += bits; + cptr += (bit_pos >> 3); + bit_pos = bit_pos & 7; + // printf("At entry:%04x, bits:%d newcode:%04x\n", entry, bits, + // newcode); + if((entry + 1) >= mask) { + bits++; + mask = (mask << 1) | 1; + // Note this is one too early, but this is needed to + // match ShrinkIt + } + + // Newcode is up to 12-bits, where <= 0xff means just this + // char, and >= 0x101 means chase down that code, output the + // character in that table entry, and repeat + if(newcode == 0x100) { + // printf("Got clear code\n"); + entry = 0x100; + bits = 9; + mask = 0x1ff; + continue; + } + if(newcode > entry) { + printf("Bad code: %04x, entry:%04x\n", newcode, entry); + return 0; + } +#if 0 + if(newcode == entry) { + // KwKwK case: operate on oldcode + printf("KwKwK case!\n"); + } +#endif + finalc_code = newcode; + depth = lzw_ptr->table[newcode & 0xfff] >> 20; + // depth will be 0 for 1 character, 1 for 2 characters, etc. + bptr = ucptr + depth; + while(bptr >= ucptr) { + finalc_code = lzw_ptr->table[finalc_code & 0xfff]; + *bptr-- = (finalc_code >> 12) & 0xff; + } + val = lzw_ptr->table[entry]; + lzw_ptr->table[entry] = (val & (~0xff000)) | + (finalc_code & 0xff000); + // [entry] has code from last iteration (which stuck in + // the last finalc char, which we need to toss now), + // and update it with the correct finalc character. + // printf("Table[%04x]=%08x\n", entry, lzw_ptr->table[entry]); + + depth++; + ucptr += depth; +#if 0 + bptr = ucptr - depth; + printf("src:%04x, out+%06x: ", (int)(cptr - cptr_start), + (int)(ucptr - depth - (end_ucptr - uclen))); + for(i = 0; i < depth; i++) { + printf(" %02x", *bptr++); + } + printf("\n"); +#endif + lzw_ptr->table[entry + 1] = (depth << 20) | + (finalc_code & 0xff000) | newcode; + // Set tab[entry+1] for KwKwK case, with this newcode, + // and this finalc character. This also saves this + // newcode when the next code is received. + entry++; + } + lzw_ptr->entry = entry; + lzw_ptr->bits = bits; + if(bit_pos) { // We used part of this byte, use it + cptr++; + } + return cptr; +} + +void +unshk_data(Disk *dsk, byte *cptr, word32 compr_size, byte *ucptr, + word32 uncompr_size, word32 thread_format, byte *base_cptr) +{ + Lzw_state lzw_state; + byte *end_cptr, *end_ucptr, *rle_inptr; + word32 rle_delim, len, use_lzw, lzw_len, crc, chunk_crc; + int ret; + int i; + + printf("Uncompress %d compress bytes into %d bytes, source offset:" + "%08x\n", compr_size, uncompr_size, (word32)(cptr - base_cptr)); + + // LZW/1 format: crc_lo, crc_hi, vol, rle_delim then start the chunk + // each chunk: rle_len_lo, rle_len_hi, lzw_used + // LZW/2 format: vol, rle_delim then start the chunk + // each chunk: rle_len_lo, rle_len_hi, lzw_len_lo, lzw_len_hi + // where rle_len_hi[7]==1 means LZW was used + end_cptr = cptr + compr_size; + end_ucptr = ucptr + uncompr_size; + + chunk_crc = 0; + if(thread_format != 3) { // LZW/1 + chunk_crc = (cptr[1] << 8) | cptr[0]; + cptr += 2; // Skip over CRC bytes + } + dsk->vol_num = cptr[0]; // LZW/1 + rle_delim = cptr[1]; + cptr += 2; + unshk_lzw_clear(&lzw_state); + + // printf("vol_num:%02x, rle_delim:%02x\n", dsk->vol_num, rle_delim); + + // LZW/1 format for each chunk: len_lo, len_hi, use_lzw + // LZW/2 format for each chunk: len_lo, len_hi. If len_hi[7]=1, then + // two more bytes: lzw_len_lo, lzw_len_hi + while(cptr < (end_cptr - 4)) { + if(ucptr >= end_ucptr) { + break; + } + len = (cptr[1] << 8) | cptr[0]; +#if 0 + printf("chunk at +%08x, len:%04x, dest offset:%08x\n", + (word32)(cptr - base_cptr), len, + (word32)(ucptr - (end_ucptr - uncompr_size))); +#endif + cptr += 2; + use_lzw = (len >> 15) & 1; + if(len & 0x6000) { + printf("Illegal length: %04x\n", len); + return; // Ilegal length + } + len = len & 0x1fff; + lzw_len = 0; + if(thread_format == 3) { // LZW/2 + if(use_lzw) { + lzw_len = (cptr[1] << 8) | cptr[0]; + if(lzw_len > 0x1004) { + printf("Bad lzw_len: %04x\n", lzw_len); + return; // Illegal + } + cptr += 2; + lzw_len -= 4; // Counts from [-4] + } + } else { // LZW/1 + use_lzw = *cptr++; + if(use_lzw >= 2) { + printf("Bad use_lzw:%02x\n", use_lzw); + return; // Bad format + } + } + rle_inptr = cptr; + if(use_lzw) { + //printf("lzw on %02x.%02x.%02x.., %d bytes (rle:%d)\n", + // cptr[0], cptr[1], cptr[2], lzw_len, len); + rle_inptr = ucptr; + if(len != 0x1000) { + // RLE pass is needed: Write to ucptr+0x1000, + // and then UnRLE down to ucptr; + rle_inptr = ucptr + 0x1000; + } + cptr = unshk_unlzw(cptr, &lzw_state, rle_inptr, len); + if(cptr == 0) { + printf("Bad LZW stream\n"); + return; + } + if(thread_format != 3) { // LZW/1 + lzw_state.entry = 0x100; // Reset table + lzw_state.bits = 9; + } + } else { + lzw_state.entry = 0x100; // Reset table + lzw_state.bits = 9; + } + if(len != 0x1000) { + // printf("RLE on %02x.%02x.%02x... %d bytes\n", + // cptr[0], cptr[1], cptr[2], len); + ret = unshk_unrle(rle_inptr, len, rle_delim, ucptr); + if(ret) { + printf("unRLE failed\n"); + return; + } + if(!use_lzw) { + cptr += len; + } + } else if(!use_lzw) { + // Uncompressed + // printf("Uncompressed %02x.%02x.%02x....%d bytes\n", + // cptr[0], cptr[1], cptr[2], len); + for(i = 0; i < 0x1000; i++) { + ucptr[i] = *cptr++; + } + } + // write(g_out_fd, ucptr, 0x1000); + ucptr += 0x1000; + } + + printf("cptr:%p, end_cptr:%p, uncompr_size:%08x\n", cptr, end_cptr, + uncompr_size); + if(thread_format != 3) { // LZW/1 + crc = unshk_calc_crc(ucptr - uncompr_size, uncompr_size, 0); + //printf("LZW/1 calc CRC %04x vs CRC %04x\n", crc, chunk_crc); + if(crc != chunk_crc) { + printf("Bad LZW/1 CRC: %04x != %04x\n", crc, chunk_crc); + return; + } + } + dsk->fd = 0; +} + +void +unshk_parse_header(Disk *dsk, byte *cptr, int compr_size, byte *base_cptr) +{ + byte *cptr_end, *dptr, *ucptr; + word32 total_records, attrib_count, total_threads, thread_class; + word32 thread_format, thread_kind, thread_eof, comp_thread_eof; + word32 thread_crc, crc, version, disk_size, block_size, num_blocks; + word32 filename_length; + int i; + + cptr_end = cptr + compr_size; + if(compr_size < 0xa0) { + printf("Didn't read everything\n"); + return; + } + + // Parse NuFX format: "NuFile" with alternating high bits + if((cptr[0] != 0x4e) || (cptr[1] != 0xf5) || (cptr[2] != 0x46) || + (cptr[3] != 0xe9) || (cptr[4] != 0x6c) || (cptr[5] != 0xe5)) { + printf("Not NuFile, exiting\n"); + return; + } + total_records = unshk_get_long4(&cptr[8]); + if(total_records < 1) { + return; + } + + // Master Header to NuFile is apparently 48 bytes. Look for "NuFX" + // Header to describe threads + cptr += 0x30; + if((cptr[0] != 0x4e) || (cptr[1] != 0xf5) || (cptr[2] != 0x46) || + (cptr[3] != 0xd8)) { + return; + } + attrib_count = unshk_get_word2(&cptr[6]); + version = unshk_get_word2(&cptr[8]); // >= 3 means File CRC + total_threads = unshk_get_long4(&cptr[10]); + num_blocks = unshk_get_long4(&cptr[26]); // extra_type + block_size = unshk_get_word2(&cptr[30]); // storage_type + // P8 ShrinkIt is riddled with bugs. Disk archives have incorrect + // thread_eof for the uncompressed total size. So we need to do + // num_blocks * block_size to get the real size. But this can be + // buggy too! These fixes are from NuFxLib::Thread.c actualThreadEOF + // comments + // First, fix block_size. SHK v3.0.1 stored it as a small value + if(block_size < 256) { + block_size = 512; + } + disk_size = block_size * num_blocks; + if(disk_size == (70*1024)) { + // Old GSHK apparently set block_size==256 but blocks=280 for + // 5.25" DOS 3.3 disks...block size must be 512 to equal 140K. + disk_size = 140*1024; + } + if(disk_size < 140*1024) { + printf("disk_size %dK is invalid\n", disk_size >> 10); + return; + } + cptr += attrib_count; + filename_length = unshk_get_word2(&cptr[-2]); // filename_length + cptr += filename_length; + dptr = cptr + 16*total_threads; + // Each thread is 16 bytes, so the data is at +16*total_threads + // The data is in the same order as the header for the threads + // We ignore anything other than a data thread for SDK + for(i = 0; i < (int)total_threads; i++) { + if((dptr >= cptr_end) || (cptr >= cptr_end)) { + return; + } + thread_class = unshk_get_word2(&cptr[0]); + thread_format = unshk_get_word2(&cptr[2]); + thread_kind = unshk_get_word2(&cptr[4]); + thread_crc = unshk_get_word2(&cptr[6]); + //thread_eof = unshk_get_long4(&cptr[8]); + // thread_eof is wrong in P8 ShrinkIt, so just use disk_size + thread_eof = disk_size; + comp_thread_eof = unshk_get_long4(&cptr[12]); + if((dptr + comp_thread_eof) > cptr_end) { + return; // Corrupt + } + if((thread_class == 2) && (thread_kind == 1)) { + // Disk image! + ucptr = malloc(thread_eof + 0x1000); + unshk_data(dsk, dptr, comp_thread_eof, ucptr, + thread_eof, thread_format, base_cptr); + if(dsk->fd == 0) { + // Success, so far. Check CRC + printf("Version:%d, thread_crc:%04x\n", + version, thread_crc); + if(version >= 3) { // CRC is valid + crc = unshk_calc_crc(ucptr, thread_eof, + 0xffff); +#if 0 + printf("Thread CRC:%04x, exp:%04x\n", + crc, thread_crc); +#endif + if(crc != thread_crc) { + printf("Bad CRC: %04x != exp " + "%04x\n", crc, + thread_crc); + dsk->fd = -1; + } + } + } + if(dsk->fd < 0) { + free(ucptr); + } else { + // Real success, set raw_size + dsk->raw_dsize = thread_eof; + dsk->raw_data = ucptr; + return; + } + } else { + dptr += comp_thread_eof; + cptr += 16; + } + } + + // Disk image thread not found, get out +} + +void +unshk(Disk *dsk, const char *name_str) +{ + byte *cptr; + int compr_size, fd, pos, ret; + int i; + + printf("unshk %s\n", name_str); + // Handle .sdk inside a .zip + if(dsk->raw_data) { + unshk_dsk_raw_data(dsk); + return; + } + + // File is not opened yet, try to open it + fd = open(name_str, O_RDONLY | O_BINARY, 0x1b6); + if(fd < 0) { + return; + } + compr_size = (int)cfg_get_fd_size(fd); + printf("size: %d\n", compr_size); + + cptr = malloc(compr_size + 0x1000); + pos = 0; + for(i = 0; i < 0x1000; i++) { + cptr[compr_size + i] = 0; + } + while(1) { + if(pos >= compr_size) { + break; + } + ret = read(fd, cptr + pos, compr_size - pos); + if(ret <= 0) { + break; + } + pos += ret; + } + close(fd); + + if(pos != compr_size) { + compr_size = 0; // Make header searching fail + } + unshk_parse_header(dsk, cptr, compr_size, cptr); + + free(cptr); +} + +void +unshk_dsk_raw_data(Disk *dsk) +{ + byte *save_raw_data, *cptr; + dword64 save_raw_dsize; + int save_fd, compr_size; + int i; + // This code handles the case of .sdk inside a .zip (for example). + // Since unshk() code uses dsk->fd, dsk->raw_data, and dsk->raw_dsize + // to communicate success in unshk'ing the disk, we need to copy + // those, and restore them, if the unshk fails + save_fd = dsk->fd; + save_raw_data = dsk->raw_data; + save_raw_dsize = dsk->raw_dsize; + if(save_raw_dsize >= (1ULL << 30)) { + return; // Too large + } + + dsk->fd = -1; + dsk->raw_data = 0; + dsk->raw_dsize = 0; + + compr_size = (int)save_raw_dsize; + cptr = malloc(compr_size + 0x1000); + for(i = 0; i < 0x1000; i++) { + cptr[compr_size + i] = 0; + } + for(i = 0; i < compr_size; i++) { + cptr[i] = save_raw_data[i]; + } + + unshk_parse_header(dsk, cptr, compr_size, cptr); + free(cptr); + + if(dsk->raw_data) { + // Success, free the old raw data + free(save_raw_data); + return; + } + dsk->fd = save_fd; + dsk->raw_data = save_raw_data; + dsk->raw_dsize = save_raw_dsize; +} + diff --git a/gsplus/src/vars b/gsplus/src/vars new file mode 100644 index 0000000..b6642a6 --- /dev/null +++ b/gsplus/src/vars @@ -0,0 +1,8 @@ +TARGET = gsplus +OBJECTS1 = macsnd_driver.o +CCOPTS = -Wall -O2 -DMAC +SUFFIX = +NAME = gsplus + +XOPTS = + diff --git a/gsplus/src/vars_mac b/gsplus/src/vars_mac new file mode 100644 index 0000000..b6642a6 --- /dev/null +++ b/gsplus/src/vars_mac @@ -0,0 +1,8 @@ +TARGET = gsplus +OBJECTS1 = macsnd_driver.o +CCOPTS = -Wall -O2 -DMAC +SUFFIX = +NAME = gsplus + +XOPTS = + diff --git a/gsplus/src/vars_mac_x b/gsplus/src/vars_mac_x new file mode 100644 index 0000000..bbca383 --- /dev/null +++ b/gsplus/src/vars_mac_x @@ -0,0 +1,9 @@ +TARGET = gsplus +OBJECTS1 = macsnd_driver.o xdriver.o +CCOPTS = -O2 -DMAC -Wall -I/usr/X11/include +SUFFIX = +NAME = gsplus + +XOPTS = +LDOPTS = -Wl,-framework,CoreAudio -Wl,-framework,CoreFoundation -Wl,-framework,AudioToolbox + diff --git a/gsplus/src/vars_x86linux b/gsplus/src/vars_x86linux new file mode 100644 index 0000000..ac985a5 --- /dev/null +++ b/gsplus/src/vars_x86linux @@ -0,0 +1,10 @@ +TARGET = gsplus +OBJECTS1 = pulseaudio_driver.o xdriver.o +CCOPTS = -O2 -Wall -fomit-frame-pointer -DPULSE_AUDIO +NAME = gsplus +LD = $(CC) +EXTRA_LIBS = -lXext -lpulse +EXTRA_SPECIALS = + +XOPTS = -I/usr/X11R6/include + diff --git a/gsplus/src/video.c b/gsplus/src/video.c new file mode 100644 index 0000000..9be841a --- /dev/null +++ b/gsplus/src/video.c @@ -0,0 +1,2926 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2025 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +#include + +#include "defc.h" + +extern int Verbose; + +word32 g_a2_filt_stat[200]; +int g_a2_line_left_edge[200]; +int g_a2_line_right_edge[200]; + +byte g_cur_border_colors[270]; + +word32 g_a2_screen_buffer_changed = (word32)-1; +word32 g_full_refresh_needed = (word32)-1; + +word32 g_cycs_in_40col = 0; +word32 g_cycs_in_xredraw = 0; +word32 g_refresh_bytes_xfer = 0; + +extern byte *g_slow_memory_ptr; +extern int g_fatal_log; + +extern dword64 g_cur_dfcyc; + +extern int g_line_ref_amt; + +extern word32 g_c034_val; +extern int g_config_control_panel; +extern int g_halt_sim; + +word32 g_slow_mem_changed[SLOW_MEM_CH_SIZE]; +word32 g_slow_mem_ch2[SLOW_MEM_CH_SIZE]; + +word32 g_a2font_bits[0x100][8]; + +word32 g_superhires_scan_save[2][256]; + +Kimage g_mainwin_kimage = { 0 }; +Kimage g_debugwin_kimage = { 0 }; +int g_debugwin_last_total = 0; + +extern int g_debug_lines_total; + +extern dword64 g_last_vbl_dfcyc; +extern dword64 g_video_pixel_dcount; + +dword64 g_video_dfcyc_check_input = 0; +int g_video_act_margin_left = BASE_MARGIN_LEFT; +int g_video_act_margin_right = BASE_MARGIN_RIGHT; +int g_video_act_margin_top = BASE_MARGIN_TOP; +int g_video_act_margin_bottom = BASE_MARGIN_BOTTOM; +int g_video_act_width = X_A2_WINDOW_WIDTH; +int g_video_act_height = X_A2_WINDOW_HEIGHT; +int g_mainwin_width = X_A2_WINDOW_WIDTH; +int g_mainwin_height = X_A2_WINDOW_HEIGHT + MAX_STATUS_LINES*16 + 2; +int g_mainwin_xpos = 100; +int g_mainwin_ypos = 300; +int g_video_no_scale_window = 0; + +word32 g_palette_change_cnt[2][16]; +int g_border_sides_refresh_needed = 1; +int g_border_special_refresh_needed = 1; +int g_border_line24_refresh_needed = 1; +int g_status_refresh_needed = 1; + +int g_vbl_border_color = 0; +int g_border_last_vbl_changes = 0; +int g_border_reparse = 0; + +int g_use_dhr140 = 0; +int g_use_bw_hires = 0; + +int g_vid_update_last_line = 0; +int g_video_save_all_stat_pos = 0; + +int g_cur_a2_stat = ALL_STAT_TEXT | ALL_STAT_ANNUNC3 | + (0xf << BIT_ALL_STAT_TEXT_COLOR); + +word32 g_palette_8to1624[2][256]; +word32 g_a2palette_1624[16]; + +word32 g_saved_line_palettes[2][200][8]; + +word32 g_cycs_in_refresh_line = 0; +word32 g_cycs_in_refresh_ximage = 0; +word32 g_cycs_in_run_16ms = 0; + +int g_num_lines_superhires = 0; +int g_num_lines_superhires640 = 0; +int g_num_lines_prev_superhires = 0; +int g_num_lines_prev_superhires640 = 0; + +int g_screen_redraw_skip_count = 0; +int g_screen_redraw_skip_amt = -1; + +word32 g_alpha_mask = 0; +word32 g_red_mask = 0xff; +word32 g_green_mask = 0xff; +word32 g_blue_mask = 0xff; +int g_red_left_shift = 16; +int g_green_left_shift = 8; +int g_blue_left_shift = 0; +int g_red_right_shift = 0; +int g_green_right_shift = 0; +int g_blue_right_shift = 0; + +int g_status_enable = 1; +int g_status_enable_previous = 1; +char g_status_buf[MAX_STATUS_LINES][STATUS_LINE_LENGTH + 1]; +char *g_status_ptrs[MAX_STATUS_LINES] = { 0 }; +word16 g_pixels_widened[128]; + +int g_video_scale_algorithm = 0; + +STRUCT(Video_all_stat) { + word32 lines_since_vbl; + word32 cur_all_stat; +}; + +#define MAX_VIDEO_ALL_STAT ((200*42) + 40) +int g_video_all_stat_pos = 0; +Video_all_stat g_video_all_stat[MAX_VIDEO_ALL_STAT]; + +STRUCT(Video_filt_stat) { + word32 line_bytes; + word32 filt_stat; +}; + +#define MAX_VIDEO_FILT_STAT 10000 +int g_video_filt_stat_pos = 0; +Video_filt_stat g_video_filt_stat[MAX_VIDEO_FILT_STAT]; + +int g_video_stat_old_pos = 0; +Video_filt_stat g_video_filt_stat_old[MAX_VIDEO_FILT_STAT]; + + +word16 g_dhires_convert[4096]; /* look up { next4, this4, prev 4 } */ + +const byte g_dhires_colors_16[] = { // Convert dhires to lores color + 0x00, /* 0x0 black */ + 0x02, /* 0x1 dark blue */ + 0x04, /* 0x2 dark green */ + 0x06, /* 0x3 medium blue */ + 0x08, /* 0x4 brown */ + 0x0a, /* 0x5 light gray */ + 0x0c, /* 0x6 green */ + 0x0e, /* 0x7 aquamarine */ + 0x01, /* 0x8 deep red */ + 0x03, /* 0x9 purple */ + 0x05, /* 0xa dark gray */ + 0x07, /* 0xb light blue */ + 0x09, /* 0xc orange */ + 0x0b, /* 0xd pink */ + 0x0d, /* 0xe yellow */ + 0x0f /* 0xf white */ +}; + +const int g_lores_colors[] = { // From IIgs Technote #63 + /* rgb */ + 0x000, /* 0x0 black */ + 0xd03, /* 0x1 deep red */ + 0x009, /* 0x2 dark blue */ + 0xd2d, /* 0x3 purple */ + 0x072, /* 0x4 dark green */ + 0x555, /* 0x5 dark gray */ + 0x22f, /* 0x6 medium blue */ + 0x6af, /* 0x7 light blue */ + 0x850, /* 0x8 brown */ + 0xf60, /* 0x9 orange */ + 0xaaa, /* 0xa light gray */ + 0xf98, /* 0xb pink */ + 0x1d0, /* 0xc green */ + 0xff0, /* 0xd yellow */ + 0x4f9, /* 0xe aquamarine */ + 0xfff /* 0xf white */ +}; + +const byte g_hires_lookup[64] = { +// Indexed by { next_bit, this_bit, prev_bit, hibit, odd_byte, odd_col }. +// Return lores colors: 0, 3, 6, 9, 0xc, 0xf + 0x00, // 00,0000 // black: this and next are 0 + 0x00, // 00,0001 + 0x00, // 00,0010 + 0x00, // 00,0011 + 0x00, // 00,0100 + 0x00, // 00,0101 + 0x00, // 00,0110 + 0x00, // 00,0111 + 0x00, // 00,1000 + 0x00, // 00,1001 + 0x00, // 00,1010 + 0x00, // 00,1011 + 0x00, // 00,1100 + 0x00, // 00,1101 + 0x00, // 00,1110 + 0x00, // 00,1111 + + 0x03, // 01,0000 // purple + 0x03, // 01,0001 // purple + 0x0c, // 01,0010 // green (odd column) + 0x0c, // 01,0011 // green + 0x06, // 01,0100 // blue + 0x06, // 01,0101 // blue + 0x09, // 01,0110 // orange + 0x09, // 01,0111 // orange + 0x0f, // 01,1000 // white: this and prev are 1 + 0x0f, // 01,1001 + 0x0f, // 01,1010 + 0x0f, // 01,1011 + 0x0f, // 01,1100 + 0x0f, // 01,1101 + 0x0f, // 01,1110 + 0x0f, // 01,1111 + + 0x00, // 10,0000 // black + 0x00, // 10,0001 // black + 0x00, // 10,0010 // black + 0x00, // 10,0011 // black + 0x00, // 10,0100 // black + 0x00, // 10,0101 // black + 0x00, // 10,0110 // black + 0x00, // 10,0111 // black + 0x0c, // 10,1000 // green + 0x0c, // 10,1001 // green + 0x03, // 10,1010 // purple + 0x03, // 10,1011 // purple + 0x09, // 10,1100 // orange + 0x09, // 10,1101 // orange + 0x06, // 10,1110 // blue + 0x06, // 10,1111 // blue + + 0x0f, // 11,0000 // white + 0x0f, // 11,0001 + 0x0f, // 11,0010 + 0x0f, // 11,0011 + 0x0f, // 11,0100 + 0x0f, // 11,0101 + 0x0f, // 11,0110 + 0x0f, // 11,0111 + 0x0f, // 11,1000 + 0x0f, // 11,1001 + 0x0f, // 11,1010 + 0x0f, // 11,1011 + 0x0f, // 11,1100 + 0x0f, // 11,1101 + 0x0f, // 11,1110 + 0x0f // 11,1111 +}; + +const int g_screen_index[] = { + 0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, + 0x028, 0x0a8, 0x128, 0x1a8, 0x228, 0x2a8, 0x328, 0x3a8, + 0x050, 0x0d0, 0x150, 0x1d0, 0x250, 0x2d0, 0x350, 0x3d0, + 0x078, 0x0f8, 0x178, 0x1f8, 0x278, 0x2f8, 0x378, 0x3f8 + // Last row is for float_bus() during VBL +}; + +byte g_font_array[256][8] = { +#include "kegsfont.h" +}; + +void +video_set_red_mask(word32 red_mask) +{ + video_set_mask_and_shift(red_mask, &g_red_mask, &g_red_left_shift, + &g_red_right_shift); +} + +void +video_set_green_mask(word32 green_mask) +{ + video_set_mask_and_shift(green_mask, &g_green_mask, &g_green_left_shift, + &g_green_right_shift); +} + +void +video_set_blue_mask(word32 blue_mask) +{ + video_set_mask_and_shift(blue_mask, &g_blue_mask, &g_blue_left_shift, + &g_blue_right_shift); +} + +void +video_set_alpha_mask(word32 alpha_mask) +{ + g_alpha_mask = alpha_mask; + printf("Set g_alpha_mask=%08x\n", alpha_mask); +} + +void +video_set_mask_and_shift(word32 x_mask, word32 *mask_ptr, int *shift_left_ptr, + int *shift_right_ptr) +{ + int shift; + int i; + + /* Shift until we find first set bit in mask, then remember mask,shift*/ + + shift = 0; + for(i = 0; i < 32; i++) { + if(x_mask & 1) { + /* we're done! */ + break; + } + x_mask = x_mask >> 1; + shift++; + } + *mask_ptr = x_mask; + *shift_left_ptr = shift; + /* Now, calculate shift_right_ptr */ + shift = 0; + x_mask |= 1; // make sure at least one bit is set + for(i = 0; i < 32; i++) { + if(x_mask >= 0x80) { + break; + } + shift++; + x_mask = x_mask << 1; + } + + *shift_right_ptr = shift; +} + +void +video_set_palette() +{ + int i; + + for(i = 0; i < 16; i++) { + video_update_color_raw(0, i, g_lores_colors[i]); + g_a2palette_1624[i] = g_palette_8to1624[0][i]; + } +} + +void +video_set_redraw_skip_amt(int amt) +{ + if(g_screen_redraw_skip_amt < amt) { + g_screen_redraw_skip_amt = amt; + printf("Set g_screen_redraw_skip_amt = %d\n", amt); + } +} + +Kimage * +video_get_kimage(int win_id) +{ + if(win_id == 0) { + return &g_mainwin_kimage; + } + if(win_id == 1) { + return &g_debugwin_kimage; + } + printf("win_id: %d not supported\n", win_id); + exit(1); +} + +char * +video_get_status_ptr(int line) +{ + if(line < MAX_STATUS_LINES) { + return g_status_ptrs[line]; + } + return 0; +} + +#if 0 +int +video_get_x_refresh_needed(Kimage *kimage_ptr) +{ + int ret; + + ret = kimage_ptr->x_refresh_needed; + kimage_ptr->x_refresh_needed = 0; + + return ret; +} +#endif + +void +video_set_x_refresh_needed(Kimage *kimage_ptr, int do_refresh) +{ + kimage_ptr->x_refresh_needed = do_refresh; +} + +int +video_get_active(Kimage *kimage_ptr) +{ + return kimage_ptr->active; +} + +void +video_set_active(Kimage *kimage_ptr, int active) +{ + kimage_ptr->active = active; + if(kimage_ptr != &g_mainwin_kimage) { + adb_nonmain_check(); + } +} + +void +video_init(int mdepth, int screen_width, int screen_height, int no_scale_window) +{ + word32 col[4]; + word32 val0, val1, val2, val3, next_col, next2_col; + word32 val, cur_col; + int i, j; + +// Initialize video system (called one-time only) + + g_video_no_scale_window = no_scale_window; + + for(i = 0; i < 200; i++) { + g_a2_line_left_edge[i] = 0; + g_a2_line_right_edge[i] = 0; + } + for(i = 0; i < 200; i++) { + g_a2_filt_stat[i] = -1; + for(j = 0; j < 8; j++) { + g_saved_line_palettes[0][i][j] = (word32)-1; + g_saved_line_palettes[1][i][j] = (word32)-1; + } + } + for(i = 0; i < 262; i++) { + g_cur_border_colors[i] = -1; + } + for(i = 0; i < 128; i++) { + val0 = i; + val1 = 0; + for(j = 0; j < 7; j++) { + val1 = val1 << 2; + if(val0 & 0x40) { + val1 |= 3; + } + val0 = val0 << 1; + } + g_pixels_widened[i] = val1; + } + + vid_printf("Zeroing out video memory, mdepth:%d\n", mdepth); + + for(i = 0; i < SLOW_MEM_CH_SIZE; i++) { + g_slow_mem_changed[i] = (word32)-1; + g_slow_mem_ch2[i] = 0; + } + + // create g_dhires_convert[] array + // TODO: Look at patent #4786893 for details on VGC and dhr + for(i = 0; i < 4096; i++) { + /* Convert index bits 11:0 where 3:0 is the previous color */ + /* and 7:4 is the current color to translate */ + /* Bit 4 will be the first pixel displayed on the screen */ + for(j = 0; j < 4; j++) { + cur_col = (i >> (1 + j)) & 0xf; + next_col = (i >> (2 + j)) & 0xf; + next2_col = (i >> (3 + j)) & 0xf; + cur_col = (((cur_col << 4) + cur_col) >> (3 - j)) & 0xf; + + if((cur_col == 0xf) || (next_col == 0xf) || + (next2_col == 0xf)) { + cur_col = 0xf; + col[j] = cur_col; + } else if((cur_col == 0) || (next_col == 0) || + (next2_col == 0)) { + cur_col = 0; + col[j] = cur_col; + } else { + col[j] = cur_col; + } + } + if(g_use_dhr140) { + for(j = 0; j < 4; j++) { + col[j] = (i >> 4) & 0xf; + } + } + val0 = g_dhires_colors_16[col[0] & 0xf]; + val1 = g_dhires_colors_16[col[1] & 0xf]; + val2 = g_dhires_colors_16[col[2] & 0xf]; + val3 = g_dhires_colors_16[col[3] & 0xf]; + val = val0 | (val1 << 4) | (val2 << 8) | (val3 << 12); + g_dhires_convert[i] = val; + if((i == 0x7bc) || (i == 0xfff)) { + //printf("g_dhires_convert[%03x] = %04x\n", i, val); + } + } + + video_init_kimage(&g_mainwin_kimage, X_A2_WINDOW_WIDTH, + X_A2_WINDOW_HEIGHT + MAX_STATUS_LINES*16 + 2, + screen_width, screen_height); + + video_init_kimage(&g_debugwin_kimage, 80*8 + 8 + 8, 25*16 + 8 + 8, + screen_width, screen_height); + + change_display_mode(g_cur_dfcyc); + video_reset(); + g_vid_update_last_line = 0; + g_video_all_stat_pos = 1; + g_video_all_stat[0].cur_all_stat = 0; + g_video_all_stat[0].lines_since_vbl = 0; + g_video_save_all_stat_pos = 0; + g_video_filt_stat_pos = 0; + video_update_status_enable(&g_mainwin_kimage); + video_update_through_line(262); + + printf("g_mainwin_kimage created and init'ed\n"); + fflush(stdout); +} + +int +video_clamp(int value, int min, int max) +{ + // Ensure value is >= min and <= max. If max <= min, return min + if(value > max) { + value = max; + } + if(value <= min) { + value = min; + } + return value; +} + +void +video_init_kimage(Kimage *kimage_ptr, int width, int height, + int screen_width, int screen_height) +{ + int x_width, x_height, a2_height, x_xpos, x_ypos; + int i; + + if(screen_width < width) { + screen_width = width; + } + if(screen_height < height) { + screen_width = height; + } + x_width = width; + x_height = height; + a2_height = height; + x_xpos = 100; + x_ypos = 300; + if(kimage_ptr == &g_mainwin_kimage) { + x_width = g_mainwin_width; + x_height = g_mainwin_height; + x_xpos = g_mainwin_xpos; + x_ypos = g_mainwin_ypos; + + // Handle status lines now + if(!g_status_enable) { + a2_height = g_video_act_margin_top + A2_WINDOW_HEIGHT + + g_video_act_margin_bottom; + } + } + + x_width = video_clamp(x_width, width, screen_width); + x_height = video_clamp(x_height, height, screen_height); + x_xpos = video_clamp(x_xpos, 0, screen_width - 640); + x_ypos = video_clamp(x_ypos, 0, screen_height - 420); + + kimage_ptr->wptr = (word32 *)calloc(1, width * (height + 2) * 4); + // Scaling routines read from line+1, expect it to be 0 + kimage_ptr->a2_width_full = width; + kimage_ptr->a2_height_full = height; + kimage_ptr->a2_width = width; + kimage_ptr->a2_height = a2_height; + kimage_ptr->x_width = x_width; + kimage_ptr->x_height = x_height; + kimage_ptr->x_refresh_needed = 1; + kimage_ptr->x_max_width = screen_width; + kimage_ptr->x_max_height = screen_height; + kimage_ptr->x_xpos = x_xpos; + kimage_ptr->x_ypos = x_ypos; + kimage_ptr->active = 0; + kimage_ptr->vbl_of_last_resize = 0; + kimage_ptr->c025_val = 0; + //printf("Created window, width:%d x_width:%d height:%d x_height:%d\n", + // width, x_width, height, x_height); + + kimage_ptr->scale_width_to_a2 = 0x10000; + kimage_ptr->scale_width_a2_to_x = 0x10000; + kimage_ptr->scale_height_to_a2 = 0x10000; + kimage_ptr->scale_height_a2_to_x = 0x10000; + kimage_ptr->num_change_rects = 0; + for(i = 0; i <= MAX_SCALE_SIZE; i++) { + kimage_ptr->scale_width[i] = i; + kimage_ptr->scale_height[i] = i; + } + + video_update_scale(kimage_ptr, x_width, x_height, 1); +} + +void +show_a2_line_stuff() +{ + int num, num_filt; + int i; + + for(i = 0; i < 200; i++) { + printf("line: %d: stat: %07x, " + "left_edge:%d, right_edge:%d\n", + i, g_a2_filt_stat[i], + g_a2_line_left_edge[i], + g_a2_line_right_edge[i]); + } + + num = g_video_all_stat_pos; + num_filt = g_video_stat_old_pos; + printf("cur_a2_stat:%04x, all_stat_pos:%d, num_filt:%d\n", + g_cur_a2_stat, num, num_filt); + for(i = 0; i < num; i++) { + printf("all_stat[%3d]=%08x stat:%08x\n", i, + g_video_all_stat[i].lines_since_vbl, + g_video_all_stat[i].cur_all_stat); + } + for(i = 0; i < num_filt; i++) { + printf("filt[%3d]=%08x filt_stat:%08x\n", i, + g_video_filt_stat_old[i].line_bytes, + g_video_filt_stat_old[i].filt_stat); + } +} + +int g_flash_count = 0; + +void +video_reset() +{ + int stat; + int i; + + voc_reset(); + + stat = ALL_STAT_TEXT | ALL_STAT_ANNUNC3 | + (0xf << BIT_ALL_STAT_TEXT_COLOR); + if(g_use_bw_hires) { + stat |= ALL_STAT_COLOR_C021; + } + if(g_config_control_panel) { + /* Don't update cur_a2_stat when in configuration panel */ + //g_save_cur_a2_stat = stat; + } else { + g_cur_a2_stat = stat; + } + + for(i = 0; i < 16; i++) { + g_palette_change_cnt[0][i] = 0; + g_palette_change_cnt[1][i] = 0; + } +} + +word32 g_cycs_in_check_input = 0; + +void +video_update() +{ + int did_video; + + if(g_fatal_log > 0) { + // NOT IMPLEMENTED YET + //adb_all_keys_up(); + clear_fatal_logs(); + } + if(g_status_enable != g_status_enable_previous) { + g_status_enable_previous = g_status_enable; + video_update_status_enable(&g_mainwin_kimage); + } + + debugger_redraw_screen(&g_debugwin_kimage); + if(g_config_control_panel) { + return; // Nothing else to do + } + + g_screen_redraw_skip_count--; + did_video = 0; + if(g_screen_redraw_skip_count < 0) { + did_video = 1; + video_copy_changed2(); + video_update_event_line(262); + update_border_info(); + g_screen_redraw_skip_count = g_screen_redraw_skip_amt; + } + + /* update flash */ + g_flash_count++; + if(g_flash_count >= 16) { + g_flash_count = 0; + g_cur_a2_stat ^= ALL_STAT_FLASH_STATE; + change_display_mode(g_cur_dfcyc); + } + + if(did_video) { + g_vid_update_last_line = 0; + g_video_all_stat_pos = 1; + g_video_all_stat[0].cur_all_stat = g_cur_a2_stat; + g_video_all_stat[0].lines_since_vbl = 0; + g_video_save_all_stat_pos = 0; + g_video_filt_stat_pos = 0; + } +} + +word32 +video_all_stat_to_filt_stat(int line, word32 new_all_stat) +{ + word32 filt_stat, merge_mask, mix_t_gr; + + filt_stat = new_all_stat & ALL_STAT_TEXT; + merge_mask = 0; + if((new_all_stat & ALL_STAT_ST80) == 0) { + merge_mask = ALL_STAT_PAGE2; + } + mix_t_gr = new_all_stat & ALL_STAT_MIX_T_GR; + if(new_all_stat & ALL_STAT_SUPER_HIRES) { + filt_stat = ALL_STAT_SUPER_HIRES; + merge_mask = ALL_STAT_VOC_INTERLACE | ALL_STAT_VOC_MAIN; + } else if(line >= 192) { + filt_stat = ALL_STAT_BORDER; + } else if(filt_stat || (line >= 160 && mix_t_gr)) { + // text mode + filt_stat |= ALL_STAT_TEXT; + merge_mask |= ALL_STAT_ALTCHARSET | ALL_STAT_BG_COLOR | + ALL_STAT_TEXT_COLOR | ALL_STAT_VID80; + if((new_all_stat & ALL_STAT_ALTCHARSET) == 0) { + merge_mask |= ALL_STAT_FLASH_STATE; + } + } else { + // GR or Hires + merge_mask |= ALL_STAT_ANNUNC3 | ALL_STAT_HIRES; + if((new_all_stat & ALL_STAT_ANNUNC3) == 0) { + // AN3 must be 0 to enable dbl-lores or dbl-hires + merge_mask |= ALL_STAT_VID80; + } + if(new_all_stat & ALL_STAT_HIRES) { + merge_mask |= ALL_STAT_COLOR_C021 | + ALL_STAT_DIS_COLOR_DHIRES; + } + } + filt_stat = filt_stat | (new_all_stat & merge_mask); + return filt_stat; +} + +void +change_display_mode(dword64 dfcyc) +{ + word32 lines_since_vbl; + + lines_since_vbl = get_lines_since_vbl(dfcyc); + video_add_new_all_stat(dfcyc, lines_since_vbl); + +} + +void +video_add_new_all_stat(dword64 dfcyc, word32 lines_since_vbl) +{ + word32 my_start, first_start, prev_lines_since_vbl; + int pos, prev; + + pos = g_video_all_stat_pos; + my_start = lines_since_vbl & 0x1ff00; + first_start = my_start + 24; + if(lines_since_vbl >= (200 << 8)) { + return; // In VBL, don't log this + } + if(pos && (lines_since_vbl < first_start)) { + prev = pos - 1; + prev_lines_since_vbl = g_video_all_stat[prev].lines_since_vbl; + // If the previous toggle has the same line, and it is before + // offset 24, then ignore it and overwrite it + if((my_start <= prev_lines_since_vbl) && + (prev_lines_since_vbl < first_start)) { + // needless toggling during HBL, just toss earlier + pos = prev; + } + } + g_video_all_stat[pos].lines_since_vbl = lines_since_vbl; + g_video_all_stat[pos].cur_all_stat = g_cur_a2_stat; + if(!g_halt_sim || g_config_control_panel) { + dbg_log_info(dfcyc, g_cur_a2_stat, lines_since_vbl, + (pos << 16) | 0x102); + } + pos++; + if(pos >= MAX_VIDEO_ALL_STAT) { + pos--; + } + g_video_all_stat_pos = pos; +} + +#define MAX_BORDER_CHANGES 16384 + +STRUCT(Border_changes) { + word32 usec; + int val; +}; + +Border_changes g_border_changes[MAX_BORDER_CHANGES]; +int g_num_border_changes = 0; + +void +change_border_color(dword64 dfcyc, int val) +{ + int pos; + + pos = g_num_border_changes; + g_border_changes[pos].usec = (word32)((dfcyc - g_last_vbl_dfcyc) >> 16); + g_border_changes[pos].val = val; + + pos++; + g_num_border_changes = pos; + + if(pos >= MAX_BORDER_CHANGES) { + halt_printf("num border changes: %d\n", pos); + g_num_border_changes = 0; + } +} + +void +update_border_info() +{ + dword64 drecip_usec, dline; + word32 usec; + int offset, new_line_offset, last_line_offset, new_line, new_val; + int limit, color_now; + int i; + + /* to get this routine to redraw the border, change */ + /* g_vbl_border_color, set g_border_last_vbl_changes = 1 */ + /* and change the cur_border_colors[] array */ + + color_now = g_vbl_border_color; + + drecip_usec = (65536LL * 65536LL) / 65; + limit = g_num_border_changes; + if(g_border_last_vbl_changes || limit || g_border_reparse) { + /* add a dummy entry */ + g_border_changes[limit].usec = CYCLES_IN_16MS_RAW + 21; + g_border_changes[limit].val = (g_c034_val & 0xf); + limit++; + } + last_line_offset = (((word32)-1L) << 8) + 44; + for(i = 0; i < limit; i++) { + usec = g_border_changes[i].usec; + dline = usec * drecip_usec; + new_line = dline >> 32; + offset = ((dword64)(word32)dline * 65ULL) >> 32; + + /* here comes the tricky part */ + /* offset is from 0 to 65, where 0-3 is the right border of */ + /* the previous line, 4-20 is horiz blanking, 21-24 is the */ + /* left border and 25-64 is the main window */ + /* Convert this to a new notation which is 0-3 is the left */ + /* border, 4-43 is the main window, and 44-47 is the right */ + /* basically, add -21 to offset, and wrap < 0 to previous ln */ + /* note this makes line -1 offset 44-47 the left hand border */ + /* for true line 261 on the screen */ + offset -= 21; + if(offset < 0) { + new_line--; + offset += 64; + } + new_val = g_border_changes[i].val; + new_line_offset = (new_line << 8) + offset; + + if((new_line_offset < -256) || + (new_line_offset > (262*256 + 0x80))) { + printf("new_line_offset: %05x\n", new_line_offset); + new_line_offset = last_line_offset; + } + while(last_line_offset < new_line_offset) { + /* see if this will finish it */ + if((last_line_offset & -256)==(new_line_offset & -256)){ + update_border_line(last_line_offset, + new_line_offset, color_now); + last_line_offset = new_line_offset; + } else { + update_border_line(last_line_offset, + (last_line_offset & -256) + 65, + color_now); + last_line_offset =(last_line_offset & -256)+256; + } + } + + color_now = new_val; + } + +#if 0 + if(g_num_border_changes) { + printf("Border changes: %d\n", g_num_border_changes); + } +#endif + + if(limit > 1) { + g_border_last_vbl_changes = 1; + } else { + g_border_last_vbl_changes = 0; + } + + g_num_border_changes = 0; + g_border_reparse = 0; + g_vbl_border_color = (g_c034_val & 0xf); +} + +void +update_border_line(int st_line_offset, int end_line_offset, int color) +{ + word32 filt_stat; + int st_offset, end_offset, left, right, width, line; + + line = st_line_offset >> 8; + if(line != (end_line_offset >> 8)) { + halt_printf("ubl, %04x %04x %02x!\n", st_line_offset, + end_line_offset, color); + } + if(line < -1 || line >= 262) { + halt_printf("ubl-b, mod line is %d\n", line); + line = 0; + } + if(line < 0 || line >= 262) { + line = 0; + } + + st_offset = st_line_offset & 0xff; + end_offset = end_line_offset & 0xff; + + if((st_offset == 0) && (end_offset >= 0x41) && !g_border_reparse) { + /* might be the same as last time, save some work */ + if(g_cur_border_colors[line] == color) { + return; + } + g_cur_border_colors[line] = color; + } else { + g_cur_border_colors[line] = -1; + } + + /* 0-3: left border, 4-43: main window, 44-47: right border */ + /* 48-65: horiz blanking */ + /* first, do the sides from line 0 to line 199 */ + if((line < 200) || (line >= 262)) { + if(line >= 262) { + line = 0; + } + if(st_offset < 4) { + /* left side */ + left = st_offset; + right = MY_MIN(4, end_offset); + video_border_pixel_write(&g_mainwin_kimage, + g_video_act_margin_top + 2*line, 2, color, + (left * BORDER_WIDTH)/4, + (right * BORDER_WIDTH) / 4); + + g_border_sides_refresh_needed = 1; + } + if((st_offset < 48) && (end_offset >= 44)) { + /* right side */ + filt_stat = g_a2_filt_stat[line]; + width = BORDER_WIDTH; + if((filt_stat & ALL_STAT_SUPER_HIRES) == 0) { + width += 80; + } + left = MY_MAX(0, st_offset - 44); + right = MY_MIN(4, end_offset - 44); + video_border_pixel_write(&g_mainwin_kimage, + g_video_act_margin_top + 2*line, 2, color, + X_A2_WINDOW_WIDTH - width + + (left * width/4), + X_A2_WINDOW_WIDTH - width + + (right * width/4)); + g_border_sides_refresh_needed = 1; + } + } + + if((line >= 192) && (line < 200)) { + filt_stat = g_a2_filt_stat[line]; + if((filt_stat & ALL_STAT_BORDER) && (st_offset < 44) && + (end_offset > 4)) { + left = MY_MAX(0, st_offset - 4); + right = MY_MIN(40, end_offset - 4); + video_border_pixel_write(&g_mainwin_kimage, + g_video_act_margin_top + 2*line, 2, color, + g_video_act_margin_left + (left * 640 / 40), + g_video_act_margin_left + (right * 640 / 40)); + g_border_line24_refresh_needed = 1; + } + } + + /* now do the bottom, lines 200 to 215 */ + if((line >= 200) && (line < (200 + BASE_MARGIN_BOTTOM/2)) ) { + line -= 200; + left = st_offset; + right = MY_MIN(48, end_offset); + video_border_pixel_write(&g_mainwin_kimage, + g_video_act_margin_top + 200*2 + 2*line, 2, + color, + (left * X_A2_WINDOW_WIDTH / 48), + (right * X_A2_WINDOW_WIDTH / 48)); + g_border_special_refresh_needed = 1; + } + + /* and top, lines 236 to 262 */ + if((line >= (262 - BASE_MARGIN_TOP/2)) && (line < 262)) { + line -= (262 - BASE_MARGIN_TOP/2); + left = st_offset; + right = MY_MIN(48, end_offset); + video_border_pixel_write(&g_mainwin_kimage, 2*line, 2, color, + (left * X_A2_WINDOW_WIDTH / 48), + (right * X_A2_WINDOW_WIDTH / 48)); + g_border_special_refresh_needed = 1; + } +} + +void +video_border_pixel_write(Kimage *kimage_ptr, int starty, int num_lines, + int color, int st_off, int end_off) +{ + word32 *wptr, *wptr0; + word32 pixel; + int width, width_full, offset; + int i, j; + + if(end_off <= st_off) { + return; + } + + width = end_off - st_off; + width_full = kimage_ptr->a2_width_full; + + if(width > width_full) { + halt_printf("border write but width %d > act %d\n", width, + width_full); + return; + } + if((starty + num_lines) > kimage_ptr->a2_height) { + halt_printf("border write line %d, > act %d\n", + starty+num_lines, kimage_ptr->a2_height); + return; + } + pixel = g_a2palette_1624[color & 0xf]; + + offset = starty * width_full; + wptr0 = kimage_ptr->wptr; + wptr0 += offset; + for(i = 0; i < num_lines; i++) { + wptr = wptr0 + st_off; + for(j = 0; j < width; j++) { + *wptr++ = pixel; + } + wptr0 += width_full; + } +} + +word32 +video_get_ch_mask(word32 mem_ptr, word32 filt_stat, int reparse) +{ + word32 ch_mask, mask; + int shift; + + if(reparse) { + return (word32)-1; + } + shift = (mem_ptr >> SHIFT_PER_CHANGE) & 0x1f; + mask = (1 << (40 >> SHIFT_PER_CHANGE)) - 1; + ch_mask = g_slow_mem_changed[mem_ptr >> CHANGE_SHIFT] | + g_slow_mem_ch2[mem_ptr >> CHANGE_SHIFT]; + if(filt_stat & ALL_STAT_VID80) { + mem_ptr += 0x10000; + ch_mask |= (g_slow_mem_changed[mem_ptr >> CHANGE_SHIFT]); + ch_mask |= (g_slow_mem_ch2[mem_ptr >> CHANGE_SHIFT]); + } + ch_mask = (ch_mask >> shift) & mask; + + return ch_mask; +} + +void +video_update_edges(int line, int left, int right, const char *str) +{ + g_a2_line_left_edge[line] = MY_MIN(left, g_a2_line_left_edge[line]); + g_a2_line_right_edge[line] = MY_MAX(right, g_a2_line_right_edge[line]); + + if((left < 0) || (right < 0) || (left > 640) || (right > 640)) { + printf("video_update_edges: %s: line %d: %d (left) >= %d " + "(right)\n", str, line, left, right); + } +} + +void +redraw_changed_text(word32 line_bytes, int reparse, word32 *in_wptr, + int pixels_per_line, word32 filt_stat) +{ + byte str_buf[81]; + byte *slow_mem_ptr; + word32 ch_mask, line_mask, mem_ptr, val0, val1, bg_pixel, fg_pixel; + int flash_state, y, bg_color, fg_color, start_line; + int x1, x2; + + // Redraws a single line, will be called over 8 lines to finish a byte. + + start_line = line_bytes >> 16; + bg_color = (filt_stat >> BIT_ALL_STAT_BG_COLOR) & 0xf; + fg_color = (filt_stat >> BIT_ALL_STAT_TEXT_COLOR) & 0xf; + bg_pixel = g_a2palette_1624[bg_color]; + fg_pixel = g_a2palette_1624[fg_color]; + + y = start_line >> 3; + line_mask = 1 << y; + mem_ptr = 0x400 + g_screen_index[y]; + if(filt_stat & ALL_STAT_PAGE2) { + mem_ptr += 0x400; + } + if((mem_ptr < 0x400) || (mem_ptr >= 0xc00)) { + halt_printf("redraw_changed_text: mem_ptr: %08x, y:%d\n", + mem_ptr, y); + return; + } + + ch_mask = video_get_ch_mask(mem_ptr, filt_stat, reparse); + if(!ch_mask) { + return; + } + + g_a2_screen_buffer_changed |= line_mask; + x2 = 0; + slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr]); + flash_state = -0x40; + if(g_cur_a2_stat & ALL_STAT_FLASH_STATE) { + flash_state = 0x40; + } + + for(x1 = 0; x1 < 40; x1++) { + val0 = slow_mem_ptr[0x10000]; + val1 = *slow_mem_ptr++; + if(!(filt_stat & ALL_STAT_ALTCHARSET)) { + if((val0 >= 0x40) && (val0 < 0x80)) { + val0 += flash_state; + } + if((val1 >= 0x40) && (val1 < 0x80)) { + val1 += flash_state; + } + } + if(filt_stat & ALL_STAT_VID80) { + str_buf[x2++] = val0; // aux mem + } + str_buf[x2++] = val1; // main mem + } + str_buf[x2] = 0; // null terminate + + redraw_changed_string(&str_buf[0], line_bytes, ch_mask, in_wptr, + bg_pixel, fg_pixel, pixels_per_line, + (filt_stat & ALL_STAT_VID80)); +} + +void +redraw_changed_string(const byte *bptr, word32 line_bytes, word32 ch_mask, + word32 *in_wptr, word32 bg_pixel, + word32 fg_pixel, int pixels_per_line, int dbl) +{ + register word32 start_time, end_time; + word32 *wptr; + word32 val0, val1, val2, val3, pixel; + int left, right, st_line_mod8, offset, pos, shift, start_line; + int start_byte, end_byte; + int x1, j; + + left = 40; + right = 0; + + GET_ITIMER(start_time); + + start_line = line_bytes >> 16; + start_byte = line_bytes & 0x3f; + end_byte = (line_bytes >> 8) & 0x3f; + st_line_mod8 = start_line & 7; + + for(x1 = start_byte; x1 < end_byte; x1++) { + shift = x1 >> SHIFT_PER_CHANGE; + if(((ch_mask >> shift) & 1) == 0) { + continue; + } + + left = MY_MIN(x1, left); + right = MY_MAX(x1 + 1, right); + offset = (start_line * 2 * pixels_per_line) + x1*14; + pos = x1; + if(dbl) { + pos = pos * 2; + } + + wptr = in_wptr + offset; + + val0 = bptr[pos]; + if(dbl) { + pos++; + } + val1 = bptr[pos++]; + val2 = g_a2font_bits[val0][st_line_mod8]; + val3 = g_a2font_bits[val1][st_line_mod8]; + // val2, [6:0] is 80-column character bits, and + // [21:8] are the 40-column char bits (double-wide) + if(dbl) { + val2 = (val3 << 7) | (val2 & 0x7f); + } else { + val2 = val3 >> 8; // 40-column format + } + for(j = 0; j < 14; j++) { + pixel = bg_pixel; + if(val2 & 1) { // LSB is first pixel + pixel = fg_pixel; + } + wptr[pixels_per_line] = pixel; + *wptr++ = pixel; + val2 = val2 >> 1; + } + } + GET_ITIMER(end_time); + if(start_line < 200) { + video_update_edges(start_line, left * 14, right * 14, "text"); + } + + if((left >= right) || (left < 0) || (right < 0)) { + printf("str line %d, 40: left >= right: %d >= %d\n", + start_line, left, right); + printf(" line_bytes:%08x ch_mask:%08x\n", line_bytes, ch_mask); + } + + g_cycs_in_40col += (end_time - start_time); +} + +// gr with an3=0: +// 0=0 +// 1,0=3 (purple). 1,1=0 +// 2,0=c (green). 2,1=0 +// 3,0=f (white). 3,1=0 +// 4,0=0. 4,1=c (green) +// 5,0=3 (purple). 5,1=c (green) +// 6,0=c (green). 6,1=c (green) +// 7,0=f (white). 7,1=c (green) +// 8,0=0 (black). 7,1=3 (purple) +// 9,0=3 (purple). 9,1=3 (purple) +// a,0=c (green). a,1=3 (purple) +// b,0=f (white). b,1=3 (purple) +// c,0=0 (black). c,1=f (white) +// d,0=3 (purple). d,1=f (white) +// e,0=c (green). e,1=f (white) +// f,0=f (white). e,1=f (white) + +void +redraw_changed_gr(word32 line_bytes, int reparse, word32 *in_wptr, + int pixels_per_line, word32 filt_stat) +{ + word32 *wptr; + byte *slow_mem_ptr; + word32 line_mask, mem_ptr, val0, val1, pixel0, pixel1, ch_mask; + int y, shift, left, right, st_line_mod8, start_line, offset; + int start_byte, end_byte; + int x1, i; + + start_line = line_bytes >> 16; + st_line_mod8 = start_line & 7; + + y = start_line >> 3; + line_mask = 1 << (y); + mem_ptr = 0x400 + g_screen_index[y]; + if(filt_stat & ALL_STAT_PAGE2) { + mem_ptr += 0x400; + } + if((mem_ptr < 0x400) || (mem_ptr >= 0xc00)) { + halt_printf("redraw_changed_gr: mem_ptr: %08x, y:%d\n", + mem_ptr, y); + return; + } + + ch_mask = video_get_ch_mask(mem_ptr, filt_stat, reparse); + if(!ch_mask) { + return; + } + + g_a2_screen_buffer_changed |= line_mask; + + left = 40; + right = 0; + + slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr]); + offset = (start_line * 2 * pixels_per_line); + start_byte = line_bytes & 0x3f; + end_byte = (line_bytes >> 8) & 0x3f; + for(x1 = start_byte; x1 < end_byte; x1++) { + shift = x1 >> SHIFT_PER_CHANGE; + if(((ch_mask >> shift) & 1) == 0) { + continue; + } + + left = MY_MIN(x1, left); + right = MY_MAX(x1 + 1, right); + + wptr = in_wptr + offset + x1*14; + + val0 = slow_mem_ptr[0x10000 + x1]; + val1 = slow_mem_ptr[x1]; + + if(st_line_mod8 >= 4) { + val0 = val0 >> 4; + val1 = val1 >> 4; + } + if(filt_stat & ALL_STAT_VID80) { + // aux pixel is { [2:0],[3] } + val0 = (val0 << 1) | ((val0 >> 3) & 1); + } else if((filt_stat & ALL_STAT_ANNUNC3) == 0) { + if(x1 & 1) { // odd cols + val0 = ((val1 >> 1) & 2) | ((val1 >> 3) & 1); + } else { + val0 = val1 & 3; // even cols + } + // map val0: 0->0, 1->3, 2->c, 3->f + val1 = 0; + if(val0 & 1) { + val1 |= 3; + } + if(val0 & 2) { + val1 |= 0xc; + } + val0 = val1; + } else { + val0 = val1; + } + pixel0 = g_a2palette_1624[val0 & 0xf]; + pixel1 = g_a2palette_1624[val1 & 0xf]; + for(i = 0; i < 7; i++) { + wptr[pixels_per_line] = pixel0; + wptr[pixels_per_line + 7] = pixel1; + wptr[0] = pixel0; + wptr[7] = pixel1; + wptr++; + } + } + + video_update_edges(start_line, left * 14, right * 14, "gr"); +} + +void +video_hgr_line_segment(byte *slow_mem_ptr, word32 *wptr, int start_byte, + int end_byte, int pixels_per_line, word32 filt_stat) +{ + word32 val0, val1, val2, prev_bits, val1_hi, dbl_step, pixel, color; + word32 monochrome; + int shift; + int x2, i; + + monochrome = filt_stat & (ALL_STAT_COLOR_C021 | + ALL_STAT_DIS_COLOR_DHIRES); + + prev_bits = 0; + if(start_byte) { + prev_bits = (slow_mem_ptr[-1] >> 3) & 0xf; + if(!(filt_stat & ALL_STAT_VID80)) { + // prev_bits is 4 bits, widen to 8 for std HGR + prev_bits = g_pixels_widened[prev_bits] >> 4; + } + prev_bits = prev_bits & 0xf; + } + for(x2 = start_byte; x2 < end_byte; x2++) { + val0 = slow_mem_ptr[0x10000]; + val1 = *slow_mem_ptr++; + val2 = slow_mem_ptr[0x10000]; // next pixel, aux mem + if(x2 >= 39) { + val2 = 0; + } + val1_hi = ((val1 >> 5) & 4) | ((x2 & 1) << 1); + // Hi-order bit in bit 2, odd pixel is in bit 0 + + dbl_step = 3; + if(filt_stat & ALL_STAT_VID80) { + // aux+1[6:0], main[6:0], aux[6:0], prev[3:0] + val0 = (val2 << 18) | ((val1 & 0x7f) << 11) | + ((val0 & 0x7f) << 4); + if(!monochrome && (x2 & 1)) { // Get 6 bits from prev + val0 = (val0 << 2); + dbl_step = 1; + } + val0 = val0 | prev_bits; + } else if(monochrome) { + val0 = g_pixels_widened[val1 & 0x7f] << 4; + } else { // color, normal hgr + val2 = g_pixels_widened[*slow_mem_ptr & 0x7f]; + if(x2 >= 39) { + val2 = 0; + } + val0 = ((val1 & 0x7f) << 4) | prev_bits | (val2 << 11); + if((filt_stat & ALL_STAT_ANNUNC3) == 0) { + val1_hi = val1_hi & 3; + } + } +#if 0 + if(st_line < 8) { + printf("hgrl %d c:%d,d:%d, off:%03x val0:%02x 1:%02x\n", + st_line, monochrome, dbl, x1 + x2, val0, val1); + } +#endif + for(i = 0; i < 14; i++) { + color = 0; // black + if(monochrome) { + if(val0 & 0x10) { + color = 0xf; // white + } + val0 = val0 >> 1; + } else { // color + if(filt_stat & ALL_STAT_VID80) { + color = g_dhires_convert[val0 & 0xfff]; + shift = (x2 + x2 + i) & 3; + color = color >> (4 * shift); + if((i & 3) == dbl_step) { + val0 = val0 >> 4; + } + } else { + val2 = (val0 & 0x38) ^ val1_hi ^(i & 3); + color = g_hires_lookup[val2 & 0x7f]; + if(i & 1) { + val0 = val0 >> 1; + } + } + } + pixel = g_a2palette_1624[color & 0xf]; + wptr[pixels_per_line] = pixel; + *wptr++ = pixel; + } + if((filt_stat & ALL_STAT_VID80) && ((x2 & 1) == 0)) { + prev_bits = val0 & 0x3f; + } else { + prev_bits = val0 & 0xf; + } + } +} + +void +redraw_changed_hgr(word32 line_bytes, int reparse, + word32 *in_wptr, int pixels_per_line, word32 filt_stat) +{ + word32 *wptr; + byte *slow_mem_ptr; + word32 ch_mask, line_mask, mem_ptr; + int y, shift, st_line_mod8, start_line, offset, start_byte; + int end_byte; + int x1; + + start_line = line_bytes >> 16; + start_byte = line_bytes & 0x3f; + end_byte = (line_bytes >> 8) & 0x3f; // Usually '40' + + y = start_line >> 3; + st_line_mod8 = start_line & 7; + line_mask = 1 << y; + mem_ptr = 0x2000 + g_screen_index[y] + (st_line_mod8 * 0x400); + if(filt_stat & ALL_STAT_PAGE2) { + mem_ptr += 0x2000; + } + if((mem_ptr < 0x2000) || (mem_ptr >= 0x6000)) { + halt_printf("redraw_changed_hgr: mem_ptr: %08x, y:%d\n", + mem_ptr, y); + return; + } + + ch_mask = video_get_ch_mask(mem_ptr, filt_stat, reparse); + if(ch_mask == 0) { + return; + } + + // Hires depends on adjacent bits, so also reparse adjacent regions + // to handle redrawing of pixels on the boundaries + ch_mask = ch_mask | (ch_mask >> 1) | (ch_mask << 1); + + g_a2_screen_buffer_changed |= line_mask; + + for(x1 = start_byte; x1 < end_byte; x1++) { + shift = x1 >> SHIFT_PER_CHANGE; + if(((ch_mask >> shift) & 1) == 0) { + continue; + } + + slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr + x1]); + offset = (start_line * 2 * pixels_per_line) + x1*14; + + wptr = in_wptr + offset; + video_hgr_line_segment(slow_mem_ptr, wptr, x1, end_byte, + pixels_per_line, filt_stat); + + video_update_edges(start_line, x1 * 14, end_byte * 14, "hgr"); + break; + } +} + +int +video_rebuild_super_hires_palette(int bank, word32 scan_info, int line, + int reparse) +{ + word32 *word_ptr; + byte *byte_ptr; + word32 ch_mask, mem_ptr, scan, old_scan, val0, val1; + int diffs, palette; + int j; + + palette = scan_info & 0xf; + + mem_ptr = (bank << 16) + 0x9e00 + (palette * 0x20); + ch_mask = video_get_ch_mask(mem_ptr, 0, 0); + + old_scan = g_superhires_scan_save[bank][line]; + scan = (scan_info & 0xfaf) + + (g_palette_change_cnt[bank][palette] << 12); + g_superhires_scan_save[bank][line] = scan; + +#if 0 + if(line == 1) { + word_ptr = (word32 *)&(g_slow_memory_ptr[0x19e00+palette*0x20]); + printf("y1vrshp, ch:%08x, s:%08x,os:%08x %d = %08x %08x %08x " + "%08x %08x %08x %08x %08x\n", + ch_mask, scan, old_scan, reparse, + word_ptr[0], word_ptr[1], word_ptr[2], word_ptr[3], + word_ptr[4], word_ptr[5], word_ptr[6], word_ptr[7]); + } +#endif + + diffs = reparse | ((scan ^ old_scan) & 0xf0f); + /* we must do full reparse if palette changed for this line */ + + if(!diffs && (ch_mask == 0) && (((scan ^ old_scan) & (~0xf0)) == 0)) { + /* nothing changed, get out fast */ + return 0; + } + + if(ch_mask) { + /* indicates the palette has changed, and other scan lines */ + /* using this palette need to do a full 32-byte compare to */ + /* decide if they need to update or not */ + g_palette_change_cnt[bank][palette]++; + } + + word_ptr = (word32 *)&(g_slow_memory_ptr[(bank << 16) + 0x9e00 + + palette*0x20]); + for(j = 0; j < 8; j++) { + if(word_ptr[j] != g_saved_line_palettes[bank][line][j]) { + diffs = 1; + break; + } + } + + if(diffs == 0) { + return 0; + } + + /* first, save this word_ptr into saved_line_palettes */ + byte_ptr = (byte *)word_ptr; + for(j = 0; j < 8; j++) { + g_saved_line_palettes[bank][line][j] = word_ptr[j]; + } + + byte_ptr = (byte *)word_ptr; + /* this palette has changed */ + for(j = 0; j < 16; j++) { + val0 = *byte_ptr++; + val1 = *byte_ptr++; + video_update_color_raw(bank, palette*16 + j, (val1<<8) + val0); + } + + return 1; +} + +word32 +redraw_changed_super_hires_oneline(int bank, word32 *in_wptr, + int pixels_per_line, int y, int scan, word32 ch_mask) +{ + word32 *palptr, *wptr; + byte *slow_mem_ptr; + word32 mem_ptr, val0, pal, pix0, pix1, pix2, pix3, save_pix; + int offset, shift_per, left, right, shift; + int x1, x2; + + mem_ptr = (bank << 16) + 0x2000 + (0xa0 * y); + + shift_per = (1 << SHIFT_PER_CHANGE); + pal = (scan & 0xf); + + save_pix = 0; + if(scan & 0x20) { // Fill mode + ch_mask = (word32)-1; + } + + palptr = &(g_palette_8to1624[bank][pal * 16]); + left = 160; + right = 0; + + for(x1 = 0; x1 < 0xa0; x1 += shift_per) { + shift = x1 >> SHIFT_PER_CHANGE; + if(((ch_mask >> shift) & 1) == 0) { + continue; + } + + left = MY_MIN(x1, left); + right = MY_MAX(x1 + shift_per, right); + + slow_mem_ptr = &(g_slow_memory_ptr[mem_ptr + x1]); + offset = x1*4; + + wptr = in_wptr + offset; + + for(x2 = 0; x2 < shift_per; x2++) { + val0 = *slow_mem_ptr++; + + if(scan & 0x80) { // 640 mode + pix0 = (val0 >> 6) & 3; + pix1 = (val0 >> 4) & 3; + pix2 = (val0 >> 2) & 3; + pix3 = val0 & 3; + pix0 = palptr[pix0 + 8]; + pix1 = palptr[pix1 + 12]; + pix2 = palptr[pix2 + 0]; + pix3 = palptr[pix3 + 4]; + } else { /* 320 mode */ + pix0 = (val0 >> 4); + pix2 = (val0 & 0xf); + if(scan & 0x20) { // Fill mode + if(!pix0) { // 0 = repeat last color + pix0 = save_pix; + } + if(!pix2) { + pix2 = pix0; + } + save_pix = pix2; + } + pix0 = palptr[pix0]; + pix1 = pix0; + pix2 = palptr[pix2]; + pix3 = pix2; + } + wptr[pixels_per_line] = pix0; + *wptr++ = pix0; + wptr[pixels_per_line] = pix1; + *wptr++ = pix1; + wptr[pixels_per_line] = pix2; + *wptr++ = pix2; + wptr[pixels_per_line] = pix3; + *wptr++ = pix3; + } + } + + return (left << 16) | (right & 0xffff); +} + +void +redraw_changed_super_hires_bank(int bank, int start_line, int reparse, + word32 *wptr, int pixels_per_line) +{ + dword64 dval, dval1; + word32 this_check, mask, tmp, scan, old_scan, mem_ptr; + int left, right, ret, shift; + + mem_ptr = (bank << 16) + 0x2000 + (160 * start_line); + dval1 = g_slow_mem_changed[(mem_ptr >> CHANGE_SHIFT) + 1] | + g_slow_mem_ch2[(mem_ptr >> CHANGE_SHIFT) + 1]; + dval = g_slow_mem_changed[mem_ptr >> CHANGE_SHIFT] | + g_slow_mem_ch2[mem_ptr >> CHANGE_SHIFT] | (dval1 << 32); + shift = (mem_ptr >> SHIFT_PER_CHANGE) & 0x1f; + mask = (1 << (160 >> SHIFT_PER_CHANGE)) - 1; + this_check = (dval >> shift) & mask; + + scan = g_slow_memory_ptr[(bank << 16) + 0x9d00 + start_line]; + + old_scan = g_superhires_scan_save[bank][start_line]; + + ret = video_rebuild_super_hires_palette(bank, scan, start_line, + reparse); + if(ret || reparse || ((scan ^ old_scan) & 0xa0)) { + /* 0x80 == mode640, 0x20 = fill */ + this_check = (word32)-1; + } + + if(!this_check) { + return; // Nothing to do, get out + } + + if(scan & 0x80) { // 640 mode + g_num_lines_superhires640++; + } + + if((scan >> 5) & 1) { // fill mode--redraw whole line + this_check = (word32)-1; + } + + g_a2_screen_buffer_changed |= (1 << (start_line >> 3)); + tmp = redraw_changed_super_hires_oneline(bank, wptr, pixels_per_line, + start_line, scan, this_check); + left = tmp >> 16; + right = tmp & 0xffff; + + video_update_edges(start_line, left * 4, right * 4, "shr"); +} + +void +redraw_changed_super_hires(word32 line_bytes, int reparse, word32 *wptr, + int pixels_per_line, word32 filt_stat) +{ + int bank, start_line; + + start_line = line_bytes >> 16; + wptr += start_line * 2 * pixels_per_line; + + if(filt_stat & ALL_STAT_VOC_INTERLACE) { + // Do 400 interlaced lines. Do aux first, then main mem + redraw_changed_super_hires_bank(1, start_line, reparse, wptr, + 0); + redraw_changed_super_hires_bank(0, start_line, reparse, + wptr + pixels_per_line, 0); + } else { + bank = 1; + if(filt_stat & ALL_STAT_VOC_MAIN) { + bank = 0; // VOC SHR in main memory + } + redraw_changed_super_hires_bank(bank, start_line, reparse, wptr, + pixels_per_line); + } +} + +void +video_copy_changed2() +{ + word32 *ch_ptr, *ch2_ptr; + int bank1_off; + int i; + + // Copy entries from g_slow_mem_changed[] to g_slow_mem_ch2[] and + // clear g_slow_mem_changed[] + ch_ptr = &g_slow_mem_changed[0]; + ch2_ptr = &g_slow_mem_ch2[0]; + bank1_off = 0x10000 >> CHANGE_SHIFT; + for(i = 4; i < 0xa0; i++) { // Pages 0x0400 through 0x9fff + ch2_ptr[i] = ch_ptr[i]; + ch2_ptr[i + bank1_off] = ch_ptr[i + bank1_off]; + ch_ptr[i] = 0; + ch_ptr[i + bank1_off] = 0; + } +} + +void +video_update_event_line(int line) +{ + int new_line; + + video_update_through_line(line); + + new_line = line + g_line_ref_amt; + if(new_line < 200) { + if(!g_config_control_panel && !g_halt_sim) { + add_event_vid_upd(new_line); + } + } else if(line >= 262) { + if(!g_config_control_panel && !g_halt_sim) { + add_event_vid_upd(0); /* add event for new screen */ + } + } +} + +void +video_force_reparse() +{ + word32 *wptr; + int height, width_full; + int i, j; + + g_video_stat_old_pos = 1; + g_video_filt_stat_old[0].filt_stat = (word32)-1; + height = g_video_act_margin_top + A2_WINDOW_HEIGHT + + g_video_act_margin_bottom; + height = MY_MIN(height, g_mainwin_kimage.a2_height); + width_full = g_mainwin_kimage.a2_width_full; + wptr = g_mainwin_kimage.wptr; + for(i = 0; i < height; i++) { + for(j = 0; j < width_full; j++) { + *wptr++ = 0; + } + } + g_border_reparse = 1; +} + +void +video_update_through_line(int line) +{ + register word32 start_time; + register word32 end_time; + word32 my_start_lines, my_end_lines, prev_all_stat, next_all_stat; + word32 prev_lines_since_vbl, next_lines_since_vbl; + int last_line, pos, last_pos, end, num; + int i; + +#if 0 + vid_printf("\nvideo_upd for line %d, lines: %06x\n", line, + get_lines_since_vbl(g_cur_dfcyc)); +#endif + + GET_ITIMER(start_time); + + last_line = MY_MIN(200, line+1); /* go through line, but not past 200 */ + + pos = g_video_save_all_stat_pos; + last_pos = g_video_all_stat_pos; + prev_all_stat = g_video_all_stat[pos].cur_all_stat; + prev_lines_since_vbl = g_video_all_stat[pos].lines_since_vbl; + g_video_all_stat[last_pos].cur_all_stat = g_cur_a2_stat; + g_video_all_stat[last_pos].lines_since_vbl = (line + 1) << 8; + next_all_stat = g_video_all_stat[pos+1].cur_all_stat; + next_lines_since_vbl = g_video_all_stat[pos+1].lines_since_vbl; + for(i = g_vid_update_last_line; i < last_line; i++) { + // We need to step through pos in g_video_all_stat[] and find + // the start/end pairs for each line + g_a2_line_left_edge[i] = 640; + g_a2_line_right_edge[i] = 0; + my_start_lines = (i << 8) + 25; + my_end_lines = (i << 8) + 65; + if(prev_lines_since_vbl > my_start_lines) { + printf("prev:%08x > %08x start at i:%d\n", + prev_lines_since_vbl, my_start_lines, i); + } + while(my_start_lines < my_end_lines) { + while(next_lines_since_vbl <= my_start_lines) { + // Step into next entry + prev_all_stat = next_all_stat; + prev_lines_since_vbl = next_lines_since_vbl; + pos++; + g_video_save_all_stat_pos = pos; + next_all_stat = + g_video_all_stat[pos+1].cur_all_stat; + next_lines_since_vbl = + g_video_all_stat[pos+1].lines_since_vbl; + if(pos >= last_pos) { + printf("FELL OFF %d %d!\n", pos, + last_pos); + pos--; + break; + } + } + end = 65; + if(next_lines_since_vbl < my_end_lines) { + end = (next_lines_since_vbl & 0xff); + if(end < 25) { + printf("i:%d next_lines_since_vbl:" + "%08x!\n", i, + next_lines_since_vbl); + end = 25; + } + } + video_do_partial_line(my_start_lines, end, + prev_all_stat); + my_start_lines = (i << 8) + end; + } + } + + + g_vid_update_last_line = last_line; + g_video_save_all_stat_pos = pos; + + /* deal with border and forming rects for xdriver.c to use */ + if(line >= 262) { + if(g_num_lines_prev_superhires != g_num_lines_superhires) { + /* switched in/out from superhires--refresh borders */ + g_border_sides_refresh_needed = 1; + } + + video_form_change_rects(); + g_num_lines_prev_superhires = g_num_lines_superhires; + g_num_lines_prev_superhires640 = g_num_lines_superhires640; + g_num_lines_superhires = 0; + g_num_lines_superhires640 = 0; + + num = g_video_filt_stat_pos; + g_video_stat_old_pos = num; + for(i = 0; i < num; i++) { + g_video_filt_stat_old[i] = g_video_filt_stat[i]; + } + g_video_filt_stat_pos = 0; + } + GET_ITIMER(end_time); + g_cycs_in_refresh_line += (end_time - start_time); +} + +extern word32 g_vbl_count; + +void +video_do_partial_line(word32 lines_since_vbl, int end, word32 cur_all_stat) +{ + word32 filt_stat, old_filt_stat, line_bytes, old_line_bytes; + int pos, old_pos, reparse, line; + + pos = g_video_filt_stat_pos; + old_pos = g_video_stat_old_pos; + filt_stat = video_all_stat_to_filt_stat(lines_since_vbl >> 8, + cur_all_stat); + line_bytes = ((lines_since_vbl & 0x1ff00) << 8) | + ((end - 25) << 8) | ((lines_since_vbl - 25) & 0x3f); + g_video_filt_stat[pos].line_bytes = line_bytes; + g_video_filt_stat[pos].filt_stat = filt_stat; + reparse = 1; + old_filt_stat = (word32)-1; + old_line_bytes = (word32)-1; + if(pos < old_pos) { + old_filt_stat = g_video_filt_stat_old[pos].filt_stat; + old_line_bytes = g_video_filt_stat_old[pos].line_bytes; + } + if((old_filt_stat == filt_stat) && (line_bytes == old_line_bytes)) { + reparse = 0; + } else if((old_filt_stat ^ filt_stat) & ALL_STAT_SUPER_HIRES) { + g_border_reparse = 1; + } + video_refresh_line(line_bytes, reparse, filt_stat); + line = lines_since_vbl >> 8; + if(line < 200) { + g_a2_filt_stat[line] = filt_stat; + } else { + printf("partial_line %08x %d %08x out of range!\n", + lines_since_vbl, end, cur_all_stat); + } + if((end <= 25) || (end < (int)(lines_since_vbl & 0xff))) { + printf("Bad lsv:%08x, end:%d, stat:%08x\n", lines_since_vbl, + end, filt_stat); + } + pos++; + if(pos >= MAX_VIDEO_FILT_STAT) { + pos--; + } + g_video_filt_stat_pos = pos; +} + +void +video_refresh_line(word32 line_bytes, int must_reparse, word32 filt_stat) +{ + word32 *wptr; + int pixels_per_line, offset, line; + + line = line_bytes >> 16; + if((word32)line >= 200) { + printf("video_refresh %08x %d %08x!\n", line_bytes, + must_reparse, filt_stat); + return; + } + wptr = g_mainwin_kimage.wptr; + pixels_per_line = g_mainwin_kimage.a2_width_full; + offset = (pixels_per_line * g_video_act_margin_top) + + g_video_act_margin_left; + wptr = wptr + offset; + + if(filt_stat & ALL_STAT_SUPER_HIRES) { + g_num_lines_superhires++; + redraw_changed_super_hires(line_bytes, must_reparse, wptr, + pixels_per_line, filt_stat); + } else if(filt_stat & ALL_STAT_BORDER) { + if(line < 192) { + halt_printf("Border line not 192: %d\n", line); + } + g_a2_line_left_edge[line] = 0; + g_a2_line_right_edge[line] = 560; + if(g_border_line24_refresh_needed) { + g_border_line24_refresh_needed = 0; + g_a2_screen_buffer_changed |= (1 << 24); + } + } else if(filt_stat & ALL_STAT_TEXT) { + redraw_changed_text(line_bytes, must_reparse, wptr, + pixels_per_line, filt_stat); + } else if(filt_stat & ALL_STAT_HIRES) { + redraw_changed_hgr(line_bytes, must_reparse, wptr, + pixels_per_line, filt_stat); + } else { + redraw_changed_gr(line_bytes, must_reparse, wptr, + pixels_per_line, filt_stat); + } +} + +void +prepare_a2_font() +{ + word32 val0, val1, val2; + int i, j, k; + + // Prepare g_a2font_bits[char][line] where each entry indicates the + // set pixels in this line of the character. Bits 6:0 are an + // 80-column character, and bits 21:8 are the 14 expanded bits of a + // 40-columns character, both with the first visible bit at the + // rightmost bit address. But g_font_array[] is in big-endian bit + // order, which is less useful + for(i = 0; i < 256; i++) { + for(j = 0; j < 8; j++) { + val0 = g_font_array[i][j] >> 1; + val1 = 0; // 80-column bits + val2 = 0; // 40-column bits (doubled) + for(k = 0; k < 7; k++) { + val1 = val1 << 1; + val2 = val2 << 2; + if(val0 & 1) { + val1 |= 1; + val2 |= 3; + } + val0 = val0 >> 1; + } + g_a2font_bits[i][j] = (val2 << 8) | val1; + } + } +} + +void +prepare_a2_romx_font(byte *font_ptr) +{ + word32 val0, val1, val2; + int i, j, k; + + // ROMX file + for(i = 0; i < 256; i++) { + for(j = 0; j < 8; j++) { + val0 = font_ptr[i*8 + j]; + val1 = 0; // 80-column bits + val2 = 0; // 40-column bits (doubled) + for(k = 0; k < 7; k++) { + val1 = val1 << 1; + val2 = val2 << 2; + if((val0 & 0x40) == 0) { + val1 |= 1; + val2 |= 3; + } + val0 = val0 << 1; + } + g_a2font_bits[i][j] = (val2 << 8) | val1; + } + } +} + +void +video_add_rect(Kimage *kimage_ptr, int x, int y, int width, int height) +{ + int pos; + + pos = kimage_ptr->num_change_rects++; + if(pos >= MAX_CHANGE_RECTS) { + return; // This will be handled later + } + kimage_ptr->change_rect[pos].x = x; + kimage_ptr->change_rect[pos].y = y; + kimage_ptr->change_rect[pos].width = width; + kimage_ptr->change_rect[pos].height = height; + + g_video_pixel_dcount += (width * height); +#if 0 + printf("Add rect %d, x:%d y:%d, w:%d h:%d\n", pos, x, y, width, height); +#endif +} + +void +video_add_a2_rect(int start_line, int end_line, int left_pix, int right_pix) +{ + int srcy; + + if((left_pix >= right_pix) || (left_pix < 0) || (right_pix <= 0)) { + halt_printf("video_push_lines: lines %d to %d, pix %d to %d\n", + start_line, end_line, left_pix, right_pix); + printf("a2_screen_buf_ch:%08x, g_full_refr:%08x\n", + g_a2_screen_buffer_changed, g_full_refresh_needed); + return; + } + + srcy = 2*start_line; + + video_add_rect(&g_mainwin_kimage, g_video_act_margin_left + left_pix, + g_video_act_margin_top + srcy, + (right_pix - left_pix), 2*(end_line - start_line)); +} + +void +video_form_change_rects() +{ + Kimage *kimage_ptr; + register word32 start_time; + register word32 end_time; + dword64 save_pixel_dcount; + word32 mask; + int start, line, left_pix, right_pix, left, right, line_div8; + int x, y, width, height; + + kimage_ptr = &g_mainwin_kimage; + if(g_border_sides_refresh_needed) { + g_border_sides_refresh_needed = 0; + // Add left side border + video_add_rect(kimage_ptr, 0, g_video_act_margin_top, + BORDER_WIDTH, A2_WINDOW_HEIGHT); + + // Add right-side border. Resend x from 560 through + // X_A2_WINDOW_WIDTH + x = g_video_act_margin_left + 560; + width = X_A2_WINDOW_WIDTH - x; + video_add_rect(kimage_ptr, x, g_video_act_margin_top, width, + A2_WINDOW_HEIGHT); + } + if(g_border_special_refresh_needed) { + g_border_special_refresh_needed = 0; + + // Do top border + width = g_video_act_width; + height = g_video_act_margin_top; + video_add_rect(kimage_ptr, 0, 0, width, height); + + // Do bottom border + height = g_video_act_margin_bottom; + y = g_video_act_margin_top + A2_WINDOW_HEIGHT; + video_add_rect(kimage_ptr, 0, y, width, height); + } + if(g_status_refresh_needed) { + g_status_refresh_needed = 0; + width = g_mainwin_kimage.a2_width; + y = g_video_act_margin_top + A2_WINDOW_HEIGHT + + g_video_act_margin_bottom; + height = kimage_ptr->a2_height - y; + if(height > 0) { + save_pixel_dcount = g_video_pixel_dcount; + video_add_rect(kimage_ptr, 0, y, width, height); + g_video_pixel_dcount = save_pixel_dcount; + } + } + + if(g_a2_screen_buffer_changed == 0) { + return; + } + + GET_ITIMER(start_time); + + start = -1; + + left_pix = 640; + right_pix = 0; + + for(line = 0; line < 200; line++) { + line_div8 = line >> 3; + mask = 1 << (line_div8); + if((g_full_refresh_needed & mask) != 0) { + left = 0; + right = 640; + } else { + left = g_a2_line_left_edge[line]; + right = g_a2_line_right_edge[line]; + } + + if(!(g_a2_screen_buffer_changed & mask) || (left >= right)) { + /* No need to update this line */ + /* Refresh previous chunks of lines, if any */ + if(start >= 0) { + video_add_a2_rect(start, line, left_pix, + right_pix); + start = -1; + left_pix = 640; + right_pix = 0; + } + } else { + /* Need to update this line */ + if(start < 0) { + start = line; + } + left_pix = MY_MIN(left, left_pix); + right_pix = MY_MAX(right, right_pix); + } + } + + if(start >= 0) { + video_add_a2_rect(start, 200, left_pix, right_pix); + } + + g_a2_screen_buffer_changed = 0; + g_full_refresh_needed = 0; + + GET_ITIMER(end_time); + + g_cycs_in_xredraw += (end_time - start_time); +} + +int +video_get_a2_width(Kimage *kimage_ptr) +{ + return kimage_ptr->a2_width; +} + +int +video_get_x_width(Kimage *kimage_ptr) +{ + return kimage_ptr->x_width; +} + +int +video_get_a2_height(Kimage *kimage_ptr) +{ + return kimage_ptr->a2_height; +} + +int +video_get_x_height(Kimage *kimage_ptr) +{ + return kimage_ptr->x_height; +} + +int +video_get_x_xpos(Kimage *kimage_ptr) +{ + return kimage_ptr->x_xpos; +} + +int +video_get_x_ypos(Kimage *kimage_ptr) +{ + return kimage_ptr->x_ypos; +} + +void +video_update_xpos_ypos(Kimage *kimage_ptr, int x_xpos, int x_ypos) +{ + x_xpos = video_clamp(x_xpos, 0, kimage_ptr->x_max_width - 640); + x_ypos = video_clamp(x_ypos, 0, kimage_ptr->x_max_height - 420); + kimage_ptr->x_xpos = x_xpos; + kimage_ptr->x_ypos = x_ypos; + if(kimage_ptr == &g_mainwin_kimage) { + g_mainwin_xpos = x_xpos; + g_mainwin_ypos = x_ypos; + // printf("Set g_mainwin_xpos:%d, ypos:%d\n", x_xpos, x_ypos); + } +} + +int +video_change_aspect_needed(Kimage *kimage_ptr, int x_width, int x_height) +{ + // Return 1 if the passed in height, width do not match the kimage + // aspect-corrected version, and at least 2 VBL periods have passed + + if((kimage_ptr->vbl_of_last_resize + 6) > g_vbl_count) { + return 0; + } + if((kimage_ptr->x_height != x_height) || + (kimage_ptr->x_width != x_width)) { +#if 0 + printf("change_aspect_needed, vbl:%d kimage width:%d height:%d " + "but x width:%d height:%d\n", g_vbl_count, + kimage_ptr->x_width, kimage_ptr->x_height, + x_width, x_height); +#endif + return 1; + } + return 0; +} + +void +video_update_status_enable(Kimage *kimage_ptr) +{ + int height, a2_height; + + height = g_video_act_margin_top + A2_WINDOW_HEIGHT + + g_video_act_margin_bottom; + a2_height = height; + if(g_status_enable) { + a2_height = kimage_ptr->a2_height_full; + } + kimage_ptr->a2_height = a2_height; + height = (a2_height * kimage_ptr->scale_width_a2_to_x) >> 16; + if(height > kimage_ptr->x_max_height) { + height = kimage_ptr->x_max_height; + } + kimage_ptr->x_height = height; +#if 0 + printf("new a2_height:%d, x_height:%d\n", kimage_ptr->a2_height, + kimage_ptr->x_height); +#endif + //printf("Calling video_update_scale from video_update_status_en\n"); + video_update_scale(kimage_ptr, kimage_ptr->x_width, height, 0); +} + +// video_out_query: return 0 if no screen drawing at all is needed. +// returns 1 or the number of change_rects if any drawing is needed +int +video_out_query(Kimage *kimage_ptr) +{ + int num_change_rects, x_refresh_needed; + + num_change_rects = kimage_ptr->num_change_rects; + x_refresh_needed = kimage_ptr->x_refresh_needed; + if(x_refresh_needed) { + return 1; + } + return num_change_rects; +} + +// video_out_done: used by specialize xdriver platform code which needs to +// clear the num_change_rects=0. +void +video_out_done(Kimage *kimage_ptr) +{ + kimage_ptr->num_change_rects = 0; + kimage_ptr->x_refresh_needed = 0; +} + +// Called by xdriver.c to copy KEGS's kimage data to the vptr buffer +int +video_out_data(void *vptr, Kimage *kimage_ptr, int out_width_act, + Change_rect *rectptr, int pos) +{ + word32 *out_wptr, *wptr; + int a2_width, a2_width_full, width, a2_height, height, x, eff_y; + int x_width, x_height, num_change_rects, x_refresh_needed; + int i, j; + + // Copy from kimage_ptr->wptr to vptr + num_change_rects = kimage_ptr->num_change_rects; + x_refresh_needed = kimage_ptr->x_refresh_needed; + if(((pos >= num_change_rects) || (pos >= MAX_CHANGE_RECTS)) && + !x_refresh_needed) { + kimage_ptr->num_change_rects = 0; + return 0; + } + a2_width = kimage_ptr->a2_width; + a2_width_full = kimage_ptr->a2_width_full; + a2_height = kimage_ptr->a2_height; + if((num_change_rects >= MAX_CHANGE_RECTS) || x_refresh_needed) { + // Table overflow, just copy everything in one go + kimage_ptr->x_refresh_needed = 0; + if(pos >= 1) { + kimage_ptr->num_change_rects = 0; + return 0; // No more to do + } + // Force full update + rectptr->x = 0; + rectptr->y = 0; + rectptr->width = a2_width; + rectptr->height = a2_height; + } else { + *rectptr = kimage_ptr->change_rect[pos]; // Struct copy + } +#if 0 + printf("video_out_data, %p rectptr:%p, pos:%d, x:%d y:%d w:%d h:%d, " + "wptr:%p\n", vptr, rectptr, pos, rectptr->x, + rectptr->y, rectptr->width, rectptr->height, + kimage_ptr->wptr); +#endif + + width = rectptr->width; + height = rectptr->height; + x = rectptr->x; + x_width = kimage_ptr->x_width; + x_height = kimage_ptr->x_height; + if(!g_video_no_scale_window && + ((a2_width != x_width) || (a2_height != x_height))) { +#if 0 + printf("a2_width:%d, x_width:%d, a2_height:%d, x_height:" + "%d\n", a2_width, x_width, a2_height, x_height); +#endif + return video_out_data_scaled(vptr, kimage_ptr, out_width_act, + rectptr); + } else { + out_wptr = (word32 *)vptr; + for(i = 0; i < height; i++) { + eff_y = rectptr->y + i; + wptr = kimage_ptr->wptr + (eff_y * a2_width_full) + x; + out_wptr = ((word32 *)vptr) + + (eff_y * out_width_act) + x; + for(j = 0; j < width; j++) { + *out_wptr++ = *wptr++; + } + } + } + + return 1; +} + + +int +video_out_data_intscaled(void *vptr, Kimage *kimage_ptr, int out_width_act, + Change_rect *rectptr) +{ + word32 *out_wptr, *wptr; + word32 pos_scale, alpha_mask; + int a2_width_full, eff_y, src_y, x, y, new_x, out_x, out_y; + int out_width, out_height, max_x, max_y, out_max_x, out_max_y, pos; + int i, j; + + // Faster scaling routine which does simple pixel replication rather + // than blending. Intended for scales >= 3.0 (or so) since at + // these scales, replication looks fine. + x = rectptr->x; + y = rectptr->y; + max_x = rectptr->width + x; + max_y = rectptr->height + y; + max_x = MY_MIN(kimage_ptr->a2_width_full, max_x + 1); + max_y = MY_MIN(kimage_ptr->a2_height, max_y + 1); + x = MY_MAX(0, x - 1); + y = MY_MAX(0, y - 1); + a2_width_full = kimage_ptr->a2_width_full; + + out_x = (x * kimage_ptr->scale_width_a2_to_x) >> 16; + out_y = (y * kimage_ptr->scale_height_a2_to_x) >> 16; + out_max_x = (max_x * kimage_ptr->scale_width_a2_to_x + 65535) >> 16; + out_max_y = (max_y * kimage_ptr->scale_height_a2_to_x + 65535) >> 16; + out_max_x = MY_MIN(out_max_x, out_width_act); + out_max_y = MY_MIN(out_max_y, kimage_ptr->x_height); + out_width = out_max_x - out_x; + out_height = out_max_y - out_y; + out_wptr = (word32 *)vptr; + rectptr->x = out_x; + rectptr->y = out_y; + rectptr->width = out_width; + rectptr->height = out_height; + alpha_mask = g_alpha_mask; + for(i = 0; i < out_height; i++) { + eff_y = out_y + i; + pos_scale = kimage_ptr->scale_height[eff_y]; + src_y = pos_scale >> 16; + wptr = kimage_ptr->wptr + (src_y * a2_width_full); + out_wptr = ((word32 *)vptr) + (eff_y * out_width_act) + out_x; + for(j = 0; j < out_width; j++) { + new_x = j + out_x; + pos_scale = kimage_ptr->scale_width[new_x]; + pos = pos_scale >> 16; + *out_wptr++ = wptr[pos] | alpha_mask; + } + } + rectptr->width = kimage_ptr->x_width - rectptr->x; + + return 1; +} + +int +video_out_data_scaled(void *vptr, Kimage *kimage_ptr, int out_width_act, + Change_rect *rectptr) +{ + word32 *out_wptr, *wptr; + dword64 dval0a, dval0b, dval1a, dval1b, dscale, dscale_y, dval; + word32 new_val, pos_scale, alpha_mask; + int a2_width_full, eff_y, src_y, x, y, new_x, out_x, out_y; + int out_width, out_height, max_x, max_y, out_max_x, out_max_y, pos; + int i, j; + + if((kimage_ptr->scale_width_a2_to_x >= 0x34000) || + (kimage_ptr->scale_height_a2_to_x >= 0x34000)) { + return video_out_data_intscaled(vptr, kimage_ptr, + out_width_act, rectptr); + } + x = rectptr->x; + y = rectptr->y; + max_x = rectptr->width + x; + max_y = rectptr->height + y; + max_x = MY_MIN(kimage_ptr->a2_width_full, max_x + 1); + max_y = MY_MIN(kimage_ptr->a2_height, max_y + 1); + x = MY_MAX(0, x - 1); + y = MY_MAX(0, y - 1); + a2_width_full = kimage_ptr->a2_width_full; + + out_x = (x * kimage_ptr->scale_width_a2_to_x) >> 16; + out_y = (y * kimage_ptr->scale_height_a2_to_x) >> 16; + out_max_x = (max_x * kimage_ptr->scale_width_a2_to_x + 65535) >> 16; + out_max_y = (max_y * kimage_ptr->scale_height_a2_to_x + 65535) >> 16; + out_max_x = MY_MIN(out_max_x, out_width_act); + out_max_y = MY_MIN(out_max_y, kimage_ptr->x_height); + out_width = out_max_x - out_x; + out_height = out_max_y - out_y; +#if 0 + printf("scaled: in %d,%d %d,%d becomes %d,%d %d,%d\n", x, y, width, + height, out_x, out_y, out_width, out_height); +#endif + out_wptr = (word32 *)vptr; + rectptr->x = out_x; + rectptr->y = out_y; + rectptr->width = out_width; + rectptr->height = out_height; + alpha_mask = g_alpha_mask; + for(i = 0; i < out_height; i++) { + eff_y = out_y + i; + pos_scale = kimage_ptr->scale_height[eff_y]; + src_y = pos_scale >> 16; + dscale_y = (pos_scale & 0xffff) >> 8; + wptr = kimage_ptr->wptr + (src_y * a2_width_full); + out_wptr = ((word32 *)vptr) + (eff_y * out_width_act) + out_x; + for(j = 0; j < out_width; j++) { + new_x = j + out_x; + pos_scale = kimage_ptr->scale_width[new_x]; + pos = pos_scale >> 16; + dscale = (pos_scale & 0xffff) >> 8; + dval0a = wptr[pos]; + dval0a = (dval0a & 0x00ff00ffULL) | + ((dval0a & 0xff00ff00ULL) << 24); + dval0b = wptr[pos + 1]; + dval0b = (dval0b & 0x00ff00ffULL) | + ((dval0b & 0xff00ff00ULL) << 24); + dval1a = wptr[pos + a2_width_full]; + dval1a = (dval1a & 0x00ff00ffULL) | + ((dval1a & 0xff00ff00ULL) << 24); + dval1b = wptr[pos + 1 + a2_width_full]; + dval1b = (dval1b & 0x00ff00ffULL) | + ((dval1b & 0xff00ff00ULL) << 24); + dval0a = ((0x100 - dscale) * dval0a) + + (dscale * dval0b); + dval1a = ((0x100 - dscale) * dval1a) + + (dscale * dval1b); + dval0a = (dval0a >> 8) & 0x00ff00ff00ff00ffULL; + dval1a = (dval1a >> 8) & 0x00ff00ff00ff00ffULL; + dval = ((0x100 - dscale_y) * dval0a) + + (dscale_y * dval1a); + new_val = ((dval >> 8) & 0x00ff00ffULL) | + ((dval >> 32) & 0xff00ff00ULL); + *out_wptr++ = new_val | alpha_mask; +#if 0 + if((pos == 300) && (eff_y == 100)) { + printf("x:%d pos:%d %08x. %016llx,%016llx " + "pos_sc:%08x, %08x\n", new_x, pos, + new_val, dval0a, dval0b, pos_scale, + wptr[pos]); + } +#endif + } + } + rectptr->width = kimage_ptr->x_width - rectptr->x; +#if 0 + for(i = 0; i < kimage_ptr->x_height; i++) { + out_wptr = ((word32 *)vptr) + (i * out_width_act) + + kimage_ptr->x_width - 1; + *out_wptr = 0x00ff00ff; +# if 0 + for(j = 0; j < 10; j++) { + if(*out_wptr != 0) { + printf("out_wptr:%p is %08x at %d,%d\n", + out_wptr, *out_wptr, + out_width_act - 1 - j, i); + } + out_wptr--; + } +# endif + } +#endif + + return 1; +} + +word32 +video_scale_calc_frac(int pos, word32 max, word32 frac_inc, word32 frac_inc_inv) +{ + word32 frac, frac_to_next, new_frac; + + frac = pos * frac_inc; + if(frac >= max) { + return max; // Clear frac bits + } + if(g_video_scale_algorithm == 2) { + return frac & -65536; // nearest neighbor + } + if(g_video_scale_algorithm == 1) { + return frac; // bilinear interp + } + // Do proper scaling. fraction=0 means 100% this pixel, fraction=ffff + // means 99.99% the next pixel + frac_to_next = frac_inc + (frac & 0xffff); + if(frac_to_next < 65536) { + frac_to_next = 0; + } + frac_to_next = (frac_to_next & 0xffff) * frac_inc_inv; + frac_to_next = frac_to_next >> 16; + new_frac = (frac & -65536) | (frac_to_next & 0xffff); +#if 0 + if((frac >= (30 << 16)) && (frac < (38 << 16))) { + printf("scale %d (%02x) -> %08x (was %08x) %08x %08x\n", + pos, pos, new_frac, frac, frac_inc, frac_inc_inv); + } +#endif + return new_frac; +} + +void +video_update_scale(Kimage *kimage_ptr, int out_width, int out_height, + int must_update) +{ + word32 frac_inc, frac_inc_inv, new_frac, max; + int a2_width, a2_height, exp_width, exp_height; + int i; + + out_width = video_clamp(out_width, 1, kimage_ptr->x_max_width); + out_width = video_clamp(out_width, 1, MAX_SCALE_SIZE); + + out_height = video_clamp(out_height, 1, kimage_ptr->x_max_height); + out_height = video_clamp(out_height, 1, MAX_SCALE_SIZE); + + a2_width = kimage_ptr->a2_width; + a2_height = kimage_ptr->a2_height; + kimage_ptr->vbl_of_last_resize = g_vbl_count; + + // Handle aspect ratio. Calculate height/width based on the other's + // aspect ratio, and pick the smaller value + exp_width = (a2_width * out_height) / a2_height; + exp_height = (a2_height * out_width) / a2_width; + + if(exp_width < a2_width) { + exp_width = a2_width; + } + if(exp_height < a2_height) { + exp_height = a2_height; + } + if(exp_width < out_width) { + // Allow off-by-one to be OK, so window doesn't keep resizing + if((exp_width + 1) != out_width) { + out_width = exp_width; + } + } + if(exp_height < out_height) { + if((exp_height + 1) != out_height) { + out_height = exp_height; + } + } + if(out_width <= 0) { + out_width = 1; + } + if(out_height <= 0) { + out_height = 1; + } + + // See if anything changed. If it's unchanged, don't do anything + if((kimage_ptr->x_width == out_width) && !must_update && + (kimage_ptr->x_height == out_height)) { + return; + } + kimage_ptr->x_width = out_width; + kimage_ptr->x_height = out_height; + kimage_ptr->x_refresh_needed = 1; + if(kimage_ptr == &g_mainwin_kimage) { + g_mainwin_width = out_width; + g_mainwin_height = out_height; + //printf("Set g_mainwin_width=%d, g_mainwin_height=%d\n", + // out_width, out_height); + } + + // the per-pixel inc = a2_width / out_width. Scale by 65536 + frac_inc = (a2_width * 65536UL) / out_width; + kimage_ptr->scale_width_to_a2 = frac_inc; + frac_inc_inv = (out_width * 65536UL) / a2_width; + kimage_ptr->scale_width_a2_to_x = frac_inc_inv; +#if 0 + printf("scale_width_to_a2: %08x, a2_to_x:%08x, is_debugwin:%d\n", + kimage_ptr->scale_width_to_a2, kimage_ptr->scale_width_a2_to_x, + (kimage_ptr == &g_debugwin_kimage)); +#endif + max = (a2_width - 1) << 16; + for(i = 0; i < out_width + 1; i++) { + new_frac = video_scale_calc_frac(i, max, frac_inc, + frac_inc_inv); + kimage_ptr->scale_width[i] = new_frac; + } + + frac_inc = (a2_height * 65536UL) / out_height; + kimage_ptr->scale_height_to_a2 = frac_inc; + frac_inc_inv = (out_height * 65536UL) / a2_height; + kimage_ptr->scale_height_a2_to_x = frac_inc_inv; +#if 0 + printf("scale_height_to_a2: %08x, a2_to_x:%08x. w:%d h:%d\n", + kimage_ptr->scale_height_to_a2, + kimage_ptr->scale_height_a2_to_x, out_width, out_height); +#endif + max = (a2_height - 1) << 16; + for(i = 0; i < out_height + 1; i++) { + new_frac = video_scale_calc_frac(i, max, frac_inc, + frac_inc_inv); + kimage_ptr->scale_height[i] = new_frac; + } +} + +int +video_scale_mouse_x(Kimage *kimage_ptr, int raw_x, int x_width) +{ + int x; + + // raw_x is in output coordinates. Scale down to a2 coordinates + if(x_width == 0) { + x = (kimage_ptr->scale_width_to_a2 * raw_x) / 65536; + } else { + // Scale raw_x using x_width + x = (raw_x * kimage_ptr->a2_width_full) / x_width; + } + x = x - BASE_MARGIN_LEFT; + return x; +} + +int +video_scale_mouse_y(Kimage *kimage_ptr, int raw_y, int y_height) +{ + int y; + + // raw_y is in output coordinates. Scale down to a2 coordinates + if(y_height == 0) { + y = (kimage_ptr->scale_height_to_a2 * raw_y) / 65536; + } else { + // Scale raw_y using y_height + y = (raw_y * kimage_ptr->a2_height) / y_height; + } + y = y - BASE_MARGIN_TOP; + return y; +} + +int +video_unscale_mouse_x(Kimage *kimage_ptr, int a2_x, int x_width) +{ + int x; + + // Convert a2_x to output coordinates + x = a2_x + BASE_MARGIN_LEFT; + if(x_width == 0) { + x = (kimage_ptr->scale_width_a2_to_x * x) / 65536; + } else { + // Scale a2_x using x_width + x = (x * x_width) / kimage_ptr->a2_width_full; + } + return x; +} + +int +video_unscale_mouse_y(Kimage *kimage_ptr, int a2_y, int y_height) +{ + int y; + + // Convert a2_y to output coordinates + y = a2_y + BASE_MARGIN_TOP; + if(y_height == 0) { + y = (kimage_ptr->scale_height_a2_to_x * y) / 65536; + } else { + // Scale a2_y using y_height + y = (y * y_height) / kimage_ptr->a2_height; + } + return y; +} + +void +video_update_color_raw(int bank, int col_num, int a2_color) +{ + word32 tmp; + int red, green, blue, newred, newgreen, newblue; + + if(col_num >= 256 || col_num < 0) { + halt_printf("video_update_color_raw: col: %03x\n", col_num); + return; + } + + red = (a2_color >> 8) & 0xf; + green = (a2_color >> 4) & 0xf; + blue = (a2_color) & 0xf; + red = ((red << 4) + red); + green = ((green << 4) + green); + blue = ((blue << 4) + blue); + + newred = red >> g_red_right_shift; + newgreen = green >> g_green_right_shift; + newblue = blue >> g_blue_right_shift; + + tmp = ((newred & g_red_mask) << g_red_left_shift) + + ((newgreen & g_green_mask) << g_green_left_shift) + + ((newblue & g_blue_mask) << g_blue_left_shift); + g_palette_8to1624[bank][col_num] = tmp; +} + +void +video_update_status_line(int line, const char *string) +{ + byte a2_str_buf[STATUS_LINE_LENGTH+1]; + word32 *wptr; + char *buf; + const char *ptr; + word32 line_bytes; + int start_line, c, pixels_per_line, offset; + int i; + + if(line >= MAX_STATUS_LINES || line < 0) { + printf("update_status_line: line: %d!\n", line); + exit(1); + } + + ptr = string; + buf = &(g_status_buf[line][0]); + g_status_ptrs[line] = buf; + for(i = 0; i < STATUS_LINE_LENGTH; i++) { + if(*ptr) { + c = *ptr++; + } else { + c = ' '; + } + buf[i] = c; + a2_str_buf[i] = c | 0x80; + } + + buf[STATUS_LINE_LENGTH] = 0; + a2_str_buf[STATUS_LINE_LENGTH] = 0; + start_line = (200 + 2*8) + line*8; + pixels_per_line = g_mainwin_kimage.a2_width_full; + offset = (pixels_per_line * g_video_act_margin_top); + wptr = g_mainwin_kimage.wptr; + wptr += offset; + for(i = 0; i < 8; i++) { + line_bytes = ((start_line + i) << 16) | (40 << 8) | 0; + redraw_changed_string(&(a2_str_buf[0]), line_bytes, -1L, + wptr, 0, 0x00ffffff, pixels_per_line, 1); + } + + // Don't add rectangle here, video_form_change_rects will do it + //video_add_a2_rect(start_line, start_line + 8, 0, 640); +} + +void +video_draw_a2_string(int line, const byte *bptr) +{ + word32 *wptr; + word32 line_bytes; + int start_line, pixels_per_line, offset; + int i; + + start_line = line*8; + pixels_per_line = g_mainwin_kimage.a2_width_full; + offset = (pixels_per_line * g_video_act_margin_top) + + g_video_act_margin_left; + wptr = g_mainwin_kimage.wptr; + wptr += offset; + for(i = 0; i < 8; i++) { + line_bytes = ((start_line + i) << 16) | (40 << 8) | 0; + redraw_changed_string(bptr, line_bytes, -1L, + wptr, 0, 0x00ffffff, pixels_per_line, 1); + } + g_mainwin_kimage.x_refresh_needed = 1; +} + +void +video_show_debug_info() +{ + word32 tmp1; + + printf("g_cur_dfcyc: %016llx, last_vbl: %016llx\n", g_cur_dfcyc, + g_last_vbl_dfcyc); + tmp1 = get_lines_since_vbl(g_cur_dfcyc); + printf("lines since vbl: %06x\n", tmp1); + printf("Last line updated: %d\n", g_vid_update_last_line); +} + +word32 +read_video_data(dword64 dfcyc) +{ + word32 val, val2; + int lines_since_vbl, line; + + // Return Charrom data at $C02C for SuperConvert 4 TDM mode + lines_since_vbl = get_lines_since_vbl(dfcyc); + val = float_bus_lines(dfcyc, lines_since_vbl); + line = lines_since_vbl >> 8; + if(line < 192) { + // Always do the character ROM + val2 = g_a2font_bits[val & 0xff][line & 7]; + dbg_log_info(dfcyc, val, + (lines_since_vbl << 8) | (val2 & 0xff), 0xc02c); + val = ~val2; // Invert it, maybe + } + return val & 0xff; +} + +word32 +float_bus(dword64 dfcyc) +{ + word32 lines_since_vbl; + + lines_since_vbl = get_lines_since_vbl(dfcyc); + return float_bus_lines(dfcyc, lines_since_vbl); +} + +word32 +float_bus_lines(dword64 dfcyc, word32 lines_since_vbl) +{ + word32 val; + int line, eff_line, line24, all_stat, byte_offset; + int hires, page2, addr; + +/* For floating bus, model hires style: Visible lines 0-191 are simply the */ +/* data being displayed at that time. Lines 192-255 are lines 0 - 63 again */ +/* and lines 256-261 are lines 58-63 again */ +/* For each line, figure out starting byte at -25 mod 128 bytes from this */ +/* line's start */ +/* This emulates an Apple II style floating bus. A real IIgs does not */ +/* drive anything meaningful during the 25 horizontal blanking cycles, */ +/* nor during veritical blanking. The data seems to be 0 or related to */ +/* the instruction fetches on a real IIgs during blankings */ + + line = lines_since_vbl >> 8; + byte_offset = lines_since_vbl & 0xff; + // byte offset is from 0 through 64, where the visible screen is drawn + // from 25 through 64 + + eff_line = line; + if(eff_line >= 0x100) { + eff_line = (eff_line - 6) & 0xff; + } + if(byte_offset == 0) { + byte_offset = 1; + } + all_stat = g_cur_a2_stat; + hires = (all_stat & ALL_STAT_HIRES) && !(all_stat & ALL_STAT_TEXT); + if((all_stat & ALL_STAT_MIX_T_GR) && (line >= 160)) { + hires = 0; + } + page2 = EXTRU(all_stat, 31 - BIT_ALL_STAT_PAGE2, 1); + if(all_stat & ALL_STAT_ST80) { + page2 = 0; + } + + line24 = (eff_line >> 3) & 0x1f; + addr = g_screen_index[line24] & 0x3ff; + addr = (addr & 0x380) + (((addr & 0x7f) - 25 + byte_offset) & 0x7f); + if(hires) { + addr = 0x2000 + addr + ((eff_line & 7) << 10) + (page2 << 13); + } else { + addr = 0x400 + addr + (page2 << 10); + } + + val = g_slow_memory_ptr[addr]; +#if 0 + printf("For %04x (%d) addr=%04x, val=%02x, dfcyc:%016llx\n", + lines_since_vbl, eff_line, addr, val, dfcyc - g_last_vbl_dfcyc); +#endif + dbg_log_info(dfcyc, ((lines_since_vbl >> 11) << 24) | + (lines_since_vbl - 25), (addr << 8) | val, 0xff); + + return val; +} diff --git a/gsplus/src/voc.c b/gsplus/src/voc.c new file mode 100644 index 0000000..a72c692 --- /dev/null +++ b/gsplus/src/voc.c @@ -0,0 +1,220 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// This file provides emulation of the Apple Video Overlay Card, which +// will appear to be in slot 3 if g_voc_enable=1 (there's a config.c +// setting to control enabling VOC). The only currently supported VOC +// feature is the SHR interlaced display using both Main and Aux memory +// to provide a 640x400 (or 320x400) pixel display. + +#include "defc.h" + +extern word32 g_c02b_val; +extern int g_cur_a2_stat; +extern word32 g_vbl_count; + +int g_voc_enable = 0; // Default to disabled for now +word32 g_voc_reg1 = 0x09; +word32 g_voc_reg3 = 0; +word32 g_voc_reg4 = 0; +word32 g_voc_reg5 = 0; +word32 g_voc_reg6 = 0; + +word32 +voc_devsel_read(word32 loc, dword64 dfcyc) +{ + // Reads to $c0b0-$c0bf. + loc = loc & 0xf; + switch(loc) { + case 0: // 0xc0b0 + return voc_read_reg0(dfcyc); + break; + case 1: // 0xc0b1 + return g_voc_reg1; + break; + case 3: // 0xc0b3 + return g_voc_reg3; + break; + case 4: // 0xc0b4 + return g_voc_reg4; + break; + case 5: // 0xc0b5 + return g_voc_reg5; + break; + case 6: // 0xc0b6 + return g_voc_reg6; + break; + case 7: // 0xc0b7, possible Uthernet 2 detection + return 0x00; + break; + case 8: // 0xc0b8, Second Sight detection by jpeGS program + return 0x00; // Second Sight returns 0x01 + break; + case 0xd: // 0xc0bd, A2OSX Uthernet 1 detection code + return 0x00; + break; + } + + halt_printf("Tried to read: %04x\n", 0xc0b0 + loc); + + return 0; +} + +void +voc_devsel_write(word32 loc, word32 val, dword64 dfcyc) +{ + // Writes to $c0b0-$c0bf. + loc = loc & 0xf; + switch(loc) { + case 0: // 0xc0b0 + // Write 0 to clear VBL interrupts + if(val != 0) { + halt_printf("VOC write %04x = %02x\n", loc, val); + } + return; + break; + case 1: // 0xc0b1 + // bit 0: R/W: 1=GG Bus Enable + // When 0, I think VOC ignores writes to $c023,etc. + // bit 2: R/W: 0=OutChromaFilter enabled, 1=ChromaFilter disab + // bit 2 is also TextMonoOver somehow using bit[5]==1 + // bit 3: R/W: 1=MainPageLin + // bits 5:4: R/W: 00=Aux mem; 01=Main Memory; 11=Interlaced + // bit 6: R/W: 1=Enable VBL Interrupt + // bit 7: R/W: 1=Enable Line interrupts + if(!g_voc_enable) { + val = 0; + } + if(val & 0xc0) { + halt_printf("VOC write %04x = %02x\n", loc, val); + } +#if 0 + if(val != g_voc_reg1) { + printf("$c0b1:%02x (was %02x)\n", val, g_voc_reg1); + } +#endif + g_voc_reg1 = val; + voc_update_interlace(dfcyc); + return; + break; + case 3: // 0xc0b3 + // bits 2:0: R/W: Key Dissolve, 0=100% graphics, 7=100% video + // bit 3: R/W: 1=Enhanced Dissolve enabled + // bits 6:4: R/W: Non-Key Dissolve, 0=100% graphics, 7=100% vid + // bit 7: R/W: 0=Output Setup Enabled, 1=Output Setup Disabled + g_voc_reg3 = val; + return; + break; + case 4: // 0xc0b4 + // bits 3:0: R/W: KeyColor Blue + // bits 7:4: R/W: KeyColor Green + g_voc_reg4 = val; + return; + break; + case 5: // 0xc0b5 + // bits 3:0: R/W: KeyColor Red + // bit 4: R/W: OutExtBlank: 0=Graphics, 1=External + // bit 5: R/W: 0=GenLock enabled, 1=GenLock disabled + // bit 6: R/W: 0=KeyColor enabled, 1=KeyColor disabled + // bit 7: R/W: 1=Interlace mode enabled + g_voc_reg5 = val; + voc_update_interlace(dfcyc); + return; + break; + case 6: // 0xc0b6 + // Write 0 to cause AdjSave to occur + // Write 8, then 9, then 8 again to cause AdjInc for Hue + // Write a, then b, then a again to cause AdjDec for Hue + // Write 4, then 5, then 4 again to cause AdjInc for Saturation + // Write 6, then 7, then 6 again to cause AdjDec for Saturation + // bit 3: hue, bit 2: saturation + g_voc_reg6 = val; + return; + break; + case 7: // 0xc0b7 + // Written by System Disk 1.1 Desktop.sys to 0xfd, ignore + if(val == 0xfd) { + return; + } + break; + case 0xa: + case 0xb: // 0xc0ba,0xc0bb written to 0 by A2OSX Uthernet1 detect + if(val == 0) { + return; + } + break; + } + halt_printf("Unknown Write %04x = %02x %016llx\n", 0xc0b0 + loc, val, + dfcyc); +} + +void +voc_iosel_c300_write(word32 loc, word32 val, dword64 dfcyc) +{ + // Writes to $c300-$c3ff + halt_printf("Wrote VOC %04x = %02x %016llx\n", 0xc300 + (loc & 0xff), + val, dfcyc); +} + +void +voc_reset() +{ + g_voc_reg1 = 0x0d; // [0]: GG Bus enable, [3]:MainPageLin + g_voc_reg3 = 0x07; + g_voc_reg4 = 0; + g_voc_reg5 = 0x40; + g_voc_reg6 = 0; +} + +double g_voc_last_pal_vbl = 0; + +word32 +voc_read_reg0(dword64 dfcyc) +{ + word32 frame, in_vbl; + + if(!g_voc_enable) { + return 0; + } + // Reading $c0b0. + // c0b0: bit 2: R/O: 1=In VBL + // c0b0: bit 3: R/O: 0=No Video Detected, 1=Video Detected + // c0b0: bit 4: R/O: 1=Video Genlocked + // c0b0: bit 5: R/O: 0=showing Field 0, 1=showing Field 1 + // c0b0: bit 6: R/O: 1=VBL Int Request pending + // c0b0: bit 7: R/O: 1=Line Int Request pending + in_vbl = in_vblank(dfcyc); + dbg_log_info(dfcyc, 0, in_vbl, 0x1c0b0); + frame = g_vbl_count & 1; + return (frame << 5) | (in_vbl << 2); +} + +void +voc_update_interlace(dword64 dfcyc) +{ + word32 new_stat, mask; + + new_stat = 0; + if(((g_voc_reg1 & 0x30) == 0x30) && (g_voc_reg5 & 0x80)) { + new_stat = ALL_STAT_VOC_INTERLACE; + } + if((g_voc_reg1 & 0x30) == 0x10) { // Draw SHR from mainmem + new_stat = ALL_STAT_VOC_MAIN; + } + mask = ALL_STAT_VOC_INTERLACE | ALL_STAT_VOC_MAIN; + if((g_cur_a2_stat ^ new_stat) & mask) { + // Interlace mode has changed + g_cur_a2_stat &= (~mask); + g_cur_a2_stat |= new_stat; + printf("Change VOC interlace mode: %08x\n", new_stat); + change_display_mode(dfcyc); + } +} + diff --git a/gsplus/src/win32snd_driver.c b/gsplus/src/win32snd_driver.c new file mode 100644 index 0000000..c783b4d --- /dev/null +++ b/gsplus/src/win32snd_driver.c @@ -0,0 +1,306 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2023 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// Audio is sent every 1/60th of a second to win32_send_audio(). Play it +// using waveOutWrite() as long as we've got at least 2/60th of a second +// buffered--otherwise waveOutPause(). If we get more than 10 buffers +// queued, drop buffers until we get down to 6 buffers queued again. + +// Track headers completed in g_wavehdr_rd_pos using callback function. + +#include "defc.h" +#include "sound.h" + +#include +#include + +extern int Verbose; + +extern int g_audio_rate; + +unsigned int __stdcall child_sound_loop_win32(void *param); +void check_wave_error(int res, char *str); + +#define NUM_WAVE_HEADERS 32 + +HWAVEOUT g_wave_handle; +WAVEHDR g_wavehdr[NUM_WAVE_HEADERS]; +// Each header is for 1/60th of a second of sound (generally). Pause +// until 2 headers are available, then unpause. Experimentally it appears +// we keep about 5 headers (5/60th of a second = 80msec) ahead, which is +// excellent latency + +extern int g_audio_enable; +extern word32 *g_sound_shm_addr; +extern int g_preferred_rate; + +int g_win32snd_buflen = 0x1000; +int g_win32_snd_playing = 0; +int g_win32_snd_to_drop = 0; +word32 g_win32_snd_dropped = 0; +volatile int g_wavehdr_rd_pos = 0; +volatile int g_wavehdr_wr_pos = 0; + +void +win32snd_init(word32 *shmaddr) +{ + printf("win32snd_init\n"); + child_sound_init_win32(); +} + +void +win32snd_shutdown() +{ + /* hmm */ + +} + +void CALLBACK +handle_wav_snd(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + LPWAVEHDR lpwavehdr; + int pos; + + /* Only service "buffer done playing messages */ + if(uMsg == WOM_DONE) { + lpwavehdr = (LPWAVEHDR)dwParam1; + if(lpwavehdr->dwFlags == (WHDR_DONE | WHDR_PREPARED)) { + lpwavehdr->dwUser = FALSE; + } + pos = (int)(lpwavehdr - &g_wavehdr[0]); + // printf("At %.3f, pos %d is done\n", get_dtime(), pos); + if(pos == g_wavehdr_rd_pos) { + pos = (pos + 1) % NUM_WAVE_HEADERS; + g_wavehdr_rd_pos = pos; + } else { + printf("wavehdr %d finished, exp %d\n", pos, + g_wavehdr_rd_pos); + } + } + + return; +} + + +void +check_wave_error(int res, char *str) +{ + char buf[256]; + + if(res == MMSYSERR_NOERROR) { + return; + } + + waveOutGetErrorText(res, &buf[0], sizeof(buf)); + printf("%s: %s\n", str, buf); + exit(1); +} + +void +child_sound_init_win32() +{ + WAVEFORMATEX wavefmt; + WAVEOUTCAPS caps; + byte *bptr; + UINT wave_id; + int bits_per_sample, channels, block_align, blen, res; + int i; + + memset(&wavefmt, 0, sizeof(WAVEFORMATEX)); + + wavefmt.wFormatTag = WAVE_FORMAT_PCM; + bits_per_sample = 16; + channels = 2; + wavefmt.wBitsPerSample = bits_per_sample; + wavefmt.nChannels = channels; + wavefmt.nSamplesPerSec = g_preferred_rate; + block_align = channels * (bits_per_sample / 8); + wavefmt.nBlockAlign = block_align; + wavefmt.nAvgBytesPerSec = block_align * g_audio_rate; + + res = waveOutOpen(&g_wave_handle, WAVE_MAPPER, &wavefmt, 0, 0, + WAVE_FORMAT_QUERY); + + if(res != MMSYSERR_NOERROR) { + printf("Cannot open audio device, res:%d, g_audio_rate:%d\n", + res, g_preferred_rate); + g_audio_enable = 0; + return; + } + + res = waveOutOpen(&g_wave_handle, WAVE_MAPPER, &wavefmt, + (DWORD_PTR)handle_wav_snd, 0, + CALLBACK_FUNCTION | WAVE_ALLOWSYNC); + + if(res != MMSYSERR_NOERROR) { + printf("Cannot register audio\n"); + g_audio_enable = 0; + return; + } + + g_audio_rate = wavefmt.nSamplesPerSec; + blen = (((g_audio_rate * block_align) / 60) * 5) / 4; + // Size buffer 25% larger than expected, to add some margin + blen = (blen + 15) & -16L; + + g_win32snd_buflen = blen; + bptr = malloc(blen * NUM_WAVE_HEADERS); + if(bptr == NULL) { + printf("Unabled to allocate sound buffer\n"); + exit(1); + } + + for(i = 0; i < NUM_WAVE_HEADERS; i++) { + memset(&g_wavehdr[i], 0, sizeof(WAVEHDR)); + g_wavehdr[i].dwUser = FALSE; + g_wavehdr[i].lpData = (char *)&(bptr[i * blen]); + g_wavehdr[i].dwBufferLength = blen; + g_wavehdr[i].dwFlags = 0; + g_wavehdr[i].dwLoops = 0; + res = waveOutPrepareHeader(g_wave_handle, &g_wavehdr[i], + sizeof(WAVEHDR)); + check_wave_error(res, "waveOutPrepareHeader"); + } + + res = waveOutGetID(g_wave_handle, &wave_id); + res = waveOutGetDevCaps(wave_id, &caps, sizeof(caps)); + check_wave_error(res, "waveOutGetDevCaps"); + printf("Using %s, buflen:%d\n", caps.szPname, g_win32snd_buflen); + printf(" Bits per Sample = %d. Channels = %d\n", + wavefmt.wBitsPerSample, wavefmt.nChannels); + printf(" Sampling rate = %d, avg_bytes_per_sec = %d\n", + (int)wavefmt.nSamplesPerSec, (int)wavefmt.nAvgBytesPerSec); + + sound_set_audio_rate(g_audio_rate); +} + +void +win32snd_set_playing(int snd_playing) +{ + g_win32_snd_playing = snd_playing; + if(snd_playing) { + waveOutRestart(g_wave_handle); +#if 0 + printf("win32 restarted sound wr:%d rd:%d\n", g_wavehdr_wr_pos, + g_wavehdr_rd_pos); +#endif + } else { + waveOutPause(g_wave_handle); +#if 0 + printf("win32 paused sound wr:%d rd:%d\n", g_wavehdr_wr_pos, + g_wavehdr_rd_pos); +#endif + } +} + +void +win32_send_audio2(byte *ptr, int size) +{ + int res, wr_pos, rd_pos, new_pos, bufs_in_use; + + wr_pos = g_wavehdr_wr_pos; + rd_pos = g_wavehdr_rd_pos; +#if 0 + if(wr_pos == 0) { + printf("send_audio2 wr:%d rd:%d sz:%d at %.3f\n", wr_pos, + rd_pos, size, get_dtime()); + } +#endif + if(g_wavehdr[wr_pos].dwUser != FALSE) { + // Audio buffer busy...should not happen! + printf("Audio buffer %d is busy!\n", wr_pos); + return; + } + + bufs_in_use = (NUM_WAVE_HEADERS + wr_pos - rd_pos) % NUM_WAVE_HEADERS; + if(g_win32_snd_to_drop) { + g_win32_snd_to_drop--; + g_win32_snd_dropped += size; + if((bufs_in_use < 4) && (g_win32_snd_to_drop != 0)) { +#if 0 + printf("bufs_in_use:%d, snd_to_drop:%d\n", bufs_in_use, + g_win32_snd_to_drop); +#endif + g_win32_snd_to_drop = 0; + } + if(g_win32_snd_to_drop == 0) { + printf("Dropped %d bytes of sound\n", + g_win32_snd_dropped); + } + return; + } +#if 0 + if(g_win32_snd_playing && (bufs_in_use <= 2)) { + printf("bufs_in_use:%d, wr:%d, rd:%d\n", bufs_in_use, wr_pos, + rd_pos); + } +#endif + if(bufs_in_use == 0) { +#if 0 + printf("bufs_in_use:%d, wr_pos:%d rd_pos:%d\n", bufs_in_use, + wr_pos, rd_pos); +#endif + // We've underflowed, so pause sound until we get some buffered + win32snd_set_playing(0); + } else if(g_win32_snd_playing == 0) { + if(bufs_in_use >= 2) { + //printf("bufs_in_use:%d, will start\n", bufs_in_use); + win32snd_set_playing(1); + } + } else { + if(bufs_in_use >= 14) { // About 230msec + // Drop 6 buffers to get us back down to 100msec delay + printf("bufs_in_use:%d, wr:%d will drop 6\n", + bufs_in_use, wr_pos); + g_win32_snd_to_drop = 6; + g_win32_snd_dropped = 0; + } + } + memcpy(g_wavehdr[wr_pos].lpData, ptr, size); + g_wavehdr[wr_pos].dwBufferLength = size; + g_wavehdr[wr_pos].dwUser = TRUE; + + new_pos = (wr_pos + 1) % NUM_WAVE_HEADERS; + g_wavehdr_wr_pos = new_pos; + res = waveOutWrite(g_wave_handle, &g_wavehdr[wr_pos], + sizeof(g_wavehdr)); + check_wave_error(res, "waveOutWrite"); + + return; +} + +int +win32_send_audio(byte *ptr, int in_size) +{ + int size; + int tmpsize; + + // printf("send_audio %d bytes at %.3f\n", in_size, get_dtime()); + size = in_size; + while(size > 0) { + tmpsize = size; + if(size > g_win32snd_buflen) { + tmpsize = g_win32snd_buflen; + } + win32_send_audio2(ptr, tmpsize); + ptr += tmpsize; + if(size != tmpsize) { +#if 0 + printf("Orig size:%d, reduced to %d\n", in_size, + tmpsize); +#endif + } + size = size - tmpsize; + } + + return in_size; +} + diff --git a/gsplus/src/win_dirent.h b/gsplus/src/win_dirent.h new file mode 100644 index 0000000..21ffdbc --- /dev/null +++ b/gsplus/src/win_dirent.h @@ -0,0 +1,32 @@ +#ifdef INCLUDE_RCSID_C +#endif + +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2022 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// Hacky defines to get something to compile for now +typedef unsigned short mode_t; + +struct dirent { + char d_name[1024]; +}; + +struct DIR_t { + int find_data_valid; + void *win_handle; + void *find_data_ptr; + struct dirent dirent; +}; +typedef struct DIR_t DIR; + +DIR *opendir(const char *filename); +struct dirent *readdir(DIR *dirp); +int closedir(DIR *dirp); + diff --git a/gsplus/src/windriver.c b/gsplus/src/windriver.c new file mode 100644 index 0000000..3857952 --- /dev/null +++ b/gsplus/src/windriver.c @@ -0,0 +1,1072 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2024 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// Based on code from Chea Chee Keong from KEGS32, which was available at +// http://www.geocities.com/akilgard/kegs32 (geocities is gone now) + +#define WIN32_LEAN_AND_MEAN /* Tell windows we want less header gunk */ +#define STRICT /* Tell Windows we want compile type checks */ + +#include +#include +#include +#include +#include +#include /* For _get_osfhandle */ + +#include "defc.h" +#include "win_dirent.h" + +extern int Verbose; + +typedef struct windowinfo { + HWND win_hwnd; + HDC win_dc; + HDC win_cdc; + BITMAPINFO *win_bmapinfo_ptr; + BITMAPINFOHEADER *win_bmaphdr_ptr; + HBITMAP win_dev_handle; + + Kimage *kimage_ptr; // KEGS Image pointer for window content + char *name_str; + byte *data_ptr; + int motion; + int mdepth; + int active; + int pixels_per_line; + int x_xpos; + int x_ypos; + int width; + int height; + int extra_width; + int extra_height; +} Window_info; + +#include "protos_windriver.h" + +Window_info g_mainwin_info = { 0 }; +Window_info g_debugwin_info = { 0 }; + +int g_win_max_width = 0; +int g_win_max_height = 0; +int g_num_a2_keycodes = 0; + +int g_win_button_states = 0; +int g_win_hide_pointer = 0; +int g_win_warp_pointer = 0; +int g_win_warp_x = 0; +int g_win_warp_y = 0; + + +/* this table is used to search for the Windows VK_* in col 1 or 2 */ +/* flags bit 8 is or'ed into the VK, so we can distinguish keypad keys */ +/* regardless of numlock */ +int g_a2_key_to_wsym[][3] = { + { 0x35, VK_ESCAPE, 0 }, + { 0x7a, VK_F1, 0 }, + { 0x78, VK_F2, 0 }, + { 0x63, VK_F3, 0 }, + { 0x76, VK_F4, 0 }, + { 0x60, VK_F5, 0 }, + { 0x61, VK_F6, 0 }, + { 0x62, VK_F7, 0 }, + { 0x64, VK_F8, 0 }, + { 0x65, VK_F9, 0 }, + { 0x6d, VK_F10, 0 }, + { 0x67, VK_F11, 0 }, + { 0x6f, VK_F12, 0 }, + { 0x69, VK_F13, 0 }, + { 0x6b, VK_F14, 0 }, + { 0x71, VK_F15, 0 }, + { 0x7f, VK_PAUSE, VK_CANCEL+0x100 }, // Reset + + { 0x12, '1', 0 }, + { 0x13, '2', 0 }, + { 0x14, '3', 0 }, + { 0x15, '4', 0 }, + { 0x17, '5', 0 }, + { 0x16, '6', 0 }, + { 0x1a, '7', 0 }, + { 0x1c, '8', 0 }, + { 0x19, '9', 0 }, + { 0x1d, '0', 0 }, + { 0x1b, 0xbd, 0 }, /* '-' */ + { 0x18, 0xbb, 0 }, /* '=' */ + { 0x33, VK_BACK, 0 }, /* backspace */ + { 0x72, VK_INSERT+0x100, 0 }, /* Insert key */ + { 0x74, VK_PRIOR+0x100, 0 }, /* pageup */ + { 0x47, VK_NUMLOCK, VK_NUMLOCK+0x100 }, /* clear */ + { 0x51, VK_HOME+0x100, 0 }, /* KP_equal is HOME key */ + { 0x4b, VK_DIVIDE, VK_DIVIDE+0x100 }, // KP / + { 0x43, VK_MULTIPLY, VK_MULTIPLY+0x100 }, // KP * + + { 0x30, VK_TAB, 0 }, + { 0x32, 0xc0, 0 }, /* '`' */ + { 0x0c, 'Q', 0 }, + { 0x0d, 'W', 0 }, + { 0x0e, 'E', 0 }, + { 0x0f, 'R', 0 }, + { 0x11, 'T', 0 }, + { 0x10, 'Y', 0 }, + { 0x20, 'U', 0 }, + { 0x22, 'I', 0 }, + { 0x1f, 'O', 0 }, + { 0x23, 'P', 0 }, + { 0x21, 0xdb, 0 }, /* [ */ + { 0x1e, 0xdd, 0 }, /* ] */ + { 0x2a, 0xdc, 0 }, /* backslash, bar */ + { 0x75, VK_DELETE+0x100, 0 }, + { 0x77, VK_END+0x100, VK_END }, + { 0x79, VK_NEXT+0x100, 0 }, + { 0x59, VK_NUMPAD7, VK_HOME }, + { 0x5b, VK_NUMPAD8, VK_UP }, + { 0x5c, VK_NUMPAD9, VK_PRIOR }, + { 0x4e, VK_SUBTRACT, VK_SUBTRACT+0x100 }, + + { 0x39, VK_CAPITAL, 0 }, // Capslock + { 0x00, 'A', 0 }, + { 0x01, 'S', 0 }, + { 0x02, 'D', 0 }, + { 0x03, 'F', 0 }, + { 0x05, 'G', 0 }, + { 0x04, 'H', 0 }, + { 0x26, 'J', 0 }, + { 0x28, 'K', 0 }, + { 0x25, 'L', 0 }, + { 0x29, 0xba, 0 }, /* ; */ + { 0x27, 0xde, 0 }, /* single quote */ + { 0x24, VK_RETURN, 0 }, + { 0x56, VK_NUMPAD4, VK_LEFT }, + { 0x57, VK_NUMPAD5, VK_CLEAR }, + { 0x58, VK_NUMPAD6, VK_RIGHT }, + { 0x45, VK_ADD, 0 }, + + { 0x38, VK_SHIFT, 0 }, + { 0x06, 'Z', 0 }, + { 0x07, 'X', 0 }, + { 0x08, 'C', 0 }, + { 0x09, 'V', 0 }, + { 0x0b, 'B', 0 }, + { 0x2d, 'N', 0 }, + { 0x2e, 'M', 0 }, + { 0x2b, 0xbc, 0 }, /* , */ + { 0x2f, 0xbe, 0 }, /* . */ + { 0x2c, 0xbf, 0 }, /* / */ + { 0x3e, VK_UP+0x100, 0 }, + { 0x53, VK_NUMPAD1, VK_END }, + { 0x54, VK_NUMPAD2, VK_DOWN }, + { 0x55, VK_NUMPAD3, VK_NEXT }, + + { 0x36, VK_CONTROL, VK_CONTROL+0x100 }, + { 0x37, VK_SCROLL, VK_MENU+0x100 }, // Command=scr_lock or alt-r + { 0x3a, VK_SNAPSHOT+0x100, VK_MENU }, // Opt=prntscrn or alt-l + { 0x31, ' ', 0 }, + { 0x3b, VK_LEFT+0x100, 0 }, + { 0x3d, VK_DOWN+0x100, 0 }, + { 0x3c, VK_RIGHT+0x100, 0 }, + { 0x52, VK_NUMPAD0, VK_INSERT }, + { 0x41, VK_DECIMAL, VK_DELETE }, + { 0x4c, VK_RETURN+0x100, 0 }, + { -1, -1, -1 } +}; + +#if 0 +int +win_nonblock_read_stdin(int fd, char *bufptr, int len) +{ + HANDLE oshandle; + DWORD dwret; + int ret; + + errno = EAGAIN; + oshandle = (HANDLE)_get_osfhandle(fd); // get stdin handle + dwret = WaitForSingleObject(oshandle, 1); // wait 1msec for data + ret = -1; + if(dwret == WAIT_OBJECT_0) { + ret = read(fd, bufptr, len); + } + return ret; +} +#endif + +Window_info * +win_find_win_info_ptr(HWND hwnd) +{ + if(hwnd == g_mainwin_info.win_hwnd) { + return &g_mainwin_info; + } + if(hwnd == g_debugwin_info.win_hwnd) { + return &g_debugwin_info; + } + return 0; +} + +void +win_hide_pointer(Window_info *win_info_ptr, int do_hide) +{ + ShowCursor(!do_hide); + // printf("Doing ShowCursor(%d)\n", !do_hide); +} + +int +win_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, + int button_states, int buttons_valid) +{ + Kimage *kimage_ptr; + int buttons_changed, x, y; + + kimage_ptr = win_info_ptr->kimage_ptr; + x = video_scale_mouse_x(kimage_ptr, raw_x, 0); + y = video_scale_mouse_y(kimage_ptr, raw_y, 0); + + // printf("wum: %d,%d -> %d,%d\n", raw_x, raw_y, x, y); + + buttons_changed = ((g_win_button_states & buttons_valid) != + button_states); + g_win_button_states = (g_win_button_states & ~buttons_valid) | + (button_states & buttons_valid); + if(g_win_warp_pointer && (raw_x == g_win_warp_x) && + (raw_y == g_win_warp_y) && (!buttons_changed) ) { + /* tell adb routs to recenter but ignore this motion */ + adb_update_mouse(kimage_ptr, x, y, 0, -1); + return 0; + } + return adb_update_mouse(kimage_ptr, x, y, button_states, + buttons_valid & 7); +} + +void +win_event_mouse(HWND hwnd, WPARAM wParam, LPARAM lParam) +{ + Window_info *win_info_ptr; + word32 flags; + int buttons, x, y, hide, warp; + + win_info_ptr = win_find_win_info_ptr(hwnd); + if(!win_info_ptr) { + return; + } + + flags = (word32)wParam; + x = LOWORD(lParam); + y = HIWORD(lParam); + + buttons = (flags & 1) | (((flags >> 1) & 1) << 2) | + (((flags >> 4) & 1) << 1); +#if 0 + printf("Mouse at %d, %d fl: %08x, but: %d\n", x, y, flags, buttons); +#endif + win_info_ptr->motion |= win_update_mouse(win_info_ptr, x, y, buttons, + 7); + + hide = 0; + warp = 0; + hide = adb_get_hide_warp_info(win_info_ptr->kimage_ptr, &warp); + if(warp != g_win_warp_pointer) { + win_info_ptr->motion = 1; + } + g_win_warp_pointer = warp; + if(g_win_hide_pointer != hide) { + win_hide_pointer(win_info_ptr, hide); + } + g_win_hide_pointer = hide; +} + +void +win_event_key(HWND hwnd, WPARAM wParam, LPARAM lParam, int down) +{ + Window_info *win_info_ptr; + Kimage *kimage_ptr; + word32 vk, raw_vk, flags, capslock_state; + int a2code, is_up; + int i; + + win_info_ptr = win_find_win_info_ptr(hwnd); + if(!win_info_ptr) { + return; + } + kimage_ptr = win_info_ptr->kimage_ptr; + raw_vk = (word32)wParam; + flags = HIWORD(lParam); +#if 0 + printf("win_event_key: raw:%04x lParam:%08x d:%d flags:%08x\n", + raw_vk, (word32)lParam, down, flags); +#endif + + if((flags & 0x4000) && down) { + /* auto-repeating, just ignore it */ + return; + } + + vk = raw_vk + (flags & 0x100); +#if 0 + printf("Key event, vk=%04x, down:%d, repeat: %d, flags: %08x\n", + vk, down, repeat, flags); +#endif + + /* remap a few keys here.. sigh */ + if((vk & 0xff) == VK_APPS) { + /* remap to command */ + vk = VK_MENU; + } + + if((vk & 0xff) == VK_CAPITAL) { + // Fix up capslock info: Windows gives us a down, then up event + // when the capslock key itself is pressed and released. We + // need to ask for the true toggle state instead + capslock_state = (GetKeyState(VK_CAPITAL) & 1); + down = capslock_state; + } + + /* search a2key_to_wsym to find wsym in col 1 or 2 */ + i = 0; + is_up = !down; + for(i = g_num_a2_keycodes-1; i >= 0; i--) { + a2code = g_a2_key_to_wsym[i][0]; + if((vk == g_a2_key_to_wsym[i][1]) || + (vk == g_a2_key_to_wsym[i][2])) { + vid_printf("Found vk:%04x = %02x\n", vk, a2code); + adb_physical_key_update(kimage_ptr, a2code, 0, is_up); + return; + } + } + printf("VK: %04x unknown\n", vk); +} + +void +win_event_redraw(HWND hwnd) +{ + Window_info *win_info_ptr; + + win_info_ptr = win_find_win_info_ptr(hwnd); + + if(win_info_ptr) { + video_set_x_refresh_needed(win_info_ptr->kimage_ptr, 1); + } +} + +void +win_event_destroy(HWND hwnd) +{ + Window_info *win_info_ptr; + + win_info_ptr = win_find_win_info_ptr(hwnd); + if(win_info_ptr == 0) { + return; + } + video_set_active(win_info_ptr->kimage_ptr, 0); + win_info_ptr->active = 0; + if(win_info_ptr == &g_mainwin_info) { + my_exit(0); + } else { + ShowWindow(win_info_ptr->win_hwnd, SW_HIDE); + ReleaseDC(hwnd, win_info_ptr->win_dc); + DeleteDC(win_info_ptr->win_cdc); + DeleteObject(win_info_ptr->win_dev_handle); + GlobalFree(win_info_ptr->win_bmapinfo_ptr); + win_info_ptr->win_hwnd = 0; + win_info_ptr->data_ptr = 0; + } +} + +void +win_event_move(HWND hwnd, WPARAM wParam, LPARAM lParam) +{ + Window_info *win_info_ptr; + int x_xpos, x_ypos; + + // These WM_MOVE events indicate the window is being moved + + win_info_ptr = win_find_win_info_ptr(hwnd); + if(!win_info_ptr) { + return; + } + // printf("WM_MOVE: %04x %08x\n", (word32)wParam, (word32)lParam); + x_xpos = lParam & 0xffff; + x_ypos = (lParam >> 16) & 0xffff; + video_update_xpos_ypos(win_info_ptr->kimage_ptr, x_xpos, x_ypos); +} + +void +win_event_size(HWND hwnd, WPARAM wParam, LPARAM lParam) +{ + Window_info *win_info_ptr; + int width, height; + + // These WM_SIZE events indicate the window is being resized + + win_info_ptr = win_find_win_info_ptr(hwnd); + if(!win_info_ptr) { + return; + } + // printf("WM_SIZE: %04x %08x\n", (word32)wParam, (word32)lParam); + width = lParam & 0xffff; + height = (lParam >> 16) & 0xffff; + video_update_scale(win_info_ptr->kimage_ptr, width, height, 0); +#if 0 + printf("Frac width: %f\n", + win_info_ptr->kimage_ptr->scale_width_a2_to_x / 65536.0); +#endif + + // The following try to do "live updating" of the resize + win_info_ptr->kimage_ptr->x_refresh_needed = 1; + x_update_display(win_info_ptr); +} + +void +win_event_minmaxinfo(HWND hwnd, LPARAM lParam) +{ + Window_info *win_info_ptr; + MINMAXINFO *minmax_ptr; + int a2_width, a2_height; + + // Windows sends WM_GETMINMAXINFO events when resizing is occurring, + // and we can modify the *lParam MINMAXINFO structure to set the + // minimum and maximum Track size (the size of the window) + // This code forces the minimum to be the A2 window size, and the + // maximum to be the screen size + win_info_ptr = win_find_win_info_ptr(hwnd); + if(!win_info_ptr) { + return; + } + minmax_ptr = (MINMAXINFO *)lParam; +#if 0 + printf("MinMax: mintrack.x:%d, mintrack.y:%d\n", + minmax_ptr->ptMinTrackSize.x, + minmax_ptr->ptMinTrackSize.y); +#endif + a2_width = video_get_a2_width(win_info_ptr->kimage_ptr); + a2_height = video_get_a2_height(win_info_ptr->kimage_ptr); + minmax_ptr->ptMinTrackSize.x = a2_width + win_info_ptr->extra_width; + minmax_ptr->ptMinTrackSize.y = a2_height + win_info_ptr->extra_height; + minmax_ptr->ptMaxTrackSize.x = g_win_max_width + + win_info_ptr->extra_width; + minmax_ptr->ptMaxTrackSize.y = g_win_max_height + + win_info_ptr->extra_height; +} + +void +win_event_focus(HWND hwnd, int gain_focus) +{ + Window_info *win_info_ptr; + word32 c025_val, info; + + win_info_ptr = win_find_win_info_ptr(hwnd); + if(!win_info_ptr) { + return; + } + + if(gain_focus) { + // printf("Got focus on %p\n", hwnd); + // Get shift, ctrl, capslock state + c025_val = 0; + info = GetKeyState(VK_SHIFT); // left or right + if(info & 0x8000) { + c025_val |= 1; // Shift key is down + } + info = GetKeyState(VK_CONTROL); // left or right + if(info & 0x8000) { + c025_val |= 2; + } + info = GetKeyState(VK_CAPITAL); // Capslock? + if(info & 1) { + c025_val |= 4; // Capslock key is down + } + //printf("Calling update_c025 with %03x\n", c025_val); + adb_update_c025_mask(win_info_ptr->kimage_ptr, c025_val, 7); + } else { + // printf("Lost focus on %p\n", hwnd); + } + if(win_info_ptr == &g_mainwin_info) { + adb_kbd_repeat_off(); + adb_mainwin_focus(gain_focus); + } +} + +LRESULT CALLBACK +win_event_handler(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + +#if 0 + printf("Message: umsg: %04x, wparam:%04x lParam:%08x\n", umsg, + (word32)wParam, (word32)lParam); +#endif + + switch(umsg) { + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + win_event_mouse(hwnd, wParam, lParam); + return 0; + case WM_KEYUP: + case WM_SYSKEYUP: + win_event_key(hwnd, wParam, lParam, 0); + return 0; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + win_event_key(hwnd, wParam, lParam, 1); + return 0; + case WM_SYSCOMMAND: + // Alt key press can cause this. Return 0 for SC_KEYMENU + if(wParam == SC_KEYMENU) { + return 0; + } + break; + case WM_KILLFOCUS: + win_event_focus(hwnd, 0); + break; + case WM_SETFOCUS: + win_event_focus(hwnd, 1); + break; + case WM_DESTROY: + win_event_destroy(hwnd); + return 0; + case WM_PAINT: + win_event_redraw(hwnd); + break; + case WM_MOVE: + win_event_move(hwnd, wParam, lParam); + break; + case WM_SIZE: + win_event_size(hwnd, wParam, lParam); + break; + case WM_GETMINMAXINFO: + win_event_minmaxinfo(hwnd, lParam); + break; + } +#if 0 + switch(umsg) { + HANDLE_MSG(hwnd, WM_KEYUP, win_event_key); + HANDLE_MSG(hwnd, WM_KEYDOWN, win_event_key); + HANDLE_MSG(hwnd, WM_SYSKEYUP, win_event_key); + HANDLE_MSG(hwnd, WM_SYSKEYDOWN, win_event_key); + HANDLE_MSG(hwnd, WM_DESTROY, win_event_destroy); + } +#endif + +#if 0 + switch(umsg) { + case WM_NCACTIVATE: + case WM_NCHITTEST: + case WM_NCMOUSEMOVE: + case WM_SETCURSOR: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_CONTEXTMENU: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_PAINT: + + break; + default: + printf("Got umsg2: %d\n", umsg); + } +#endif + + return DefWindowProc(hwnd, umsg, wParam, lParam); +} + + +int +main(int argc, char **argv) +{ + int ret, mdepth; + + ret = parse_argv(argc, argv, 1); + if(ret) { + printf("parse_argv ret: %d, stopping\n", ret); + exit(1); + } + + mdepth = 32; + + video_set_blue_mask(0x0000ff); + video_set_green_mask(0x00ff00); + video_set_red_mask(0xff0000); + + g_win_max_width = GetSystemMetrics(SM_CXSCREEN); + g_win_max_height = GetSystemMetrics(SM_CYSCREEN); + vid_printf("g_win_max_width:%d, g_win_max_height:%d\n", + g_win_max_width, g_win_max_height); + + ret = kegs_init(mdepth, g_win_max_width, g_win_max_height, 0); + printf("kegs_init done\n"); + if(ret) { + printf("kegs_init ret: %d, stopping\n", ret); + exit(1); + } + + win_video_init(mdepth); + + printf("Entering main loop!\n"); + fflush(stdout); + while(1) { + ret = run_16ms(); + if(ret != 0) { + printf("run_16ms returned: %d\n", ret); + break; + } + x_update_display(&g_mainwin_info); + x_update_display(&g_debugwin_info); + check_input_events(); + } + xdriver_end(); + exit(0); +} + +void +check_input_events() +{ + MSG msg; + POINT pt; + BOOL ret; + Window_info *win_info_ptr; + + while(PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE)) { + if(GetMessage(&msg, 0, 0, 0) > 0) { + //TranslateMessage(&msg); + DispatchMessage(&msg); + } else { + printf("GetMessage returned <= 0\n"); + my_exit(2); + } + } + + win_info_ptr = &g_mainwin_info; + if(win_info_ptr->motion == 0) { + return; + } + + // ONLY look at g_mainwin_info! + win_info_ptr->motion = 0; + + if(g_win_warp_pointer) { + /* move mouse to center of screen */ + g_win_warp_x = video_unscale_mouse_x(win_info_ptr->kimage_ptr, + BASE_MARGIN_LEFT + (A2_WINDOW_WIDTH/2), 0); + g_win_warp_y = video_unscale_mouse_y(win_info_ptr->kimage_ptr, + BASE_MARGIN_TOP + (A2_WINDOW_HEIGHT/2), 0); + pt.x = g_win_warp_x; + pt.y = g_win_warp_y; + ClientToScreen(win_info_ptr->win_hwnd, &pt); + ret = SetCursorPos(pt.x, pt.y); +#if 0 + printf("Did SetCursorPos(%d, %d) warp_x:%d,y:%d, ret:%d\n", + pt.x, pt.y, g_win_warp_x, g_win_warp_y, ret); +#endif + } + + return; +} + +void +win_video_init(int mdepth) +{ + WNDCLASS wndclass; + int a2code; + int i; + + video_set_palette(); + + g_num_a2_keycodes = 0; + for(i = 0; i < 0x7f; i++) { + a2code = g_a2_key_to_wsym[i][0]; + if(a2code < 0) { + g_num_a2_keycodes = i; + break; + } + } + wndclass.style = 0; + wndclass.lpfnWndProc = (WNDPROC)win_event_handler; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = GetModuleHandle(NULL); + wndclass.hIcon = LoadIcon((HINSTANCE)NULL, IDI_APPLICATION); + wndclass.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW); + wndclass.hbrBackground = GetStockObject(WHITE_BRUSH); + wndclass.lpszMenuName = NULL; + wndclass.lpszClassName = "kegswin"; + + // Register the window + if(!RegisterClass(&wndclass)) { + printf("Registering window failed\n"); + exit(1); + } + + win_init_window(&g_mainwin_info, video_get_kimage(0), "KEGS", mdepth); + win_init_window(&g_debugwin_info, video_get_kimage(1), + "KEGS Debugger", mdepth); + + win_create_window(&g_mainwin_info); +} + +void +win_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str, + int mdepth) +{ + int height, width, x_xpos, x_ypos; + + height = video_get_x_height(kimage_ptr); + width = video_get_x_width(kimage_ptr); + + x_xpos = video_get_x_xpos(kimage_ptr); + x_ypos = video_get_x_ypos(kimage_ptr); + + win_info_ptr->win_hwnd = 0; + win_info_ptr->win_dc = 0; + win_info_ptr->win_cdc = 0; + win_info_ptr->win_bmapinfo_ptr = 0; + win_info_ptr->win_bmaphdr_ptr = 0; + win_info_ptr->win_dev_handle = 0; + win_info_ptr->kimage_ptr = kimage_ptr; + win_info_ptr->name_str = name_str; + win_info_ptr->data_ptr = 0; + win_info_ptr->motion = 0; + win_info_ptr->mdepth = mdepth; + win_info_ptr->active = 0; + win_info_ptr->pixels_per_line = width; + win_info_ptr->x_xpos = x_xpos; + win_info_ptr->x_ypos = x_ypos; + win_info_ptr->width = width; + win_info_ptr->height = height; +} + +void +win_create_window(Window_info *win_info_ptr) +{ + HWND win_hwnd; + RECT rect; + BITMAPINFO *bmapinfo_ptr; + BITMAPINFOHEADER *bmaphdr_ptr; + HBITMAP win_dev_handle; + Kimage *kimage_ptr; + int height, width, extra_width, extra_height; + int extra_size, w_flags; + + kimage_ptr = win_info_ptr->kimage_ptr; + + height = win_info_ptr->height; + width = win_info_ptr->width; + + printf("Got height: %d, width:%d\n", height, width); + + // We must call CreateWindow with a width,height that accounts for + // the title bar and any other stuff. Use AdjustWindowRect() to + // calculate this info for us + w_flags = WS_TILED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | + WS_SIZEBOX; + rect.left = 0; + rect.top = 0; + rect.right = width; + rect.bottom = height; + (void)AdjustWindowRect(&rect, w_flags, 0); + extra_width = rect.right - rect.left - width; + extra_height = rect.bottom - rect.top - height; + win_info_ptr->extra_width = extra_width; + win_info_ptr->extra_height = extra_height; + win_hwnd = CreateWindow("kegswin", win_info_ptr->name_str, w_flags, + win_info_ptr->x_xpos, win_info_ptr->x_ypos, width + extra_width, + height + extra_height, NULL, NULL, GetModuleHandle(NULL), NULL); + win_info_ptr->win_hwnd = win_hwnd; + win_info_ptr->active = 0; + + video_set_active(kimage_ptr, 1); + video_update_scale(kimage_ptr, win_info_ptr->width, + win_info_ptr->height, 1); + + printf("win_hwnd = %p, height = %d\n", win_hwnd, height); + GetWindowRect(win_hwnd, &rect); + printf("...rect is: %ld, %ld, %ld, %ld\n", rect.left, rect.top, + rect.right, rect.bottom); + + win_info_ptr->win_dc = GetDC(win_hwnd); + + SetTextColor(win_info_ptr->win_dc, 0); + SetBkColor(win_info_ptr->win_dc, 0xffffff); + + win_info_ptr->win_cdc = CreateCompatibleDC(win_info_ptr->win_dc); + printf("win_cdc: %p, win_dc:%p\n", win_info_ptr->win_cdc, + win_info_ptr->win_dc); + + + printf("Getting height, kimage_ptr:%p\n", kimage_ptr); + fflush(stdout); + + win_info_ptr->data_ptr = 0; + + extra_size = sizeof(RGBQUAD); + bmapinfo_ptr = (BITMAPINFO *)GlobalAlloc(GPTR, + sizeof(BITMAPINFOHEADER) + extra_size); + win_info_ptr->win_bmapinfo_ptr = bmapinfo_ptr; + + bmaphdr_ptr = (BITMAPINFOHEADER *)bmapinfo_ptr; + win_info_ptr->win_bmaphdr_ptr = bmaphdr_ptr; + bmaphdr_ptr->biSize = sizeof(BITMAPINFOHEADER); + bmaphdr_ptr->biWidth = g_win_max_width; + bmaphdr_ptr->biHeight = -g_win_max_height; + bmaphdr_ptr->biPlanes = 1; + bmaphdr_ptr->biBitCount = win_info_ptr->mdepth; + bmaphdr_ptr->biCompression = BI_RGB; + bmaphdr_ptr->biClrUsed = 0; + + /* Use g_bmapinfo_ptr, adjusting width, height */ + printf("bmaphdr_ptr:%p\n", bmaphdr_ptr); + + printf("About to call CreateDIBSection, win_dc:%p\n", + win_info_ptr->win_dc); + fflush(stdout); + win_dev_handle = CreateDIBSection(win_info_ptr->win_dc, + win_info_ptr->win_bmapinfo_ptr, DIB_RGB_COLORS, + (VOID **)&(win_info_ptr->data_ptr), NULL, 0); + win_info_ptr->win_dev_handle = win_dev_handle; + + win_info_ptr->pixels_per_line = g_win_max_width; + printf("kim: %p, dev:%p data: %p\n", kimage_ptr, + win_dev_handle, win_info_ptr->data_ptr); + fflush(stdout); +} + +void +xdriver_end() +{ + printf("xdriver_end\n"); +} + +void +win_resize_window(Window_info *win_info_ptr) +{ + RECT rect1, rect2; + BOOL ret; + Kimage *kimage_ptr; + int x_width, x_height; + + kimage_ptr = win_info_ptr->kimage_ptr; + x_width = video_get_x_width(kimage_ptr); + x_height = video_get_x_height(kimage_ptr); + // printf("win_resize_window, x_w:%d, x_h:%d\n", x_width, x_height); + + ret = GetWindowRect(win_info_ptr->win_hwnd, &rect1); + ret = GetClientRect(win_info_ptr->win_hwnd, &rect2); +#if 0 + printf("window_rect: l:%d, t:%d, r:%d, b:%d\n", + rect1.left, rect1.top, rect1.right, rect1.bottom); + printf("client_rect: l:%d, t:%d, r:%d, b:%d\n", + rect2.left, rect2.top, rect2.right, rect2.bottom); +#endif + ret = MoveWindow(win_info_ptr->win_hwnd, rect1.left, rect1.top, + x_width + win_info_ptr->extra_width, + x_height + win_info_ptr->extra_height, TRUE); + // printf("MoveWindow ret:%d\n", ret); + win_info_ptr->width = x_width; + win_info_ptr->height = x_height; +} + +void +x_update_display(Window_info *win_info_ptr) +{ + Change_rect rect; + void *bitm_old; + //POINT point; + int valid, a2_active, x_active; + int i; + + a2_active = video_get_active(win_info_ptr->kimage_ptr); + x_active = win_info_ptr->active; + + if(x_active && !a2_active) { + // We need to SW_HIDE this window + ShowWindow(win_info_ptr->win_hwnd, SW_HIDE); + x_active = 0; + win_info_ptr->active = x_active; + } + if(!x_active && a2_active) { + // We need to SW_SHOWDEFAULT this window (and maybe create it) + if(win_info_ptr->win_hwnd == 0) { + win_create_window(win_info_ptr); + } + ShowWindow(win_info_ptr->win_hwnd, SW_SHOWDEFAULT); + UpdateWindow(win_info_ptr->win_hwnd); + x_active = 1; + win_info_ptr->active = x_active; + } + if(x_active == 0) { + return; + } + + if(video_change_aspect_needed(win_info_ptr->kimage_ptr, + win_info_ptr->width, win_info_ptr->height)) { + win_resize_window(win_info_ptr); + } + for(i = 0; i < MAX_CHANGE_RECTS; i++) { + valid = video_out_data(win_info_ptr->data_ptr, + win_info_ptr->kimage_ptr, win_info_ptr->pixels_per_line, + &rect, i); + if(!valid) { + break; + } +#if 0 + point.x = 0; + point.y = 0; + ClientToScreen(win_info_ptr->win_hwnd, &point); +#endif + bitm_old = SelectObject(win_info_ptr->win_cdc, + win_info_ptr->win_dev_handle); + + BitBlt(win_info_ptr->win_dc, rect.x, rect.y, rect.width, + rect.height, win_info_ptr->win_cdc, rect.x, rect.y, + SRCCOPY); + + SelectObject(win_info_ptr->win_cdc, bitm_old); + } +} + +void +x_hide_pointer(int do_hide) +{ + if(do_hide) { + ShowCursor(0); + } else { + ShowCursor(1); + } +} + +int +opendir_int(DIR *dirp, const char *in_filename) +{ + HANDLE handle1; + wchar_t *wcstr; + char *filename; + size_t ret_val; + int buflen, len; + int i; + + printf("opendir on %s\n", in_filename); + len = (int)strlen(in_filename); + buflen = len + 8; + if(buflen >= sizeof(dirp->dirent.d_name)) { + printf("buflen %d >= d_name %d\n", buflen, + (int)sizeof(dirp->dirent.d_name)); + return 1; + } + filename = &dirp->dirent.d_name[0]; + memcpy(filename, in_filename, len + 1); + while(len && (filename[len-1] == '/')) { + filename[len - 1] = 0; + len--; + } + cfg_strlcat(filename, "/*.*", buflen); + for(i = 0; i < len; i++) { + if(filename[i] == '/') { + filename[i] = '\\'; + } + } + len = (int)strlen(filename); + wcstr = malloc(buflen * 2); + (void)mbstowcs_s(&ret_val, wcstr, buflen, filename, _TRUNCATE); + handle1 = FindFirstFileW(wcstr, dirp->find_data_ptr); + dirp->win_handle = handle1; + free(wcstr); + if(handle1) { + dirp->find_data_valid = 1; + return 0; + } + return 1; +} + +DIR * +opendir(const char *in_filename) +{ + DIR *dirp; + int ret; + + dirp = calloc(1, sizeof(DIR)); + if(!dirp) { + return 0; + } + dirp->find_data_valid = 1; + dirp->find_data_ptr = calloc(1, sizeof(WIN32_FIND_DATAW)); + ret = 1; + if(dirp->find_data_ptr) { + ret = opendir_int(dirp, in_filename); + } + if(ret) { // Bad + free(dirp->find_data_ptr); // free(0) is OK + free(dirp); + return 0; + } + return dirp; +} + +struct dirent * +readdir(DIR *dirp) +{ + WIN32_FIND_DATAW *find_data_ptr; + HANDLE handle1; + size_t ret_val; + BOOL ret; + + handle1 = dirp->win_handle; + find_data_ptr = dirp->find_data_ptr; + if(!handle1 || !find_data_ptr) { + return 0; + } + ret = 1; + if(!dirp->find_data_valid) { + if(handle1) { + find_data_ptr->cFileName[MAX_PATH-1] = 0; + ret = FindNextFileW(handle1, find_data_ptr); + } + } + dirp->find_data_valid = 0; + if(!ret) { + return 0; + } + (void)wcstombs_s(&ret_val, &(dirp->dirent.d_name[0]), + (int)sizeof(dirp->dirent.d_name), + &(find_data_ptr->cFileName[0]), _TRUNCATE); + printf("Returning file %s\n", &(dirp->dirent.d_name[0])); + + return &(dirp->dirent);; +} + +int +closedir(DIR *dirp) +{ + FindClose(dirp->win_handle); + free(dirp->find_data_ptr); + free(dirp); + + return 0; +} + +int +lstat(const char *path, struct stat *bufptr) +{ + return stat(path, bufptr); +} + +int +ftruncate(int fd, word32 length) +{ + HANDLE handle1; + + handle1 = (HANDLE)_get_osfhandle(fd); + SetFilePointer(handle1, length, 0, FILE_BEGIN); + SetEndOfFile(handle1); + + return 0; +} + diff --git a/gsplus/src/woz.c b/gsplus/src/woz.c new file mode 100644 index 0000000..2b26230 --- /dev/null +++ b/gsplus/src/woz.c @@ -0,0 +1,1138 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2025 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +// Based on WOZ 2.0/1.0 spec at https://applesaucefdc.com/woz/reference2/ + +#include "defc.h" + +extern const char g_kegs_version_str[]; + +byte g_woz_hdr_bytes[12] = { 0x57, 0x4f, 0x5a, 0x32, // "WOZ2" + 0xff, 0x0a, 0x0d, 0x0a, 0, 0, 0, 0 }; + +word32 g_woz_crc32_tab[256]; + +void +woz_crc_init() +{ + word32 crc, val, xor; + int i, j; + + for(i = 0; i < 256; i++) { + crc = 0; + val = i; + for(j = 0; j < 8; j++) { + xor = 0; + if((val ^ crc) & 1) { + xor = 0xedb88320UL; + } + crc = (crc >> 1) ^ xor; + val = val >> 1; + } + g_woz_crc32_tab[i] = crc; + // printf("crc32_tab[%d] = %08x\n", i, crc); + } +} + +word32 +woz_calc_crc32(byte *bptr, dword64 dlen, word32 bytes_to_skip) +{ + word32 crc, c; + + crc = (~0U); + if(bytes_to_skip > dlen) { + dlen = 0; + } else { + bptr += bytes_to_skip; + dlen -= bytes_to_skip; + } + while(dlen != 0) { + c = *bptr++; + dlen--; + crc = g_woz_crc32_tab[(crc ^ c) & 0xff] ^ (crc >> 8); + } + return (~crc); +} + +void +woz_rewrite_crc(Disk *dsk, int min_write_size) +{ + Woz_info *wozinfo_ptr; + byte *wozptr; + word32 crc; + + // Recalculate WOZ image CRC and write it to disk + + wozinfo_ptr = dsk->wozinfo_ptr; + if(!wozinfo_ptr || (dsk->fd < 0)) { + return; + } + wozptr = wozinfo_ptr->wozptr; + crc = woz_calc_crc32(&wozptr[0], wozinfo_ptr->woz_size, 12); + cfg_set_le32(&wozptr[8], crc); + if(min_write_size < 12) { + min_write_size = 12; + } + cfg_write_to_fd(dsk->fd, wozptr, 0, min_write_size); +} + +void +woz_rewrite_lock(Disk *dsk) +{ + Woz_info *wozinfo_ptr; + byte *wozptr; + int offset; + + wozinfo_ptr = dsk->wozinfo_ptr; + if(!wozinfo_ptr || (dsk->fd < 0)) { + return; + } + wozptr = wozinfo_ptr->wozptr; + offset = wozinfo_ptr->info_offset; + + wozptr[offset + 2] = (dsk->write_prot != 0); // Update locked + woz_rewrite_crc(dsk, offset + 2 + 1); +} + +void +woz_check_file(Disk *dsk) +{ + Woz_info *wozinfo_ptr; + byte *wozptr, *newwozptr, *bptr; + word32 fdval, memval, crc, mem_crc, crcnew, file_size; + int woz_size; + int i; + + wozinfo_ptr = dsk->wozinfo_ptr; + if(!wozinfo_ptr) { + return; + } + woz_size = wozinfo_ptr->woz_size; + wozptr = wozinfo_ptr->wozptr; + crc = woz_calc_crc32(wozptr, woz_size, 12); + mem_crc = wozptr[8] | (wozptr[9] << 8) | (wozptr[10] << 16) | + (wozptr[11] << 24); + if(crc != mem_crc) { + halt_printf("WOZ CRC calc:%08x, from mem:%08x\n", crc, mem_crc); + } + if((dsk->fd < 0) || dsk->raw_data || !dsk->write_through_to_unix) { + printf("woz_check_file: CRC check done, cannot check fd\n"); + return; + } + + file_size = (word32)cfg_get_fd_size(dsk->fd); + if(file_size != (word32)woz_size) { + halt_printf("woz_size:%08x != file_size %08x\n", woz_size, + file_size); + if((word32)woz_size > file_size) { + woz_size = file_size; + } + } + newwozptr = malloc(woz_size); + cfg_read_from_fd(dsk->fd, newwozptr, 0, woz_size); + + for(i = 0; i < woz_size; i++) { + fdval = newwozptr[i]; + memval = wozptr[i]; + if(fdval == memval) { + continue; + } + halt_printf("byte %07x of %07x: mem %02x != %02x fd\n", i, + woz_size, memval, fdval); + } + + crcnew = woz_calc_crc32(newwozptr, woz_size, 12); + free(newwozptr); + printf("Woz check file complete. mem %08x vs fd %08x, freed %p\n", + crc, crcnew, newwozptr); + + bptr = dsk->cur_trk_ptr->raw_bptr; + printf("dsk->cur_trk_ptr->raw_bptr = %p. offset:%d\n", bptr, + (int)(bptr - wozptr)); +} + +void +woz_parse_meta(Disk *dsk, int offset, int size) +{ + Woz_info *wozinfo_ptr; + byte *wozptr, *bptr; + int c; + int i; + + wozinfo_ptr = dsk->wozinfo_ptr; + wozptr = wozinfo_ptr->wozptr; + if(wozinfo_ptr->meta_offset) { + printf("Bad WOZ file, 2 META chunks\n"); + wozinfo_ptr->woz_size = 0; + return; + } + wozinfo_ptr->meta_offset = offset; + wozinfo_ptr->meta_size = size; + printf("META field, %d bytes:\n", size); + bptr = &(wozptr[offset]); + for(i = 0; i < size; i++) { + c = bptr[i]; + if(c == 0) { + break; + } + putchar(c); + } + putchar('\n'); +} + +void +woz_parse_info(Disk *dsk, int offset, int size) +{ + byte new_buf[36]; + Woz_info *wozinfo_ptr; + byte *wozptr, *bptr; + int info_version, disk_type, write_protect, synchronized; + int cleaned, ram, largest_track; + + wozinfo_ptr = dsk->wozinfo_ptr; + wozptr = wozinfo_ptr->wozptr; + if(wozinfo_ptr->info_offset) { + printf("Two INFO chunks, bad WOZ file\n"); + wozinfo_ptr->woz_size = 0; + return; + } + wozinfo_ptr->info_offset = offset; + bptr = &(wozptr[offset]); + if(size < 60) { + printf("INFO field is %d, too short\n", size); + wozinfo_ptr->woz_size = 0; + return; + } + info_version = bptr[0]; // Only "1" or "2" is supported + disk_type = bptr[1]; // 1==5.25", 2=3.5" + write_protect = bptr[2]; // 1==write protected + synchronized = bptr[3]; // 1==cross track sync during imaging + cleaned = bptr[4]; // 1==MC3470 fake bits have been removed + memcpy(&new_buf[0], &(bptr[5]), 32); + new_buf[32] = 0; // Null terminate + printf("INFO, %d bytes. info_version:%d, disk_type:%d, wp:%d, sync:" + "%d, cleaned:%d\n", size, info_version, disk_type, + write_protect, synchronized, cleaned); + printf("Creator: %s\n", (char *)&new_buf[0]); + if(info_version >= 2) { + ram = bptr[42] + (bptr[43] << 8); + largest_track = (bptr[44] + (bptr[45] << 8)) * 512; + printf("Disk sides:%d, boot_format:%d bit_timing:%d, hw:" + "%02x%02x, ram:%d, largest_track:0x%07x\n", bptr[37], + bptr[38], bptr[39], bptr[41], bptr[40], ram, + largest_track); + } + + if(write_protect) { + printf("Write protected\n"); + dsk->write_prot = 1; + } +} + +void +woz_parse_tmap(Disk *dsk, int offset, int size) +{ + Woz_info *wozinfo_ptr; + byte *bptr, *wozptr; + int i; + + wozinfo_ptr = dsk->wozinfo_ptr; + wozptr = wozinfo_ptr->wozptr; + if(wozinfo_ptr->tmap_offset) { + printf("Second TMAP chunk, bad WOZ file!\n"); + wozinfo_ptr->woz_size = 0; + return; + } + wozinfo_ptr->tmap_offset = offset; + printf("TMAP field, %d bytes\n", size); + bptr = &(wozptr[offset]); + for(i = 0; i < 40; i++) { + printf("Track %2d.00: %02x, %2d.25:%02x %2d.50:%02x %2d.75:" + "%02x\n", i, bptr[0], i, bptr[1], + i, bptr[2], i, bptr[3]); + bptr += 4; + } +} + +void +woz_parse_trks(Disk *dsk, int offset, int size) +{ + Woz_info *wozinfo_ptr; + + printf("TRKS field, %d bytes, offset: %d\n", size, offset); + wozinfo_ptr = dsk->wozinfo_ptr; + if(wozinfo_ptr->trks_offset) { + printf("Second TRKS chunk, illegal Woz file\n"); + wozinfo_ptr->woz_size = 0; + return; + } + wozinfo_ptr->trks_offset = offset; + wozinfo_ptr->trks_size = size; +} + +int +woz_add_track(Disk *dsk, int qtr_track, word32 tmap, dword64 dfcyc) +{ + Woz_info *wozinfo_ptr; + Trk *trk; + byte *wozptr, *sync_ptr, *bptr; + word32 raw_bytes, num_bytes, trks_size, len_bits, offset, num_blocks; + word32 block; + int trks_offset; + int i; + + wozinfo_ptr = dsk->wozinfo_ptr; + wozptr = wozinfo_ptr->wozptr; + trks_offset = wozinfo_ptr->trks_offset; + trks_size = wozinfo_ptr->trks_size; + if(wozinfo_ptr->version == 1) { + offset = tmap * 6656; + if((offset + 6656) > trks_size) { + printf("Trk %d is out of range!\n", tmap); + return 0; + } + + offset = trks_offset + offset; + bptr = &(wozptr[offset]); + len_bits = bptr[6648] | (bptr[6649] << 8); + if(len_bits > (6656*8)) { + printf("Trk bits: %d too big\n", len_bits); + return 0; + } + } else { + bptr = &(wozptr[trks_offset + (tmap * 8)]); + // This is a TRK 8-byte structure + block = cfg_get_le16(&bptr[0]); // Starting Block + num_blocks = cfg_get_le16(&bptr[2]); // Block Count + len_bits = cfg_get_le32(&bptr[4]); // Bits Count +#if 0 + printf("qtr_track:%02x, block:%04x, num_blocks:%04x, " + "len_bits:%06x\n", qtr_track, block, num_blocks, + len_bits); + printf("File offset: %05lx\n", bptr - wozinfo_ptr->wozptr); +#endif + + if(block < 3) { + printf("block %04x is < 3\n", block); + return 0; + } + offset = (block * 512); // Offset from wozptr + if((offset + (num_blocks * 512)) > (trks_size + trks_offset)) { + printf("Trk %d is out of range!\n", tmap); + return 0; + } + bptr = &(wozptr[offset]); +#if 0 + printf("Qtr_track %03x offset:%06x, bptr:%p, trks_bptr:%p\n", + qtr_track, offset, bptr, trks_bptr); + printf(" len_bits:%d %06x bptr-wozptr: %07lx\n", len_bits, + len_bits, bptr - wozinfo_ptr->wozptr); +#endif + if(len_bits > (num_blocks * 512 * 8)) { + printf("Trk bits: %d too big\n", len_bits); + return 0; + } + } + + dsk->raw_bptr_malloc = 0; + + raw_bytes = (len_bits + 7) >> 3; + num_bytes = raw_bytes + 8; + trk = &(dsk->trks[qtr_track]); + trk->raw_bptr = bptr; + trk->dunix_pos = offset; + trk->unix_len = raw_bytes; + trk->dirty = 0; + trk->track_bits = len_bits; +#if 0 + printf("track %d.%d dunix_pos:%08llx\n", qtr_track >> 2, + (qtr_track & 3) * 25, trk->dunix_pos); +#endif + trk->sync_ptr = (byte *)malloc(num_bytes); + + dsk->cur_trk_ptr = 0; + iwm_move_to_ftrack(dsk, qtr_track << 16, 0, dfcyc); + sync_ptr = &(trk->sync_ptr[0]); + for(i = 0; i < (int)raw_bytes; i++) { + sync_ptr[i] = 0xff; + } + + iwm_recalc_sync_from(dsk, qtr_track, 0, dfcyc); + + if(qtr_track == 0) { + printf("Track 0 data begins: %02x %02x %02x, offset:%d\n", + bptr[0], bptr[1], bptr[2], offset); + } + for(i = 0; i < qtr_track; i++) { + trk = &(dsk->trks[i]); + if(trk->track_bits && (trk->raw_bptr == bptr)) { + // Multiple tracks point to the same woz track + // This is not allowed, reparse + wozinfo_ptr->reparse_needed = 1; + printf("Track %04x matchs track %04x, reparse needed\n", + qtr_track, i); + break; + } + } + + return 1; +} + +int +woz_parse_header(Disk *dsk) +{ + Woz_info *wozinfo_ptr; + byte *wozptr, *bptr; + word32 chunk_id, size, woz_size; + int pos, version; + int i; + + wozinfo_ptr = dsk->wozinfo_ptr; + wozptr = wozinfo_ptr->wozptr; + woz_size = wozinfo_ptr->woz_size; + version = 2; + if(woz_size < 8) { + return 0; + } + for(i = 0; i < 8; i++) { + if(wozptr[i] != g_woz_hdr_bytes[i]) { + if(i == 3) { // Check for WOZ1 + if(wozptr[i] == 0x31) { // WOZ1 + version = 1; + continue; + } + } + printf("WOZ header[%d]=%02x, invalid\n", i, wozptr[i]); + return 0; + } + } + wozinfo_ptr->version = version; + + printf("WOZ version: %d\n", version); + pos = 12; + while(pos < (int)woz_size) { + bptr = &(wozptr[pos]); + chunk_id = bptr[0] | (bptr[1] << 8) | (bptr[2] << 16) | + (bptr[3] << 24); + size = bptr[4] | (bptr[5] << 8) | (bptr[6] << 16) | + (bptr[7] << 24); + pos += 8; + printf("chunk_id: %08x, size:%08x\n", chunk_id, size); + if(((pos + size) > woz_size) || (size < 8) || + ((size >> 30) != 0)) { + return 0; + } + bptr = &(wozptr[pos]); + if(chunk_id == 0x4f464e49) { // "INFO" + woz_parse_info(dsk, pos, size); + } else if(chunk_id == 0x50414d54) { // "TMAP" + woz_parse_tmap(dsk, pos, size); + } else if(chunk_id == 0x534b5254) { // "TRKS" + woz_parse_trks(dsk, pos, size); + } else if(chunk_id == 0x4154454d) { // "META" + woz_parse_meta(dsk, pos, size); + } else { + printf("Chunk header %08x is unknown\n", chunk_id); + } + pos += size; + } + + return 1; // Good so far +} + +Woz_info * +woz_malloc(byte *wozptr, word32 woz_size) +{ + Woz_info *wozinfo_ptr; + + wozinfo_ptr = malloc(sizeof(Woz_info)); + printf("malloc wozinfo_ptr:%p\n", wozinfo_ptr); + if(!wozinfo_ptr) { + fprintf(stderr, "Out of memory\n"); + exit(1); + } + + wozinfo_ptr->wozptr = wozptr; + wozinfo_ptr->woz_size = woz_size; + wozinfo_ptr->version = 0; + wozinfo_ptr->reparse_needed = 0; + wozinfo_ptr->max_trk_blocks = 0; + wozinfo_ptr->meta_size = 0; + wozinfo_ptr->trks_size = 0; + wozinfo_ptr->tmap_offset = 0; + wozinfo_ptr->trks_offset = 0; + wozinfo_ptr->info_offset = 0; + wozinfo_ptr->meta_offset = 0; + + if(woz_size < 12) { + if(wozptr) { + free(wozptr); + } + wozptr = 0; + } + if(!wozptr) { + wozptr = malloc(12); + woz_append_bytes(wozptr, &g_woz_hdr_bytes[0], 12); + wozinfo_ptr->wozptr = wozptr; + wozinfo_ptr->woz_size = 12; + } + + return wozinfo_ptr; +} + +int +woz_reopen(Disk *dsk, dword64 dfcyc) +{ + byte act_tmap[160]; + Woz_info *wozinfo_ptr; + byte *wozptr, *tmap_bptr; + word32 tmap, prev_tmap, last_act; + int ret, num_tracks, num_match; + int i, j; + + wozinfo_ptr = dsk->wozinfo_ptr; + wozptr = wozinfo_ptr->wozptr; + + if(!wozinfo_ptr->tmap_offset) { + printf("No TMAP found\n"); + return 0; + } + if(!wozinfo_ptr->trks_offset) { + printf("No TRKS found\n"); + return 0; + } + if(!wozinfo_ptr->info_offset) { + printf("No INFO found\n"); + return 0; + } + if(wozinfo_ptr->woz_size == 0) { + printf("woz_size is 0!\n"); + return 0; + } + + tmap_bptr = wozptr + wozinfo_ptr->tmap_offset; + dsk->cur_fbit_pos = 0; + num_tracks = 4*35; + for(i = num_tracks; i < 160; i++) { + // See what the largest track is, go to track 39.50 + if(tmap_bptr[i] != 0xff) { + num_tracks = i + 1; + //printf("Will set num_tracks=%d\n", num_tracks); + } + } + dsk->fbit_mult = 128; // 5.25" multipler value + if(!dsk->disk_525) { + num_tracks = 160; + dsk->fbit_mult = 256; // 3.5" multipler value + } + disk_set_num_tracks(dsk, num_tracks); + for(i = 0; i < 160; i++) { + act_tmap[i] = 0xff; + } + for(i = 0; i < 160; i++) { + tmap = tmap_bptr[i]; + if(tmap >= 0xff) { + continue; // Skip + } + // WOZ format adds dup entries for adjacent qtr tracks, so + // track 2.0 has entries at 1.75 and 2.25 pointing to 2.0. + // KEGS doesn't want these, it handles this itself, so remove + // them. + if(dsk->disk_525) { + num_match = 1; + for(j = i + 1; j < 160; j++) { + if(tmap_bptr[j] != tmap) { + break; + } + num_match++; + } + // From i, num_match is the number of time tmap repeats + prev_tmap = 0xff; + last_act = 0xff; + if(i) { + prev_tmap = tmap_bptr[i - 1]; + last_act = act_tmap[i - 1]; + } else if(num_match == 3) { + // Weird case where WOZ starts with 0,0,0, we + // should treat track 0.25 as the real track + continue; + } + if(num_match >= 3) { + // long run of repeats, add a track every other + // one. + if(last_act == tmap) { // Just did trk + continue; + } + if(prev_tmap != tmap) { // Start of run + continue; + } + } else if(num_match == 2) { + // Handle A, B, [B], B, C and A, B, B, [B], B, C + // ALWAYS add this track + } else { // num_match==1 + if(prev_tmap == tmap) { + continue; + } + // Otherwise, a lone track, always add it + } + } + ret = woz_add_track(dsk, i, tmap, dfcyc); + if(ret == 0) { + printf("woz_add_track i:%04x tmap:%04x ret 0\n", i, + tmap); + return ret; + } + } + + return 1; // WOZ file is good! +} + +int +woz_open(Disk *dsk, dword64 dfcyc) +{ + Woz_info *wozinfo_ptr; + byte *wozptr; + dword64 doff; + word32 woz_size, crc, file_crc; + int fd, ret; + + // return 0 for bad WOZ file, 1 for success + // We set dsk->wozinfo_ptr, and caller will free it if we return 0 + printf("woz_open on file %s, write_prot:%d\n", dsk->name_ptr, + dsk->write_prot); + if(dsk->trks == 0) { + return 0; // Smartport? + } + if(dsk->raw_data) { + wozptr = dsk->raw_data; + woz_size = (word32)dsk->raw_dsize; + dsk->write_prot = 1; + dsk->write_through_to_unix = 0; + } else { + fd = dsk->fd; + if(fd < 0) { + return 0; + } + woz_size = (word32)cfg_get_fd_size(fd); + printf("size: %d\n", woz_size); + + wozptr = malloc(woz_size); + doff = cfg_read_from_fd(fd, wozptr, 0, woz_size); + if(doff != woz_size) { + close(fd); + return 0; + } + } + + wozinfo_ptr = woz_malloc(wozptr, woz_size); + dsk->wozinfo_ptr = wozinfo_ptr; + + if(woz_size < 16) { + return 0; + } + crc = woz_calc_crc32(wozptr, woz_size, 12); + file_crc = wozptr[8] | (wozptr[9] << 8) | (wozptr[10] << 16) | + (wozptr[11] << 24); + if((crc != file_crc) && (file_crc != 0)) { + printf("Bad Woz CRC:%08x in file, calc:%08x\n", file_crc, crc); + return 0; + } + + ret = woz_parse_header(dsk); + printf("woz_parse_header ret:%d, write_prot:%d\n", ret, + dsk->write_prot); + if(ret == 0) { + return ret; + } + + ret = woz_reopen(dsk, dfcyc); + printf("woz_reopen ret:%d\n", ret); + + woz_maybe_reparse(dsk); + return ret; +} + +byte * +woz_append_bytes(byte *wozptr, byte *in_bptr, int len) +{ + int i; + + for(i = 0; i < len; i++) { + *wozptr++ = *in_bptr++; + } + + return wozptr; +} + +byte * +woz_append_word32(byte *wozptr, word32 val) +{ + int i; + + for(i = 0; i < 4; i++) { + *wozptr++ = val & 0xff; + val = val >> 8; + } + + return wozptr; +} + +int +woz_append_chunk(Woz_info *wozinfo_ptr, word32 chunk_id, word32 length, + byte *bptr) +{ + byte *wozptr, *new_wozptr, *save_wozptr; + word32 woz_size, new_size; + int offset; + + wozptr = wozinfo_ptr->wozptr; + woz_size = wozinfo_ptr->woz_size; + + new_size = woz_size + 4 + 4 + length; + new_wozptr = realloc(wozptr, new_size); + if(new_wozptr == 0) { + return 0; + } + wozptr = new_wozptr + woz_size; + wozptr = woz_append_word32(wozptr, chunk_id); + save_wozptr = woz_append_word32(wozptr, length); + wozptr = woz_append_bytes(save_wozptr, bptr, length); + + wozinfo_ptr->wozptr = new_wozptr; + wozinfo_ptr->woz_size = new_size; + offset = (int)(save_wozptr - new_wozptr); + switch(chunk_id) { + case 0x4f464e49: // "INFO" + wozinfo_ptr->info_offset = offset; + break; + case 0x50414d54: // "TMAP" + wozinfo_ptr->tmap_offset = offset; + break; + case 0x534b5254: // "TRKS" + wozinfo_ptr->trks_offset = offset; + wozinfo_ptr->trks_size = new_size; + break; + } + if(wozptr != (new_wozptr + new_size)) { + halt_printf("wozptr:%p != %p + %08x\n", wozptr, new_wozptr, + new_size); + return 0; + } + + return 1; +} + +byte * +woz_append_a_trk(Woz_info *wozinfo_ptr, Disk *dsk, int trk_num, byte *bptr, + word32 *num_blocks_ptr, dword64 *tmap_dptr) +{ + byte *new_bptr; + word32 num_blocks, blocks_start, this_blocks, size_bytes, track_bits; + int i; + + // Align trks_buf_size to 512 bytes + blocks_start = *num_blocks_ptr; + + track_bits = dsk->trks[trk_num].track_bits; + size_bytes = (dsk->trks[trk_num].track_bits + 7) >> 3; + this_blocks = (size_bytes + 511) >> 9; + if(wozinfo_ptr->max_trk_blocks < this_blocks) { + wozinfo_ptr->max_trk_blocks = this_blocks; + printf("max_trk_blocks=%d from trk %03x\n", this_blocks, + trk_num); + } + + num_blocks = blocks_start + this_blocks; + *num_blocks_ptr = num_blocks; + + *tmap_dptr = (((dword64)track_bits) << 32) | (this_blocks << 16) | + blocks_start; + + new_bptr = realloc(bptr, num_blocks << 9); + if(new_bptr == 0) { + return 0; + } + // Zero out last 512 byte block, to ensure a partial track has all 0's + // in the last block + for(i = 0; i < 512; i++) { + new_bptr[(num_blocks << 9) - 512 + i] = 0; + } + woz_append_bytes(new_bptr + (blocks_start << 9), + dsk->trks[trk_num].raw_bptr, size_bytes); + + return new_bptr; +} + +Woz_info * +woz_new_from_woz(Disk *dsk, int disk_525) +{ + dword64 tmap_dvals[160]; + int tmap_qtrk[160]; + byte buf[160]; + Woz_info *wozinfo_ptr, *in_wozinfo_ptr; + Trk *trkptr; + byte *in_wozptr, *wozptr, *trks_bufptr; + dword64 dval; + word32 type, woz_size, num_blocks, crc, track_bits; + int c, num_valid_tmap, pos, offset, raw_bytes; + int i, j; + + wozinfo_ptr = woz_malloc(0, 0); // New wozinfo + in_wozptr = 0; + in_wozinfo_ptr = 0; + if(dsk) { + in_wozinfo_ptr = dsk->wozinfo_ptr; + if(!in_wozinfo_ptr) { + halt_printf("Changing to WOZ format!\n"); + } + } + if(in_wozinfo_ptr) { + in_wozptr = in_wozinfo_ptr->wozptr; + } + printf(" START woz_new_from_woz, in_wozinfo_ptr:%p, in_wozptr:%p\n", + in_wozinfo_ptr, in_wozptr); + for(i = 0; i < 60; i++) { + buf[i] = 0; + } + if(in_wozptr) { + // Output the INFO chunk + memcpy(&buf[0], &in_wozptr[20], 60); + } else { + // Output an INFO chunk for KEGS + buf[3] = 1; // 1=synchronized tracks + buf[4] = 1; // 1=MC3470 fake bits removed + for(i = 0; i < 32; i++) { + buf[5 + i] = ' '; // Creator field + } + memcpy(&buf[5], "KEGS", 4); + // And put the revision number after it + for(i = 0; i < 20; i++) { + c = g_kegs_version_str[i]; + if(c == 0) { + break; + } + buf[5 + 5 + i] = c; + } + } + buf[0] = 2; // WOZ2 version + type = 2 - disk_525; // 1=5.25, 2=3.5 + buf[1] = type; + buf[2] = 0; // write_prot=0 + if(buf[37] == 0) { + buf[37] = type; // sides, re-use type + } + if(buf[39] == 0) { + buf[39] = 16 + 16 * (type & 1); // 5.25=32, 3.5=16 + } + woz_append_chunk(wozinfo_ptr, 0x4f464e49, 60, &buf[0]); // INFO + + // TMAP + for(i = 0; i < 160; i++) { + buf[i] = 0xff; + } + num_blocks = 6; // 1280 + 256 = 6x512 + trks_bufptr = malloc(num_blocks << 9); + printf("trk_bufptr = %p\n", trks_bufptr); + for(i = 0; i < (int)(num_blocks << 9); i++) { + trks_bufptr[i] = 0; + } + num_valid_tmap = 0; + if((dsk != 0) && (dsk->trks != 0)) { + // Output all valid tracks, and set the TMAP for adjacent .25 + for(i = 0; i < 160; i++) { + trkptr = &(dsk->trks[i]); + if(trkptr->track_bits >= 10000) { + buf[i] = num_valid_tmap; + if(i && (buf[i-1] == 0xff)) { + buf[i-1] = num_valid_tmap; + } + if((i < 159) && (trkptr[1].track_bits < 10000)){ + buf[i+1] = num_valid_tmap; + } + trks_bufptr = woz_append_a_trk(wozinfo_ptr, dsk, + i, trks_bufptr, &num_blocks, + &tmap_dvals[num_valid_tmap]); + tmap_qtrk[num_valid_tmap] = i; + printf("Did append, tmap:%04x qtrk:%04x dval:" + "%016llx\n", num_valid_tmap, i, + tmap_dvals[num_valid_tmap]); + num_valid_tmap++; + } + } + } + woz_append_chunk(wozinfo_ptr, 0x50414d54, 160, &buf[0]); // TMAP + + // TRKS. Already all 0 if there are no tracks. Fill in tmap_dvals[] + for(i = 0; i < num_valid_tmap; i++) { + pos = 256 + i*8; + dval = tmap_dvals[i]; + for(j = 0; j < 8; j++) { + trks_bufptr[pos + j] = dval & 0xff; + dval = dval >> 8; + } + } + woz_append_chunk(wozinfo_ptr, 0x534b5254, (num_blocks << 9) - 256, + trks_bufptr + 256); + // TRKS, 1280 minimum size + free(trks_bufptr); + + // Final META chunk...just remove, we've added or removed tracks from + // an original WOZ image, so the Meta data is no longer accurate + + wozptr = wozinfo_ptr->wozptr; + woz_size = wozinfo_ptr->woz_size; + + printf(" new wozptr:%p, woz_size:%08x\n", wozptr, woz_size); + wozptr[64] = wozinfo_ptr->max_trk_blocks; // largest track + + crc = woz_calc_crc32(wozptr, woz_size, 12); + cfg_set_le32(&wozptr[8], crc); + if(dsk) { + pos = 0; + for(i = 0; i < 160; i++) { + // Go through and fix up trks structure to match new WOZ + trkptr = &(dsk->trks[i]); + if((pos < num_valid_tmap) && (tmap_qtrk[pos] == i)) { + // This is a valid track + dval = tmap_dvals[pos]; + track_bits = dval >> 32; + offset = (dval & 0xffff) << 9; + + raw_bytes = (track_bits + 7) >> 3; + if(trkptr->unix_len == 0) { + free(trkptr->raw_bptr); + } + trkptr->raw_bptr = &wozptr[offset]; + trkptr->dunix_pos = offset; + trkptr->unix_len = raw_bytes; + trkptr->dirty = 0; + trkptr->track_bits = track_bits; + if(trkptr->sync_ptr == 0) { + halt_printf("sync_ptr 0 qtrk:%04x\n", + i); + } + pos++; + } else { + // No longer a valid track, free any ptrs + if(dsk->raw_bptr_malloc) { + free(trkptr->raw_bptr); + } + free(trkptr->sync_ptr); + trkptr->raw_bptr = 0; + trkptr->sync_ptr = 0; + trkptr->dunix_pos = 0; + trkptr->unix_len = 0; + trkptr->dirty = 0; + trkptr->track_bits = 0; + } + } + dsk->raw_bptr_malloc = 0; + + if(dsk->raw_data) { + free(dsk->raw_data); + dsk->raw_data = wozptr; + dsk->raw_dsize = woz_size; + dsk->dimage_start = 0; + dsk->dimage_size = woz_size; + in_wozptr = 0; + } + free(in_wozptr); + free(in_wozinfo_ptr); + if(in_wozinfo_ptr == 0) { + dsk->write_through_to_unix = 0; + halt_printf("Force write_through_to_unix since image " + "changed to WOZ format\n"); + } + } + + printf(" END woz_new_from_woz, wozinfo_ptr:%p wozptr:%p\n", + wozinfo_ptr, wozinfo_ptr->wozptr); + return wozinfo_ptr; +} + +int +woz_new(int fd, const char *str, int size_kb) +{ + Woz_info *wozinfo_ptr; + byte *wozptr; + word32 size, woz_size; + int disk_525; + + disk_525 = 0; + if(size_kb <= 140) { + disk_525 = 1; + } + wozinfo_ptr = woz_new_from_woz(0, disk_525); + + wozptr = wozinfo_ptr->wozptr; + woz_size = wozinfo_ptr->woz_size; + + + size = (word32)must_write(fd, wozptr, woz_size); + free(wozptr); + free(wozinfo_ptr); + if(size != woz_size) { + return -1; + } + if(str) { + // Avoid unused var warning + } + + return 0; +} + +void +woz_maybe_reparse(Disk *dsk) +{ + Woz_info *wozinfo_ptr; + + wozinfo_ptr = dsk->wozinfo_ptr; + if(wozinfo_ptr) { + if(wozinfo_ptr->reparse_needed) { + woz_reparse_woz(dsk); + } + } +} + +void +woz_set_reparse(Disk *dsk) +{ + Woz_info *wozinfo_ptr; + + wozinfo_ptr = dsk->wozinfo_ptr; + if(wozinfo_ptr) { + wozinfo_ptr->reparse_needed = 1; + } else { + woz_reparse_woz(dsk); + } +} + +void +woz_reparse_woz(Disk *dsk) +{ + Woz_info *wozinfo_ptr; + +#if 0 + printf("In woz_reparse_woz, showing track 0\n"); + + iwm_show_a_track(dsk, &(dsk->trks[0]), 0.0); + iwm_show_stats(3); +#endif + + wozinfo_ptr = woz_new_from_woz(dsk, dsk->disk_525); + // This wozinfo_ptr has reparse_needed==0 + + dsk->wozinfo_ptr = wozinfo_ptr; + if(!dsk->raw_data && dsk->write_through_to_unix) { + (void)!ftruncate(dsk->fd, wozinfo_ptr->woz_size); + (void)cfg_write_to_fd(dsk->fd, wozinfo_ptr->wozptr, 0, + wozinfo_ptr->woz_size); + printf("did ftruncate and write of WOZ to %s\n", dsk->name_ptr); + } + + // Need to recalculate dsk->cur_track_bits, cur_trk_ptr + dsk->cur_trk_ptr = 0; + iwm_move_to_ftrack(dsk, dsk->cur_frac_track, 0, 0); + +#if 0 + printf("End of woz_reparse_woz, showing track 0\n"); + iwm_show_a_track(dsk, &(dsk->trks[0]), 0.0); + iwm_show_stats(3); +#endif + + woz_check_file(dsk); + printf("woz_reparse_woz complete!\n"); +} + +void +woz_remove_a_track(Disk *dsk, word32 qtr_track) +{ + Trk *trkptr; + + printf("woz_remove_track: %s qtr_track:%03x\n", dsk->name_ptr, + qtr_track); + trkptr = &(dsk->trks[qtr_track]); + trkptr->track_bits = 0; // Track invalid + + woz_set_reparse(dsk); +} + +word32 +woz_add_a_track(Disk *dsk, word32 qtr_track) +{ + Trk *trkptr, *other_trkptr; + byte *bptr, *other_bptr, *sync_ptr, *other_sync_ptr; + word32 track_bits, val; + int raw_bytes; + int i; + + // Return track_bits for the new track + + trkptr = &(dsk->trks[qtr_track]); + + other_trkptr = 0; + if((qtr_track > 0) && (trkptr[-1].track_bits > 0)) { + // Copy this track + other_trkptr = trkptr - 1; + } else if((qtr_track < 159) && (trkptr[1].track_bits > 0)) { + other_trkptr = trkptr + 1; + } + other_trkptr = 0; // HACK + if(dsk->disk_525 && other_trkptr) { + // We're .25 tracks away from a valid track, copy it's data + track_bits = other_trkptr->track_bits; + raw_bytes = (track_bits + 7) >> 3; + trkptr->track_bits = track_bits; + trkptr->raw_bptr = malloc(raw_bytes + 8); + trkptr->sync_ptr = malloc(raw_bytes + 8); + printf(" add a track, copy bptr:%p sync_ptr:%p size:%08x\n", + trkptr->raw_bptr, trkptr->sync_ptr, raw_bytes + 8); + bptr = trkptr->raw_bptr; + sync_ptr = trkptr->sync_ptr; + other_bptr = other_trkptr->raw_bptr; + other_sync_ptr = other_trkptr->sync_ptr; + for(i = 0; i < raw_bytes; i++) { + bptr[i] = other_bptr[i]; + sync_ptr[i] = other_sync_ptr[i]; + } + } else { + track_bits = iwm_get_default_track_bits(dsk, qtr_track); + raw_bytes = (track_bits + 7) >> 3; + trkptr->track_bits = track_bits; + trkptr->raw_bptr = malloc(raw_bytes + 8); + trkptr->sync_ptr = malloc(raw_bytes + 8); + printf(" add a track, raw_bptr:%p sync_ptr:%p size:%08x\n", + trkptr->raw_bptr, trkptr->sync_ptr, raw_bytes + 8); + + bptr = trkptr->raw_bptr; + sync_ptr = trkptr->sync_ptr; + for(i = 0; i < raw_bytes; i++) { + val = ((i >> 6) ^ i) & 0x7f; + if(((val & 0xf0) == 0) || ((val & 0x0f) == 0)) { + val |= 0x21; + } + bptr[i] = val; + sync_ptr[i] = 0xff; + } + bptr[raw_bytes - 1] = 0; + + iwm_recalc_sync_from(dsk, qtr_track, 0, 0); + } + trkptr->dunix_pos = 0; + trkptr->unix_len = 0; // Mark as a newly created trk + trkptr->dirty = 0; + + printf("woz_add_new_track: %s qtr_track:%03x\n", dsk->name_ptr, + qtr_track); + woz_set_reparse(dsk); + + return track_bits; +} + diff --git a/gsplus/src/xdriver.c b/gsplus/src/xdriver.c new file mode 100644 index 0000000..d91c9e4 --- /dev/null +++ b/gsplus/src/xdriver.c @@ -0,0 +1,1511 @@ +/**********************************************************************/ +/* GSplus - Apple //gs Emulator */ +/* Based on KEGS by Kent Dickey */ +/* Copyright 2002-2025 Kent Dickey */ +/* Copyright 2025-2026 GSplus Contributors */ +/* */ +/* This code is covered by the GNU GPL v3 */ +/* See the file COPYING.txt or https://www.gnu.org/licenses/ */ +/**********************************************************************/ + +# if !defined(__CYGWIN__) && !defined(__POWERPC__) +/* No shared memory on Cygwin */ +# define X_SHARED_MEM +#endif /* CYGWIN */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef X_SHARED_MEM +# include +# include +# include +#endif + +int XShmQueryExtension(Display *display); +void _XInitImageFuncPtrs(XImage *xim); + +#include "defc.h" + +extern int g_video_scale_algorithm; +extern int g_audio_enable; + +typedef struct windowinfo { + XShmSegmentInfo *seginfo; + XImage *xim; + Kimage *kimage_ptr; // KEGS Image pointer for window content + char *name_str; + Window x_win; + GC x_winGC; + Atom delete_atom; + int x_use_shmem; + int active; + int width_req; + int pixels_per_line; + int main_height; + int full_min_width; + int full_min_height; +} Window_info; + +#include "protos_xdriver.h" + +int g_x_warp_x = 0; +int g_x_warp_y = 0; +int g_x_warp_pointer = 0; +int g_x_hide_pointer = 0; + +extern int Verbose; +int g_x_screen_depth = 24; +int g_x_screen_mdepth = 32; +int g_x_max_width = 0; +int g_x_max_height = 0; +int g_screen_num = 0; + +extern int _Xdebug; + +int g_auto_repeat_on = -1; + +Display *g_display = 0; +Visual *g_vis = 0; +Colormap g_default_colormap = 0; + +Window_info g_mainwin_info = { 0 }; +Window_info g_debugwin_info = { 0 }; + +Cursor g_cursor; +Pixmap g_cursor_shape; +Pixmap g_cursor_mask; + +XColor g_xcolor_black = { 0, 0x0000, 0x0000, 0x0000, DoRed|DoGreen|DoBlue, 0 }; +XColor g_xcolor_white = { 0, 0xffff, 0xffff, 0xffff, DoRed|DoGreen|DoBlue, 0 }; + +const char *g_x_selection_strings[3] = { + // If we get SelectionRequests with target=Atom("TARGETS"), then + // send XA_STRING plus this list to say what we can provide for copy + "UTF8_STRING", "text/plain", "text/plain;charset=utf-8" +}; + +int g_x_num_targets = 0; +Atom g_x_targets_array[5] = { 0 }; + +Atom g_x_atom_targets = None; // Will be set to "TARGETS" + +int g_depth_attempt_list[] = { 24, 16, 15 }; + +#define X_EVENT_LIST_ALL_WIN \ + (ExposureMask | ButtonPressMask | ButtonReleaseMask | \ + OwnerGrabButtonMask | KeyPressMask | KeyReleaseMask | \ + KeymapStateMask | FocusChangeMask) + +#define X_BASE_WIN_EVENT_LIST \ + (X_EVENT_LIST_ALL_WIN | PointerMotionMask | ButtonMotionMask | \ + StructureNotifyMask) + +#define X_A2_WIN_EVENT_LIST \ + (X_BASE_WIN_EVENT_LIST) + +int g_num_a2_keycodes = 0; + +int g_x_a2_key_to_xsym[][3] = { + { 0x35, XK_Escape, 0 }, + { 0x7a, XK_F1, 0 }, + { 0x78, XK_F2, 0 }, + { 0x63, XK_F3, 0 }, + { 0x76, XK_F4, 0 }, + { 0x60, XK_F5, 0 }, + { 0x61, XK_F6, 0 }, + { 0x62, XK_F7, 0 }, + { 0x64, XK_F8, 0 }, + { 0x65, XK_F9, 0 }, + { 0x6d, XK_F10, 0 }, + { 0x67, XK_F11, 0 }, + { 0x6f, XK_F12, 0 }, + { 0x69, XK_F13, 0 }, + { 0x6b, XK_F14, 0 }, + { 0x71, XK_F15, 0 }, + { 0x7f, XK_Pause, XK_Break }, + { 0x32, '`', '~' }, /* Key number 18? */ + { 0x12, '1', '!' }, + { 0x13, '2', '@' }, + { 0x14, '3', '#' }, + { 0x15, '4', '$' }, + { 0x17, '5', '%' }, + { 0x16, '6', '^' }, + { 0x1a, '7', '&' }, + { 0x1c, '8', '*' }, + { 0x19, '9', '(' }, + { 0x1d, '0', ')' }, + { 0x1b, '-', '_' }, + { 0x18, '=', '+' }, + { 0x33, XK_BackSpace, 0 }, + { 0x72, XK_Insert, XK_Help }, /* Help? */ +/* { 0x73, XK_Home, 0 }, alias XK_Home to be XK_KP_Equal! */ + { 0x74, XK_Page_Up, 0 }, + { 0x47, XK_Num_Lock, XK_Clear }, /* Clear */ + { 0x51, XK_KP_Equal, XK_Home }, /* Note XK_Home alias! */ + { 0x4b, XK_KP_Divide, 0 }, + { 0x43, XK_KP_Multiply, 0 }, + + { 0x30, XK_Tab, 0 }, + { 0x0c, 'q', 'Q' }, + { 0x0d, 'w', 'W' }, + { 0x0e, 'e', 'E' }, + { 0x0f, 'r', 'R' }, + { 0x11, 't', 'T' }, + { 0x10, 'y', 'Y' }, + { 0x20, 'u', 'U' }, + { 0x22, 'i', 'I' }, + { 0x1f, 'o', 'O' }, + { 0x23, 'p', 'P' }, + { 0x21, '[', '{' }, + { 0x1e, ']', '}' }, + { 0x2a, 0x5c, '|' }, /* backslash, bar */ + { 0x75, XK_Delete, 0 }, + { 0x77, XK_End, 0 }, + { 0x79, XK_Page_Down, 0 }, + { 0x59, XK_KP_7, XK_KP_Home }, + { 0x5b, XK_KP_8, XK_KP_Up }, + { 0x5c, XK_KP_9, XK_KP_Page_Up }, + { 0x4e, XK_KP_Subtract, 0 }, + + { 0x39, XK_Caps_Lock, 0 }, + { 0x00, 'a', 'A' }, + { 0x01, 's', 'S' }, + { 0x02, 'd', 'D' }, + { 0x03, 'f', 'F' }, + { 0x05, 'g', 'G' }, + { 0x04, 'h', 'H' }, + { 0x26, 'j', 'J' }, + { 0x28, 'k', 'K' }, + { 0x25, 'l', 'L' }, + { 0x29, ';', ':' }, + { 0x27, 0x27, '"' }, /* single quote */ + { 0x24, XK_Return, 0 }, + { 0x56, XK_KP_4, XK_KP_Left }, + { 0x57, XK_KP_5, XK_KP_Begin }, + { 0x58, XK_KP_6, XK_KP_Right }, + { 0x45, XK_KP_Add, 0 }, + + { 0x38, XK_Shift_L, XK_Shift_R }, + { 0x06, 'z', 'Z' }, + { 0x07, 'x', 'X' }, + { 0x08, 'c', 'C' }, + { 0x09, 'v', 'V' }, + { 0x0b, 'b', 'B' }, + { 0x2d, 'n', 'N' }, + { 0x2e, 'm', 'M' }, + { 0x2b, ',', '<' }, + { 0x2f, '.', '>' }, + { 0x2c, '/', '?' }, + { 0x3e, XK_Up, 0 }, + { 0x53, XK_KP_1, XK_KP_End }, + { 0x54, XK_KP_2, XK_KP_Down }, + { 0x55, XK_KP_3, XK_KP_Page_Down }, + + { 0x36, XK_Control_L, XK_Control_R }, + { 0x3a, XK_Print, XK_Sys_Req }, /* Option */ + { 0x37, XK_Scroll_Lock, 0 }, /* Command */ + { 0x31, ' ', 0 }, + { 0x3b, XK_Left, 0 }, + { 0x3d, XK_Down, 0 }, + { 0x3c, XK_Right, 0 }, + { 0x52, XK_KP_0, XK_KP_Insert }, + { 0x41, XK_KP_Decimal, XK_KP_Separator }, + { 0x4c, XK_KP_Enter, 0 }, + { -1, -1, -1 } +}; + +int +main(int argc, char **argv) +{ + int ret, mdepth; + + ret = parse_argv(argc, argv, 1); + if(ret) { + printf("kegsmain ret: %d, stopping\n", ret); + exit(1); + } + + mdepth = x_video_get_mdepth(); + + ret = kegs_init(mdepth, g_x_max_width, g_x_max_height, 0); + printf("kegs_init done\n"); + if(ret) { + printf("kegs_init ret: %d, stopping\n", ret); + exit(1); + } + x_video_init(); + + // This is the main loop of KEGS, when this exits, KEGS exits + // run_16ms() does one video frame worth of instructions and video + // updates: 17030 1MHz clock cycles. + while(1) { + ret = run_16ms(); + if(ret != 0) { + printf("run_16ms returned: %d\n", ret); + break; + } + x_input_events(); + x_update_display(&g_mainwin_info); + x_update_display(&g_debugwin_info); + } + xdriver_end(); + exit(0); +} + +int +my_error_handler(Display *display, XErrorEvent *ev) +{ + char msg[1024]; + XGetErrorText(display, ev->error_code, msg, 1000); + printf("X Error code %s\n", msg); + fflush(stdout); + + return 0; +} + +void +xdriver_end() +{ + printf("xdriver_end\n"); + if(g_display) { + x_auto_repeat_on(1); + XFlush(g_display); + } +} + +void +x_try_xset_r() +{ + /* attempt "xset r" */ + (void)!system("xset r"); + xdriver_end(); + exit(5); +} + +void +x_badpipe(int signum) +{ + /* restore normal sigpipe handling */ + signal(SIGPIPE, SIG_DFL); + + x_try_xset_r(); +} + +int +kegs_x_io_error_handler(Display *display) +{ + printf("kegs_x_io_error_handler called (likely window closed)\n"); + g_display = 0; + x_try_xset_r(); + return 0; +} + +int +x_video_get_mdepth() +{ + XWindowAttributes get_attr; + int depth, len, ret, force_depth; + int i; + + printf("Preparing X Windows graphics system\n"); + ret = 0; + + signal(SIGPIPE, x_badpipe); + signal(SIGPIPE, x_badpipe); + +#if 0 + printf("Setting _Xdebug = 1, makes X synchronous\n"); + _Xdebug = 1; +#endif + + g_display = XOpenDisplay(NULL); + if(g_display == NULL) { + fprintf(stderr, "Can't open display\n"); + exit(1); + } + + vid_printf("Just opened display = %p\n", g_display); + fflush(stdout); + + g_screen_num = DefaultScreen(g_display); + + get_attr.width = 0; + get_attr.height = 0; + ret = XGetWindowAttributes(g_display, DefaultRootWindow(g_display), + &get_attr); + printf("XGetWindowAttributes ret: %d\n", ret); + g_x_max_width = get_attr.width; + g_x_max_height = get_attr.height; + printf("get_attr.width:%d, height:%d\n", g_x_max_width, g_x_max_height); + + len = sizeof(g_depth_attempt_list)/sizeof(int); + force_depth = sim_get_force_depth(); + if(force_depth > 0) { + /* Only use the requested user depth */ + len = 1; + g_depth_attempt_list[0] = force_depth; + } + + for(i = 0; i < len; i++) { + depth = g_depth_attempt_list[i]; + + g_x_screen_mdepth = x_try_find_visual(depth, g_screen_num); + if(g_x_screen_mdepth != 0) { + break; + } + } + if(g_x_screen_mdepth == 0) { + fprintf(stderr, "Couldn't find any visuals at any depth!\n"); + exit(2); + } + + return g_x_screen_mdepth; +} + +int +x_try_find_visual(int depth, int screen_num) +{ + XVisualInfo *visualList; + XVisualInfo *v_chosen; + XVisualInfo vTemplate; + int visualsMatched, visual_chosen, mdepth; + int i; + + vTemplate.screen = screen_num; + vTemplate.depth = depth; + + visualList = XGetVisualInfo(g_display, + (VisualScreenMask | VisualDepthMask), + &vTemplate, &visualsMatched); + + vid_printf("visuals matched: %d\n", visualsMatched); + if(visualsMatched == 0) { + return 0; + } + + visual_chosen = -1; + for(i = 0; i < visualsMatched; i++) { + printf("Visual %d\n", i); + printf(" id: %08x, screen: %d, depth: %d, class: %d\n", + (word32)visualList[i].visualid, + visualList[i].screen, + visualList[i].depth, + visualList[i].class); + printf(" red: %08lx, green: %08lx, blue: %08lx\n", + visualList[i].red_mask, + visualList[i].green_mask, + visualList[i].blue_mask); + printf(" cmap size: %d, bits_per_rgb: %d\n", + visualList[i].colormap_size, + visualList[i].bits_per_rgb); + if((depth != 8) && (visualList[i].class == TrueColor)) { + visual_chosen = i; + break; + } + } + + if(visual_chosen < 0) { + printf("Couldn't find any good visuals at depth %d!\n", + depth); + return 0; + } + + printf("Chose visual: %d\n", visual_chosen); + + v_chosen = &(visualList[visual_chosen]); + video_set_red_mask((word32)v_chosen->red_mask); + video_set_green_mask((word32)v_chosen->green_mask); + video_set_blue_mask((word32)v_chosen->blue_mask); + + video_set_palette(); // Uses above masks to initialize palettes + + g_x_screen_depth = depth; + mdepth = depth; + if(depth > 8) { + mdepth = 16; + } + if(depth > 16) { + mdepth = 32; + } + + // XFree(visualList); -- Cannot free, still using g_vis... + + g_vis = v_chosen->visual; + + return mdepth; +} + +void +x_video_init() +{ + int tmp_array[0x80]; + Atom target; + char cursor_data; + int keycode, num_targets, max_targets, num; + int i; + + printf("Opening X Window now\n"); + + g_num_a2_keycodes = 0; + for(i = 0; i <= 0x7f; i++) { + tmp_array[i] = 0; + } + for(i = 0; i < 0x7f; i++) { + keycode = g_x_a2_key_to_xsym[i][0]; + if(keycode < 0) { + g_num_a2_keycodes = i; + break; + } else if(keycode > 0x7f) { + printf("a2_key_to_xsym[%d] = %02x!\n", i, keycode); + exit(2); + } else { + if(tmp_array[keycode]) { + printf("a2_key_to_x[%d] = %02x used by %d\n", + i, keycode, tmp_array[keycode] - 1); + } + tmp_array[keycode] = i + 1; + } + } + + + g_default_colormap = XDefaultColormap(g_display, g_screen_num); + if(!g_default_colormap) { + printf("g_default_colormap == 0!\n"); + exit(4); + } + + /* and define cursor */ + cursor_data = 0; + g_cursor_shape = XCreatePixmapFromBitmapData(g_display, + RootWindow(g_display,g_screen_num), &cursor_data, 1, 1, 1, 0,1); + g_cursor_mask = XCreatePixmapFromBitmapData(g_display, + RootWindow(g_display,g_screen_num), &cursor_data, 1, 1, 1, 0,1); + + g_cursor = XCreatePixmapCursor(g_display, g_cursor_shape, + g_cursor_mask, &g_xcolor_black, &g_xcolor_white, 0, 0); + + XFreePixmap(g_display, g_cursor_shape); + XFreePixmap(g_display, g_cursor_mask); + + XFlush(g_display); + g_x_atom_targets = XInternAtom(g_display, "TARGETS", 0); + num = sizeof(g_x_selection_strings)/sizeof(g_x_selection_strings[0]); + g_x_targets_array[0] = XA_STRING; + num_targets = 1; + for(i = 0; i < num; i++) { + target = XInternAtom(g_display, g_x_selection_strings[i], 0); + if(target != None) { + g_x_targets_array[num_targets++] = target; + } + } + g_x_num_targets = num_targets; + max_targets = sizeof(g_x_targets_array)/sizeof(g_x_targets_array[0]); + if(num_targets > max_targets) { + printf("Overflowed g_x_targets_array: %d out of %d\n", + num_targets, max_targets); + exit(-1); + } + + x_init_window(&g_mainwin_info, video_get_kimage(0), "KEGS"); + x_init_window(&g_debugwin_info, video_get_kimage(1), "KEGS Debugger"); + + x_create_window(&g_mainwin_info); + + vid_printf("Set error handler to my_x_handler\n"); + XSetIOErrorHandler(kegs_x_io_error_handler); +} + +void +x_init_window(Window_info *win_info_ptr, Kimage *kimage_ptr, char *name_str) +{ + int width, height, x_use_shmem, ret; + + height = video_get_x_height(kimage_ptr); + width = video_get_x_width(kimage_ptr); + + win_info_ptr->seginfo = 0; + win_info_ptr->xim = 0; + win_info_ptr->kimage_ptr = kimage_ptr; + win_info_ptr->name_str = name_str; + win_info_ptr->x_win = 0; + win_info_ptr->x_winGC = 0; + win_info_ptr->delete_atom = 0; + win_info_ptr->active = 0; + win_info_ptr->x_use_shmem = 0; + win_info_ptr->width_req = width; + win_info_ptr->pixels_per_line = width; + win_info_ptr->main_height = height; + win_info_ptr->full_min_width = width; + win_info_ptr->full_min_height = height; + vid_printf("init win %p (main:%p) width:%d, height:%d\n", win_info_ptr, + &g_mainwin_info, width, height); + + x_use_shmem = 0; + // Allow cmd-line args to force shmem off +/* Check for XShm */ +#ifdef X_SHARED_MEM + if(sim_get_use_shmem()) { + ret = XShmQueryExtension(g_display); + if(ret == 0) { + printf("XShmQueryExt ret: %d, not using shared mem\n", + ret); + } else { + vid_printf("Will use shared memory for X\n"); + x_use_shmem = 1; + } + } +#endif + win_info_ptr->x_use_shmem = x_use_shmem; + + video_update_scale(kimage_ptr, win_info_ptr->width_req, + win_info_ptr->main_height, 1); +} + +void +x_create_window(Window_info *win_info_ptr) +{ + XGCValues new_gc; + XSetWindowAttributes win_attr; + Window x_win; + GC x_winGC; + word32 create_win_list; + int width, height, x_xpos, x_ypos; + + win_attr.event_mask = X_A2_WIN_EVENT_LIST; + win_attr.colormap = g_default_colormap; + win_attr.backing_store = WhenMapped; + win_attr.border_pixel = 1; + win_attr.background_pixel = 0; + if(g_x_warp_pointer) { + win_attr.cursor = g_cursor; + } else { + win_attr.cursor = None; + } + + vid_printf("About to win, depth: %d\n", g_x_screen_depth); + fflush(stdout); + + create_win_list = CWEventMask | CWBackingStore | CWCursor; + create_win_list |= CWColormap | CWBorderPixel | CWBackPixel; + + x_xpos = video_get_x_xpos(win_info_ptr->kimage_ptr); + x_ypos = video_get_x_ypos(win_info_ptr->kimage_ptr); + width = win_info_ptr->width_req; + height = win_info_ptr->main_height; + + x_win = XCreateWindow(g_display, RootWindow(g_display, g_screen_num), + x_xpos, x_ypos, width, height, 0, g_x_screen_depth, + InputOutput, g_vis, create_win_list, &win_attr); + vid_printf("x_win = %d, width:%d, height:%d\n", (int)x_win, width, + height); + + XFlush(g_display); + + win_info_ptr->x_win = x_win; + win_info_ptr->active = 0; + video_set_active(win_info_ptr->kimage_ptr, 1); + + x_allocate_window_data(win_info_ptr); + + if(!win_info_ptr->x_use_shmem) { + printf("Not using shared memory, setting skip_amt = 2, " + "g_audio_enable=0\n"); + video_set_redraw_skip_amt(2); + g_audio_enable = 0; + } + + x_set_size_hints(win_info_ptr); + + XMapRaised(g_display, x_win); + + if(win_info_ptr != &g_mainwin_info) { + // Debugger window + win_info_ptr->delete_atom = XInternAtom(g_display, + "WM_DELETE_WINDOW", False); + XSetWMProtocols(g_display, x_win, &(win_info_ptr->delete_atom), + 1); + } + + XSync(g_display, False); + + x_winGC = XCreateGC(g_display, x_win, 0, (XGCValues *) 0); + win_info_ptr->x_winGC = x_winGC; + win_info_ptr->active = 1; + + new_gc.fill_style = FillSolid; + XChangeGC(g_display, x_winGC, GCFillStyle, &new_gc); + + /* XSync(g_display, False); */ + + XFlush(g_display); + fflush(stdout); +} + +int g_xshm_error = 0; + +int +xhandle_shm_error(Display *display, XErrorEvent *event) +{ + g_xshm_error = 1; + return 0; +} + +void +x_allocate_window_data(Window_info *win_info_ptr) +{ + int width, height; + + width = g_x_max_width; + height = g_x_max_height; + if(win_info_ptr->x_use_shmem) { + win_info_ptr->x_use_shmem = 0; // Default to no shmem + get_shm(win_info_ptr, width, height); + } + if(!win_info_ptr->x_use_shmem) { + get_ximage(win_info_ptr, width, height); + } +} + +void +get_shm(Window_info *win_info_ptr, int width, int height) +{ +#ifdef X_SHARED_MEM + XShmSegmentInfo *seginfo; + XImage *xim; + int (*old_x_handler)(Display *, XErrorEvent *); + int depth, size; + + depth = g_x_screen_depth; // 24, actual bits per pixel + + seginfo = (XShmSegmentInfo *)malloc(sizeof(XShmSegmentInfo)); + xim = XShmCreateImage(g_display, g_vis, depth, ZPixmap, + (char *)0, seginfo, width, height); + + /* check mdepth, which should be 32 */ + if(xim->bits_per_pixel != g_x_screen_mdepth) { + printf("get_shm bits_per_pix: %d != %d\n", + xim->bits_per_pixel, g_x_screen_mdepth); + } + vid_printf("xim: %p, DO_VERBOSE:%d, Verbose:%d, VERBOSE_VIDEO:%d\n", + xim, DO_VERBOSE, Verbose, VERBOSE_VIDEO); + + vid_printf("xim: %p\n", xim); + win_info_ptr->seginfo = seginfo; + if(xim == 0) { + return; + } + vid_printf("bytes_per_line:%d, height:%d\n", xim->bytes_per_line, + xim->height); + size = xim->bytes_per_line * xim->height; + vid_printf("size: %d\n", size); + + /* It worked, we got it */ + seginfo->shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777); + vid_printf("seginfo->shmid = %d, errno:%d, %s\n", seginfo->shmid, + errno, strerror(errno)); + if(seginfo->shmid < 0) { + XDestroyImage(xim); + return; + } + + /* Still working */ + seginfo->shmaddr = (char *)shmat(seginfo->shmid, 0, 0); + vid_printf("seginfo->shmaddr: %p\n", seginfo->shmaddr); + if(seginfo->shmaddr == ((char *) -1)) { + XDestroyImage(xim); + return; + } + + /* Still working */ + xim->data = seginfo->shmaddr; + seginfo->readOnly = False; + vid_printf("xim->data is %p, size:%08x\n", xim->data, size); + + /* XShmAttach will trigger X error if server is remote, so catch it */ + g_xshm_error = 0; + old_x_handler = XSetErrorHandler(xhandle_shm_error); + + XShmAttach(g_display, seginfo); + XSync(g_display, False); + + + vid_printf("about to RMID the shmid\n"); + shmctl(seginfo->shmid, IPC_RMID, 0); + + XFlush(g_display); + XSetErrorHandler(old_x_handler); + + if(g_xshm_error) { + XDestroyImage(xim); + /* We could release the shared mem segment, but by doing the */ + /* RMID, it will go away when we die now, so just leave it */ + printf("Not using shared memory\n"); + return; + } + + width = xim->bytes_per_line; + win_info_ptr->pixels_per_line = width / 4; + win_info_ptr->xim = xim; + win_info_ptr->x_use_shmem = 1; + + vid_printf("Sharing memory. xim: %p, xim->data: %p, width:%d\n", xim, + xim->data, win_info_ptr->pixels_per_line); +#endif /* X_SHARED_MEM */ +} + +void +get_ximage(Window_info *win_info_ptr, int width, int height) +{ + XImage *xim; + byte *ptr; + int depth, mdepth, size; + + depth = g_x_screen_depth; + mdepth = g_x_screen_mdepth; + + size = (width * height * mdepth) >> 3; + printf("Get_ximage, w:%d, h:%d, mdepth:%d, size:%08x\n", width, + height, mdepth, size); + ptr = (byte *)malloc(size); + + vid_printf("ptr: %p\n", ptr); + + if(ptr == 0) { + printf("malloc for data failed, mdepth: %d\n", mdepth); + exit(2); + } + + win_info_ptr->pixels_per_line = width; + + xim = XCreateImage(g_display, g_vis, depth, ZPixmap, 0, + (char *)ptr, width, height, 8, 0); + +#ifdef KEGS_BIG_ENDIAN + xim->byte_order = MSBFirst; +#else + xim->byte_order = LSBFirst; +#endif + _XInitImageFuncPtrs(xim); /* adjust to new byte order */ + + /* check mdepth! */ + if(xim->bits_per_pixel != mdepth) { + printf("shm_ximage bits_per_pix: %d != %d\n", + xim->bits_per_pixel, mdepth); + } + + vid_printf("xim: %p\n", xim); + + win_info_ptr->xim = xim; + win_info_ptr->x_use_shmem = 0; + + return; +} + +void +x_set_size_hints(Window_info *win_info_ptr) +{ + XSizeHints my_winSizeHints; + XClassHint my_winClassHint; + XTextProperty my_winText; + Kimage *kimage_ptr; + int width, height, a2_width, a2_height; + + width = win_info_ptr->width_req; + height = win_info_ptr->main_height; + kimage_ptr = win_info_ptr->kimage_ptr; + video_update_scale(kimage_ptr, width, height, 0); + + a2_width = video_get_a2_width(kimage_ptr); + a2_height = video_get_a2_height(kimage_ptr); + + XStringListToTextProperty(&(win_info_ptr->name_str), 1, &my_winText); + + my_winSizeHints.flags = PSize | PMinSize | PMaxSize | PAspect; + my_winSizeHints.width = width; + my_winSizeHints.height = height; + my_winSizeHints.min_width = a2_width; + my_winSizeHints.min_height = a2_height; + my_winSizeHints.max_width = g_x_max_width; + my_winSizeHints.max_height = g_x_max_height; + my_winSizeHints.min_aspect.x = a2_width - 1; + my_winSizeHints.min_aspect.y = a2_height; + my_winSizeHints.max_aspect.x = a2_width + 1; + my_winSizeHints.max_aspect.y = a2_height; + my_winClassHint.res_name = win_info_ptr->name_str; + my_winClassHint.res_class = win_info_ptr->name_str; + + XSetWMProperties(g_display, win_info_ptr->x_win, &my_winText, + &my_winText, 0, 0, &my_winSizeHints, 0, &my_winClassHint); + // printf("Did XSetWMProperties w:%d h:%d\n", width, height); +} + +void +x_resize_window(Window_info *win_info_ptr) +{ + Kimage *kimage_ptr; + int x_width, x_height, ret; + + kimage_ptr = win_info_ptr->kimage_ptr; + x_width = video_get_x_width(kimage_ptr); + x_height = video_get_x_height(kimage_ptr); + + win_info_ptr->main_height = MY_MIN(x_height, g_x_max_height); + win_info_ptr->width_req = MY_MIN(x_width, g_x_max_width); + + ret = XResizeWindow(g_display, win_info_ptr->x_win, x_width, x_height); + if(0) { + printf("XResizeWindow ret:%d, w:%d, h:%d\n", ret, x_width, + x_height); + } +} + +void +x_update_display(Window_info *win_info_ptr) +{ + Change_rect rect; + int did_copy, valid, x_active, a2_active; + int i; + + // Update active state + a2_active = video_get_active(win_info_ptr->kimage_ptr); + x_active = win_info_ptr->active; + if(x_active && !a2_active) { + // We need to unmap this window + XUnmapWindow(g_display, win_info_ptr->x_win); + x_active = 0; + win_info_ptr->active = x_active; + } + if(!x_active && a2_active) { + // We need to map this window (and maybe create it) + if(win_info_ptr->xim == 0) { + x_create_window(win_info_ptr); + } + XMapWindow(g_display, win_info_ptr->x_win); + x_active = 1; + win_info_ptr->active = x_active; + } + if(x_active == 0) { + return; + } + + if(video_change_aspect_needed(win_info_ptr->kimage_ptr, + win_info_ptr->width_req, win_info_ptr->main_height)) { + x_resize_window(win_info_ptr); + } + did_copy = 0; + for(i = 0; i < MAX_CHANGE_RECTS; i++) { + valid = video_out_data(win_info_ptr->xim->data, + win_info_ptr->kimage_ptr, + win_info_ptr->pixels_per_line, &rect, i); + if(!valid) { + break; + } +#if 0 + if(win_info_ptr == &g_debugwin_info) { + printf(" i:%d valid:%d, w:%d h:%d\n", i, valid, + rect.width, rect.height); + } +#endif + did_copy = 1; +#ifdef X_SHARED_MEM + if(win_info_ptr->x_use_shmem) { + XShmPutImage(g_display, win_info_ptr->x_win, + win_info_ptr->x_winGC, + win_info_ptr->xim, rect.x, rect.y, + rect.x, rect.y, + rect.width, rect.height, False); + } +#endif + if(!win_info_ptr->x_use_shmem) { + XPutImage(g_display, win_info_ptr->x_win, + win_info_ptr->x_winGC, + win_info_ptr->xim, rect.x, rect.y, + rect.x, rect.y, + rect.width, rect.height); + } + } + + if(did_copy) { + XFlush(g_display); + } +} + +Window_info * +x_find_xwin(Window in_win) +{ + if(g_mainwin_info.kimage_ptr) { + if(g_mainwin_info.x_win == in_win) { + return &g_mainwin_info; + } + } + if(g_debugwin_info.kimage_ptr) { + if(g_debugwin_info.x_win == in_win) { + return &g_debugwin_info; + } + } + printf("in_win:%d not found\n", (int)in_win); + exit(1); +} + +#define KEYBUFLEN 128 + +int g_num_check_input_calls = 0; +int g_check_input_flush_rate = 2; + +// https://stackoverflow.com/questions/72236711/trouble-with-xsetselectionowner +// See answer from "n.m" on May 17th. +void +x_send_copy_data(Window_info *win_info_ptr) +{ + printf("x_send_copy_data!\n"); + XSetSelectionOwner(g_display, XA_PRIMARY, win_info_ptr->x_win, + CurrentTime); + (void)cfg_text_screen_str(); +} + +void +x_handle_copy(XSelectionRequestEvent *req_ev_ptr) +{ + byte *bptr; + int ret; + + bptr = (byte *)cfg_get_current_copy_selection(); + ret = XChangeProperty(g_display, req_ev_ptr->requestor, + req_ev_ptr->property, req_ev_ptr->target, 8, + PropModeReplace, bptr, strlen((char *)bptr)); + // req_ev_ptr->target is either XA_STRING, or equivalent + if(0) { + // Seems to return 1, BadRequest always, but it works + printf("XChangeProperty ret: %d\n", ret); + } +} + +void +x_handle_targets(XSelectionRequestEvent *req_ev_ptr) +{ + int ret; + + // Tell the other client what targets we can supply from + // g_x_targets_array[] + ret = XChangeProperty(g_display, req_ev_ptr->requestor, + req_ev_ptr->property, XA_ATOM, 32, + PropModeReplace, (byte *)&g_x_targets_array[0], + g_x_num_targets); + if(0) { + // Seems to return 1, BadRequest always, but it works + printf("XChangeProperty TARGETS ret: %d\n", ret); + } +} + +void +x_request_paste_data(Window_info *win_info_ptr) +{ + printf("Pasting selection\n"); + // printf("Calling XConvertSelection\n"); + XConvertSelection(g_display, XA_PRIMARY, XA_STRING, XA_STRING, + win_info_ptr->x_win, CurrentTime); + // This will cause a SelectionNotify event, and we get the data + // by using XGetWindowProperty on our own window. This will eventually + // call x_handle_paste(). +} + +void +x_handle_paste(Window w, Atom property) +{ + byte *bptr; + Atom sel_type; + unsigned long sel_nitems, sel_bytes_after; + long sel_length; + int sel_format, ret, ret2, c; + int i; + + sel_length = 16384; + sel_type = 0; + sel_format = 0; + sel_nitems = 0; + sel_bytes_after = 0; + bptr = 0; + ret = XGetWindowProperty(g_display, w, property, 0, sel_length, 1, + AnyPropertyType, &sel_type, &sel_format, &sel_nitems, + &sel_bytes_after, &bptr); +#if 0 + printf("XGetWindowProperty ret:%d, sel_type:%ld, sel_format:%d, " + "sel_nitems:%ld, sel_bytes_after:%ld, bptr:%p\n", + ret, sel_type, sel_format, sel_nitems, sel_bytes_after, bptr); +#endif + if(bptr && (sel_type == property) && sel_nitems && (sel_format == 8)) { + //printf("bptr: %s\n", (char *)bptr); + for(i = 0; i < sel_nitems; i++) { + c = bptr[i]; + ret2 = adb_paste_add_buf(c); + if(ret2) { + printf("Paste buffer full!\n"); + break; + } + } + } + if(ret == 0) { + XFree(bptr); + } +} + +int +x_update_mouse(Window_info *win_info_ptr, int raw_x, int raw_y, + int button_states, int buttons_valid) +{ + Kimage *kimage_ptr; + int x, y, ret; + + if((button_states & buttons_valid & 2) == 2) { + // Middle button: Paste request + x_request_paste_data(win_info_ptr); + button_states = button_states & (~2); + } + kimage_ptr = win_info_ptr->kimage_ptr; + x = video_scale_mouse_x(kimage_ptr, raw_x, 0); + y = video_scale_mouse_y(kimage_ptr, raw_y, 0); + + if(g_x_warp_pointer && (raw_x == g_x_warp_x) && + (raw_y == g_x_warp_y) && (buttons_valid == 0) ) { + /* tell adb routs to recenter but ignore this motion */ + adb_update_mouse(kimage_ptr, x, y, 0, -1); + return 0; + } + ret = adb_update_mouse(kimage_ptr, x, y, button_states, + buttons_valid & 7); + return ret; +} + +void +x_input_events() +{ + XEvent ev, response; + XSelectionRequestEvent *req_ev_ptr; + Window_info *win_info_ptr; + char *str; + int len, motion, key_or_mouse, refresh_needed, buttons, hide, warp; + int width, height, resp_property, match, x_xpos, x_ypos; + int i; + + str = 0; + if(str) { + // Use str + } + if(adb_get_copy_requested()) { + x_send_copy_data(&g_mainwin_info); + } + g_num_check_input_calls--; + if(g_num_check_input_calls < 0) { + len = XPending(g_display); + g_num_check_input_calls = g_check_input_flush_rate; + } else { + len = QLength(g_display); + } + + motion = 0; + win_info_ptr = 0; + refresh_needed = 0; + key_or_mouse = 0; + while(len > 0) { + XNextEvent(g_display, &ev); + len--; + if(len == 0) { + /* Xaccel on linux only buffers one X event */ + /* must look for more now */ + len = XPending(g_display); + } + switch(ev.type) { + case FocusIn: + case FocusOut: + win_info_ptr = x_find_xwin(ev.xfocus.window); + if(ev.xfocus.type == FocusOut) { + /* Allow keyrepeat again! */ + vid_printf("Left window, auto repeat on\n"); + x_auto_repeat_on(0); + } else if(ev.xfocus.type == FocusIn && + (win_info_ptr == &g_mainwin_info)) { + /* Allow keyrepeat again! */ + vid_printf("Enter window, auto repeat off\n"); + x_auto_repeat_off(0); + } + break; + case EnterNotify: + case LeaveNotify: + /* These events are disabled now */ + printf("Enter/Leave event for winow %08x, sub: %08x\n", + (word32)ev.xcrossing.window, + (word32)ev.xcrossing.subwindow); + printf("Enter/L mode: %08x, detail: %08x, type:%02x\n", + ev.xcrossing.mode, ev.xcrossing.detail, + ev.xcrossing.type); + break; + case ButtonPress: + win_info_ptr = x_find_xwin(ev.xbutton.window); + vid_printf("Got button press of button %d!\n", + ev.xbutton.button); + buttons = (1 << ev.xbutton.button) >> 1; + motion |= x_update_mouse(win_info_ptr, ev.xbutton.x, + ev.xbutton.y, buttons, buttons & 7); + key_or_mouse = 1; + break; + case ButtonRelease: + win_info_ptr = x_find_xwin(ev.xbutton.window); + buttons = (1 << ev.xbutton.button) >> 1; + motion |= x_update_mouse(win_info_ptr, ev.xbutton.x, + ev.xbutton.y, 0, buttons & 7); + key_or_mouse = 1; + break; + case MotionNotify: + win_info_ptr = x_find_xwin(ev.xmotion.window); + motion |= x_update_mouse(win_info_ptr, ev.xmotion.x, + ev.xmotion.y, 0, 0); + key_or_mouse = 1; + break; + case Expose: + win_info_ptr = x_find_xwin(ev.xexpose.window); + refresh_needed = -1; + //printf("Got an Expose event\n"); + break; + case NoExpose: + /* do nothing */ + break; + case KeyPress: + case KeyRelease: + x_handle_keysym(&ev); + key_or_mouse = 1; + break; + case KeymapNotify: + break; + case DestroyNotify: + win_info_ptr = x_find_xwin(ev.xdestroywindow.window); + video_set_active(win_info_ptr->kimage_ptr, 0); + win_info_ptr->active = 0; + printf("Destroy %s\n", win_info_ptr->name_str); + if(win_info_ptr == &g_mainwin_info) { + x_try_xset_r(); // Mainwin: quit BURST + } + break; + case ReparentNotify: + case UnmapNotify: + case MapNotify: + break; + case ClientMessage: + win_info_ptr = x_find_xwin(ev.xclient.window); + if(ev.xclient.data.l[0] == win_info_ptr->delete_atom) { + // This is a WM_DELETE_WINDOW event + // Just unmap the window + win_info_ptr->kimage_ptr->active = 0; + } else { + printf("unknown ClientMessage\n"); + } + break; + case ConfigureNotify: + win_info_ptr = x_find_xwin(ev.xconfigure.window); + width = ev.xconfigure.width; + height = ev.xconfigure.height; +#if 0 + printf("ConfigureNotify, width:%d, height:%d\n", + width, height); +#endif + video_update_scale(win_info_ptr->kimage_ptr, width, + height, 0); + x_xpos = ev.xconfigure.x; + x_ypos = ev.xconfigure.y; + video_update_xpos_ypos(win_info_ptr->kimage_ptr, + x_xpos, x_ypos); + break; + case SelectionRequest: + //printf("SelectionRequest received\n"); + req_ev_ptr = &(ev.xselectionrequest); + // This is part of the dance for copy: Another client + // is asking us what format we can supply (TARGETS), + // or is doing to tell us one at a time what types + // it would like. +#if 0 + printf("req:%ld, property:%ld, target:%ld, " + "selection:%ld\n", req_ev_ptr->requestor, + req_ev_ptr->property, req_ev_ptr->target, + req_ev_ptr->selection); + str = XGetAtomName(g_display, req_ev_ptr->target); + printf("XAtom target str: %s\n", str); + XFree(str); + str = XGetAtomName(g_display, req_ev_ptr->property); + printf("XAtom property str: %s\n", str); + XFree(str); +#endif + resp_property = None; + match = 0; + for(i = 0; i < g_x_num_targets; i++) { + if(req_ev_ptr->target == g_x_targets_array[i]) { + match = 1; + break; + } + } + if(match) { + x_handle_copy(req_ev_ptr); + resp_property = req_ev_ptr->property; + } else if(req_ev_ptr->target == g_x_atom_targets) { + // Some other agent is asking us "TARGETS", + // so send our list of targets + x_handle_targets(req_ev_ptr); + } + // But no matter what the request target was, respond + // so it will send an eventual request for XA_STRING + response.xselection.type = SelectionNotify; + response.xselection.display = req_ev_ptr->display; + response.xselection.requestor = req_ev_ptr->requestor; + response.xselection.selection = req_ev_ptr->selection; + response.xselection.target = req_ev_ptr->target; + response.xselection.property = resp_property; + response.xselection.time = req_ev_ptr->time; + XSendEvent(g_display, req_ev_ptr->requestor, 0, 0, + &response); + XFlush(g_display); // Speed up getting more resp + break; + case SelectionNotify: + // We get this event after we requested the PRIMARY + // selection, so paste this to adb(). + vid_printf("SelectionNotify received\n"); + vid_printf("req:%ld, selection:%ld, target:%ld, " + "property:%ld\n", ev.xselection.requestor, + ev.xselection.selection, + ev.xselection.target, + ev.xselection.property); + if(ev.xselection.property == None) { + printf("No selection\n"); + break; + } + x_handle_paste(ev.xselection.requestor, + ev.xselection.property); + + break; + default: + printf("X event 0x%08x is unknown!\n", + ev.type); + break; + } + } + + if(key_or_mouse && (win_info_ptr == &g_mainwin_info)) { + hide = adb_get_hide_warp_info(win_info_ptr->kimage_ptr, &warp); + if(warp != g_x_warp_pointer) { + motion = 1; + } + g_x_warp_pointer = warp; + if(g_x_hide_pointer != hide) { + x_hide_pointer(&g_mainwin_info, hide); + } + g_x_hide_pointer = hide; + } + + if(motion && g_x_warp_pointer && (win_info_ptr == &g_mainwin_info)) { + // Calculate where to warp to + g_x_warp_x = video_unscale_mouse_x(win_info_ptr->kimage_ptr, + BASE_MARGIN_LEFT + (BASE_WINDOW_WIDTH/2), 0); + g_x_warp_y = video_unscale_mouse_y(win_info_ptr->kimage_ptr, + BASE_MARGIN_TOP + (A2_WINDOW_HEIGHT/2), 0); + + XWarpPointer(g_display, None, win_info_ptr->x_win, 0, 0, 0, 0, + g_x_warp_x, g_x_warp_y); + } + + if(refresh_needed && win_info_ptr) { + //printf("...at end, refresh_needed:%d\n", refresh_needed); + video_set_x_refresh_needed(win_info_ptr->kimage_ptr, 1); + } + +} + +void +x_hide_pointer(Window_info *win_info_ptr, int do_hide) +{ + if(do_hide) { // invisible + XDefineCursor(g_display, win_info_ptr->x_win, g_cursor); + } else { // Default cursor + XDefineCursor(g_display, win_info_ptr->x_win, None); + } +} + +void +x_handle_keysym(XEvent *xev_in) +{ + Window_info *win_info_ptr; + KeySym keysym; + word32 state; + int keycode, a2code, type, is_up; + + win_info_ptr = x_find_xwin(xev_in->xkey.window); + + keycode = xev_in->xkey.keycode; + type = xev_in->xkey.type; + + keysym = XLookupKeysym(&(xev_in->xkey), 0); + + state = xev_in->xkey.state; + + vid_printf("keycode: %d, type: %d, state:%d, sym: %08x\n", + keycode, type, state, (word32)keysym); + + x_update_modifier_state(win_info_ptr, state); + + is_up = 0; + if(type == KeyRelease) { + is_up = 1; + } + + /* first, do conversions */ + switch(keysym) { + case XK_Alt_L: + case XK_Meta_R: // Windows key on right side + case XK_Super_R: + case XK_Mode_switch: + case XK_Cancel: + keysym = XK_Print; /* option */ + break; + case XK_Meta_L: // Windows key on left side + case XK_Alt_R: + case XK_Super_L: + case XK_Menu: + keysym = XK_Scroll_Lock; /* cmd */ + break; + case XK_F5: + break; + case XK_F10: + if(!is_up) { + g_video_scale_algorithm++; + if(g_video_scale_algorithm >= 3) { + g_video_scale_algorithm = 0; + } + printf("g_video_scale_algorithm = %d\n", + g_video_scale_algorithm); + video_update_scale(win_info_ptr->kimage_ptr, + win_info_ptr->width_req, + win_info_ptr->main_height, 1); + } + break; + case 0x1000003: + if(keycode == 0x3c) { + /* enter key on Mac OS X laptop--make it option */ + keysym = XK_Print; + } + break; + case NoSymbol: + switch(keycode) { + /* 94-95 are for my PC101 kbd + windows keys on HPUX */ + case 0x0095: + /* left windows key = option */ + keysym = XK_Print; + break; + case 0x0096: + case 0x0094: + /* right windows key = cmd */ + keysym = XK_Scroll_Lock; + break; + /* 0072 is for cra@WPI.EDU who says it's Break under XFree86 */ + case 0x0072: + /* 006e is break according to mic@research.nj.nec.com */ + case 0x006e: + keysym = XK_Break; + break; + + /* 0x0042, 0x0046, and 0x0048 are the windows keys according */ + /* to Geoff Weiss on Solaris x86 */ + case 0x0042: + case 0x0046: + /* flying windows == open apple */ + keysym = XK_Scroll_Lock; + break; + case 0x0048: + case 0x0076: /* Windows menu key on Mac OS X */ + /* menu windows == option */ + keysym = XK_Print; + break; + } + } + + a2code = x_keysym_to_a2code(win_info_ptr, (int)keysym, is_up); + if(a2code >= 0) { + adb_physical_key_update(win_info_ptr->kimage_ptr, a2code, + 0, is_up); + } else if(a2code != -2) { + printf("Keysym: %04x of keycode: %02x unknown\n", + (word32)keysym, keycode); + } +} + +int +x_keysym_to_a2code(Window_info *win_info_ptr, int keysym, int is_up) +{ + int i; + + if(keysym == 0) { + return -1; + } + + /* Look up Apple 2 keycode */ + for(i = g_num_a2_keycodes - 1; i >= 0; i--) { + if((keysym == g_x_a2_key_to_xsym[i][1]) || + (keysym == g_x_a2_key_to_xsym[i][2])) { + + vid_printf("Found keysym:%04x = a[%d] = %04x or %04x\n", + (int)keysym, i, g_x_a2_key_to_xsym[i][1], + g_x_a2_key_to_xsym[i][2]); + + return g_x_a2_key_to_xsym[i][0]; + } + } + + return -1; +} + +void +x_update_modifier_state(Window_info *win_info_ptr, int state) +{ + word32 c025_val; + + c025_val = 0; + if(state & ShiftMask) { + c025_val |= 1; + } + if(state & ControlMask) { + c025_val |= 2; + } + if(state & LockMask) { + c025_val |= 4; + } + adb_update_c025_mask(win_info_ptr->kimage_ptr, c025_val, 7); +} + +void +x_auto_repeat_on(int must) +{ + if((g_auto_repeat_on <= 0) || must) { + g_auto_repeat_on = 1; + XAutoRepeatOn(g_display); + XFlush(g_display); + adb_kbd_repeat_off(); + } +} + +void +x_auto_repeat_off(int must) +{ + if((g_auto_repeat_on != 0) || must) { + XAutoRepeatOff(g_display); + XFlush(g_display); + g_auto_repeat_on = 0; + adb_kbd_repeat_off(); + } +} + +void +x_full_screen(int do_full) +{ + return; +}