mirror of
https://github.com/irmen/ksim65.git
synced 2024-11-10 08:05:27 +00:00
replaced fixed 8x16 bitmap font by psf font
This commit is contained in:
parent
3925c80258
commit
d1d433c3a6
36
README.md
36
README.md
@ -6,9 +6,6 @@ JCenter: [![Download from Jcenter](https://api.bintray.com/packages/irmen/maven/
|
||||
|
||||
*Written by Irmen de Jong (irmen@razorvine.net)*
|
||||
|
||||
*Software license: MIT, see file LICENSE*
|
||||
|
||||
|
||||
![6502](https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/KL_MOS_6502.jpg/320px-KL_MOS_6502.jpg)
|
||||
|
||||
This is a Kotlin/JVM library that simulates the 8-bit 6502 and 65C02 microprocessors,
|
||||
@ -67,3 +64,36 @@ various timers and IRQs. It's not cycle perfect, and the video display is drawn
|
||||
so raster splits/rasterbars are impossible. But many other things work fine.
|
||||
|
||||
![C64 emulation](c64.png)
|
||||
|
||||
|
||||
### License information
|
||||
|
||||
Ksim65 itself is licensed under the MIT software license, see file LICENSE.
|
||||
|
||||
It includes the 'Spleen' bitmap font (https://github.com/fcambus/spleen),
|
||||
which has the following license (BSD):
|
||||
|
||||
Copyright (c) 2018-2020, Frederic Cambus
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
|
||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
@ -1,57 +1,27 @@
|
||||
package razorvine.examplemachines
|
||||
|
||||
import razorvine.fonts.PsfFont
|
||||
import razorvine.ksim65.*
|
||||
import java.awt.*
|
||||
import java.awt.event.*
|
||||
import java.awt.image.BufferedImage
|
||||
import java.util.*
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.*
|
||||
import javax.swing.event.MouseInputListener
|
||||
|
||||
|
||||
/**
|
||||
* Define a monochrome screen that can display 640x480 pixels
|
||||
* and/or 80x30 characters (these are 8x16 pixels).
|
||||
* Define a monochrome screen that can display 80x30 charaacters
|
||||
* (usually equivalent to 640x480 pixels, but depends on the font size)
|
||||
*/
|
||||
object ScreenDefs {
|
||||
const val SCREEN_WIDTH_CHARS = 80
|
||||
const val SCREEN_HEIGHT_CHARS = 30
|
||||
const val SCREEN_WIDTH = SCREEN_WIDTH_CHARS*8
|
||||
const val SCREEN_HEIGHT = SCREEN_HEIGHT_CHARS*16
|
||||
const val PIXEL_SCALING = 1.5
|
||||
const val COLUMNS = 80
|
||||
const val ROWS = 30
|
||||
const val BORDER_SIZE = 32
|
||||
|
||||
val BG_COLOR = Color(0, 10, 20)
|
||||
val FG_COLOR = Color(200, 255, 230)
|
||||
val BORDER_COLOR = Color(20, 30, 40)
|
||||
val Characters = loadCharacters()
|
||||
|
||||
private fun loadCharacters(): Array<BufferedImage> {
|
||||
val img = ImageIO.read(javaClass.getResourceAsStream("/charset/unscii8x16.png"))
|
||||
val charactersImage = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
|
||||
charactersImage.createGraphics().drawImage(img, 0, 0, null)
|
||||
|
||||
val black = Color(0, 0, 0).rgb
|
||||
val foreground = FG_COLOR.rgb
|
||||
val nopixel = Color(0, 0, 0, 0).rgb
|
||||
for (y in 0 until charactersImage.height) {
|
||||
for (x in 0 until charactersImage.width) {
|
||||
val col = charactersImage.getRGB(x, y)
|
||||
if (col == black) charactersImage.setRGB(x, y, nopixel)
|
||||
else charactersImage.setRGB(x, y, foreground)
|
||||
}
|
||||
}
|
||||
|
||||
val numColumns = charactersImage.width/8
|
||||
val charImages = (0..255).map {
|
||||
val charX = it%numColumns
|
||||
val charY = it/numColumns
|
||||
charactersImage.getSubimage(charX*8, charY*16, 8, 16)
|
||||
}
|
||||
|
||||
return charImages.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
private class BitmapScreenPanel : JPanel() {
|
||||
@ -61,15 +31,21 @@ private class BitmapScreenPanel : JPanel() {
|
||||
private var cursorX: Int = 0
|
||||
private var cursorY: Int = 0
|
||||
private var cursorState: Boolean = false
|
||||
private val screenFont = PsfFont("spleen-12x24") // nice fonts: sun12x22, iso01-12x22, ter-124b, spleen-12x24, default8x16
|
||||
private val PIXEL_SCALING: Double = if(screenFont.width <= 8) 1.5 else 1.0
|
||||
private val screenFontImage: BufferedImage
|
||||
|
||||
init {
|
||||
println("SCREENFONT WIDTH: ${screenFont.width}")
|
||||
|
||||
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
||||
val gd = ge.defaultScreenDevice.defaultConfiguration
|
||||
image = gd.createCompatibleImage(ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT, Transparency.OPAQUE)
|
||||
image = gd.createCompatibleImage(ScreenDefs.COLUMNS*screenFont.width, ScreenDefs.ROWS*screenFont.height, Transparency.OPAQUE)
|
||||
g2d = image.graphics as Graphics2D
|
||||
screenFontImage = screenFont.convertToImage(g2d, ScreenDefs.FG_COLOR)
|
||||
|
||||
val size = Dimension((image.width*ScreenDefs.PIXEL_SCALING).toInt(),
|
||||
(image.height*ScreenDefs.PIXEL_SCALING).toInt())
|
||||
val size = Dimension((image.width*PIXEL_SCALING).toInt(),
|
||||
(image.height*PIXEL_SCALING).toInt())
|
||||
minimumSize = size
|
||||
maximumSize = size
|
||||
preferredSize = size
|
||||
@ -82,13 +58,13 @@ private class BitmapScreenPanel : JPanel() {
|
||||
override fun paint(graphics: Graphics) {
|
||||
val g2d = graphics as Graphics2D
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
|
||||
g2d.drawImage(image, 0, 0, (image.width*ScreenDefs.PIXEL_SCALING).toInt(),
|
||||
(image.height*ScreenDefs.PIXEL_SCALING).toInt(), null)
|
||||
g2d.drawImage(image, 0, 0, (image.width*PIXEL_SCALING).toInt(),
|
||||
(image.height*PIXEL_SCALING).toInt(), null)
|
||||
if (cursorState) {
|
||||
val scx = (cursorX*ScreenDefs.PIXEL_SCALING*8).toInt()
|
||||
val scy = (cursorY*ScreenDefs.PIXEL_SCALING*16).toInt()
|
||||
val scw = (8*ScreenDefs.PIXEL_SCALING).toInt()
|
||||
val sch = (16*ScreenDefs.PIXEL_SCALING).toInt()
|
||||
val scx = (cursorX*PIXEL_SCALING*screenFont.width).toInt()
|
||||
val scy = (cursorY*PIXEL_SCALING*screenFont.height).toInt()
|
||||
val scw = (screenFont.width*PIXEL_SCALING).toInt()
|
||||
val sch = (screenFont.height*PIXEL_SCALING).toInt()
|
||||
g2d.setXORMode(Color.CYAN)
|
||||
g2d.fillRect(scx, scy, scw, sch)
|
||||
g2d.setPaintMode()
|
||||
@ -98,7 +74,7 @@ private class BitmapScreenPanel : JPanel() {
|
||||
|
||||
fun clearScreen() {
|
||||
g2d.background = ScreenDefs.BG_COLOR
|
||||
g2d.clearRect(0, 0, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT)
|
||||
g2d.clearRect(0, 0, ScreenDefs.COLUMNS*screenFont.width, ScreenDefs.ROWS*screenFont.height)
|
||||
cursorPos(0, 0)
|
||||
}
|
||||
|
||||
@ -110,20 +86,26 @@ private class BitmapScreenPanel : JPanel() {
|
||||
fun getPixel(x: Int, y: Int) = image.getRGB(x, y) != ScreenDefs.BG_COLOR.rgb
|
||||
|
||||
fun setChar(x: Int, y: Int, character: Char) {
|
||||
g2d.clearRect(8*x, 16*y, 8, 16)
|
||||
val coloredImage = ScreenDefs.Characters[character.toInt()]
|
||||
g2d.drawImage(coloredImage, 8*x, 16*y, null)
|
||||
val charnum = character.toInt()
|
||||
val cx = charnum % (screenFontImage.width/screenFont.width)
|
||||
val cy = charnum / (screenFontImage.width/screenFont.width)
|
||||
g2d.clearRect(x*screenFont.width, y*screenFont.height, screenFont.width, screenFont.height)
|
||||
g2d.drawImage(screenFontImage, x*screenFont.width, y*screenFont.height, (x+1)*screenFont.width,
|
||||
(y+1)*screenFont.height, cx*screenFont.width, cy*screenFont.height,
|
||||
(cx+1)*screenFont.width, (cy+1)*screenFont.height, null)
|
||||
}
|
||||
|
||||
fun scrollUp() {
|
||||
g2d.copyArea(0, 16, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT-16, 0, -16)
|
||||
g2d.copyArea(0, screenFont.height,
|
||||
ScreenDefs.COLUMNS*screenFont.width, (ScreenDefs.ROWS-1)*screenFont.height,
|
||||
0, -screenFont.height)
|
||||
g2d.background = ScreenDefs.BG_COLOR
|
||||
g2d.clearRect(0, ScreenDefs.SCREEN_HEIGHT-16, ScreenDefs.SCREEN_WIDTH, 16)
|
||||
g2d.clearRect(0, (ScreenDefs.ROWS-1)*screenFont.height, ScreenDefs.COLUMNS*screenFont.width, screenFont.height)
|
||||
}
|
||||
|
||||
fun mousePixelPosition(): Point? {
|
||||
val pos = mousePosition ?: return null
|
||||
return Point((pos.x/ScreenDefs.PIXEL_SCALING).toInt(), (pos.y/ScreenDefs.PIXEL_SCALING).toInt())
|
||||
return Point((pos.x/PIXEL_SCALING).toInt(), (pos.y/PIXEL_SCALING).toInt())
|
||||
}
|
||||
|
||||
fun cursorPos(x: Int, y: Int) {
|
||||
|
@ -21,7 +21,7 @@ class EhBasicMachine(title: String) {
|
||||
val rom = Rom(0xc000, 0xffff).also { it.load(javaClass.getResourceAsStream("/ehbasic_C000.bin").readBytes()) }
|
||||
|
||||
private val hostDisplay = MainWindow(title)
|
||||
private val display = Display(0xd000, 0xd00a, hostDisplay, ScreenDefs.SCREEN_WIDTH_CHARS, ScreenDefs.SCREEN_HEIGHT_CHARS)
|
||||
private val display = Display(0xd000, 0xd00a, hostDisplay, ScreenDefs.COLUMNS, ScreenDefs.ROWS)
|
||||
private val keyboard = Keyboard(0xd400, 0xd400, hostDisplay)
|
||||
private var paused = false
|
||||
|
||||
|
@ -20,7 +20,7 @@ class VirtualMachine(title: String) : IVirtualMachine {
|
||||
private val monitor = Monitor(bus, cpu)
|
||||
private val debugWindow = DebugWindow(this)
|
||||
private val hostDisplay = MainWindow(title)
|
||||
private val display = Display(0xd000, 0xd00a, hostDisplay, ScreenDefs.SCREEN_WIDTH_CHARS, ScreenDefs.SCREEN_HEIGHT_CHARS)
|
||||
private val display = Display(0xd000, 0xd00a, hostDisplay, ScreenDefs.COLUMNS, ScreenDefs.ROWS)
|
||||
private val mouse = Mouse(0xd300, 0xd305, hostDisplay)
|
||||
private val keyboard = Keyboard(0xd400, 0xd400, hostDisplay)
|
||||
private var paused = false
|
||||
|
138
src/main/kotlin/razorvine/fonts/PsfFont.kt
Normal file
138
src/main/kotlin/razorvine/fonts/PsfFont.kt
Normal file
@ -0,0 +1,138 @@
|
||||
package razorvine.fonts
|
||||
|
||||
import java.awt.Color
|
||||
import java.awt.Graphics2D
|
||||
import java.awt.Transparency
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.IOException
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
|
||||
class PsfFont(name: String) {
|
||||
|
||||
// font format info: https://www.win.tue.nl/~aeb/linux/kbd/font-formats-1.html
|
||||
|
||||
val numChars: Int
|
||||
val bytesPerChar: Int
|
||||
val height: Int
|
||||
val width: Int
|
||||
private val hasUnicodeTable: Boolean
|
||||
private val rawBitmaps: List<ByteArray>
|
||||
|
||||
init {
|
||||
var data = ByteArray(0)
|
||||
val fontsDirectory = "/usr/share/kbd/consolefonts"
|
||||
var stream = javaClass.getResourceAsStream("/charset/$name.psfu.gz") ?:
|
||||
javaClass.getResourceAsStream("/charset/$name.psf.gz") ?:
|
||||
(if(File("$fontsDirectory/$name.psfu.gz").exists()) FileInputStream("$fontsDirectory/$name.psfu.gz") else null ) ?:
|
||||
(if(File("$fontsDirectory/$name.psf.gz").exists()) FileInputStream("$fontsDirectory/$name.psf.gz") else null ) ?:
|
||||
(if(File("$fontsDirectory/$name.fnt.gz").exists()) FileInputStream("$fontsDirectory/$name.fnt.gz") else null )
|
||||
if(stream==null) {
|
||||
stream = javaClass.getResourceAsStream("/charset/$name.psfu") ?:
|
||||
javaClass.getResourceAsStream("/charset/$name.psf") ?:
|
||||
(if(File("$fontsDirectory/$name.psfu").exists()) FileInputStream("$fontsDirectory/$name.psfu") else null ) ?:
|
||||
(if(File("$fontsDirectory/$name.psf").exists()) FileInputStream("$fontsDirectory/$name.psf") else null ) ?:
|
||||
(if(File("$fontsDirectory/$name.fnt").exists()) FileInputStream("$fontsDirectory/$name.fnt") else null ) ?:
|
||||
throw IOException("no such font: $name")
|
||||
data = stream.readBytes()
|
||||
} else {
|
||||
GZIPInputStream(stream).use { data = it.readBytes() }
|
||||
}
|
||||
stream.close()
|
||||
|
||||
if (data[0] == 0x36.toByte() && data[1] == 0x04.toByte()) {
|
||||
// continue reading PSF1 font
|
||||
val mode = data[2].toInt()
|
||||
numChars = if (mode and 1 != 0) 512 else 256
|
||||
bytesPerChar = data[3].toInt()
|
||||
hasUnicodeTable = mode and 2 != 0
|
||||
height = bytesPerChar
|
||||
width = 8
|
||||
rawBitmaps = (0..numChars).map {
|
||||
data.sliceArray(3+it*bytesPerChar..3+(it+1)*bytesPerChar)
|
||||
}
|
||||
// ignore unicode table for now: val table = stream.readAllBytes()
|
||||
} else {
|
||||
if (data[0] == 0x72.toByte() && data[1] == 0xb5.toByte() && data[2] == 0x4a.toByte() && data[3] == 0x86.toByte()) {
|
||||
// continue reading PSF2 font
|
||||
// skip the version val version = makeInt(data, 4)
|
||||
val headersize = makeInt(data, 8)
|
||||
val flags = makeInt(data, 12)
|
||||
hasUnicodeTable = flags and 1 != 0
|
||||
numChars = makeInt(data, 16)
|
||||
bytesPerChar = makeInt(data, 20)
|
||||
height = makeInt(data, 24)
|
||||
width = makeInt(data, 28)
|
||||
rawBitmaps = (0..numChars).map {
|
||||
data.sliceArray(headersize+it*bytesPerChar..headersize+(it+1)*bytesPerChar)
|
||||
}
|
||||
} else {
|
||||
hasUnicodeTable = false
|
||||
numChars = 0
|
||||
bytesPerChar = 0
|
||||
height = 0
|
||||
width = 0
|
||||
rawBitmaps = emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun convertToImage(gfx: Graphics2D, textColor: Color): BufferedImage {
|
||||
// create a single image with all the characters in a vertical column from top to bottom.
|
||||
val bitmap = gfx.deviceConfiguration.createCompatibleImage((width+7) and 0b11111000, height*numChars, Transparency.BITMASK)
|
||||
val bytesHoriz = (width+7)/8
|
||||
val color = textColor.rgb
|
||||
val nopixel = Color(0, 0, 0, 0).rgb
|
||||
for (char in 0 until numChars) {
|
||||
for (b in 0 until bytesPerChar) {
|
||||
val c = rawBitmaps[char][b].toInt()
|
||||
val ix = 8*(b%bytesHoriz)
|
||||
val iy = b/bytesHoriz+char*height
|
||||
bitmap.setRGB(ix, iy, if (c and 0b10000000 != 0) color else nopixel)
|
||||
bitmap.setRGB(ix+1, iy, if (c and 0b01000000 != 0) color else nopixel)
|
||||
bitmap.setRGB(ix+2, iy, if (c and 0b00100000 != 0) color else nopixel)
|
||||
bitmap.setRGB(ix+3, iy, if (c and 0b00010000 != 0) color else nopixel)
|
||||
bitmap.setRGB(ix+4, iy, if (c and 0b00001000 != 0) color else nopixel)
|
||||
bitmap.setRGB(ix+5, iy, if (c and 0b00000100 != 0) color else nopixel)
|
||||
bitmap.setRGB(ix+6, iy, if (c and 0b00000010 != 0) color else nopixel)
|
||||
bitmap.setRGB(ix+7, iy, if (c and 0b00000001 != 0) color else nopixel)
|
||||
}
|
||||
}
|
||||
return bitmap
|
||||
}
|
||||
|
||||
private fun makeInt(bytes: ByteArray, offset: Int) =
|
||||
makeInt(bytes[offset], bytes[offset+1], bytes[offset+2], bytes[offset+3])
|
||||
private fun makeInt(b0: Byte, b1: Byte, b2: Byte, b3: Byte) =
|
||||
b0.toInt() or (b1.toInt() shl 8) or (b2.toInt() shl 16) or (b3.toInt() shl 24)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// private fun loadFallbackCharacters(): Array<BufferedImage> {
|
||||
// val img = ImageIO.read(javaClass.getResourceAsStream("/charset/unscii8x16.png"))
|
||||
// val charactersImage = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
|
||||
// charactersImage.createGraphics().drawImage(img, 0, 0, null)
|
||||
//
|
||||
// val black = Color(0, 0, 0).rgb
|
||||
// val foreground = FG_COLOR.rgb
|
||||
// val nopixel = Color(0, 0, 0, 0).rgb
|
||||
// for (y in 0 until charactersImage.height) {
|
||||
// for (x in 0 until charactersImage.width) {
|
||||
// val col = charactersImage.getRGB(x, y)
|
||||
// if (col == black) charactersImage.setRGB(x, y, nopixel)
|
||||
// else charactersImage.setRGB(x, y, foreground)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// val numColumns = charactersImage.width/8
|
||||
// val charImages = (0..255).map {
|
||||
// val charX = it%numColumns
|
||||
// val charY = it/numColumns
|
||||
// charactersImage.getSubimage(charX*8, charY*16, 8, 16)
|
||||
// }
|
||||
//
|
||||
// return charImages.toTypedArray()
|
||||
// }
|
@ -102,7 +102,7 @@ class Display(startAddress: Address, endAddress: Address, private val host: IHos
|
||||
0x05 -> pixelY = (pixelY and 0xff00) or data.toInt()
|
||||
0x06 -> pixelY = (pixelY and 0x00ff) or (data.toInt() shl 8)
|
||||
0x07 -> {
|
||||
if (pixelX in 0 until ScreenDefs.SCREEN_WIDTH && pixelY in 0 until ScreenDefs.SCREEN_HEIGHT) {
|
||||
if (pixelX in 0 until ScreenDefs.COLUMNS*charWidth && pixelY in 0 until ScreenDefs.ROWS*charHeight) {
|
||||
if (data == 0.toShort()) host.clearPixel(pixelX, pixelY)
|
||||
else host.setPixel(pixelX, pixelY)
|
||||
}
|
||||
|
BIN
src/main/resources/charset/spleen-12x24.psfu.gz
Normal file
BIN
src/main/resources/charset/spleen-12x24.psfu.gz
Normal file
Binary file not shown.
BIN
src/main/resources/charset/spleen-16x32.psfu.gz
Normal file
BIN
src/main/resources/charset/spleen-16x32.psfu.gz
Normal file
Binary file not shown.
BIN
src/main/resources/charset/spleen-8x16.psfu.gz
Normal file
BIN
src/main/resources/charset/spleen-8x16.psfu.gz
Normal file
Binary file not shown.
24
src/main/resources/charset/spleen-LICENSE
Normal file
24
src/main/resources/charset/spleen-LICENSE
Normal file
@ -0,0 +1,24 @@
|
||||
Copyright (c) 2018-2020, Frederic Cambus
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
|
||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB |
Loading…
Reference in New Issue
Block a user