rubyboy 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/rubyboy/lcd.rb CHANGED
@@ -7,8 +7,9 @@ module Rubyboy
7
7
  SCREEN_WIDTH = 160
8
8
  SCREEN_HEIGHT = 144
9
9
  SCALE = 3
10
+ SDL_EVENT_SIZE = 56
10
11
 
11
- def initialize
12
+ def initialize(vsync: nil)
12
13
  raise SDL.GetError() if SDL.InitSubSystem(SDL::INIT_VIDEO) != 0
13
14
 
14
15
  @buffer = FFI::MemoryPointer.new(:uint32, SCREEN_WIDTH * SCREEN_HEIGHT)
@@ -16,11 +17,19 @@ module Rubyboy
16
17
 
17
18
  raise SDL.GetError() if @window.null?
18
19
 
19
- @renderer = SDL.CreateRenderer(@window, -1, 0)
20
+ begin
21
+ @renderer = SDL.CreateRenderer(@window, -1, 0)
22
+ raise SDL.GetError() if @renderer.null?
23
+ raise SDL.GetError() if !vsync.nil? && SDL.RenderSetVSync(@renderer, vsync ? 1 : 0) != 0
24
+ rescue StandardError
25
+ close_window
26
+ raise
27
+ end
28
+
20
29
  SDL.SetHint('SDL_HINT_RENDER_SCALE_QUALITY', '2')
21
30
  SDL.RenderSetLogicalSize(@renderer, SCREEN_WIDTH * SCALE, SCREEN_HEIGHT * SCALE)
22
31
  @texture = SDL.CreateTexture(@renderer, SDL::PIXELFORMAT_ABGR8888, 1, SCREEN_WIDTH, SCREEN_HEIGHT)
23
- @event = FFI::MemoryPointer.new(:pointer)
32
+ @event = FFI::MemoryPointer.new(:uint8, SDL_EVENT_SIZE)
24
33
  end
25
34
 
26
35
  def draw(framebuffer)
data/lib/rubyboy/ram.rb CHANGED
@@ -4,8 +4,8 @@ module Rubyboy
4
4
  class Ram
5
5
  attr_accessor :eram, :wram1, :wram2, :hram
6
6
 
7
- def initialize
8
- @eram = Array.new(0x2000, 0)
7
+ def initialize(rom)
8
+ @eram = Array.new(rom.ram_size_bytes, 0)
9
9
  @wram1 = Array.new(0x1000, 0)
10
10
  @wram2 = Array.new(0x1000, 0)
11
11
  @hram = Array.new(0x80, 0)
data/lib/rubyboy/rom.rb CHANGED
@@ -10,11 +10,34 @@ module Rubyboy
10
10
  BB BB 67 63 6E 0E EC CC DD DC 99 9F BB B9 33 3E
11
11
  ].map(&:hex).freeze
12
12
 
13
+ RAM_SIZE_BYTES = {
14
+ 0x00 => 0,
15
+ 0x01 => 2 * 1024,
16
+ 0x02 => 8 * 1024,
17
+ 0x03 => 32 * 1024,
18
+ 0x04 => 128 * 1024,
19
+ 0x05 => 64 * 1024
20
+ }.freeze
21
+
22
+ BATTERY_CARTRIDGE_TYPES = [0x03, 0x06, 0x09, 0x0d, 0x0f, 0x10, 0x13, 0x1b, 0x1e, 0x22, 0xff].freeze
23
+
13
24
  def initialize(data)
14
25
  @data = data
15
26
  load_data
16
27
  end
17
28
 
29
+ def ram_size_bytes
30
+ RAM_SIZE_BYTES.fetch(@ram_size, 0)
31
+ end
32
+
33
+ def battery?
34
+ BATTERY_CARTRIDGE_TYPES.include?(@cartridge_type)
35
+ end
36
+
37
+ def global_checksum_value
38
+ (@global_checksum[0] << 8) | @global_checksum[1]
39
+ end
40
+
18
41
  private
19
42
 
20
43
  def load_data
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ class SaveFile
5
+ attr_reader :path
6
+
7
+ def initialize(path)
8
+ @path = path
9
+ end
10
+
11
+ def read(expected_size)
12
+ return nil unless File.exist?(@path)
13
+
14
+ bytes = File.binread(@path).bytes
15
+ if bytes.size != expected_size
16
+ warn "[rubyboy] save file size mismatch (expected #{expected_size} bytes, got #{bytes.size}); ignoring #{@path}"
17
+ return nil
18
+ end
19
+
20
+ bytes
21
+ rescue StandardError => e
22
+ warn "[rubyboy] failed to read save file #{@path}: #{e.message}"
23
+ nil
24
+ end
25
+
26
+ def write(bytes)
27
+ tmp_path = "#{@path}.tmp"
28
+ File.binwrite(tmp_path, bytes.pack('C*'))
29
+ File.rename(tmp_path, @path)
30
+ true
31
+ rescue StandardError => e
32
+ warn "[rubyboy] failed to write save file #{@path}: #{e.message}"
33
+ false
34
+ end
35
+ end
36
+ end
data/lib/rubyboy/sdl.rb CHANGED
@@ -25,6 +25,17 @@ module Rubyboy
25
25
  SDL_SCANCODE_K = 14
26
26
  SDL_SCANCODE_U = 24
27
27
  SDL_SCANCODE_I = 12
28
+ SDL_SCANCODE_1 = 30
29
+ SDL_SCANCODE_2 = 31
30
+ SDL_SCANCODE_3 = 32
31
+ SDL_SCANCODE_4 = 33
32
+ SDL_SCANCODE_5 = 34
33
+ SDL_SCANCODE_6 = 35
34
+ SDL_SCANCODE_7 = 36
35
+ SDL_SCANCODE_8 = 37
36
+ SDL_SCANCODE_9 = 38
37
+ SDL_SCANCODE_0 = 39
38
+ SDL_SCANCODE_LSHIFT = 225
28
39
 
29
40
  attach_function :Init, 'SDL_Init', [:uint32], :int
30
41
  attach_function :InitSubSystem, 'SDL_InitSubSystem', [:uint32], :int
@@ -42,6 +53,7 @@ module Rubyboy
42
53
  attach_function :GetKeyboardState, 'SDL_GetKeyboardState', [:pointer], :pointer
43
54
  attach_function :SetHint, 'SDL_SetHint', %i[string string], :int
44
55
  attach_function :RenderSetLogicalSize, 'SDL_RenderSetLogicalSize', %i[pointer int int], :int
56
+ attach_function :RenderSetVSync, 'SDL_RenderSetVSync', %i[pointer int], :int
45
57
  attach_function :SetWindowTitle, 'SDL_SetWindowTitle', %i[pointer string], :void
46
58
  attach_function :RaiseWindow, 'SDL_RaiseWindow', [:pointer], :void
47
59
  attach_function :GetError, 'SDL_GetError', [], :string
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubyboy
4
+ class StateFile
5
+ MAGIC = 'RBYSTATE'
6
+ FORMAT_VERSION = 1
7
+ HEADER_SIZE = 16
8
+
9
+ def self.write(path, rom:)
10
+ payload = Marshal.dump(yield)
11
+ header = MAGIC.b + [FORMAT_VERSION, rom.global_checksum_value, 0].pack('L<S<S<')
12
+ tmp_path = "#{path}.tmp"
13
+ File.binwrite(tmp_path, header + payload)
14
+ File.rename(tmp_path, path)
15
+ true
16
+ rescue StandardError => e
17
+ warn "[rubyboy] failed to write state file #{path}: #{e.message}"
18
+ false
19
+ end
20
+
21
+ def self.read(path, rom:)
22
+ return warn_and_false("state file does not exist: #{path}") unless File.exist?(path)
23
+
24
+ data = File.binread(path)
25
+ return warn_and_false("state file is too small: #{path}") if data.bytesize < HEADER_SIZE
26
+
27
+ magic = data.byteslice(0, MAGIC.bytesize)
28
+ return warn_and_false("invalid state file magic: #{path}") unless magic == MAGIC
29
+
30
+ version, checksum = data.byteslice(8, 6).unpack('L<S<')
31
+ return warn_and_false("unsupported state file version #{version} (expected #{FORMAT_VERSION})") unless version == FORMAT_VERSION
32
+ return warn_and_false("ROM checksum mismatch for state file #{path}") unless checksum == rom.global_checksum_value
33
+
34
+ # State files are local emulator snapshots written by this process.
35
+ # rubocop:disable Security/MarshalLoad
36
+ yield Marshal.load(data.byteslice(HEADER_SIZE..))
37
+ # rubocop:enable Security/MarshalLoad
38
+ true
39
+ rescue StandardError => e
40
+ warn "[rubyboy] failed to read state file #{path}: #{e.message}"
41
+ false
42
+ end
43
+
44
+ def self.warn_and_false(message)
45
+ warn "[rubyboy] #{message}"
46
+ false
47
+ end
48
+ private_class_method :warn_and_false
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubyboy
4
- VERSION = '1.5.0'
4
+ VERSION = '1.6.0'
5
5
  end
data/lib/rubyboy.rb CHANGED
@@ -9,8 +9,11 @@ require_relative 'rubyboy/emulator'
9
9
  require_relative 'rubyboy/ppu'
10
10
  require_relative 'rubyboy/rom'
11
11
  require_relative 'rubyboy/ram'
12
+ require_relative 'rubyboy/save_file'
13
+ require_relative 'rubyboy/state_file'
12
14
  require_relative 'rubyboy/timer'
13
15
  require_relative 'rubyboy/lcd'
14
16
  require_relative 'rubyboy/joypad'
15
17
  require_relative 'rubyboy/interrupt'
16
18
  require_relative 'rubyboy/cartridge/factory'
19
+ require_relative 'rubyboy/hardware_state'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyboy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sacckey
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-09 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ffi
@@ -29,6 +29,20 @@ dependencies:
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
31
  version: 1.16.3
32
+ - !ruby/object:Gem::Dependency
33
+ name: json
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '2.8'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '2.8'
32
46
  executables:
33
47
  - rubyboy
34
48
  - rubyboy-bench
@@ -90,6 +104,7 @@ files:
90
104
  - lib/rubyboy/emulator.rb
91
105
  - lib/rubyboy/emulator_headless.rb
92
106
  - lib/rubyboy/emulator_wasm.rb
107
+ - lib/rubyboy/hardware_state.rb
93
108
  - lib/rubyboy/interrupt.rb
94
109
  - lib/rubyboy/joypad.rb
95
110
  - lib/rubyboy/lcd.rb
@@ -100,7 +115,9 @@ files:
100
115
  - lib/rubyboy/raylib/raylib_loader.rb
101
116
  - lib/rubyboy/registers.rb
102
117
  - lib/rubyboy/rom.rb
118
+ - lib/rubyboy/save_file.rb
103
119
  - lib/rubyboy/sdl.rb
120
+ - lib/rubyboy/state_file.rb
104
121
  - lib/rubyboy/timer.rb
105
122
  - lib/rubyboy/version.rb
106
123
  - lib/stackprof.rb
@@ -133,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
150
  - !ruby/object:Gem::Version
134
151
  version: '0'
135
152
  requirements: []
136
- rubygems_version: 3.6.2
153
+ rubygems_version: 4.0.20
137
154
  specification_version: 4
138
155
  summary: A Game Boy emulator written in Ruby
139
156
  test_files: []