reading config from config.json

This commit is contained in:
minjaesong
2022-11-02 22:03:47 +09:00
parent 889df7a593
commit 3d10f9338a
7 changed files with 535 additions and 23 deletions

View File

@@ -71,13 +71,13 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe
val loadedClassInstance = loadedClassConstructor.newInstance("./assets", vm)
gpu = (loadedClassInstance as GraphicsAdapter)
vm.getIO().blockTransferPorts[0].attachDevice(TestDiskDrive(vm, 0, File(loaderInfo.diskPath)))
vm.getIO().blockTransferPorts[0].attachDevice(TestDiskDrive(vm, 0, loaderInfo.diskPath))
vm.peripheralTable[1] = PeripheralEntry(
gpu,
GraphicsAdapter.VRAM_SIZE,
16,
0
// GraphicsAdapter.VRAM_SIZE,
// 16,
// 0
)
vm.getPrintStream = { gpu!!.getPrintStream() }
@@ -99,9 +99,9 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe
vm.peripheralTable[port] = PeripheralEntry(
loadedClassInstance as PeriBase,
peri.memsize,
peri.mmioSize,
peri.interruptCount
// peri.memsize,
// peri.mmioSize,
// peri.interruptCount
)
}

View File

@@ -0,0 +1,102 @@
package net.torvald.terrarum
/**
* Created by minjaesong on 2015-12-30.
*/
open class KVHashMap {
constructor() {
hashMap = HashMap<String, Any>()
}
protected constructor(newMap: HashMap<String, Any>) {
hashMap = newMap
}
var hashMap: HashMap<String, Any>
/**
* Add key-value pair to the configuration table.
* If key does not exist on the table, new key will be generated.
* If key already exists, the value will be overwritten.
* @param key case insensitive
* *
* @param value
*/
open operator fun set(key: String, value: Any) {
hashMap.put(key.toLowerCase(), value)
}
/**
* Get value using key from configuration table.
* @param key case insensitive
* *
* @return Object value
*/
operator fun get(key: String): Any? {
return hashMap[key.toLowerCase()]
}
fun getAsInt(key: String): Int? {
val value = get(key)
if (value == null) return null
return value as Int
}
fun getAsDouble(key: String): Double? {
val value = get(key)
if (value == null) return null
if (value is Int)
return value.toDouble()
return value as Double
}
fun getAsFloat(key: String): Float? {
val value = get(key)
if (value == null) return null
if (value is Float) return value as Float
return getAsDouble(key)?.toFloat()
}
fun getAsString(key: String): String? {
val value = get(key)
if (value == null) return null
return value as String
}
fun getAsBoolean(key: String): Boolean? {
val value = get(key)
if (value == null) return null
return value as Boolean
}
fun hasKey(key: String) = hashMap.containsKey(key)
val keySet: Set<Any>
get() = hashMap.keys
open fun remove(key: String) {
if (hashMap[key] != null) {
hashMap.remove(key, hashMap[key]!!)
}
}
open fun clone(): KVHashMap {
val cloneOfMap = hashMap.clone() as HashMap<String, Any>
return KVHashMap(cloneOfMap)
}
}

View File

@@ -0,0 +1,69 @@
package net.torvald.terrarum.serialise
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonValue
import com.badlogic.gdx.utils.JsonWriter
import net.torvald.terrarum.KVHashMap
import net.torvald.terrarum.utils.JsonFetcher
import net.torvald.tsvm.TsvmEmulator
/**
* Created by minjaesong on 2021-09-19.
*/
object WriteConfig {
private val jsoner = Json(JsonWriter.OutputType.json)
init {
jsoner.ignoreUnknownFields = true
jsoner.setUsePrototypes(false)
jsoner.setIgnoreDeprecated(false)
// KVHashMap
jsoner.setSerializer(KVHashMap::class.java, object : Json.Serializer<KVHashMap> {
override fun write(json: Json, obj: KVHashMap, knownType: Class<*>?) {
json.writeObjectStart()
obj.hashMap.toSortedMap().forEach { (k, v) ->
json.writeValue(k, v)
}
json.writeObjectEnd()
}
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): KVHashMap {
val map = KVHashMap()
JsonFetcher.forEachSiblings(jsonData) { key, obj ->
map[key] = json.readValue(null, obj)
}
return map
}
})
}
/*fun getJson(): String {
val sb = StringBuilder()
App.gameConfig.hashMap.toSortedMap().forEach { (k, v) ->
sb.append("$k:")
when (v) {
is DoubleArray -> { sb.append("[${v.joinToString(",")}]") }
is IntArray -> { sb.append("[${v.joinToString(",")}]") }
is Array<*> -> { sb.append("[${v.joinToString(",")}]") }
else -> { sb.append("$v") }
}
sb.append("\n")
}
return "{\n$sb}"
}*/
operator fun invoke() {
val writer = java.io.FileWriter(TsvmEmulator.configDir, false)
//writer.write(getJson())
writer.write(jsoner.prettyPrint(TsvmEmulator.gameConfig))
writer.close()
}
}

View File

@@ -0,0 +1,15 @@
package net.torvald.tsvm
/**
* Created by minjaesong on 2022-11-02.
*/
object DefaultConfig {
val hashMap = hashMapOf<String, Any>(
"viewport_width" to 640,
"viewport_height" to 480,
"viewports_rows" to 2,
"viewports_cols" to 3
)
}

View File

@@ -1,5 +1,7 @@
package net.torvald.tsvm
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input.Buttons
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.tsvm.VMEmuExecutableWrapper.Companion.FONT
@@ -32,12 +34,33 @@ class ProfilesMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : Em
}
override fun update() {
if (Gdx.input.isButtonPressed(Buttons.LEFT)) {
val mx = Gdx.input.x - x
val my = Gdx.input.y - y
if (mx in 10 until 10+228) {
if (my in 11 until 11+446) {
val li = (my - 11) / (2*FONT.H)
if (li < profileNames.size - profilesScroll)
selectedProfileIndex = li + profilesScroll
else
selectedProfileIndex = null
}
}
}
}
override fun render(batch: SpriteBatch) {
batch.inUse {
batch.color = EmulatorGuiToolkit.Theme.COL_WELL
it.fillRect(10, 11, 228, 446)
// draw list of installed profiles
for (i in 0 until PROFILES_ROWS) {
batch.color = if (i % 2 == 0) EmulatorGuiToolkit.Theme.COL_WELL
else EmulatorGuiToolkit.Theme.COL_WELL2
batch.fillRect(10, 11 + i*2*FONT.H, 228, 2*FONT.H)
}
for (i in 0 until Math.min(PROFILES_ROWS, profileNames.size)) {
val index = profilesScroll + i
@@ -47,6 +70,8 @@ class ProfilesMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : Em
else EmulatorGuiToolkit.Theme.COL_WELL2
val colFore = if (index == selectedProfileIndex) EmulatorGuiToolkit.Theme.COL_ACTIVE
else EmulatorGuiToolkit.Theme.COL_ACTIVE2
val colFore2 = if (index == selectedProfileIndex) EmulatorGuiToolkit.Theme.COL_ACTIVE3
else EmulatorGuiToolkit.Theme.COL_INACTIVE3
val theVM = parent.getVMbyProfileName(profileNames[index])
val isVMrunning = if (theVM != null) !theVM.disposed && theVM.startTime >= 0 else false
@@ -56,14 +81,43 @@ class ProfilesMenu(parent: VMEmuExecutable, x: Int, y: Int, w: Int, h: Int) : Em
val vmViewportText = if (vmViewport != null) "on viewport #${vmViewport+1}" else "and hidden"
batch.color = colBack
it.fillRect(10, 11 + i*2*FONT.H, 228, 26)
batch.fillRect(10, 11 + i*2*FONT.H, 228, 2*FONT.H)
batch.color = colFore
FONT.draw(batch, profileNames[index], 12f, 11f + i*2*FONT.H)
batch.color = EmulatorGuiToolkit.Theme.COL_ACTIVE3
batch.color = colFore2
FONT.draw(batch, "$vmRunStatusText $vmViewportText", 12f, 11f+FONT.H + i*2*FONT.H)
}
// draw profile detals view
batch.color = EmulatorGuiToolkit.Theme.COL_WELL2
batch.fillRect(251, 11, 375, 403)
batch.fillRect(251, 427, 375, 26)
if (selectedProfileIndex != null) profileNames[selectedProfileIndex!!].let { profileName ->
val profile = parent.profiles[profileName]!!
val ramsize = profile.getLong("ramsize")
val cardslots = profile.getInt("cardslots")
val roms = profile.get("roms").iterator().map { it }
val extraRomCount = roms.size - 1
val coms = (1..4).map { profile.get("com$it")?.getString("cls") } // full classname of the COM device
val cards = (1 until cardslots).map { profile.get("card$it")?.getString("cls") } // full classname of the cards
batch.color = Color.WHITE
FONT.draw(batch, "Memory: $ramsize bytes", 253f, 11f)
FONT.draw(batch, "Card Slots: $cardslots", 253f, 11f + 1*FONT.H)
FONT.draw(batch, "Extra ROMs: ${if (extraRomCount == 0) "none" else extraRomCount}", 253f, 11f + 2*FONT.H)
FONT.draw(batch, "COM Ports:", 253f, 11f + 4*FONT.H)
for (i in 1..4) {
FONT.draw(batch, "$i) ${coms[i-1]?.let { it.substring(it.lastIndexOf('.')+1) } ?: ""}", 253f, 11f + (4+i)*FONT.H)
}
FONT.draw(batch, "Peripherals (cards):", 253f, 11f + 10*FONT.H)
FONT.draw(batch, "1) [Emulated Reference Graphics Adapter]", 253f, 11f + 11*FONT.H)
for (i in 2 until cardslots) {
FONT.draw(batch, "$i) ${cards[i-1]?.let { it.substring(it.lastIndexOf('.')+1) } ?: ""}", 253f, 11f + (10+i)*FONT.H)
}
}
}
}

View File

@@ -5,6 +5,15 @@ import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.JsonValue;
import net.torvald.terrarum.KVHashMap;
import net.torvald.terrarum.serialise.WriteConfig;
import net.torvald.terrarum.utils.JsonFetcher;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Created by minjaesong on 2022-10-22.
@@ -15,14 +24,64 @@ public class TsvmEmulator {
public static String appTitle = "tsvm";
public static Lwjgl3ApplicationConfiguration appConfig;
public static int WIDTH = 640 * 2;
public static int HEIGHT = 480 * 2;
public static int PANELS_X = 2;
public static int PANELS_Y = 2;
public static int VIEWPORT_W = 640;
public static int VIEWPORT_H = 480;
public static int WIDTH = VIEWPORT_W * PANELS_X;
public static int HEIGHT = VIEWPORT_H * PANELS_Y;
public static String OSName = System.getProperty("os.name");
public static String OSVersion = System.getProperty("os.version");
public static String operationSystem;
/** %appdata%/Terrarum, without trailing slash */
public static String defaultDir;
/** defaultDir + "/config.json" */
public static String configDir;
/** defaultDir + "/profiles.json" */
public static String profilesDir;
public static KVHashMap gameConfig = new KVHashMap();
public static void main(String[] args) {
ShaderProgram.pedantic = false;
getDefaultDirectory();
// initialise the game config
for (Map.Entry<String, Object> entry : DefaultConfig.INSTANCE.getHashMap().entrySet()) {
gameConfig.set(entry.getKey(), entry.getValue());
}
// actually read the config.json
try {
// read from disk and build config from it
JsonValue map = JsonFetcher.INSTANCE.invoke(configDir);
// make config
for (JsonValue entry = map.child; entry != null; entry = entry.next) {
setToGameConfigForced(entry, null);
}
}
catch (IOException e) {
// write default config to game dir. Call th.is method again to read config from it.
try {
createConfigJson();
}
catch (IOException e1) {
System.out.println("[AppLoader] Unable to write config.json file");
e.printStackTrace();
}
}
PANELS_X = Math.max(2, getConfigInt("viewports_cols"));
PANELS_Y = Math.max(2, getConfigInt("viewports_rows"));
VIEWPORT_W = Math.max(560, getConfigInt("viewport_width"));
VIEWPORT_H = Math.max(448, getConfigInt("viewport_height"));
WIDTH = VIEWPORT_W * PANELS_X;
HEIGHT = VIEWPORT_H * PANELS_Y;
appConfig = new Lwjgl3ApplicationConfiguration();
appConfig.setIdleFPS(60);
appConfig.setForegroundFPS(60);
@@ -32,7 +91,224 @@ public class TsvmEmulator {
appConfig.setWindowedMode(WIDTH, HEIGHT);
new Lwjgl3Application(new VMEmuExecutableWrapper(640, 480, 2, 2,"assets/"), appConfig);
new Lwjgl3Application(new VMEmuExecutableWrapper(VIEWPORT_W, VIEWPORT_H, PANELS_X, PANELS_Y,"assets/"), appConfig);
}
private static void getDefaultDirectory() {
String OS = OSName.toUpperCase();
if (OS.contains("WIN")) {
operationSystem = "WINDOWS";
defaultDir = System.getenv("APPDATA") + "/tsvmdevenv";
}
else if (OS.contains("OS X") || OS.contains("MACOS")) { // OpenJDK for mac will still report "Mac OS X" with version number "10.16", even on Big Sur and beyond
operationSystem = "OSX";
defaultDir = System.getProperty("user.home") + "/Library/Application Support/tsvmdevenv";
}
else if (OS.contains("NUX") || OS.contains("NIX") || OS.contains("BSD")) {
operationSystem = "LINUX";
defaultDir = System.getProperty("user.home") + "/.tsvmdevenv";
}
else if (OS.contains("SUNOS")) {
operationSystem = "SOLARIS";
defaultDir = System.getProperty("user.home") + "/.tsvmdevenv";
}
else {
operationSystem = "UNKNOWN";
defaultDir = System.getProperty("user.home") + "/.tsvmdevenv";
}
configDir = defaultDir + "/config.json";
profilesDir = defaultDir + "/profiles.json";
System.out.println(String.format("os.name = %s (with identifier %s)", OSName, operationSystem));
System.out.println(String.format("os.version = %s", OSVersion));
System.out.println(String.format("default directory: %s", defaultDir));
System.out.println(String.format("java version = %s", System.getProperty("java.version")));
}
private static void createConfigJson() throws IOException {
File configFile = new File(configDir);
if (!configFile.exists() || configFile.length() == 0L) {
WriteConfig.INSTANCE.invoke();
}
}
/**
* Will forcibly overwrite previously loaded config value.
*
* Key naming convention will be 'modName:propertyName'; if modName is null, the key will be just propertyName.
*
* @param value JsonValue (the key-value pair)
* @param modName module name, nullable
*/
public static void setToGameConfigForced(JsonValue value, String modName) {
gameConfig.set((modName == null) ? value.name : modName+":"+value.name,
value.isArray() ? value.asDoubleArray() :
value.isDouble() ? value.asDouble() :
value.isBoolean() ? value.asBoolean() :
value.isLong() ? value.asInt() :
value.asString()
);
}
/**
* Will not overwrite previously loaded config value.
*
* Key naming convention will be 'modName:propertyName'; if modName is null, the key will be just propertyName.
*
* @param value JsonValue (the key-value pair)
* @param modName module name, nullable
*/
public static void setToGameConfig(JsonValue value, String modName) {
String key = (modName == null) ? value.name : modName+":"+value.name;
if (gameConfig.get(key) == null) {
gameConfig.set(key,
value.isArray() ? value.asDoubleArray() :
value.isDouble() ? value.asDouble() :
value.isBoolean() ? value.asBoolean() :
value.isLong() ? value.asInt() :
value.asString()
);
}
}
/**
*
* @return true on successful, false on failure.
*/
private static Boolean readConfigJson() {
try {
// read from disk and build config from it
JsonValue map = JsonFetcher.INSTANCE.invoke(configDir);
// make config
for (JsonValue entry = map.child; entry != null; entry = entry.next) {
setToGameConfigForced(entry, null);
}
return true;
}
catch (IOException e) {
// write default config to game dir. Call th.is method again to read config from it.
try {
createConfigJson();
}
catch (IOException e1) {
System.out.println("[AppLoader] Unable to write config.json file");
e.printStackTrace();
}
return false;
}
}
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist.
* *
* @throws NullPointerException if the specified config simply does not exist.
*/
public static int getConfigInt(String key) {
Object cfg = getConfigMaster(key);
if (cfg instanceof Integer) return ((int) cfg);
double value = (double) cfg;
if (Math.abs(value % 1.0) < 0.00000001)
return (int) Math.round(value);
return ((int) cfg);
}
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist.
* *
* @throws NullPointerException if the specified config simply does not exist.
*/
public static double getConfigDouble(String key) {
Object cfg = getConfigMaster(key);
return (cfg instanceof Integer) ? (((Integer) cfg) * 1.0) : ((double) (cfg));
}
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist.
* *
* @throws NullPointerException if the specified config simply does not exist.
*/
public static String getConfigString(String key) {
Object cfg = getConfigMaster(key);
return ((String) cfg);
}
/**
* Return config from config set. If the config does not exist, default value will be returned.
* @param key
* *
* @return Config from config set or default config if it does not exist. If the default value is undefined, will return false.
*/
public static boolean getConfigBoolean(String key) {
try {
Object cfg = getConfigMaster(key);
return ((boolean) cfg);
}
catch (NullPointerException keyNotFound) {
return false;
}
}
/**
* Get config from config file. If the entry does not exist, get from defaults; if the entry is not in the default, NullPointerException will be thrown
*/
private static HashMap<String, Object> getDefaultConfig() {
return DefaultConfig.INSTANCE.getHashMap();
}
private static Object getConfigMaster(String key1) {
String key = key1.toLowerCase();
Object config;
try {
config = gameConfig.get(key);
}
catch (NullPointerException e) {
config = null;
}
Object defaults;
try {
defaults = getDefaultConfig().get(key);
}
catch (NullPointerException e) {
defaults = null;
}
if (config == null) {
if (defaults == null) {
throw new NullPointerException("key not found: '" + key + "'");
}
else {
return defaults;
}
}
else {
return config;
}
}
public static void setConfig(String key, Object value) {
gameConfig.set(key.toLowerCase(), value);
}
}

View File

@@ -5,7 +5,6 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonReader
import com.badlogic.gdx.utils.JsonValue
import com.badlogic.gdx.utils.JsonWriter
@@ -79,14 +78,10 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
var coroutineJobs = HashMap<Int, Job>() // <VM's identifier, Job>
companion object {
val APPDATADIR = System.getProperty("os.name").toUpperCase().let {
if (it.contains("WIN")) System.getenv("APPDATA") + "/tsvmdevenv"
else if (it.contains("OS X") || it.contains("MACOS")) System.getProperty("user.home") + "/Library/Application Support/tsvmdevenv"
else System.getProperty("user.home") + "/.tsvmdevenv"
}
val APPDATADIR = TsvmEmulator.defaultDir
val FILE_CONFIG = Gdx.files.absolute("$APPDATADIR/config.json")
val FILE_PROFILES = Gdx.files.absolute("$APPDATADIR/profiles.json")
val FILE_CONFIG = Gdx.files.absolute(TsvmEmulator.configDir)
val FILE_PROFILES = Gdx.files.absolute(TsvmEmulator.profilesDir)
}
val fullscreenQuad = Mesh(
@@ -571,9 +566,10 @@ object EmulatorGuiToolkit {
object Theme {
val COL_INACTIVE = Color(0x858585ff.toInt())
val COL_INACTIVE2 = Color(0x5a5a5fff.toInt())
val COL_INACTIVE3 = Color.WHITE
val COL_ACTIVE = Color(0x23ff00ff.toInt()) // neon green
val COL_ACTIVE2 = Color(0xfff600ff.toInt()) // yellow
val COL_ACTIVE3 = Color.WHITE
val COL_ACTIVE3 = Color(0x5ff8ffff.toInt()) // cyan
val COL_HIGHLIGHT = Color(0xe43380ff.toInt()) // magenta
val COL_DISABLED = Color(0xaaaaaaff.toInt())