From 7c9a9bb5ca9f07da642ff19d2f70c3079b3e3678 Mon Sep 17 00:00:00 2001 From: yangkui <752544765@qq.com> Date: Fri, 2 Dec 2022 15:19:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/navclub/nes4j/app/CHRView.java | 151 ---------------- .../nes4j/app/{NES4J.java => Nes4j.java} | 6 +- .../nes4j/app/{ => assets}/FXResource.java | 2 +- .../nes4j/app/control/NesGameItem.java | 3 +- .../cn/navclub/nes4j/app/dialog/DHandle.java | 6 +- .../cn/navclub/nes4j/app/dialog/DPalette.java | 6 +- .../navclub/nes4j/app/view/DebuggerView.java | 6 +- .../cn/navclub/nes4j/app/view/GameWorld.java | 23 ++- app/src/main/java/module-info.java | 1 + .../nes4j/app/{ => assets}/css/DHandle.css | 0 .../app/{ => assets}/css/DebuggerView.css | 0 .../nes4j/app/{ => assets}/css/Nes4j.css | 0 .../app/{ => assets}/css/SystemPalette.css | 0 .../nes4j/app/{ => assets}/css/common.css | 0 .../nes4j/app/{ => assets}/img/bin.png | Bin .../nes4j/app/{ => assets}/img/delete.png | Bin .../nes4j/app/{ => assets}/img/game.png | Bin .../app/{ => assets}/img/handler/down.png | Bin .../app/{ => assets}/img/handler/left.png | Bin .../app/{ => assets}/img/handler/right.png | Bin .../nes4j/app/{ => assets}/img/handler/up.png | Bin .../nes4j/app/{ => assets}/img/icon.png | Bin .../nes4j/app/{ => assets}/img/launcher.png | Bin .../nes4j/app/{ => assets}/img/rrun.png | Bin .../nes4j/app/{ => assets}/img/run.png | Bin .../nes4j/app/{ => assets}/img/stepinto.png | Bin .../nes4j/app/{ => assets}/img/stepout.png | Bin .../{ => assets}/language/nes4j.properties | 0 .../language/nes4j_zh_CN.properties | 0 .../cn/navclub/nes4j/bin/core/Cartridge.java | 20 ++- .../java/cn/navclub/nes4j/bin/core/PPU.java | 5 +- .../nes4j/bin/core/impl/CTRegister.java | 11 +- .../navclub/nes4j/bin/enums/NameMirror.java | 2 + .../navclub/nes4j/bin/enums/NameTMirror.java | 15 ++ .../cn/navclub/nes4j/bin/screen/Camera.java | 15 ++ .../cn/navclub/nes4j/bin/screen/Render.java | 40 ++--- .../cn/navclub/nes4j/bin/util/PPUUtil.java | 162 ++++++++++++++++++ 37 files changed, 257 insertions(+), 217 deletions(-) delete mode 100644 app/src/main/java/cn/navclub/nes4j/app/CHRView.java rename app/src/main/java/cn/navclub/nes4j/app/{NES4J.java => Nes4j.java} (97%) rename app/src/main/java/cn/navclub/nes4j/app/{ => assets}/FXResource.java (94%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/css/DHandle.css (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/css/DebuggerView.css (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/css/Nes4j.css (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/css/SystemPalette.css (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/css/common.css (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/bin.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/delete.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/game.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/handler/down.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/handler/left.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/handler/right.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/handler/up.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/icon.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/launcher.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/rrun.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/run.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/stepinto.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/img/stepout.png (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/language/nes4j.properties (100%) rename app/src/main/resources/cn/navclub/nes4j/app/{ => assets}/language/nes4j_zh_CN.properties (100%) create mode 100644 bin/src/main/java/cn/navclub/nes4j/bin/enums/NameTMirror.java create mode 100644 bin/src/main/java/cn/navclub/nes4j/bin/util/PPUUtil.java diff --git a/app/src/main/java/cn/navclub/nes4j/app/CHRView.java b/app/src/main/java/cn/navclub/nes4j/app/CHRView.java deleted file mode 100644 index 563b679..0000000 --- a/app/src/main/java/cn/navclub/nes4j/app/CHRView.java +++ /dev/null @@ -1,151 +0,0 @@ -package cn.navclub.nes4j.app; - -import cn.navclub.nes4j.app.control.Tile; -import cn.navclub.nes4j.app.util.OSUtil; -import cn.navclub.nes4j.bin.core.Cartridge; -import cn.navclub.nes4j.bin.util.PatternTableUtil; -import javafx.application.Application; -import javafx.application.Platform; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Scene; -import javafx.scene.control.*; -import javafx.scene.image.PixelFormat; -import javafx.scene.image.WritableImage; -import javafx.scene.input.TransferMode; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.FlowPane; -import javafx.scene.paint.Color; -import javafx.stage.Stage; - -import java.io.File; -import java.nio.ByteBuffer; -import java.util.concurrent.CompletableFuture; - -/** - * ch-rom数据可视化查看程序 - */ -public class CHRView extends Application { - private static final Color TRANSPARENT = new Color(102 / 255.0, 102 / 255.0, 102 / 255.0, .5); - - private FlowPane flowPane; - - @Override - public void start(Stage stage) { - - this.flowPane = new FlowPane(); - - this.flowPane.setHgap(10); - this.flowPane.setVgap(10); - this.flowPane.setAlignment(Pos.CENTER); - - - var scrollPane = new ScrollPane(); - scrollPane.setFitToWidth(true); - scrollPane.setFitToHeight(true); - scrollPane.setContent(this.flowPane); - scrollPane.setPadding(new Insets(10)); - - this.flowPane.prefWidthProperty().bind(scrollPane.widthProperty()); - - var menuBar = new MenuBar(); - - var menu = new Menu("File"); - var open = new MenuItem("Open"); - var setting = new MenuItem("Setting"); - open.setOnAction(e -> { - var optional = OSUtil.chooseFile(stage, "NES rom file", "*.nes", "*.NES"); - if (optional.isEmpty()) { - return; - } - this.loadNESFile(optional.get()); - }); - menu.getItems().addAll(open, setting); - menuBar.getMenus().add(menu); - - var root = new BorderPane(); - - root.setTop(menuBar); - root.setCenter(scrollPane); - - var scene = new Scene(root); - scene.setOnDragOver(event -> { - event.acceptTransferModes(TransferMode.ANY); - event.consume(); - }); - scene.setOnDragDropped(event -> { - var board = event.getDragboard(); - var list = board - .getFiles() - .stream() - .filter(it -> it.getName().endsWith(".nes")) - .toList(); - if (list.isEmpty()) { - return; - } - this.loadNESFile(list.get(0)); - event.consume(); - }); - stage.setWidth(600); - stage.setHeight(800); - stage.setScene(scene); - stage.setTitle("CHView"); - stage.getIcons().add(FXResource.loadImage("bin.png")); - scene.getStylesheets().add(FXResource.loadStyleSheet("common.css")); - - stage.show(); - } - - public void loadNESFile(File file) { - //Clear already exist tiles - this.flowPane.getChildren().clear(); - var future = CompletableFuture.supplyAsync(() -> new Cartridge(file)); - future.whenComplete((nesFile, t) -> { - if (t != null) { - t.printStackTrace(); - return; - } - var ch = nesFile.getChrom(); - var len = ch.length / 16; - for (int i = 0; i < len; i++) { - var k = i * 16; - var arr = new byte[0x10]; - System.arraycopy(ch, k, arr, 0, 0x10); - this.renderTile(PatternTableUtil.tiles(arr), i); - } - }); - } - - public void renderTile(byte[][] tile, int index) { - var w = 15; - var h = 15; - var image = new WritableImage(w * 8, h * 8); - var writer = image.getPixelWriter(); - var pixelFormat = PixelFormat.getByteBgraInstance(); - for (int i = 0; i < tile.length; i++) { - for (int j = 0; j < tile[i].length; j++) { - var value = tile[i][j]; - var color = switch (value) { - case 1 -> Color.RED; - case 2 -> Color.GREEN; - case 3 -> Color.BLUE; - default -> TRANSPARENT; - }; - var arr = new byte[w * h]; - for (int k = 0; k < (w * h); k++) { - arr[k] = (byte) Math.round(color.getBlue() * 0xff); - arr[k + 1] = (byte) Math.round(color.getGreen() * 0xff); - arr[k + 2] = (byte) Math.round(color.getRed() * 0xff); - arr[k + 3] = (byte) Math.round(color.getOpacity() * 0xff); - k += 4; - } - writer.setPixels(j * w, i * h, w, h, pixelFormat, ByteBuffer.wrap(arr), 0); - } - } - Platform.runLater(() -> this.flowPane.getChildren().add(new Tile(image, index))); - } - - public static void main(String[] args) { - launch(args); - } -} diff --git a/app/src/main/java/cn/navclub/nes4j/app/NES4J.java b/app/src/main/java/cn/navclub/nes4j/app/Nes4j.java similarity index 97% rename from app/src/main/java/cn/navclub/nes4j/app/NES4J.java rename to app/src/main/java/cn/navclub/nes4j/app/Nes4j.java index e4700aa..bac7f49 100644 --- a/app/src/main/java/cn/navclub/nes4j/app/NES4J.java +++ b/app/src/main/java/cn/navclub/nes4j/app/Nes4j.java @@ -1,11 +1,11 @@ package cn.navclub.nes4j.app; +import cn.navclub.nes4j.app.assets.FXResource; import cn.navclub.nes4j.app.config.NESConfig; import cn.navclub.nes4j.app.control.NesGameItem; import cn.navclub.nes4j.app.dialog.DHandle; import cn.navclub.nes4j.app.util.JsonUtil; import cn.navclub.nes4j.app.util.StrUtil; -import cn.navclub.nes4j.bin.Player; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.ListView; @@ -23,7 +23,7 @@ import java.util.ResourceBundle; @Slf4j -public class NES4J extends Application { +public class Nes4j extends Application { public static final ResourceBundle RESOURCE_BUNDLE; private static final String DEFAULT_CONFIG_PATH = "config/config.json"; @@ -33,7 +33,7 @@ public class NES4J extends Application { static { System.loadLibrary("nes4j"); System.setProperty("java.util.PropertyResourceBundle.encoding", "UTF-8"); - RESOURCE_BUNDLE = ResourceBundle.getBundle("cn.navclub.nes4j.app.language.nes4j"); + RESOURCE_BUNDLE = ResourceBundle.getBundle("cn.navclub.nes4j.app.assets.language.nes4j"); } private ListView listView; diff --git a/app/src/main/java/cn/navclub/nes4j/app/FXResource.java b/app/src/main/java/cn/navclub/nes4j/app/assets/FXResource.java similarity index 94% rename from app/src/main/java/cn/navclub/nes4j/app/FXResource.java rename to app/src/main/java/cn/navclub/nes4j/app/assets/FXResource.java index e96fa20..16a025a 100644 --- a/app/src/main/java/cn/navclub/nes4j/app/FXResource.java +++ b/app/src/main/java/cn/navclub/nes4j/app/assets/FXResource.java @@ -1,4 +1,4 @@ -package cn.navclub.nes4j.app; +package cn.navclub.nes4j.app.assets; import javafx.scene.image.Image; diff --git a/app/src/main/java/cn/navclub/nes4j/app/control/NesGameItem.java b/app/src/main/java/cn/navclub/nes4j/app/control/NesGameItem.java index ed1cf03..5a2ecb9 100644 --- a/app/src/main/java/cn/navclub/nes4j/app/control/NesGameItem.java +++ b/app/src/main/java/cn/navclub/nes4j/app/control/NesGameItem.java @@ -1,9 +1,8 @@ package cn.navclub.nes4j.app.control; -import cn.navclub.nes4j.app.FXResource; +import cn.navclub.nes4j.app.assets.FXResource; import cn.navclub.nes4j.app.util.StrUtil; import cn.navclub.nes4j.app.view.GameWorld; -import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.Tooltip; diff --git a/app/src/main/java/cn/navclub/nes4j/app/dialog/DHandle.java b/app/src/main/java/cn/navclub/nes4j/app/dialog/DHandle.java index 65ea3c7..d62639f 100644 --- a/app/src/main/java/cn/navclub/nes4j/app/dialog/DHandle.java +++ b/app/src/main/java/cn/navclub/nes4j/app/dialog/DHandle.java @@ -1,7 +1,7 @@ package cn.navclub.nes4j.app.dialog; -import cn.navclub.nes4j.app.FXResource; -import cn.navclub.nes4j.app.NES4J; +import cn.navclub.nes4j.app.assets.FXResource; +import cn.navclub.nes4j.app.Nes4j; import cn.navclub.nes4j.app.model.KeyMapper; import javafx.geometry.Pos; import javafx.scene.control.*; @@ -114,7 +114,7 @@ public DHandle(final KeyMapper[] mappers) { this.initTableView(); - this.setTitle(NES4J.localeValue("nes4j.handle", true)); + this.setTitle(Nes4j.localeValue("nes4j.handle", true)); } private void initTableView() { diff --git a/app/src/main/java/cn/navclub/nes4j/app/dialog/DPalette.java b/app/src/main/java/cn/navclub/nes4j/app/dialog/DPalette.java index 8ef9f7b..83b0b19 100644 --- a/app/src/main/java/cn/navclub/nes4j/app/dialog/DPalette.java +++ b/app/src/main/java/cn/navclub/nes4j/app/dialog/DPalette.java @@ -1,8 +1,8 @@ package cn.navclub.nes4j.app.dialog; -import cn.navclub.nes4j.app.FXResource; +import cn.navclub.nes4j.app.assets.FXResource; -import cn.navclub.nes4j.app.NES4J; +import cn.navclub.nes4j.app.Nes4j; import javafx.scene.control.*; import javafx.scene.layout.GridPane; import javafx.scene.layout.VBox; @@ -50,7 +50,7 @@ public DPalette(int[][] palette) { this.getDialogPane().getStylesheets().add(STYLE_SHEET); this.getDialogPane().getButtonTypes().addAll(ButtonType.APPLY, ButtonType.CANCEL); - this.setTitle(NES4J.localeValue("nes4j.palette", true)); + this.setTitle(Nes4j.localeValue("nes4j.palette", true)); } private void initPalette() { diff --git a/app/src/main/java/cn/navclub/nes4j/app/view/DebuggerView.java b/app/src/main/java/cn/navclub/nes4j/app/view/DebuggerView.java index 99a3457..dc7421c 100644 --- a/app/src/main/java/cn/navclub/nes4j/app/view/DebuggerView.java +++ b/app/src/main/java/cn/navclub/nes4j/app/view/DebuggerView.java @@ -1,7 +1,7 @@ package cn.navclub.nes4j.app.view; -import cn.navclub.nes4j.app.FXResource; -import cn.navclub.nes4j.app.NES4J; +import cn.navclub.nes4j.app.assets.FXResource; +import cn.navclub.nes4j.app.Nes4j; import cn.navclub.nes4j.app.control.BreakLine; import cn.navclub.nes4j.app.control.CPUControlPane; import cn.navclub.nes4j.app.control.PPUControlPane; @@ -53,7 +53,7 @@ public DebuggerView(final Window owner) { rrun.setTooltip(new Tooltip("re-run")); stepOut.setTooltip(new Tooltip("step out")); stepInto.setTooltip(new Tooltip("step into")); - run.setTooltip(new Tooltip(NES4J.localeValue("nes4j.run"))); + run.setTooltip(new Tooltip(Nes4j.localeValue("nes4j.run"))); run.setGraphic(new ImageView(FXResource.loadImage("run.png"))); rrun.setGraphic(new ImageView(FXResource.loadImage("rrun.png"))); diff --git a/app/src/main/java/cn/navclub/nes4j/app/view/GameWorld.java b/app/src/main/java/cn/navclub/nes4j/app/view/GameWorld.java index c68a668..99d56ab 100644 --- a/app/src/main/java/cn/navclub/nes4j/app/view/GameWorld.java +++ b/app/src/main/java/cn/navclub/nes4j/app/view/GameWorld.java @@ -1,7 +1,7 @@ package cn.navclub.nes4j.app.view; -import cn.navclub.nes4j.app.FXResource; -import cn.navclub.nes4j.app.NES4J; +import cn.navclub.nes4j.app.assets.FXResource; +import cn.navclub.nes4j.app.Nes4j; import cn.navclub.nes4j.app.audio.NativePlayer; import cn.navclub.nes4j.app.dialog.DPalette; import cn.navclub.nes4j.app.event.GameEventWrap; @@ -33,7 +33,6 @@ import java.io.File; import java.nio.ByteBuffer; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.LinkedBlockingDeque; public class GameWorld extends Stage { @@ -65,14 +64,14 @@ public GameWorld(final File file) { this.eventQueue = new LinkedBlockingDeque<>(); this.debuggerView = new DebuggerView(this); - var view = new Menu(NES4J.localeValue("nes4j.view")); - var tool = new Menu(NES4J.localeValue("nes4j.tool")); - var emulator = new Menu(NES4J.localeValue("nes4j.emulator")); + var view = new Menu(Nes4j.localeValue("nes4j.view")); + var tool = new Menu(Nes4j.localeValue("nes4j.tool")); + var emulator = new Menu(Nes4j.localeValue("nes4j.emulator")); - var debug = new MenuItem(NES4J.localeValue("nes4j.debug")); - var softRest = new MenuItem(NES4J.localeValue("nes4j.reset")); - var pausePlay = new MenuItem(NES4J.localeValue("nes4j.pplay")); - var palette = new MenuItem(NES4J.localeValue("nes4j.palette")); + var debug = new MenuItem(Nes4j.localeValue("nes4j.debug")); + var softRest = new MenuItem(Nes4j.localeValue("nes4j.reset")); + var pausePlay = new MenuItem(Nes4j.localeValue("nes4j.pplay")); + var palette = new MenuItem(Nes4j.localeValue("nes4j.palette")); palette.setOnAction(this::systemPalette); @@ -124,7 +123,7 @@ public GameWorld(final File file) { if (!(eventType == KeyEvent.KEY_PRESSED || eventType == KeyEvent.KEY_RELEASED)) { return; } - for (KeyMapper keyMapper : NES4J.config.getMapper()) { + for (KeyMapper keyMapper : Nes4j.config.getMapper()) { if (keyMapper.getKeyCode() == code) { try { this.eventQueue.put(new GameEventWrap(eventType, keyMapper.getButton())); @@ -187,7 +186,7 @@ private void dispose(Throwable t) { } if (t != null) { var dialog = new ExceptionDialog(t); - dialog.setHeaderText(NES4J.localeValue("nes4j.game.error")); + dialog.setHeaderText(Nes4j.localeValue("nes4j.game.error")); dialog.showAndWait(); // this.close(); } diff --git a/app/src/main/java/module-info.java b/app/src/main/java/module-info.java index 2a074bf..227ac36 100644 --- a/app/src/main/java/module-info.java +++ b/app/src/main/java/module-info.java @@ -21,6 +21,7 @@ opens cn.navclub.nes4j.app.config to com.fasterxml.jackson.databind; opens cn.navclub.nes4j.app.model to javafx.base, com.fasterxml.jackson.databind; + opens cn.navclub.nes4j.app.assets; uses cn.navclub.nes4j.bin.Player; } \ No newline at end of file diff --git a/app/src/main/resources/cn/navclub/nes4j/app/css/DHandle.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DHandle.css similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/css/DHandle.css rename to app/src/main/resources/cn/navclub/nes4j/app/assets/css/DHandle.css diff --git a/app/src/main/resources/cn/navclub/nes4j/app/css/DebuggerView.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/DebuggerView.css similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/css/DebuggerView.css rename to app/src/main/resources/cn/navclub/nes4j/app/assets/css/DebuggerView.css diff --git a/app/src/main/resources/cn/navclub/nes4j/app/css/Nes4j.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/Nes4j.css similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/css/Nes4j.css rename to app/src/main/resources/cn/navclub/nes4j/app/assets/css/Nes4j.css diff --git a/app/src/main/resources/cn/navclub/nes4j/app/css/SystemPalette.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/SystemPalette.css similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/css/SystemPalette.css rename to app/src/main/resources/cn/navclub/nes4j/app/assets/css/SystemPalette.css diff --git a/app/src/main/resources/cn/navclub/nes4j/app/css/common.css b/app/src/main/resources/cn/navclub/nes4j/app/assets/css/common.css similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/css/common.css rename to app/src/main/resources/cn/navclub/nes4j/app/assets/css/common.css diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/bin.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/bin.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/bin.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/bin.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/delete.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/delete.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/delete.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/delete.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/game.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/game.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/game.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/game.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/handler/down.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/handler/down.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/handler/down.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/handler/down.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/handler/left.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/handler/left.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/handler/left.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/handler/left.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/handler/right.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/handler/right.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/handler/right.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/handler/right.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/handler/up.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/handler/up.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/handler/up.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/handler/up.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/icon.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/icon.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/icon.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/icon.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/launcher.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/launcher.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/launcher.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/launcher.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/rrun.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/rrun.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/rrun.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/rrun.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/run.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/run.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/run.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/run.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/stepinto.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/stepinto.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/stepinto.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/stepinto.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/img/stepout.png b/app/src/main/resources/cn/navclub/nes4j/app/assets/img/stepout.png similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/img/stepout.png rename to app/src/main/resources/cn/navclub/nes4j/app/assets/img/stepout.png diff --git a/app/src/main/resources/cn/navclub/nes4j/app/language/nes4j.properties b/app/src/main/resources/cn/navclub/nes4j/app/assets/language/nes4j.properties similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/language/nes4j.properties rename to app/src/main/resources/cn/navclub/nes4j/app/assets/language/nes4j.properties diff --git a/app/src/main/resources/cn/navclub/nes4j/app/language/nes4j_zh_CN.properties b/app/src/main/resources/cn/navclub/nes4j/app/assets/language/nes4j_zh_CN.properties similarity index 100% rename from app/src/main/resources/cn/navclub/nes4j/app/language/nes4j_zh_CN.properties rename to app/src/main/resources/cn/navclub/nes4j/app/assets/language/nes4j_zh_CN.properties diff --git a/bin/src/main/java/cn/navclub/nes4j/bin/core/Cartridge.java b/bin/src/main/java/cn/navclub/nes4j/bin/core/Cartridge.java index 5d64c9a..be68621 100644 --- a/bin/src/main/java/cn/navclub/nes4j/bin/core/Cartridge.java +++ b/bin/src/main/java/cn/navclub/nes4j/bin/core/Cartridge.java @@ -4,6 +4,7 @@ import cn.navclub.nes4j.bin.enums.NESFormat; import cn.navclub.nes4j.bin.enums.NMapper; import cn.navclub.nes4j.bin.enums.NameMirror; +import cn.navclub.nes4j.bin.enums.NameTMirror; import cn.navclub.nes4j.bin.util.ByteUtil; import cn.navclub.nes4j.bin.util.IOUtil; @@ -180,10 +181,7 @@ public Cartridge(byte[] buffer) { var flag7 = headers[7] & 0xff; var flag8 = headers[8] & 0xff; - - this.mirrors = NameMirror.values()[flag6 & 1]; - - var mapper = (flag7 & 0b1111_0000) | ((flag6 & 0b1111_0000) >> 4); + var mapper = (flag7 & 0xf0) | ((flag6 & 0xf0) >> 4); //NES2.0包含12位 if (this.format == NESFormat.NES_20) { @@ -196,6 +194,20 @@ public Cartridge(byte[] buffer) { this.mapper = NMapper.values()[mapper]; } + NameMirror mirrors; + if (((flag6 >> 3) & 0x01) == 1) { + mirrors = NameMirror.FOUR_SCREEN; + } else { + mirrors = NameMirror.values()[flag6 & 1]; + } + + //UNROM 512 uses %....1..0 to indicate a 1-screen board, and %....1..1 to indicate a 4-screen board. + if (this.mapper == NMapper.UX_ROM && mirrors == NameMirror.FOUR_SCREEN && (flag6 & 0x01) == 0) { + mirrors = NameMirror.SINGLE_SCREEN; + } + + this.mirrors = mirrors; + var trainSize = this.trainAreaSize(flag6); chrom = new byte[chSize]; diff --git a/bin/src/main/java/cn/navclub/nes4j/bin/core/PPU.java b/bin/src/main/java/cn/navclub/nes4j/bin/core/PPU.java index 80f97e4..e3a32f9 100644 --- a/bin/src/main/java/cn/navclub/nes4j/bin/core/PPU.java +++ b/bin/src/main/java/cn/navclub/nes4j/bin/core/PPU.java @@ -8,12 +8,11 @@ import cn.navclub.nes4j.bin.enums.MaskFlag; import cn.navclub.nes4j.bin.enums.NameMirror; import cn.navclub.nes4j.bin.enums.PStatus; +import cn.navclub.nes4j.bin.screen.Render; import cn.navclub.nes4j.bin.util.MathUtil; import lombok.Getter; import lombok.Setter; -import java.security.PublicKey; -import java.util.concurrent.atomic.AtomicBoolean; /** * PPU document @@ -108,6 +107,7 @@ public void tick() { if (this.scanLine >= 262) { this.nmi = false; this.scanLine = 0; + //Sprite 0 notify cpu VBL already end. this.status.clear(PStatus.V_BLANK_OCCUR, PStatus.SPRITE_ZERO_HIT); } } @@ -191,6 +191,7 @@ public void write(int address, byte b) { public byte readStatus() { var b = this.status.getBits(); + //Due to every read ppu status clear VBL so can't judge VBL whether end need use sprite zero this.status.clear(PStatus.V_BLANK_OCCUR); this.addr.reset(); this.scroll.reset(); diff --git a/bin/src/main/java/cn/navclub/nes4j/bin/core/impl/CTRegister.java b/bin/src/main/java/cn/navclub/nes4j/bin/core/impl/CTRegister.java index 3e1c3db..4f5286f 100644 --- a/bin/src/main/java/cn/navclub/nes4j/bin/core/impl/CTRegister.java +++ b/bin/src/main/java/cn/navclub/nes4j/bin/core/impl/CTRegister.java @@ -1,6 +1,7 @@ package cn.navclub.nes4j.bin.core.impl; import cn.navclub.nes4j.bin.core.SRegister; +import cn.navclub.nes4j.bin.enums.NameTMirror; import cn.navclub.nes4j.bin.enums.PControl; import lombok.extern.slf4j.Slf4j; @@ -36,14 +37,8 @@ public CTRegister() { /** * Get current name table address */ - public int nameTableAddr() { - return switch (this.bits & 0x03) { - case 0 -> 0x2000; - case 1 -> 0x2400; - case 2 -> 0x2800; - case 3 -> 0x2c00; - default -> 0; - }; + public NameTMirror nameTableAddr() { + return NameTMirror.values()[this.bits & 0x03]; } public int VRamIncrement() { diff --git a/bin/src/main/java/cn/navclub/nes4j/bin/enums/NameMirror.java b/bin/src/main/java/cn/navclub/nes4j/bin/enums/NameMirror.java index 86b2420..eaa9eef 100644 --- a/bin/src/main/java/cn/navclub/nes4j/bin/enums/NameMirror.java +++ b/bin/src/main/java/cn/navclub/nes4j/bin/enums/NameMirror.java @@ -3,4 +3,6 @@ public enum NameMirror { HORIZONTAL, VERTICAL, + SINGLE_SCREEN, + FOUR_SCREEN } diff --git a/bin/src/main/java/cn/navclub/nes4j/bin/enums/NameTMirror.java b/bin/src/main/java/cn/navclub/nes4j/bin/enums/NameTMirror.java new file mode 100644 index 0000000..5ab2478 --- /dev/null +++ b/bin/src/main/java/cn/navclub/nes4j/bin/enums/NameTMirror.java @@ -0,0 +1,15 @@ +package cn.navclub.nes4j.bin.enums; + + +public enum NameTMirror { + L1(0x2000), + L2(0x2400), + L3(0x2800), + L4(0x2c00); + + public final int address; + + NameTMirror(int address) { + this.address = address; + } +} diff --git a/bin/src/main/java/cn/navclub/nes4j/bin/screen/Camera.java b/bin/src/main/java/cn/navclub/nes4j/bin/screen/Camera.java index e7ddcfe..a92c5aa 100644 --- a/bin/src/main/java/cn/navclub/nes4j/bin/screen/Camera.java +++ b/bin/src/main/java/cn/navclub/nes4j/bin/screen/Camera.java @@ -1,5 +1,20 @@ package cn.navclub.nes4j.bin.screen; +/** + * Current camera visible range

+ * + * +++++++++++++++++++++++++++++++++++++++(x1/y1) + * + + + * + + + * + + + * + visible area + + * + + + * + + + * + + + * + + + * (x0/y0)+++++++++++++++++++++++++++++++++++++++ + * + */ public record Camera(int x0, int y0, int x1, int y1) { } diff --git a/bin/src/main/java/cn/navclub/nes4j/bin/screen/Render.java b/bin/src/main/java/cn/navclub/nes4j/bin/screen/Render.java index c332e54..b90923c 100644 --- a/bin/src/main/java/cn/navclub/nes4j/bin/screen/Render.java +++ b/bin/src/main/java/cn/navclub/nes4j/bin/screen/Render.java @@ -4,6 +4,7 @@ import cn.navclub.nes4j.bin.core.impl.CTRegister; import cn.navclub.nes4j.bin.enums.MaskFlag; import cn.navclub.nes4j.bin.enums.NameMirror; +import cn.navclub.nes4j.bin.util.PPUUtil; import lombok.Getter; public class Render { @@ -71,11 +72,6 @@ public void render(PPU ppu, Frame frame) { var background = mask.contain(MaskFlag.SHOW_BACKGROUND); //Render background if (background) { - var vram = ppu.getVram(); - var mirror = ppu.getMirrors(); - - var ctr = ppu.getControl(); - var nameTable = ctr.nameTableAddr(); var scrollX = ppu.getScroll().getX(); var scrollY = ppu.getScroll().getY(); @@ -83,17 +79,8 @@ public void render(PPU ppu, Frame frame) { var firstNameTable = new byte[0x400]; var secondNameTable = new byte[0x400]; - if ((mirror == NameMirror.VERTICAL && (nameTable == 0x2000 || nameTable == 0x2800)) - || (mirror == NameMirror.HORIZONTAL && (nameTable == 0x2000 || nameTable == 0x2400))) { - System.arraycopy(vram, 0, firstNameTable, 0, 0x400); - System.arraycopy(vram, 0x400, secondNameTable, 0, 0x400); - } else if ((mirror == NameMirror.VERTICAL && (nameTable == 0x2400 || nameTable == 0x2c00)) - || (mirror == NameMirror.HORIZONTAL && (nameTable == 0x2800 || nameTable == 0x2c00))) { - System.arraycopy(vram, 0x400, firstNameTable, 0, 0x400); - System.arraycopy(vram, 0, secondNameTable, 0, 0x400); - } else { - throw new RuntimeException("Not support mirror type:" + mirror); - } + //Fill fist and second name table + PPUUtil.fillNameTable(ppu, firstNameTable, secondNameTable); //Render first screen background @@ -144,7 +131,7 @@ public void render(PPU ppu, Frame frame) { var ctrl = ppu.getControl(); var size = ctrl.spriteSize(); - var tile = new byte[size + 8]; + var tile = new byte[16]; var bank = size == 0x08 ? ctrl.spritePattern8() : ctrl.spritePattern16(idx); @@ -218,22 +205,23 @@ private void renderNameTable(PPU ppu, Frame frame, byte[] nameTable, Camera came var bank = ppu.getControl().bkNamePatternTable(); - //渲染背景960个tile + var tile = new byte[16]; + + //渲染背景32*30=960个tile for (int i = 0; i < 0x3c0; i++) { var row = i / 32; var column = i % 32; var idx = nameTable[i] & 0xff; - var tile = new byte[16]; var offset = bank + idx * 16; System.arraycopy(ppu.getCh(), offset, tile, 0, 16); var palette = bgPalette(ppu, attrTable, column, row); for (int y = 0; y < 8; y++) { - var upper = Byte.toUnsignedInt(tile[y]); - var lower = Byte.toUnsignedInt(tile[y + 8]); + var left = tile[y] & 0xff; + var right = tile[y + 8] & 0xff; for (int x = 7; x >= 0; x--) { - var value = ((1 & lower) << 1) | (1 & upper); - upper >>= 1; - lower >>= 1; + var value = ((1 & right) << 1) | (1 & left); + left >>= 1; + right >>= 1; var rgb = switch (value) { case 0 -> sysPalette[ppu.getPaletteTable()[0]]; case 1 -> sysPalette[palette[1]]; @@ -246,7 +234,7 @@ private void renderNameTable(PPU ppu, Frame frame, byte[] nameTable, Camera came var py = row * 8 + y; var px = column * 8 + x; - //判断是否显示范围 + //判断是否相机显示范围 if (px >= camera.x0() && px < camera.x1() && py >= camera.y0() && py < camera.y1()) { frame.updatePixel(sx + px, sy + py, rgb); } @@ -254,4 +242,6 @@ private void renderNameTable(PPU ppu, Frame frame, byte[] nameTable, Camera came } } } + + } diff --git a/bin/src/main/java/cn/navclub/nes4j/bin/util/PPUUtil.java b/bin/src/main/java/cn/navclub/nes4j/bin/util/PPUUtil.java new file mode 100644 index 0000000..e81ef09 --- /dev/null +++ b/bin/src/main/java/cn/navclub/nes4j/bin/util/PPUUtil.java @@ -0,0 +1,162 @@ +package cn.navclub.nes4j.bin.util; + +import cn.navclub.nes4j.bin.core.PPU; +import cn.navclub.nes4j.bin.enums.MaskFlag; +import cn.navclub.nes4j.bin.enums.NameMirror; +import cn.navclub.nes4j.bin.enums.NameTMirror; + +public class PPUUtil { + /** + * + * + * The NES only has 2 KB to store name tables and attribute tables, allowing it to store two of + * each. However it can address up to four of each. Mirroring is used to allow it to do this. There + * are four types of mirroring which are described below, using abbreviations for logical name + * tables (those that can be addressed), L1 at $2000, L2 at $2400, L3 at $2800 and L4 at + * $2C00:

+ *
  • + * Horizontal mirroring maps L1 and L2 to the first physical name table and L3 and L4 to the + * second as shown in figure 3-4.

    + *
  • + * + * + * + * + * + * + * + * + * + *
    Name table 1Name table 1
    Name table 2Name table 2
    + * Figure 3-4. Horizontal mirroring. + *
  • + * Vertical mirroring maps L1 and L3 to the first physical name table and L2 and L4 to the + * second as shown in figure 3-5. + *
  • + * + * + * + * + * + * + * + * + * + *
    Name table 1Name table 2
    Name table 1Name table 2
    + * Figure 3-5. Vertical mirroring. + * + * @param ppu PPU instance + * @param t1 First name table + * @param t2 Second name table + */ + public static void fillNameTable(PPU ppu, byte[] t1, byte[] t2) { + var vram = ppu.getVram(); + var ctr = ppu.getControl(); + var nameTable = ctr.nameTableAddr(); + var mirror = ppu.getMirrors(); + + if (nameTable == NameTMirror.L1 + || ((mirror == NameMirror.HORIZONTAL && nameTable == NameTMirror.L2) + || (mirror == NameMirror.VERTICAL && nameTable == NameTMirror.L3))) { + System.arraycopy(vram, 0, t1, 0, 0x400); + System.arraycopy(vram, 0x400, t2, 0, 0x400); + } else if (nameTable == NameTMirror.L4 + || ((mirror == NameMirror.VERTICAL && nameTable == NameTMirror.L2) + || (mirror == NameMirror.HORIZONTAL && nameTable == NameTMirror.L3))) { + System.arraycopy(vram, 0, t2, 0, 0x400); + System.arraycopy(vram, 0x400, t1, 0, 0x400); + } else { + throw new RuntimeException("Not satisfy name table fill condition."); + } + } + + // + // + // Sprite zero hits + // Sprites are conventionally numbered 0 to 63. Sprite 0 is the sprite controlled by OAM addresses $00-$03, sprite 1 is controlled by $04-$07, ..., and sprite 63 is controlled by $FC-$FF. + // + // While the PPU is drawing the picture, when an opaque pixel of sprite 0 overlaps an opaque pixel of the background, this is a sprite zero hit. The PPU detects this condition and sets bit 6 of PPUSTATUS ($2002) to 1 starting at this pixel, letting the CPU know how far along the PPU is in drawing the picture. + // + // Sprite 0 hit does not happen: + // + // If background or sprite rendering is disabled in PPUMASK ($2001) + // At x=0 to x=7 if the left-side clipping window is enabled (if bit 2 or bit 1 of PPUMASK is 0). + // At x=255, for an obscure reason related to the pixel pipeline. + // At any pixel where the background or sprite pixel is transparent (2-bit color index from the CHR pattern is %00). + // If sprite 0 hit has already occurred this frame. Bit 6 of PPUSTATUS ($2002) is cleared to 0 at dot 1 of the pre-render line. This means only the first sprite 0 hit in a frame can be detected. + // Sprite 0 hit happens regardless of the following: + // + // Sprite priority. Sprite 0 can still hit the background from behind. + // The pixel colors. Only the CHR pattern bits are relevant, not the actual rendered colors, and any CHR color index except %00 is considered opaque. + // The palette. The contents of the palette are irrelevant to sprite 0 hits. For example: a black ($0F) sprite pixel can hit a black ($0F) background as long as neither is the transparent color index %00. + // The PAL PPU blanking on the left and right edges at x=0, x=1, and x=254 (see Overscan). + // + // + public static boolean checkSpriteZeroHit(PPU ppu) { + var mask = ppu.getMask(); + // + // Set when a nonzero pixel of sprite 0 overlaps a nonzero background pixel; + // Sprite 0 hit does not trigger in any area where the background or sprites are hidden. + // + if (!mask.contain(MaskFlag.SHOW_SPRITES) || !mask.contain(MaskFlag.SHOW_BACKGROUND)) { + return false; + } + + var oam = ppu.getOam(); + var tx = oam[3] & 0xff; + var ty = oam[0] & 0xff; + var idx = oam[1] & 0xff; + var attr = oam[2] & 0xff; + + var vf = (attr >> 7 & 1) == 1; + var hf = (attr >> 6 & 1) == 1; + var ctrl = ppu.getControl(); + var scroll = ppu.getScroll(); + + var size = ctrl.spriteSize(); + var bank = size == 0x08 ? ctrl.spritePattern8() : ctrl.spritePattern16(idx); + + if (size == 0x10) { + idx = idx & 0xfe; + } + var tile = new byte[16]; + boolean hit = false; + for (int k = 8; k <= size; k += 8) { + System.arraycopy(ppu.getCh(), bank + idx * 16, tile, 0, 16); + for (int y = 0; y < 8; y++) { + var l = tile[y] & 0xff; + var r = tile[y + 8]; + for (int x = 7; x >= 0; x--) { + var value = ((r & 0x01) << 1) | l & 0x01; + + l >>= 1; + r >>= 1; + + //Detect sprite zero opaque pixels + if (value == 0) { + continue; + } + final int x0; + final int y0; + if (!hf && !vf) { + x0 = tx + x; + y0 = ty + y; + } else if (hf && !vf) { + x0 = tx + 7 - x; + y0 = ty + y; + } else if (!hf && vf) { + x0 = tx + x; + y0 = ty + 7 - y; + } else { + x0 = tx + 7 - x; + y0 = ty + 7 - y; + } + //Check x0 and y0 position background + + } + } + ty += 8; + } + return hit; + } +}