////////////////////////////////////////////////////////////////////// // // // jdev_main.pas: Main form of the Mappy VM user interface // // // // The contents of this file are subject to the Bottled Light // // Public License Version 1.0 (the "License"); you may not use this // // file except in compliance with the License. You may obtain a // // copy of the License at http://www.bottledlight.com/BLPL/ // // // // Software distributed under the License is distributed on an // // "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or // // implied. See the License for the specific language governing // // rights and limitations under the License. // // // // The Original Code is the Mappy VM User Interface, released // // April 1st, 2003. The Initial Developer of the Original Code is // // Bottled Light, Inc. Portions created by Bottled Light, Inc. are // // Copyright (C) 2001-2003 Bottled Light, Inc. All Rights Reserved. // // // // Author(s): // // Michael Noland (joat), michael@bottledlight.com // // // // Changelog: // // 1.0: First public release (April 1st, 2003) // // // // Notes: // // The menu items should be converted to actions so MVM can use // // the configuration manager in delphi 6/7 if/when development // // switches to a newer version. // // // // This is also as good a place as any to describe the powerful // // if quirky localization system Mappy VM uses. It allows many // // VCL controls to have their text localized to a particular // // language, as well as making hotkeys configurable (you have to // // edit a text file to do so sadly). // // // // The translation system currently goes for caption, color, and // // shortcut properties of any component owned by the root passed // // to it in a LoadTranslation call. This uses the undocumented // // Delphi reflection units, and is NOT portable across different // // versions of delphi. As far as I know, its just a matter of // // changing the unit names when going from Delphi 5 to Delphi 7, // // but YMMV. Adding other properties to the localization // // capabilities is pretty easy as well, see nexus.pas for more // // information on the subject (such as LoadTranslation) // // // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// unit jdev_main; ////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// interface //////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Menus, StdCtrls, ExtCtrls, IniFiles, Math, Contnrs, ComCtrls, MMSystem, ClipBrd, sysAviWriter, console, CpuObservers, platformSound, platformVideo, dwarfUtils, nexus, elfUtils, addressSpace, dbgBreakpoints, dbgWatches, romUtils, Tools, fisFileNotification, ShellApi; ////////////////////////////////////////////////////////////////////// type // File type codes TSFileType = (ftROM, ftMultiboot, ftELF, ftSaveState); TjdevMain = class(TForm) mainMenu: TMainMenu; mFile: TMenuItem; mLoadFile: TMenuItem; mReopenFile: TMenuItem; mReloadFile: TMenuItem; mRemoveCart: TMenuItem; mExportDisassembly: TMenuItem; mCopyScreenToClipboard: TMenuItem; mSaveScreenshot: TMenuItem; mExitMappy: TMenuItem; mOptions: TMenuItem; mToggleSoundEnabled: TMenuItem; mView: TMenuItem; mClearLog: TMenuItem; mRun: TMenuItem; mRunCPU: TMenuItem; mStepOver: TMenuItem; mTraceInto: TMenuItem; mStepFrame: TMenuItem; mPauseCPU: TMenuItem; mResetCPU: TMenuItem; mEvaluateModify: TMenuItem; mAddBreakpoint: TMenuItem; mRunNFrames: TMenuItem; mRunNCycles: TMenuItem; mTraceSourceLine: TMenuItem; mAddWatch: TMenuItem; mAdvancedStuff: TMenuItem; mTools: TMenuItem; mNothingness: TMenuItem; mHelp: TMenuItem; mShowHelp: TMenuItem; mShowSDK: TMenuItem; mShowMappyPage: TMenuItem; mShowCommunityNews: TMenuItem; mShowAboutBox: TMenuItem; mShowCompanyPage: TMenuItem; N2: TMenuItem; N3: TMenuItem; N4: TMenuItem; N6: TMenuItem; N7: TMenuItem; N1: TMenuItem; N10: TMenuItem; N11: TMenuItem; N12: TMenuItem; N5: TMenuItem; openDialog: TOpenDialog; saveDialog: TSaveDialog; status: TStatusBar; fileWatcher: TfisFileNotification; N8: TMenuItem; mLoadCore: TMenuItem; mWriteSavestate: TMenuItem; DwarfDebugger1: TMenuItem; procedure FormCreate(Sender: TObject); procedure FormShow(Sender: TObject); procedure FormResize(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormPaint(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure clockTimer(Sender: TObject); // File Menu procedure LoadFile(Sender: TObject); procedure RemoveCart(Sender: TObject); procedure ReloadFile(Sender: TObject); procedure LoadRecent(Sender: TObject); procedure SaveSavestate(Sender: TObject); procedure ExportDisassembly(Sender: TObject); procedure CopyScreenToClipboard(Sender: TObject); procedure SaveScreenshot(Sender: TObject); procedure ExitApplication(Sender: TObject); // Options Menu procedure ToggleSoundEnabled(Sender: TObject); // View Menu procedure ClearConsole(Sender: TObject); // Run Menu procedure RunCPU(Sender: TObject); procedure StepOver(Sender: TObject); procedure TraceInto(Sender: TObject); procedure TraceToSourceLine(Sender: TObject); procedure StepOneFrame(Sender: TObject); procedure PauseCPU(Sender: TObject); procedure ResetCPU(Sender: TObject); procedure EvaluateModify(Sender: TObject); procedure AddAWatch(Sender: TObject); procedure AddABreakpoint(Sender: TObject); procedure RunNCycles(Sender: TObject); procedure RunNFrames(Sender: TObject); // Tools Menu // Help Menu procedure ShowHelp(Sender: TObject); procedure ShowSDK(Sender: TObject); procedure ShowCompanyPage(Sender: TObject); procedure ShowMappyPage(Sender: TObject); procedure ShowCommunityNews(Sender: TObject); procedure ShowAboutBox(Sender: TObject); // Directory watching procedure OnDirectoryChange(Sender: TObject); procedure mNothingnessClick(Sender: TObject); procedure LoadNewCore(Sender: TObject); procedure DwarfDebugger1Click(Sender: TObject); private function LoadInBinary(filename: string): boolean; procedure LoadSavestate(filename: string; stream: TStream); procedure ExecuteCleanup; public lastFileAge: integer; cartLoaded: boolean; appFocused: boolean; mruList: TStringList; keyMask: uint16; downkeys: uint16; isActive: boolean; // Graphics related stuff dontResize: boolean; // Contains the dynamic menu items, since they must be freed menuViewers: TObjectList; procedure OnViewerClick(Sender: TObject); // Drag-drop handler procedure FileDropHandler(var msg: TMessage); message WM_DROPFILES; // Most recently used file list procedure RebuildMRUList; // Plugin list procedure RebuildPluginList; procedure OnTriggerPluginClick(Sender: TObject); procedure OnIdle(Sender: TObject; var Done: Boolean); procedure OnActivateApp(sender: TObject); procedure OnDeactivateApp(sender: TObject); function OnShowHelp(Command: word; Data: longint; var CallHelp: boolean): boolean; procedure LoadCore(filename: string); procedure UnloadCore; procedure SaveCartInfo; procedure LoadCartInfo; end; ////////////////////////////////////////////////////////////////////// var jdevMain: TjdevMain; clockMutex: boolean = true; fps: double; // File locations lastRomFilename: string; lastScreenshotFilename: string; ////////////////////////////////////////////////////////////////////// implementation /////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// uses jdevAboutBox, jdevDwarfDebugger, debugBreakpointProperties, debugWatchProperties, observerCSource, jdevSplashScreen, jdevCapture, jdevEvalModify, VideoOptions, AudioOptions, DebugOptions, JoypadOptions, observerBreakpointList, observerWatchList, jdevDisassemblyDialog, GeneralOptions, debugFindPattern; ////////////////////////////////////////////////////////////////////// {$R *.DFM} {$R jdev2.res} ////////////////////////////////////////////////////////////////////// procedure OnConsoleReady(line: PChar); begin logWrite(line); end; ////////////////////////////////////////////////////////////////////// // Form Events /////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure TjdevMain.FormCreate(Sender: TObject); var i: integer; item: TMenuItem; begin HelpContext := LinkHelp('index.html'); cartLoaded := false; // Platform graphics code dontResize := false; // Application events appFocused := true; Application.OnHelp := OnShowHelp; // Drag-drop support DragAcceptFiles(Handle, true); // Key input keyMask := $03FF; // MRU file list mruList := TStringList.Create; // Create the viewer menu menuViewers := TObjectList.Create; observers.Sort; for i := 0 to observers.Count-1 do begin item := NewItem(observers.Strings[i], 0, false, true, OnViewerClick, i, 'oviewer'+IntToStr(i)); menuViewers.Add(item); mView.Add(item); end; // Create the options menu guiObservers.Sort; for i := 0 to guiObservers.Count-1 do begin item := NewItem(guiObservers.Strings[i], 0, false, true, OnViewerClick, i, 'odialog'+IntToStr(i)); menuViewers.Add(item); mOptions.Add(item); end; // Populate the tools menu EnumeratePlugins; RebuildPluginList; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.FormShow(Sender: TObject); var i: integer; st: string; begin // Find a suitable MVM core st := ExtractFilePath(ParamStr(0)) + 'core/standard.cor'; if not FileExists(st) then st := st + 'e'; if not FileExists(st) then st := ExtractFilePath(ParamStr(0)) + 'core/vmcore.dll'; if not FileExists(st) then begin ShowMessage('Could not find a suitable core file, terminating!'); Halt; end; // st := ExtractFilePath(ParamStr(0)) + 'core/vmcore.dll'; LoadCore(st); // Load the translation LoadTranslation(self, translation); //fixme, get a new license here: if showSplashScreen then jdevSplash.ShowModal; if not runOnce then begin // show several options that can be changed fixme findme todo end; // Handle command line parameters for i := 1 to ParamCount do begin st := ParamStr(i); if st[1] = '-' then begin Delete(st, 1, 1); logProcessCommand(st); end else if FileExists(st) then LoadInBinary(st); end; clockMutex := false; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.FormResize(Sender: TObject); begin if not dontResize then begin screenWidth := ClientWidth; screenHeight := ClientHeight - status.Height; status.Panels[1].Text := Format(' Screen: %d x %d', [screenWidth, screenHeight]); Repaint; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.FormClose(Sender: TObject; var Action: TCloseAction); begin clockMutex := true; isActive := false; RemoveCart(Sender); UnloadCore; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.FormPaint(Sender: TObject); begin platformRenderScreen; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.FormDestroy(Sender: TObject); begin menuViewers.Free; mruList.Free; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var i: integer; begin for i := 0 to 9 do if Key = keyCodes[i] then keyMask := keyMask and not (1 shl i); if downkeys <> keyMask then begin downkeys := keyMask; vmKeyInput(keyMask); end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); var i: integer; begin for i := 0 to 9 do if Key = keyCodes[i] then keyMask := keyMask or (1 shl i); // if Key = captureToggleKey then ToggleMovieCapture(nil); if downkeys <> keyMask then begin downkeys := keyMask; vmKeyInput(keyMask); end; end; ////////////////////////////////////////////////////////////////////// // File Menu Actions ///////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure TjdevMain.LoadFile(Sender: TObject); begin openDialog.Filter := 'Binaries|*.bin;*.gba;*.agb;*.mb;*.elf;*.jst|All files|*.*'; openDialog.DefaultExt := 'bin'; openDialog.FileName := lastRomFilename; if openDialog.Execute then LoadInBinary(openDialog.FileName); end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.RemoveCart(Sender: TObject); begin if cartLoaded then begin cartLoaded := false; SaveCartInfo; vmRemoveCartridge; if Assigned(dwarf) then dwarf.Free; dwarf := nil; if Assigned(elf) then elf.Free; elf := nil; Caption := 'Mappy VM'; UpdateObservers; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ReloadFile(Sender: TObject); begin LoadInBinary(lastRomFilename); end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.LoadRecent(Sender: TObject); var name: string; i: integer; begin name := StripHotKey(TMenuItem(Sender).Caption); if not LoadInBinary(name) then begin i := mruList.IndexOf(name); mruList.Delete(i); RebuildMRUList; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ExportDisassembly(Sender: TObject); begin jdevDisasmDialog.Show; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.CopyScreenToClipboard(Sender: TObject); var myFormat: word; data: THandle; pal: HPalette; begin displayBMP.SaveToClipBoardFormat(myFormat, data, pal); clipboard.SetAsHandle(myFormat, data); end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.SaveScreenshot(Sender: TObject); begin saveDialog.Filter := 'Bitmap Images|*.bmp|All files|*.*'; saveDialog.filename := lastScreenshotFilename; saveDialog.DefaultExt := 'bmp'; if saveDialog.Execute then displayBMP.SaveToFile(saveDialog.FileName); lastScreenshotFilename := saveDialog.filename; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ExitApplication(Sender: TObject); begin Close; end; ////////////////////////////////////////////////////////////////////// // Options Menu Actions ////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ToggleSoundEnabled(Sender: TObject); begin mToggleSoundEnabled.Checked := not mToggleSoundEnabled.Checked; sysSound.enabled := mToggleSoundEnabled.Checked; end; ////////////////////////////////////////////////////////////////////// // View Menu Actions ///////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ClearConsole(Sender: TObject); begin logProcessCommand('clear'); end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.OnViewerClick(Sender: TObject); var ref: TCpuObserver; item: TMenuItem; begin item := Sender as TMenuItem; if item.checked then begin ref := FindObserver(StripHotKey(item.Caption)); if ref <> nil then if ref.CloseQuery then begin ref.Close; item.checked := false; end; end else begin CreateObserver(StripHotKey(item.Caption), item); end; end; ////////////////////////////////////////////////////////////////////// // Run Menu Actions ////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure TjdevMain.RunCPU(Sender: TObject); begin ClearTempBreakpoint; isActive := true; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.StepOver(Sender: TObject); var address: uint32; begin ClearTempBreakpoint; // Figure out where to set a temporary breakpoint // (2 for thumb, 4 for arm or thumb BL) address := vmCurrentPC + 2; if (vmGetRegister(CPSR) and SR_T = 0) then Inc(address, 2) else if (vmReadHalfword(vmCurrentPC) and $F800 = $F000) then Inc(address, 2); // Add a breakpoint and make a note to remove it if needed SetTempBreakpoint(address); isActive := true; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.TraceInto(Sender: TObject); begin ClearTempBreakpoint; vmStep; ExecuteCleanup; isActive := false; UpdateObservers; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.TraceToSourceLine(Sender: TObject); begin cpuSourceDebug := true; vmSoftBreakpoints(cpuSourceDebug); isActive := true; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.StepOneFrame(Sender: TObject); begin // Run one frame ClearTempBreakpoint; vmRenderFrame; ExecuteCleanup; isActive := false; // Update the observers UpdateObservers; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.PauseCPU(Sender: TObject); begin isActive := false; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ResetCPU(Sender: TObject); begin vmReset; isActive := false; UpdateObservers; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.EvaluateModify(Sender: TObject); begin jdevModify.Show; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.AddAWatch(Sender: TObject); var watchProperties: TdbgWatchProperties; begin Application.CreateForm(TdbgWatchProperties, watchProperties); watchProperties.watch := TWatch.Create; AddWatch(watchProperties.watch); watchProperties.Show; UpdateObservers; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.AddABreakpoint(Sender: TObject); var bpProperties: TdbgBreakpointProperties; begin Application.CreateForm(TdbgBreakpointProperties, bpProperties); bpProperties.bp := TBreakpoint.Create; AddBreakpoint(bpProperties.bp); bpProperties.Show; UpdateObservers; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.RunNCycles(Sender: TObject); var st: string; cycs: integer; begin try // Offer an input box for the user to enter a cycle count st := InputBox('Mappy', 'Enter the number of cycles to process:', '10'); cycs := StrToIntDef(st, 1); // Run 'n' cycles if cycs < 0 then cycs := 1; isActive := true; vmExecute(cycs); ExecuteCleanup; isActive := false; // Update the observers UpdateObservers; except on e: EConvertError do ShowMessage('The number of cycles must be a positive integer.'); end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.RunNFrames(Sender: TObject); var st: string; frames: integer; begin // Offer an input box for the user to enter a frame count st := InputBox('Mappy', 'Enter the number of frames to process:', '10'); frames := StrToIntDef(st, 1); // Process 'n' frames, allowing windows to get a word in edgewise // every so often. isActive := true; while isActive and (frames > 0) do begin vmRenderFrame; ExecuteCleanup; Dec(frames); if frames and 7 = 0 then begin Application.ProcessMessages; UpdateObservers; end; end; isActive := false; end; ////////////////////////////////////////////////////////////////////// // Tools Menu Actions //////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // Help Menu Actions ///////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ShowHelp(Sender: TObject); begin ShowWebPage(helpFiles.strings[HelpContext-1]); end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ShowSDK(Sender: TObject); begin ShowWebPage('file://' + ExtractFilePath(ParamStr(0)) + 'sdk/index.html'); end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ShowCompanyPage(Sender: TObject); begin ShowWebPage('http://www.bottledlight.com/index.html'); end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ShowMappyPage(Sender: TObject); begin ShowWebPage('http://www.bottledlight.com/mappy/index.html'); end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ShowCommunityNews(Sender: TObject); begin ShowWebPage('http://www.gbadev.org/'); end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.showAboutBox(Sender: TObject); begin jdevAbout.ShowModal; end; ////////////////////////////////////////////////////////////////////// // Misc. events ////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure TjdevMain.FileDropHandler(var msg: TMessage); var number: integer; filename: string; begin SetLength(filename, 256); number := DragQueryFile(msg.wparam, $FFFFFFFF, PChar(filename), 255); if number > 0 then begin DragQueryFile(msg.wparam, 0, PChar(filename), 255); LoadInBinary(filename); end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.OnDirectoryChange(Sender: TObject); begin Beep; if lastFileAge <> FileAge(lastRomFilename) then begin caption := inttostr(lastFileAge); if MessageDlg('Image ' + lastRomFilename + '''s time/date changed. Reload?', mtInformation, [mbYes, mbNo], 0) = mrYes then begin ReloadFile(Sender); lastFileAge := FileAge(lastRomFilename); end; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.OnActivateApp(sender: TObject); begin sysSound.enabled := mToggleSoundEnabled.Checked; appFocused := true; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.OnDeactivateApp(sender: TObject); begin sysSound.enabled := false; appFocused := false; end; ////////////////////////////////////////////////////////////////////// function TjdevMain.OnShowHelp(Command: word; Data: longint; var CallHelp: boolean): boolean; begin CallHelp := false; if (data <= helpFiles.Count) and (data > 0) then ShowWebPage(helpFiles.strings[data-1]); Result := true; end; ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure TjdevMain.ExecuteCleanup; var bp: TBreakpoint; begin if vmHitBP then begin if IsTempBreakpoint then isActive := false else begin bp := FindBreakpoint(vmCurrentPC); if bp <> nil then if bp.TriggerBP then begin ShowMessage('Breakpoint at ' + IntToHex(vmCurrentPC, 8) + ' encountered'); isActive := false; end; end; end; cpuSourceDebug := cpuSourceDebug and isActive; if dwarf <> nil then cpuSourceDebug := isActive; vmSoftBreakpoints(cpuSourceDebug); end; ////////////////////////////////////////////////////////////////////// // Translates a file name into a file type based solely on the extension // Further refinement is neccecary for them anyways, but this is a start function SimpleFileType(filename: string): TSFileType; const exts: array[0..5] of string = ('.BIN', '.GBA', '.AGB', '.MB', '.ELF', '.JST'); typs: array[0..5] of TSFileType = (ftROM, ftROM, ftROM, ftMultiboot, ftELF, ftSaveState); var i: integer; begin i := 0; filename := Uppercase(ExtractFileExt(filename)); while i <= 5 do begin if filename = exts[i] then Break; Inc(i); end; if i > 5 then i := 0; Result := typs[i]; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.LoadSavestate(filename: string; stream: TStream); var save: PvmSavestate; data: Puint8array; size: integer; st: string; begin size := stream.Size; GetMem(save, size); stream.Read(save^, stream.size); stream.Free; if save^.cartLoaded then begin st := ExtractFilePath(filename) + save^.filename; if FileExists(st) then begin stream := TFileStream.Create(st, fmOpenRead or fmShareDenyNone); GetMem(data, stream.size); stream.Read(data^, stream.size); vmInsertCartridge(data, stream.size); FreeMem(data, stream.size); stream.Free; if autopatchLogo then PatchHeader; if save^.crc <> CrcROM then logWriteLn('LoadSavestate: Error, image CRC does not match stored value'); end else logWriteLn('LoadSavestate: Error, could not load image "' + st + '"'); end; vmLoadState(save); FreeMem(save); end; ////////////////////////////////////////////////////////////////////// function TjdevMain.LoadInBinary(filename: string): boolean; var stream: TFileStream; data: Puint8array; banks: TvmMemoryLock1; size: integer; typ: TSFileType; begin Result := false; if not FileExists(filename) then begin logWriteLn('Error: File "' + filename + '" not found!'); Exit; end; RemoveCart(nil); typ := SimpleFileType(filename); fileWatcher.Stop; isActive := false; if Assigned(dwarf) then dwarf.Free; dwarf := nil; if Assigned(elf) then elf.Free; elf := nil; try // Load the file into the core stream := TFileStream.Create(filename, fmOpenRead or fmShareDenyNone); case typ of ftROM: begin GetMem(data, stream.size); stream.Read(data^, stream.size); vmInsertCartridge(data, stream.size); FreeMem(data, stream.size); cartLoaded := true; stream.Free; end; ftMultiboot: begin vmLockMemory(banks); stream.Read(banks.exwram^, Min(stream.size, $40000)); banks.wram^[$7FFA] := 1; vmUnlockMemory(banks); stream.Free; end; ftELF: begin cartLoaded := true; // Create and build an elf class out of the file elf := TELFFile.Create(stream); elf.Build; // Flatten the elf into a binary size := elf.Flatten(data); vmInsertCartridge(data, size); dwarf := ProcessDWARF(elf, ExtractFilePath(filename)); stream.Free; end; ftSaveState: LoadSavestate(filename, stream); else stream.Free; end; ReassertBreakpoints; except on e: Exception do begin logWriteLn('Error: ' + e.Message); e.Free; end; end; // Reset user interface stuff isActive := autorunROMs; fps := 0; lastRomFilename := filename; if typ <> ftSavestate then begin vmReset; LoadCartInfo; if typ = ftMultiboot then begin vmSetRegister(r15, $02000000) end else if autopatchLogo then PatchHeader; end; // Update the observer display if mruList.IndexOf(lastRomFilename) = -1 then begin mruList.Insert(0, lastRomFilename); if mruList.Count > 10 then mruList.Delete(10); RebuildMRUList; end; Caption := 'Mappy VM [' + ExtractFileName(lastROMFilename) + ']'; UpdateObservers; // Initialize the file watcher lastFileAge := FileAge(filename); fileWatcher.Directory := ExtractFilePath(filename); fileWatcher.Start; Result := true; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.clockTimer(Sender: TObject); begin // end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.OnIdle(Sender: TObject; var Done: Boolean); var cycles, delta: integer; token: TvmProfileToken; t0, tslf: uint32; begin Done := true; if clockMutex then Exit; if not coreLoaded then Exit; clockMutex := true; timeBeginPeriod(1); tslf := timeGetTime; repeat if isActive then begin // If we're meant to do automatic updates, and there is a rom loaded, then lets rock // Start the fps processing token := vmStartProfile; vmRenderFrame; cycles := vmStopProfile(token); ExecuteCleanup; UpdateObservers; repeat until timeGetTime-tslf > msBetweenFrames; // Update the framerate t0 := timeGetTime; delta := t0-tslf; tslf := t0; if delta <> 0 then begin fps := 0.95*fps + 0.05*(1000/delta)*(cycles/(1232*228)); if sysSound.frequency > 0 then vmSetAudioRate(max(trunc((fps * 16777216) / (sysSound.frequency*59.727)), 64)); end; if capturing then status.Panels[0].Text := ' Status: Capturing' else status.Panels[0].Text := ' Status: Running'; status.Panels[1].Text := Format(' FPS: %.1f (%.0f%% optimal)', [fps, 100*fps/59.727]) end else begin status.Panels[0].Text := ' Status: Paused'; if Copy(status.Panels[1].Text, 1, 4) = ' FPS' then status.Panels[1].Text := ''; end; sysSound.enabled := isActive and appFocused and mToggleSoundEnabled.Checked; Application.ProcessMessages; until Application.Terminated or not isActive; timeEndPeriod(1); clockMutex := false; end; ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure TjdevMain.LoadCore(filename: string); var i: integer; ini: TIniFile; item: TMenuItem; st: string; ws: TWindowState; begin ini := TIniFile.Create(appIniFile); vmLoadCore(PChar(filename)); // Video Setup InitVideo; vmSetOnVideo(OnVideoReady); vmSetOnConsole(OnConsoleReady); ws := TWindowState(Min(2, Max(0, ini.ReadInteger('Video Options', 'WindowState', Ord(wsNormal))))); if ws <> wsMaximized then begin ClientWidth := ini.ReadInteger('Video Options', 'ScreenWidth', 240); ClientHeight := status.height + ini.ReadInteger('Video Options', 'ScreenHeight', 160); Left := ini.ReadInteger('Video Options', 'Left', Left); Top := ini.ReadInteger('Video Options', 'Top', Top); end else begin ClientWidth := 240; ClientHeight := status.height + 160; end; WindowState := ws; LoadVideoOptions(ini); // Audio Setup sysSound := TDirectSoundDriver.Create; LoadAudioOptions(ini); sysSound.Available := true; // Joypad Setup LoadJoypadOptions(ini); // Debug Setup LoadDebugOptions(ini); // General Setup LoadGeneralOptions(ini); // Reset the CPU vmReset; // Filenames lastRomFilename := ini.ReadString('Filenames', 'LastImage', ''); lastScreenshotFilename := ini.ReadString('Filenames', 'LastScreenshot', ''); if not FileExists(lastRomFilename) then lastRomFilename := ''; if not FileExists(lastScreenshotFilename) then lastScreenshotFilename := ''; mruList.Clear; for i := 0 to 9 do begin st := ini.ReadString('Filenames', 'Recent' + IntToStr(i), ''); if FileExists(st) then mruList.Insert(i, st); end; RebuildMRUList; // Open any previously opened viewers for i := 0 to menuViewers.Count - 1 do begin item := TMenuItem(menuViewers.Items[i]); if ini.ReadBool(StripHotKey(item.Caption), 'Opened', false) then CreateObserver(StripHotKey(item.Caption), item); end; // Close the .INI file ini.Free; Application.OnActivate := OnActivateApp; Application.OnDeactivate := OnDeactivateApp; Application.OnIdle := OnIdle; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.UnloadCore; var ini: TIniFile; i: integer; st: string; begin ini := TIniFile.Create(appIniFile); // Video Setup vmSetOnVideo(nil); SaveVideoOptions(ini); FreeVideo; ini.WriteInteger('Video Options', 'WindowState', Ord(WindowState)); ini.WriteInteger('Video Options', 'ScreenWidth', ClientWidth); ini.WriteInteger('Video Options', 'ScreenHeight', ClientHeight - status.height); ini.WriteInteger('Video Options', 'Left', Left); ini.WriteInteger('Video Options', 'Top', Top); // Audio Setup SaveAudioOptions(ini); sysSound.Free; // Joypad Setup SaveJoypadOptions(ini); // Debug Setup SaveDebugOptions(ini); // General Setup SaveGeneralOptions(ini); // Observers for i := 0 to menuViewers.Count - 1 do begin st := StripHotKey(TMenuItem(menuViewers.Items[i]).Caption); ini.WriteBool(st, 'Opened', FindObserver(st) <> nil); end; // Filenames ini.WriteString('Filenames', 'LastImage', lastRomFilename); ini.WriteString('Filenames', 'LastScreenshot', lastScreenshotFilename); for i := 0 to 9 do begin if i < mruList.Count then st := mruList.Strings[i] else st := ''; ini.WriteString('Filenames', 'Recent' + IntToStr(i), st); end; mruList.Clear; RebuildMRUList; CloseObservers; // Unload the core vmSetOnConsole(nil); vmUnloadCore; ini.Free; Application.OnActivate := nil; Application.OnDeactivate := nil; Application.OnIdle := nil; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.LoadCartInfo; var size: integer; data: PByteArray; stream: TFileStream; name: string; banks: TvmMemoryLock1; begin name := ChangeFileExt(lastRomFilename, '.sav'); if FileExists(name) then begin logWriteLn('Loading cartridge info from ' + ExtractFileName(name)); stream := TFileStream.Create(name, fmOpenRead or fmShareDenyNone); size := stream.Size; GetMem(data, size); stream.Read(data^, size); vmLockMemory(banks); vmSetCartInfo(size, data); vmUnlockMemory(banks); FreeMem(data, size); stream.Free; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.SaveCartInfo; var size: integer; data: pointer; stream: TFileStream; name: string; begin size := vmGetCartInfo(nil); if size > 0 then begin name := ChangeFileExt(lastRomFilename, '.sav'); logWriteLn('Saving cartridge info to ' + ExtractFileName(name)); GetMem(data, size); vmGetCartInfo(data); stream := TFileStream.Create(name, fmCreate); stream.Write(data^, size); stream.Free; FreeMem(data, size); end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.mNothingnessClick(Sender: TObject); var list: TStringList; i: integer; begin Beep; list := TStringList.Create; SaveTranslation(self, list); for i := 0 to observerList.count - 1 do SaveTranslation((observerList.Items[i] as TCpuObserver), list); SaveTranslation(jdevModify, list); SaveTranslation(jdevDisasmDialog, list); SaveTranslation(jdevCaptureForm, list); SaveTranslation(dbgFindPattern, list); list.SaveToFile(ExtractFilePath(ParamStr(0)) + 'American English.trs'); end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.LoadNewCore(Sender: TObject); begin openDialog.Filter := 'Mappy VM Cores|*.core|All files|*.*'; openDialog.DefaultExt := 'core'; openDialog.FileName := ExtractFilePath(ParamStr(0)) + 'core\standard.core'; if openDialog.Execute then if FileExists(openDialog.Filename) then begin UnloadCore; LoadCore(openDialog.Filename); end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.SaveSavestate(Sender: TObject); var size: integer; save: PvmSavestate; stream: TFileStream; st: string; begin saveDialog.Filter := 'Maypy VM Savestates|*.jst|All files|*.*'; saveDialog.filename := ChangeFileExt(lastRomFilename, '.jst'); saveDialog.DefaultExt := 'jst'; if saveDialog.Execute then begin size := vmSaveState(nil); if size > 0 then begin GetMem(save, size); vmSaveState(save); if save^.cartLoaded then begin st := ExtractFileName(lastRomFilename); if st <> '' then begin Move(st[1], save^.filename, Min(Length(st), 255)); save^.filename[Min(Length(st), 255)] := #0; save^.crc := CrcROM; end; end; stream := TFileStream.Create(saveDialog.Filename, fmCreate); stream.Write(save^, size); stream.Free; FreeMem(save, size); end; end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.DwarfDebugger1Click(Sender: TObject); begin jdevDwarfDebug.Show; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.RebuildMRUList; var i: integer; begin mReopenFile.Clear; for i := 0 to mruList.Count - 1 do begin mReopenFile.Add(NewItem(mruList.Strings[i], 0, false, true, LoadRecent, i, 'mruitem'+IntToStr(i))); end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.RebuildPluginList; var i: integer; plugin: PvmPluginHeader; item: TMenuItem; begin for i := 0 to plugins.Count - 1 do begin plugin := @(TPlugin(plugins.items[i]).header); item := NewItem(plugin^.triggerCaption, 0, false, true, OnTriggerPluginClick, i, 'triggerPlugin'+IntToStr(i)); item.Tag := i; mTools.Add(item); end; end; ////////////////////////////////////////////////////////////////////// procedure TjdevMain.OnTriggerPluginClick(Sender: TObject); begin TriggerPlugin(TMenuItem(Sender).Tag); end; ////////////////////////////////////////////////////////////////////// initialization msBetweenFrames := 0; end. //////////////////////////////////////////////////////////////////////